diff --git a/README.md b/README.md index 7b569217a8..8e616ad5e5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## Telegram messenger for Android -[Telegram](http://telegram.org) is a messaging app with a focus on speed and security. It’s superfast, simple and free. +[Telegram](https://telegram.org) is a messaging app with a focus on speed and security. It’s superfast, simple and free. This repo contains the official source code for [Telegram App for Android](https://play.google.com/store/apps/details?id=org.telegram.messenger). ##Creating your Telegram Application @@ -16,9 +16,9 @@ There are several things we require from **all developers** for the moment. ### API, Protocol documentation -Telegram API manuals: http://core.telegram.org/api +Telegram API manuals: https://core.telegram.org/api -MTproto protocol manuals: http://core.telegram.org/mtproto +MTproto protocol manuals: https://core.telegram.org/mtproto ### Usage diff --git a/TMessagesProj/build.gradle b/TMessagesProj/build.gradle index 34a38990db..bd3d3a7f41 100644 --- a/TMessagesProj/build.gradle +++ b/TMessagesProj/build.gradle @@ -5,7 +5,7 @@ repositories { } dependencies { - compile 'com.android.support:support-v4:23.1.+' + compile 'com.android.support:support-v4:23.2.1' compile "com.google.android.gms:play-services-gcm:8.4.0" compile "com.google.android.gms:play-services-maps:8.4.0" compile 'net.hockeyapp.android:HockeySDK:3.6.+' @@ -63,6 +63,8 @@ android { } } + defaultConfig.versionCode = 767 + sourceSets.main { jniLibs.srcDir 'libs' jni.srcDirs = [] //disable automatic ndk-build call @@ -80,10 +82,38 @@ android { manifest.srcFile 'config/foss/AndroidManifest.xml' } + productFlavors { + x86 { + ndk { + abiFilter "x86" + } + versionCode = 2 + } + arm { + ndk { + abiFilter "armeabi" + } + versionCode = 0 + } + armv7 { + ndk { + abiFilter "armeabi-v7a" + } + versionCode = 1 + } + fat { + versionCode = 3 + } + } + + applicationVariants.all { variant -> + def abiVersion = variant.productFlavors.get(0).versionCode + variant.mergedFlavor.versionCode = defaultConfig.versionCode * 10 + abiVersion; + } + defaultConfig { minSdkVersion 9 targetSdkVersion 23 - versionCode 755 - versionName "3.6.1" + versionName "3.7.0" } } diff --git a/TMessagesProj/jni/Android.mk b/TMessagesProj/jni/Android.mk index 995a53ee69..b783ad8ebd 100755 --- a/TMessagesProj/jni/Android.mk +++ b/TMessagesProj/jni/Android.mk @@ -235,7 +235,7 @@ include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_PRELINK_MODULE := false -LOCAL_MODULE := tmessages.19 +LOCAL_MODULE := tmessages.20 LOCAL_CFLAGS := -w -std=c11 -Os -DNULL=0 -DSOCKLEN_T=socklen_t -DLOCALE_NOT_USED -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 LOCAL_CFLAGS += -Drestrict='' -D__EMX__ -DOPUS_BUILD -DFIXED_POINT -DUSE_ALLOCA -DHAVE_LRINT -DHAVE_LRINTF -fno-math-errno LOCAL_CFLAGS += -DANDROID_NDK -DDISABLE_IMPORTGL -fno-strict-aliasing -fprefetch-loop-arrays -DAVOID_TABLES -DANDROID_TILE_BASED_DECODE -DANDROID_ARMV6_IDCT -ffast-math -D__STDC_CONSTANT_MACROS diff --git a/TMessagesProj/jni/Application.mk b/TMessagesProj/jni/Application.mk index 0460b33405..bd546a41a7 100644 --- a/TMessagesProj/jni/Application.mk +++ b/TMessagesProj/jni/Application.mk @@ -1,4 +1,4 @@ APP_PLATFORM := android-9 APP_ABI := armeabi armeabi-v7a x86 -NDK_TOOLCHAIN_VERSION := 4.8 +NDK_TOOLCHAIN_VERSION := 4.9 APP_STL := gnustl_static \ No newline at end of file diff --git a/TMessagesProj/jni/audio.c b/TMessagesProj/jni/audio.c index 87f946d4d2..c6fd3650ab 100644 --- a/TMessagesProj/jni/audio.c +++ b/TMessagesProj/jni/audio.c @@ -671,8 +671,7 @@ JNIEXPORT int Java_org_telegram_messenger_MediaController_isOpusFile(JNIEnv *env return result; } -static inline void set_bits(uint8_t *bytes, int32_t bitOffset, int32_t numBits, int32_t value) { - numBits = (unsigned int) (2 << (numBits - 1)) - 1; +static inline void set_bits(uint8_t *bytes, int32_t bitOffset, int32_t value) { bytes += bitOffset / 8; bitOffset %= 8; *((int32_t *) bytes) |= (value << bitOffset); @@ -727,7 +726,7 @@ JNIEXPORT jbyteArray Java_org_telegram_messenger_MediaController_getWaveform2(JN for (int i = 0; i < resultSamples; i++) { int32_t value = min(31, abs((int32_t) samples[i]) * 31 / peak); - set_bits(bytes, i * 5, 5, value & 31); + set_bits(bytes, i * 5, value & 31); } (*env)->ReleaseByteArrayElements(env, result, bytes, JNI_COMMIT); @@ -805,7 +804,7 @@ JNIEXPORT jbyteArray Java_org_telegram_messenger_MediaController_getWaveform(JNI for (int i = 0; i < resultSamples; i++) { int32_t value = min(31, abs((int32_t) samples[i]) * 31 / peak); - set_bits(bytes, i * 5, 5, value & 31); + set_bits(bytes, i * 5, value & 31); } (*env)->ReleaseByteArrayElements(env, result, bytes, JNI_COMMIT); diff --git a/TMessagesProj/jni/jni.c b/TMessagesProj/jni/jni.c index 2b17fd6171..711b91c903 100644 --- a/TMessagesProj/jni/jni.c +++ b/TMessagesProj/jni/jni.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "utils.h" #include "sqlite.h" #include "image.h" diff --git a/TMessagesProj/jni/sqlite_statement.c b/TMessagesProj/jni/sqlite_statement.c index 2fc4ef3373..4957f790b9 100755 --- a/TMessagesProj/jni/sqlite_statement.c +++ b/TMessagesProj/jni/sqlite_statement.c @@ -8,8 +8,8 @@ jint sqliteOnJNILoad(JavaVM *vm, void *reserved, JNIEnv *env) { return JNI_VERSION_1_6; } -int Java_org_telegram_SQLite_SQLitePreparedStatement_step(JNIEnv* env, jobject object, int statementHandle) { - sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; +int Java_org_telegram_SQLite_SQLitePreparedStatement_step(JNIEnv *env, jobject object, int statementHandle) { + sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; int errcode = sqlite3_step(handle); if (errcode == SQLITE_ROW) { @@ -23,7 +23,7 @@ int Java_org_telegram_SQLite_SQLitePreparedStatement_step(JNIEnv* env, jobject o } int Java_org_telegram_SQLite_SQLitePreparedStatement_prepare(JNIEnv *env, jobject object, int sqliteHandle, jstring sql) { - sqlite3* handle = (sqlite3 *)sqliteHandle; + sqlite3 *handle = (sqlite3 *) sqliteHandle; char const *sqlStr = (*env)->GetStringUTFChars(env, sql, 0); @@ -41,11 +41,11 @@ int Java_org_telegram_SQLite_SQLitePreparedStatement_prepare(JNIEnv *env, jobjec (*env)->ReleaseStringUTFChars(env, sql, sqlStr); } - return (int)stmt_handle; + return (int) stmt_handle; } void Java_org_telegram_SQLite_SQLitePreparedStatement_reset(JNIEnv *env, jobject object, int statementHandle) { - sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; + sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; int errcode = sqlite3_reset(handle); if (SQLITE_OK != errcode) { @@ -54,16 +54,11 @@ void Java_org_telegram_SQLite_SQLitePreparedStatement_reset(JNIEnv *env, jobject } void Java_org_telegram_SQLite_SQLitePreparedStatement_finalize(JNIEnv *env, jobject object, int statementHandle) { - sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; - - int errcode = sqlite3_finalize (handle); - if (SQLITE_OK != errcode) { - throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode); - } + sqlite3_finalize((sqlite3_stmt *) statementHandle); } void Java_org_telegram_SQLite_SQLitePreparedStatement_bindByteBuffer(JNIEnv *env, jobject object, int statementHandle, int index, jobject value, int length) { - sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle; + sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; jbyte *buf = (*env)->GetDirectBufferAddress(env, value); int errcode = sqlite3_bind_blob(handle, index, buf, length, SQLITE_STATIC); @@ -73,7 +68,7 @@ void Java_org_telegram_SQLite_SQLitePreparedStatement_bindByteBuffer(JNIEnv *env } void Java_org_telegram_SQLite_SQLitePreparedStatement_bindString(JNIEnv *env, jobject object, int statementHandle, int index, jstring value) { - sqlite3_stmt *handle = (sqlite3_stmt*)statementHandle; + sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; char const *valueStr = (*env)->GetStringUTFChars(env, value, 0); @@ -88,7 +83,7 @@ void Java_org_telegram_SQLite_SQLitePreparedStatement_bindString(JNIEnv *env, jo } void Java_org_telegram_SQLite_SQLitePreparedStatement_bindInt(JNIEnv *env, jobject object, int statementHandle, int index, int value) { - sqlite3_stmt *handle = (sqlite3_stmt*)statementHandle; + sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; int errcode = sqlite3_bind_int(handle, index, value); if (SQLITE_OK != errcode) { @@ -97,7 +92,7 @@ void Java_org_telegram_SQLite_SQLitePreparedStatement_bindInt(JNIEnv *env, jobje } void Java_org_telegram_SQLite_SQLitePreparedStatement_bindLong(JNIEnv *env, jobject object, int statementHandle, int index, long long value) { - sqlite3_stmt *handle = (sqlite3_stmt*)statementHandle; + sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; int errcode = sqlite3_bind_int64(handle, index, value); if (SQLITE_OK != errcode) { @@ -105,8 +100,8 @@ void Java_org_telegram_SQLite_SQLitePreparedStatement_bindLong(JNIEnv *env, jobj } } -void Java_org_telegram_SQLite_SQLitePreparedStatement_bindDouble(JNIEnv* env, jobject object, int statementHandle, int index, double value) { - sqlite3_stmt *handle = (sqlite3_stmt*)statementHandle; +void Java_org_telegram_SQLite_SQLitePreparedStatement_bindDouble(JNIEnv *env, jobject object, int statementHandle, int index, double value) { + sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; int errcode = sqlite3_bind_double(handle, index, value); if (SQLITE_OK != errcode) { @@ -114,8 +109,8 @@ void Java_org_telegram_SQLite_SQLitePreparedStatement_bindDouble(JNIEnv* env, jo } } -void Java_org_telegram_SQLite_SQLitePreparedStatement_bindNull(JNIEnv* env, jobject object, int statementHandle, int index) { - sqlite3_stmt *handle = (sqlite3_stmt*)statementHandle; +void Java_org_telegram_SQLite_SQLitePreparedStatement_bindNull(JNIEnv *env, jobject object, int statementHandle, int index) { + sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle; int errcode = sqlite3_bind_null(handle, index); if (SQLITE_OK != errcode) { diff --git a/TMessagesProj/jni/tgnet/Connection.cpp b/TMessagesProj/jni/tgnet/Connection.cpp index 3a9e7a3afd..1a51abe543 100644 --- a/TMessagesProj/jni/tgnet/Connection.cpp +++ b/TMessagesProj/jni/tgnet/Connection.cpp @@ -57,7 +57,7 @@ void Connection::suspendConnection() { } void Connection::onReceivedData(NativeByteBuffer *buffer) { - //AES_ctr128_encrypt(buffer->bytes(), buffer->bytes(), buffer->limit(), &decryptKey, decryptIv, decryptCount, &decryptNum); + AES_ctr128_encrypt(buffer->bytes(), buffer->bytes(), buffer->limit(), &decryptKey, decryptIv, decryptCount, &decryptNum); failedConnectionCount = 0; @@ -323,11 +323,11 @@ void Connection::sendData(NativeByteBuffer *buff, bool reportAck) { uint32_t val = (bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | (bytes[0]); uint32_t val2 = (bytes[7] << 24) | (bytes[6] << 16) | (bytes[5] << 8) | (bytes[4]); if (bytes[0] != 0xef && val != 0x44414548 && val != 0x54534f50 && val != 0x20544547 && val != 0x4954504f && val != 0xeeeeeeee && val2 != 0x00000000) { - //bytes[56] = bytes[57] = bytes[58] = bytes[59] = 0xef; + bytes[56] = bytes[57] = bytes[58] = bytes[59] = 0xef; break; } } - /*for (int a = 0; a < 48; a++) { + for (int a = 0; a < 48; a++) { temp[a] = bytes[55 - a]; } @@ -348,7 +348,7 @@ void Connection::sendData(NativeByteBuffer *buff, bool reportAck) { memcpy(decryptIv, temp + 32, 16); AES_ctr128_encrypt(bytes, temp, 64, &encryptKey, encryptIv, encryptCount, &encryptNum); - memcpy(bytes + 56, temp + 56, 8);*/ + memcpy(bytes + 56, temp + 56, 8); firstPacketSent = true; } @@ -358,7 +358,7 @@ void Connection::sendData(NativeByteBuffer *buff, bool reportAck) { } buffer->writeByte((uint8_t) packetLength); bytes += (buffer->limit() - 1); - //AES_ctr128_encrypt(bytes, bytes, 1, &encryptKey, encryptIv, encryptCount, &encryptNum); + AES_ctr128_encrypt(bytes, bytes, 1, &encryptKey, encryptIv, encryptCount, &encryptNum); } else { packetLength = (packetLength << 8) + 0x7f; if (reportAck) { @@ -366,13 +366,13 @@ void Connection::sendData(NativeByteBuffer *buff, bool reportAck) { } buffer->writeInt32(packetLength); bytes += (buffer->limit() - 4); - //AES_ctr128_encrypt(bytes, bytes, 4, &encryptKey, encryptIv, encryptCount, &encryptNum); + AES_ctr128_encrypt(bytes, bytes, 4, &encryptKey, encryptIv, encryptCount, &encryptNum); } buffer->rewind(); writeBuffer(buffer); buff->rewind(); - //AES_ctr128_encrypt(buff->bytes(), buff->bytes(), buff->limit(), &encryptKey, encryptIv, encryptCount, &encryptNum); + AES_ctr128_encrypt(buff->bytes(), buff->bytes(), buff->limit(), &encryptKey, encryptIv, encryptCount, &encryptNum); writeBuffer(buff); } diff --git a/TMessagesProj/jni/tgnet/Defines.h b/TMessagesProj/jni/tgnet/Defines.h index 92d9ab34a4..5490180be4 100644 --- a/TMessagesProj/jni/tgnet/Defines.h +++ b/TMessagesProj/jni/tgnet/Defines.h @@ -17,7 +17,7 @@ #define USE_DEBUG_SESSION false #define READ_BUFFER_SIZE 1024 * 128 -//#define DEBUG_VERSION +#define DEBUG_VERSION #define DEFAULT_DATACENTER_ID INT_MAX #define DC_UPDATE_TIME 60 * 60 #define DOWNLOAD_CONNECTIONS_COUNT 2 diff --git a/TMessagesProj/src/main/AndroidManifest.xml b/TMessagesProj/src/main/AndroidManifest.xml index d73a9affac..766336f013 100644 --- a/TMessagesProj/src/main/AndroidManifest.xml +++ b/TMessagesProj/src/main/AndroidManifest.xml @@ -99,6 +99,8 @@ + + diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java b/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java index c0dece7b24..4dd43713ce 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java @@ -11,6 +11,7 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; +import android.app.PendingIntent; import android.content.ContentUris; import android.content.Context; import android.content.DialogInterface; @@ -19,6 +20,7 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.database.Cursor; +import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; @@ -26,6 +28,7 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; +import android.os.Bundle; import android.os.Environment; import android.os.Parcelable; import android.provider.Browser; @@ -84,7 +87,9 @@ public class AndroidUtilities { private static final Hashtable typefaceCache = new Hashtable<>(); private static int prevOrientation = -10; private static boolean waitingForSms = false; + private static boolean waitingForCall = false; private static final Object smsLock = new Object(); + private static final Object callLock = new Object(); public static int statusBarHeight = 0; public static float density = 1; @@ -240,6 +245,20 @@ public static void setWaitingForSms(boolean value) { } } + public static boolean isWaitingForCall() { + boolean value; + synchronized (callLock) { + value = waitingForCall; + } + return value; + } + + public static void setWaitingForCall(boolean value) { + synchronized (callLock) { + waitingForCall = value; + } + } + public static void showKeyboard(View view) { if (view == null) { return; @@ -422,11 +441,28 @@ public static void openUrl(Context context, Uri uri) { if (context == null || uri == null) { return; } + try { Intent intent = new Intent(Intent.ACTION_VIEW, uri); - intent.putExtra("android.support.customtabs.extra.SESSION", (Parcelable) null); - intent.putExtra("android.support.customtabs.extra.TOOLBAR_COLOR", 0xff54759e); - intent.putExtra("android.support.customtabs.extra.TITLE_VISIBILITY", 1); + if (MediaController.getInstance().canCustomTabs()) { + intent.putExtra("android.support.customtabs.extra.SESSION", (Parcelable) null); + intent.putExtra("android.support.customtabs.extra.TOOLBAR_COLOR", 0xff54759e); + intent.putExtra("android.support.customtabs.extra.TITLE_VISIBILITY", 1); + + Intent actionIntent = new Intent(Intent.ACTION_SEND); + actionIntent.setType("text/plain"); + actionIntent.putExtra(Intent.EXTRA_TEXT, uri.toString()); + actionIntent.putExtra(Intent.EXTRA_SUBJECT, ""); + PendingIntent pendingIntent = PendingIntent.getActivity(ApplicationLoader.applicationContext, 0, actionIntent, PendingIntent.FLAG_ONE_SHOT); + + Bundle bundle = new Bundle(); + bundle.putInt("android.support.customtabs.customaction.ID", 0); + bundle.putParcelable("android.support.customtabs.customaction.ICON", BitmapFactory.decodeResource(context.getResources(), R.drawable.abc_ic_menu_share_mtrl_alpha)); + bundle.putString("android.support.customtabs.customaction.DESCRIPTION", LocaleController.getString("ShareFile", R.string.ShareFile)); + bundle.putParcelable("android.support.customtabs.customaction.PENDING_INTENT", pendingIntent); + intent.putExtra("android.support.customtabs.extra.ACTION_BUTTON_BUNDLE", bundle); + intent.putExtra("android.support.customtabs.extra.TINT_ACTION_BUTTON", false); + } intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); context.startActivity(intent); } catch (Exception e) { @@ -917,7 +953,11 @@ public static String getDataColumn(Context context, Uri uri, String selection, S cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { final int column_index = cursor.getColumnIndexOrThrow(column); - return cursor.getString(column_index); + String value = cursor.getString(column_index); + if (value.startsWith("content://") || !value.startsWith("/") && !value.startsWith("file://")) { + return null; + } + return value; } } catch (Exception e) { FileLog.e("tmessages", e); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java index e511f2b168..944477f0d6 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java @@ -37,6 +37,8 @@ import java.io.File; import java.io.RandomAccessFile; +import java.util.ArrayList; +import java.util.Locale; public class ApplicationLoader extends Application { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java b/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java index 561857a6c6..74e2ab8aad 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java @@ -10,8 +10,8 @@ public class BuildVars { public static boolean DEBUG_VERSION = false; - public static int BUILD_VERSION = 753; - public static String BUILD_VERSION_STRING = "3.6"; + public static int BUILD_VERSION = 767; + public static String BUILD_VERSION_STRING = "3.7"; public static int APP_ID = 0; //obtain your own APP_ID at https://core.telegram.org/api/obtaining_api_id public static String APP_HASH = ""; //obtain your own APP_HASH at https://core.telegram.org/api/obtaining_api_id public static String HOCKEY_APP_HASH = "your-hockeyapp-api-key-here"; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/CallReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/CallReceiver.java index 2aae70ab01..58eeacd262 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/CallReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/CallReceiver.java @@ -11,20 +11,24 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; + +import org.telegram.PhoneFormat.PhoneFormat; public class CallReceiver extends BroadcastReceiver { @Override public void onReceive(final Context context, Intent intent) { - /*TelephonyManager telephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + TelephonyManager telephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); telephony.listen(new PhoneStateListener() { @Override public void onCallStateChanged(int state, String incomingNumber) { super.onCallStateChanged(state, incomingNumber); if (state == 1 && incomingNumber != null && incomingNumber.length() > 0) { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.didReceiveCall, incomingNumber); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.didReceiveCall, PhoneFormat.stripExceptNumbers(incomingNumber)); } } - }, PhoneStateListener.LISTEN_CALL_STATE);*/ + }, PhoneStateListener.LISTEN_CALL_STATE); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/Emoji.java b/TMessagesProj/src/main/java/org/telegram/messenger/Emoji.java index 49c0dc44f0..7a5328550b 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/Emoji.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/Emoji.java @@ -265,9 +265,9 @@ public void run() { b = getBounds(); } - if (!canvas.quickReject(b.left, b.top, b.right, b.bottom, Canvas.EdgeType.AA)) { + //if (!canvas.quickReject(b.left, b.top, b.right, b.bottom, Canvas.EdgeType.AA)) { canvas.drawBitmap(emojiBmp[info.page][info.page2], info.rect, b, paint); - } + //} } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java index 6e8d4f3fed..a902ad76f5 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java @@ -550,13 +550,17 @@ public static String getMessageFileName(TLRPC.Message message) { return getAttachFileName(sizeFull); } } - } else if (message.media instanceof TLRPC.TL_messageMediaWebPage && message.media.webpage.photo != null) { - ArrayList sizes = message.media.webpage.photo.sizes; - if (sizes.size() > 0) { - TLRPC.PhotoSize sizeFull = getClosestPhotoSizeWithSize(sizes, AndroidUtilities.getPhotoSize()); - if (sizeFull != null) { - return getAttachFileName(sizeFull); + } else if (message.media instanceof TLRPC.TL_messageMediaWebPage) { + if (message.media.webpage.photo != null) { + ArrayList sizes = message.media.webpage.photo.sizes; + if (sizes.size() > 0) { + TLRPC.PhotoSize sizeFull = getClosestPhotoSizeWithSize(sizes, AndroidUtilities.getPhotoSize()); + if (sizeFull != null) { + return getAttachFileName(sizeFull); + } } + } else if (message.media.webpage.document != null) { + return getAttachFileName(message.media.webpage.document); } } } @@ -588,13 +592,17 @@ public static File getPathToMessage(TLRPC.Message message) { return getPathToAttach(sizeFull); } } - } else if (message.media instanceof TLRPC.TL_messageMediaWebPage && message.media.webpage.photo != null) { - ArrayList sizes = message.media.webpage.photo.sizes; - if (sizes.size() > 0) { - TLRPC.PhotoSize sizeFull = getClosestPhotoSizeWithSize(sizes, AndroidUtilities.getPhotoSize()); - if (sizeFull != null) { - return getPathToAttach(sizeFull); + } else if (message.media instanceof TLRPC.TL_messageMediaWebPage) { + if (message.media.webpage.photo != null) { + ArrayList sizes = message.media.webpage.photo.sizes; + if (sizes.size() > 0) { + TLRPC.PhotoSize sizeFull = getClosestPhotoSizeWithSize(sizes, AndroidUtilities.getPhotoSize()); + if (sizeFull != null) { + return getPathToAttach(sizeFull); + } } + } else if (message.media.webpage.document != null) { + return getPathToAttach(message.media.webpage.document); } } } @@ -704,6 +712,23 @@ public static String getDocumentFileName(TLRPC.Document document) { return ""; } + public static String getDocumentExtension(TLRPC.Document document) { + String fileName = getDocumentFileName(document); + int idx = fileName.lastIndexOf("."); + String ext = null; + if (idx != -1) { + ext = fileName.substring(idx + 1); + } + if (ext == null || ext.length() == 0) { + ext = document.mime_type; + } + if (ext == null) { + ext = ""; + } + ext = ext.toUpperCase(); + return ext; + } + public static String getAttachFileName(TLObject attach) { return getAttachFileName(attach, null); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java index 580f3ac998..f39780957b 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java @@ -196,14 +196,17 @@ public static void cleanupLogs() { File sdCard = ApplicationLoader.applicationContext.getExternalFilesDir(null); File dir = new File (sdCard.getAbsolutePath() + "/logs"); File[] files = dir.listFiles(); - for (File file : files) { - if (getInstance().currentFile != null && file.getAbsolutePath().equals(getInstance().currentFile.getAbsolutePath())) { - continue; - } - if (getInstance().networkFile != null && file.getAbsolutePath().equals(getInstance().networkFile.getAbsolutePath())) { - continue; + if (files != null) { + for (int a = 0; a < files.length; a++) { + File file = files[a]; + if (getInstance().currentFile != null && file.getAbsolutePath().equals(getInstance().currentFile.getAbsolutePath())) { + continue; + } + if (getInstance().networkFile != null && file.getAbsolutePath().equals(getInstance().networkFile.getAbsolutePath())) { + continue; + } + file.delete(); } - file.delete(); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java index 68efb2c5f3..52d87aa352 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java @@ -23,7 +23,6 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Environment; -import android.os.ParcelFileDescriptor; import android.provider.MediaStore; import org.telegram.tgnet.ConnectionsManager; @@ -33,7 +32,6 @@ import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; @@ -2031,8 +2029,7 @@ public void run() { public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxHeight, boolean useMaxScale) { BitmapFactory.Options bmOptions = new BitmapFactory.Options(); bmOptions.inJustDecodeBounds = true; - FileDescriptor fileDescriptor = null; - ParcelFileDescriptor parcelFD = null; + InputStream inputStream = null; if (path == null && uri != null && uri.getScheme() != null) { String imageFilePath = null; @@ -2052,9 +2049,10 @@ public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxH } else if (uri != null) { boolean error = false; try { - parcelFD = ApplicationLoader.applicationContext.getContentResolver().openFileDescriptor(uri, "r"); - fileDescriptor = parcelFD.getFileDescriptor(); - BitmapFactory.decodeFileDescriptor(fileDescriptor, null, bmOptions); + inputStream = ApplicationLoader.applicationContext.getContentResolver().openInputStream(uri); + BitmapFactory.decodeStream(inputStream, null, bmOptions); + inputStream.close(); + inputStream = ApplicationLoader.applicationContext.getContentResolver().openInputStream(uri); } catch (Throwable e) { FileLog.e("tmessages", e); return null; @@ -2138,7 +2136,7 @@ public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxH } } else if (uri != null) { try { - b = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, bmOptions); + b = BitmapFactory.decodeStream(inputStream, null, bmOptions); if (b != null) { if (bmOptions.inPurgeable) { Utilities.pinBitmap(b); @@ -2153,7 +2151,7 @@ public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxH FileLog.e("tmessages", e); } finally { try { - parcelFD.close(); + inputStream.close(); } catch (Throwable e) { FileLog.e("tmessages", e); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java b/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java index 97ea54fdff..1968f81e6a 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java @@ -322,7 +322,7 @@ public static String getLocaleString(Locale locale) { StringBuilder result = new StringBuilder(11); result.append(languageCode); if (countryCode.length() > 0 || variantCode.length() > 0) { - result.append('_'); + result.append('-'); } result.append(countryCode); if (variantCode.length() > 0) { @@ -664,16 +664,21 @@ public void onDeviceConfigurationChange(Configuration newConfig) { } public static String formatDateChat(long date) { - Calendar rightNow = Calendar.getInstance(); - int year = rightNow.get(Calendar.YEAR); + try { + Calendar rightNow = Calendar.getInstance(); + int year = rightNow.get(Calendar.YEAR); - rightNow.setTimeInMillis(date * 1000); - int dateYear = rightNow.get(Calendar.YEAR); + rightNow.setTimeInMillis(date * 1000); + int dateYear = rightNow.get(Calendar.YEAR); - if (year == dateYear) { - return getInstance().chatDate.format(date * 1000); + if (year == dateYear) { + return getInstance().chatDate.format(date * 1000); + } + return getInstance().chatFullDate.format(date * 1000); + } catch (Exception e) { + FileLog.e("tmessages", e); } - return getInstance().chatFullDate.format(date * 1000); + return "LOC_ERR: formatDateChat"; } public static String formatDate(long date) { @@ -697,7 +702,7 @@ public static String formatDate(long date) { } catch (Exception e) { FileLog.e("tmessages", e); } - return "LOC_ERR"; + return "LOC_ERR: formatDate"; } public static String formatDateAudio(long date) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java index 5d5cb32974..b3b8517c4a 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java @@ -42,10 +42,10 @@ import android.net.Uri; import android.os.Build; import android.os.Environment; -import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.Vibrator; import android.provider.MediaStore; +import android.provider.OpenableColumns; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; @@ -63,6 +63,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.InputStream; import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -235,7 +236,7 @@ public static class SearchImage { private float[] gravityFast = new float[3]; private float[] linearAcceleration = new float[3]; - private boolean hasAudioFoces; + private boolean hasAudioFocus; private boolean callInProgress; private ArrayList videoConvertQueue = new ArrayList<>(); @@ -269,6 +270,8 @@ public static class SearchImage { private boolean saveToGallery = true; private boolean autoplayGifs = true; private boolean raiseToSpeak = true; + private boolean customTabs = true; + private boolean directShare = true; private boolean shuffleMusic; private int repeatMode; @@ -595,6 +598,8 @@ public MediaController() { saveToGallery = preferences.getBoolean("save_gallery", false); autoplayGifs = preferences.getBoolean("autoplay_gif", true) && Build.VERSION.SDK_INT >= 11; raiseToSpeak = preferences.getBoolean("raise_to_speak", true) && Build.VERSION.SDK_INT >= 11; + customTabs = preferences.getBoolean("custom_tabs", true); + directShare = preferences.getBoolean("direct_share", true); shuffleMusic = preferences.getBoolean("shuffleMusic", false); repeatMode = preferences.getInt("repeatMode", 0); @@ -689,7 +694,7 @@ public void onAudioFocusChange(int focusChange) { if (MediaController.getInstance().isPlayingAudio(MediaController.getInstance().getPlayingMessageObject()) && !MediaController.getInstance().isAudioPaused()) { MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject()); } - hasAudioFoces = false; + hasAudioFocus = false; } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { //MediaController.getInstance().playAudio(MediaController.getInstance().getPlayingMessageObject()); } @@ -1653,14 +1658,24 @@ private void startAudioAgain(boolean paused) { if (playingMessageObject == null) { return; } + boolean post = audioPlayer != null; NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioRouteChanged, useFrontSpeaker); - MessageObject currentMessageObject = playingMessageObject; + final MessageObject currentMessageObject = playingMessageObject; float progress = playingMessageObject.audioProgress; cleanupPlayer(false, true); currentMessageObject.audioProgress = progress; playAudio(currentMessageObject); if (paused) { - pauseAudio(currentMessageObject); + if (post) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + pauseAudio(currentMessageObject); + } + }, 100); + } else { + pauseAudio(currentMessageObject); + } } } @@ -1959,6 +1974,11 @@ private void playNextMessage(boolean byStop) { if (byStop && repeatMode == 0) { if (audioPlayer != null || audioTrackPlayer != null) { if (audioPlayer != null) { + try { + audioPlayer.reset(); + } catch (Exception e) { + FileLog.e("tmessages", e); + } try { audioPlayer.stop(); } catch (Exception e) { @@ -2058,7 +2078,7 @@ public void setVoiceMessagesPlaylist(ArrayList playlist, boolean } } - public boolean playAudio(MessageObject messageObject) { + public boolean playAudio(final MessageObject messageObject) { if (messageObject == null) { return false; } @@ -2171,7 +2191,7 @@ public void onCompletion(MediaPlayer mediaPlayer) { if (!playlist.isEmpty() && playlist.size() > 1) { playNextMessage(true); } else { - cleanupPlayer(true, true); + cleanupPlayer(true, true, messageObject != null && messageObject.isVoice()); } } }); @@ -2201,8 +2221,8 @@ public void onCompletion(MediaPlayer mediaPlayer) { return false; } } - if (!hasAudioFoces) { - hasAudioFoces = true; + if (!hasAudioFocus) { + hasAudioFocus = true; NotificationsController.getInstance().audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); } @@ -2270,6 +2290,11 @@ public void stopAudio() { } try { if (audioPlayer != null) { + try { + audioPlayer.reset(); + } catch (Exception e) { + FileLog.e("tmessages", e); + } audioPlayer.stop(); } else if (audioTrackPlayer != null) { audioTrackPlayer.pause(); @@ -2378,8 +2403,8 @@ public boolean resumeAudio(MessageObject messageObject) { audioTrackPlayer.play(); checkPlayerQueue(); } - if (!hasAudioFoces) { - hasAudioFoces = true; + if (!hasAudioFocus) { + hasAudioFocus = true; NotificationsController.getInstance().audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); } isPaused = false; @@ -2507,19 +2532,6 @@ public void run() { public void generateWaveform(MessageObject messageObject) { final String id = messageObject.getId() + "_" + messageObject.getDialogId(); final String path = FileLoader.getPathToMessage(messageObject.messageOwner).getAbsolutePath(); - /*for (int a = 0; a < currentMessageObject.messageOwner.media.document.attributes.size(); a++) { TODO if old attribute - TLRPC.DocumentAttribute attribute = currentMessageObject.messageOwner.media.document.attributes.get(a); - if (attribute instanceof TLRPC.TL_documentAttributeAudio) { - if (attribute.waveform == null || attribute.waveform.length == 0) { - attribute.waveform = MediaController.getInstance().getWaveform(path.getAbsolutePath()); - } - if (attribute.waveform != null) { - hasWaveform = true; - } - seekBarWaveform.setWaveform(attribute.waveform); - break; - } - }*/ if (generatingWaveform.containsKey(id)) { return; } @@ -2764,14 +2776,11 @@ public void run() { } public static boolean isWebp(Uri uri) { - ParcelFileDescriptor parcelFD = null; - FileInputStream input = null; + InputStream inputStream = null; try { - parcelFD = ApplicationLoader.applicationContext.getContentResolver().openFileDescriptor(uri, "r"); - input = new FileInputStream(parcelFD.getFileDescriptor()); - if (input.getChannel().size() > 12) { - byte[] header = new byte[12]; - input.read(header, 0, 12); + inputStream = ApplicationLoader.applicationContext.getContentResolver().openInputStream(uri); + byte[] header = new byte[12]; + if (inputStream.read(header, 0, 12) == 12) { String str = new String(header); if (str != null) { str = str.toLowerCase(); @@ -2784,15 +2793,8 @@ public static boolean isWebp(Uri uri) { FileLog.e("tmessages", e); } finally { try { - if (parcelFD != null) { - parcelFD.close(); - } - } catch (Exception e2) { - FileLog.e("tmessages", e2); - } - try { - if (input != null) { - input.close(); + if (inputStream != null) { + inputStream.close(); } } catch (Exception e2) { FileLog.e("tmessages", e2); @@ -2802,14 +2804,11 @@ public static boolean isWebp(Uri uri) { } public static boolean isGif(Uri uri) { - ParcelFileDescriptor parcelFD = null; - FileInputStream input = null; + InputStream inputStream = null; try { - parcelFD = ApplicationLoader.applicationContext.getContentResolver().openFileDescriptor(uri, "r"); - input = new FileInputStream(parcelFD.getFileDescriptor()); - if (input.getChannel().size() > 3) { - byte[] header = new byte[3]; - input.read(header, 0, 3); + inputStream = ApplicationLoader.applicationContext.getContentResolver().openInputStream(uri); + byte[] header = new byte[3]; + if (inputStream.read(header, 0, 3) == 3) { String str = new String(header); if (str != null && str.equalsIgnoreCase("gif")) { return true; @@ -2819,50 +2818,68 @@ public static boolean isGif(Uri uri) { FileLog.e("tmessages", e); } finally { try { - if (parcelFD != null) { - parcelFD.close(); + if (inputStream != null) { + inputStream.close(); } } catch (Exception e2) { FileLog.e("tmessages", e2); } + } + return false; + } + + public static String getFileName(Uri uri) { + String result = null; + if (uri.getScheme().equals("content")) { + Cursor cursor = ApplicationLoader.applicationContext.getContentResolver().query(uri, null, null, null, null); try { - if (input != null) { - input.close(); + if (cursor.moveToFirst()) { + result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } finally { + if (cursor != null) { + cursor.close(); } - } catch (Exception e2) { - FileLog.e("tmessages", e2); } } - return false; + if (result == null) { + result = uri.getPath(); + int cut = result.lastIndexOf('/'); + if (cut != -1) { + result = result.substring(cut + 1); + } + } + return result; } - public static String copyDocumentToCache(Uri uri, String ext) { - ParcelFileDescriptor parcelFD = null; - FileInputStream input = null; + public static String copyFileToCache(Uri uri, String ext) { + InputStream inputStream = null; FileOutputStream output = null; try { - int id = UserConfig.lastLocalId; - UserConfig.lastLocalId--; - parcelFD = ApplicationLoader.applicationContext.getContentResolver().openFileDescriptor(uri, "r"); - input = new FileInputStream(parcelFD.getFileDescriptor()); - File f = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), String.format(Locale.US, "%d.%s", id, ext)); + String name = getFileName(uri); + if (name == null) { + int id = UserConfig.lastLocalId; + UserConfig.lastLocalId--; + UserConfig.saveConfig(false); + name = String.format(Locale.US, "%d.%s", id, ext); + } + inputStream = ApplicationLoader.applicationContext.getContentResolver().openInputStream(uri); + File f = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), name); output = new FileOutputStream(f); - input.getChannel().transferTo(0, input.getChannel().size(), output.getChannel()); - UserConfig.saveConfig(false); + byte[] buffer = new byte[1024 * 20]; + int len; + while ((len = inputStream.read(buffer)) != -1) { + output.write(buffer, 0, len); + } return f.getAbsolutePath(); } catch (Exception e) { FileLog.e("tmessages", e); } finally { try { - if (parcelFD != null) { - parcelFD.close(); - } - } catch (Exception e2) { - FileLog.e("tmessages", e2); - } - try { - if (input != null) { - input.close(); + if (inputStream != null) { + inputStream.close(); } } catch (Exception e2) { FileLog.e("tmessages", e2); @@ -2903,6 +2920,22 @@ public void toogleRaiseToSpeak() { editor.commit(); } + public void toggleCustomTabs() { + customTabs = !customTabs; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putBoolean("custom_tabs", customTabs); + editor.commit(); + } + + public void toggleDirectShare() { + directShare = !directShare; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putBoolean("direct_share", directShare); + editor.commit(); + } + public void checkSaveToGalleryFiles() { try { File telegramPath = new File(Environment.getExternalStorageDirectory(), "Telegram"); @@ -2943,6 +2976,14 @@ public boolean canRaiseToSpeak() { return raiseToSpeak; } + public boolean canCustomTabs() { + return customTabs; + } + + public boolean canDirectShare() { + return directShare; + } + public static void loadGalleryPhotosAlbums(final int guid) { new Thread(new Runnable() { @Override @@ -3246,6 +3287,7 @@ private long readAndWriteTrack(final MessageObject messageObject, MediaExtractor long startTime = -1; checkConversionCanceled(); + long lastTimestamp = -100; while (!inputDone) { checkConversionCanceled(); @@ -3254,28 +3296,37 @@ private long readAndWriteTrack(final MessageObject messageObject, MediaExtractor int index = extractor.getSampleTrackIndex(); if (index == trackIndex) { info.size = extractor.readSampleData(buffer, 0); - - if (info.size < 0) { + if (info.size >= 0) { + info.presentationTimeUs = extractor.getSampleTime(); + } else { info.size = 0; eof = true; - } else { - info.presentationTimeUs = extractor.getSampleTime(); + } + + if (info.size > 0 && !eof) { if (start > 0 && startTime == -1) { startTime = info.presentationTimeUs; } if (end < 0 || info.presentationTimeUs < end) { - info.offset = 0; - info.flags = extractor.getSampleFlags(); - if (mediaMuxer.writeSampleData(muxerTrackIndex, buffer, info, isAudio)) { - didWriteData(messageObject, file, false, false); + if (info.presentationTimeUs > lastTimestamp) { + info.offset = 0; + info.flags = extractor.getSampleFlags(); + if (mediaMuxer.writeSampleData(muxerTrackIndex, buffer, info, isAudio)) { + didWriteData(messageObject, file, false, false); + } } - extractor.advance(); + lastTimestamp = info.presentationTimeUs; } else { eof = true; } } + if (!eof) { + extractor.advance(); + } } else if (index == -1) { eof = true; + } else { + extractor.advance(); } if (eof) { inputDone = true; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java index aa34a37586..4033311a7c 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java @@ -58,11 +58,10 @@ public class MessageObject { public VideoEditedInfo videoEditedInfo; public boolean viewsReloaded; - public static TextPaint textPaint; + private static TextPaint textPaint; public int lastLineWidth; public int textWidth; public int textHeight; - public int blockHeight = Integer.MAX_VALUE; private boolean layoutCreated; @@ -70,9 +69,10 @@ public class MessageObject { public static class TextLayoutBlock { public StaticLayout textLayout; - public float textXOffset = 0; - public float textYOffset = 0; - public int charactersOffset = 0; + public float textXOffset; + public float textYOffset; + public int charactersOffset; + public int height; } private static final int LINES_PER_BLOCK = 10; @@ -99,7 +99,7 @@ public MessageObject(TLRPC.Message message, AbstractMap use } TLRPC.User fromUser = null; - if (isFromUser()) { + if (message.from_id > 0) { if (users != null) { fromUser = users.get(message.from_id); } @@ -114,22 +114,14 @@ public MessageObject(TLRPC.Message message, AbstractMap use if (isOut()) { messageText = LocaleController.getString("ActionYouCreateGroup", R.string.ActionYouCreateGroup); } else { - if (fromUser != null) { - messageText = replaceWithLink(LocaleController.getString("ActionCreateGroup", R.string.ActionCreateGroup), "un1", fromUser); - } else { - messageText = LocaleController.getString("ActionCreateGroup", R.string.ActionCreateGroup).replace("un1", ""); - } + messageText = replaceWithLink(LocaleController.getString("ActionCreateGroup", R.string.ActionCreateGroup), "un1", fromUser); } } else if (message.action instanceof TLRPC.TL_messageActionChatDeleteUser) { if (message.action.user_id == message.from_id) { if (isOut()) { messageText = LocaleController.getString("ActionYouLeftUser", R.string.ActionYouLeftUser); } else { - if (fromUser != null) { - messageText = replaceWithLink(LocaleController.getString("ActionLeftUser", R.string.ActionLeftUser), "un1", fromUser); - } else { - messageText = LocaleController.getString("ActionLeftUser", R.string.ActionLeftUser).replace("un1", ""); - } + messageText = replaceWithLink(LocaleController.getString("ActionLeftUser", R.string.ActionLeftUser), "un1", fromUser); } } else { TLRPC.User whoUser = null; @@ -139,17 +131,13 @@ public MessageObject(TLRPC.Message message, AbstractMap use if (whoUser == null) { whoUser = MessagesController.getInstance().getUser(message.action.user_id); } - if (whoUser != null && fromUser != null) { - if (isOut()) { - messageText = replaceWithLink(LocaleController.getString("ActionYouKickUser", R.string.ActionYouKickUser), "un2", whoUser); - } else if (message.action.user_id == UserConfig.getClientUserId()) { - messageText = replaceWithLink(LocaleController.getString("ActionKickUserYou", R.string.ActionKickUserYou), "un1", fromUser); - } else { - messageText = replaceWithLink(LocaleController.getString("ActionKickUser", R.string.ActionKickUser), "un2", whoUser); - messageText = replaceWithLink(messageText, "un1", fromUser); - } + if (isOut()) { + messageText = replaceWithLink(LocaleController.getString("ActionYouKickUser", R.string.ActionYouKickUser), "un2", whoUser); + } else if (message.action.user_id == UserConfig.getClientUserId()) { + messageText = replaceWithLink(LocaleController.getString("ActionKickUserYou", R.string.ActionKickUserYou), "un1", fromUser); } else { - messageText = LocaleController.getString("ActionKickUser", R.string.ActionKickUser).replace("un2", "").replace("un1", ""); + messageText = replaceWithLink(LocaleController.getString("ActionKickUser", R.string.ActionKickUser), "un2", whoUser); + messageText = replaceWithLink(messageText, "un1", fromUser); } } } else if (message.action instanceof TLRPC.TL_messageActionChatAddUser) { @@ -165,36 +153,38 @@ public MessageObject(TLRPC.Message message, AbstractMap use if (whoUser == null) { whoUser = MessagesController.getInstance().getUser(singleUserId); } - if (message.to_id.channel_id != 0 && !isMegagroup()) { - if (whoUser != null && whoUser.id != UserConfig.getClientUserId()) { - if (isMegagroup()) { - messageText = replaceWithLink(LocaleController.getString("MegaAddedBy", R.string.MegaAddedBy), "un1", whoUser); + if (singleUserId == message.from_id) { + if (message.to_id.channel_id != 0 && !isMegagroup()) { + messageText = LocaleController.getString("ChannelJoined", R.string.ChannelJoined); + } else { + if (message.to_id.channel_id != 0 && isMegagroup()) { + if (singleUserId == UserConfig.getClientUserId()) { + messageText = LocaleController.getString("ChannelMegaJoined", R.string.ChannelMegaJoined); + } else { + messageText = replaceWithLink(LocaleController.getString("ActionAddUserSelfMega", R.string.ActionAddUserSelfMega), "un1", fromUser); + } + } else if (isOut()) { + messageText = LocaleController.getString("ActionAddUserSelfYou", R.string.ActionAddUserSelfYou); } else { - messageText = replaceWithLink(LocaleController.getString("ChannelAddedBy", R.string.ChannelAddedBy), "un1", whoUser); + messageText = replaceWithLink(LocaleController.getString("ActionAddUserSelf", R.string.ActionAddUserSelf), "un1", fromUser); } - } else { - messageText = LocaleController.getString("ChannelJoined", R.string.ChannelJoined); } } else { - if (whoUser != null && fromUser != null) { - if (whoUser.id == fromUser.id) { - if (isOut()) { - messageText = LocaleController.getString("ActionAddUserSelfYou", R.string.ActionAddUserSelfYou); + if (isOut()) { + messageText = replaceWithLink(LocaleController.getString("ActionYouAddUser", R.string.ActionYouAddUser), "un2", whoUser); + } else if (singleUserId == UserConfig.getClientUserId()) { + if (message.to_id.channel_id != 0) { + if (isMegagroup()) { + messageText = replaceWithLink(LocaleController.getString("MegaAddedBy", R.string.MegaAddedBy), "un1", fromUser); } else { - messageText = replaceWithLink(LocaleController.getString("ActionAddUserSelf", R.string.ActionAddUserSelf), "un1", fromUser); + messageText = replaceWithLink(LocaleController.getString("ChannelAddedBy", R.string.ChannelAddedBy), "un1", fromUser); } } else { - if (isOut()) { - messageText = replaceWithLink(LocaleController.getString("ActionYouAddUser", R.string.ActionYouAddUser), "un2", whoUser); - } else if (singleUserId == UserConfig.getClientUserId()) { - messageText = replaceWithLink(LocaleController.getString("ActionAddUserYou", R.string.ActionAddUserYou), "un1", fromUser); - } else { - messageText = replaceWithLink(LocaleController.getString("ActionAddUser", R.string.ActionAddUser), "un2", whoUser); - messageText = replaceWithLink(messageText, "un1", fromUser); - } + messageText = replaceWithLink(LocaleController.getString("ActionAddUserYou", R.string.ActionAddUserYou), "un1", fromUser); } } else { - messageText = LocaleController.getString("ActionAddUser", R.string.ActionAddUser).replace("un2", "").replace("un1", ""); + messageText = replaceWithLink(LocaleController.getString("ActionAddUser", R.string.ActionAddUser), "un2", whoUser); + messageText = replaceWithLink(messageText, "un1", fromUser); } } } else { @@ -206,14 +196,10 @@ public MessageObject(TLRPC.Message message, AbstractMap use } } } else if (message.action instanceof TLRPC.TL_messageActionChatJoinedByLink) { - if (fromUser != null) { - if (isOut()) { - messageText = LocaleController.getString("ActionInviteYou", R.string.ActionInviteYou); - } else { - messageText = replaceWithLink(LocaleController.getString("ActionInviteUser", R.string.ActionInviteUser), "un1", fromUser); - } + if (isOut()) { + messageText = LocaleController.getString("ActionInviteYou", R.string.ActionInviteYou); } else { - messageText = LocaleController.getString("ActionInviteUser", R.string.ActionInviteUser).replace("un1", ""); + messageText = replaceWithLink(LocaleController.getString("ActionInviteUser", R.string.ActionInviteUser), "un1", fromUser); } } else if (message.action instanceof TLRPC.TL_messageActionChatEditPhoto) { if (message.to_id.channel_id != 0 && !isMegagroup()) { @@ -222,11 +208,7 @@ public MessageObject(TLRPC.Message message, AbstractMap use if (isOut()) { messageText = LocaleController.getString("ActionYouChangedPhoto", R.string.ActionYouChangedPhoto); } else { - if (fromUser != null) { - messageText = replaceWithLink(LocaleController.getString("ActionChangedPhoto", R.string.ActionChangedPhoto), "un1", fromUser); - } else { - messageText = LocaleController.getString("ActionChangedPhoto", R.string.ActionChangedPhoto).replace("un1", ""); - } + messageText = replaceWithLink(LocaleController.getString("ActionChangedPhoto", R.string.ActionChangedPhoto), "un1", fromUser); } } } else if (message.action instanceof TLRPC.TL_messageActionChatEditTitle) { @@ -236,11 +218,7 @@ public MessageObject(TLRPC.Message message, AbstractMap use if (isOut()) { messageText = LocaleController.getString("ActionYouChangedTitle", R.string.ActionYouChangedTitle).replace("un2", message.action.title); } else { - if (fromUser != null) { - messageText = replaceWithLink(LocaleController.getString("ActionChangedTitle", R.string.ActionChangedTitle).replace("un2", message.action.title), "un1", fromUser); - } else { - messageText = LocaleController.getString("ActionChangedTitle", R.string.ActionChangedTitle).replace("un1", "").replace("un2", message.action.title); - } + messageText = replaceWithLink(LocaleController.getString("ActionChangedTitle", R.string.ActionChangedTitle).replace("un2", message.action.title), "un1", fromUser); } } } else if (message.action instanceof TLRPC.TL_messageActionChatDeletePhoto) { @@ -250,11 +228,7 @@ public MessageObject(TLRPC.Message message, AbstractMap use if (isOut()) { messageText = LocaleController.getString("ActionYouRemovedPhoto", R.string.ActionYouRemovedPhoto); } else { - if (fromUser != null) { - messageText = replaceWithLink(LocaleController.getString("ActionRemovedPhoto", R.string.ActionRemovedPhoto), "un1", fromUser); - } else { - messageText = LocaleController.getString("ActionRemovedPhoto", R.string.ActionRemovedPhoto).replace("un1", ""); - } + messageText = replaceWithLink(LocaleController.getString("ActionRemovedPhoto", R.string.ActionRemovedPhoto), "un1", fromUser); } } } else if (message.action instanceof TLRPC.TL_messageActionTTLChange) { @@ -262,21 +236,13 @@ public MessageObject(TLRPC.Message message, AbstractMap use if (isOut()) { messageText = LocaleController.formatString("MessageLifetimeChangedOutgoing", R.string.MessageLifetimeChangedOutgoing, AndroidUtilities.formatTTLString(message.action.ttl)); } else { - if (fromUser != null) { - messageText = LocaleController.formatString("MessageLifetimeChanged", R.string.MessageLifetimeChanged, UserObject.getFirstName(fromUser), AndroidUtilities.formatTTLString(message.action.ttl)); - } else { - messageText = LocaleController.formatString("MessageLifetimeChanged", R.string.MessageLifetimeChanged, "", AndroidUtilities.formatTTLString(message.action.ttl)); - } + messageText = LocaleController.formatString("MessageLifetimeChanged", R.string.MessageLifetimeChanged, UserObject.getFirstName(fromUser), AndroidUtilities.formatTTLString(message.action.ttl)); } } else { if (isOut()) { messageText = LocaleController.getString("MessageLifetimeYouRemoved", R.string.MessageLifetimeYouRemoved); } else { - if (fromUser != null) { - messageText = LocaleController.formatString("MessageLifetimeRemoved", R.string.MessageLifetimeRemoved, UserObject.getFirstName(fromUser)); - } else { - messageText = LocaleController.formatString("MessageLifetimeRemoved", R.string.MessageLifetimeRemoved, ""); - } + messageText = LocaleController.formatString("MessageLifetimeRemoved", R.string.MessageLifetimeRemoved, UserObject.getFirstName(fromUser)); } } } else if (message.action instanceof TLRPC.TL_messageActionLoginUnknownLocation) { @@ -299,27 +265,15 @@ public MessageObject(TLRPC.Message message, AbstractMap use String name = to_user != null ? UserObject.getFirstName(to_user) : ""; messageText = LocaleController.formatString("NotificationUnrecognizedDevice", R.string.NotificationUnrecognizedDevice, name, date, message.action.title, message.action.address); } else if (message.action instanceof TLRPC.TL_messageActionUserJoined) { - if (fromUser != null) { - messageText = LocaleController.formatString("NotificationContactJoined", R.string.NotificationContactJoined, UserObject.getUserName(fromUser)); - } else { - messageText = LocaleController.formatString("NotificationContactJoined", R.string.NotificationContactJoined, ""); - } + messageText = LocaleController.formatString("NotificationContactJoined", R.string.NotificationContactJoined, UserObject.getUserName(fromUser)); } else if (message.action instanceof TLRPC.TL_messageActionUserUpdatedPhoto) { - if (fromUser != null) { - messageText = LocaleController.formatString("NotificationContactNewPhoto", R.string.NotificationContactNewPhoto, UserObject.getUserName(fromUser)); - } else { - messageText = LocaleController.formatString("NotificationContactNewPhoto", R.string.NotificationContactNewPhoto, ""); - } + messageText = LocaleController.formatString("NotificationContactNewPhoto", R.string.NotificationContactNewPhoto, UserObject.getUserName(fromUser)); } else if (message.action instanceof TLRPC.TL_messageEncryptedAction) { if (message.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionScreenshotMessages) { if (isOut()) { messageText = LocaleController.formatString("ActionTakeScreenshootYou", R.string.ActionTakeScreenshootYou); } else { - if (fromUser != null) { - messageText = replaceWithLink(LocaleController.getString("ActionTakeScreenshoot", R.string.ActionTakeScreenshoot), "un1", fromUser); - } else { - messageText = LocaleController.formatString("ActionTakeScreenshoot", R.string.ActionTakeScreenshoot).replace("un1", ""); - } + messageText = replaceWithLink(LocaleController.getString("ActionTakeScreenshoot", R.string.ActionTakeScreenshoot), "un1", fromUser); } } else if (message.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionSetMessageTTL) { TLRPC.TL_decryptedMessageActionSetMessageTTL action = (TLRPC.TL_decryptedMessageActionSetMessageTTL) message.action.encryptedAction; @@ -327,21 +281,13 @@ public MessageObject(TLRPC.Message message, AbstractMap use if (isOut()) { messageText = LocaleController.formatString("MessageLifetimeChangedOutgoing", R.string.MessageLifetimeChangedOutgoing, AndroidUtilities.formatTTLString(action.ttl_seconds)); } else { - if (fromUser != null) { - messageText = LocaleController.formatString("MessageLifetimeChanged", R.string.MessageLifetimeChanged, UserObject.getFirstName(fromUser), AndroidUtilities.formatTTLString(action.ttl_seconds)); - } else { - messageText = LocaleController.formatString("MessageLifetimeChanged", R.string.MessageLifetimeChanged, "", AndroidUtilities.formatTTLString(action.ttl_seconds)); - } + messageText = LocaleController.formatString("MessageLifetimeChanged", R.string.MessageLifetimeChanged, UserObject.getFirstName(fromUser), AndroidUtilities.formatTTLString(action.ttl_seconds)); } } else { if (isOut()) { messageText = LocaleController.getString("MessageLifetimeYouRemoved", R.string.MessageLifetimeYouRemoved); } else { - if (fromUser != null) { - messageText = LocaleController.formatString("MessageLifetimeRemoved", R.string.MessageLifetimeRemoved, UserObject.getFirstName(fromUser)); - } else { - messageText = LocaleController.formatString("MessageLifetimeRemoved", R.string.MessageLifetimeRemoved, ""); - } + messageText = LocaleController.formatString("MessageLifetimeRemoved", R.string.MessageLifetimeRemoved, UserObject.getFirstName(fromUser)); } } } @@ -357,6 +303,8 @@ public MessageObject(TLRPC.Message message, AbstractMap use messageText = LocaleController.getString("ActionMigrateFromGroup", R.string.ActionMigrateFromGroup); } else if (message.action instanceof TLRPC.TL_messageActionChannelMigrateFrom) { messageText = LocaleController.getString("ActionMigrateFromGroup", R.string.ActionMigrateFromGroup); + } else if (message.action instanceof TLRPC.TL_messageActionPinMessage) { + generatePinMessageText(fromUser, fromUser == null ? chats.get(message.to_id.channel_id) : null); } } } else if (!isMediaEmpty()) { @@ -402,27 +350,31 @@ public MessageObject(TLRPC.Message message, AbstractMap use if (message instanceof TLRPC.TL_message || message instanceof TLRPC.TL_messageForwarded_old2) { if (isMediaEmpty()) { - contentType = type = 0; + contentType = 0; + type = 0; if (messageText == null || messageText.length() == 0) { messageText = "Empty message"; } } else if (message.media instanceof TLRPC.TL_messageMediaPhoto) { - contentType = type = 1; + contentType = 0; + type = 1; } else if (message.media instanceof TLRPC.TL_messageMediaGeo || message.media instanceof TLRPC.TL_messageMediaVenue) { - contentType = 1; + contentType = 0; type = 4; } else if (isVideo()) { - contentType = 1; + contentType = 0; type = 3; } else if (isVoice()) { - contentType = type = 2; + contentType = 2; + type = 2; } else if (message.media instanceof TLRPC.TL_messageMediaContact) { contentType = 3; type = 12; } else if (message.media instanceof TLRPC.TL_messageMediaUnsupported) { - contentType = type = 0; + contentType = 0; + type = 0; } else if (message.media instanceof TLRPC.TL_messageMediaDocument) { - contentType = 1; + contentType = 0; if (message.media.document.mime_type != null) { if (isGifDocument(message.media.document)) { type = 8; @@ -440,7 +392,8 @@ public MessageObject(TLRPC.Message message, AbstractMap use } } else if (message instanceof TLRPC.TL_messageService) { if (message.action instanceof TLRPC.TL_messageActionLoginUnknownLocation) { - contentType = type = 0; + contentType = 0; + type = 0; } else if (message.action instanceof TLRPC.TL_messageActionChatEditPhoto || message.action instanceof TLRPC.TL_messageActionUserUpdatedPhoto) { contentType = 4; type = 11; @@ -464,7 +417,7 @@ public MessageObject(TLRPC.Message message, AbstractMap use int dateYear = rightNow.get(Calendar.YEAR); int dateMonth = rightNow.get(Calendar.MONTH); dateKey = String.format("%d_%02d_%02d", dateYear, dateMonth, dateDay); - if (contentType == 1 || contentType == 2 || contentType == 0 || contentType == 8) { + if (contentType == 2 || contentType == 0 || contentType == 8) { monthKey = String.format("%d_%02d", dateYear, dateMonth); } else if (contentType == 9) { //dateKey = "0_0_0"; @@ -484,6 +437,58 @@ public MessageObject(TLRPC.Message message, AbstractMap use generateThumbs(false); } + public static TextPaint getTextPaint() { + if (textPaint == null) { + textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + textPaint.setColor(0xff000000); + textPaint.linkColor = 0xff316f9f; + textPaint.setTextSize(AndroidUtilities.dp(MessagesController.getInstance().fontSize)); + } + return textPaint; + } + + public void generatePinMessageText(TLRPC.User fromUser, TLRPC.Chat chat) { + if (fromUser == null && chat == null) { + if (messageOwner.from_id > 0) { + fromUser = MessagesController.getInstance().getUser(messageOwner.from_id); + } + if (fromUser == null) { + chat = MessagesController.getInstance().getChat(messageOwner.to_id.channel_id); + } + } + if (replyMessageObject == null) { + messageText = replaceWithLink(LocaleController.getString("ActionPinnedNoText", R.string.ActionPinnedNoText), "un1", fromUser != null ? fromUser : chat); + } else { + if (replyMessageObject.isMusic()) { + messageText = replaceWithLink(LocaleController.getString("ActionPinnedMusic", R.string.ActionPinnedMusic), "un1", fromUser != null ? fromUser : chat); + } else if (replyMessageObject.isVideo()) { + messageText = replaceWithLink(LocaleController.getString("ActionPinnedVideo", R.string.ActionPinnedVideo), "un1", fromUser != null ? fromUser : chat); + } else if (replyMessageObject.isGif()) { + messageText = replaceWithLink(LocaleController.getString("ActionPinnedGif", R.string.ActionPinnedGif), "un1", fromUser != null ? fromUser : chat); + } else if (replyMessageObject.isVoice()) { + messageText = replaceWithLink(LocaleController.getString("ActionPinnedVoice", R.string.ActionPinnedVoice), "un1", fromUser != null ? fromUser : chat); + } else if (replyMessageObject.isSticker()) { + messageText = replaceWithLink(LocaleController.getString("ActionPinnedSticker", R.string.ActionPinnedSticker), "un1", fromUser != null ? fromUser : chat); + } else if (replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { + messageText = replaceWithLink(LocaleController.getString("ActionPinnedFile", R.string.ActionPinnedFile), "un1", fromUser != null ? fromUser : chat); + } else if (replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo) { + messageText = replaceWithLink(LocaleController.getString("ActionPinnedGeo", R.string.ActionPinnedGeo), "un1", fromUser != null ? fromUser : chat); + } else if (replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) { + messageText = replaceWithLink(LocaleController.getString("ActionPinnedContact", R.string.ActionPinnedContact), "un1", fromUser != null ? fromUser : chat); + } else if (replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { + messageText = replaceWithLink(LocaleController.getString("ActionPinnedPhoto", R.string.ActionPinnedPhoto), "un1", fromUser != null ? fromUser : chat); + } else if (replyMessageObject.messageText != null && replyMessageObject.messageText.length() > 0) { + CharSequence mess = replyMessageObject.messageText; + if (mess.length() > 20) { + mess = mess.subSequence(0, 20) + "..."; + } + messageText = replaceWithLink(LocaleController.formatString("ActionPinnedText", R.string.ActionPinnedText, mess), "un1", fromUser != null ? fromUser : chat); + } else { + messageText = replaceWithLink(LocaleController.getString("ActionPinnedNoText", R.string.ActionPinnedNoText), "un1", fromUser != null ? fromUser : chat); + } + } + } + public void checkLayout() { if (!layoutCreated) { layoutCreated = true; @@ -662,6 +667,8 @@ public String getFileName() { return FileLoader.getAttachFileName(sizeFull); } } + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) { + return FileLoader.getAttachFileName(messageOwner.media.webpage.document); } return ""; } @@ -950,7 +957,7 @@ private void generateLayout(TLRPC.User fromUser) { block.textLayout = textLayout; block.textYOffset = 0; block.charactersOffset = 0; - blockHeight = textHeight; + block.height = textHeight; } else { int startCharacter = textLayout.getLineStart(linesOffset); int endCharacter = textLayout.getLineEnd(linesOffset + currentBlockLinesCount - 1); @@ -963,16 +970,10 @@ private void generateLayout(TLRPC.User fromUser) { block.textLayout = new StaticLayout(str, textPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); block.textYOffset = textLayout.getLineTop(linesOffset); if (a != 0) { - blockHeight = Math.min(blockHeight, (int) (block.textYOffset - prevOffset)); + block.height = (int) (block.textYOffset - prevOffset); } + block.height = Math.max(block.height, block.textLayout.getLineBottom(block.textLayout.getLineCount() - 1)); prevOffset = block.textYOffset; - /*if (a != blocksCount - 1) { - int height = block.textLayout.getHeight(); - blockHeight = Math.min(blockHeight, block.textLayout.getHeight()); - prevOffset = block.textYOffset; - } else { - blockHeight = Math.min(blockHeight, (int)(block.textYOffset - prevOffset)); - }*/ } catch (Exception e) { FileLog.e("tmessages", e); continue; @@ -1067,9 +1068,6 @@ private void generateLayout(TLRPC.User fromUser) { linesOffset += currentBlockLinesCount; } - if (blockHeight == 0) { - blockHeight = 1; - } } public boolean isOut() { @@ -1421,7 +1419,11 @@ public boolean isVideo() { } public boolean isGif() { - return isGifDocument(messageOwner.media.document); + return messageOwner.media instanceof TLRPC.TL_messageMediaDocument && isGifDocument(messageOwner.media.document); + } + + public boolean isWebpageDocument() { + return messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && messageOwner.media.webpage.document != null && !isGifDocument(messageOwner.media.webpage.document); } public boolean isNewGif() { @@ -1512,16 +1514,16 @@ public boolean canEditMessage(TLRPC.Chat chat) { } public static boolean canEditMessage(TLRPC.Message message, TLRPC.Chat chat) { - if (message.action != null && !(message.action instanceof TLRPC.TL_messageActionEmpty) || isForwardedMessage(message) || message.via_bot_id != 0 || message.id < 0 || Math.abs(message.date - ConnectionsManager.getInstance().getCurrentTime()) > MessagesController.getInstance().maxEditTime) { + if (message == null || message.to_id == null || message.to_id.channel_id == 0 || message.action != null && !(message.action instanceof TLRPC.TL_messageActionEmpty) || isForwardedMessage(message) || message.via_bot_id != 0 || message.id < 0 || Math.abs(message.date - ConnectionsManager.getInstance().getCurrentTime()) > MessagesController.getInstance().maxEditTime) { return false; } if (chat == null && message.to_id.channel_id != 0) { chat = MessagesController.getInstance().getChat(message.to_id.channel_id); } - if (ChatObject.isChannel(chat) && chat.megagroup) { - return message.out; + if (chat == null) { + return false; } - if (ChatObject.isChannel(chat) && !chat.megagroup && (chat.creator || chat.editor && isOut(message)) && isImportant(message)) { + if (chat.megagroup && message.out || !chat.megagroup && (chat.creator || chat.editor && isOut(message)) && isImportant(message)) { if (message.media instanceof TLRPC.TL_messageMediaPhoto || message.media instanceof TLRPC.TL_messageMediaDocument && (isVideoMessage(message) || isGifDocument(message.media.document)) || message.media instanceof TLRPC.TL_messageMediaEmpty || diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java index c8080cc57b..c38da36442 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java @@ -24,6 +24,7 @@ import org.telegram.SQLite.SQLiteCursor; import org.telegram.messenger.query.BotQuery; +import org.telegram.messenger.query.MessagesQuery; import org.telegram.messenger.query.StickersQuery; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; @@ -70,7 +71,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter public ConcurrentHashMap onlinePrivacy = new ConcurrentHashMap<>(20, 1.0f, 2); private int lastPrintingStringCount = 0; - private long lastCreatedDialogId; + private HashMap loadingPeerSettings = new HashMap<>(); + + private ArrayList createdDialogIds = new ArrayList<>(); private SparseIntArray shortPollChannels = new SparseIntArray(); private SparseIntArray needShortPollChannels = new SparseIntArray(); @@ -93,6 +96,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter private long updatesStartWaitTimeSeq = 0; private long updatesStartWaitTimePts = 0; private long updatesStartWaitTimeQts = 0; + private HashMap fullUsersAbout = new HashMap<>(); private ArrayList loadingFullUsers = new ArrayList<>(); private ArrayList loadedFullUsers = new ArrayList<>(); private ArrayList loadingFullChats = new ArrayList<>(); @@ -130,7 +134,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter public int fontSize = AndroidUtilities.dp(16); public int maxGroupCount = 200; public int maxBroadcastCount = 100; - public int maxMegagroupCount = 1000; + public int maxMegagroupCount = 5000; public int minGroupConvertSize = 200; public int maxEditTime = 172800; public int groupBigSize; @@ -222,7 +226,7 @@ public MessagesController() { } public void updateConfig(final TLRPC.TL_config config) { - AndroidUtilities.runOnUIThread(new Runnable() { //TODO use new config params + AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { //maxBroadcastCount = config.broadcast_size_max; @@ -313,7 +317,7 @@ public static TLRPC.InputUser getInputUser(TLRPC.User user) { } public static TLRPC.InputUser getInputUser(int user_id) { - TLRPC.User user = MessagesController.getInstance().getUser(user_id); + TLRPC.User user = getInstance().getUser(user_id); return getInputUser(user); } @@ -470,6 +474,7 @@ public void cleanUp() { dialogs_dict.clear(); dialogs_read_inbox_max.clear(); exportedChats.clear(); + fullUsersAbout.clear(); dialogs.clear(); joiningToChannels.clear(); channelViewsToSend.clear(); @@ -486,6 +491,7 @@ public void cleanUp() { printingStrings.clear(); printingStringsTypes.clear(); onlinePrivacy.clear(); + loadingPeerSettings.clear(); lastPrintingStringCount = 0; nextDialogsCacheOffset = 0; Utilities.stageQueue.postRunnable(new Runnable() { @@ -497,7 +503,7 @@ public void run() { updatesStartWaitTimeSeq = 0; updatesStartWaitTimePts = 0; updatesStartWaitTimeQts = 0; - lastCreatedDialogId = 0; + createdDialogIds.clear(); gettingDifference = false; } }); @@ -596,9 +602,9 @@ public void setLastCreatedDialogId(final long dialog_id, final boolean set) { @Override public void run() { if (set) { - lastCreatedDialogId = dialog_id; - } else if (lastCreatedDialogId == dialog_id) { - lastCreatedDialogId = 0; + createdDialogIds.add(dialog_id); + } else { + createdDialogIds.remove(dialog_id); } } }); @@ -637,6 +643,13 @@ public boolean putUser(TLRPC.User user, boolean fromCache) { oldUser.last_name = null; oldUser.flags = oldUser.flags &~ 4; } + if (user.username != null) { + oldUser.username = user.username; + oldUser.flags |= 8; + } else { + oldUser.username = null; + oldUser.flags = oldUser.flags &~ 8; + } if (user.photo != null) { oldUser.photo = user.photo; oldUser.flags |= 32; @@ -660,6 +673,37 @@ public boolean putUser(TLRPC.User user, boolean fromCache) { } } else if (oldUser == null) { users.put(user.id, user); + } else if (oldUser.min) { + user.min = false; + if (oldUser.first_name != null) { + user.first_name = oldUser.first_name; + user.flags |= 2; + } else { + user.first_name = null; + user.flags = user.flags &~ 2; + } + if (oldUser.last_name != null) { + user.last_name = oldUser.last_name; + user.flags |= 4; + } else { + user.last_name = null; + user.flags = user.flags &~ 4; + } + if (oldUser.username != null) { + user.username = oldUser.username; + user.flags |= 8; + } else { + user.username = null; + user.flags = user.flags &~ 8; + } + if (oldUser.photo != null) { + user.photo = oldUser.photo; + user.flags |= 32; + } else { + user.photo = null; + user.flags = user.flags &~ 32; + } + users.put(user.id, user); } } return false; @@ -692,13 +736,52 @@ public void putChat(TLRPC.Chat chat, boolean fromCache) { return; } TLRPC.Chat oldChat = chats.get(chat.id); - if (!fromCache) { - if (oldChat != null && chat.version != oldChat.version) { - loadedFullChats.remove((Integer) chat.id); + + if (chat.min) { + if (oldChat != null) { + if (!fromCache) { + oldChat.title = chat.title; + oldChat.photo = chat.photo; + oldChat.broadcast = chat.broadcast; + oldChat.verified = chat.verified; + oldChat.megagroup = chat.megagroup; + oldChat.democracy = chat.democracy; + if (chat.username != null) { + oldChat.username = chat.username; + oldChat.flags |= 64; + } else { + oldChat.username = null; + oldChat.flags = oldChat.flags &~ 64; + } + } + } else { + chats.put(chat.id, chat); + } + } else { + if (!fromCache) { + if (oldChat != null && chat.version != oldChat.version) { + loadedFullChats.remove((Integer) chat.id); + } + chats.put(chat.id, chat); + } else if (oldChat == null) { + chats.put(chat.id, chat); + } else if (oldChat.min) { + chat.min = false; + chat.title = oldChat.title; + chat.photo = oldChat.photo; + chat.broadcast = oldChat.broadcast; + chat.verified = oldChat.verified; + chat.megagroup = oldChat.megagroup; + chat.democracy = oldChat.democracy; + if (oldChat.username != null) { + chat.username = oldChat.username; + chat.flags |= 64; + } else { + chat.username = null; + chat.flags = chat.flags &~ 64; + } + chats.put(chat.id, chat); } - chats.put(chat.id, chat); - } else if (oldChat == null) { - chats.put(chat.id, chat); } } @@ -735,6 +818,10 @@ public void putEncryptedChats(ArrayList encryptedChats, boo } } + public String getUserAbout(int uid) { + return fullUsersAbout.get(uid); + } + public void cancelLoadFullUser(int uid) { loadingFullUsers.remove((Integer) uid); } @@ -748,10 +835,6 @@ protected void clearFullUsers() { loadedFullChats.clear(); } - public void loadFullChat(final int chat_id, final int classGuid) { - loadFullChat(chat_id, classGuid, false); - } - public void loadFullChat(final int chat_id, final int classGuid, boolean force) { if (loadingFullChats.contains(chat_id) || !force && loadedFullChats.contains(chat_id)) { return; @@ -796,16 +879,14 @@ public void run() { putUsers(res.users, false); putChats(res.chats, false); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, res.full_chat, classGuid, false); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, res.full_chat, classGuid, false, null); } }); } else { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - if (error.text.equals("CHANNEL_PRIVATE")) { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoCantLoad, chat_id); - } + checkChannelError(error.text, chat_id); loadingFullChats.remove((Integer) chat_id); } }); @@ -817,8 +898,8 @@ public void run() { } } - public void loadFullUser(final TLRPC.User user, final int classGuid) { - if (user == null || loadingFullUsers.contains(user.id) || loadedFullUsers.contains(user.id)) { + public void loadFullUser(final TLRPC.User user, final int classGuid, boolean force) { + if (user == null || loadingFullUsers.contains(user.id) || !force && loadedFullUsers.contains(user.id)) { return; } loadingFullUsers.add(user.id); @@ -836,6 +917,11 @@ public void run() { if (userFull.bot_info instanceof TLRPC.TL_botInfo) { BotQuery.putBotInfo(userFull.bot_info); } + if (userFull.about != null && userFull.about.length() > 0) { + fullUsersAbout.put(user.id, userFull.about); + } else { + fullUsersAbout.remove(user.id); + } loadingFullUsers.remove((Integer) user.id); loadedFullUsers.add(user.id); String names = user.first_name + user.last_name + user.username; @@ -849,6 +935,7 @@ public void run() { if (userFull.bot_info instanceof TLRPC.TL_botInfo) { NotificationCenter.getInstance().postNotificationName(NotificationCenter.botInfoDidLoaded, userFull.bot_info, classGuid); } + NotificationCenter.getInstance().postNotificationName(NotificationCenter.userInfoDidLoaded, user.id); } }); } else { @@ -962,6 +1049,116 @@ public void run() { }); } + public void hideReportSpam(final long dialogId, TLRPC.User currentUser, TLRPC.Chat currentChat) { + if (currentUser == null && currentChat == null) { + return; + } + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putInt("spam3_" + dialogId, 1); + editor.commit(); + TLRPC.TL_messages_hideReportSpam req = new TLRPC.TL_messages_hideReportSpam(); + if (currentUser != null) { + req.peer = MessagesController.getInputPeer(currentUser.id); + } else if (currentChat != null) { + req.peer = MessagesController.getInputPeer(-currentChat.id); + } + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }); + } + + public void reportSpam(final long dialogId, TLRPC.User currentUser, TLRPC.Chat currentChat) { + if (currentUser == null && currentChat == null) { + return; + } + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putInt("spam3_" + dialogId, 1); + editor.commit(); + TLRPC.TL_messages_reportSpam req = new TLRPC.TL_messages_reportSpam(); + if (currentChat != null) { + req.peer = MessagesController.getInputPeer(-currentChat.id); + } else if (currentUser != null) { + req.peer = MessagesController.getInputPeer(currentUser.id); + } + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }, ConnectionsManager.RequestFlagFailOnServerErrors); + } + + public void loadPeerSettings(final long dialogId, TLRPC.User currentUser, TLRPC.Chat currentChat) { + if (loadingPeerSettings.containsKey(dialogId) || currentUser == null && currentChat == null) { + return; + } + loadingPeerSettings.put(dialogId, true); + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + if (preferences.getInt("spam3_" + dialogId, 0) == 1) { + return; + } + boolean hidden = preferences.getBoolean("spam_" + dialogId, false); + if (hidden) { + TLRPC.TL_messages_hideReportSpam req = new TLRPC.TL_messages_hideReportSpam(); + if (currentUser != null) { + req.peer = MessagesController.getInputPeer(currentUser.id); + } else if (currentChat != null) { + req.peer = MessagesController.getInputPeer(-currentChat.id); + } + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + loadingPeerSettings.remove(dialogId); + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.remove("spam_" + dialogId); + editor.putInt("spam3_" + dialogId, 1); + editor.commit(); + } + }); + } + }); + return; + } + TLRPC.TL_messages_getPeerSettings req = new TLRPC.TL_messages_getPeerSettings(); + if (currentUser != null) { + req.peer = MessagesController.getInputPeer(currentUser.id); + } else if (currentChat != null) { + req.peer = MessagesController.getInputPeer(-currentChat.id); + } + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + loadingPeerSettings.remove(dialogId); + if (response != null) { + TLRPC.TL_peerSettings res = (TLRPC.TL_peerSettings) response; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + if (!res.report_spam) { + editor.putInt("spam3_" + dialogId, 1); + } else { + editor.putInt("spam3_" + dialogId, 2); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.peerSettingsDidLoaded, dialogId); + } + editor.commit(); + } + } + }); + } + }); + } + protected void processNewChannelDifferenceParams(int pts, int pts_count, int channelId) { FileLog.e("tmessages", "processNewChannelDifferenceParams pts = " + pts + " pts_count = " + pts_count + " channeldId = " + channelId); TLRPC.Dialog dialog = dialogs_dict.get((long) -channelId); @@ -1168,13 +1365,13 @@ public void run(TLObject response, TLRPC.TL_error error) { public void blockUser(int user_id) { final TLRPC.User user = getUser(user_id); - if (user == null || MessagesController.getInstance().blockedUsers.contains(user_id)) { + if (user == null || blockedUsers.contains(user_id)) { return; } blockedUsers.add(user_id); NotificationCenter.getInstance().postNotificationName(NotificationCenter.blockedUsersDidLoaded); TLRPC.TL_contacts_block req = new TLRPC.TL_contacts_block(); - req.id = MessagesController.getInputUser(user); + req.id = getInputUser(user); ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(TLObject response, TLRPC.TL_error error) { @@ -1189,12 +1386,12 @@ public void run(TLObject response, TLRPC.TL_error error) { public void unblockUser(int user_id) { TLRPC.TL_contacts_unblock req = new TLRPC.TL_contacts_unblock(); - final TLRPC.User user = MessagesController.getInstance().getUser(user_id); + final TLRPC.User user = getUser(user_id); if (user == null) { return; } blockedUsers.remove((Integer) user.id); - req.id = MessagesController.getInputUser(user); + req.id = getInputUser(user); NotificationCenter.getInstance().postNotificationName(NotificationCenter.blockedUsersDidLoaded); ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override @@ -1240,7 +1437,7 @@ public void processLoadedBlockedUsers(final ArrayList ids, final ArrayL @Override public void run() { if (users != null) { - MessagesController.getInstance().putUsers(users, cache); + putUsers(users, cache); } loadingBlockedUsers = false; if (ids.isEmpty() && cache && !UserConfig.blockedUsersLoaded) { @@ -1262,7 +1459,7 @@ public void deleteUserPhoto(TLRPC.InputPhoto photo) { req.id = new TLRPC.TL_inputPhotoEmpty(); req.crop = new TLRPC.TL_inputPhotoCropAuto(); UserConfig.getCurrentUser().photo = new TLRPC.TL_userProfilePhotoEmpty(); - TLRPC.User user = MessagesController.getInstance().getUser(UserConfig.getClientUserId()); + TLRPC.User user = getUser(UserConfig.getClientUserId()); if (user == null) { user = UserConfig.getCurrentUser(); } @@ -1271,15 +1468,15 @@ public void deleteUserPhoto(TLRPC.InputPhoto photo) { } user.photo = UserConfig.getCurrentUser().photo; NotificationCenter.getInstance().postNotificationName(NotificationCenter.mainUserInfoChanged); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, MessagesController.UPDATE_MASK_ALL); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_ALL); ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(TLObject response, TLRPC.TL_error error) { if (error == null) { - TLRPC.User user = MessagesController.getInstance().getUser(UserConfig.getClientUserId()); + TLRPC.User user = getUser(UserConfig.getClientUserId()); if (user == null) { user = UserConfig.getCurrentUser(); - MessagesController.getInstance().putUser(user, false); + putUser(user, false); } else { UserConfig.setCurrentUser(user); } @@ -1295,7 +1492,7 @@ public void run(TLObject response, TLRPC.TL_error error) { @Override public void run() { NotificationCenter.getInstance().postNotificationName(NotificationCenter.mainUserInfoChanged); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, MessagesController.UPDATE_MASK_ALL); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_ALL); UserConfig.saveConfig(true); } }); @@ -1338,6 +1535,19 @@ public void uploadAndApplyUserAvatar(TLRPC.PhotoSize bigPhoto) { } } + public void markChannelDialogMessageAsDeleted(ArrayList messages, final int channelId) { + MessageObject obj = dialogMessage.get((long) -channelId); + if (obj != null) { + for (int a = 0; a < messages.size(); a++) { + Integer id = messages.get(a); + if (obj.getId() == id) { + obj.deleted = true; + break; + } + } + } + } + public void deleteMessages(ArrayList messages, ArrayList randoms, TLRPC.EncryptedChat encryptedChat, final int channelId) { if (messages == null || messages.isEmpty()) { return; @@ -1351,16 +1561,7 @@ public void deleteMessages(ArrayList messages, ArrayList randoms, } } } else { - MessageObject obj = dialogMessage.get((long) -channelId); - if (obj != null) { - for (int a = 0; a < messages.size(); a++) { - Integer id = messages.get(a); - if (obj.getId() == id) { - obj.deleted = true; - break; - } - } - } + markChannelDialogMessageAsDeleted(messages, channelId); } ArrayList toSend = new ArrayList<>(); for (int a = 0; a < messages.size(); a++) { @@ -1403,10 +1604,47 @@ public void run(TLObject response, TLRPC.TL_error error) { } } + public void pinChannelMessage(TLRPC.Chat chat, int id, boolean notify) { + TLRPC.TL_channels_updatePinnedMessage req = new TLRPC.TL_channels_updatePinnedMessage(); + req.channel = getInputChannel(chat); + req.id = id; + req.silent = !notify; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error == null) { + TLRPC.Updates updates = (TLRPC.Updates) response; + processUpdates(updates, false); + } + } + }); + } + public void deleteDialog(final long did, final int onlyHistory) { deleteDialog(did, true, onlyHistory, 0); } + public void deleteUserChannelHistory(final TLRPC.Chat chat, final TLRPC.User user, int offset) { + if (offset == 0) { + MessagesStorage.getInstance().deleteUserChannelHistory(chat.id, user.id); + } + TLRPC.TL_channels_deleteUserHistory req = new TLRPC.TL_channels_deleteUserHistory(); + req.channel = getInputChannel(chat); + req.user_id = getInputUser(user); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error == null) { + TLRPC.TL_messages_affectedHistory res = (TLRPC.TL_messages_affectedHistory) response; + if (res.offset > 0) { + deleteUserChannelHistory(chat, user, res.offset); + } + processNewChannelDifferenceParams(res.pts, res.pts_count, chat.id); + } + } + }); + } + private void deleteDialog(final long did, final boolean first, final int onlyHistory, final int max_id) { int lower_part = (int) did; int high_id = (int) (did >> 32); @@ -1502,6 +1740,30 @@ public void run(TLObject response, TLRPC.TL_error error) { } } + public MediaController.SearchImage saveGif(TLRPC.Document document) { + MediaController.SearchImage searchImage = new MediaController.SearchImage(); + searchImage.type = 2; + searchImage.document = document; + searchImage.date = (int) (System.currentTimeMillis() / 1000); + searchImage.id = "" + searchImage.document.id; + + ArrayList arrayList = new ArrayList<>(); + arrayList.add(searchImage); + MessagesStorage.getInstance().putWebRecent(arrayList); + TLRPC.TL_messages_saveGif req = new TLRPC.TL_messages_saveGif(); + req.id = new TLRPC.TL_inputDocument(); + req.id.id = searchImage.document.id; + req.id.access_hash = searchImage.document.access_hash; + req.unsave = false; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }); + return searchImage; + } + public void loadChannelParticipants(final Integer chat_id) { if (loadingFullParticipants.contains(chat_id) || loadedFullParticipants.contains(chat_id)) { return; @@ -1509,7 +1771,7 @@ public void loadChannelParticipants(final Integer chat_id) { loadingFullParticipants.add(chat_id); final TLRPC.TL_channels_getParticipants req = new TLRPC.TL_channels_getParticipants(); - req.channel = MessagesController.getInputChannel(chat_id); + req.channel = getInputChannel(chat_id); req.filter = new TLRPC.TL_channelParticipantsRecent(); req.offset = 0; req.limit = 32; @@ -1537,7 +1799,7 @@ public void loadChatInfo(final int chat_id, Semaphore semaphore, boolean force) MessagesStorage.getInstance().loadChatInfo(chat_id, semaphore, force, false); } - public void processChatInfo(int chat_id, final TLRPC.ChatFull info, final ArrayList usersArr, final boolean fromCache, boolean force, final boolean byChannelUsers) { + public void processChatInfo(int chat_id, final TLRPC.ChatFull info, final ArrayList usersArr, final boolean fromCache, boolean force, final boolean byChannelUsers, final MessageObject pinnedMessageObject) { if (fromCache && chat_id > 0 && !byChannelUsers) { loadFullChat(chat_id, 0, force); } @@ -1546,7 +1808,7 @@ public void processChatInfo(int chat_id, final TLRPC.ChatFull info, final ArrayL @Override public void run() { putUsers(usersArr, fromCache); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, info, 0, byChannelUsers); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, info, 0, byChannelUsers, pinnedMessageObject); } }); } @@ -1874,7 +2136,7 @@ public void sendTyping(final long dialog_id, final int action, int classGuid) { TLRPC.TL_messages_setTyping req = new TLRPC.TL_messages_setTyping(); req.peer = getInputPeer(lower_part); if (req.peer instanceof TLRPC.TL_inputPeerChannel) { - TLRPC.Chat chat = MessagesController.getInstance().getChat(req.peer.channel_id); + TLRPC.Chat chat = getChat(req.peer.channel_id); if (chat == null || !chat.megagroup) { return; } @@ -2007,9 +2269,6 @@ public void run(TLObject response, TLRPC.TL_error error) { } public void reloadWebPages(final long dialog_id, HashMap> webpagesToReload) { - //if (secretWebpagePreview != 1) { - // return; - //} for (HashMap.Entry> entry : webpagesToReload.entrySet()) { final String url = entry.getKey(); final ArrayList messages = entry.getValue(); @@ -2077,7 +2336,11 @@ public void run() { if (channelPts == 0) { channelsPts.put(channelId, messagesRes.pts); createDialog = true; - getChannelDifference(channelId); + if (needShortPollChannels.indexOfKey(channelId) >= 0 && shortPollChannels.indexOfKey(channelId) < 0) { + getChannelDifference(channelId, 2); + } else { + getChannelDifference(channelId); + } } } for (int a = 0; a < messagesRes.chats.size(); a++) { @@ -2222,7 +2485,7 @@ public void loadDialogs(final int offset, final int count, boolean fromCache) { } else { id = message.messageOwner.to_id.user_id; } - req.offset_peer = MessagesController.getInputPeer(id); + req.offset_peer = getInputPeer(id); found = true; break; } @@ -2387,7 +2650,7 @@ public void run() { } cursor.dispose(); - MessagesController.getInstance().processLoadedDialogs(dialogsRes, null, offsetId, 0, false, false, true); + processLoadedDialogs(dialogsRes, null, offsetId, 0, false, false, true); } catch (Exception e) { FileLog.e("tmessages", e); AndroidUtilities.runOnUIThread(new Runnable() { @@ -2591,6 +2854,9 @@ public void run() { } } } else { + if (!isCache) { + currentDialog.notify_settings = value.notify_settings; + } MessageObject oldMsg = dialogMessage.get(key); if (oldMsg != null && oldMsg.deleted || oldMsg == null || currentDialog.top_message > 0) { if (value.top_message >= currentDialog.top_message) { @@ -3250,7 +3516,11 @@ public void run(TLObject response, final TLRPC.TL_error error) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - AlertsCreator.showAddUserAlert(error.text, fragment, false); + if (error.text.startsWith("FLOOD_WAIT")) { + AlertsCreator.showFloodWaitAlert(error.text, fragment); + } else { + AlertsCreator.showAddUserAlert(error.text, fragment, false); + } NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatDidFailCreate); } }); @@ -3271,7 +3541,7 @@ public void run() { } }); } - }); + }, ConnectionsManager.RequestFlagFailOnServerErrors); } else if (type == ChatObject.CHAT_TYPE_CHANNEL || type == ChatObject.CHAT_TYPE_MEGAGROUP) { TLRPC.TL_channels_createChannel req = new TLRPC.TL_channels_createChannel(); req.title = title; @@ -3283,11 +3553,14 @@ public void run() { } return ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override - public void run(TLObject response, TLRPC.TL_error error) { + public void run(TLObject response, final TLRPC.TL_error error) { if (error != null) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { + if (error.text.startsWith("FLOOD_WAIT")) { + AlertsCreator.showFloodWaitAlert(error.text, fragment); + } NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatDidFailCreate); } }); @@ -3308,7 +3581,7 @@ public void run() { } }); } - }); + }, ConnectionsManager.RequestFlagFailOnServerErrors); } return 0; } @@ -3475,7 +3748,7 @@ public void run(TLObject response, TLRPC.TL_error error) { public void run() { info.about = about; MessagesStorage.getInstance().updateChatInfo(info, false); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, info, 0, false); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, info, 0, false, null); } }); } @@ -3569,7 +3842,7 @@ public void addUserToChat(final int chat_id, final TLRPC.User user, final TLRPC. TLObject request; final boolean isChannel = ChatObject.isChannel(chat_id); - final boolean isMegagroup = isChannel && MessagesController.getInstance().getChat(chat_id).megagroup; + final boolean isMegagroup = isChannel && getChat(chat_id).megagroup; final TLRPC.InputUser inputUser = getInputUser(user); if (botHash == null || isChannel && !isMegagroup) { if (isChannel) { @@ -3585,7 +3858,7 @@ public void addUserToChat(final int chat_id, final TLRPC.User user, final TLRPC. if (user.bot && !isMegagroup) { TLRPC.TL_channels_editAdmin req = new TLRPC.TL_channels_editAdmin(); req.channel = getInputChannel(chat_id); - req.user_id = MessagesController.getInputUser(user); + req.user_id = getInputUser(user); req.role = new TLRPC.TL_channelRoleEditor(); request = req; } else { @@ -3642,9 +3915,20 @@ public void run() { }); return; } - processUpdates((TLRPC.Updates) response, false); + boolean hasJoinMessage = false; + TLRPC.Updates updates = (TLRPC.Updates) response; + for (int a = 0; a < updates.updates.size(); a++) { + TLRPC.Update update = updates.updates.get(a); + if (update instanceof TLRPC.TL_updateNewChannelMessage) { + if (((TLRPC.TL_updateNewChannelMessage) update).message.action instanceof TLRPC.TL_messageActionChatAddUser) { + hasJoinMessage = true; + break; + } + } + } + processUpdates(updates, false); if (isChannel) { - if (inputUser instanceof TLRPC.TL_inputUserSelf) { + if (!hasJoinMessage && inputUser instanceof TLRPC.TL_inputUserSelf) { generateJoinMessage(chat_id, true); } AndroidUtilities.runOnUIThread(new Runnable() { @@ -3679,7 +3963,7 @@ public void run() { newPart.date = ConnectionsManager.getInstance().getCurrentTime(); info.participants.participants.add(0, newPart); MessagesStorage.getInstance().updateChatInfo(info, true); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, info, 0, false); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, info, 0, false, null); NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT_MEMBERS); } } @@ -3725,7 +4009,7 @@ public void run(TLObject response, TLRPC.TL_error error) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - MessagesController.getInstance().deleteDialog(-chat_id, 0); + deleteDialog(-chat_id, 0); } }); } @@ -3763,7 +4047,7 @@ public void run() { } if (changed) { MessagesStorage.getInstance().updateChatInfo(info, true); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, info, 0, false); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, info, 0, false, null); } NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT_MEMBERS); } @@ -4252,8 +4536,7 @@ public void getChannelDifference(final int channelId, final int newDialogType) { channelsPts.put(channelId, channelPts); } if (channelPts == 0 && newDialogType == 2) { - channelPts = 1; - limit = 1; + return; } } if (channelPts == 0) { @@ -4269,7 +4552,7 @@ public void getChannelDifference(final int channelId, final int newDialogType) { FileLog.e("tmessages", "start getChannelDifference with pts = " + channelPts + " channelId = " + channelId); ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override - public void run(TLObject response, TLRPC.TL_error error) { + public void run(TLObject response, final TLRPC.TL_error error) { if (error == null) { final TLRPC.updates_ChannelDifference res = (TLRPC.updates_ChannelDifference) response; @@ -4301,6 +4584,7 @@ public void run(TLObject response, TLRPC.TL_error error) { } } + MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true); AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { @@ -4368,7 +4652,7 @@ public void run() { value = MessagesStorage.getInstance().getChannelReadInboxMax(channelId); } - MessageObject obj = new MessageObject(message, usersDict, dialog_id == lastCreatedDialogId); + MessageObject obj = new MessageObject(message, usersDict, createdDialogIds.contains(dialog_id)); if (channelFinal != null && channelFinal.left || value >= obj.getId()) { obj.setIsRead(); obj.setContentIsRead(); @@ -4409,10 +4693,7 @@ public void run() { } }); } - MessagesStorage.getInstance().startTransaction(false); - MessagesStorage.getInstance().putMessages(res.new_messages, false, false, false, MediaController.getInstance().getAutodownloadMask()); - MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, false, false); - MessagesStorage.getInstance().commitTransaction(false); + MessagesStorage.getInstance().putMessages(res.new_messages, true, false, false, MediaController.getInstance().getAutodownloadMask()); } }); } @@ -4448,7 +4729,6 @@ public void run() { message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; } } - MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true); if (channelFinal != null && channelFinal.megagroup) { res.unread_important_count = Math.max(res.unread_count, res.unread_important_count); res.top_important_message = Math.max(res.top_important_message, res.top_message); @@ -4471,20 +4751,32 @@ public void run() { } }); } else { - if (error.text.equals("CHANNEL_PRIVATE")) { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoCantLoad, channelId); - } - }); - } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + checkChannelError(error.text, channelId); + } + }); gettingDifferenceChannels.remove(channelId); } } }); } + private void checkChannelError(String text, int channelId) { + switch (text) { + case "CHANNEL_PRIVATE": + NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoCantLoad, channelId, 0); + break; + case "CHANNEL_PUBLIC_GROUP_NA": + NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoCantLoad, channelId, 1); + break; + case "USER_BANNED_IN_CHANNEL": + NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoCantLoad, channelId, 2); + break; + } + } + public void getDifference() { getDifference(MessagesStorage.lastPtsValue, MessagesStorage.lastDateValue, MessagesStorage.lastQtsValue, false); } @@ -4625,7 +4917,7 @@ public void run() { } } - MessageObject obj = new MessageObject(message, usersDict, chatsDict, uid == lastCreatedDialogId); + MessageObject obj = new MessageObject(message, usersDict, chatsDict, createdDialogIds.contains(uid)); if (!obj.isOut() && obj.isUnread()) { pushMessages.add(obj); @@ -4715,7 +5007,7 @@ public void run() { public void generateJoinMessage(final int chat_id, boolean ignoreLeft) { TLRPC.Chat chat = getChat(chat_id); - if (chat == null || !ChatObject.isChannel(chat_id) || chat.megagroup || (chat.left || chat.kicked) && !ignoreLeft) { + if (chat == null || !ChatObject.isChannel(chat_id) || (chat.left || chat.kicked) && !ignoreLeft) { return; } @@ -4723,12 +5015,16 @@ public void generateJoinMessage(final int chat_id, boolean ignoreLeft) { message.flags = TLRPC.MESSAGE_FLAG_HAS_FROM_ID; message.local_id = message.id = UserConfig.getNewMessageId(); message.date = ConnectionsManager.getInstance().getCurrentTime(); - message.from_id = -chat_id; + message.from_id = UserConfig.getClientUserId(); message.to_id = new TLRPC.TL_peerChannel(); message.to_id.channel_id = chat_id; message.dialog_id = -chat_id; + message.post = true; message.action = new TLRPC.TL_messageActionChatAddUser(); message.action.users.add(UserConfig.getClientUserId()); + if (chat.megagroup) { + message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; + } UserConfig.saveConfig(false); final ArrayList pushMessages = new ArrayList<>(); @@ -4795,19 +5091,15 @@ public void run() { message.media_unread = true; message.unread = true; message.flags = TLRPC.MESSAGE_FLAG_HAS_FROM_ID; + message.post = true; if (chat.megagroup) { message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; } message.local_id = message.id = UserConfig.getNewMessageId(); message.date = res.participant.date; message.action = new TLRPC.TL_messageActionChatAddUser(); - if (chat.megagroup) { - message.from_id = res.participant.inviter_id; - message.action.users.add(UserConfig.getClientUserId()); - } else { - message.from_id = -chat_id; - message.action.users.add(res.participant.inviter_id); - } + message.from_id = res.participant.inviter_id; + message.action.users.add(UserConfig.getClientUserId()); message.to_id = new TLRPC.TL_peerChannel(); message.to_id.channel_id = chat_id; message.dialog_id = -chat_id; @@ -4968,7 +5260,7 @@ public void processUpdates(final TLRPC.Updates updates, boolean fromQueue) { message.reply_to_msg_id = updates.reply_to_msg_id; message.media = new TLRPC.TL_messageMediaEmpty(); MessagesStorage.lastPtsValue = updates.pts; - final MessageObject obj = new MessageObject(message, null, message.dialog_id == lastCreatedDialogId); + final MessageObject obj = new MessageObject(message, null, createdDialogIds.contains(message.dialog_id)); final ArrayList objArr = new ArrayList<>(); objArr.add(obj); ArrayList arr = new ArrayList<>(); @@ -5034,198 +5326,233 @@ public void run() { } } } else if (updates instanceof TLRPC.TL_updatesCombined || updates instanceof TLRPC.TL_updates) { - MessagesStorage.getInstance().putUsersAndChats(updates.users, updates.chats, true, true); - Collections.sort(updates.updates, new Comparator() { - @Override - public int compare(TLRPC.Update lhs, TLRPC.Update rhs) { - int ltype = getUpdateType(lhs); - int rtype = getUpdateType(rhs); - if (ltype != rtype) { - return AndroidUtilities.compare(ltype, rtype); - } else if (ltype == 0) { - return AndroidUtilities.compare(lhs.pts, rhs.pts); - } else if (ltype == 1) { - return AndroidUtilities.compare(lhs.qts, rhs.qts); - } - return 0; - } - }); - for (int a = 0; a < updates.updates.size(); a++) { - TLRPC.Update update = updates.updates.get(a); - if (getUpdateType(update) == 0) { - TLRPC.TL_updates updatesNew = new TLRPC.TL_updates(); - updatesNew.updates.add(update); - updatesNew.pts = update.pts; - updatesNew.pts_count = update.pts_count; - for (int b = a + 1; b < updates.updates.size(); b++) { - TLRPC.Update update2 = updates.updates.get(b); - if (getUpdateType(update2) == 0 && updatesNew.pts + update2.pts_count == update2.pts) { - updatesNew.updates.add(update2); - updatesNew.pts = update2.pts; - updatesNew.pts_count += update2.pts_count; - updates.updates.remove(b); - b--; - } else { - break; + HashMap minChannels = null; + for (int a = 0; a < updates.chats.size(); a++) { + TLRPC.Chat chat = updates.chats.get(a); + if (chat instanceof TLRPC.TL_channel && chat.min) { + TLRPC.Chat existChat = getChat(chat.id); + if (existChat == null || existChat.min) { + TLRPC.Chat cacheChat = MessagesStorage.getInstance().getChatSync(updates.chat_id); + if (existChat == null) { + putChat(cacheChat, true); } + existChat = cacheChat; } - if (MessagesStorage.lastPtsValue + updatesNew.pts_count == updatesNew.pts) { - if (!processUpdateArray(updatesNew.updates, updates.users, updates.chats)) { - FileLog.e("tmessages", "need get diff inner TL_updates, seq: " + MessagesStorage.lastSeqValue + " " + updates.seq); - needGetDiff = true; - } else { - MessagesStorage.lastPtsValue = updatesNew.pts; + if (existChat == null || existChat.min) { + if (minChannels == null) { + minChannels = new HashMap<>(); } - } else if (MessagesStorage.lastPtsValue != updatesNew.pts) { - FileLog.e("tmessages", update + " need get diff, pts: " + MessagesStorage.lastPtsValue + " " + updatesNew.pts + " count = " + updatesNew.pts_count); - if (gettingDifference || updatesStartWaitTimePts == 0 || updatesStartWaitTimePts != 0 && Math.abs(System.currentTimeMillis() - updatesStartWaitTimePts) <= 1500) { - if (updatesStartWaitTimePts == 0) { - updatesStartWaitTimePts = System.currentTimeMillis(); - } - FileLog.e("tmessages", "add to queue"); - updatesQueuePts.add(updatesNew); - } else { + minChannels.put(chat.id, chat); + } + } + } + if (minChannels != null) { + for (int a = 0; a < updates.updates.size(); a++) { + TLRPC.Update update = updates.updates.get(a); + if (update instanceof TLRPC.TL_updateNewChannelMessage) { + int channelId = ((TLRPC.TL_updateNewChannelMessage) update).message.to_id.channel_id; + if (minChannels.containsKey(channelId)) { + FileLog.e("tmessages", "need get diff because of min channel " + channelId); needGetDiff = true; + break; } } - } else if (getUpdateType(update) == 1) { - TLRPC.TL_updates updatesNew = new TLRPC.TL_updates(); - updatesNew.updates.add(update); - updatesNew.pts = update.qts; - for (int b = a + 1; b < updates.updates.size(); b++) { - TLRPC.Update update2 = updates.updates.get(b); - if (getUpdateType(update2) == 1 && updatesNew.pts + 1 == update2.qts) { - updatesNew.updates.add(update2); - updatesNew.pts = update2.qts; - updates.updates.remove(b); - b--; - } else { - break; + } + } + if (!needGetDiff) { + MessagesStorage.getInstance().putUsersAndChats(updates.users, updates.chats, true, true); + Collections.sort(updates.updates, new Comparator() { + @Override + public int compare(TLRPC.Update lhs, TLRPC.Update rhs) { + int ltype = getUpdateType(lhs); + int rtype = getUpdateType(rhs); + if (ltype != rtype) { + return AndroidUtilities.compare(ltype, rtype); + } else if (ltype == 0 || ltype == 2) { + return AndroidUtilities.compare(lhs.pts, rhs.pts); + } else if (ltype == 1) { + return AndroidUtilities.compare(lhs.qts, rhs.qts); } + return 0; } - if (MessagesStorage.lastQtsValue == 0 || MessagesStorage.lastQtsValue + updatesNew.updates.size() == updatesNew.pts) { - processUpdateArray(updatesNew.updates, updates.users, updates.chats); - MessagesStorage.lastQtsValue = updatesNew.pts; - needReceivedQueue = true; - } else if (MessagesStorage.lastPtsValue != updatesNew.pts) { - FileLog.e("tmessages", update + " need get diff, qts: " + MessagesStorage.lastQtsValue + " " + updatesNew.pts); - if (gettingDifference || updatesStartWaitTimeQts == 0 || updatesStartWaitTimeQts != 0 && Math.abs(System.currentTimeMillis() - updatesStartWaitTimeQts) <= 1500) { - if (updatesStartWaitTimeQts == 0) { - updatesStartWaitTimeQts = System.currentTimeMillis(); + }); + for (int a = 0; a < updates.updates.size(); a++) { + TLRPC.Update update = updates.updates.get(a); + if (getUpdateType(update) == 0) { + TLRPC.TL_updates updatesNew = new TLRPC.TL_updates(); + updatesNew.updates.add(update); + updatesNew.pts = update.pts; + updatesNew.pts_count = update.pts_count; + for (int b = a + 1; b < updates.updates.size(); b++) { + TLRPC.Update update2 = updates.updates.get(b); + if (getUpdateType(update2) == 0 && updatesNew.pts + update2.pts_count == update2.pts) { + updatesNew.updates.add(update2); + updatesNew.pts = update2.pts; + updatesNew.pts_count += update2.pts_count; + updates.updates.remove(b); + b--; + } else { + break; } - FileLog.e("tmessages", "add to queue"); - updatesQueueQts.add(updatesNew); - } else { - needGetDiff = true; } - } - } else if (getUpdateType(update) == 2) { - int channelId; - if (update instanceof TLRPC.TL_updateNewChannelMessage) { - channelId = ((TLRPC.TL_updateNewChannelMessage) update).message.to_id.channel_id; - } else if (update instanceof TLRPC.TL_updateEditChannelMessage) { - channelId = ((TLRPC.TL_updateEditChannelMessage) update).message.to_id.channel_id; - } else { - channelId = update.channel_id; - } - Integer channelPts = channelsPts.get(channelId); - if (channelPts == null) { - channelPts = MessagesStorage.getInstance().getChannelPtsSync(channelId); - if (channelPts == 0) { - channelPts = update.pts - update.pts_count; - } - channelsPts.put(channelId, channelPts); - } - TLRPC.TL_updates updatesNew = new TLRPC.TL_updates(); - updatesNew.updates.add(update); - updatesNew.pts = update.pts; - updatesNew.pts_count = update.pts_count; - for (int b = a + 1; b < updates.updates.size(); b++) { - TLRPC.Update update2 = updates.updates.get(b); - if (getUpdateType(update2) == 2 && updatesNew.pts + update2.pts_count == update2.pts) { - updatesNew.updates.add(update2); - updatesNew.pts = update2.pts; - updatesNew.pts_count += update2.pts_count; - updates.updates.remove(b); - b--; - } else { - break; + if (MessagesStorage.lastPtsValue + updatesNew.pts_count == updatesNew.pts) { + if (!processUpdateArray(updatesNew.updates, updates.users, updates.chats)) { + FileLog.e("tmessages", "need get diff inner TL_updates, seq: " + MessagesStorage.lastSeqValue + " " + updates.seq); + needGetDiff = true; + } else { + MessagesStorage.lastPtsValue = updatesNew.pts; + } + } else if (MessagesStorage.lastPtsValue != updatesNew.pts) { + FileLog.e("tmessages", update + " need get diff, pts: " + MessagesStorage.lastPtsValue + " " + updatesNew.pts + " count = " + updatesNew.pts_count); + if (gettingDifference || updatesStartWaitTimePts == 0 || updatesStartWaitTimePts != 0 && Math.abs(System.currentTimeMillis() - updatesStartWaitTimePts) <= 1500) { + if (updatesStartWaitTimePts == 0) { + updatesStartWaitTimePts = System.currentTimeMillis(); + } + FileLog.e("tmessages", "add to queue"); + updatesQueuePts.add(updatesNew); + } else { + needGetDiff = true; + } } - } - if (channelPts + updatesNew.pts_count == updatesNew.pts) { - if (!processUpdateArray(updatesNew.updates, updates.users, updates.chats)) { - FileLog.e("tmessages", "need get channel diff inner TL_updates, channel_id = " + channelId); - if (needGetChannelsDiff == null) { - needGetChannelsDiff = new ArrayList<>(); - } else if (!needGetChannelsDiff.contains(channelId)) { - needGetChannelsDiff.add(channelId); + } else if (getUpdateType(update) == 1) { + TLRPC.TL_updates updatesNew = new TLRPC.TL_updates(); + updatesNew.updates.add(update); + updatesNew.pts = update.qts; + for (int b = a + 1; b < updates.updates.size(); b++) { + TLRPC.Update update2 = updates.updates.get(b); + if (getUpdateType(update2) == 1 && updatesNew.pts + 1 == update2.qts) { + updatesNew.updates.add(update2); + updatesNew.pts = update2.qts; + updates.updates.remove(b); + b--; + } else { + break; + } + } + if (MessagesStorage.lastQtsValue == 0 || MessagesStorage.lastQtsValue + updatesNew.updates.size() == updatesNew.pts) { + processUpdateArray(updatesNew.updates, updates.users, updates.chats); + MessagesStorage.lastQtsValue = updatesNew.pts; + needReceivedQueue = true; + } else if (MessagesStorage.lastPtsValue != updatesNew.pts) { + FileLog.e("tmessages", update + " need get diff, qts: " + MessagesStorage.lastQtsValue + " " + updatesNew.pts); + if (gettingDifference || updatesStartWaitTimeQts == 0 || updatesStartWaitTimeQts != 0 && Math.abs(System.currentTimeMillis() - updatesStartWaitTimeQts) <= 1500) { + if (updatesStartWaitTimeQts == 0) { + updatesStartWaitTimeQts = System.currentTimeMillis(); + } + FileLog.e("tmessages", "add to queue"); + updatesQueueQts.add(updatesNew); + } else { + needGetDiff = true; } + } + } else if (getUpdateType(update) == 2) { + int channelId; + if (update instanceof TLRPC.TL_updateNewChannelMessage) { + channelId = ((TLRPC.TL_updateNewChannelMessage) update).message.to_id.channel_id; + } else if (update instanceof TLRPC.TL_updateEditChannelMessage) { + channelId = ((TLRPC.TL_updateEditChannelMessage) update).message.to_id.channel_id; } else { - channelsPts.put(channelId, updatesNew.pts); - MessagesStorage.getInstance().saveChannelPts(channelId, updatesNew.pts); - } - } else if (channelPts != updatesNew.pts) { - FileLog.e("tmessages", update + " need get channel diff, pts: " + channelPts + " " + updatesNew.pts + " count = " + updatesNew.pts_count + " channelId = " + channelId); - Long updatesStartWaitTime = updatesStartWaitTimeChannels.get(channelId); - Boolean gettingDifferenceChannel = gettingDifferenceChannels.get(channelId); - if (gettingDifferenceChannel == null) { - gettingDifferenceChannel = false; - } - if (gettingDifferenceChannel || updatesStartWaitTime == null || Math.abs(System.currentTimeMillis() - updatesStartWaitTime) <= 1500) { - if (updatesStartWaitTime == null) { - updatesStartWaitTimeChannels.put(channelId, System.currentTimeMillis()); + channelId = update.channel_id; + } + Integer channelPts = channelsPts.get(channelId); + if (channelPts == null) { + channelPts = MessagesStorage.getInstance().getChannelPtsSync(channelId); + if (channelPts == 0) { + channelPts = update.pts - update.pts_count; } - FileLog.e("tmessages", "add to queue"); - ArrayList arrayList = updatesQueueChannels.get(channelId); - if (arrayList == null) { - arrayList = new ArrayList<>(); - updatesQueueChannels.put(channelId, arrayList); + channelsPts.put(channelId, channelPts); + } + TLRPC.TL_updates updatesNew = new TLRPC.TL_updates(); + updatesNew.updates.add(update); + updatesNew.pts = update.pts; + updatesNew.pts_count = update.pts_count; + for (int b = a + 1; b < updates.updates.size(); b++) { + TLRPC.Update update2 = updates.updates.get(b); + if (getUpdateType(update2) == 2 && updatesNew.pts + update2.pts_count == update2.pts) { + updatesNew.updates.add(update2); + updatesNew.pts = update2.pts; + updatesNew.pts_count += update2.pts_count; + updates.updates.remove(b); + b--; + } else { + break; } - arrayList.add(updatesNew); - } else { - if (needGetChannelsDiff == null) { - needGetChannelsDiff = new ArrayList<>(); - } else if (!needGetChannelsDiff.contains(channelId)) { - needGetChannelsDiff.add(channelId); + } + if (channelPts + updatesNew.pts_count == updatesNew.pts) { + if (!processUpdateArray(updatesNew.updates, updates.users, updates.chats)) { + FileLog.e("tmessages", "need get channel diff inner TL_updates, channel_id = " + channelId); + if (needGetChannelsDiff == null) { + needGetChannelsDiff = new ArrayList<>(); + } else if (!needGetChannelsDiff.contains(channelId)) { + needGetChannelsDiff.add(channelId); + } + } else { + channelsPts.put(channelId, updatesNew.pts); + MessagesStorage.getInstance().saveChannelPts(channelId, updatesNew.pts); + } + } else if (channelPts != updatesNew.pts) { + FileLog.e("tmessages", update + " need get channel diff, pts: " + channelPts + " " + updatesNew.pts + " count = " + updatesNew.pts_count + " channelId = " + channelId); + Long updatesStartWaitTime = updatesStartWaitTimeChannels.get(channelId); + Boolean gettingDifferenceChannel = gettingDifferenceChannels.get(channelId); + if (gettingDifferenceChannel == null) { + gettingDifferenceChannel = false; + } + if (gettingDifferenceChannel || updatesStartWaitTime == null || Math.abs(System.currentTimeMillis() - updatesStartWaitTime) <= 1500) { + if (updatesStartWaitTime == null) { + updatesStartWaitTimeChannels.put(channelId, System.currentTimeMillis()); + } + FileLog.e("tmessages", "add to queue"); + ArrayList arrayList = updatesQueueChannels.get(channelId); + if (arrayList == null) { + arrayList = new ArrayList<>(); + updatesQueueChannels.put(channelId, arrayList); + } + arrayList.add(updatesNew); + } else { + if (needGetChannelsDiff == null) { + needGetChannelsDiff = new ArrayList<>(); + } else if (!needGetChannelsDiff.contains(channelId)) { + needGetChannelsDiff.add(channelId); + } } } + } else { + break; } - } else { - break; + updates.updates.remove(a); + a--; } - updates.updates.remove(a); - a--; - } - boolean processUpdate; - if (updates instanceof TLRPC.TL_updatesCombined) { - processUpdate = MessagesStorage.lastSeqValue + 1 == updates.seq_start || MessagesStorage.lastSeqValue == updates.seq_start; - } else { - processUpdate = MessagesStorage.lastSeqValue + 1 == updates.seq || updates.seq == 0 || updates.seq == MessagesStorage.lastSeqValue; - } - if (processUpdate) { - processUpdateArray(updates.updates, updates.users, updates.chats); - if (updates.date != 0) { - MessagesStorage.lastDateValue = updates.date; - } - if (updates.seq != 0) { - MessagesStorage.lastSeqValue = updates.seq; - } - } else { + boolean processUpdate; if (updates instanceof TLRPC.TL_updatesCombined) { - FileLog.e("tmessages", "need get diff TL_updatesCombined, seq: " + MessagesStorage.lastSeqValue + " " + updates.seq_start); + processUpdate = MessagesStorage.lastSeqValue + 1 == updates.seq_start || MessagesStorage.lastSeqValue == updates.seq_start; } else { - FileLog.e("tmessages", "need get diff TL_updates, seq: " + MessagesStorage.lastSeqValue + " " + updates.seq); + processUpdate = MessagesStorage.lastSeqValue + 1 == updates.seq || updates.seq == 0 || updates.seq == MessagesStorage.lastSeqValue; } - - if (gettingDifference || updatesStartWaitTimeSeq == 0 || Math.abs(System.currentTimeMillis() - updatesStartWaitTimeSeq) <= 1500) { - if (updatesStartWaitTimeSeq == 0) { - updatesStartWaitTimeSeq = System.currentTimeMillis(); + if (processUpdate) { + processUpdateArray(updates.updates, updates.users, updates.chats); + if (updates.date != 0) { + MessagesStorage.lastDateValue = updates.date; + } + if (updates.seq != 0) { + MessagesStorage.lastSeqValue = updates.seq; } - FileLog.e("tmessages", "add TL_updates/Combined to queue"); - updatesQueueSeq.add(updates); } else { - needGetDiff = true; + if (updates instanceof TLRPC.TL_updatesCombined) { + FileLog.e("tmessages", "need get diff TL_updatesCombined, seq: " + MessagesStorage.lastSeqValue + " " + updates.seq_start); + } else { + FileLog.e("tmessages", "need get diff TL_updates, seq: " + MessagesStorage.lastSeqValue + " " + updates.seq); + } + + if (gettingDifference || updatesStartWaitTimeSeq == 0 || Math.abs(System.currentTimeMillis() - updatesStartWaitTimeSeq) <= 1500) { + if (updatesStartWaitTimeSeq == 0) { + updatesStartWaitTimeSeq = System.currentTimeMillis(); + } + FileLog.e("tmessages", "add TL_updates/Combined to queue"); + updatesQueueSeq.add(updates); + } else { + needGetDiff = true; + } } } } else if (updates instanceof TLRPC.TL_updatesTooLong) { @@ -5357,6 +5684,7 @@ public void run() { } else { message = ((TLRPC.TL_updateNewChannelMessage) update).message; } + TLRPC.Chat chat = null; if (checkForUsers) { int chat_id = 0; if (message.to_id.channel_id != 0) { @@ -5365,10 +5693,14 @@ public void run() { chat_id = message.to_id.chat_id; } if (chat_id != 0) { - TLRPC.Chat chat = chatsDict.get(chat_id); + chat = chatsDict.get(chat_id); if (chat == null) { chat = getChat(chat_id); } + if (chat == null) { + chat = MessagesStorage.getInstance().getChatSync(chat_id); + putChat(chat, true); + } if (chat == null) { return false; } @@ -5411,7 +5743,7 @@ public void run() { if (value == null) { value = MessagesStorage.getInstance().getChannelReadInboxMax(update.channel_id); } - if (value >= message.id) { + if (value >= message.id || ChatObject.isNotInChat(chat)) { message.unread = false; message.media_unread = false; } @@ -5428,7 +5760,7 @@ public void run() { } message.dialog_id = message.to_id.user_id; } - MessageObject obj = new MessageObject(message, usersDict, chatsDict, message.dialog_id == lastCreatedDialogId); + MessageObject obj = new MessageObject(message, usersDict, chatsDict, createdDialogIds.contains(message.dialog_id)); if (obj.type == 11) { interfaceUpdateMask |= UPDATE_MASK_CHAT_AVATAR; } else if (obj.type == 10) { @@ -5549,7 +5881,7 @@ public void run() { newMessage.dialog_id = update.user_id; messagesArr.add(newMessage); - MessageObject obj = new MessageObject(newMessage, usersDict, chatsDict, newMessage.dialog_id == lastCreatedDialogId); + MessageObject obj = new MessageObject(newMessage, usersDict, chatsDict, createdDialogIds.contains(newMessage.dialog_id)); ArrayList arr = messages.get(newMessage.dialog_id); if (arr == null) { arr = new ArrayList<>(); @@ -5597,7 +5929,7 @@ public void run() { newMessage.dialog_id = 777000; messagesArr.add(newMessage); - MessageObject obj = new MessageObject(newMessage, usersDict, chatsDict, newMessage.dialog_id == lastCreatedDialogId); + MessageObject obj = new MessageObject(newMessage, usersDict, chatsDict, createdDialogIds.contains(newMessage.dialog_id)); ArrayList arr = messages.get(newMessage.dialog_id); if (arr == null) { arr = new ArrayList<>(); @@ -5621,7 +5953,7 @@ public void run() { TLRPC.Message message = decryptedMessages.get(a); ImageLoader.saveMessageThumbs(message); messagesArr.add(message); - MessageObject obj = new MessageObject(message, usersDict, chatsDict, uid == lastCreatedDialogId); + MessageObject obj = new MessageObject(message, usersDict, chatsDict, createdDialogIds.contains(uid)); arr.add(obj); pushMessages.add(obj); } @@ -5715,7 +6047,7 @@ public void run() { newMessage.message = notification.message; messagesArr.add(newMessage); - MessageObject obj = new MessageObject(newMessage, usersDict, chatsDict, newMessage.dialog_id == lastCreatedDialogId); + MessageObject obj = new MessageObject(newMessage, usersDict, chatsDict, createdDialogIds.contains(newMessage.dialog_id)); ArrayList arr = messages.get(newMessage.dialog_id); if (arr == null) { arr = new ArrayList<>(); @@ -5728,7 +6060,21 @@ public void run() { } else if (update instanceof TLRPC.TL_updateWebPage) { webPages.put(update.webpage.id, update.webpage); } else if (update instanceof TLRPC.TL_updateChannelTooLong) { - getChannelDifference(update.channel_id); + if ((update.flags & 1) != 0) { + Integer channelPts = channelsPts.get(update.channel_id); + if (channelPts == null) { + channelPts = MessagesStorage.getInstance().getChannelPtsSync(update.channel_id); + if (channelPts == 0) { + channelPts = 1; + } + channelsPts.put(update.channel_id, channelPts); + } + if (update.pts > channelPts) { + getChannelDifference(update.channel_id); + } + } else { + getChannelDifference(update.channel_id); + } } else if (update instanceof TLRPC.TL_updateChannelGroup) { ArrayList arrayList = channelsGroups.get(update.channel_id); if (arrayList == null) { @@ -5806,7 +6152,7 @@ public void run() { } message.dialog_id = message.to_id.user_id; } - MessageObject obj = new MessageObject(message, usersDict, chatsDict, message.dialog_id == lastCreatedDialogId); + MessageObject obj = new MessageObject(message, usersDict, chatsDict, createdDialogIds.contains(message.dialog_id)); ArrayList arr = editingMessages.get(message.dialog_id); if (arr == null) { @@ -5814,6 +6160,8 @@ public void run() { editingMessages.put(message.dialog_id, arr); } arr.add(obj); + } else if (update instanceof TLRPC.TL_updateChannelPinnedMessage) { + MessagesStorage.getInstance().updateChannelPinnedMessage(update.channel_id, update.id); } } if (!messages.isEmpty()) { @@ -5869,7 +6217,7 @@ public void run() { MessagesStorage.getInstance().putChannelViews(channelViews, true); } if (channelsGroups.size() != 0) { - //MessagesStorage.getInstance().applyNewChannelsGroups(channelsGroups); TODO + //MessagesStorage.getInstance().applyNewChannelsGroups(channelsGroups); } AndroidUtilities.runOnUIThread(new Runnable() { @@ -6320,7 +6668,7 @@ protected void updateInterfaceWithMessages(final long uid, final ArrayList 0) { + String type = reason.substring(0, index); + if (type.contains("-all") || type.contains("-android")) { + return reason.substring(index + 2); + } + } + return null; + } + + private static void showCantOpenAlert(BaseFragment fragment, String reason) { + AlertDialog.Builder builder = new AlertDialog.Builder(fragment.getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + builder.setMessage(reason); + fragment.showDialog(builder.create()); + } + + public static boolean checkCanOpenChat(Bundle bundle, BaseFragment fragment) { + if (bundle == null || fragment == null) { + return true; + } + TLRPC.User user = null; + TLRPC.Chat chat = null; + int user_id = bundle.getInt("user_id", 0); + int chat_id = bundle.getInt("chat_id", 0); + if (user_id != 0) { + user = MessagesController.getInstance().getUser(user_id); + } else if (chat_id != 0) { + chat = MessagesController.getInstance().getChat(chat_id); + } + if (user == null && chat == null) { + return true; + } + String reason = null; + if (chat != null) { + reason = getRestrictionReason(chat.restriction_reason); + } else if (user != null) { + reason = getRestrictionReason(user.restriction_reason); + } + if (reason != null) { + showCantOpenAlert(fragment, reason); + return false; + } + return true; + } + + public static void openChatOrProfileWith(TLRPC.User user, TLRPC.Chat chat, BaseFragment fragment, int type) { + if (user == null && chat == null || fragment == null) { return; } - TLRPC.User user = MessagesController.getInstance().getUser(username); - if (user != null) { + String reason = null; + if (chat != null) { + reason = getRestrictionReason(chat.restriction_reason); + } else if (user != null) { + reason = getRestrictionReason(user.restriction_reason); + } + if (reason != null) { + showCantOpenAlert(fragment, reason); + } else { Bundle args = new Bundle(); - args.putInt("user_id", user.id); + if (chat != null) { + args.putInt("chat_id", chat.id); + } else { + args.putInt("user_id", user.id); + } if (type == 0) { fragment.presentFragment(new ProfileActivity(args)); } else { - fragment.presentFragment(new ChatActivity(args)); + fragment.presentFragment(new ChatActivity(args)/*, fragment instanceof ChatActivity*/); } + } + } + + public static void openByUserName(String username, final BaseFragment fragment, final int type) { + if (username == null || fragment == null) { + return; + } + TLRPC.User user = getInstance().getUser(username); + if (user != null) { + openChatOrProfileWith(user, null, fragment, type); } else { if (fragment.getParentActivity() == null) { return; @@ -6473,21 +6893,13 @@ public void run() { fragment.setVisibleDialog(null); if (error == null) { TLRPC.TL_contacts_resolvedPeer res = (TLRPC.TL_contacts_resolvedPeer) response; - MessagesController.getInstance().putUsers(res.users, false); - MessagesController.getInstance().putChats(res.chats, false); + getInstance().putUsers(res.users, false); + getInstance().putChats(res.chats, false); MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, false, true); - Bundle args = new Bundle(); if (!res.chats.isEmpty()) { - args.putInt("chat_id", res.chats.get(0).id); - } else { - args.putInt("user_id", res.users.get(0).id); - } - if (fragment != null) { - if (type == 0 && res.chats.isEmpty()) { - fragment.presentFragment(new ProfileActivity(args)); - } else { - fragment.presentFragment(new ChatActivity(args)); - } + openChatOrProfileWith(null, res.chats.get(0), fragment, 1); + } else if (!res.users.isEmpty()) { + openChatOrProfileWith(res.users.get(0), null, fragment, type); } } else { if (fragment != null && fragment.getParentActivity() != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java index 15079dbb06..a1dd92f524 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java @@ -19,6 +19,7 @@ import org.telegram.SQLite.SQLiteDatabase; import org.telegram.SQLite.SQLitePreparedStatement; import org.telegram.messenger.query.BotQuery; +import org.telegram.messenger.query.MessagesQuery; import org.telegram.messenger.query.SharedMediaQuery; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.NativeByteBuffer; @@ -141,10 +142,16 @@ public void openDatabase() { database.executeFast("CREATE TABLE bot_keyboard(uid INTEGER PRIMARY KEY, mid INTEGER, info BLOB)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS bot_keyboard_idx_mid ON bot_keyboard(mid);").stepThis().dispose(); + database.executeFast("CREATE TABLE chat_settings_v2(uid INTEGER PRIMARY KEY, info BLOB, pinned INTEGER)").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS chat_settings_pinned_idx ON chat_settings_v2(uid, pinned) WHERE pinned != 0;").stepThis().dispose(); + + database.executeFast("CREATE TABLE chat_pinned(uid INTEGER PRIMARY KEY, pinned INTEGER, data BLOB)").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS chat_pinned_mid_idx ON chat_pinned(uid, pinned) WHERE pinned != 0;").stepThis().dispose(); + + database.executeFast("CREATE TABLE users_data(uid INTEGER PRIMARY KEY, about TEXT)").stepThis().dispose(); database.executeFast("CREATE TABLE users(uid INTEGER PRIMARY KEY, name TEXT, status INTEGER, data BLOB)").stepThis().dispose(); database.executeFast("CREATE TABLE chats(uid INTEGER PRIMARY KEY, name TEXT, data BLOB)").stepThis().dispose(); database.executeFast("CREATE TABLE enc_chats(uid INTEGER PRIMARY KEY, user INTEGER, name TEXT, data BLOB, g BLOB, authkey BLOB, ttl INTEGER, layer INTEGER, seq_in INTEGER, seq_out INTEGER, use_count INTEGER, exchange_id INTEGER, key_date INTEGER, fprint INTEGER, fauthkey BLOB, khash BLOB)").stepThis().dispose(); - database.executeFast("CREATE TABLE chat_settings_v2(uid INTEGER PRIMARY KEY, info BLOB)").stepThis().dispose(); database.executeFast("CREATE TABLE channel_users_v2(did INTEGER, uid INTEGER, date INTEGER, data BLOB, PRIMARY KEY(did, uid))").stepThis().dispose(); database.executeFast("CREATE TABLE contacts(uid INTEGER PRIMARY KEY, mutual INTEGER)").stepThis().dispose(); database.executeFast("CREATE TABLE pending_read(uid INTEGER PRIMARY KEY, max_id INTEGER)").stepThis().dispose(); @@ -165,7 +172,7 @@ public void openDatabase() { database.executeFast("CREATE TABLE bot_info(uid INTEGER PRIMARY KEY, info BLOB)").stepThis().dispose(); //version - database.executeFast("PRAGMA user_version = 30").stepThis().dispose(); + database.executeFast("PRAGMA user_version = 31").stepThis().dispose(); //database.executeFast("CREATE TABLE secret_holes(uid INTEGER, seq_in INTEGER, seq_out INTEGER, data BLOB, PRIMARY KEY (uid, seq_in, seq_out));").stepThis().dispose(); //database.executeFast("CREATE TABLE attach_data(uid INTEGER, id INTEGER, data BLOB, PRIMARY KEY (uid, id))").stepThis().dispose(); @@ -199,7 +206,7 @@ public void openDatabase() { } } int version = database.executeInt("PRAGMA user_version"); - if (version < 30) { + if (version < 31) { updateDbToLastVersion(version); } } @@ -487,7 +494,16 @@ public void run() { database.executeFast("DELETE FROM sent_files_v2 WHERE 1").stepThis().dispose(); database.executeFast("DELETE FROM download_queue WHERE 1").stepThis().dispose(); database.executeFast("PRAGMA user_version = 30").stepThis().dispose(); - //version = 30; + version = 30; + } + if (version == 30) { + database.executeFast("ALTER TABLE chat_settings_v2 ADD COLUMN pinned INTEGER default 0").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS chat_settings_pinned_idx ON chat_settings_v2(uid, pinned) WHERE pinned != 0;").stepThis().dispose(); + database.executeFast("CREATE TABLE IF NOT EXISTS chat_pinned(uid INTEGER PRIMARY KEY, pinned INTEGER, data BLOB)").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS chat_pinned_mid_idx ON chat_pinned(uid, pinned) WHERE pinned != 0;").stepThis().dispose(); + database.executeFast("CREATE TABLE IF NOT EXISTS users_data(uid INTEGER PRIMARY KEY, about TEXT)").stepThis().dispose(); + database.executeFast("PRAGMA user_version = 31").stepThis().dispose(); + //version = 31; } } catch (Exception e) { FileLog.e("tmessages", e); @@ -1012,22 +1028,22 @@ public void run() { }); } - public void deleteDialog(final long did, final int messagesOnly) { + public void deleteUserChannelHistory(final int channelId, final int uid) { storageQueue.postRunnable(new Runnable() { @Override public void run() { try { - if ((int) did == 0 || messagesOnly == 2) { - SQLiteCursor cursor = database.queryFinalized("SELECT data FROM messages WHERE uid = " + did); - ArrayList filesToDelete = new ArrayList<>(); - try { - while (cursor.next()) { - NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data) != 0) { - TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - if (message == null || message.media == null) { - continue; - } + long did = -channelId; + final ArrayList mids = new ArrayList<>(); + SQLiteCursor cursor = database.queryFinalized("SELECT data FROM messages WHERE uid = " + did); + ArrayList filesToDelete = new ArrayList<>(); + try { + while (cursor.next()) { + NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); + if (data != null && cursor.byteBufferValue(0, data) != 0) { + TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + if (message != null && message.from_id == uid && message.id != 1) { + mids.add(message.id); if (message.media instanceof TLRPC.TL_messageMediaPhoto) { for (TLRPC.PhotoSize photoSize : message.media.photo.sizes) { File file = FileLoader.getPathToAttach(photoSize); @@ -1046,6 +1062,70 @@ public void run() { } } } + } + data.reuse(); + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + cursor.dispose(); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + MessagesController.getInstance().markChannelDialogMessageAsDeleted(mids, channelId); + } + }); + markMessagesAsDeletedInternal(mids, channelId); + updateDialogsWithDeletedMessagesInternal(mids, channelId); + FileLoader.getInstance().deleteFiles(filesToDelete, 0); + if (!mids.isEmpty()) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesDeleted, mids, channelId); + } + }); + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + }); + } + + public void deleteDialog(final long did, final int messagesOnly) { + storageQueue.postRunnable(new Runnable() { + @Override + public void run() { + try { + if ((int) did == 0 || messagesOnly == 2) { + SQLiteCursor cursor = database.queryFinalized("SELECT data FROM messages WHERE uid = " + did); + ArrayList filesToDelete = new ArrayList<>(); + try { + while (cursor.next()) { + NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); + if (data != null && cursor.byteBufferValue(0, data) != 0) { + TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + if (message != null && message.media != null) { + if (message.media instanceof TLRPC.TL_messageMediaPhoto) { + for (TLRPC.PhotoSize photoSize : message.media.photo.sizes) { + File file = FileLoader.getPathToAttach(photoSize); + if (file != null && file.toString().length() > 0) { + filesToDelete.add(file); + } + } + } else if (message.media instanceof TLRPC.TL_messageMediaDocument) { + File file = FileLoader.getPathToAttach(message.media.document); + if (file != null && file.toString().length() > 0) { + filesToDelete.add(file); + } + file = FileLoader.getPathToAttach(message.media.document.thumb); + if (file != null && file.toString().length() > 0) { + filesToDelete.add(file); + } + } + } + } data.reuse(); } } catch (Exception e) { @@ -1058,6 +1138,7 @@ public void run() { if (messagesOnly == 0) { database.executeFast("DELETE FROM dialogs WHERE did = " + did).stepThis().dispose(); database.executeFast("DELETE FROM chat_settings_v2 WHERE uid = " + did).stepThis().dispose(); + database.executeFast("DELETE FROM chat_pinned WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM channel_users_v2 WHERE did = " + did).stepThis().dispose(); database.executeFast("DELETE FROM search_recent WHERE did = " + did).stepThis().dispose(); int lower_id = (int)did; @@ -1084,10 +1165,9 @@ public void run() { NativeByteBuffer data = new NativeByteBuffer(cursor2.byteArrayLength(0)); if (data != null && cursor2.byteBufferValue(0, data) != 0) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - if (message == null) { - continue; + if (message != null) { + arrayList.add(message); } - arrayList.add(message); } data.reuse(); } @@ -1429,13 +1509,14 @@ public void updateChatParticipants(final TLRPC.ChatParticipants participants) { @Override public void run() { try { - SQLiteCursor cursor = database.queryFinalized("SELECT info FROM chat_settings_v2 WHERE uid = " + participants.chat_id); + SQLiteCursor cursor = database.queryFinalized("SELECT info, pinned FROM chat_settings_v2 WHERE uid = " + participants.chat_id); TLRPC.ChatFull info = null; ArrayList loadedUsers = new ArrayList<>(); if (cursor.next()) { NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); if (data != null && cursor.byteBufferValue(0, data) != 0) { info = TLRPC.ChatFull.TLdeserialize(data, data.readInt32(false), false); + info.pinned_msg_id = cursor.intValue(1); } data.reuse(); } @@ -1446,15 +1527,16 @@ public void run() { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, finalInfo, 0, false); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, finalInfo, 0, false, null); } }); - SQLitePreparedStatement state = database.executeFast("REPLACE INTO chat_settings_v2 VALUES(?, ?)"); + SQLitePreparedStatement state = database.executeFast("REPLACE INTO chat_settings_v2 VALUES(?, ?, ?)"); NativeByteBuffer data = new NativeByteBuffer(info.getObjectSize()); info.serializeToStream(data); state.bindInteger(1, info.id); state.bindByteBuffer(2, data); + state.bindInteger(3, info.pinned_msg_id); state.step(); state.dispose(); data.reuse(); @@ -1516,11 +1598,12 @@ public void run() { return; } } - SQLitePreparedStatement state = database.executeFast("REPLACE INTO chat_settings_v2 VALUES(?, ?)"); + SQLitePreparedStatement state = database.executeFast("REPLACE INTO chat_settings_v2 VALUES(?, ?, ?)"); NativeByteBuffer data = new NativeByteBuffer(info.getObjectSize()); info.serializeToStream(data); state.bindInteger(1, info.id); state.bindByteBuffer(2, data); + state.bindInteger(3, info.pinned_msg_id); state.step(); state.dispose(); data.reuse(); @@ -1557,18 +1640,65 @@ public void run() { }); } + public void updateChannelPinnedMessage(final int channelId, final int messageId) { + storageQueue.postRunnable(new Runnable() { + @Override + public void run() { + try { + SQLiteCursor cursor = database.queryFinalized("SELECT info, pinned FROM chat_settings_v2 WHERE uid = " + channelId); + TLRPC.ChatFull info = null; + ArrayList loadedUsers = new ArrayList<>(); + if (cursor.next()) { + NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); + if (cursor.byteBufferValue(0, data) != 0) { + info = TLRPC.ChatFull.TLdeserialize(data, data.readInt32(false), false); + info.pinned_msg_id = cursor.intValue(1); + } + data.reuse(); + } + cursor.dispose(); + if (info instanceof TLRPC.TL_channelFull) { + info.pinned_msg_id = messageId; + info.flags |= 32; + + final TLRPC.ChatFull finalInfo = info; + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, finalInfo, 0, false, null); + } + }); + + SQLitePreparedStatement state = database.executeFast("REPLACE INTO chat_settings_v2 VALUES(?, ?, ?)"); + NativeByteBuffer data = new NativeByteBuffer(info.getObjectSize()); + info.serializeToStream(data); + state.bindInteger(1, channelId); + state.bindByteBuffer(2, data); + state.bindInteger(3, info.pinned_msg_id); + state.step(); + state.dispose(); + data.reuse(); + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + }); + } + public void updateChatInfo(final int chat_id, final int user_id, final int what, final int invited_id, final int version) { storageQueue.postRunnable(new Runnable() { @Override public void run() { try { - SQLiteCursor cursor = database.queryFinalized("SELECT info FROM chat_settings_v2 WHERE uid = " + chat_id); + SQLiteCursor cursor = database.queryFinalized("SELECT info, pinned FROM chat_settings_v2 WHERE uid = " + chat_id); TLRPC.ChatFull info = null; ArrayList loadedUsers = new ArrayList<>(); if (cursor.next()) { NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); if (data != null && cursor.byteBufferValue(0, data) != 0) { info = TLRPC.ChatFull.TLdeserialize(data, data.readInt32(false), false); + info.pinned_msg_id = cursor.intValue(1); } data.reuse(); } @@ -1620,15 +1750,16 @@ public void run() { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, finalInfo, 0, false); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, finalInfo, 0, false, null); } }); - SQLitePreparedStatement state = database.executeFast("REPLACE INTO chat_settings_v2 VALUES(?, ?)"); + SQLitePreparedStatement state = database.executeFast("REPLACE INTO chat_settings_v2 VALUES(?, ?, ?)"); NativeByteBuffer data = new NativeByteBuffer(info.getObjectSize()); info.serializeToStream(data); state.bindInteger(1, chat_id); state.bindByteBuffer(2, data); + state.bindInteger(3, info.pinned_msg_id); state.step(); state.dispose(); data.reuse(); @@ -1684,13 +1815,14 @@ public void loadChatInfo(final int chat_id, final Semaphore semaphore, final boo @Override public void run() { try { - SQLiteCursor cursor = database.queryFinalized("SELECT info FROM chat_settings_v2 WHERE uid = " + chat_id); + SQLiteCursor cursor = database.queryFinalized("SELECT info, pinned FROM chat_settings_v2 WHERE uid = " + chat_id); TLRPC.ChatFull info = null; ArrayList loadedUsers = new ArrayList<>(); if (cursor.next()) { NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); if (data != null && cursor.byteBufferValue(0, data) != 0) { info = TLRPC.ChatFull.TLdeserialize(data, data.readInt32(false), false); + info.pinned_msg_id = cursor.intValue(1); } data.reuse(); } @@ -1754,7 +1886,12 @@ public void run() { if (semaphore != null) { semaphore.release(); } - MessagesController.getInstance().processChatInfo(chat_id, info, loadedUsers, true, force, byChannelUsers); + MessageObject pinnedMessageObject = null; + if (info instanceof TLRPC.TL_channelFull && info.pinned_msg_id != 0) { + pinnedMessageObject = MessagesQuery.loadPinnedMessage(chat_id, info.pinned_msg_id, false); + } + MessagesController.getInstance().processChatInfo(chat_id, info, loadedUsers, true, force, byChannelUsers, pinnedMessageObject); + } catch (Exception e) { FileLog.e("tmessages", e); } finally { @@ -2281,7 +2418,7 @@ public void run() { holeMessageMinId |= ((long) channelId) << 32; } } - /*if (holeMessageMaxId == holeMessageMinId) { TODO ??? + /*if (holeMessageMaxId == holeMessageMinId) { holeMessageMaxId = 0; holeMessageMinId = 1; }*/ @@ -2422,19 +2559,17 @@ public void run() { addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); if (message.reply_to_msg_id != 0 || message.reply_to_random_id != 0) { - boolean ok = false; if (!cursor.isNull(6)) { NativeByteBuffer data2 = new NativeByteBuffer(cursor.byteArrayLength(6)); if (data2 != null && cursor.byteBufferValue(6, data2) != 0) { message.replyMessage = TLRPC.Message.TLdeserialize(data2, data2.readInt32(false), false); if (message.replyMessage != null) { addUsersAndChatsFromMessage(message.replyMessage, usersToLoad, chatsToLoad); - ok = true; } } data2.reuse(); } - if (!ok) { + if (message.replyMessage == null) { if (message.reply_to_msg_id != 0) { long messageId = message.reply_to_msg_id; if (message.to_id.channel_id != 0) { @@ -3037,7 +3172,7 @@ private void putUsersInternal(ArrayList users) throws Exception { NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); if (data != null && cursor.byteBufferValue(0, data) != 0) { TLRPC.User oldUser = TLRPC.User.TLdeserialize(data, data.readInt32(false), false); - if (user != null) { + if (oldUser != null) { if (user.first_name != null) { oldUser.first_name = user.first_name; oldUser.flags |= 2; @@ -3052,6 +3187,13 @@ private void putUsersInternal(ArrayList users) throws Exception { oldUser.last_name = null; oldUser.flags = oldUser.flags &~ 4; } + if (user.username != null) { + oldUser.username = user.username; + oldUser.flags |= 8; + } else { + oldUser.username = null; + oldUser.flags = oldUser.flags &~ 8; + } if (user.photo != null) { oldUser.photo = user.photo; oldUser.flags |= 32; @@ -3059,8 +3201,8 @@ private void putUsersInternal(ArrayList users) throws Exception { oldUser.photo = null; oldUser.flags = oldUser.flags &~ 32; } + user = oldUser; } - user = oldUser; } data.reuse(); } catch (Exception e) { @@ -3099,6 +3241,36 @@ private void putChatsInternal(ArrayList chats) throws Exception { SQLitePreparedStatement state = database.executeFast("REPLACE INTO chats VALUES(?, ?, ?)"); for (int a = 0; a < chats.size(); a++) { TLRPC.Chat chat = chats.get(a); + if (chat.min) { + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM chats WHERE uid = %d", chat.id)); + if (cursor.next()) { + try { + NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); + if (data != null && cursor.byteBufferValue(0, data) != 0) { + TLRPC.Chat oldChat = TLRPC.Chat.TLdeserialize(data, data.readInt32(false), false); + if (oldChat != null) { + oldChat.title = chat.title; + oldChat.photo = chat.photo; + oldChat.broadcast = chat.broadcast; + oldChat.verified = chat.verified; + oldChat.megagroup = chat.megagroup; + oldChat.democracy = chat.democracy; + if (chat.username != null) { + oldChat.username = chat.username; + oldChat.flags |= 64; + } else { + oldChat.username = null; + oldChat.flags = oldChat.flags &~ 64; + } + chat = oldChat; + } + } + data.reuse(); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + } state.requery(); NativeByteBuffer data = new NativeByteBuffer(chat.getObjectSize()); chat.serializeToStream(data); @@ -4119,7 +4291,6 @@ private long[] updateMessageStateAndIdInternal(long random_id, Integer _oldId, i } catch (Exception e2) { FileLog.e("tmessages", e2); } - FileLog.e("tmessages", e); } finally { if (state != null) { state.dispose(); @@ -4138,7 +4309,6 @@ private long[] updateMessageStateAndIdInternal(long random_id, Integer _oldId, i } catch (Exception e2) { FileLog.e("tmessages", e2); } - FileLog.e("tmessages", e); } finally { if (state != null) { state.dispose(); @@ -4388,24 +4558,23 @@ private void markMessagesAsDeletedInternal(final ArrayList messages, in NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(1)); if (data != null && cursor.byteBufferValue(1, data) != 0) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - if (message == null || message.media == null) { - continue; - } - if (message.media instanceof TLRPC.TL_messageMediaPhoto) { - for (TLRPC.PhotoSize photoSize : message.media.photo.sizes) { - File file = FileLoader.getPathToAttach(photoSize); + if (message != null && message.media != null) { + if (message.media instanceof TLRPC.TL_messageMediaPhoto) { + for (TLRPC.PhotoSize photoSize : message.media.photo.sizes) { + File file = FileLoader.getPathToAttach(photoSize); + if (file != null && file.toString().length() > 0) { + filesToDelete.add(file); + } + } + } else if (message.media instanceof TLRPC.TL_messageMediaDocument) { + File file = FileLoader.getPathToAttach(message.media.document); + if (file != null && file.toString().length() > 0) { + filesToDelete.add(file); + } + file = FileLoader.getPathToAttach(message.media.document.thumb); if (file != null && file.toString().length() > 0) { filesToDelete.add(file); } - } - } else if (message.media instanceof TLRPC.TL_messageMediaDocument) { - File file = FileLoader.getPathToAttach(message.media.document); - if (file != null && file.toString().length() > 0) { - filesToDelete.add(file); - } - file = FileLoader.getPathToAttach(message.media.document.thumb); - if (file != null && file.toString().length() > 0) { - filesToDelete.add(file); } } } @@ -5136,7 +5305,9 @@ public void run() { usersToLoad.add(UserConfig.getClientUserId()); ArrayList chatsToLoad = new ArrayList<>(); ArrayList encryptedToLoad = new ArrayList<>(); - SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, s.flags, m.date, d.last_mid_i, d.unread_count_i, d.pts, d.inbox_max, d.date_i FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid LEFT JOIN dialog_settings as s ON d.did = s.did ORDER BY d.date DESC LIMIT %d,%d", offset, count)); + ArrayList replyMessages = new ArrayList<>(); + HashMap replyMessageOwners = new HashMap<>(); + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, s.flags, m.date, d.last_mid_i, d.unread_count_i, d.pts, d.inbox_max, d.date_i, m.replydata FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid LEFT JOIN dialog_settings as s ON d.did = s.did ORDER BY d.date DESC LIMIT %d,%d", offset, count)); while (cursor.next()) { TLRPC.Dialog dialog; int pts = cursor.intValue(12); @@ -5181,6 +5352,33 @@ public void run() { dialogs.messages.add(message); addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); + + try { + if (message.reply_to_msg_id != 0 && message.action instanceof TLRPC.TL_messageActionPinMessage) { + if (!cursor.isNull(15)) { + NativeByteBuffer data2 = new NativeByteBuffer(cursor.byteArrayLength(15)); + if (cursor.byteBufferValue(15, data2) != 0) { + message.replyMessage = TLRPC.Message.TLdeserialize(data2, data2.readInt32(false), false); + if (message.replyMessage != null) { + addUsersAndChatsFromMessage(message.replyMessage, usersToLoad, chatsToLoad); + } + } + data2.reuse(); + } + if (message.replyMessage == null) { + long messageId = message.reply_to_msg_id; + if (message.to_id.channel_id != 0) { + messageId |= ((long) message.to_id.channel_id) << 32; + } + if (!replyMessages.contains(messageId)) { + replyMessages.add(messageId); + } + replyMessageOwners.put(dialog.id, message); + } + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } } } data.reuse(); @@ -5211,6 +5409,29 @@ public void run() { } cursor.dispose(); + if (!replyMessages.isEmpty()) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid, date, uid FROM messages WHERE mid IN(%s)", TextUtils.join(",", replyMessages))); + while (cursor.next()) { + NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); + if (data != null && cursor.byteBufferValue(0, data) != 0) { + TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + message.id = cursor.intValue(1); + message.date = cursor.intValue(2); + message.dialog_id = cursor.longValue(3); + + addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); + + TLRPC.Message owner = replyMessageOwners.get(message.dialog_id); + if (owner != null) { + owner.replyMessage = message; + message.dialog_id = owner.dialog_id; + } + } + data.reuse(); + } + cursor.dispose(); + } + if (!encryptedToLoad.isEmpty()) { getEncryptedChatsInternal(TextUtils.join(",", encryptedToLoad), encryptedChats, usersToLoad); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NativeLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/NativeLoader.java index 46251dfbd6..79f5237119 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NativeLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NativeLoader.java @@ -23,7 +23,7 @@ public class NativeLoader { - private final static int LIB_VERSION = 19; + private final static int LIB_VERSION = 20; private final static String LIB_NAME = "tmessages." + LIB_VERSION; private final static String LIB_SO_NAME = "lib" + LIB_NAME + ".so"; private final static String LOCALE_LIB_SO_NAME = "lib" + LIB_NAME + "loc.so"; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java index 6fd1db2400..6a37d46aea 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java @@ -53,6 +53,7 @@ public class NotificationCenter { public static final int didSetTwoStepPassword = totalEvents++; public static final int screenStateChanged = totalEvents++; public static final int didLoadedReplyMessages = totalEvents++; + public static final int didLoadedPinnedMessage = totalEvents++; public static final int newSessionReceived = totalEvents++; public static final int didReceivedWebpages = totalEvents++; public static final int didReceivedWebpagesInUpdates = totalEvents++; @@ -60,6 +61,7 @@ public class NotificationCenter { public static final int didReplacedPhotoInMemCache = totalEvents++; public static final int messagesReadContent = totalEvents++; public static final int botInfoDidLoaded = totalEvents++; + public static final int userInfoDidLoaded = totalEvents++; public static final int botKeyboardDidLoaded = totalEvents++; public static final int chatSearchResultsAvailable = totalEvents++; public static final int musicDidLoaded = totalEvents++; @@ -67,6 +69,7 @@ public class NotificationCenter { public static final int didUpdatedMessagesViews = totalEvents++; public static final int needReloadRecentDialogsSearch = totalEvents++; public static final int locationPermissionGranted = totalEvents++; + public static final int peerSettingsDidLoaded = totalEvents++; public static final int httpFileDidLoaded = totalEvents++; public static final int httpFileDidFailedLoad = totalEvents++; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java index 5276b742f0..865cd616e5 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java @@ -61,7 +61,7 @@ public class NotificationsController { private int wearNotificationId = 10000; private int autoNotificationId = 20000; public ArrayList popupMessages = new ArrayList<>(); - private long openned_dialog_id = 0; + private long opened_dialog_id = 0; private int total_unread_count = 0; private int personal_count = 0; private boolean notifyCheck = false; @@ -147,7 +147,7 @@ public void cleanup() { notificationsQueue.postRunnable(new Runnable() { @Override public void run() { - openned_dialog_id = 0; + opened_dialog_id = 0; total_unread_count = 0; personal_count = 0; pushMessages.clear(); @@ -178,11 +178,11 @@ public void setInChatSoundEnabled(boolean value) { inChatSoundEnabled = value; } - public void setOpennedDialogId(final long dialog_id) { + public void setOpenedDialogId(final long dialog_id) { notificationsQueue.postRunnable(new Runnable() { @Override public void run() { - openned_dialog_id = dialog_id; + opened_dialog_id = dialog_id; } }); } @@ -393,7 +393,7 @@ public void run() { } long dialog_id = messageObject.getDialogId(); long original_dialog_id = dialog_id; - if (dialog_id == openned_dialog_id && ApplicationLoader.isScreenOn) { + if (dialog_id == opened_dialog_id && ApplicationLoader.isScreenOn) { playInChatSound(); continue; } @@ -406,7 +406,7 @@ public void run() { added = true; Boolean value = settingsCache.get(dialog_id); - boolean isChat = (int)dialog_id < 0; + boolean isChat = (int) dialog_id < 0; popup = (int)dialog_id == 0 ? 0 : preferences.getInt(isChat ? "popupGroup" : "popupAll", 0); if (value == null) { int notifyOverride = getNotifyOverride(preferences, dialog_id); @@ -582,7 +582,7 @@ public void run() { value = !(notifyOverride == 2 || (!preferences.getBoolean("EnableAll", true) || ((int) dialog_id < 0) && !preferences.getBoolean("EnableGroup", true)) && notifyOverride == 0); settingsCache.put(dialog_id, value); } - if (!value || dialog_id == openned_dialog_id && ApplicationLoader.isScreenOn) { + if (!value || dialog_id == opened_dialog_id && ApplicationLoader.isScreenOn) { continue; } pushMessagesDict.put(mid, messageObject); @@ -757,6 +757,8 @@ private String getStringForMessage(MessageObject messageObject, boolean shortMes msg = LocaleController.formatString("NotificationMessageVideo", R.string.NotificationMessageVideo, name); } else if (messageObject.isVoice()) { msg = LocaleController.formatString("NotificationMessageAudio", R.string.NotificationMessageAudio, name); + } else if (messageObject.isMusic()) { + msg = LocaleController.formatString("NotificationMessageMusic", R.string.NotificationMessageMusic, name); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) { msg = LocaleController.formatString("NotificationMessageContact", R.string.NotificationMessageContact, name); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaVenue) { @@ -801,7 +803,11 @@ private String getStringForMessage(MessageObject messageObject, boolean shortMes return null; } if (from_id == u2.id) { - msg = LocaleController.formatString("NotificationGroupAddSelf", R.string.NotificationGroupAddSelf, name, chat.title); + if (messageObject.isMegagroup()) { + msg = LocaleController.formatString("NotificationGroupAddSelfMega", R.string.NotificationGroupAddSelfMega, name, chat.title); + } else { + msg = LocaleController.formatString("NotificationGroupAddSelf", R.string.NotificationGroupAddSelf, name, chat.title); + } } else { msg = LocaleController.formatString("NotificationGroupAddMember", R.string.NotificationGroupAddMember, name, chat.title, UserObject.getUserName(u2)); } @@ -851,10 +857,91 @@ private String getStringForMessage(MessageObject messageObject, boolean shortMes msg = LocaleController.formatString("ActionMigrateFromGroupNotify", R.string.ActionMigrateFromGroupNotify, chat.title); } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionChannelMigrateFrom) { msg = LocaleController.formatString("ActionMigrateFromGroupNotify", R.string.ActionMigrateFromGroupNotify, messageObject.messageOwner.action.title); + } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPinMessage) { + if (messageObject.replyMessageObject == null) { + if (!ChatObject.isChannel(chat) || chat.megagroup) { + msg = LocaleController.formatString("NotificationActionPinnedNoText", R.string.NotificationActionPinnedNoText, name, chat.title); + } else { + msg = LocaleController.formatString("NotificationActionPinnedNoTextChannel", R.string.NotificationActionPinnedNoTextChannel, name, chat.title); + } + } else { + MessageObject object = messageObject.replyMessageObject; + if (object.isMusic()) { + if (!ChatObject.isChannel(chat) || chat.megagroup) { + msg = LocaleController.formatString("NotificationActionPinnedMusic", R.string.NotificationActionPinnedMusic, name, chat.title); + } else { + msg = LocaleController.formatString("NotificationActionPinnedMusicChannel", R.string.NotificationActionPinnedMusicChannel, chat.title); + } + } else if (object.isVideo()) { + if (!ChatObject.isChannel(chat) || chat.megagroup) { + msg = LocaleController.formatString("NotificationActionPinnedVideo", R.string.NotificationActionPinnedVideo, name, chat.title); + } else { + msg = LocaleController.formatString("NotificationActionPinnedVideoChannel", R.string.NotificationActionPinnedVideoChannel, chat.title); + } + } else if (object.isGif()) { + if (!ChatObject.isChannel(chat) || chat.megagroup) { + msg = LocaleController.formatString("NotificationActionPinnedGif", R.string.NotificationActionPinnedGif, name, chat.title); + } else { + msg = LocaleController.formatString("NotificationActionPinnedGifChannel", R.string.NotificationActionPinnedGifChannel, chat.title); + } + } else if (object.isVoice()) { + if (!ChatObject.isChannel(chat) || chat.megagroup) { + msg = LocaleController.formatString("NotificationActionPinnedVoice", R.string.NotificationActionPinnedVoice, name, chat.title); + } else { + msg = LocaleController.formatString("NotificationActionPinnedVoiceChannel", R.string.NotificationActionPinnedVoiceChannel, chat.title); + } + } else if (object.isSticker()) { + if (!ChatObject.isChannel(chat) || chat.megagroup) { + msg = LocaleController.formatString("NotificationActionPinnedSticker", R.string.NotificationActionPinnedSticker, name, chat.title); + } else { + msg = LocaleController.formatString("NotificationActionPinnedStickerChannel", R.string.NotificationActionPinnedStickerChannel, chat.title); + } + } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { + if (!ChatObject.isChannel(chat) || chat.megagroup) { + msg = LocaleController.formatString("NotificationActionPinnedFile", R.string.NotificationActionPinnedFile, name, chat.title); + } else { + msg = LocaleController.formatString("NotificationActionPinnedFileChannel", R.string.NotificationActionPinnedFileChannel, chat.title); + } + } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaGeo) { + if (!ChatObject.isChannel(chat) || chat.megagroup) { + msg = LocaleController.formatString("NotificationActionPinnedGeo", R.string.NotificationActionPinnedGeo, name, chat.title); + } else { + msg = LocaleController.formatString("NotificationActionPinnedGeoChannel", R.string.NotificationActionPinnedGeoChannel, chat.title); + } + } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaContact) { + if (!ChatObject.isChannel(chat) || chat.megagroup) { + msg = LocaleController.formatString("NotificationActionPinnedContact", R.string.NotificationActionPinnedContact, name, chat.title); + } else { + msg = LocaleController.formatString("NotificationActionPinnedContactChannel", R.string.NotificationActionPinnedContactChannel, chat.title); + } + } else if (object.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { + if (!ChatObject.isChannel(chat) || chat.megagroup) { + msg = LocaleController.formatString("NotificationActionPinnedPhoto", R.string.NotificationActionPinnedPhoto, name, chat.title); + } else { + msg = LocaleController.formatString("NotificationActionPinnedPhotoChannel", R.string.NotificationActionPinnedPhotoChannel, chat.title); + } + } else if (object.messageText != null && object.messageText.length() > 0) { + CharSequence message = object.messageText; + if (message.length() > 20) { + message = message.subSequence(0, 20) + "..."; + } + if (!ChatObject.isChannel(chat) || chat.megagroup) { + msg = LocaleController.formatString("NotificationActionPinnedText", R.string.NotificationActionPinnedText, name, message, chat.title); + } else { + msg = LocaleController.formatString("NotificationActionPinnedTextChannel", R.string.NotificationActionPinnedTextChannel, chat.title, message); + } + } else { + if (!ChatObject.isChannel(chat) || chat.megagroup) { + msg = LocaleController.formatString("NotificationActionPinnedNoText", R.string.NotificationActionPinnedNoText, name, chat.title); + } else { + msg = LocaleController.formatString("NotificationActionPinnedNoTextChannel", R.string.NotificationActionPinnedNoTextChannel, chat.title); + } + } + } } } else { if (ChatObject.isChannel(chat) && !chat.megagroup) { - if (from_id < 0) { + if (messageObject.isImportant()) { if (messageObject.isMediaEmpty()) { if (!shortMessage && messageObject.messageOwner.message != null && messageObject.messageOwner.message.length() != 0) { msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, messageObject.messageOwner.message); @@ -867,6 +954,8 @@ private String getStringForMessage(MessageObject messageObject, boolean shortMes msg = LocaleController.formatString("ChannelMessageVideo", R.string.ChannelMessageVideo, name, chat.title); } else if (messageObject.isVoice()) { msg = LocaleController.formatString("ChannelMessageAudio", R.string.ChannelMessageAudio, name, chat.title); + } else if (messageObject.isMusic()) { + msg = LocaleController.formatString("ChannelMessageMusic", R.string.ChannelMessageMusic, name, chat.title); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) { msg = LocaleController.formatString("ChannelMessageContact", R.string.ChannelMessageContact, name, chat.title); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaVenue) { @@ -893,6 +982,8 @@ private String getStringForMessage(MessageObject messageObject, boolean shortMes msg = LocaleController.formatString("ChannelMessageGroupVideo", R.string.ChannelMessageGroupVideo, name, chat.title); } else if (messageObject.isVoice()) { msg = LocaleController.formatString("ChannelMessageGroupAudio", R.string.ChannelMessageGroupAudio, name, chat.title); + } else if (messageObject.isMusic()) { + msg = LocaleController.formatString("ChannelMessageGroupMusic", R.string.ChannelMessageGroupMusic, name, chat.title); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) { msg = LocaleController.formatString("ChannelMessageGroupContact", R.string.ChannelMessageGroupContact, name, chat.title); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaVenue) { @@ -920,6 +1011,8 @@ private String getStringForMessage(MessageObject messageObject, boolean shortMes msg = LocaleController.formatString("NotificationMessageGroupVideo", R.string.NotificationMessageGroupVideo, name, chat.title); } else if (messageObject.isVoice()) { msg = LocaleController.formatString("NotificationMessageGroupAudio", R.string.NotificationMessageGroupAudio, name, chat.title); + } else if (messageObject.isMusic()) { + msg = LocaleController.formatString("NotificationMessageGroupMusic", R.string.NotificationMessageGroupMusic, name, chat.title); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) { msg = LocaleController.formatString("NotificationMessageGroupContact", R.string.NotificationMessageGroupContact, name, chat.title); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaVenue) { @@ -1073,7 +1166,7 @@ private void playInChatSound() { try { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE); - int notifyOverride = getNotifyOverride(preferences, openned_dialog_id); + int notifyOverride = getNotifyOverride(preferences, opened_dialog_id); if (notifyOverride == 2) { return; } @@ -1405,12 +1498,6 @@ private void showOrUpdateNotification(boolean notifyAboutLast) { } } - if (silent == 1) { - FileLog.e("tmessages", "don't notify " + lastMessage); - } else { - FileLog.e("tmessages", "notify" + lastMessage); - } - if (!notifyAboutLast || silent == 1) { mBuilder.setPriority(NotificationCompat.PRIORITY_LOW); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java index 05c96ff7ac..35c22b8548 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java @@ -820,6 +820,7 @@ public void editMessage(MessageObject messageObject, String message, boolean sea req.message = message; req.id = messageObject.getId(); req.no_webpage = !searchLinks; + FileLog.d("tmessages", "try to edit message " + req.id + " in channel " + req.channel.channel_id + " hash " + req.channel.access_hash + " message " + req.message); final int reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(TLObject response, TLRPC.TL_error error) { @@ -2002,6 +2003,7 @@ public void run() { if (!isSentError) { newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SENT; + NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, (isBroadcast ? oldId : newMsgObj.id), newMsgObj, newMsgObj.dialog_id); //TODO remove later? MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { @Override public void run() { @@ -2274,7 +2276,7 @@ private static boolean prepareSendingDocumentInternal(String path, String origin if (extension == null) { extension = "txt"; } - path = MediaController.copyDocumentToCache(uri, extension); + path = MediaController.copyFileToCache(uri, extension); if (path == null) { return false; } @@ -2923,12 +2925,12 @@ public void run() { if (MediaController.isGif(uri)) { isDocument = true; originalPath = uri.toString(); - tempPath = MediaController.copyDocumentToCache(uri, "gif"); + tempPath = MediaController.copyFileToCache(uri, "gif"); extension = "gif"; } else if (MediaController.isWebp(uri)) { isDocument = true; originalPath = uri.toString(); - tempPath = MediaController.copyDocumentToCache(uri, "webp"); + tempPath = MediaController.copyFileToCache(uri, "webp"); extension = "webp"; } } @@ -3009,8 +3011,7 @@ public void run() { } TLRPC.TL_document document = null; if (!isEncrypted) { - TLObject object = MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 2 : 5); - document = (TLRPC.TL_document) object; + //document = (TLRPC.TL_document) MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 2 : 5); } if (document == null) { Bitmap thumb = ThumbnailUtils.createVideoThumbnail(videoPath, MediaStore.Video.Thumbnails.MINI_KIND); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/TgChooserTargetService.java b/TMessagesProj/src/main/java/org/telegram/messenger/TgChooserTargetService.java index 6f017e6730..7f89fcb80e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/TgChooserTargetService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/TgChooserTargetService.java @@ -9,8 +9,10 @@ package org.telegram.messenger; import android.annotation.TargetApi; +import android.app.Activity; import android.content.ComponentName; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapShader; @@ -48,6 +50,11 @@ public List onGetChooserTargets(ComponentName targetActivityName, if (!UserConfig.isClientActivated()) { return targets; } + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + if (!preferences.getBoolean("direct_share", true)) { + return targets; + } + ImageLoader imageLoader = ImageLoader.getInstance(); final Semaphore semaphore = new Semaphore(0); final ComponentName componentName = new ComponentName(getPackageName(), LaunchActivity.class.getCanonicalName()); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/BotQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/BotQuery.java index ab3f8a9f89..03646523ba 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/query/BotQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/BotQuery.java @@ -179,7 +179,7 @@ public void run() { } public static void putBotInfo(final TLRPC.BotInfo botInfo) { - if (botInfo == null || botInfo instanceof TLRPC.TL_botInfoEmpty) { + if (botInfo == null) { return; } botInfos.put(botInfo.user_id, botInfo); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/ReplyMessageQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesQuery.java similarity index 65% rename from TMessagesProj/src/main/java/org/telegram/messenger/query/ReplyMessageQuery.java rename to TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesQuery.java index 4ef51007c4..d7e767ab3f 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/query/ReplyMessageQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesQuery.java @@ -29,10 +29,158 @@ import java.util.HashMap; import java.util.Locale; -public class ReplyMessageQuery { +public class MessagesQuery { - public static void loadReplyMessagesForMessages(final ArrayList messages, final long dialog_id) { - if ((int) dialog_id == 0) { + public static MessageObject loadPinnedMessage(final int channelId, final int mid, boolean useQueue) { + if (useQueue) { + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + loadPinnedMessageInternal(channelId, mid, false); + } + }); + } else { + return loadPinnedMessageInternal(channelId, mid, true); + } + return null; + } + + private static MessageObject loadPinnedMessageInternal(final int channelId, final int mid, boolean returnValue) { + try { + long messageId = ((long) mid) | ((long) channelId) << 32; + + TLRPC.Message result = null; + final ArrayList users = new ArrayList<>(); + final ArrayList chats = new ArrayList<>(); + ArrayList usersToLoad = new ArrayList<>(); + ArrayList chatsToLoad = new ArrayList<>(); + + SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid, date FROM messages WHERE mid = %d", messageId)); + if (cursor.next()) { + NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); + if (data != null && cursor.byteBufferValue(0, data) != 0) { + result = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + result.id = cursor.intValue(1); + result.date = cursor.intValue(2); + result.dialog_id = -channelId; + MessagesStorage.addUsersAndChatsFromMessage(result, usersToLoad, chatsToLoad); + } + data.reuse(); + } + cursor.dispose(); + + if (result == null) { + cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data FROM chat_pinned WHERE uid = %d", channelId)); + if (cursor.next()) { + NativeByteBuffer data = new NativeByteBuffer(cursor.byteArrayLength(0)); + if (data != null && cursor.byteBufferValue(0, data) != 0) { + result = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + if (result.id != mid) { + result = null; + } else { + result.dialog_id = -channelId; + MessagesStorage.addUsersAndChatsFromMessage(result, usersToLoad, chatsToLoad); + } + } + data.reuse(); + } + cursor.dispose(); + } + + if (result == null) { + final TLRPC.TL_channels_getMessages req = new TLRPC.TL_channels_getMessages(); + req.channel = MessagesController.getInputChannel(channelId); + req.id.add(mid); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + boolean ok = false; + if (error == null) { + TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; + if (!messagesRes.messages.isEmpty()) { + ImageLoader.saveMessagesThumbs(messagesRes.messages); + broadcastPinnedMessage(messagesRes.messages.get(0), messagesRes.users, messagesRes.chats, false, false); + MessagesStorage.getInstance().putUsersAndChats(messagesRes.users, messagesRes.chats, true, true); + savePinnedMessage(messagesRes.messages.get(0)); + ok = true; + } + } + if (!ok) { + MessagesStorage.getInstance().updateChannelPinnedMessage(channelId, 0); + } + } + }); + } else { + if (returnValue) { + return broadcastPinnedMessage(result, users, chats, true, returnValue); + } else { + if (!usersToLoad.isEmpty()) { + MessagesStorage.getInstance().getUsersInternal(TextUtils.join(",", usersToLoad), users); + } + if (!chatsToLoad.isEmpty()) { + MessagesStorage.getInstance().getChatsInternal(TextUtils.join(",", chatsToLoad), chats); + } + broadcastPinnedMessage(result, users, chats, true, false); + } + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + return null; + } + + private static void savePinnedMessage(final TLRPC.Message result) { + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + try { + MessagesStorage.getInstance().getDatabase().beginTransaction(); + SQLitePreparedStatement state = MessagesStorage.getInstance().getDatabase().executeFast("REPLACE INTO chat_pinned VALUES(?, ?, ?)"); + NativeByteBuffer data = new NativeByteBuffer(result.getObjectSize()); + result.serializeToStream(data); + state.requery(); + state.bindInteger(1, result.to_id.channel_id); + state.bindInteger(2, result.id); + state.bindByteBuffer(3, data); + state.step(); + data.reuse(); + state.dispose(); + MessagesStorage.getInstance().getDatabase().commitTransaction(); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + }); + } + + private static MessageObject broadcastPinnedMessage(final TLRPC.Message result, final ArrayList users, final ArrayList chats, final boolean isCache, boolean returnValue) { + final HashMap usersDict = new HashMap<>(); + for (int a = 0; a < users.size(); a++) { + TLRPC.User user = users.get(a); + usersDict.put(user.id, user); + } + final HashMap chatsDict = new HashMap<>(); + for (int a = 0; a < chats.size(); a++) { + TLRPC.Chat chat = chats.get(a); + chatsDict.put(chat.id, chat); + } + if (returnValue) { + return new MessageObject(result, usersDict, chatsDict, false); + } else { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + MessagesController.getInstance().putUsers(users, isCache); + MessagesController.getInstance().putChats(chats, isCache); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.didLoadedPinnedMessage, new MessageObject(result, usersDict, chatsDict, false)); + } + }); + } + return null; + } + + public static void loadReplyMessagesForMessages(final ArrayList messages, final long dialogId) { + if ((int) dialogId == 0) { final ArrayList replyMessages = new ArrayList<>(); final HashMap> replyMessageRandomOwners = new HashMap<>(); final StringBuilder stringBuilder = new StringBuilder(); @@ -70,7 +218,7 @@ public void run() { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.id = cursor.intValue(1); message.date = cursor.intValue(2); - message.dialog_id = dialog_id; + message.dialog_id = dialogId; ArrayList arrayList = replyMessageRandomOwners.remove(cursor.longValue(3)); @@ -97,7 +245,7 @@ public void run() { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.didLoadedReplyMessages, dialog_id); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.didLoadedReplyMessages, dialogId); } }); } catch (Exception e) { @@ -105,7 +253,6 @@ public void run() { } } }); - } else { final ArrayList replyMessages = new ArrayList<>(); final HashMap> replyMessageOwners = new HashMap<>(); @@ -157,7 +304,7 @@ public void run() { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.id = cursor.intValue(1); message.date = cursor.intValue(2); - message.dialog_id = dialog_id; + message.dialog_id = dialogId; MessagesStorage.addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); result.add(message); replyMessages.remove((Integer) message.id); @@ -172,7 +319,7 @@ public void run() { if (!chatsToLoad.isEmpty()) { MessagesStorage.getInstance().getChatsInternal(TextUtils.join(",", chatsToLoad), chats); } - broadcastReplyMessages(result, replyMessageOwners, users, chats, dialog_id, true); + broadcastReplyMessages(result, replyMessageOwners, users, chats, dialogId, true); if (!replyMessages.isEmpty()) { if (channelIdFinal != 0) { @@ -185,7 +332,7 @@ public void run(TLObject response, TLRPC.TL_error error) { if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; ImageLoader.saveMessagesThumbs(messagesRes.messages); - broadcastReplyMessages(messagesRes.messages, replyMessageOwners, messagesRes.users, messagesRes.chats, dialog_id, false); + broadcastReplyMessages(messagesRes.messages, replyMessageOwners, messagesRes.users, messagesRes.chats, dialogId, false); MessagesStorage.getInstance().putUsersAndChats(messagesRes.users, messagesRes.chats, true, true); saveReplyMessages(replyMessageOwners, messagesRes.messages); } @@ -200,7 +347,7 @@ public void run(TLObject response, TLRPC.TL_error error) { if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; ImageLoader.saveMessagesThumbs(messagesRes.messages); - broadcastReplyMessages(messagesRes.messages, replyMessageOwners, messagesRes.users, messagesRes.chats, dialog_id, false); + broadcastReplyMessages(messagesRes.messages, replyMessageOwners, messagesRes.users, messagesRes.chats, dialogId, false); MessagesStorage.getInstance().putUsersAndChats(messagesRes.users, messagesRes.chats, true, true); saveReplyMessages(replyMessageOwners, messagesRes.messages); } @@ -223,12 +370,14 @@ public void run() { try { MessagesStorage.getInstance().getDatabase().beginTransaction(); SQLitePreparedStatement state = MessagesStorage.getInstance().getDatabase().executeFast("UPDATE messages SET replydata = ? WHERE mid = ?"); - for (TLRPC.Message message : result) { + for (int a = 0; a < result.size(); a++) { + TLRPC.Message message = result.get(a); ArrayList messageObjects = replyMessageOwners.get(message.id); if (messageObjects != null) { NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); - for (MessageObject messageObject : messageObjects) { + for (int b = 0; b < messageObjects.size(); b++) { + MessageObject messageObject = messageObjects.get(b); state.requery(); long messageId = messageObject.getId(); if (messageObject.messageOwner.to_id.channel_id != 0) { @@ -275,6 +424,9 @@ public void run() { for (int b = 0; b < arrayList.size(); b++) { MessageObject m = arrayList.get(b); m.replyMessageObject = messageObject; + if (m.messageOwner.action instanceof TLRPC.TL_messageActionPinMessage) { + m.generatePinMessageText(null, null); + } } changed = true; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/util/AsyncListUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/AsyncListUtil.java index f16cad0c4c..70e53dd4a9 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/util/AsyncListUtil.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/AsyncListUtil.java @@ -22,9 +22,6 @@ import android.util.SparseBooleanArray; import android.util.SparseIntArray; -import org.telegram.messenger.support.util.ThreadUtil; -import org.telegram.messenger.support.util.TileList; - /** * A utility class that supports asynchronous content loading. *

@@ -42,7 +39,7 @@ * Note that this class uses a single thread to load the data, so it suitable to load data from * secondary storage such as disk, but not from network. *

- * This class is designed to work with {@link org.telegram.messenger.support.widget.RecyclerView}, but it does + * This class is designed to work with {@link android.support.v7.widget.RecyclerView}, but it does * not depend on it and can be used with other list views. * */ @@ -113,7 +110,7 @@ private boolean isRefreshPending() { *

* Identifies the data items that have not been loaded yet and initiates loading them in the * background. Should be called from the view's scroll listener (such as - * {@link org.telegram.messenger.support.widget.RecyclerView.OnScrollListener#onScrolled}). + * {@link android.support.v7.widget.RecyclerView.OnScrollListener#onScrolled}). */ public void onRangeChanged() { if (isRefreshPending()) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/util/MessageThreadUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/MessageThreadUtil.java index a9d59b1fe1..bf78e6aa91 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/util/MessageThreadUtil.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/MessageThreadUtil.java @@ -18,10 +18,11 @@ import android.os.Handler; import android.os.Looper; +import android.support.v4.content.ParallelExecutorCompat; import android.util.Log; import java.util.concurrent.Executor; -import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; class MessageThreadUtil implements ThreadUtil { @@ -83,7 +84,8 @@ public void run() { public BackgroundCallback getBackgroundProxy(final BackgroundCallback callback) { return new BackgroundCallback() { final private MessageQueue mQueue = new MessageQueue(); - final private Executor mExecutor = Executors.newSingleThreadExecutor(); + final private Executor mExecutor = ParallelExecutorCompat.getParallelExecutor(); + AtomicBoolean mBackgroundRunning = new AtomicBoolean(false); private static final int REFRESH = 1; private static final int UPDATE_RANGE = 2; @@ -114,42 +116,51 @@ public void recycleTile(TileList.Tile tile) { private void sendMessage(SyncQueueItem msg) { mQueue.sendMessage(msg); - mExecutor.execute(mBackgroundRunnable); + maybeExecuteBackgroundRunnable(); } private void sendMessageAtFrontOfQueue(SyncQueueItem msg) { mQueue.sendMessageAtFrontOfQueue(msg); - mExecutor.execute(mBackgroundRunnable); + maybeExecuteBackgroundRunnable(); + } + + private void maybeExecuteBackgroundRunnable() { + if (mBackgroundRunning.compareAndSet(false, true)) { + mExecutor.execute(mBackgroundRunnable); + } } private Runnable mBackgroundRunnable = new Runnable() { @Override public void run() { - SyncQueueItem msg = mQueue.next(); - if (msg == null) { - return; - } - switch (msg.what) { - case REFRESH: - mQueue.removeMessages(REFRESH); - callback.refresh(msg.arg1); + while (true) { + SyncQueueItem msg = mQueue.next(); + if (msg == null) { break; - case UPDATE_RANGE: - mQueue.removeMessages(UPDATE_RANGE); - mQueue.removeMessages(LOAD_TILE); - callback.updateRange( - msg.arg1, msg.arg2, msg.arg3, msg.arg4, msg.arg5); - break; - case LOAD_TILE: - callback.loadTile(msg.arg1, msg.arg2); - break; - case RECYCLE_TILE: - //noinspection unchecked - callback.recycleTile((TileList.Tile) msg.data); - break; - default: - Log.e("ThreadUtil", "Unsupported message, what=" + msg.what); + } + switch (msg.what) { + case REFRESH: + mQueue.removeMessages(REFRESH); + callback.refresh(msg.arg1); + break; + case UPDATE_RANGE: + mQueue.removeMessages(UPDATE_RANGE); + mQueue.removeMessages(LOAD_TILE); + callback.updateRange( + msg.arg1, msg.arg2, msg.arg3, msg.arg4, msg.arg5); + break; + case LOAD_TILE: + callback.loadTile(msg.arg1, msg.arg2); + break; + case RECYCLE_TILE: + //noinspection unchecked + callback.recycleTile((TileList.Tile) msg.data); + break; + default: + Log.e("ThreadUtil", "Unsupported message, what=" + msg.what); + } } + mBackgroundRunning.set(false); } }; }; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/util/SortedList.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/SortedList.java index 319428f0b0..82c555d09f 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/util/SortedList.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/SortedList.java @@ -24,7 +24,7 @@ /** * A Sorted list implementation that can keep items in order and also notify for changes in the * list - * such that it can be bound to a {@link org.telegram.messenger.support.widget.RecyclerView.Adapter + * such that it can be bound to a {@link android.support.v7.widget.RecyclerView.Adapter * RecyclerView.Adapter}. *

* It keeps items ordered using the {@link Callback#compare(Object, Object)} method and uses @@ -737,7 +737,7 @@ public static abstract class Callback implements Comparator { * so * that you can change its behavior depending on your UI. *

- * For example, if you are using SortedList with a {@link org.telegram.messenger.support.widget.RecyclerView.Adapter + * For example, if you are using SortedList with a {@link android.support.v7.widget.RecyclerView.Adapter * RecyclerView.Adapter}, you should * return whether the items' visual representations are the same or not. * diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/util/ThreadUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/ThreadUtil.java index c9a1583ff6..cdb8689fcd 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/util/ThreadUtil.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/ThreadUtil.java @@ -16,8 +16,6 @@ package org.telegram.messenger.support.util; -import org.telegram.messenger.support.util.TileList; - interface ThreadUtil { interface MainThreadCallback { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/AdapterHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/AdapterHelper.java index 8ea6697311..1c40eafc70 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/AdapterHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/AdapterHelper.java @@ -19,9 +19,6 @@ import android.support.v4.util.Pools; import android.util.Log; -import org.telegram.messenger.support.widget.OpReorderer; -import org.telegram.messenger.support.widget.RecyclerView; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -70,6 +67,8 @@ class AdapterHelper implements OpReorderer.Callback { final OpReorderer mOpReorderer; + private int mExistingUpdateTypes = 0; + AdapterHelper(Callback callback) { this(callback, false); } @@ -88,6 +87,7 @@ AdapterHelper addUpdateOp(UpdateOp... ops) { void reset() { recycleUpdateOpsAndClearList(mPendingUpdates); recycleUpdateOpsAndClearList(mPostponedList); + mExistingUpdateTypes = 0; } void preProcess() { @@ -122,6 +122,7 @@ void consumePostponedUpdates() { mCallback.onDispatchSecondPass(mPostponedList.get(i)); } recycleUpdateOpsAndClearList(mPostponedList); + mExistingUpdateTypes = 0; } private void applyMove(UpdateOp op) { @@ -460,6 +461,10 @@ boolean hasPendingUpdates() { return mPendingUpdates.size() > 0; } + boolean hasAnyUpdateTypes(int updateTypes) { + return (mExistingUpdateTypes & updateTypes) != 0; + } + int findPositionOffset(int position) { return findPositionOffset(position, 0); } @@ -498,6 +503,7 @@ int findPositionOffset(int position, int firstPostponedItem) { */ boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) { mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload)); + mExistingUpdateTypes |= UpdateOp.UPDATE; return mPendingUpdates.size() == 1; } @@ -506,6 +512,7 @@ boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) { */ boolean onItemRangeInserted(int positionStart, int itemCount) { mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null)); + mExistingUpdateTypes |= UpdateOp.ADD; return mPendingUpdates.size() == 1; } @@ -514,6 +521,7 @@ boolean onItemRangeInserted(int positionStart, int itemCount) { */ boolean onItemRangeRemoved(int positionStart, int itemCount) { mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount, null)); + mExistingUpdateTypes |= UpdateOp.REMOVE; return mPendingUpdates.size() == 1; } @@ -522,12 +530,13 @@ boolean onItemRangeRemoved(int positionStart, int itemCount) { */ boolean onItemRangeMoved(int from, int to, int itemCount) { if (from == to) { - return false;//no-op + return false; // no-op } if (itemCount != 1) { throw new IllegalArgumentException("Moving more than 1 item is not supported yet"); } mPendingUpdates.add(obtainUpdateOp(UpdateOp.MOVE, from, to, null)); + mExistingUpdateTypes |= UpdateOp.MOVE; return mPendingUpdates.size() == 1; } @@ -564,6 +573,7 @@ void consumeUpdatesInOnePass() { } } recycleUpdateOpsAndClearList(mPendingUpdates); + mExistingUpdateTypes = 0; } public int applyPendingUpdatesToPosition(int position) { @@ -602,18 +612,22 @@ public int applyPendingUpdatesToPosition(int position) { return position; } + boolean hasUpdates() { + return !mPostponedList.isEmpty() && !mPendingUpdates.isEmpty(); + } + /** * Queued operation to happen when child views are updated. */ static class UpdateOp { - static final int ADD = 0; + static final int ADD = 1; - static final int REMOVE = 1; + static final int REMOVE = 1 << 1; - static final int UPDATE = 2; + static final int UPDATE = 1 << 2; - static final int MOVE = 3; + static final int MOVE = 1 << 3; static final int POOL_SIZE = 30; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ChildHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ChildHelper.java index e247875c3e..582b8f245e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ChildHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ChildHelper.java @@ -208,8 +208,8 @@ View findHiddenNonRemovedView(int position, int type) { for (int i = 0; i < count; i++) { final View view = mHiddenViews.get(i); RecyclerView.ViewHolder holder = mCallback.getChildViewHolder(view); - if (holder.getLayoutPosition() == position && !holder.isInvalid() && - (type == RecyclerView.INVALID_TYPE || holder.getItemViewType() == type)) { + if (holder.getLayoutPosition() == position && !holder.isInvalid() && !holder.isRemoved() + && (type == RecyclerView.INVALID_TYPE || holder.getItemViewType() == type)) { return view; } } @@ -339,6 +339,25 @@ void hide(View view) { } } + /** + * Moves a child view from hidden list to regular list. + * Calling this method should probably be followed by a detach, otherwise, it will suddenly + * show up in LayoutManager's children list. + * + * @param view The hidden View to unhide + */ + void unhide(View view) { + final int offset = mCallback.indexOfChild(view); + if (offset < 0) { + throw new IllegalArgumentException("view is not a child, cannot hide " + view); + } + if (!mBucket.get(offset)) { + throw new RuntimeException("trying to unhide a view that was not hidden" + view); + } + mBucket.clear(offset); + unhideViewInternal(view); + } + @Override public String toString() { return mBucket.toString() + ", hidden list:" + mHiddenViews.size(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/DefaultItemAnimator.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/DefaultItemAnimator.java index ad58b3d6cd..546f93f0af 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/DefaultItemAnimator.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/DefaultItemAnimator.java @@ -15,12 +15,11 @@ */ package org.telegram.messenger.support.widget; +import android.support.annotation.NonNull; import android.support.v4.animation.AnimatorCompatHelper; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPropertyAnimatorCompat; import android.support.v4.view.ViewPropertyAnimatorListener; - -import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.messenger.support.widget.RecyclerView.ViewHolder; import android.view.View; @@ -34,23 +33,22 @@ * * @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator) */ -public class DefaultItemAnimator extends RecyclerView.ItemAnimator { +public class DefaultItemAnimator extends SimpleItemAnimator { private static final boolean DEBUG = false; - private ArrayList mPendingRemovals = new ArrayList(); - private ArrayList mPendingAdditions = new ArrayList(); - private ArrayList mPendingMoves = new ArrayList(); - private ArrayList mPendingChanges = new ArrayList(); + private ArrayList mPendingRemovals = new ArrayList<>(); + private ArrayList mPendingAdditions = new ArrayList<>(); + private ArrayList mPendingMoves = new ArrayList<>(); + private ArrayList mPendingChanges = new ArrayList<>(); - private ArrayList> mAdditionsList = - new ArrayList>(); - private ArrayList> mMovesList = new ArrayList>(); - private ArrayList> mChangesList = new ArrayList>(); + private ArrayList> mAdditionsList = new ArrayList<>(); + private ArrayList> mMovesList = new ArrayList<>(); + private ArrayList> mChangesList = new ArrayList<>(); - private ArrayList mAddAnimations = new ArrayList(); - private ArrayList mMoveAnimations = new ArrayList(); - private ArrayList mRemoveAnimations = new ArrayList(); - private ArrayList mChangeAnimations = new ArrayList(); + private ArrayList mAddAnimations = new ArrayList<>(); + private ArrayList mMoveAnimations = new ArrayList<>(); + private ArrayList mRemoveAnimations = new ArrayList<>(); + private ArrayList mChangeAnimations = new ArrayList<>(); private static class MoveInfo { public ViewHolder holder; @@ -112,7 +110,7 @@ public void runPendingAnimations() { mPendingRemovals.clear(); // Next, move stuff if (movesPending) { - final ArrayList moves = new ArrayList(); + final ArrayList moves = new ArrayList<>(); moves.addAll(mPendingMoves); mMovesList.add(moves); mPendingMoves.clear(); @@ -136,7 +134,7 @@ public void run() { } // Next, change stuff, to run in parallel with move animations if (changesPending) { - final ArrayList changes = new ArrayList(); + final ArrayList changes = new ArrayList<>(); changes.addAll(mPendingChanges); mChangesList.add(changes); mPendingChanges.clear(); @@ -159,7 +157,7 @@ public void run() { } // Next, add stuff if (additionsPending) { - final ArrayList additions = new ArrayList(); + final ArrayList additions = new ArrayList<>(); additions.addAll(mPendingAdditions); mAdditionsList.add(additions); mPendingAdditions.clear(); @@ -312,6 +310,11 @@ public void onAnimationEnd(View view) { @Override public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder, int fromX, int fromY, int toX, int toY) { + if (oldHolder == newHolder) { + // Don't know how to run change animations when the same view holder is re-used. + // run a move animation to handle position changes. + return animateMove(oldHolder, fromX, fromY, toX, toY); + } final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView); final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView); final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView); @@ -322,7 +325,7 @@ public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder, ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX); ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY); ViewCompat.setAlpha(oldHolder.itemView, prevAlpha); - if (newHolder != null && newHolder.itemView != null) { + if (newHolder != null) { // carry over translation values resetAnimation(newHolder); ViewCompat.setTranslationX(newHolder.itemView, -deltaX); @@ -481,21 +484,25 @@ public void endAnimation(ViewHolder item) { } // animations should be ended by the cancel above. + //noinspection PointlessBooleanExpression,ConstantConditions if (mRemoveAnimations.remove(item) && DEBUG) { throw new IllegalStateException("after animation is cancelled, item should not be in " + "mRemoveAnimations list"); } + //noinspection PointlessBooleanExpression,ConstantConditions if (mAddAnimations.remove(item) && DEBUG) { throw new IllegalStateException("after animation is cancelled, item should not be in " + "mAddAnimations list"); } + //noinspection PointlessBooleanExpression,ConstantConditions if (mChangeAnimations.remove(item) && DEBUG) { throw new IllegalStateException("after animation is cancelled, item should not be in " + "mChangeAnimations list"); } + //noinspection PointlessBooleanExpression,ConstantConditions if (mMoveAnimations.remove(item) && DEBUG) { throw new IllegalStateException("after animation is cancelled, item should not be in " + "mMoveAnimations list"); @@ -626,6 +633,28 @@ void cancelAll(List viewHolders) { } } + /** + * {@inheritDoc} + *

+ * If the payload list is not empty, DefaultItemAnimator returns true. + * When this is the case: + *

    + *
  • If you override {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}, both + * ViewHolder arguments will be the same instance. + *
  • + *
  • + * If you are not overriding {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}, + * then DefaultItemAnimator will call {@link #animateMove(ViewHolder, int, int, int, int)} and + * run a move animation instead. + *
  • + *
+ */ + @Override + public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder, + @NonNull List payloads) { + return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads); + } + private static class VpaListenerAdapter implements ViewPropertyAnimatorListener { @Override public void onAnimationStart(View view) {} @@ -635,5 +664,5 @@ public void onAnimationEnd(View view) {} @Override public void onAnimationCancel(View view) {} - }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/GridLayoutManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/GridLayoutManager.java index f347ce3c01..84c3cb49c7 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/GridLayoutManager.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/GridLayoutManager.java @@ -37,11 +37,6 @@ public class GridLayoutManager extends LinearLayoutManager { private static final boolean DEBUG = false; private static final String TAG = "GridLayoutManager"; public static final int DEFAULT_SPAN_COUNT = -1; - /** - * The measure spec for the scroll direction. - */ - static final int MAIN_DIR_SPEC = - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); /** * Span size have been changed but we've not done a new layout calculation. */ @@ -63,6 +58,21 @@ public class GridLayoutManager extends LinearLayoutManager { // re-used variable to acquire decor insets from RecyclerView final Rect mDecorInsets = new Rect(); + + /** + * Constructor used when layout manager is set in XML by RecyclerView attribute + * "layoutManager". If spanCount is not specified in the XML, it defaults to a + * single column. + * + * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount + */ + public GridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes); + setSpanCount(properties.spanCount); + } + /** * Creates a vertical GridLayoutManager * @@ -110,7 +120,9 @@ public int getRowCountForAccessibility(RecyclerView.Recycler recycler, if (state.getItemCount() < 1) { return 0; } - return getSpanGroupIndex(recycler, state, state.getItemCount() - 1); + + // Row count is one more than the last item's row index. + return getSpanGroupIndex(recycler, state, state.getItemCount() - 1) + 1; } @Override @@ -122,7 +134,9 @@ public int getColumnCountForAccessibility(RecyclerView.Recycler recycler, if (state.getItemCount() < 1) { return 0; } - return getSpanGroupIndex(recycler, state, state.getItemCount() - 1); + + // Column count is one more than the last item's column index. + return getSpanGroupIndex(recycler, state, state.getItemCount() - 1) + 1; } @Override @@ -206,8 +220,13 @@ public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCo @Override public RecyclerView.LayoutParams generateDefaultLayoutParams() { - return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT); + if (mOrientation == HORIZONTAL) { + return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.FILL_PARENT); + } else { + return new LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + } } @Override @@ -258,47 +277,124 @@ private void updateMeasurements() { calculateItemBorders(totalSpace); } + @Override + public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) { + if (mCachedBorders == null) { + super.setMeasuredDimension(childrenBounds, wSpec, hSpec); + } + final int width, height; + final int horizontalPadding = getPaddingLeft() + getPaddingRight(); + final int verticalPadding = getPaddingTop() + getPaddingBottom(); + if (mOrientation == VERTICAL) { + final int usedHeight = childrenBounds.height() + verticalPadding; + height = chooseSize(hSpec, usedHeight, getMinimumHeight()); + width = chooseSize(wSpec, mCachedBorders[mCachedBorders.length - 1] + horizontalPadding, + getMinimumWidth()); + } else { + final int usedWidth = childrenBounds.width() + horizontalPadding; + width = chooseSize(wSpec, usedWidth, getMinimumWidth()); + height = chooseSize(hSpec, mCachedBorders[mCachedBorders.length - 1] + verticalPadding, + getMinimumHeight()); + } + setMeasuredDimension(width, height); + } + + /** + * @param totalSpace Total available space after padding is removed + */ private void calculateItemBorders(int totalSpace) { - if (mCachedBorders == null || mCachedBorders.length != mSpanCount + 1 - || mCachedBorders[mCachedBorders.length - 1] != totalSpace) { - mCachedBorders = new int[mSpanCount + 1]; + mCachedBorders = calculateItemBorders(mCachedBorders, mSpanCount, totalSpace); + } + + /** + * @param cachedBorders The out array + * @param spanCount number of spans + * @param totalSpace total available space after padding is removed + * @return The updated array. Might be the same instance as the provided array if its size + * has not changed. + */ + static int[] calculateItemBorders(int[] cachedBorders, int spanCount, int totalSpace) { + if (cachedBorders == null || cachedBorders.length != spanCount + 1 + || cachedBorders[cachedBorders.length - 1] != totalSpace) { + cachedBorders = new int[spanCount + 1]; } - mCachedBorders[0] = 0; - int sizePerSpan = totalSpace / mSpanCount; - int sizePerSpanRemainder = totalSpace % mSpanCount; + cachedBorders[0] = 0; + int sizePerSpan = totalSpace / spanCount; + int sizePerSpanRemainder = totalSpace % spanCount; int consumedPixels = 0; int additionalSize = 0; - for (int i = 1; i <= mSpanCount; i++) { + for (int i = 1; i <= spanCount; i++) { int itemSize = sizePerSpan; additionalSize += sizePerSpanRemainder; - if (additionalSize > 0 && (mSpanCount - additionalSize) < sizePerSpanRemainder) { + if (additionalSize > 0 && (spanCount - additionalSize) < sizePerSpanRemainder) { itemSize += 1; - additionalSize -= mSpanCount; + additionalSize -= spanCount; } consumedPixels += itemSize; - mCachedBorders[i] = consumedPixels; + cachedBorders[i] = consumedPixels; } + return cachedBorders; } @Override void onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state, - AnchorInfo anchorInfo) { - super.onAnchorReady(recycler, state, anchorInfo); + AnchorInfo anchorInfo, int itemDirection) { + super.onAnchorReady(recycler, state, anchorInfo, itemDirection); updateMeasurements(); if (state.getItemCount() > 0 && !state.isPreLayout()) { - ensureAnchorIsInFirstSpan(recycler, state, anchorInfo); + ensureAnchorIsInCorrectSpan(recycler, state, anchorInfo, itemDirection); } + ensureViewSet(); + } + + private void ensureViewSet() { if (mSet == null || mSet.length != mSpanCount) { mSet = new View[mSpanCount]; } } - private void ensureAnchorIsInFirstSpan(RecyclerView.Recycler recycler, RecyclerView.State state, - AnchorInfo anchorInfo) { + @Override + public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, + RecyclerView.State state) { + updateMeasurements(); + ensureViewSet(); + return super.scrollHorizontallyBy(dx, recycler, state); + } + + @Override + public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, + RecyclerView.State state) { + updateMeasurements(); + ensureViewSet(); + return super.scrollVerticallyBy(dy, recycler, state); + } + + private void ensureAnchorIsInCorrectSpan(RecyclerView.Recycler recycler, + RecyclerView.State state, AnchorInfo anchorInfo, int itemDirection) { + final boolean layingOutInPrimaryDirection = + itemDirection == LayoutState.ITEM_DIRECTION_TAIL; int span = getSpanIndex(recycler, state, anchorInfo.mPosition); - while (span > 0 && anchorInfo.mPosition > 0) { - anchorInfo.mPosition--; - span = getSpanIndex(recycler, state, anchorInfo.mPosition); + if (layingOutInPrimaryDirection) { + // choose span 0 + while (span > 0 && anchorInfo.mPosition > 0) { + anchorInfo.mPosition--; + span = getSpanIndex(recycler, state, anchorInfo.mPosition); + } + } else { + // choose the max span we can get. hopefully last one + final int indexLimit = state.getItemCount() - 1; + int pos = anchorInfo.mPosition; + int bestSpan = span; + while (pos < indexLimit) { + int next = getSpanIndex(recycler, state, pos + 1); + if (next > bestSpan) { + pos += 1; + bestSpan = next; + } else { + break; + } + } + anchorInfo.mPosition = pos; } } @@ -398,6 +494,15 @@ private int getSpanSize(RecyclerView.Recycler recycler, RecyclerView.State state @Override void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { + final int otherDirSpecMode = mOrientationHelper.getModeInOther(); + final boolean flexibleInOtherDir = otherDirSpecMode != View.MeasureSpec.EXACTLY; + final int currentOtherDirSize = getChildCount() > 0 ? mCachedBorders[mSpanCount] : 0; + // if grid layout's dimensions are not specified, let the new row change the measurements + // This is not perfect since we not covering all rows but still solves an important case + // where they may have a header row which should be laid out according to children. + if (flexibleInOtherDir) { + updateMeasurements(); // reset measurements + } final boolean layingOutInPrimaryDirection = layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_TAIL; int count = 0; @@ -435,6 +540,7 @@ void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, } int maxSize = 0; + float maxSizeInOther = 0; // use a float to get size per span // we should assign spans before item decor offsets are calculated assignSpans(recycler, state, count, consumedSpanCount, layingOutInPrimaryDirection); @@ -455,35 +561,73 @@ void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, } final LayoutParams lp = (LayoutParams) view.getLayoutParams(); - final int spec = View.MeasureSpec.makeMeasureSpec( - mCachedBorders[lp.mSpanIndex + lp.mSpanSize] - - mCachedBorders[lp.mSpanIndex], - View.MeasureSpec.EXACTLY); + final int spec = getChildMeasureSpec(mCachedBorders[lp.mSpanIndex + lp.mSpanSize] - + mCachedBorders[lp.mSpanIndex], otherDirSpecMode, 0, + mOrientation == HORIZONTAL ? lp.height : lp.width, + false); + final int mainSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), + mOrientationHelper.getMode(), 0, + mOrientation == VERTICAL ? lp.height : lp.width, true); + // Unless the child has MATCH_PARENT, measure it from its specs before adding insets. if (mOrientation == VERTICAL) { - measureChildWithDecorationsAndMargin(view, spec, getMainDirSpec(lp.height), false); + @SuppressWarnings("deprecation") + final boolean applyInsets = lp.height == ViewGroup.LayoutParams.FILL_PARENT; + measureChildWithDecorationsAndMargin(view, spec, mainSpec, applyInsets, false); } else { - measureChildWithDecorationsAndMargin(view, getMainDirSpec(lp.width), spec, false); + //noinspection deprecation + final boolean applyInsets = lp.width == ViewGroup.LayoutParams.FILL_PARENT; + measureChildWithDecorationsAndMargin(view, mainSpec, spec, applyInsets, false); } final int size = mOrientationHelper.getDecoratedMeasurement(view); if (size > maxSize) { maxSize = size; } + final float otherSize = 1f * mOrientationHelper.getDecoratedMeasurementInOther(view) / + lp.mSpanSize; + if (otherSize > maxSizeInOther) { + maxSizeInOther = otherSize; + } } - - // views that did not measure the maxSize has to be re-measured - final int maxMeasureSpec = getMainDirSpec(maxSize); + if (flexibleInOtherDir) { + // re-distribute columns + guessMeasurement(maxSizeInOther, currentOtherDirSize); + // now we should re-measure any item that was match parent. + maxSize = 0; + for (int i = 0; i < count; i++) { + View view = mSet[i]; + final LayoutParams lp = (LayoutParams) view.getLayoutParams(); + final int spec = getChildMeasureSpec(mCachedBorders[lp.mSpanIndex + lp.mSpanSize] - + mCachedBorders[lp.mSpanIndex], View.MeasureSpec.EXACTLY, 0, + mOrientation == HORIZONTAL ? lp.height : lp.width, false); + final int mainSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), + mOrientationHelper.getMode(), 0, + mOrientation == VERTICAL ? lp.height : lp.width, true); + if (mOrientation == VERTICAL) { + measureChildWithDecorationsAndMargin(view, spec, mainSpec, false, true); + } else { + measureChildWithDecorationsAndMargin(view, mainSpec, spec, false, true); + } + final int size = mOrientationHelper.getDecoratedMeasurement(view); + if (size > maxSize) { + maxSize = size; + } + } + } + // Views that did not measure the maxSize has to be re-measured + // We will stop doing this once we introduce Gravity in the GLM layout params + final int maxMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxSize, + View.MeasureSpec.EXACTLY); for (int i = 0; i < count; i ++) { final View view = mSet[i]; if (mOrientationHelper.getDecoratedMeasurement(view) != maxSize) { final LayoutParams lp = (LayoutParams) view.getLayoutParams(); - final int spec = View.MeasureSpec.makeMeasureSpec( - mCachedBorders[lp.mSpanIndex + lp.mSpanSize] - - mCachedBorders[lp.mSpanIndex], - View.MeasureSpec.EXACTLY); + final int spec = getChildMeasureSpec(mCachedBorders[lp.mSpanIndex + lp.mSpanSize] + - mCachedBorders[lp.mSpanIndex], View.MeasureSpec.EXACTLY, 0, + mOrientation == HORIZONTAL ? lp.height : lp.width, false); if (mOrientation == VERTICAL) { - measureChildWithDecorationsAndMargin(view, spec, maxMeasureSpec, true); + measureChildWithDecorationsAndMargin(view, spec, maxMeasureSpec, true, true); } else { - measureChildWithDecorationsAndMargin(view, maxMeasureSpec, spec, true); + measureChildWithDecorationsAndMargin(view, maxMeasureSpec, spec, true, true); } } } @@ -512,8 +656,13 @@ void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, View view = mSet[i]; LayoutParams params = (LayoutParams) view.getLayoutParams(); if (mOrientation == VERTICAL) { - left = getPaddingLeft() + mCachedBorders[params.mSpanIndex]; - right = left + mOrientationHelper.getDecoratedMeasurementInOther(view); + if (isLayoutRTL()) { + right = getPaddingLeft() + mCachedBorders[params.mSpanIndex + params.mSpanSize]; + left = right - mOrientationHelper.getDecoratedMeasurementInOther(view); + } else { + left = getPaddingLeft() + mCachedBorders[params.mSpanIndex]; + right = left + mOrientationHelper.getDecoratedMeasurementInOther(view); + } } else { top = getPaddingTop() + mCachedBorders[params.mSpanIndex]; bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view); @@ -537,16 +686,24 @@ void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, Arrays.fill(mSet, null); } - private int getMainDirSpec(int dim) { - if (dim < 0) { - return MAIN_DIR_SPEC; - } else { - return View.MeasureSpec.makeMeasureSpec(dim, View.MeasureSpec.EXACTLY); - } + /** + * This is called after laying out a row (if vertical) or a column (if horizontal) when the + * RecyclerView does not have exact measurement specs. + *

+ * Here we try to assign a best guess width or height and re-do the layout to update other + * views that wanted to FILL_PARENT in the non-scroll orientation. + * + * @param maxSizeInOther The maximum size per span ratio from the measurement of the children. + * @param currentOtherDirSize The size before this layout chunk. There is no reason to go below. + */ + private void guessMeasurement(float maxSizeInOther, int currentOtherDirSize) { + final int contentSize = Math.round(maxSizeInOther * mSpanCount); + // always re-calculate because borders were stretched during the fill + calculateItemBorders(Math.max(contentSize, currentOtherDirSize)); } private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec, - boolean capBothSpecs) { + boolean capBothSpecs, boolean alreadyMeasured) { calculateItemDecorationsForChild(child, mDecorInsets); RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams(); if (capBothSpecs || mOrientation == VERTICAL) { @@ -557,7 +714,16 @@ private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mDecorInsets.top, lp.bottomMargin + mDecorInsets.bottom); } - child.measure(widthSpec, heightSpec); + final boolean measure; + if (alreadyMeasured) { + measure = shouldReMeasureChild(child, widthSpec, heightSpec, lp); + } else { + measure = shouldMeasureChild(child, widthSpec, heightSpec, lp); + } + if (measure) { + child.measure(widthSpec, heightSpec); + } + } private int updateSpecWithExtra(int spec, int startInset, int endInset) { @@ -567,7 +733,7 @@ private int updateSpecWithExtra(int spec, int startInset, int endInset) { final int mode = View.MeasureSpec.getMode(spec); if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) { return View.MeasureSpec.makeMeasureSpec( - View.MeasureSpec.getSize(spec) - startInset - endInset, mode); + Math.max(0, View.MeasureSpec.getSize(spec) - startInset - endInset), mode); } return spec; } @@ -806,6 +972,78 @@ public int getSpanGroupIndex(int adapterPosition, int spanCount) { } } + @Override + public View onFocusSearchFailed(View focused, int focusDirection, + RecyclerView.Recycler recycler, RecyclerView.State state) { + View prevFocusedChild = findContainingItemView(focused); + if (prevFocusedChild == null) { + return null; + } + LayoutParams lp = (LayoutParams) prevFocusedChild.getLayoutParams(); + final int prevSpanStart = lp.mSpanIndex; + final int prevSpanEnd = lp.mSpanIndex + lp.mSpanSize; + View view = super.onFocusSearchFailed(focused, focusDirection, recycler, state); + if (view == null) { + return null; + } + // LinearLayoutManager finds the last child. What we want is the child which has the same + // spanIndex. + final int layoutDir = convertFocusDirectionToLayoutDirection(focusDirection); + final boolean ascend = (layoutDir == LayoutState.LAYOUT_END) != mShouldReverseLayout; + final int start, inc, limit; + if (ascend) { + start = getChildCount() - 1; + inc = -1; + limit = -1; + } else { + start = 0; + inc = 1; + limit = getChildCount(); + } + final boolean preferLastSpan = mOrientation == VERTICAL && isLayoutRTL(); + View weakCandidate = null; // somewhat matches but not strong + int weakCandidateSpanIndex = -1; + int weakCandidateOverlap = 0; // how many spans overlap + + for (int i = start; i != limit; i += inc) { + View candidate = getChildAt(i); + if (candidate == prevFocusedChild) { + break; + } + if (!candidate.isFocusable()) { + continue; + } + final LayoutParams candidateLp = (LayoutParams) candidate.getLayoutParams(); + final int candidateStart = candidateLp.mSpanIndex; + final int candidateEnd = candidateLp.mSpanIndex + candidateLp.mSpanSize; + if (candidateStart == prevSpanStart && candidateEnd == prevSpanEnd) { + return candidate; // perfect match + } + boolean assignAsWeek = false; + if (weakCandidate == null) { + assignAsWeek = true; + } else { + int maxStart = Math.max(candidateStart, prevSpanStart); + int minEnd = Math.min(candidateEnd, prevSpanEnd); + int overlap = minEnd - maxStart; + if (overlap > weakCandidateOverlap) { + assignAsWeek = true; + } else if (overlap == weakCandidateOverlap && + preferLastSpan == (candidateStart > weakCandidateSpanIndex)) { + assignAsWeek = true; + } + } + + if (assignAsWeek) { + weakCandidate = candidate; + weakCandidateSpanIndex = candidateLp.mSpanIndex; + weakCandidateOverlap = Math.min(candidateEnd, prevSpanEnd) - + Math.max(candidateStart, prevSpanStart); + } + } + return weakCandidate; + } + @Override public boolean supportsPredictiveItemAnimations() { return mPendingSavedState == null && !mPendingSpanCountChange; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LayoutState.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LayoutState.java index e3a9c8b18a..9ee6745ead 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LayoutState.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LayoutState.java @@ -15,6 +15,7 @@ */ package org.telegram.messenger.support.widget; + import android.view.View; /** @@ -35,8 +36,11 @@ class LayoutState { final static int ITEM_DIRECTION_TAIL = 1; - final static int SCOLLING_OFFSET_NaN = Integer.MIN_VALUE; - + /** + * We may not want to recycle children in some cases (e.g. layout) + */ + boolean mRecycle = true; + /** * Number of pixels that we should fill, in the layout direction. */ @@ -69,6 +73,16 @@ class LayoutState { */ int mEndLine = 0; + /** + * If true, layout should stop if a focusable view is added + */ + boolean mStopInFocusable; + + /** + * If the content is not wrapped with any value + */ + boolean mInfinite; + /** * @return true if there are more items in the data adapter */ diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearLayoutManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearLayoutManager.java index 44864b0ff9..9d83de9c5a 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearLayoutManager.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearLayoutManager.java @@ -16,6 +16,8 @@ package org.telegram.messenger.support.widget; +import static org.telegram.messenger.support.widget.RecyclerView.NO_POSITION; + import android.content.Context; import android.graphics.PointF; import android.os.Parcel; @@ -23,20 +25,18 @@ import android.support.v4.view.ViewCompat; import android.support.v4.view.accessibility.AccessibilityEventCompat; import android.support.v4.view.accessibility.AccessibilityRecordCompat; - +import org.telegram.messenger.support.widget.RecyclerView.LayoutParams; import org.telegram.messenger.support.widget.helper.ItemTouchHelper; +import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; -import org.telegram.messenger.support.widget.RecyclerView.LayoutParams; import java.util.List; -import static org.telegram.messenger.support.widget.RecyclerView.NO_POSITION; - /** - * A {@link RecyclerView.LayoutManager} implementation which provides + * A {@link android.support.v7.widget.RecyclerView.LayoutManager} implementation which provides * similar functionality to {@link android.widget.ListView}. */ public class LinearLayoutManager extends RecyclerView.LayoutManager implements @@ -58,7 +58,7 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements * than this factor times the total space of the list. If layout is vertical, total space is the * height minus padding, if layout is horizontal, total space is the width minus padding. */ - private static final float MAX_SCROLL_FACTOR = 0.33f; + private static final float MAX_SCROLL_FACTOR = 1 / 3f; /** @@ -154,6 +154,24 @@ public LinearLayoutManager(Context context) { public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) { setOrientation(orientation); setReverseLayout(reverseLayout); + setAutoMeasureEnabled(true); + } + + /** + * Constructor used when layout manager is set in XML by RecyclerView attribute + * "layoutManager". Defaults to vertical orientation. + * + * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation + * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout + * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd + */ + public LinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes); + setOrientation(properties.orientation); + setReverseLayout(properties.reverseLayout); + setStackFromEnd(properties.stackFromEnd); + setAutoMeasureEnabled(true); } /** @@ -288,8 +306,7 @@ public boolean getStackFromEnd() { /** * Returns the current orientaion of the layout. * - * @return Current orientation. - * @see #mOrientation + * @return Current orientation, either {@link #HORIZONTAL} or {@link #VERTICAL} * @see #setOrientation(int) */ public int getOrientation() { @@ -297,7 +314,7 @@ public int getOrientation() { } /** - * Sets the orientation of the layout. {@link org.telegram.messenger.support.widget.LinearLayoutManager} + * Sets the orientation of the layout. {@link android.support.v7.widget.LinearLayoutManager} * will do its best to keep scroll position. * * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL} @@ -333,7 +350,7 @@ private void resolveShouldLayoutReverse() { * Returns if views are laid out from the opposite direction of the layout. * * @return If layout is reversed or not. - * @see {@link #setReverseLayout(boolean)} + * @see #setReverseLayout(boolean) */ public boolean getReverseLayout() { return mReverseLayout; @@ -345,8 +362,8 @@ public boolean getReverseLayout() { * laid out at the end of the UI, second item is laid out before it etc. * * For horizontal layouts, it depends on the layout direction. - * When set to true, If {@link RecyclerView} is LTR, than it will - * layout from RTL, if {@link RecyclerView}} is RTL, it will layout + * When set to true, If {@link android.support.v7.widget.RecyclerView} is LTR, than it will + * layout from RTL, if {@link android.support.v7.widget.RecyclerView}} is RTL, it will layout * from LTR. * * If you are looking for the exact same behavior of @@ -385,7 +402,7 @@ public View findViewByPosition(int position) { /** *

Returns the amount of extra space that should be laid out by LayoutManager. - * By default, {@link org.telegram.messenger.support.widget.LinearLayoutManager} lays out 1 extra page of + * By default, {@link android.support.v7.widget.LinearLayoutManager} lays out 1 extra page of * items while smooth scrolling and 0 otherwise. You can override this method to implement your * custom layout pre-cache logic.

*

Laying out invisible elements will eventually come with performance cost. On the other @@ -512,8 +529,18 @@ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State } int startOffset; int endOffset; - onAnchorReady(recycler, state, mAnchorInfo); + final int firstLayoutDirection; + if (mAnchorInfo.mLayoutFromEnd) { + firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL : + LayoutState.ITEM_DIRECTION_HEAD; + } else { + firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD : + LayoutState.ITEM_DIRECTION_TAIL; + } + + onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection); detachAndScrapAttachedViews(recycler); + mLayoutState.mInfinite = mOrientationHelper.getMode() == View.MeasureSpec.UNSPECIFIED; mLayoutState.mIsPreLayout = state.isPreLayout(); if (mAnchorInfo.mLayoutFromEnd) { // fill towards start @@ -606,13 +633,14 @@ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State /** * Method called when Anchor position is decided. Extending class can setup accordingly or * even update anchor info if necessary. - * - * @param recycler - * @param state - * @param anchorInfo Simple data structure to keep anchor point information for the next layout + * @param recycler The recycler for the layout + * @param state The layout state + * @param anchorInfo The mutable POJO that keeps the position and offset. + * @param firstLayoutItemDirection The direction of the first layout filling in terms of adapter + * indices. */ void onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state, - AnchorInfo anchorInfo) { + AnchorInfo anchorInfo, int firstLayoutItemDirection) { } /** @@ -1100,9 +1128,10 @@ public boolean isSmoothScrollbarEnabled() { private void updateLayoutState(int layoutDirection, int requiredSpace, boolean canUseExistingSpace, RecyclerView.State state) { + mLayoutState.mInfinite = mOrientationHelper.getMode() == View.MeasureSpec.UNSPECIFIED; mLayoutState.mExtra = getExtraLayoutSpace(state); mLayoutState.mLayoutDirection = layoutDirection; - int fastScrollSpace; + int scrollingOffset; if (layoutDirection == LayoutState.LAYOUT_END) { mLayoutState.mExtra += mOrientationHelper.getEndPadding(); // get the first child in the direction we are going @@ -1113,7 +1142,7 @@ private void updateLayoutState(int layoutDirection, int requiredSpace, mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection; mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child); // calculate how much we can scroll without adding new children (independent of layout) - fastScrollSpace = mOrientationHelper.getDecoratedEnd(child) + scrollingOffset = mOrientationHelper.getDecoratedEnd(child) - mOrientationHelper.getEndAfterPadding(); } else { @@ -1123,14 +1152,14 @@ private void updateLayoutState(int layoutDirection, int requiredSpace, : LayoutState.ITEM_DIRECTION_HEAD; mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection; mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child); - fastScrollSpace = -mOrientationHelper.getDecoratedStart(child) + scrollingOffset = -mOrientationHelper.getDecoratedStart(child) + mOrientationHelper.getStartAfterPadding(); } mLayoutState.mAvailable = requiredSpace; if (canUseExistingSpace) { - mLayoutState.mAvailable -= fastScrollSpace; + mLayoutState.mAvailable -= scrollingOffset; } - mLayoutState.mScrollingOffset = fastScrollSpace; + mLayoutState.mScrollingOffset = scrollingOffset; } int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { @@ -1142,8 +1171,8 @@ int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START; final int absDy = Math.abs(dy); updateLayoutState(layoutDirection, absDy, true, state); - final int freeScroll = mLayoutState.mScrollingOffset; - final int consumed = freeScroll + fill(recycler, mLayoutState, state, false); + final int consumed = mLayoutState.mScrollingOffset + + fill(recycler, mLayoutState, state, false); if (consumed < 0) { if (DEBUG) { Log.d(TAG, "Don't have any more elements to scroll"); @@ -1193,7 +1222,7 @@ private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int /** * Recycles views that went out of bounds after scrolling towards the end of the layout. * - * @param recycler Recycler instance of {@link RecyclerView} + * @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView} * @param dt This can be used to add additional padding to the visible area. This is used * to detect children that will go out of bounds after scrolling, without * actually moving them. @@ -1232,7 +1261,7 @@ private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) { /** * Recycles views that went out of bounds after scrolling towards the start of the layout. * - * @param recycler Recycler instance of {@link RecyclerView} + * @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView} * @param dt This can be used to add additional padding to the visible area. This is used * to detect children that will go out of bounds after scrolling, without * actually moving them. @@ -1274,12 +1303,12 @@ private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) { * @param layoutState Current layout state. Right now, this object does not change but * we may consider moving it out of this view so passing around as a * parameter for now, rather than accessing {@link #mLayoutState} - * @see #recycleViewsFromStart(RecyclerView.Recycler, int) - * @see #recycleViewsFromEnd(RecyclerView.Recycler, int) - * @see org.telegram.messenger.support.widget.LinearLayoutManager.LayoutState#mLayoutDirection + * @see #recycleViewsFromStart(android.support.v7.widget.RecyclerView.Recycler, int) + * @see #recycleViewsFromEnd(android.support.v7.widget.RecyclerView.Recycler, int) + * @see android.support.v7.widget.LinearLayoutManager.LayoutState#mLayoutDirection */ private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) { - if (!layoutState.mRecycle) { + if (!layoutState.mRecycle || layoutState.mInfinite) { return; } if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { @@ -1291,7 +1320,7 @@ private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState la /** * The magic functions :). Fills the given layout, defined by the layoutState. This is fairly - * independent from the rest of the {@link org.telegram.messenger.support.widget.LinearLayoutManager} + * independent from the rest of the {@link android.support.v7.widget.LinearLayoutManager} * and with little change, can be made publicly available as a helper class. * * @param recycler Current recycler that is attached to RecyclerView @@ -1313,7 +1342,7 @@ int fill(RecyclerView.Recycler recycler, LayoutState layoutState, } int remainingSpace = layoutState.mAvailable + layoutState.mExtra; LayoutChunkResult layoutChunkResult = new LayoutChunkResult(); - while (remainingSpace > 0 && layoutState.hasMore(state)) { + while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { layoutChunkResult.resetInternal(); layoutChunk(recycler, state, layoutState, layoutChunkResult); if (layoutChunkResult.mFinished) { @@ -1424,6 +1453,13 @@ void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, result.mFocusable = view.isFocusable(); } + @Override + boolean shouldMeasureTwice() { + return getHeightMode() != View.MeasureSpec.EXACTLY + && getWidthMode() != View.MeasureSpec.EXACTLY + && hasFlexibleChildInBothOrientations(); + } + /** * Converts a focusDirection to orientation. * @@ -1434,7 +1470,7 @@ void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise. */ - private int convertFocusDirectionToLayoutDirection(int focusDirection) { + int convertFocusDirectionToLayoutDirection(int focusDirection) { switch (focusDirection) { case View.FOCUS_BACKWARD: return LayoutState.LAYOUT_START; @@ -1916,7 +1952,8 @@ static class LayoutState { boolean mIsPreLayout = false; /** - * The most recent {@link #scrollBy(int, RecyclerView.Recycler, RecyclerView.State)} amount. + * The most recent {@link #scrollBy(int, RecyclerView.Recycler, RecyclerView.State)} + * amount. */ int mLastScrollDelta; @@ -1926,6 +1963,11 @@ static class LayoutState { */ List mScrapList = null; + /** + * Used when there is no limit in how many views can be laid out. + */ + boolean mInfinite; + /** * @return true if there are more items in the data adapter */ @@ -2020,7 +2062,10 @@ void log() { } } - static class SavedState implements Parcelable { + /** + * @hide + */ + public static class SavedState implements Parcelable { int mAnchorPosition; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearSmoothScroller.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearSmoothScroller.java index dbb50209c8..e5a611180a 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearSmoothScroller.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearSmoothScroller.java @@ -24,8 +24,6 @@ import android.view.animation.DecelerateInterpolator; import android.view.animation.LinearInterpolator; -import org.telegram.messenger.support.widget.RecyclerView; - /** * {@link RecyclerView.SmoothScroller} implementation which uses * {@link android.view.animation.LinearInterpolator} until the target position becames a child of @@ -124,6 +122,7 @@ protected void onSeekTargetStep(int dx, int dy, RecyclerView.State state, Action stop(); return; } + //noinspection PointlessBooleanExpression if (DEBUG && mTargetVector != null && ((mTargetVector.x * dx < 0 || mTargetVector.y * dy < 0))) { throw new IllegalStateException("Scroll happened in the opposite direction" @@ -293,13 +292,13 @@ public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd * @param view The view which we want to make fully visible * @param snapPreference The edge which the view should snap to when entering the visible * area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or - * {@link #SNAP_TO_END}. + * {@link #SNAP_TO_ANY}. * @return The vertical scroll amount necessary to make the view visible with the given * snap preference. */ public int calculateDyToMakeVisible(View view, int snapPreference) { final RecyclerView.LayoutManager layoutManager = getLayoutManager(); - if (!layoutManager.canScrollVertically()) { + if (layoutManager == null || !layoutManager.canScrollVertically()) { return 0; } final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) @@ -324,7 +323,7 @@ public int calculateDyToMakeVisible(View view, int snapPreference) { */ public int calculateDxToMakeVisible(View view, int snapPreference) { final RecyclerView.LayoutManager layoutManager = getLayoutManager(); - if (!layoutManager.canScrollHorizontally()) { + if (layoutManager == null || !layoutManager.canScrollHorizontally()) { return 0; } final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/OrientationHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/OrientationHelper.java index 42a04f8b13..1c8aa4a9dd 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/OrientationHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/OrientationHelper.java @@ -19,8 +19,6 @@ import android.view.View; import android.widget.LinearLayout; -import org.telegram.messenger.support.widget.RecyclerView; - /** * Helper class for LayoutManagers to abstract measurements depending on the View's orientation. *

@@ -167,6 +165,28 @@ public int getTotalSpaceChange() { */ public abstract int getEndPadding(); + /** + * Returns the MeasureSpec mode for the current orientation from the LayoutManager. + * + * @return The current measure spec mode. + * + * @see View.MeasureSpec + * @see RecyclerView.LayoutManager#getWidthMode() + * @see RecyclerView.LayoutManager#getHeightMode() + */ + public abstract int getMode(); + + /** + * Returns the MeasureSpec mode for the perpendicular orientation from the LayoutManager. + * + * @return The current measure spec mode. + * + * @see View.MeasureSpec + * @see RecyclerView.LayoutManager#getWidthMode() + * @see RecyclerView.LayoutManager#getHeightMode() + */ + public abstract int getModeInOther(); + /** * Creates an OrientationHelper for the given LayoutManager and orientation. * @@ -259,6 +279,16 @@ public void offsetChild(View view, int offset) { public int getEndPadding() { return mLayoutManager.getPaddingRight(); } + + @Override + public int getMode() { + return mLayoutManager.getWidthMode(); + } + + @Override + public int getModeInOther() { + return mLayoutManager.getHeightMode(); + } }; } @@ -335,6 +365,16 @@ public void offsetChild(View view, int offset) { public int getEndPadding() { return mLayoutManager.getPaddingBottom(); } + + @Override + public int getMode() { + return mLayoutManager.getHeightMode(); + } + + @Override + public int getModeInOther() { + return mLayoutManager.getWidthMode(); + } }; } } \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerView.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerView.java index 6f160865a1..8f224ea241 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerView.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerView.java @@ -18,6 +18,7 @@ package org.telegram.messenger.support.widget; import android.content.Context; +import android.content.res.TypedArray; import android.database.Observable; import android.graphics.Canvas; import android.graphics.PointF; @@ -26,10 +27,13 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import android.support.annotation.CallSuper; import android.os.SystemClock; +import android.support.annotation.CallSuper; +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v4.util.ArrayMap; +import android.support.annotation.VisibleForTesting; +import android.support.v4.os.TraceCompat; import android.support.v4.view.InputDeviceCompat; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.NestedScrollingChild; @@ -43,6 +47,9 @@ import android.support.v4.view.accessibility.AccessibilityRecordCompat; import android.support.v4.widget.EdgeEffectCompat; import android.support.v4.widget.ScrollerCompat; + +import org.telegram.messenger.FileLog; +import org.telegram.messenger.support.widget.RecyclerView.ItemAnimator.ItemHolderInfo; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; @@ -58,11 +65,10 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.animation.Interpolator; -import android.widget.AbsListView; import android.widget.EdgeEffect; -import org.telegram.messenger.FileLog; - +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -241,10 +247,21 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro private SavedState mPendingSavedState; + /** + * Handles adapter updates + */ AdapterHelper mAdapterHelper; + /** + * Handles abstraction between LayoutManager children and RecyclerView children + */ ChildHelper mChildHelper; + /** + * Keeps data about views to be used for animations + */ + final ViewInfoStore mViewInfoStore = new ViewInfoStore(); + /** * Prior to L, there is no way to query this variable which is why we override the setter and * track it here. @@ -259,36 +276,33 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro */ private final Runnable mUpdateChildViewsRunnable = new Runnable() { public void run() { - if (!mFirstLayoutComplete) { + if (!mFirstLayoutComplete || isLayoutRequested()) { // a layout request will happen, we should not do layout here. return; } - if (mDataSetHasChangedAfterLayout) { - dispatchLayout(); - } else if (mAdapterHelper.hasPendingUpdates()) { - eatRequestLayout(); - mAdapterHelper.preProcess(); - if (!mLayoutRequestEaten) { - // We run this after pre-processing is complete so that ViewHolders have their - // final adapter positions. No need to run it if a layout is already requested. - rebindUpdatedViewHolders(); - } - resumeRequestLayout(true); + if (mLayoutFrozen) { + mLayoutRequestEaten = true; + return; //we'll process updates when ice age ends. } + consumePendingUpdateOperations(); } }; private final Rect mTempRect = new Rect(); private Adapter mAdapter; - private LayoutManager mLayout; + @VisibleForTesting LayoutManager mLayout; private RecyclerListener mRecyclerListener; private final ArrayList mItemDecorations = new ArrayList<>(); - private final ArrayList mOnItemTouchListeners = new ArrayList<>(); + private final ArrayList mOnItemTouchListeners = + new ArrayList<>(); private OnItemTouchListener mActiveOnItemTouchListener; private boolean mIsAttached; private boolean mHasFixedSize; private boolean mFirstLayoutComplete; - private boolean mEatRequestLayout; + + // Counting lock to control whether we should ignore requestLayout calls from children or not. + private int mEatRequestLayout = 0; + private boolean mLayoutRequestEaten; private boolean mLayoutFrozen; private boolean mIgnoreMotionEventTillDown; @@ -400,6 +414,44 @@ public float getInterpolation(float t) { } }; + /** + * The callback to convert view info diffs into animations. + */ + private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback = + new ViewInfoStore.ProcessCallback() { + @Override + public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info, + @Nullable ItemHolderInfo postInfo) { + mRecycler.unscrapView(viewHolder); + animateDisappearance(viewHolder, info, postInfo); + } + @Override + public void processAppeared(ViewHolder viewHolder, + ItemHolderInfo preInfo, ItemHolderInfo info) { + animateAppearance(viewHolder, preInfo, info); + } + + @Override + public void processPersistent(ViewHolder viewHolder, + @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) { + viewHolder.setIsRecyclable(false); + if (mDataSetHasChangedAfterLayout) { + // since it was rebound, use change instead as we'll be mapping them from + // stable ids. If stable ids were false, we would not be running any + // animations + if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo, postInfo)) { + postAnimationRunner(); + } + } else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) { + postAnimationRunner(); + } + } + @Override + public void unused(ViewHolder viewHolder) { + mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler); + } + }; + public RecyclerView(Context context) { this(context, null); } @@ -516,7 +568,7 @@ private String getFullClassName(Context context, String className) { if (className.charAt(0) == '.') { return context.getPackageName() + className; } - if (className.contains("")) { + if (className.contains(".")) { return className; } return RecyclerView.class.getPackage().getName() + '.' + className; @@ -704,9 +756,13 @@ public void offsetPositionsForMove(int from, int to) { } /** - * RecyclerView can perform several optimizations if it can know in advance that changes in - * adapter content cannot change the size of the RecyclerView itself. - * If your use of RecyclerView falls into this category, set this to true. + * RecyclerView can perform several optimizations if it can know in advance that RecyclerView's + * size is not affected by the adapter contents. RecyclerView can still change its size based + * on other factors (e.g. its parent's size) but this size calculation cannot depend on the + * size of its children or contents of its adapter (except the number of items in the adapter). + *

+ * If your use of RecyclerView falls into this category, set this to {@code true}. It will allow + * RecyclerView to avoid invalidating the whole layout when its adapter contents change. * * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView. */ @@ -897,7 +953,7 @@ public int getBaseline() { */ public void addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) { if (mOnChildAttachStateListeners == null) { - mOnChildAttachStateListeners = new ArrayList(); + mOnChildAttachStateListeners = new ArrayList<>(); } mOnChildAttachStateListeners.add(listener); } @@ -940,6 +996,7 @@ public void setLayoutManager(LayoutManager layout) { if (layout == mLayout) { return; } + stopScroll(); // TODO We should do this switch a dispachLayout pass and animate children. There is a good // chance that LayoutManagers will re-use views. if (mLayout != null) { @@ -1042,7 +1099,8 @@ private boolean removeAnimatingView(View view) { Log.d(TAG, "after removing animated view: " + view + ", " + this); } } - resumeRequestLayout(false); + // only clear request eaten flag if we removed the view. + resumeRequestLayout(!removed); return removed; } @@ -1148,7 +1206,7 @@ private void setScrollState(int state) { public void addItemDecoration(ItemDecoration decor, int index) { if (mLayout != null) { mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or" - + "layout"); + + " layout"); } if (mItemDecorations.isEmpty()) { setWillNotDraw(false); @@ -1190,7 +1248,7 @@ public void addItemDecoration(ItemDecoration decor) { public void removeItemDecoration(ItemDecoration decor) { if (mLayout != null) { mLayout.assertNotInLayoutOrScroll("Cannot remove item decoration during a scroll or" - + "layout"); + + " layout"); } mItemDecorations.remove(decor); if (mItemDecorations.isEmpty()) { @@ -1244,7 +1302,7 @@ public void setOnScrollListener(OnScrollListener listener) { */ public void addOnScrollListener(OnScrollListener listener) { if (mScrollListeners == null) { - mScrollListeners = new ArrayList(); + mScrollListeners = new ArrayList<>(); } mScrollListeners.add(listener); } @@ -1269,14 +1327,6 @@ public void clearOnScrollListeners() { } } - /** - * Convenience method to scroll to a certain position. - * - * RecyclerView does not implement scrolling logic, rather forwards the call to - * {@link org.telegram.messenger.support.widget.RecyclerView.LayoutManager#scrollToPosition(int)} - * @param position Scroll to this adapter position - * @see org.telegram.messenger.support.widget.RecyclerView.LayoutManager#scrollToPosition(int) - */ public void scrollToPosition(int position) { if (mLayoutFrozen) { return; @@ -1328,8 +1378,8 @@ public void smoothScrollToPosition(int position) { @Override public void scrollTo(int x, int y) { - throw new UnsupportedOperationException( - "RecyclerView does not support scrolling to an absolute position."); + Log.w(TAG, "RecyclerView does not support scrolling to an absolute position. " + + "Use scrollToPosition instead"); } @Override @@ -1358,7 +1408,59 @@ public void scrollBy(int x, int y) { * This method consumes all deferred changes to avoid that case. */ private void consumePendingUpdateOperations() { - mUpdateChildViewsRunnable.run(); + if (!mFirstLayoutComplete) { + // a layout request will happen, we should not do layout here. + return; + } + if (mDataSetHasChangedAfterLayout) { + TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG); + dispatchLayout(); + TraceCompat.endSection(); + return; + } + if (!mAdapterHelper.hasPendingUpdates()) { + return; + } + + // if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any + // of the visible items is affected and if not, just ignore the change. + if (mAdapterHelper.hasAnyUpdateTypes(UpdateOp.UPDATE) && !mAdapterHelper + .hasAnyUpdateTypes(UpdateOp.ADD | UpdateOp.REMOVE | UpdateOp.MOVE)) { + TraceCompat.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG); + eatRequestLayout(); + mAdapterHelper.preProcess(); + if (!mLayoutRequestEaten) { + if (hasUpdatedView()) { + dispatchLayout(); + } else { + // no need to layout, clean state + mAdapterHelper.consumePostponedUpdates(); + } + } + resumeRequestLayout(true); + TraceCompat.endSection(); + } else if (mAdapterHelper.hasPendingUpdates()) { + TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG); + dispatchLayout(); + TraceCompat.endSection(); + } + } + + /** + * @return True if an existing view holder needs to be updated + */ + private boolean hasUpdatedView() { + final int childCount = mChildHelper.getChildCount(); + for (int i = 0; i < childCount; i++) { + final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); + if (holder == null || holder.shouldIgnore()) { + continue; + } + if (holder.isUpdated()) { + return true; + } + } + return false; } /** @@ -1380,6 +1482,7 @@ boolean scrollByInternal(int x, int y, MotionEvent ev) { if (mAdapter != null) { eatRequestLayout(); onEnterLayoutOrScroll(); + TraceCompat.beginSection(TRACE_SCROLL_TAG); if (x != 0) { consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState); unconsumedX = x - consumedX; @@ -1388,27 +1491,8 @@ boolean scrollByInternal(int x, int y, MotionEvent ev) { consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState); unconsumedY = y - consumedY; } - if (supportsChangeAnimations()) { - // Fix up shadow views used by changing animations - int count = mChildHelper.getChildCount(); - for (int i = 0; i < count; i++) { - View view = mChildHelper.getChildAt(i); - ViewHolder holder = getChildViewHolder(view); - if (holder != null && holder.mShadowingHolder != null) { - ViewHolder shadowingHolder = holder.mShadowingHolder; - View shadowingView = shadowingHolder != null ? shadowingHolder.itemView : null; - if (shadowingView != null) { - int left = view.getLeft(); - int top = view.getTop(); - if (left != shadowingView.getLeft() || top != shadowingView.getTop()) { - shadowingView.layout(left, top, - left + shadowingView.getWidth(), - top + shadowingView.getHeight()); - } - } - } - } - } + TraceCompat.endSection(); + repositionShadowingViews(); onExitLayoutOrScroll(); resumeRequestLayout(false); } @@ -1455,13 +1539,15 @@ boolean scrollByInternal(int x, int y, MotionEvent ev) { * LayoutManager.

* * @return The horizontal offset of the scrollbar's thumb - * @see org.telegram.messenger.support.widget.RecyclerView.LayoutManager#computeHorizontalScrollOffset + * @see android.support.v7.widget.RecyclerView.LayoutManager#computeHorizontalScrollOffset * (RecyclerView.Adapter) */ @Override public int computeHorizontalScrollOffset() { - return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState) - : 0; + if (mLayout == null) { + return 0; + } + return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState) : 0; } /** @@ -1483,6 +1569,9 @@ public int computeHorizontalScrollOffset() { */ @Override public int computeHorizontalScrollExtent() { + if (mLayout == null) { + return 0; + } return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mState) : 0; } @@ -1503,6 +1592,9 @@ public int computeHorizontalScrollExtent() { */ @Override public int computeHorizontalScrollRange() { + if (mLayout == null) { + return 0; + } return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0; } @@ -1520,11 +1612,14 @@ public int computeHorizontalScrollRange() { * LayoutManager.

* * @return The vertical offset of the scrollbar's thumb - * @see org.telegram.messenger.support.widget.RecyclerView.LayoutManager#computeVerticalScrollOffset + * @see android.support.v7.widget.RecyclerView.LayoutManager#computeVerticalScrollOffset * (RecyclerView.Adapter) */ @Override public int computeVerticalScrollOffset() { + if (mLayout == null) { + return 0; + } return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0; } @@ -1546,6 +1641,9 @@ public int computeVerticalScrollOffset() { */ @Override public int computeVerticalScrollExtent() { + if (mLayout == null) { + return 0; + } return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mState) : 0; } @@ -1566,31 +1664,50 @@ public int computeVerticalScrollExtent() { */ @Override public int computeVerticalScrollRange() { + if (mLayout == null) { + return 0; + } return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0; } void eatRequestLayout() { - if (!mEatRequestLayout) { - mEatRequestLayout = true; - if (!mLayoutFrozen) { - mLayoutRequestEaten = false; - } + mEatRequestLayout++; + if (mEatRequestLayout == 1 && !mLayoutFrozen) { + mLayoutRequestEaten = false; } } void resumeRequestLayout(boolean performLayoutChildren) { - if (mEatRequestLayout) { + if (mEatRequestLayout < 1) { + //noinspection PointlessBooleanExpression + if (DEBUG) { + throw new IllegalStateException("invalid eat request layout count"); + } + mEatRequestLayout = 1; + } + if (!performLayoutChildren) { + // Reset the layout request eaten counter. + // This is necessary since eatRequest calls can be nested in which case the outher + // call will override the inner one. + // for instance: + // eat layout for process adapter updates + // eat layout for dispatchLayout + // a bunch of req layout calls arrive + + mLayoutRequestEaten = false; + } + if (mEatRequestLayout == 1) { // when layout is frozen we should delay dispatchLayout() if (performLayoutChildren && mLayoutRequestEaten && !mLayoutFrozen && mLayout != null && mAdapter != null) { dispatchLayout(); } - mEatRequestLayout = false; if (!mLayoutFrozen) { mLayoutRequestEaten = false; } } + mEatRequestLayout--; } /** @@ -1619,7 +1736,7 @@ public void setLayoutFrozen(boolean frozen) { if (frozen != mLayoutFrozen) { assertNotInLayoutOrScroll("Do not setLayoutFrozen in layout or scroll"); if (!frozen) { - mLayoutFrozen = frozen; + mLayoutFrozen = false; if (mLayoutRequestEaten && mLayout != null && mAdapter != null) { requestLayout(); } @@ -1629,7 +1746,7 @@ public void setLayoutFrozen(boolean frozen) { MotionEvent cancelEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); onTouchEvent(cancelEvent); - mLayoutFrozen = frozen; + mLayoutFrozen = true; mIgnoreMotionEventTillDown = true; stopScroll(); } @@ -2007,6 +2124,7 @@ protected void onDetachedFromWindow() { mLayout.dispatchDetachedFromWindow(this, mRecycler); } removeCallbacks(mItemAnimatorRunner); + mViewInfoStore.onDetach(); } /** @@ -2170,6 +2288,9 @@ public boolean onInterceptTouchEvent(MotionEvent e) { setScrollState(SCROLL_STATE_DRAGGING); } + // Clear the nested offsets + mNestedOffsets[0] = mNestedOffsets[1] = 0; + int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; if (canScrollHorizontally) { nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; @@ -2209,10 +2330,6 @@ public boolean onInterceptTouchEvent(MotionEvent e) { startScroll = true; } if (startScroll) { - final ViewParent parent = getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } setScrollState(SCROLL_STATE_DRAGGING); } } @@ -2338,10 +2455,6 @@ public boolean onTouchEvent(MotionEvent e) { startScroll = true; } if (startScroll) { - final ViewParent parent = getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } setScrollState(SCROLL_STATE_DRAGGING); } } @@ -2463,79 +2576,93 @@ private float getScrollFactor() { } else { return 0; //listPreferredItemHeight is not defined, no generic scrolling } - } return mScrollFactor; } @Override protected void onMeasure(int widthSpec, int heightSpec) { - if (mAdapterUpdateDuringMeasure) { - eatRequestLayout(); - processAdapterUpdatesAndSetAnimationFlags(); - - if (mState.mRunPredictiveAnimations) { - // TODO: try to provide a better approach. - // When RV decides to run predictive animations, we need to measure in pre-layout - // state so that pre-layout pass results in correct layout. - // On the other hand, this will prevent the layout manager from resizing properly. - mState.mInPreLayout = true; - } else { - // consume remaining updates to provide a consistent state with the layout pass. - mAdapterHelper.consumeUpdatesInOnePass(); - mState.mInPreLayout = false; - } - mAdapterUpdateDuringMeasure = false; - resumeRequestLayout(false); - } - - if (mAdapter != null) { - mState.mItemCount = mAdapter.getItemCount(); - } else { - mState.mItemCount = 0; - } if (mLayout == null) { defaultOnMeasure(widthSpec, heightSpec); + return; + } + if (mLayout.mAutoMeasure) { + final int widthMode = MeasureSpec.getMode(widthSpec); + final int heightMode = MeasureSpec.getMode(heightSpec); + final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY + && heightMode == MeasureSpec.EXACTLY; + mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); + if (skipMeasure || mAdapter == null) { + return; + } + if (mState.mLayoutStep == State.STEP_START) { + dispatchLayoutStep1(); + } + // set dimensions in 2nd step. Pre-layout should happen with old dimensions for + // consistency + mLayout.setMeasureSpecs(widthSpec, heightSpec); + mState.mIsMeasuring = true; + dispatchLayoutStep2(); + + // now we can get the width and height from the children. + mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); + + // if RecyclerView has non-exact width and height and if there is at least one child + // which also has non-exact width & height, we have to re-measure. + if (mLayout.shouldMeasureTwice()) { + mLayout.setMeasureSpecs( + MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); + mState.mIsMeasuring = true; + dispatchLayoutStep2(); + // now we can get the width and height from the children. + mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); + } } else { + if (mHasFixedSize) { + mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); + return; + } + // custom onMeasure + if (mAdapterUpdateDuringMeasure) { + eatRequestLayout(); + processAdapterUpdatesAndSetAnimationFlags(); + + if (mState.mRunPredictiveAnimations) { + mState.mInPreLayout = true; + } else { + // consume remaining updates to provide a consistent state with the layout pass. + mAdapterHelper.consumeUpdatesInOnePass(); + mState.mInPreLayout = false; + } + mAdapterUpdateDuringMeasure = false; + resumeRequestLayout(false); + } + + if (mAdapter != null) { + mState.mItemCount = mAdapter.getItemCount(); + } else { + mState.mItemCount = 0; + } + eatRequestLayout(); mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); + resumeRequestLayout(false); + mState.mInPreLayout = false; // clear } - - mState.mInPreLayout = false; // clear } /** * Used when onMeasure is called before layout manager is set */ - private void defaultOnMeasure(int widthSpec, int heightSpec) { - final int widthMode = MeasureSpec.getMode(widthSpec); - final int heightMode = MeasureSpec.getMode(heightSpec); - final int widthSize = MeasureSpec.getSize(widthSpec); - final int heightSize = MeasureSpec.getSize(heightSpec); - - int width = 0; - int height = 0; - - switch (widthMode) { - case MeasureSpec.EXACTLY: - case MeasureSpec.AT_MOST: - width = widthSize; - break; - case MeasureSpec.UNSPECIFIED: - default: - width = ViewCompat.getMinimumWidth(this); - break; - } - - switch (heightMode) { - case MeasureSpec.EXACTLY: - case MeasureSpec.AT_MOST: - height = heightSize; - break; - case MeasureSpec.UNSPECIFIED: - default: - height = ViewCompat.getMinimumHeight(this); - break; - } + void defaultOnMeasure(int widthSpec, int heightSpec) { + // calling LayoutManager here is not pretty but that API is already public and it is better + // than creating another method since this is internal. + final int width = LayoutManager.chooseSize(widthSpec, + getPaddingLeft() + getPaddingRight(), + ViewCompat.getMinimumWidth(this)); + final int height = LayoutManager.chooseSize(heightSpec, + getPaddingTop() + getPaddingBottom(), + ViewCompat.getMinimumHeight(this)); setMeasuredDimension(width, height); } @@ -2545,6 +2672,7 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (w != oldw || h != oldh) { invalidateGlows(); + // layout's w/h are updated during measure/layout steps. } } @@ -2668,10 +2796,6 @@ public ItemAnimator getItemAnimator() { return mItemAnimator; } - private boolean supportsChangeAnimations() { - return mItemAnimator != null && mItemAnimator.getSupportsChangeAnimations(); - } - /** * Post a runnable to the next frame to run pending item animations. Only the first such * request will be posted, governed by the mPostedAnimatorRunner flag. @@ -2704,13 +2828,12 @@ private void processAdapterUpdatesAndSetAnimationFlags() { // simple animations are a subset of advanced animations (which will cause a // pre-layout step) // If layout supports predictive animations, pre-process to decide if we want to run them - if (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations()) { + if (predictiveItemAnimationsEnabled()) { mAdapterHelper.preProcess(); } else { mAdapterHelper.consumeUpdatesInOnePass(); } - boolean animationTypeSupported = (mItemsAddedOrRemoved && !mItemsChanged) || - (mItemsAddedOrRemoved || (mItemsChanged && supportsChangeAnimations())); + boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged; mState.mRunSimpleAnimations = mFirstLayoutComplete && mItemAnimator != null && (mDataSetHasChangedAfterLayout || animationTypeSupported || mLayout.mRequestedSimpleAnimations) && @@ -2736,48 +2859,90 @@ private void processAdapterUpdatesAndSetAnimationFlags() { * The overall approach figures out what items exist before/after layout and * infers one of the five above states for each of the items. Then the animations * are set up accordingly: - * PERSISTENT views are moved ({@link ItemAnimator#animateMove(ViewHolder, int, int, int, int)}) - * REMOVED views are removed ({@link ItemAnimator#animateRemove(ViewHolder)}) - * ADDED views are added ({@link ItemAnimator#animateAdd(ViewHolder)}) - * DISAPPEARING views are moved off screen - * APPEARING views are moved on screen + * PERSISTENT views are animated via + * {@link ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)} + * DISAPPEARING views are animated via + * {@link ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} + * APPEARING views are animated via + * {@link ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} + * and changed views are animated via + * {@link ItemAnimator#animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)}. */ void dispatchLayout() { if (mAdapter == null) { Log.e(TAG, "No adapter attached; skipping layout"); + // leave the state in START return; } if (mLayout == null) { Log.e(TAG, "No layout manager attached; skipping layout"); + // leave the state in START return; } - mState.mDisappearingViewsInLayoutPass.clear(); + mState.mIsMeasuring = false; + if (mState.mLayoutStep == State.STEP_START) { + dispatchLayoutStep1(); + mLayout.setExactMeasureSpecsFrom(this); + dispatchLayoutStep2(); + } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() || + mLayout.getHeight() != getHeight()) { + // First 2 steps are done in onMeasure but looks like we have to run again due to + // changed size. + mLayout.setExactMeasureSpecsFrom(this); + dispatchLayoutStep2(); + } else { + // always make sure we sync them (to ensure mode is exact) + mLayout.setExactMeasureSpecsFrom(this); + } + dispatchLayoutStep3(); + } + + /** + * The first step of a layout where we; + * - process adapter updates + * - decide which animation should run + * - save information about current views + * - If necessary, run predictive layout and save its information + */ + private void dispatchLayoutStep1() { + mState.assertLayoutStep(State.STEP_START); + mState.mIsMeasuring = false; eatRequestLayout(); + mViewInfoStore.clear(); onEnterLayoutOrScroll(); processAdapterUpdatesAndSetAnimationFlags(); - - mState.mOldChangedHolders = mState.mRunSimpleAnimations && mItemsChanged - && supportsChangeAnimations() ? new ArrayMap() : null; + mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged; mItemsAddedOrRemoved = mItemsChanged = false; - ArrayMap appearingViewInitialBounds = null; mState.mInPreLayout = mState.mRunPredictiveAnimations; mState.mItemCount = mAdapter.getItemCount(); findMinMaxChildLayoutPositions(mMinMaxLayoutPositions); if (mState.mRunSimpleAnimations) { // Step 0: Find out where all non-removed items are, pre-layout - mState.mPreLayoutHolderMap.clear(); - mState.mPostLayoutHolderMap.clear(); int count = mChildHelper.getChildCount(); for (int i = 0; i < count; ++i) { final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) { continue; } - final View view = holder.itemView; - mState.mPreLayoutHolderMap.put(holder, new ItemHolderInfo(holder, - view.getLeft(), view.getTop(), view.getRight(), view.getBottom())); + final ItemHolderInfo animationInfo = mItemAnimator + .recordPreLayoutInformation(mState, holder, + ItemAnimator.buildAdapterChangeFlagsForAnimations(holder), + holder.getUnmodifiedPayloads()); + mViewInfoStore.addToPreLayout(holder, animationInfo); + if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved() + && !holder.shouldIgnore() && !holder.isInvalid()) { + long key = getChangedHolderKey(holder); + // This is NOT the only place where a ViewHolder is added to old change holders + // list. There is another case where: + // * A VH is currently hidden but not deleted + // * The hidden item is changed in the adapter + // * Layout manager decides to layout the item in the pre-Layout pass (step1) + // When this case is detected, RV will un-hide that view and add to the old + // change holders list. + mViewInfoStore.addToOldChangeHolders(key, holder); + } } } if (mState.mRunPredictiveAnimations) { @@ -2788,63 +2953,53 @@ void dispatchLayout() { // Save old positions so that LayoutManager can run its mapping logic. saveOldPositions(); - // processAdapterUpdatesAndSetAnimationFlags already run pre-layout animations. - if (mState.mOldChangedHolders != null) { - int count = mChildHelper.getChildCount(); - for (int i = 0; i < count; ++i) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); - if (holder.isChanged() && !holder.isRemoved() && !holder.shouldIgnore()) { - long key = getChangedHolderKey(holder); - mState.mOldChangedHolders.put(key, holder); - mState.mPreLayoutHolderMap.remove(holder); - } - } - } - final boolean didStructureChange = mState.mStructureChanged; mState.mStructureChanged = false; // temporarily disable flag because we are asking for previous layout mLayout.onLayoutChildren(mRecycler, mState); mState.mStructureChanged = didStructureChange; - appearingViewInitialBounds = new ArrayMap(); for (int i = 0; i < mChildHelper.getChildCount(); ++i) { - boolean found = false; - View child = mChildHelper.getChildAt(i); - if (getChildViewHolderInt(child).shouldIgnore()) { + final View child = mChildHelper.getChildAt(i); + final ViewHolder viewHolder = getChildViewHolderInt(child); + if (viewHolder.shouldIgnore()) { continue; } - for (int j = 0; j < mState.mPreLayoutHolderMap.size(); ++j) { - ViewHolder holder = mState.mPreLayoutHolderMap.keyAt(j); - if (holder.itemView == child) { - found = true; - break; + if (!mViewInfoStore.isInPreLayout(viewHolder)) { + int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder); + boolean wasHidden = viewHolder + .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); + if (!wasHidden) { + flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT; + } + final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation( + mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads()); + if (wasHidden) { + recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo); + } else { + mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo); } - } - if (!found) { - appearingViewInitialBounds.put(child, new Rect(child.getLeft(), child.getTop(), - child.getRight(), child.getBottom())); } } // we don't process disappearing list because they may re-appear in post layout pass. clearOldPositions(); - mAdapterHelper.consumePostponedUpdates(); } else { clearOldPositions(); - // in case pre layout did run but we decided not to run predictive animations. - mAdapterHelper.consumeUpdatesInOnePass(); - if (mState.mOldChangedHolders != null) { - int count = mChildHelper.getChildCount(); - for (int i = 0; i < count; ++i) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); - if (holder.isChanged() && !holder.isRemoved() && !holder.shouldIgnore()) { - long key = getChangedHolderKey(holder); - mState.mOldChangedHolders.put(key, holder); - mState.mPreLayoutHolderMap.remove(holder); - } - } - } } + onExitLayoutOrScroll(); + resumeRequestLayout(false); + mState.mLayoutStep = State.STEP_LAYOUT; + } + + /** + * The second layout step where we do the actual layout of the views for the final state. + * This step might be run multiple times if necessary (e.g. measure). + */ + private void dispatchLayoutStep2() { + eatRequestLayout(); + onEnterLayoutOrScroll(); + mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS); + mAdapterHelper.consumeUpdatesInOnePass(); mState.mItemCount = mAdapter.getItemCount(); mState.mDeletedInvisibleItemCountSincePreviousLayout = 0; @@ -2857,114 +3012,151 @@ void dispatchLayout() { // onLayoutChildren may have caused client code to disable item animations; re-check mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null; + mState.mLayoutStep = State.STEP_ANIMATIONS; + onExitLayoutOrScroll(); + resumeRequestLayout(false); + } + /** + * The final step of the layout where we save the information about views for animations, + * trigger animations and do any necessary cleanup. + */ + private void dispatchLayoutStep3() { + mState.assertLayoutStep(State.STEP_ANIMATIONS); + eatRequestLayout(); + onEnterLayoutOrScroll(); + mState.mLayoutStep = State.STEP_START; if (mState.mRunSimpleAnimations) { - // Step 3: Find out where things are now, post-layout - ArrayMap newChangedHolders = mState.mOldChangedHolders != null ? - new ArrayMap() : null; - int count = mChildHelper.getChildCount(); - for (int i = 0; i < count; ++i) { + // Step 3: Find out where things are now, and process change animations. + // traverse list in reverse because we may call animateChange in the loop which may + // remove the target view holder. + for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) { ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); if (holder.shouldIgnore()) { continue; } - final View view = holder.itemView; long key = getChangedHolderKey(holder); - if (newChangedHolders != null && mState.mOldChangedHolders.get(key) != null) { - newChangedHolders.put(key, holder); - } else { - mState.mPostLayoutHolderMap.put(holder, new ItemHolderInfo(holder, - view.getLeft(), view.getTop(), view.getRight(), view.getBottom())); - } - } - processDisappearingList(appearingViewInitialBounds); - // Step 4: Animate DISAPPEARING and REMOVED items - int preLayoutCount = mState.mPreLayoutHolderMap.size(); - for (int i = preLayoutCount - 1; i >= 0; i--) { - ViewHolder itemHolder = mState.mPreLayoutHolderMap.keyAt(i); - if (!mState.mPostLayoutHolderMap.containsKey(itemHolder)) { - ItemHolderInfo disappearingItem = mState.mPreLayoutHolderMap.valueAt(i); - mState.mPreLayoutHolderMap.removeAt(i); - - View disappearingItemView = disappearingItem.holder.itemView; - mRecycler.unscrapView(disappearingItem.holder); - animateDisappearance(disappearingItem); - } - } - // Step 5: Animate APPEARING and ADDED items - int postLayoutCount = mState.mPostLayoutHolderMap.size(); - if (postLayoutCount > 0) { - for (int i = postLayoutCount - 1; i >= 0; i--) { - ViewHolder itemHolder = mState.mPostLayoutHolderMap.keyAt(i); - ItemHolderInfo info = mState.mPostLayoutHolderMap.valueAt(i); - if ((mState.mPreLayoutHolderMap.isEmpty() || - !mState.mPreLayoutHolderMap.containsKey(itemHolder))) { - mState.mPostLayoutHolderMap.removeAt(i); - Rect initialBounds = (appearingViewInitialBounds != null) ? - appearingViewInitialBounds.get(itemHolder.itemView) : null; - animateAppearance(itemHolder, initialBounds, - info.left, info.top); - } - } - } - // Step 6: Animate PERSISTENT items - count = mState.mPostLayoutHolderMap.size(); - for (int i = 0; i < count; ++i) { - ViewHolder postHolder = mState.mPostLayoutHolderMap.keyAt(i); - ItemHolderInfo postInfo = mState.mPostLayoutHolderMap.valueAt(i); - ItemHolderInfo preInfo = mState.mPreLayoutHolderMap.get(postHolder); - if (preInfo != null && postInfo != null) { - if (preInfo.left != postInfo.left || preInfo.top != postInfo.top) { - postHolder.setIsRecyclable(false); - if (DEBUG) { - Log.d(TAG, "PERSISTENT: " + postHolder + - " with view " + postHolder.itemView); - } - if (mItemAnimator.animateMove(postHolder, - preInfo.left, preInfo.top, postInfo.left, postInfo.top)) { - postAnimationRunner(); + final ItemHolderInfo animationInfo = mItemAnimator + .recordPostLayoutInformation(mState, holder); + ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key); + if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) { + // run a change animation + + // If an Item is CHANGED but the updated version is disappearing, it creates + // a conflicting case. + // Since a view that is marked as disappearing is likely to be going out of + // bounds, we run a change animation. Both views will be cleaned automatically + // once their animations finish. + // On the other hand, if it is the same view holder instance, we run a + // disappearing animation instead because we are not going to rebind the updated + // VH unless it is enforced by the layout manager. + final boolean oldDisappearing = mViewInfoStore.isDisappearing( + oldChangeViewHolder); + final boolean newDisappearing = mViewInfoStore.isDisappearing(holder); + if (oldDisappearing && oldChangeViewHolder == holder) { + // run disappear animation instead of change + mViewInfoStore.addToPostLayout(holder, animationInfo); + } else { + final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout( + oldChangeViewHolder); + // we add and remove so that any post info is merged. + mViewInfoStore.addToPostLayout(holder, animationInfo); + ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder); + if (preInfo == null) { + handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder); + } else { + animateChange(oldChangeViewHolder, holder, preInfo, postInfo, + oldDisappearing, newDisappearing); } } + } else { + mViewInfoStore.addToPostLayout(holder, animationInfo); } } - // Step 7: Animate CHANGING items - count = mState.mOldChangedHolders != null ? mState.mOldChangedHolders.size() : 0; - // traverse reverse in case view gets recycled while we are traversing the list. - for (int i = count - 1; i >= 0; i--) { - long key = mState.mOldChangedHolders.keyAt(i); - ViewHolder oldHolder = mState.mOldChangedHolders.get(key); - View oldView = oldHolder.itemView; - if (oldHolder.shouldIgnore()) { - continue; - } - // We probably don't need this check anymore since these views are removed from - // the list if they are recycled. - if (mRecycler.mChangedScrap != null && - mRecycler.mChangedScrap.contains(oldHolder)) { - animateChange(oldHolder, newChangedHolders.get(key)); - } else if (DEBUG) { - Log.e(TAG, "cannot find old changed holder in changed scrap :/" + oldHolder); - } - } + + // Step 4: Process view info lists and trigger animations + mViewInfoStore.process(mViewInfoProcessCallback); } - resumeRequestLayout(false); + mLayout.removeAndRecycleScrapInt(mRecycler); mState.mPreviousLayoutItemCount = mState.mItemCount; mDataSetHasChangedAfterLayout = false; mState.mRunSimpleAnimations = false; + mState.mRunPredictiveAnimations = false; - onExitLayoutOrScroll(); mLayout.mRequestedSimpleAnimations = false; if (mRecycler.mChangedScrap != null) { mRecycler.mChangedScrap.clear(); } - mState.mOldChangedHolders = null; - + onExitLayoutOrScroll(); + resumeRequestLayout(false); + mViewInfoStore.clear(); if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) { dispatchOnScrolled(0, 0); } } + /** + * This handles the case where there is an unexpected VH missing in the pre-layout map. + *

+ * We might be able to detect the error in the application which will help the developer to + * resolve the issue. + *

+ * If it is not an expected error, we at least print an error to notify the developer and ignore + * the animation. + * + * https://code.google.com/p/android/issues/detail?id=193958 + * + * @param key The change key + * @param holder Current ViewHolder + * @param oldChangeViewHolder Changed ViewHolder + */ + private void handleMissingPreInfoForChangeError(long key, + ViewHolder holder, ViewHolder oldChangeViewHolder) { + // check if two VH have the same key, if so, print that as an error + final int childCount = mChildHelper.getChildCount(); + for (int i = 0; i < childCount; i++) { + View view = mChildHelper.getChildAt(i); + ViewHolder other = getChildViewHolderInt(view); + if (other == holder) { + continue; + } + final long otherKey = getChangedHolderKey(other); + if (otherKey == key) { + if (mAdapter != null && mAdapter.hasStableIds()) { + throw new IllegalStateException("Two different ViewHolders have the same stable" + + " ID. Stable IDs in your adapter MUST BE unique and SHOULD NOT" + + " change.\n ViewHolder 1:" + other + " \n View Holder 2:" + holder); + } else { + throw new IllegalStateException("Two different ViewHolders have the same change" + + " ID. This might happen due to inconsistent Adapter update events or" + + " if the LayoutManager lays out the same View multiple times." + + "\n ViewHolder 1:" + other + " \n View Holder 2:" + holder); + } + } + } + // Very unlikely to happen but if it does, notify the developer. + Log.e(TAG, "Problem while matching changed view holders with the new" + + "ones. The pre-layout information for the change holder " + oldChangeViewHolder + + " cannot be found but it is necessary for " + holder); + } + + /** + * Records the animation information for a view holder that was bounced from hidden list. It + * also clears the bounce back flag. + */ + private void recordAnimationInfoIfBouncedHiddenView(ViewHolder viewHolder, + ItemHolderInfo animationInfo) { + // looks like this view bounced back from hidden list! + viewHolder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); + if (mState.mTrackOldChangeHolders && viewHolder.isUpdated() + && !viewHolder.isRemoved() && !viewHolder.shouldIgnore()) { + long key = getChangedHolderKey(viewHolder); + mViewInfoStore.addToOldChangeHolders(key, viewHolder); + } + mViewInfoStore.addToPreLayout(viewHolder, animationInfo); + } + private void findMinMaxChildLayoutPositions(int[] into) { final int count = mChildHelper.getChildCount(); if (count == 0) { @@ -3032,129 +3224,57 @@ long getChangedHolderKey(ViewHolder holder) { return mAdapter.hasStableIds() ? holder.getItemId() : holder.mPosition; } - /** - * A LayoutManager may want to layout a view just to animate disappearance. - * This method handles those views and triggers remove animation on them. - */ - private void processDisappearingList(ArrayMap appearingViews) { - final List disappearingList = mState.mDisappearingViewsInLayoutPass; - for (int i = disappearingList.size() - 1; i >= 0; i --) { - View view = disappearingList.get(i); - ViewHolder vh = getChildViewHolderInt(view); - final ItemHolderInfo info = mState.mPreLayoutHolderMap.remove(vh); - if (!mState.isPreLayout()) { - mState.mPostLayoutHolderMap.remove(vh); - } - if (appearingViews.remove(view) != null) { - mLayout.removeAndRecycleView(view, mRecycler); - continue; - } - if (info != null) { - animateDisappearance(info); - } else { - // let it disappear from the position it becomes visible - animateDisappearance(new ItemHolderInfo(vh, view.getLeft(), view.getTop(), - view.getRight(), view.getBottom())); - } + private void animateAppearance(@NonNull ViewHolder itemHolder, + @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) { + itemHolder.setIsRecyclable(false); + if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) { + postAnimationRunner(); } - disappearingList.clear(); } - private void animateAppearance(ViewHolder itemHolder, Rect beforeBounds, int afterLeft, - int afterTop) { - View newItemView = itemHolder.itemView; - if (beforeBounds != null && - (beforeBounds.left != afterLeft || beforeBounds.top != afterTop)) { - // slide items in if before/after locations differ - itemHolder.setIsRecyclable(false); - if (DEBUG) { - Log.d(TAG, "APPEARING: " + itemHolder + " with view " + newItemView); - } - if (mItemAnimator.animateMove(itemHolder, - beforeBounds.left, beforeBounds.top, - afterLeft, afterTop)) { - postAnimationRunner(); - } - } else { - if (DEBUG) { - Log.d(TAG, "ADDED: " + itemHolder + " with view " + newItemView); - } - itemHolder.setIsRecyclable(false); - if (mItemAnimator.animateAdd(itemHolder)) { - postAnimationRunner(); - } + private void animateDisappearance(@NonNull ViewHolder holder, + @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) { + addAnimatingView(holder); + holder.setIsRecyclable(false); + if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) { + postAnimationRunner(); } } - private void animateDisappearance(ItemHolderInfo disappearingItem) { - View disappearingItemView = disappearingItem.holder.itemView; - addAnimatingView(disappearingItem.holder); - int oldLeft = disappearingItem.left; - int oldTop = disappearingItem.top; - int newLeft = disappearingItemView.getLeft(); - int newTop = disappearingItemView.getTop(); - if (!disappearingItem.holder.isRemoved() && (oldLeft != newLeft || oldTop != newTop)) { - disappearingItem.holder.setIsRecyclable(false); - disappearingItemView.layout(newLeft, newTop, - newLeft + disappearingItemView.getWidth(), - newTop + disappearingItemView.getHeight()); - if (DEBUG) { - Log.d(TAG, "DISAPPEARING: " + disappearingItem.holder + - " with view " + disappearingItemView); - } - if (mItemAnimator.animateMove(disappearingItem.holder, oldLeft, oldTop, - newLeft, newTop)) { - postAnimationRunner(); + private void animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder, + @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo, + boolean oldHolderDisappearing, boolean newHolderDisappearing) { + oldHolder.setIsRecyclable(false); + if (oldHolderDisappearing) { + addAnimatingView(oldHolder); + } + if (oldHolder != newHolder) { + if (newHolderDisappearing) { + addAnimatingView(newHolder); } - } else { - if (DEBUG) { - Log.d(TAG, "REMOVED: " + disappearingItem.holder + - " with view " + disappearingItemView); - } - disappearingItem.holder.setIsRecyclable(false); - if (mItemAnimator.animateRemove(disappearingItem.holder)) { - postAnimationRunner(); - } - } - } - - private void animateChange(ViewHolder oldHolder, ViewHolder newHolder) { - oldHolder.setIsRecyclable(false); - addAnimatingView(oldHolder); - oldHolder.mShadowedHolder = newHolder; - mRecycler.unscrapView(oldHolder); - if (DEBUG) { - Log.d(TAG, "CHANGED: " + oldHolder + " with view " + oldHolder.itemView); - } - final int fromLeft = oldHolder.itemView.getLeft(); - final int fromTop = oldHolder.itemView.getTop(); - final int toLeft, toTop; - if (newHolder == null || newHolder.shouldIgnore()) { - toLeft = fromLeft; - toTop = fromTop; - } else { - toLeft = newHolder.itemView.getLeft(); - toTop = newHolder.itemView.getTop(); + oldHolder.mShadowedHolder = newHolder; + // old holder should disappear after animation ends + addAnimatingView(oldHolder); + mRecycler.unscrapView(oldHolder); newHolder.setIsRecyclable(false); newHolder.mShadowingHolder = oldHolder; } - if(mItemAnimator.animateChange(oldHolder, newHolder, - fromLeft, fromTop, toLeft, toTop)) { + if (mItemAnimator.animateChange(oldHolder, newHolder, preInfo, postInfo)) { postAnimationRunner(); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { - eatRequestLayout(); + TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG); dispatchLayout(); - resumeRequestLayout(false); + TraceCompat.endSection(); mFirstLayoutComplete = true; } @Override public void requestLayout() { - if (!mEatRequestLayout && !mLayoutFrozen) { + if (mEatRequestLayout == 0 && !mLayoutFrozen) { super.requestLayout(); } else { mLayoutRequestEaten = true; @@ -3418,9 +3538,6 @@ void viewRangeUpdate(int positionStart, int itemCount, Object payload) { // ViewHolders have their final positions assigned. holder.addFlags(ViewHolder.FLAG_UPDATE); holder.addChangePayload(payload); - if (supportsChangeAnimations()) { - holder.addFlags(ViewHolder.FLAG_CHANGED); - } // lp cannot be null since we get ViewHolder from it. ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; } @@ -3428,36 +3545,9 @@ void viewRangeUpdate(int positionStart, int itemCount, Object payload) { mRecycler.viewRangeUpdate(positionStart, itemCount); } - void rebindUpdatedViewHolders() { - final int childCount = mChildHelper.getChildCount(); - for (int i = 0; i < childCount; i++) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); - // validate type is correct - if (holder == null || holder.shouldIgnore()) { - continue; - } - if (holder.isRemoved() || holder.isInvalid()) { - requestLayout(); - } else if (holder.needsUpdate()) { - final int type = mAdapter.getItemViewType(holder.mPosition); - if (holder.getItemViewType() == type) { - // Binding an attached view will request a layout if needed. - if (!holder.isChanged() || !supportsChangeAnimations()) { - mAdapter.bindViewHolder(holder, holder.mPosition); - } else { - // Don't rebind changed holders if change animations are enabled. - // We want the old contents for the animation and will get a new - // holder for the new contents. - requestLayout(); - } - } else { - // binding to a new view will need re-layout anyways. We can as well trigger - // it here so that it happens during layout - requestLayout(); - break; - } - } - } + private boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) { + return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder, + viewHolder.getUnmodifiedPayloads()); } private void setDataSetChangedAfterLayout() { @@ -3522,6 +3612,44 @@ public ViewHolder getChildViewHolder(View child) { return getChildViewHolderInt(child); } + /** + * Traverses the ancestors of the given view and returns the item view that contains it and + * also a direct child of the RecyclerView. This returned view can be used to get the + * ViewHolder by calling {@link #getChildViewHolder(View)}. + * + * @param view The view that is a descendant of the RecyclerView. + * + * @return The direct child of the RecyclerView which contains the given view or null if the + * provided view is not a descendant of this RecyclerView. + * + * @see #getChildViewHolder(View) + * @see #findContainingViewHolder(View) + */ + @Nullable + public View findContainingItemView(View view) { + ViewParent parent = view.getParent(); + while (parent != null && parent != this && parent instanceof View) { + view = (View) parent; + parent = view.getParent(); + } + return parent == this ? view : null; + } + + /** + * Returns the ViewHolder that contains the given view. + * + * @param view The view that is a descendant of the RecyclerView. + * + * @return The ViewHolder that contains the given view or null if the provided view is not a + * descendant of this RecyclerView. + */ + @Nullable + public ViewHolder findContainingViewHolder(View view) { + View itemView = findContainingItemView(view); + return itemView == null ? null : getChildViewHolder(itemView); + } + + static ViewHolder getChildViewHolderInt(View child) { if (child == null) { return null; @@ -3897,6 +4025,10 @@ public ViewFlinger() { @Override public void run() { + if (mLayout == null) { + stop(); + return; // no layout, cannot scroll. + } disableRunOnAnimationRequests(); consumePendingUpdateOperations(); // keep a local reference so that if it is changed during onAnimation method, it won't @@ -3916,6 +4048,7 @@ public void run() { if (mAdapter != null) { eatRequestLayout(); onEnterLayoutOrScroll(); + TraceCompat.beginSection(TRACE_SCROLL_TAG); if (dx != 0) { hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState); overscrollX = dx - hresult; @@ -3924,25 +4057,9 @@ public void run() { vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState); overscrollY = dy - vresult; } - if (supportsChangeAnimations()) { - // Fix up shadow views used by changing animations - int count = mChildHelper.getChildCount(); - for (int i = 0; i < count; i++) { - View view = mChildHelper.getChildAt(i); - ViewHolder holder = getChildViewHolder(view); - if (holder != null && holder.mShadowingHolder != null) { - View shadowingView = holder.mShadowingHolder.itemView; - int left = view.getLeft(); - int top = view.getTop(); - if (left != shadowingView.getLeft() || - top != shadowingView.getTop()) { - shadowingView.layout(left, top, - left + shadowingView.getWidth(), - top + shadowingView.getHeight()); - } - } - } - } + TraceCompat.endSection(); + repositionShadowingViews(); + onExitLayoutOrScroll(); resumeRequestLayout(false); @@ -4108,6 +4225,26 @@ public void stop() { } + private void repositionShadowingViews() { + // Fix up shadow views used by change animations + int count = mChildHelper.getChildCount(); + for (int i = 0; i < count; i++) { + View view = mChildHelper.getChildAt(i); + ViewHolder holder = getChildViewHolder(view); + if (holder != null && holder.mShadowingHolder != null) { + View shadowingView = holder.mShadowingHolder.itemView; + int left = view.getLeft(); + int top = view.getTop(); + if (left != shadowingView.getLeft() || + top != shadowingView.getTop()) { + shadowingView.layout(left, top, + left + shadowingView.getWidth(), + top + shadowingView.getHeight()); + } + } + } + } + private class RecyclerViewDataObserver extends AdapterDataObserver { @Override public void onChanged() { @@ -4184,7 +4321,7 @@ public static class RecycledViewPool { private SparseIntArray mMaxScrap = new SparseIntArray(); private int mAttachCount = 0; - private static final int DEFAULT_MAX_SCRAP = 10; + private static final int DEFAULT_MAX_SCRAP = 5; public void clear() { mScrap.clear(); @@ -4271,7 +4408,7 @@ void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, private ArrayList getScrapHeapForType(int viewType) { ArrayList scrap = mScrap.get(viewType); if (scrap == null) { - scrap = new ArrayList(); + scrap = new ArrayList<>(); mScrap.put(viewType, scrap); if (mMaxScrap.indexOfKey(viewType) < 0) { mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP); @@ -4295,7 +4432,7 @@ private ArrayList getScrapHeapForType(int viewType) { * may be repositioned by a LayoutManager without remeasurement.

*/ public final class Recycler { - final ArrayList mAttachedScrap = new ArrayList(); + final ArrayList mAttachedScrap = new ArrayList<>(); private ArrayList mChangedScrap = null; final ArrayList mCachedViews = new ArrayList(); @@ -4354,7 +4491,11 @@ boolean validateViewHolderForOffsetPosition(ViewHolder holder) { // if it is a removed holder, nothing to verify since we cannot ask adapter anymore // if it is not removed, verify the type and id. if (holder.isRemoved()) { - return true; + if (DEBUG && !mState.isPreLayout()) { + throw new IllegalStateException("should not receive a removed view unelss it" + + " is pre layout"); + } + return mState.isPreLayout(); } if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) { throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder " @@ -4565,6 +4706,23 @@ View getViewForPosition(int position, boolean dryRun) { } } } + + // This is very ugly but the only place we can grab this information + // before the View is rebound and returned to the LayoutManager for post layout ops. + // We don't need this in pre-layout since the VH is not updated by the LM. + if (fromScrap && !mState.isPreLayout() && holder + .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) { + holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); + if (mState.mRunSimpleAnimations) { + int changeFlags = ItemAnimator + .buildAdapterChangeFlagsForAnimations(holder); + changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT; + final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState, + holder, changeFlags, holder.getUnmodifiedPayloads()); + recordAnimationInfoIfBouncedHiddenView(holder, info); + } + } + boolean bound = false; if (mState.isPreLayout() && holder.isBound()) { // do not update unless we absolutely have to. @@ -4741,8 +4899,8 @@ void recycleViewHolderInternal(ViewHolder holder) { holder); } if (forceRecycle || holder.isRecyclable()) { - if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | - ViewHolder.FLAG_CHANGED | ViewHolder.FLAG_UPDATE)) { + if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED + | ViewHolder.FLAG_UPDATE)) { // Retire oldest cached view final int cachedViewSize = mCachedViews.size(); if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) { @@ -4763,7 +4921,7 @@ void recycleViewHolderInternal(ViewHolder holder) { } // even if the holder is not removed, we still call this method so that it is removed // from view holder lists. - mState.onViewRecycled(holder); + mViewInfoStore.removeViewHolder(holder); if (!cached && !recycled && transientStatePreventsRecycling) { holder.mOwnerRecyclerView = null; } @@ -4784,6 +4942,7 @@ void addViewHolderToRecycledViewPool(ViewHolder holder) { void quickRecycleScrapView(View view) { final ViewHolder holder = getChildViewHolderInt(view); holder.mScrapContainer = null; + holder.mInChangeScrap = false; holder.clearReturnedFromScrapFlag(); recycleViewHolderInternal(holder); } @@ -4799,18 +4958,20 @@ void quickRecycleScrapView(View view) { */ void scrapView(View view) { final ViewHolder holder = getChildViewHolderInt(view); - holder.setScrapContainer(this); - if (!holder.isChanged() || !supportsChangeAnimations()) { + if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) + || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) { if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) { throw new IllegalArgumentException("Called scrap view with an invalid view." + " Invalid views cannot be reused from scrap, they should rebound from" + " recycler pool."); } + holder.setScrapContainer(this, false); mAttachedScrap.add(holder); } else { if (mChangedScrap == null) { mChangedScrap = new ArrayList(); } + holder.setScrapContainer(this, true); mChangedScrap.add(holder); } } @@ -4822,12 +4983,13 @@ void scrapView(View view) { * until it is explicitly removed and recycled.

*/ void unscrapView(ViewHolder holder) { - if (!holder.isChanged() || !supportsChangeAnimations() || mChangedScrap == null) { - mAttachedScrap.remove(holder); - } else { + if (holder.mInChangeScrap) { mChangedScrap.remove(holder); + } else { + mAttachedScrap.remove(holder); } holder.mScrapContainer = null; + holder.mInChangeScrap = false; holder.clearReturnedFromScrapFlag(); } @@ -4841,6 +5003,9 @@ View getScrapViewAt(int index) { void clearScrap() { mAttachedScrap.clear(); + if (mChangedScrap != null) { + mChangedScrap.clear(); + } } ViewHolder getChangedScrapViewForPosition(int position) { @@ -4905,8 +5070,20 @@ ViewHolder getScrapViewForPosition(int position, int type, boolean dryRun) { if (!dryRun) { View view = mChildHelper.findHiddenNonRemovedView(position, type); if (view != null) { - // ending the animation should cause it to get recycled before we reuse it - mItemAnimator.endAnimation(getChildViewHolder(view)); + // This View is good to be used. We just need to unhide, detach and move to the + // scrap list. + final ViewHolder vh = getChildViewHolderInt(view); + mChildHelper.unhide(view); + int layoutIndex = mChildHelper.indexOfChild(view); + if (layoutIndex == RecyclerView.NO_POSITION) { + throw new IllegalStateException("layout index should not be -1 after " + + "unhiding a view:" + vh); + } + mChildHelper.detachViewFromParent(layoutIndex); + scrapView(view); + vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP + | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); + return vh; } } @@ -4954,6 +5131,8 @@ ViewHolder getScrapViewForId(long id, int type, boolean dryRun) { } return holder; } else if (!dryRun) { + // if we are running animations, it is actually better to keep it in scrap + // but this would force layout manager to lay it out which would be bad. // Recycle this scrap. Type mismatch. mAttachedScrap.remove(i); removeDetachedView(holder.itemView, false); @@ -4988,7 +5167,7 @@ void dispatchViewRecycled(ViewHolder holder) { mAdapter.onViewRecycled(holder); } if (mState != null) { - mState.onViewRecycled(holder); + mViewInfoStore.removeViewHolder(holder); } if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder); } @@ -5032,7 +5211,7 @@ void offsetPositionRecordsForInsert(int insertedAt, int count) { final int cachedCount = mCachedViews.size(); for (int i = 0; i < cachedCount; i++) { final ViewHolder holder = mCachedViews.get(i); - if (holder != null && holder.getLayoutPosition() >= insertedAt) { + if (holder != null && holder.mPosition >= insertedAt) { if (DEBUG) { Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder " + holder + " now at position " + (holder.mPosition + count)); @@ -5054,14 +5233,14 @@ void offsetPositionRecordsForRemove(int removedFrom, int count, boolean applyToP for (int i = cachedCount - 1; i >= 0; i--) { final ViewHolder holder = mCachedViews.get(i); if (holder != null) { - if (holder.getLayoutPosition() >= removedEnd) { + if (holder.mPosition >= removedEnd) { if (DEBUG) { Log.d(TAG, "offsetPositionRecordsForRemove cached " + i + " holder " + holder + " now at position " + (holder.mPosition - count)); } holder.offsetPosition(-count, applyToPreLayout); - } else if (holder.getLayoutPosition() >= removedFrom) { + } else if (holder.mPosition >= removedFrom) { // Item for this view was removed. Dump it from the cache. holder.addFlags(ViewHolder.FLAG_REMOVED); recycleCachedViewAt(i); @@ -5295,8 +5474,10 @@ public void onBindViewHolder(VH holder, int position, List payloads) { * @see #onCreateViewHolder(ViewGroup, int) */ public final VH createViewHolder(ViewGroup parent, int viewType) { + TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG); final VH holder = onCreateViewHolder(parent, viewType); holder.mItemViewType = viewType; + TraceCompat.endSection(); return holder; } @@ -5315,8 +5496,10 @@ public final void bindViewHolder(VH holder, int position) { holder.setFlags(ViewHolder.FLAG_BOUND, ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN); + TraceCompat.beginSection(TRACE_BIND_VIEW_TAG); onBindViewHolder(holder, position, holder.getUnmodifiedPayloads()); holder.clearPayload(); + TraceCompat.endSection(); } /** @@ -5478,7 +5661,7 @@ public final boolean hasObservers() { * *

The adapter may publish a variety of events describing specific changes. * Not all adapters may support all change types and some may fall back to a generic - * {@link org.telegram.messenger.support.widget.RecyclerView.AdapterDataObserver#onChanged() + * {@link android.support.v7.widget.RecyclerView.AdapterDataObserver#onChanged() * "something changed"} event if more specific data is not available.

* *

Components registering observers with an adapter are responsible for @@ -5764,7 +5947,6 @@ private void dispatchChildAttached(View child) { mOnChildAttachStateListeners.get(i).onChildViewAttachedToWindow(child); } } - } /** @@ -5792,17 +5974,119 @@ public static abstract class LayoutManager { private boolean mRequestedSimpleAnimations = false; - private boolean mIsAttachedToWindow = false; + boolean mIsAttachedToWindow = false; + + private boolean mAutoMeasure = false; + + /** + * LayoutManager has its own more strict measurement cache to avoid re-measuring a child + * if the space that will be given to it is already larger than what it has measured before. + */ + private boolean mMeasurementCacheEnabled = true; + + + /** + * These measure specs might be the measure specs that were passed into RecyclerView's + * onMeasure method OR fake measure specs created by the RecyclerView. + * For example, when a layout is run, RecyclerView always sets these specs to be + * EXACTLY because a LayoutManager cannot resize RecyclerView during a layout pass. + */ + private int mWidthSpec, mHeightSpec; void setRecyclerView(RecyclerView recyclerView) { if (recyclerView == null) { mRecyclerView = null; mChildHelper = null; + mWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY); + mHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY); } else { mRecyclerView = recyclerView; mChildHelper = recyclerView.mChildHelper; + mWidthSpec = MeasureSpec + .makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY); + mHeightSpec = MeasureSpec + .makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY); + } + } + + void setMeasureSpecs(int wSpec, int hSpec) { + mWidthSpec = wSpec; + mHeightSpec = hSpec; + } + + /** + * Called after a layout is calculated during a measure pass when using auto-measure. + *

+ * It simply traverses all children to calculate a bounding box then calls + * {@link #setMeasuredDimension(Rect, int, int)}. LayoutManagers can override that method + * if they need to handle the bounding box differently. + *

+ * For example, GridLayoutManager override that method to ensure that even if a column is + * empty, the GridLayoutManager still measures wide enough to include it. + * + * @param widthSpec The widthSpec that was passing into RecyclerView's onMeasure + * @param heightSpec The heightSpec that was passing into RecyclerView's onMeasure + */ + void setMeasuredDimensionFromChildren(int widthSpec, int heightSpec) { + final int count = getChildCount(); + if (count == 0) { + mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); + return; + } + int minX = Integer.MAX_VALUE; + int minY = Integer.MAX_VALUE; + int maxX = Integer.MIN_VALUE; + int maxY = Integer.MIN_VALUE; + + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + int left = getDecoratedLeft(child) - lp.leftMargin; + int right = getDecoratedRight(child) + lp.rightMargin; + int top = getDecoratedTop(child) - lp.topMargin; + int bottom = getDecoratedBottom(child) + lp.bottomMargin; + if (left < minX) { + minX = left; + } + if (right > maxX) { + maxX = right; + } + if (top < minY) { + minY = top; + } + if (bottom > maxY) { + maxY = bottom; + } } + mRecyclerView.mTempRect.set(minX, minY, maxX, maxY); + setMeasuredDimension(mRecyclerView.mTempRect, widthSpec, heightSpec); + } + /** + * Sets the measured dimensions from the given bounding box of the children and the + * measurement specs that were passed into {@link RecyclerView#onMeasure(int, int)}. It is + * called after the RecyclerView calls + * {@link LayoutManager#onLayoutChildren(Recycler, State)} during a measurement pass. + *

+ * This method should call {@link #setMeasuredDimension(int, int)}. + *

+ * The default implementation adds the RecyclerView's padding to the given bounding box + * then caps the value to be within the given measurement specs. + *

+ * This method is only called if the LayoutManager opted into the auto measurement API. + * + * @param childrenBounds The bounding box of all children + * @param wSpec The widthMeasureSpec that was passed into the RecyclerView. + * @param hSpec The heightMeasureSpec that was passed into the RecyclerView. + * + * @see #setAutoMeasureEnabled(boolean) + */ + public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) { + int usedWidth = childrenBounds.width() + getPaddingLeft() + getPaddingRight(); + int usedHeight = childrenBounds.height() + getPaddingTop() + getPaddingBottom(); + int width = chooseSize(wSpec, usedWidth, getMinimumWidth()); + int height = chooseSize(hSpec, usedHeight, getMinimumHeight()); + setMeasuredDimension(width, height); } /** @@ -5827,6 +6111,30 @@ public void assertInLayoutOrScroll(String message) { } } + /** + * Chooses a size from the given specs and parameters that is closest to the desired size + * and also complies with the spec. + * + * @param spec The measureSpec + * @param desired The preferred measurement + * @param min The minimum value + * + * @return A size that fits to the given specs + */ + public static int chooseSize(int spec, int desired, int min) { + final int mode = View.MeasureSpec.getMode(spec); + final int size = View.MeasureSpec.getSize(spec); + switch (mode) { + case View.MeasureSpec.EXACTLY: + return size; + case View.MeasureSpec.AT_MOST: + return Math.min(size, Math.max(desired, min)); + case View.MeasureSpec.UNSPECIFIED: + default: + return Math.max(desired, min); + } + } + /** * Checks if RecyclerView is in the middle of a layout or scroll and throws an * {@link IllegalStateException} if it is. @@ -5840,6 +6148,86 @@ public void assertNotInLayoutOrScroll(String message) { } } + /** + * Defines whether the layout should be measured by the RecyclerView or the LayoutManager + * wants to handle the layout measurements itself. + *

+ * This method is usually called by the LayoutManager with value {@code true} if it wants + * to support WRAP_CONTENT. If you are using a public LayoutManager but want to customize + * the measurement logic, you can call this method with {@code false} and override + * {@link LayoutManager#onMeasure(int, int)} to implement your custom measurement logic. + *

+ * AutoMeasure is a convenience mechanism for LayoutManagers to easily wrap their content or + * handle various specs provided by the RecyclerView's parent. + * It works by calling {@link LayoutManager#onLayoutChildren(Recycler, State)} during an + * {@link RecyclerView#onMeasure(int, int)} call, then calculating desired dimensions based + * on children's positions. It does this while supporting all existing animation + * capabilities of the RecyclerView. + *

+ * AutoMeasure works as follows: + *

    + *
  1. LayoutManager should call {@code setAutoMeasureEnabled(true)} to enable it. All of + * the framework LayoutManagers use {@code auto-measure}.
  2. + *
  3. When {@link RecyclerView#onMeasure(int, int)} is called, if the provided specs are + * exact, RecyclerView will only call LayoutManager's {@code onMeasure} and return without + * doing any layout calculation.
  4. + *
  5. If one of the layout specs is not {@code EXACT}, the RecyclerView will start the + * layout process in {@code onMeasure} call. It will process all pending Adapter updates and + * decide whether to run a predictive layout or not. If it decides to do so, it will first + * call {@link #onLayoutChildren(Recycler, State)} with {@link State#isPreLayout()} set to + * {@code true}. At this stage, {@link #getWidth()} and {@link #getHeight()} will still + * return the width and height of the RecyclerView as of the last layout calculation. + *

    + * After handling the predictive case, RecyclerView will call + * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to + * {@code true} and {@link State#isPreLayout()} set to {@code false}. The LayoutManager can + * access the measurement specs via {@link #getHeight()}, {@link #getHeightMode()}, + * {@link #getWidth()} and {@link #getWidthMode()}.

  6. + *
  7. After the layout calculation, RecyclerView sets the measured width & height by + * calculating the bounding box for the children (+ RecyclerView's padding). The + * LayoutManagers can override {@link #setMeasuredDimension(Rect, int, int)} to choose + * different values. For instance, GridLayoutManager overrides this value to handle the case + * where if it is vertical and has 3 columns but only 2 items, it should still measure its + * width to fit 3 items, not 2.
  8. + *
  9. Any following on measure call to the RecyclerView will run + * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to + * {@code true} and {@link State#isPreLayout()} set to {@code false}. RecyclerView will + * take care of which views are actually added / removed / moved / changed for animations so + * that the LayoutManager should not worry about them and handle each + * {@link #onLayoutChildren(Recycler, State)} call as if it is the last one. + *
  10. + *
  11. When measure is complete and RecyclerView's + * {@link #onLayout(boolean, int, int, int, int)} method is called, RecyclerView checks + * whether it already did layout calculations during the measure pass and if so, it re-uses + * that information. It may still decide to call {@link #onLayoutChildren(Recycler, State)} + * if the last measure spec was different from the final dimensions or adapter contents + * have changed between the measure call and the layout call.
  12. + *
  13. Finally, animations are calculated and run as usual.
  14. + *
+ * + * @param enabled True if the Layout should be measured by the + * RecyclerView, false if the LayoutManager wants + * to measure itself. + * + * @see #setMeasuredDimension(Rect, int, int) + * @see #isAutoMeasureEnabled() + */ + public void setAutoMeasureEnabled(boolean enabled) { + mAutoMeasure = enabled; + } + + /** + * Returns whether the LayoutManager uses the automatic measurement API or not. + * + * @return True if the LayoutManager is measured by the RecyclerView or + * false if it measures itself. + * + * @see #setAutoMeasureEnabled(boolean) + */ + public boolean isAutoMeasureEnabled() { + return mAutoMeasure; + } + /** * Returns whether this LayoutManager supports automatic item animations. * A LayoutManager wishing to support item animations should obey certain @@ -6283,14 +6671,14 @@ private void addViewInt(View child, int index, boolean disappearing) { final ViewHolder holder = getChildViewHolderInt(child); if (disappearing || holder.isRemoved()) { // these views will be hidden at the end of the layout pass. - mRecyclerView.mState.addToDisappearingList(child); + mRecyclerView.mViewInfoStore.addToDisappearedInLayout(holder); } else { // This may look like unnecessary but may happen if layout manager supports // predictive layouts and adapter removed then re-added the same item. // In this case, added version will be visible in the post layout (because add is // deferred) but RV will still bind it to the same View. // So if a View re-appears in post layout pass, remove it from disappearing list. - mRecyclerView.mState.removeFromDisappearingList(child); + mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(holder); } final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (holder.wasReturnedFromScrap() || holder.isScrap()) { @@ -6403,6 +6791,36 @@ public int getItemViewType(View view) { return getChildViewHolderInt(view).getItemViewType(); } + /** + * Traverses the ancestors of the given view and returns the item view that contains it + * and also a direct child of the LayoutManager. + *

+ * Note that this method may return null if the view is a child of the RecyclerView but + * not a child of the LayoutManager (e.g. running a disappear animation). + * + * @param view The view that is a descendant of the LayoutManager. + * + * @return The direct child of the LayoutManager which contains the given view or null if + * the provided view is not a descendant of this LayoutManager. + * + * @see RecyclerView#getChildViewHolder(View) + * @see RecyclerView#findContainingViewHolder(View) + */ + @Nullable + public View findContainingItemView(View view) { + if (mRecyclerView == null) { + return null; + } + View found = mRecyclerView.findContainingItemView(view); + if (found == null) { + return null; + } + if (mChildHelper.isHidden(found)) { + return null; + } + return found; + } + /** * Finds the view which represents the given adapter position. *

@@ -6492,9 +6910,9 @@ private void detachViewInternal(int index, View view) { public void attachView(View child, int index, LayoutParams lp) { ViewHolder vh = getChildViewHolderInt(child); if (vh.isRemoved()) { - mRecyclerView.mState.addToDisappearingList(child); + mRecyclerView.mViewInfoStore.addToDisappearedInLayout(vh); } else { - mRecyclerView.mState.removeFromDisappearingList(child); + mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(vh); } mChildHelper.attachViewToParent(child, index, lp, vh.isRemoved()); if (DISPATCH_TEMP_DETACH) { @@ -6621,13 +7039,49 @@ public View getChildAt(int index) { return mChildHelper != null ? mChildHelper.getChildAt(index) : null; } + /** + * Return the width measurement spec mode of the RecyclerView. + *

+ * This value is set only if the LayoutManager opts into the auto measure api via + * {@link #setAutoMeasureEnabled(boolean)}. + *

+ * When RecyclerView is running a layout, this value is always set to + * {@link MeasureSpec#EXACTLY} even if it was measured with a different spec mode. + * + * @return Width measure spec mode. + * + * @see MeasureSpec#getMode(int) + * @see View#onMeasure(int, int) + */ + public int getWidthMode() { + return MeasureSpec.getMode(mWidthSpec); + } + + /** + * Return the height measurement spec mode of the RecyclerView. + *

+ * This value is set only if the LayoutManager opts into the auto measure api via + * {@link #setAutoMeasureEnabled(boolean)}. + *

+ * When RecyclerView is running a layout, this value is always set to + * {@link MeasureSpec#EXACTLY} even if it was measured with a different spec mode. + * + * @return Height measure spec mode. + * + * @see MeasureSpec#getMode(int) + * @see View#onMeasure(int, int) + */ + public int getHeightMode() { + return MeasureSpec.getMode(mHeightSpec); + } + /** * Return the width of the parent RecyclerView * * @return Width in pixels */ public int getWidth() { - return mRecyclerView != null ? mRecyclerView.getWidth() : 0; + return MeasureSpec.getSize(mWidthSpec); } /** @@ -6636,7 +7090,7 @@ public int getWidth() { * @return Height in pixels */ public int getHeight() { - return mRecyclerView != null ? mRecyclerView.getHeight() : 0; + return MeasureSpec.getSize(mHeightSpec); } /** @@ -6792,7 +7246,7 @@ public void ignoreView(View view) { } final ViewHolder vh = getChildViewHolderInt(view); vh.addFlags(ViewHolder.FLAG_IGNORE); - mRecyclerView.mState.onViewIgnored(vh); + mRecyclerView.mViewInfoStore.removeViewHolder(vh); } /** @@ -6834,13 +7288,14 @@ private void scrapOrRecycleView(Recycler recycler, int index, View view) { } return; } - if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !viewHolder.isChanged() && + if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !mRecyclerView.mAdapter.hasStableIds()) { removeViewAt(index); recycler.recycleViewHolderInternal(viewHolder); } else { detachViewAt(index); recycler.scrapView(view); + mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); } } @@ -6901,14 +7356,85 @@ public void measureChild(View child, int widthUsed, int heightUsed) { final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); widthUsed += insets.left + insets.right; heightUsed += insets.top + insets.bottom; - - final int widthSpec = getChildMeasureSpec(getWidth(), + final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), getPaddingLeft() + getPaddingRight() + widthUsed, lp.width, canScrollHorizontally()); - final int heightSpec = getChildMeasureSpec(getHeight(), + final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), getPaddingTop() + getPaddingBottom() + heightUsed, lp.height, canScrollVertically()); - child.measure(widthSpec, heightSpec); + if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { + child.measure(widthSpec, heightSpec); + } + } + + /** + * RecyclerView internally does its own View measurement caching which should help with + * WRAP_CONTENT. + *

+ * Use this method if the View is already measured once in this layout pass. + */ + boolean shouldReMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) { + return !mMeasurementCacheEnabled + || !isMeasurementUpToDate(child.getMeasuredWidth(), widthSpec, lp.width) + || !isMeasurementUpToDate(child.getMeasuredHeight(), heightSpec, lp.height); + } + + // we may consider making this public + /** + * RecyclerView internally does its own View measurement caching which should help with + * WRAP_CONTENT. + *

+ * Use this method if the View is not yet measured and you need to decide whether to + * measure this View or not. + */ + boolean shouldMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) { + return child.isLayoutRequested() + || !mMeasurementCacheEnabled + || !isMeasurementUpToDate(child.getWidth(), widthSpec, lp.width) + || !isMeasurementUpToDate(child.getHeight(), heightSpec, lp.height); + } + + /** + * In addition to the View Framework's measurement cache, RecyclerView uses its own + * additional measurement cache for its children to avoid re-measuring them when not + * necessary. It is on by default but it can be turned off via + * {@link #setMeasurementCacheEnabled(boolean)}. + * + * @return True if measurement cache is enabled, false otherwise. + * + * @see #setMeasurementCacheEnabled(boolean) + */ + public boolean isMeasurementCacheEnabled() { + return mMeasurementCacheEnabled; + } + + /** + * Sets whether RecyclerView should use its own measurement cache for the children. This is + * a more aggressive cache than the framework uses. + * + * @param measurementCacheEnabled True to enable the measurement cache, false otherwise. + * + * @see #isMeasurementCacheEnabled() + */ + public void setMeasurementCacheEnabled(boolean measurementCacheEnabled) { + mMeasurementCacheEnabled = measurementCacheEnabled; + } + + private static boolean isMeasurementUpToDate(int childSize, int spec, int dimension) { + final int specMode = MeasureSpec.getMode(spec); + final int specSize = MeasureSpec.getSize(spec); + if (dimension > 0 && childSize != dimension) { + return false; + } + switch (specMode) { + case MeasureSpec.UNSPECIFIED: + return true; + case MeasureSpec.AT_MOST: + return specSize >= childSize; + case MeasureSpec.EXACTLY: + return specSize == childSize; + } + return false; } /** @@ -6930,40 +7456,43 @@ public void measureChildWithMargins(View child, int widthUsed, int heightUsed) { widthUsed += insets.left + insets.right; heightUsed += insets.top + insets.bottom; - final int widthSpec = getChildMeasureSpec(getWidth(), + final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin + widthUsed, lp.width, canScrollHorizontally()); - final int heightSpec = getChildMeasureSpec(getHeight(), + final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin + heightUsed, lp.height, canScrollVertically()); - child.measure(widthSpec, heightSpec); + if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { + child.measure(widthSpec, heightSpec); + } } /** * Calculate a MeasureSpec value for measuring a child view in one dimension. * * @param parentSize Size of the parent view where the child will be placed - * @param padding Total space currently consumed by other elements of parent - * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT. + * @param padding Total space currently consumed by other elements of the parent + * @param childDimension Desired size of the child view, or FILL_PARENT/WRAP_CONTENT. * Generally obtained from the child view's LayoutParams * @param canScroll true if the parent RecyclerView can scroll in this dimension * * @return a MeasureSpec value for the child view + * @deprecated use {@link #getChildMeasureSpec(int, int, int, int, boolean)} */ + @Deprecated public static int getChildMeasureSpec(int parentSize, int padding, int childDimension, boolean canScroll) { int size = Math.max(0, parentSize - padding); int resultSize = 0; int resultMode = 0; - if (canScroll) { if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else { - // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap + // FILL_PARENT can't be applied since we can scroll in this dimension, wrap // instead using UNSPECIFIED. resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; @@ -6974,6 +7503,7 @@ public static int getChildMeasureSpec(int parentSize, int padding, int childDime resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.FILL_PARENT) { resultSize = size; + // TODO this should be my spec. resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size; @@ -6983,6 +7513,63 @@ public static int getChildMeasureSpec(int parentSize, int padding, int childDime return MeasureSpec.makeMeasureSpec(resultSize, resultMode); } + /** + * Calculate a MeasureSpec value for measuring a child view in one dimension. + * + * @param parentSize Size of the parent view where the child will be placed + * @param parentMode The measurement spec mode of the parent + * @param padding Total space currently consumed by other elements of parent + * @param childDimension Desired size of the child view, or FILL_PARENT/WRAP_CONTENT. + * Generally obtained from the child view's LayoutParams + * @param canScroll true if the parent RecyclerView can scroll in this dimension + * + * @return a MeasureSpec value for the child view + */ + public static int getChildMeasureSpec(int parentSize, int parentMode, int padding, + int childDimension, boolean canScroll) { + int size = Math.max(0, parentSize - padding); + int resultSize = 0; + int resultMode = 0; + if (canScroll) { + if (childDimension >= 0) { + resultSize = childDimension; + resultMode = MeasureSpec.EXACTLY; + } else if (childDimension == LayoutParams.FILL_PARENT){ + switch (parentMode) { + case MeasureSpec.AT_MOST: + case MeasureSpec.EXACTLY: + resultSize = size; + resultMode = parentMode; + break; + case MeasureSpec.UNSPECIFIED: + resultSize = 0; + resultMode = MeasureSpec.UNSPECIFIED; + break; + } + } else if (childDimension == LayoutParams.WRAP_CONTENT) { + resultSize = 0; + resultMode = MeasureSpec.UNSPECIFIED; + } + } else { + if (childDimension >= 0) { + resultSize = childDimension; + resultMode = MeasureSpec.EXACTLY; + } else if (childDimension == LayoutParams.FILL_PARENT) { + resultSize = size; + resultMode = parentMode; + } else if (childDimension == LayoutParams.WRAP_CONTENT) { + resultSize = size; + if (parentMode == MeasureSpec.AT_MOST || parentMode == MeasureSpec.EXACTLY) { + resultMode = MeasureSpec.AT_MOST; + } else { + resultMode = MeasureSpec.UNSPECIFIED; + } + + } + } + return MeasureSpec.makeMeasureSpec(resultSize, resultMode); + } + /** * Returns the measured width of the given child, plus the additional size of * any insets applied by {@link ItemDecoration ItemDecorations}. @@ -7242,8 +7829,8 @@ public boolean requestChildRectangleOnScreen(RecyclerView parent, View child, Re final int parentTop = getPaddingTop(); final int parentRight = getWidth() - getPaddingRight(); final int parentBottom = getHeight() - getPaddingBottom(); - final int childLeft = child.getLeft() + rect.left; - final int childTop = child.getTop() + rect.top; + final int childLeft = child.getLeft() + rect.left - child.getScrollX(); + final int childTop = child.getTop() + rect.top - child.getScrollY(); final int childRight = childLeft + rect.width(); final int childBottom = childTop + rect.height(); @@ -7898,28 +8485,81 @@ public boolean performAccessibilityActionForItem(Recycler recycler, State state, } /** - * Some general properties that a LayoutManager may want to use. + * Parse the xml attributes to get the most common properties used by layout managers. + * + * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation + * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount + * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout + * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd + * + * @return an object containing the properties as specified in the attrs. */ - public static class Properties { - /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation */ - public int orientation; - /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount */ - public int spanCount; - /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout */ - public boolean reverseLayout; - /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd */ - public boolean stackFromEnd; + public static Properties getProperties(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + Properties properties = new Properties(); + properties.orientation = VERTICAL; + properties.spanCount = 1; + properties.reverseLayout = false; + properties.stackFromEnd = false; + return properties; } - } - /** - * An ItemDecoration allows the application to add a special drawing and layout offset - * to specific item views from the adapter's data set. This can be useful for drawing dividers - * between items, highlights, visual grouping boundaries and more. - * - *

All ItemDecorations are drawn in the order they were added, before the item - * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()} - * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView, + void setExactMeasureSpecsFrom(RecyclerView recyclerView) { + setMeasureSpecs( + MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY) + ); + } + + /** + * Internal API to allow LayoutManagers to be measured twice. + *

+ * This is not public because LayoutManagers should be able to handle their layouts in one + * pass but it is very convenient to make existing LayoutManagers support wrapping content + * when both orientations are undefined. + *

+ * This API will be removed after default LayoutManagers properly implement wrap content in + * non-scroll orientation. + */ + boolean shouldMeasureTwice() { + return false; + } + + boolean hasFlexibleChildInBothOrientations() { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final ViewGroup.LayoutParams lp = child.getLayoutParams(); + if (lp.width < 0 && lp.height < 0) { + return true; + } + } + return false; + } + + /** + * Some general properties that a LayoutManager may want to use. + */ + public static class Properties { + /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation */ + public int orientation; + /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount */ + public int spanCount; + /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout */ + public boolean reverseLayout; + /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd */ + public boolean stackFromEnd; + } + } + + /** + * An ItemDecoration allows the application to add a special drawing and layout offset + * to specific item views from the adapter's data set. This can be useful for drawing dividers + * between items, highlights, visual grouping boundaries and more. + * + *

All ItemDecorations are drawn in the order they were added, before the item + * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()} + * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView, * RecyclerView.State)}.

*/ public static abstract class ItemDecoration { @@ -8077,16 +8717,12 @@ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { /** - * An OnScrollListener can be set on a RecyclerView to receive messages - * when a scrolling event has occurred on that RecyclerView. - * - * @see RecyclerView#setOnScrollListener(OnScrollListener) and - * RecyclerView#addOnScrollListener(OnScrollListener) + * An OnScrollListener can be added to a RecyclerView to receive messages when a scrolling event + * has occurred on that RecyclerView. + *

+ * @see RecyclerView#addOnScrollListener(OnScrollListener) + * @see RecyclerView#clearOnChildAttachStateChangeListeners() * - * If you are planning to have several listeners at the same time, use - * RecyclerView#addOnScrollListener. If there will be only one listener at the time and you - * want your components to be able to easily replace the listener use - * RecyclerView#setOnScrollListener. */ public abstract static class OnScrollListener { /** @@ -8219,12 +8855,6 @@ public static abstract class ViewHolder { */ static final int FLAG_RETURNED_FROM_SCRAP = 1 << 5; - /** - * This ViewHolder's contents have changed. This flag is used as an indication that - * change animations may be used, if supported by the ItemAnimator. - */ - static final int FLAG_CHANGED = 1 << 6; - /** * This ViewHolder is fully managed by the LayoutManager. We do not scrap, recycle or remove * it unless LayoutManager is replaced. @@ -8252,6 +8882,31 @@ public static abstract class ViewHolder { */ static final int FLAG_ADAPTER_FULLUPDATE = 1 << 10; + /** + * Used by ItemAnimator when a ViewHolder's position changes + */ + static final int FLAG_MOVED = 1 << 11; + + /** + * Used by ItemAnimator when a ViewHolder appears in pre-layout + */ + static final int FLAG_APPEARED_IN_PRE_LAYOUT = 1 << 12; + + /** + * Used when a ViewHolder starts the layout pass as a hidden ViewHolder but is re-used from + * hidden list (as if it was scrap) without being recycled in between. + * + * When a ViewHolder is hidden, there are 2 paths it can be re-used: + * a) Animation ends, view is recycled and used from the recycle pool. + * b) LayoutManager asks for the View for that position while the ViewHolder is hidden. + * + * This flag is used to represent "case b" where the ViewHolder is reused without being + * recycled (thus "bounced" from the hidden list). This state requires special handling + * because the ViewHolder must be added to pre layout maps for animations as if it was + * already there. + */ + static final int FLAG_BOUNCED_FROM_HIDDEN_LIST = 1 << 13; + private int mFlags; private static final List FULLUPDATE_PAYLOADS = Collections.EMPTY_LIST; @@ -8264,6 +8919,8 @@ public static abstract class ViewHolder { // If non-null, view is currently considered scrap and may be reused for other data by the // scrap container. private Recycler mScrapContainer = null; + // Keeps whether this ViewHolder lives in Change scrap or Attached scrap + private boolean mInChangeScrap = false; // Saves isImportantForAccessibility value for the view item while it's in hidden state and // marked as unimportant for accessibility. @@ -8444,8 +9101,9 @@ void stopIgnoring() { mFlags = mFlags & ~FLAG_IGNORE; } - void setScrapContainer(Recycler recycler) { + void setScrapContainer(Recycler recycler, boolean isChangeScrap) { mScrapContainer = recycler; + mInChangeScrap = isChangeScrap; } boolean isInvalid() { @@ -8456,10 +9114,6 @@ boolean needsUpdate() { return (mFlags & FLAG_UPDATE) != 0; } - boolean isChanged() { - return (mFlags & FLAG_CHANGED) != 0; - } - boolean isBound() { return (mFlags & FLAG_BOUND) != 0; } @@ -8563,16 +9217,18 @@ public String toString() { final StringBuilder sb = new StringBuilder("ViewHolder{" + Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId + ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition); - if (isScrap()) sb.append(" scrap"); + if (isScrap()) { + sb.append(" scrap ") + .append(mInChangeScrap ? "[changeScrap]" : "[attachedScrap]"); + } if (isInvalid()) sb.append(" invalid"); if (!isBound()) sb.append(" unbound"); if (needsUpdate()) sb.append(" update"); if (isRemoved()) sb.append(" removed"); if (shouldIgnore()) sb.append(" ignored"); - if (isChanged()) sb.append(" changed"); if (isTmpDetached()) sb.append(" tmpDetached"); if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")"); - if (isAdapterPositionUnknown()) sb.append("undefined adapter position"); + if (isAdapterPositionUnknown()) sb.append(" undefined adapter position"); if (itemView.getParent() == null) sb.append(" no parent"); sb.append("}"); @@ -8635,6 +9291,10 @@ private boolean shouldBeKeptAsChild() { private boolean doesTransientStatePreventRecycling() { return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && ViewCompat.hasTransientState(itemView); } + + boolean isUpdated() { + return (mFlags & FLAG_UPDATE) != 0; + } } private int getAdapterPositionFor(ViewHolder viewHolder) { @@ -8769,7 +9429,7 @@ public boolean isItemRemoved() { * @return true if the item the view corresponds to was changed in the data set */ public boolean isItemChanged() { - return mViewHolder.isChanged(); + return mViewHolder.isUpdated(); } /** @@ -9304,7 +9964,11 @@ public void notifyItemMoved(int fromPosition, int toPosition) { } } - static class SavedState extends android.view.View.BaseSavedState { + /** + * This is public so that the CREATOR can be access on cold launch. + * @hide + */ + public static class SavedState extends android.view.View.BaseSavedState { Parcelable mLayoutState; @@ -9357,17 +10021,28 @@ public SavedState[] newArray(int size) { * data between your components without needing to manage their lifecycles.

*/ public static class State { + static final int STEP_START = 1; + static final int STEP_LAYOUT = 1 << 1; + static final int STEP_ANIMATIONS = 1 << 2; + + void assertLayoutStep(int accepted) { + if ((accepted & mLayoutStep) == 0) { + throw new IllegalStateException("Layout state should be one of " + + Integer.toBinaryString(accepted) + " but it is " + + Integer.toBinaryString(mLayoutStep)); + } + } + + @IntDef(flag = true, value = { + STEP_START, STEP_LAYOUT, STEP_ANIMATIONS + }) + @Retention(RetentionPolicy.SOURCE) + @interface LayoutState {} private int mTargetPosition = RecyclerView.NO_POSITION; - ArrayMap mPreLayoutHolderMap = - new ArrayMap(); - ArrayMap mPostLayoutHolderMap = - new ArrayMap(); - // nullable - ArrayMap mOldChangedHolders = new ArrayMap(); - // we use this like a set - final List mDisappearingViewsInLayoutPass = new ArrayList(); + @LayoutState + private int mLayoutStep = STEP_START; private SparseArray mData; @@ -9395,6 +10070,10 @@ public static class State { private boolean mRunPredictiveAnimations = false; + private boolean mTrackOldChangeHolders = false; + + private boolean mIsMeasuring = false; + State reset() { mTargetPosition = RecyclerView.NO_POSITION; if (mData != null) { @@ -9402,9 +10081,32 @@ State reset() { } mItemCount = 0; mStructureChanged = false; + mIsMeasuring = false; return this; } + /** + * Returns true if the RecyclerView is currently measuring the layout. This value is + * {@code true} only if the LayoutManager opted into the auto measure API and RecyclerView + * has non-exact measurement specs. + *

+ * Note that if the LayoutManager supports predictive animations and it is calculating the + * pre-layout step, this value will be {@code false} even if the RecyclerView is in + * {@code onMeasure} call. This is because pre-layout means the previous state of the + * RecyclerView and measurements made for that state cannot change the RecyclerView's size. + * LayoutManager is always guaranteed to receive another call to + * {@link LayoutManager#onLayoutChildren(Recycler, State)} when this happens. + * + * @return True if the RecyclerView is currently calculating its bounds, false otherwise. + */ + public boolean isMeasuring() { + return mIsMeasuring; + } + + /** + * Returns true if + * @return + */ public boolean isPreLayout() { return mInPreLayout; } @@ -9531,45 +10233,10 @@ public int getItemCount() { mItemCount; } - void onViewRecycled(ViewHolder holder) { - mPreLayoutHolderMap.remove(holder); - mPostLayoutHolderMap.remove(holder); - if (mOldChangedHolders != null) { - removeFrom(mOldChangedHolders, holder); - } - mDisappearingViewsInLayoutPass.remove(holder.itemView); - // holder cannot be in new list. - } - - public void onViewIgnored(ViewHolder holder) { - onViewRecycled(holder); - } - - private void removeFrom(ArrayMap holderMap, ViewHolder holder) { - for (int i = holderMap.size() - 1; i >= 0; i --) { - if (holder == holderMap.valueAt(i)) { - holderMap.removeAt(i); - return; - } - } - } - - void removeFromDisappearingList(View child) { - mDisappearingViewsInLayoutPass.remove(child); - } - - void addToDisappearingList(View child) { - if (!mDisappearingViewsInLayoutPass.contains(child)) { - mDisappearingViewsInLayoutPass.add(child); - } - } - @Override public String toString() { return "State{" + "mTargetPosition=" + mTargetPosition + - ", mPreLayoutHolderMap=" + mPreLayoutHolderMap + - ", mPostLayoutHolderMap=" + mPostLayoutHolderMap + ", mData=" + mData + ", mItemCount=" + mItemCount + ", mPreviousLayoutItemCount=" + mPreviousLayoutItemCount + @@ -9592,71 +10259,21 @@ public String toString() { private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener { @Override - public void onRemoveFinished(ViewHolder item) { - item.setIsRecyclable(true); - if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) { - removeDetachedView(item.itemView, false); - } - } - - @Override - public void onAddFinished(ViewHolder item) { - item.setIsRecyclable(true); - if (!item.shouldBeKeptAsChild()) { - removeAnimatingView(item.itemView); - } - } - - @Override - public void onMoveFinished(ViewHolder item) { - item.setIsRecyclable(true); - if (!item.shouldBeKeptAsChild()) { - removeAnimatingView(item.itemView); - } - } - - @Override - public void onChangeFinished(ViewHolder item) { + public void onAnimationFinished(ViewHolder item) { item.setIsRecyclable(true); - /** - * We check both shadowed and shadowing because a ViewHolder may get both roles at the - * same time. - * - * Assume this flow: - * item X is represented by VH_1. Then itemX changes, so we create VH_2 . - * RV sets the following and calls item animator: - * VH_1.shadowed = VH_2; - * VH_1.mChanged = true; - * VH_2.shadowing =VH_1; - * - * Then, before the first change finishes, item changes again so we create VH_3. - * RV sets the following and calls item animator: - * VH_2.shadowed = VH_3 - * VH_2.mChanged = true - * VH_3.shadowing = VH_2 - * - * Because VH_2 already has an animation, it will be cancelled. At this point VH_2 has - * both shadowing and shadowed fields set. Shadowing information is obsolete now - * because the first animation where VH_2 is newViewHolder is not valid anymore. - * We ended up in this case because VH_2 played both roles. On the other hand, - * we DO NOT want to clear its changed flag. - * - * If second change was simply reverting first change, we would find VH_1 in - * {@link Recycler#getScrapViewForPosition(int, int, boolean)} and recycle it before - * re-using - */ if (item.mShadowedHolder != null && item.mShadowingHolder == null) { // old vh item.mShadowedHolder = null; - item.setFlags(~ViewHolder.FLAG_CHANGED, item.mFlags); } // always null this because an OldViewHolder can never become NewViewHolder w/o being // recycled. item.mShadowingHolder = null; if (!item.shouldBeKeptAsChild()) { - removeAnimatingView(item.itemView); + if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) { + removeDetachedView(item.itemView, false); + } } } - }; + } /** * This class defines the animations that take place on items as changes are made @@ -9664,22 +10281,78 @@ public void onChangeFinished(ViewHolder item) { * * Subclasses of ItemAnimator can be used to implement custom animations for actions on * ViewHolder items. The RecyclerView will manage retaining these items while they - * are being animated, but implementors must call the appropriate "Starting" - * ({@link #dispatchRemoveStarting(ViewHolder)}, {@link #dispatchMoveStarting(ViewHolder)}, - * {@link #dispatchChangeStarting(ViewHolder, boolean)}, or - * {@link #dispatchAddStarting(ViewHolder)}) - * and "Finished" ({@link #dispatchRemoveFinished(ViewHolder)}, - * {@link #dispatchMoveFinished(ViewHolder)}, - * {@link #dispatchChangeFinished(ViewHolder, boolean)}, - * or {@link #dispatchAddFinished(ViewHolder)}) methods when each item animation is - * being started and ended. + * are being animated, but implementors must call {@link #dispatchAnimationFinished(ViewHolder)} + * when a ViewHolder's animation is finished. In other words, there must be a matching + * {@link #dispatchAnimationFinished(ViewHolder)} call for each + * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) animateAppearance()}, + * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) + * animateChange()} + * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) animatePersistence()}, + * and + * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) + * animateDisappearance()} call. * - *

By default, RecyclerView uses {@link DefaultItemAnimator}

+ *

By default, RecyclerView uses {@link DefaultItemAnimator}.

* * @see #setItemAnimator(ItemAnimator) */ + @SuppressWarnings("UnusedParameters") public static abstract class ItemAnimator { + /** + * The Item represented by this ViewHolder is updated. + *

+ * @see #recordPreLayoutInformation(State, ViewHolder, int, List) + */ + public static final int FLAG_CHANGED = ViewHolder.FLAG_UPDATE; + + /** + * The Item represented by this ViewHolder is removed from the adapter. + *

+ * @see #recordPreLayoutInformation(State, ViewHolder, int, List) + */ + public static final int FLAG_REMOVED = ViewHolder.FLAG_REMOVED; + + /** + * Adapter {@link Adapter#notifyDataSetChanged()} has been called and the content + * represented by this ViewHolder is invalid. + *

+ * @see #recordPreLayoutInformation(State, ViewHolder, int, List) + */ + public static final int FLAG_INVALIDATED = ViewHolder.FLAG_INVALID; + + /** + * The position of the Item represented by this ViewHolder has been changed. This flag is + * not bound to {@link Adapter#notifyItemMoved(int, int)}. It might be set in response to + * any adapter change that may have a side effect on this item. (e.g. The item before this + * one has been removed from the Adapter). + *

+ * @see #recordPreLayoutInformation(State, ViewHolder, int, List) + */ + public static final int FLAG_MOVED = ViewHolder.FLAG_MOVED; + + /** + * This ViewHolder was not laid out but has been added to the layout in pre-layout state + * by the {@link LayoutManager}. This means that the item was already in the Adapter but + * invisible and it may become visible in the post layout phase. LayoutManagers may prefer + * to add new items in pre-layout to specify their virtual location when they are invisible + * (e.g. to specify the item should animate in from below the visible area). + *

+ * @see #recordPreLayoutInformation(State, ViewHolder, int, List) + */ + public static final int FLAG_APPEARED_IN_PRE_LAYOUT + = ViewHolder.FLAG_APPEARED_IN_PRE_LAYOUT; + + /** + * The set of flags that might be passed to + * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. + */ + @IntDef(flag=true, value={ + FLAG_CHANGED, FLAG_REMOVED, FLAG_MOVED, FLAG_INVALIDATED, + FLAG_APPEARED_IN_PRE_LAYOUT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AdapterChanges {} private ItemAnimatorListener mListener = null; private ArrayList mFinishedListeners = new ArrayList(); @@ -9689,8 +10362,6 @@ public static abstract class ItemAnimator { private long mMoveDuration = 250; private long mChangeDuration = 250; - private boolean mSupportsChangeAnimations = true; - /** * Gets the current duration for which all move animations will run. * @@ -9763,35 +10434,6 @@ public void setChangeDuration(long changeDuration) { mChangeDuration = changeDuration; } - /** - * Returns whether this ItemAnimator supports animations of change events. - * - * @return true if change animations are supported, false otherwise - */ - public boolean getSupportsChangeAnimations() { - return mSupportsChangeAnimations; - } - - /** - * Sets whether this ItemAnimator supports animations of item change events. - * If you set this property to false, actions on the data set which change the - * contents of items will not be animated. What those animations are is left - * up to the discretion of the ItemAnimator subclass, in its - * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} implementation. - * The value of this property is true by default. - * - * @see Adapter#notifyItemChanged(int) - * @see Adapter#notifyItemRangeChanged(int, int) - * - * @param supportsChangeAnimations true if change animations are supported by - * this ItemAnimator, false otherwise. If the property is false, the ItemAnimator - * will not receive a call to - * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} when changes occur. - */ - public void setSupportsChangeAnimations(boolean supportsChangeAnimations) { - mSupportsChangeAnimations = supportsChangeAnimations; - } - /** * Internal only: * Sets the listener that must be called when the animator is finished @@ -9805,244 +10447,379 @@ void setListener(ItemAnimatorListener listener) { } /** - * Called when there are pending animations waiting to be started. This state - * is governed by the return values from {@link #animateAdd(ViewHolder) animateAdd()}, - * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, and - * {@link #animateRemove(ViewHolder) animateRemove()}, which inform the - * RecyclerView that the ItemAnimator wants to be called later to start the - * associated animations. runPendingAnimations() will be scheduled to be run - * on the next frame. + * Called by the RecyclerView before the layout begins. Item animator should record + * necessary information about the View before it is potentially rebound, moved or removed. + *

+ * The data returned from this method will be passed to the related animate** + * methods. + *

+ * Note that this method may be called after pre-layout phase if LayoutManager adds new + * Views to the layout in pre-layout pass. + *

+ * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of + * the View and the adapter change flags. + * + * @param state The current State of RecyclerView which includes some useful data + * about the layout that will be calculated. + * @param viewHolder The ViewHolder whose information should be recorded. + * @param changeFlags Additional information about what changes happened in the Adapter + * about the Item represented by this ViewHolder. For instance, if + * item is deleted from the adapter, {@link #FLAG_REMOVED} will be set. + * @param payloads The payload list that was previously passed to + * {@link Adapter#notifyItemChanged(int, Object)} or + * {@link Adapter#notifyItemRangeChanged(int, int, Object)}. + * + * @return An ItemHolderInfo instance that preserves necessary information about the + * ViewHolder. This object will be passed back to related animate** methods + * after layout is complete. + * + * @see #recordPostLayoutInformation(State, ViewHolder) + * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) + * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) + * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) + * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) + */ + public @NonNull ItemHolderInfo recordPreLayoutInformation(@NonNull State state, + @NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags, + @NonNull List payloads) { + return obtainHolderInfo().setFrom(viewHolder); + } + + /** + * Called by the RecyclerView after the layout is complete. Item animator should record + * necessary information about the View's final state. + *

+ * The data returned from this method will be passed to the related animate** + * methods. + *

+ * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of + * the View. + * + * @param state The current State of RecyclerView which includes some useful data about + * the layout that will be calculated. + * @param viewHolder The ViewHolder whose information should be recorded. + * + * @return An ItemHolderInfo that preserves necessary information about the ViewHolder. + * This object will be passed back to related animate** methods when + * RecyclerView decides how items should be animated. + * + * @see #recordPreLayoutInformation(State, ViewHolder, int, List) + * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) + * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) + * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) + * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) */ - abstract public void runPendingAnimations(); + public @NonNull ItemHolderInfo recordPostLayoutInformation(@NonNull State state, + @NonNull ViewHolder viewHolder) { + return obtainHolderInfo().setFrom(viewHolder); + } /** - * Called when an item is removed from the RecyclerView. Implementors can choose - * whether and how to animate that change, but must always call - * {@link #dispatchRemoveFinished(ViewHolder)} when done, either - * immediately (if no animation will occur) or after the animation actually finishes. - * The return value indicates whether an animation has been set up and whether the - * ItemAnimator's {@link #runPendingAnimations()} method should be called at the - * next opportunity. This mechanism allows ItemAnimator to set up individual animations - * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()}, - * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, - * {@link #animateRemove(ViewHolder) animateRemove()}, and - * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one, - * then start the animations together in the later call to {@link #runPendingAnimations()}. - * - *

This method may also be called for disappearing items which continue to exist in the - * RecyclerView, but for which the system does not have enough information to animate - * them out of view. In that case, the default animation for removing items is run - * on those items as well.

- * - * @param holder The item that is being removed. + * Called by the RecyclerView when a ViewHolder has disappeared from the layout. + *

+ * This means that the View was a child of the LayoutManager when layout started but has + * been removed by the LayoutManager. It might have been removed from the adapter or simply + * become invisible due to other factors. You can distinguish these two cases by checking + * the change flags that were passed to + * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. + *

+ * Note that when a ViewHolder both changes and disappears in the same layout pass, the + * animation callback method which will be called by the RecyclerView depends on the + * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the + * LayoutManager's decision whether to layout the changed version of a disappearing + * ViewHolder or not. RecyclerView will call + * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) + * animateChange} instead of {@code animateDisappearance} if and only if the ItemAnimator + * returns {@code false} from + * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the + * LayoutManager lays out a new disappearing view that holds the updated information. + * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views. + *

+ * If LayoutManager supports predictive animations, it might provide a target disappear + * location for the View by laying it out in that location. When that happens, + * RecyclerView will call {@link #recordPostLayoutInformation(State, ViewHolder)} and the + * response of that call will be passed to this method as the postLayoutInfo. + *

+ * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation + * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it + * decides not to animate the view). + * + * @param viewHolder The ViewHolder which should be animated + * @param preLayoutInfo The information that was returned from + * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. + * @param postLayoutInfo The information that was returned from + * {@link #recordPostLayoutInformation(State, ViewHolder)}. Might be + * null if the LayoutManager did not layout the item. + * * @return true if a later call to {@link #runPendingAnimations()} is requested, * false otherwise. */ - abstract public boolean animateRemove(ViewHolder holder); + public abstract boolean animateDisappearance(@NonNull ViewHolder viewHolder, + @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo); /** - * Called when an item is added to the RecyclerView. Implementors can choose - * whether and how to animate that change, but must always call - * {@link #dispatchAddFinished(ViewHolder)} when done, either - * immediately (if no animation will occur) or after the animation actually finishes. - * The return value indicates whether an animation has been set up and whether the - * ItemAnimator's {@link #runPendingAnimations()} method should be called at the - * next opportunity. This mechanism allows ItemAnimator to set up individual animations - * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()}, - * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, - * {@link #animateRemove(ViewHolder) animateRemove()}, and - * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one, - * then start the animations together in the later call to {@link #runPendingAnimations()}. - * - *

This method may also be called for appearing items which were already in the - * RecyclerView, but for which the system does not have enough information to animate - * them into view. In that case, the default animation for adding items is run - * on those items as well.

+ * Called by the RecyclerView when a ViewHolder is added to the layout. + *

+ * In detail, this means that the ViewHolder was not a child when the layout started + * but has been added by the LayoutManager. It might be newly added to the adapter or + * simply become visible due to other factors. + *

+ * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation + * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it + * decides not to animate the view). + * + * @param viewHolder The ViewHolder which should be animated + * @param preLayoutInfo The information that was returned from + * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. + * Might be null if Item was just added to the adapter or + * LayoutManager does not support predictive animations or it could + * not predict that this ViewHolder will become visible. + * @param postLayoutInfo The information that was returned from {@link + * #recordPreLayoutInformation(State, ViewHolder, int, List)}. * - * @param holder The item that is being added. * @return true if a later call to {@link #runPendingAnimations()} is requested, * false otherwise. */ - abstract public boolean animateAdd(ViewHolder holder); + public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder, + @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); /** - * Called when an item is moved in the RecyclerView. Implementors can choose - * whether and how to animate that change, but must always call - * {@link #dispatchMoveFinished(ViewHolder)} when done, either - * immediately (if no animation will occur) or after the animation actually finishes. - * The return value indicates whether an animation has been set up and whether the - * ItemAnimator's {@link #runPendingAnimations()} method should be called at the - * next opportunity. This mechanism allows ItemAnimator to set up individual animations - * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()}, - * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, - * {@link #animateRemove(ViewHolder) animateRemove()}, and - * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one, - * then start the animations together in the later call to {@link #runPendingAnimations()}. + * Called by the RecyclerView when a ViewHolder is present in both before and after the + * layout and RecyclerView has not received a {@link Adapter#notifyItemChanged(int)} call + * for it or a {@link Adapter#notifyDataSetChanged()} call. + *

+ * This ViewHolder still represents the same data that it was representing when the layout + * started but its position / size may be changed by the LayoutManager. + *

+ * If the Item's layout position didn't change, RecyclerView still calls this method because + * it does not track this information (or does not necessarily know that an animation is + * not required). Your ItemAnimator should handle this case and if there is nothing to + * animate, it should call {@link #dispatchAnimationFinished(ViewHolder)} and return + * false. + *

+ * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation + * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it + * decides not to animate the view). + * + * @param viewHolder The ViewHolder which should be animated + * @param preLayoutInfo The information that was returned from + * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. + * @param postLayoutInfo The information that was returned from {@link + * #recordPreLayoutInformation(State, ViewHolder, int, List)}. * - * @param holder The item that is being moved. * @return true if a later call to {@link #runPendingAnimations()} is requested, * false otherwise. */ - abstract public boolean animateMove(ViewHolder holder, int fromX, int fromY, - int toX, int toY); + public abstract boolean animatePersistence(@NonNull ViewHolder viewHolder, + @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); /** - * Called when an item is changed in the RecyclerView, as indicated by a call to - * {@link Adapter#notifyItemChanged(int)} or - * {@link Adapter#notifyItemRangeChanged(int, int)}. + * Called by the RecyclerView when an adapter item is present both before and after the + * layout and RecyclerView has received a {@link Adapter#notifyItemChanged(int)} call + * for it. This method may also be called when + * {@link Adapter#notifyDataSetChanged()} is called and adapter has stable ids so that + * RecyclerView could still rebind views to the same ViewHolders. If viewType changes when + * {@link Adapter#notifyDataSetChanged()} is called, this method will not be called, + * instead, {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} will be + * called for the new ViewHolder and the old one will be recycled. + *

+ * If this method is called due to a {@link Adapter#notifyDataSetChanged()} call, there is + * a good possibility that item contents didn't really change but it is rebound from the + * adapter. {@link DefaultItemAnimator} will skip animating the View if its location on the + * screen didn't change and your animator should handle this case as well and avoid creating + * unnecessary animations. *

- * Implementers can choose whether and how to animate changes, but must always call - * {@link #dispatchChangeFinished(ViewHolder, boolean)} for each non-null ViewHolder, - * either immediately (if no animation will occur) or after the animation actually finishes. - * The return value indicates whether an animation has been set up and whether the - * ItemAnimator's {@link #runPendingAnimations()} method should be called at the - * next opportunity. This mechanism allows ItemAnimator to set up individual animations - * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()}, - * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, - * {@link #animateRemove(ViewHolder) animateRemove()}, and - * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one, - * then start the animations together in the later call to {@link #runPendingAnimations()}. - * - * @param oldHolder The original item that changed. - * @param newHolder The new item that was created with the changed content. Might be null - * @param fromLeft Left of the old view holder - * @param fromTop Top of the old view holder - * @param toLeft Left of the new view holder - * @param toTop Top of the new view holder + * When an item is updated, ItemAnimator has a chance to ask RecyclerView to keep the + * previous presentation of the item as-is and supply a new ViewHolder for the updated + * presentation (see: {@link #canReuseUpdatedViewHolder(ViewHolder, List)}. + * This is useful if you don't know the contents of the Item and would like + * to cross-fade the old and the new one ({@link DefaultItemAnimator} uses this technique). + *

+ * When you are writing a custom item animator for your layout, it might be more performant + * and elegant to re-use the same ViewHolder and animate the content changes manually. + *

+ * When {@link Adapter#notifyItemChanged(int)} is called, the Item's view type may change. + * If the Item's view type has changed or ItemAnimator returned false for + * this ViewHolder when {@link #canReuseUpdatedViewHolder(ViewHolder, List)} was called, the + * oldHolder and newHolder will be different ViewHolder instances + * which represent the same Item. In that case, only the new ViewHolder is visible + * to the LayoutManager but RecyclerView keeps old ViewHolder attached for animations. + *

+ * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} for each distinct + * ViewHolder when their animation is complete + * (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it decides not to + * animate the view). + *

+ * If oldHolder and newHolder are the same instance, you should call + * {@link #dispatchAnimationFinished(ViewHolder)} only once. + *

+ * Note that when a ViewHolder both changes and disappears in the same layout pass, the + * animation callback method which will be called by the RecyclerView depends on the + * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the + * LayoutManager's decision whether to layout the changed version of a disappearing + * ViewHolder or not. RecyclerView will call + * {@code animateChange} instead of + * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) + * animateDisappearance} if and only if the ItemAnimator returns {@code false} from + * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the + * LayoutManager lays out a new disappearing view that holds the updated information. + * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views. + * + * @param oldHolder The ViewHolder before the layout is started, might be the same + * instance with newHolder. + * @param newHolder The ViewHolder after the layout is finished, might be the same + * instance with oldHolder. + * @param preLayoutInfo The information that was returned from + * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. + * @param postLayoutInfo The information that was returned from {@link + * #recordPreLayoutInformation(State, ViewHolder, int, List)}. + * * @return true if a later call to {@link #runPendingAnimations()} is requested, * false otherwise. */ - abstract public boolean animateChange(ViewHolder oldHolder, - ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop); - + public abstract boolean animateChange(@NonNull ViewHolder oldHolder, + @NonNull ViewHolder newHolder, + @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); - /** - * Method to be called by subclasses when a remove animation is done. - * - * @param item The item which has been removed - */ - public final void dispatchRemoveFinished(ViewHolder item) { - onRemoveFinished(item); - if (mListener != null) { - mListener.onRemoveFinished(item); + @AdapterChanges static int buildAdapterChangeFlagsForAnimations(ViewHolder viewHolder) { + int flags = viewHolder.mFlags & (FLAG_INVALIDATED | FLAG_REMOVED | FLAG_CHANGED); + if (viewHolder.isInvalid()) { + return FLAG_INVALIDATED; + } + if ((flags & FLAG_INVALIDATED) == 0) { + final int oldPos = viewHolder.getOldPosition(); + final int pos = viewHolder.getAdapterPosition(); + if (oldPos != NO_POSITION && pos != NO_POSITION && oldPos != pos){ + flags |= FLAG_MOVED; + } } + return flags; } /** - * Method to be called by subclasses when a move animation is done. - * - * @param item The item which has been moved + * Called when there are pending animations waiting to be started. This state + * is governed by the return values from + * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) + * animateAppearance()}, + * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) + * animateChange()} + * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) + * animatePersistence()}, and + * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) + * animateDisappearance()}, which inform the RecyclerView that the ItemAnimator wants to be + * called later to start the associated animations. runPendingAnimations() will be scheduled + * to be run on the next frame. */ - public final void dispatchMoveFinished(ViewHolder item) { - onMoveFinished(item); - if (mListener != null) { - mListener.onMoveFinished(item); - } - } + abstract public void runPendingAnimations(); /** - * Method to be called by subclasses when an add animation is done. + * Method called when an animation on a view should be ended immediately. + * This could happen when other events, like scrolling, occur, so that + * animating views can be quickly put into their proper end locations. + * Implementations should ensure that any animations running on the item + * are canceled and affected properties are set to their end values. + * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished + * animation since the animations are effectively done when this method is called. * - * @param item The item which has been added + * @param item The item for which an animation should be stopped. */ - public final void dispatchAddFinished(ViewHolder item) { - onAddFinished(item); - if (mListener != null) { - mListener.onAddFinished(item); - } - } + abstract public void endAnimation(ViewHolder item); /** - * Method to be called by subclasses when a change animation is done. - * - * @see #animateChange(ViewHolder, ViewHolder, int, int, int, int) - * @param item The item which has been changed (this method must be called for - * each non-null ViewHolder passed into - * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}). - * @param oldItem true if this is the old item that was changed, false if - * it is the new item that replaced the old item. + * Method called when all item animations should be ended immediately. + * This could happen when other events, like scrolling, occur, so that + * animating views can be quickly put into their proper end locations. + * Implementations should ensure that any animations running on any items + * are canceled and affected properties are set to their end values. + * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished + * animation since the animations are effectively done when this method is called. */ - public final void dispatchChangeFinished(ViewHolder item, boolean oldItem) { - onChangeFinished(item, oldItem); - if (mListener != null) { - mListener.onChangeFinished(item); - } - } + abstract public void endAnimations(); /** - * Method to be called by subclasses when a remove animation is being started. + * Method which returns whether there are any item animations currently running. + * This method can be used to determine whether to delay other actions until + * animations end. * - * @param item The item being removed + * @return true if there are any item animations currently running, false otherwise. */ - public final void dispatchRemoveStarting(ViewHolder item) { - onRemoveStarting(item); - } + abstract public boolean isRunning(); /** - * Method to be called by subclasses when a move animation is being started. + * Method to be called by subclasses when an animation is finished. + *

+ * For each call RecyclerView makes to + * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) + * animateAppearance()}, + * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) + * animatePersistence()}, or + * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) + * animateDisappearance()}, there + * should + * be a matching {@link #dispatchAnimationFinished(ViewHolder)} call by the subclass. + *

+ * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) + * animateChange()}, sublcass should call this method for both the oldHolder + * and newHolder (if they are not the same instance). * - * @param item The item being moved + * @param viewHolder The ViewHolder whose animation is finished. + * @see #onAnimationFinished(ViewHolder) */ - public final void dispatchMoveStarting(ViewHolder item) { - onMoveStarting(item); + public final void dispatchAnimationFinished(ViewHolder viewHolder) { + onAnimationFinished(viewHolder); + if (mListener != null) { + mListener.onAnimationFinished(viewHolder); + } } /** - * Method to be called by subclasses when an add animation is being started. + * Called after {@link #dispatchAnimationFinished(ViewHolder)} is called by the + * ItemAniamtor. * - * @param item The item being added + * @param viewHolder The ViewHolder whose animation is finished. There might still be other + * animations running on this ViewHolder. + * @see #dispatchAnimationFinished(ViewHolder) */ - public final void dispatchAddStarting(ViewHolder item) { - onAddStarting(item); + public void onAnimationFinished(ViewHolder viewHolder) { } /** - * Method to be called by subclasses when a change animation is being started. + * Method to be called by subclasses when an animation is started. + *

+ * For each call RecyclerView makes to + * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) + * animateAppearance()}, + * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) + * animatePersistence()}, or + * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) + * animateDisappearance()}, there should be a matching + * {@link #dispatchAnimationStarted(ViewHolder)} call by the subclass. + *

+ * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) + * animateChange()}, sublcass should call this method for both the oldHolder + * and newHolder (if they are not the same instance). + *

+ * If your ItemAnimator decides not to animate a ViewHolder, it should call + * {@link #dispatchAnimationFinished(ViewHolder)} without calling + * {@link #dispatchAnimationStarted(ViewHolder)}. * - * @param item The item which has been changed (this method must be called for - * each non-null ViewHolder passed into - * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}). - * @param oldItem true if this is the old item that was changed, false if - * it is the new item that replaced the old item. + * @param viewHolder The ViewHolder whose animation is starting. + * @see #onAnimationStarted(ViewHolder) */ - public final void dispatchChangeStarting(ViewHolder item, boolean oldItem) { - onChangeStarting(item, oldItem); + public final void dispatchAnimationStarted(ViewHolder viewHolder) { + onAnimationStarted(viewHolder); } /** - * Method called when an animation on a view should be ended immediately. - * This could happen when other events, like scrolling, occur, so that - * animating views can be quickly put into their proper end locations. - * Implementations should ensure that any animations running on the item - * are canceled and affected properties are set to their end values. - * Also, appropriate dispatch methods (e.g., {@link #dispatchAddFinished(ViewHolder)} - * should be called since the animations are effectively done when this - * method is called. + * Called when a new animation is started on the given ViewHolder. * - * @param item The item for which an animation should be stopped. - */ - abstract public void endAnimation(ViewHolder item); - - /** - * Method called when all item animations should be ended immediately. - * This could happen when other events, like scrolling, occur, so that - * animating views can be quickly put into their proper end locations. - * Implementations should ensure that any animations running on any items - * are canceled and affected properties are set to their end values. - * Also, appropriate dispatch methods (e.g., {@link #dispatchAddFinished(ViewHolder)} - * should be called since the animations are effectively done when this - * method is called. + * @param viewHolder The ViewHolder which started animating. Note that the ViewHolder + * might already be animating and this might be another animation. + * @see #dispatchAnimationStarted(ViewHolder) */ - abstract public void endAnimations(); + public void onAnimationStarted(ViewHolder viewHolder) { - /** - * Method which returns whether there are any item animations currently running. - * This method can be used to determine whether to delay other actions until - * animations end. - * - * @return true if there are any item animations currently running, false otherwise. - */ - abstract public boolean isRunning(); + } /** * Like {@link #isRunning()}, this method returns whether there are any item @@ -10073,15 +10850,58 @@ public final boolean isRunning(ItemAnimatorFinishedListener listener) { } /** - * The interface to be implemented by listeners to animation events from this - * ItemAnimator. This is used internally and is not intended for developers to - * create directly. + * When an item is changed, ItemAnimator can decide whether it wants to re-use + * the same ViewHolder for animations or RecyclerView should create a copy of the + * item and ItemAnimator will use both to run the animation (e.g. cross-fade). + *

+ * Note that this method will only be called if the {@link ViewHolder} still has the same + * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive + * both {@link ViewHolder}s in the + * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method. + *

+ * If your application is using change payloads, you can override + * {@link #canReuseUpdatedViewHolder(ViewHolder, List)} to decide based on payloads. + * + * @param viewHolder The ViewHolder which represents the changed item's old content. + * + * @return True if RecyclerView should just rebind to the same ViewHolder or false if + * RecyclerView should create a new ViewHolder and pass this ViewHolder to the + * ItemAnimator to animate. Default implementation returns true. + * + * @see #canReuseUpdatedViewHolder(ViewHolder, List) */ - interface ItemAnimatorListener { - void onRemoveFinished(ViewHolder item); - void onAddFinished(ViewHolder item); - void onMoveFinished(ViewHolder item); - void onChangeFinished(ViewHolder item); + public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder) { + return true; + } + + /** + * When an item is changed, ItemAnimator can decide whether it wants to re-use + * the same ViewHolder for animations or RecyclerView should create a copy of the + * item and ItemAnimator will use both to run the animation (e.g. cross-fade). + *

+ * Note that this method will only be called if the {@link ViewHolder} still has the same + * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive + * both {@link ViewHolder}s in the + * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method. + * + * @param viewHolder The ViewHolder which represents the changed item's old content. + * @param payloads A non-null list of merged payloads that were sent with change + * notifications. Can be empty if the adapter is invalidated via + * {@link RecyclerView.Adapter#notifyDataSetChanged()}. The same list of + * payloads will be passed into + * {@link RecyclerView.Adapter#onBindViewHolder(ViewHolder, int, List)} + * method if this method returns true. + * + * @return True if RecyclerView should just rebind to the same ViewHolder or false if + * RecyclerView should create a new ViewHolder and pass this ViewHolder to the + * ItemAnimator to animate. Default implementation calls + * {@link #canReuseUpdatedViewHolder(ViewHolder)}. + * + * @see #canReuseUpdatedViewHolder(ViewHolder) + */ + public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder, + @NonNull List payloads) { + return canReuseUpdatedViewHolder(viewHolder); } /** @@ -10097,116 +10917,112 @@ public final void dispatchAnimationsFinished() { } /** - * This interface is used to inform listeners when all pending or running animations - * in an ItemAnimator are finished. This can be used, for example, to delay an action - * in a data set until currently-running animations are complete. + * Returns a new {@link ItemHolderInfo} which will be used to store information about the + * ViewHolder. This information will later be passed into animate** methods. + *

+ * You can override this method if you want to extend {@link ItemHolderInfo} and provide + * your own instances. * - * @see #isRunning(ItemAnimatorFinishedListener) + * @return A new {@link ItemHolderInfo}. */ - public interface ItemAnimatorFinishedListener { - void onAnimationsFinished(); + public ItemHolderInfo obtainHolderInfo() { + return new ItemHolderInfo(); } /** - * Called when a remove animation is being started on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. + * The interface to be implemented by listeners to animation events from this + * ItemAnimator. This is used internally and is not intended for developers to + * create directly. */ - public void onRemoveStarting(ViewHolder item) {} + interface ItemAnimatorListener { + void onAnimationFinished(ViewHolder item); + } /** - * Called when a remove animation has ended on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. + * This interface is used to inform listeners when all pending or running animations + * in an ItemAnimator are finished. This can be used, for example, to delay an action + * in a data set until currently-running animations are complete. * - * @param item The ViewHolder being animated. + * @see #isRunning(ItemAnimatorFinishedListener) */ - public void onRemoveFinished(ViewHolder item) {} + public interface ItemAnimatorFinishedListener { + void onAnimationsFinished(); + } /** - * Called when an add animation is being started on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. + * A simple data structure that holds information about an item's bounds. + * This information is used in calculating item animations. Default implementation of + * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)} and + * {@link #recordPostLayoutInformation(RecyclerView.State, ViewHolder)} returns this data + * structure. You can extend this class if you would like to keep more information about + * the Views. + *

+ * If you want to provide your own implementation butstill use `super` methods to record + * basic information, you can override {@link #obtainHolderInfo()} to provide your own + * instances. */ - public void onAddStarting(ViewHolder item) {} + public static class ItemHolderInfo { - /** - * Called when an add animation has ended on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - */ - public void onAddFinished(ViewHolder item) {} + /** + * The left edge of the View (excluding decorations) + */ + public int left; - /** - * Called when a move animation is being started on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - */ - public void onMoveStarting(ViewHolder item) {} + /** + * The top edge of the View (excluding decorations) + */ + public int top; - /** - * Called when a move animation has ended on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - */ - public void onMoveFinished(ViewHolder item) {} + /** + * The right edge of the View (excluding decorations) + */ + public int right; - /** - * Called when a change animation is being started on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - * @param oldItem true if this is the old item that was changed, false if - * it is the new item that replaced the old item. - */ - public void onChangeStarting(ViewHolder item, boolean oldItem) {} + /** + * The bottom edge of the View (excluding decorations) + */ + public int bottom; - /** - * Called when a change animation has ended on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - * @param oldItem true if this is the old item that was changed, false if - * it is the new item that replaced the old item. - */ - public void onChangeFinished(ViewHolder item, boolean oldItem) {} + /** + * The change flags that were passed to + * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)}. + */ + @AdapterChanges + public int changeFlags; - } + public ItemHolderInfo() { + } - /** - * Internal data structure that holds information about an item's bounds. - * This information is used in calculating item animations. - */ - private static class ItemHolderInfo { - ViewHolder holder; - int left, top, right, bottom; + /** + * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from + * the given ViewHolder. Clears all {@link #changeFlags}. + * + * @param holder The ViewHolder whose bounds should be copied. + * @return This {@link ItemHolderInfo} + */ + public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder) { + return setFrom(holder, 0); + } - ItemHolderInfo(ViewHolder holder, int left, int top, int right, int bottom) { - this.holder = holder; - this.left = left; - this.top = top; - this.right = right; - this.bottom = bottom; + /** + * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from + * the given ViewHolder and sets the {@link #changeFlags} to the given flags parameter. + * + * @param holder The ViewHolder whose bounds should be copied. + * @param flags The adapter change flags that were passed into + * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, + * List)}. + * @return This {@link ItemHolderInfo} + */ + public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder, + @AdapterChanges int flags) { + final View view = holder.itemView; + this.left = view.getLeft(); + this.top = view.getTop(); + this.right = view.getRight(); + this.bottom = view.getBottom(); + return this; + } } } @@ -10227,7 +11043,7 @@ protected int getChildDrawingOrder(int childCount, int i) { * order of two views will not have any effect if their elevation values are different since * elevation overrides the result of this callback. */ - public static interface ChildDrawingOrderCallback { + public interface ChildDrawingOrderCallback { /** * Returns the index of the child to draw for this iteration. Override this * if you want to change the drawing order of children. By default, it @@ -10238,6 +11054,6 @@ public static interface ChildDrawingOrderCallback { * * @see RecyclerView#setChildDrawingOrderCallback(RecyclerView.ChildDrawingOrderCallback) */ - public int onGetChildDrawingOrder(int childCount, int i); + int onGetChildDrawingOrder(int childCount, int i); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerViewAccessibilityDelegate.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerViewAccessibilityDelegate.java index 9ffd89f220..dc3b90194a 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerViewAccessibilityDelegate.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerViewAccessibilityDelegate.java @@ -22,8 +22,6 @@ import android.view.View; import android.view.accessibility.AccessibilityEvent; -import org.telegram.messenger.support.widget.RecyclerView; - /** * The AccessibilityDelegate used by RecyclerView. *

diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ScrollbarHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ScrollbarHelper.java index 998252584b..4dd6308dbb 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ScrollbarHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ScrollbarHelper.java @@ -17,8 +17,6 @@ import android.view.View; -import org.telegram.messenger.support.widget.RecyclerView; - /** * A helper class to do scroll offset calculations. */ diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/SimpleItemAnimator.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/SimpleItemAnimator.java new file mode 100644 index 0000000000..2eb4cce5d0 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/SimpleItemAnimator.java @@ -0,0 +1,442 @@ +package org.telegram.messenger.support.widget; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import org.telegram.messenger.support.widget.RecyclerView.Adapter; +import org.telegram.messenger.support.widget.RecyclerView.ViewHolder; +import org.telegram.messenger.support.widget.RecyclerView.ItemAnimator.ItemHolderInfo; +import android.util.Log; +import android.view.View; + +import java.util.List; + +/** + * A wrapper class for ItemAnimator that records View bounds and decides whether it should run + * move, change, add or remove animations. This class also replicates the original ItemAnimator + * API. + *

+ * It uses {@link ItemHolderInfo} to track the bounds information of the Views. If you would like + * to + * extend this class, you can override {@link #obtainHolderInfo()} method to provide your own info + * class that extends {@link ItemHolderInfo}. + */ +abstract public class SimpleItemAnimator extends RecyclerView.ItemAnimator { + + private static final boolean DEBUG = false; + + private static final String TAG = "SimpleItemAnimator"; + + boolean mSupportsChangeAnimations = true; + + /** + * Returns whether this ItemAnimator supports animations of change events. + * + * @return true if change animations are supported, false otherwise + */ + @SuppressWarnings("unused") + public boolean getSupportsChangeAnimations() { + return mSupportsChangeAnimations; + } + + /** + * Sets whether this ItemAnimator supports animations of item change events. + * If you set this property to false, actions on the data set which change the + * contents of items will not be animated. What those animations do is left + * up to the discretion of the ItemAnimator subclass, in its + * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} implementation. + * The value of this property is true by default. + * + * @param supportsChangeAnimations true if change animations are supported by + * this ItemAnimator, false otherwise. If the property is false, + * the ItemAnimator + * will not receive a call to + * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, + * int)} when changes occur. + * @see Adapter#notifyItemChanged(int) + * @see Adapter#notifyItemRangeChanged(int, int) + */ + public void setSupportsChangeAnimations(boolean supportsChangeAnimations) { + mSupportsChangeAnimations = supportsChangeAnimations; + } + + /** + * {@inheritDoc} + * + * @return True if change animations are not supported or the ViewHolder is invalid, + * false otherwise. + * + * @see #setSupportsChangeAnimations(boolean) + */ + @Override + public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) { + return !mSupportsChangeAnimations || viewHolder.isInvalid(); + } + + @Override + public boolean animateDisappearance(@NonNull ViewHolder viewHolder, + @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) { + int oldLeft = preLayoutInfo.left; + int oldTop = preLayoutInfo.top; + View disappearingItemView = viewHolder.itemView; + int newLeft = postLayoutInfo == null ? disappearingItemView.getLeft() : postLayoutInfo.left; + int newTop = postLayoutInfo == null ? disappearingItemView.getTop() : postLayoutInfo.top; + if (!viewHolder.isRemoved() && (oldLeft != newLeft || oldTop != newTop)) { + disappearingItemView.layout(newLeft, newTop, + newLeft + disappearingItemView.getWidth(), + newTop + disappearingItemView.getHeight()); + if (DEBUG) { + Log.d(TAG, "DISAPPEARING: " + viewHolder + " with view " + disappearingItemView); + } + return animateMove(viewHolder, oldLeft, oldTop, newLeft, newTop); + } else { + if (DEBUG) { + Log.d(TAG, "REMOVED: " + viewHolder + " with view " + disappearingItemView); + } + return animateRemove(viewHolder); + } + } + + @Override + public boolean animateAppearance(@NonNull ViewHolder viewHolder, + @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) { + if (preLayoutInfo != null && (preLayoutInfo.left != postLayoutInfo.left + || preLayoutInfo.top != postLayoutInfo.top)) { + // slide items in if before/after locations differ + if (DEBUG) { + Log.d(TAG, "APPEARING: " + viewHolder + " with view " + viewHolder); + } + return animateMove(viewHolder, preLayoutInfo.left, preLayoutInfo.top, + postLayoutInfo.left, postLayoutInfo.top); + } else { + if (DEBUG) { + Log.d(TAG, "ADDED: " + viewHolder + " with view " + viewHolder); + } + return animateAdd(viewHolder); + } + } + + @Override + public boolean animatePersistence(@NonNull ViewHolder viewHolder, + @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) { + if (preInfo.left != postInfo.left || preInfo.top != postInfo.top) { + if (DEBUG) { + Log.d(TAG, "PERSISTENT: " + viewHolder + + " with view " + viewHolder.itemView); + } + return animateMove(viewHolder, + preInfo.left, preInfo.top, postInfo.left, postInfo.top); + } + dispatchMoveFinished(viewHolder); + return false; + } + + @Override + public boolean animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder, + @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) { + if (DEBUG) { + Log.d(TAG, "CHANGED: " + oldHolder + " with view " + oldHolder.itemView); + } + final int fromLeft = preInfo.left; + final int fromTop = preInfo.top; + final int toLeft, toTop; + if (newHolder.shouldIgnore()) { + toLeft = preInfo.left; + toTop = preInfo.top; + } else { + toLeft = postInfo.left; + toTop = postInfo.top; + } + return animateChange(oldHolder, newHolder, fromLeft, fromTop, toLeft, toTop); + } + + /** + * Called when an item is removed from the RecyclerView. Implementors can choose + * whether and how to animate that change, but must always call + * {@link #dispatchRemoveFinished(ViewHolder)} when done, either + * immediately (if no animation will occur) or after the animation actually finishes. + * The return value indicates whether an animation has been set up and whether the + * ItemAnimator's {@link #runPendingAnimations()} method should be called at the + * next opportunity. This mechanism allows ItemAnimator to set up individual animations + * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()}, + * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, + * {@link #animateRemove(ViewHolder) animateRemove()}, and + * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one, + * then start the animations together in the later call to {@link #runPendingAnimations()}. + * + *

This method may also be called for disappearing items which continue to exist in the + * RecyclerView, but for which the system does not have enough information to animate + * them out of view. In that case, the default animation for removing items is run + * on those items as well.

+ * + * @param holder The item that is being removed. + * @return true if a later call to {@link #runPendingAnimations()} is requested, + * false otherwise. + */ + abstract public boolean animateRemove(ViewHolder holder); + + /** + * Called when an item is added to the RecyclerView. Implementors can choose + * whether and how to animate that change, but must always call + * {@link #dispatchAddFinished(ViewHolder)} when done, either + * immediately (if no animation will occur) or after the animation actually finishes. + * The return value indicates whether an animation has been set up and whether the + * ItemAnimator's {@link #runPendingAnimations()} method should be called at the + * next opportunity. This mechanism allows ItemAnimator to set up individual animations + * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()}, + * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, + * {@link #animateRemove(ViewHolder) animateRemove()}, and + * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one, + * then start the animations together in the later call to {@link #runPendingAnimations()}. + * + *

This method may also be called for appearing items which were already in the + * RecyclerView, but for which the system does not have enough information to animate + * them into view. In that case, the default animation for adding items is run + * on those items as well.

+ * + * @param holder The item that is being added. + * @return true if a later call to {@link #runPendingAnimations()} is requested, + * false otherwise. + */ + abstract public boolean animateAdd(ViewHolder holder); + + /** + * Called when an item is moved in the RecyclerView. Implementors can choose + * whether and how to animate that change, but must always call + * {@link #dispatchMoveFinished(ViewHolder)} when done, either + * immediately (if no animation will occur) or after the animation actually finishes. + * The return value indicates whether an animation has been set up and whether the + * ItemAnimator's {@link #runPendingAnimations()} method should be called at the + * next opportunity. This mechanism allows ItemAnimator to set up individual animations + * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()}, + * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, + * {@link #animateRemove(ViewHolder) animateRemove()}, and + * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one, + * then start the animations together in the later call to {@link #runPendingAnimations()}. + * + * @param holder The item that is being moved. + * @return true if a later call to {@link #runPendingAnimations()} is requested, + * false otherwise. + */ + abstract public boolean animateMove(ViewHolder holder, int fromX, int fromY, + int toX, int toY); + + /** + * Called when an item is changed in the RecyclerView, as indicated by a call to + * {@link Adapter#notifyItemChanged(int)} or + * {@link Adapter#notifyItemRangeChanged(int, int)}. + *

+ * Implementers can choose whether and how to animate changes, but must always call + * {@link #dispatchChangeFinished(ViewHolder, boolean)} for each non-null distinct ViewHolder, + * either immediately (if no animation will occur) or after the animation actually finishes. + * If the {@code oldHolder} is the same ViewHolder as the {@code newHolder}, you must call + * {@link #dispatchChangeFinished(ViewHolder, boolean)} once and only once. In that case, the + * second parameter of {@code dispatchChangeFinished} is ignored. + *

+ * The return value indicates whether an animation has been set up and whether the + * ItemAnimator's {@link #runPendingAnimations()} method should be called at the + * next opportunity. This mechanism allows ItemAnimator to set up individual animations + * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()}, + * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, + * {@link #animateRemove(ViewHolder) animateRemove()}, and + * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one, + * then start the animations together in the later call to {@link #runPendingAnimations()}. + * + * @param oldHolder The original item that changed. + * @param newHolder The new item that was created with the changed content. Might be null + * @param fromLeft Left of the old view holder + * @param fromTop Top of the old view holder + * @param toLeft Left of the new view holder + * @param toTop Top of the new view holder + * @return true if a later call to {@link #runPendingAnimations()} is requested, + * false otherwise. + */ + abstract public boolean animateChange(ViewHolder oldHolder, + ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop); + + /** + * Method to be called by subclasses when a remove animation is done. + * + * @param item The item which has been removed + * @see RecyclerView.ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo, + * ItemHolderInfo) + */ + public final void dispatchRemoveFinished(ViewHolder item) { + onRemoveFinished(item); + dispatchAnimationFinished(item); + } + + /** + * Method to be called by subclasses when a move animation is done. + * + * @param item The item which has been moved + * @see RecyclerView.ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo, + * ItemHolderInfo) + * @see RecyclerView.ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) + * @see RecyclerView.ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) + */ + public final void dispatchMoveFinished(ViewHolder item) { + onMoveFinished(item); + dispatchAnimationFinished(item); + } + + /** + * Method to be called by subclasses when an add animation is done. + * + * @param item The item which has been added + */ + public final void dispatchAddFinished(ViewHolder item) { + onAddFinished(item); + dispatchAnimationFinished(item); + } + + /** + * Method to be called by subclasses when a change animation is done. + * + * @param item The item which has been changed (this method must be called for + * each non-null ViewHolder passed into + * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}). + * @param oldItem true if this is the old item that was changed, false if + * it is the new item that replaced the old item. + * @see #animateChange(ViewHolder, ViewHolder, int, int, int, int) + */ + public final void dispatchChangeFinished(ViewHolder item, boolean oldItem) { + onChangeFinished(item, oldItem); + dispatchAnimationFinished(item); + } + + /** + * Method to be called by subclasses when a remove animation is being started. + * + * @param item The item being removed + */ + public final void dispatchRemoveStarting(ViewHolder item) { + onRemoveStarting(item); + } + + /** + * Method to be called by subclasses when a move animation is being started. + * + * @param item The item being moved + */ + public final void dispatchMoveStarting(ViewHolder item) { + onMoveStarting(item); + } + + /** + * Method to be called by subclasses when an add animation is being started. + * + * @param item The item being added + */ + public final void dispatchAddStarting(ViewHolder item) { + onAddStarting(item); + } + + /** + * Method to be called by subclasses when a change animation is being started. + * + * @param item The item which has been changed (this method must be called for + * each non-null ViewHolder passed into + * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}). + * @param oldItem true if this is the old item that was changed, false if + * it is the new item that replaced the old item. + */ + public final void dispatchChangeStarting(ViewHolder item, boolean oldItem) { + onChangeStarting(item, oldItem); + } + + /** + * Called when a remove animation is being started on the given ViewHolder. + * The default implementation does nothing. Subclasses may wish to override + * this method to handle any ViewHolder-specific operations linked to animation + * lifecycles. + * + * @param item The ViewHolder being animated. + */ + @SuppressWarnings("UnusedParameters") + public void onRemoveStarting(ViewHolder item) { + } + + /** + * Called when a remove animation has ended on the given ViewHolder. + * The default implementation does nothing. Subclasses may wish to override + * this method to handle any ViewHolder-specific operations linked to animation + * lifecycles. + * + * @param item The ViewHolder being animated. + */ + public void onRemoveFinished(ViewHolder item) { + } + + /** + * Called when an add animation is being started on the given ViewHolder. + * The default implementation does nothing. Subclasses may wish to override + * this method to handle any ViewHolder-specific operations linked to animation + * lifecycles. + * + * @param item The ViewHolder being animated. + */ + @SuppressWarnings("UnusedParameters") + public void onAddStarting(ViewHolder item) { + } + + /** + * Called when an add animation has ended on the given ViewHolder. + * The default implementation does nothing. Subclasses may wish to override + * this method to handle any ViewHolder-specific operations linked to animation + * lifecycles. + * + * @param item The ViewHolder being animated. + */ + public void onAddFinished(ViewHolder item) { + } + + /** + * Called when a move animation is being started on the given ViewHolder. + * The default implementation does nothing. Subclasses may wish to override + * this method to handle any ViewHolder-specific operations linked to animation + * lifecycles. + * + * @param item The ViewHolder being animated. + */ + @SuppressWarnings("UnusedParameters") + public void onMoveStarting(ViewHolder item) { + } + + /** + * Called when a move animation has ended on the given ViewHolder. + * The default implementation does nothing. Subclasses may wish to override + * this method to handle any ViewHolder-specific operations linked to animation + * lifecycles. + * + * @param item The ViewHolder being animated. + */ + public void onMoveFinished(ViewHolder item) { + } + + /** + * Called when a change animation is being started on the given ViewHolder. + * The default implementation does nothing. Subclasses may wish to override + * this method to handle any ViewHolder-specific operations linked to animation + * lifecycles. + * + * @param item The ViewHolder being animated. + * @param oldItem true if this is the old item that was changed, false if + * it is the new item that replaced the old item. + */ + @SuppressWarnings("UnusedParameters") + public void onChangeStarting(ViewHolder item, boolean oldItem) { + } + + /** + * Called when a change animation has ended on the given ViewHolder. + * The default implementation does nothing. Subclasses may wish to override + * this method to handle any ViewHolder-specific operations linked to animation + * lifecycles. + * + * @param item The ViewHolder being animated. + * @param oldItem true if this is the old item that was changed, false if + * it is the new item that replaced the old item. + */ + public void onChangeFinished(ViewHolder item, boolean oldItem) { + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/StaggeredGridLayoutManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/StaggeredGridLayoutManager.java index 59e7f59f59..dd24ad4255 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/StaggeredGridLayoutManager.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/StaggeredGridLayoutManager.java @@ -16,11 +16,18 @@ package org.telegram.messenger.support.widget; +import static org.telegram.messenger.support.widget.LayoutState.ITEM_DIRECTION_HEAD; +import static org.telegram.messenger.support.widget.LayoutState.ITEM_DIRECTION_TAIL; +import static org.telegram.messenger.support.widget.LayoutState.LAYOUT_END; +import static org.telegram.messenger.support.widget.LayoutState.LAYOUT_START; +import static org.telegram.messenger.support.widget.RecyclerView.NO_POSITION; + import android.content.Context; import android.graphics.PointF; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; +import android.support.annotation.Nullable; import android.support.v4.view.ViewCompat; import android.support.v4.view.accessibility.AccessibilityEventCompat; import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; @@ -31,24 +38,11 @@ import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; -import org.telegram.messenger.support.widget.AdapterHelper; -import org.telegram.messenger.support.widget.LayoutState; -import org.telegram.messenger.support.widget.LinearSmoothScroller; -import org.telegram.messenger.support.widget.OrientationHelper; -import org.telegram.messenger.support.widget.RecyclerView; -import org.telegram.messenger.support.widget.ScrollbarHelper; - import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.List; -import static org.telegram.messenger.support.widget.LayoutState.LAYOUT_START; -import static org.telegram.messenger.support.widget.LayoutState.LAYOUT_END; -import static org.telegram.messenger.support.widget.LayoutState.ITEM_DIRECTION_HEAD; -import static org.telegram.messenger.support.widget.LayoutState.ITEM_DIRECTION_TAIL; -import static org.telegram.messenger.support.widget.RecyclerView.NO_POSITION; - /** * A LayoutManager that lays out children in a staggered grid formation. * It supports horizontal & vertical layout as well as an ability to layout children in reverse. @@ -72,6 +66,7 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { */ public static final int GAP_HANDLING_NONE = 0; + @SuppressWarnings("unused") @Deprecated public static final int GAP_HANDLING_LAZY = 1; @@ -97,6 +92,12 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2; private static final int INVALID_OFFSET = Integer.MIN_VALUE; + /** + * While trying to find next view to focus, LayoutManager will not try to scroll more + * than this factor times the total space of the list. If layout is vertical, total space is the + * height minus padding, if layout is horizontal, total space is the width minus padding. + */ + private static final float MAX_SCROLL_FACTOR = 1 / 3f; /** * Number of spans @@ -175,7 +176,7 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { /** * Re-used measurement specs. updated by onLayout. */ - private int mFullSizeSpec, mWidthSpec, mHeightSpec; + private int mFullSizeSpec; /** * Re-used rectangle to get child decor offsets. @@ -208,6 +209,20 @@ public void run() { } }; + /** + * Constructor used when layout manager is set in XML by RecyclerView attribute + * "layoutManager". Defaults to single column and vertical. + */ + @SuppressWarnings("unused") + public StaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes); + setOrientation(properties.orientation); + setSpanCount(properties.spanCount); + setReverseLayout(properties.reverseLayout); + setAutoMeasureEnabled(mGapStrategy != GAP_HANDLING_NONE); + } + /** * Creates a StaggeredGridLayoutManager with given parameters. * @@ -218,6 +233,7 @@ public void run() { public StaggeredGridLayoutManager(int spanCount, int orientation) { mOrientation = orientation; setSpanCount(spanCount); + setAutoMeasureEnabled(mGapStrategy != GAP_HANDLING_NONE); } /** @@ -358,10 +374,16 @@ View hasGapsToFix() { private boolean checkSpanForGap(Span span) { if (mShouldReverseLayout) { if (span.getEndLine() < mPrimaryOrientation.getEndAfterPadding()) { - return true; + // if it is full span, it is OK + final View endView = span.mViews.get(span.mViews.size() - 1); + final LayoutParams lp = span.getLayoutParams(endView); + return !lp.mFullSpan; } } else if (span.getStartLine() > mPrimaryOrientation.getStartAfterPadding()) { - return true; + // if it is full span, it is OK + final View startView = span.mViews.get(0); + final LayoutParams lp = span.getLayoutParams(startView); + return !lp.mFullSpan; } return false; } @@ -473,6 +495,7 @@ public void setGapStrategy(int gapStrategy) { + "or GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS"); } mGapStrategy = gapStrategy; + setAutoMeasureEnabled(mGapStrategy != GAP_HANDLING_NONE); requestLayout(); } @@ -541,8 +564,35 @@ boolean isLayoutRTL() { public boolean getReverseLayout() { return mReverseLayout; } + + @Override + public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) { + // we don't like it to wrap content in our non-scroll direction. + final int width, height; + final int horizontalPadding = getPaddingLeft() + getPaddingRight(); + final int verticalPadding = getPaddingTop() + getPaddingBottom(); + if (mOrientation == VERTICAL) { + final int usedHeight = childrenBounds.height() + verticalPadding; + height = chooseSize(hSpec, usedHeight, getMinimumHeight()); + width = chooseSize(wSpec, mSizePerSpan * mSpanCount + horizontalPadding, + getMinimumWidth()); + } else { + final int usedWidth = childrenBounds.width() + horizontalPadding; + width = chooseSize(wSpec, usedWidth, getMinimumWidth()); + height = chooseSize(hSpec, mSizePerSpan * mSpanCount + verticalPadding, + getMinimumHeight()); + } + setMeasuredDimension(width, height); + } + @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { + onLayoutChildren(recycler, state, true); + } + + + private void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state, + boolean shouldCheckForGaps) { ensureOrientationHelper(); final AnchorInfo anchorInfo = mAnchorInfo; anchorInfo.reset(); @@ -588,8 +638,9 @@ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State } } detachAndScrapAttachedViews(recycler); + mLayoutState.mRecycle = false; mLaidOutInvalidFullSpan = false; - updateMeasureSpecs(); + updateMeasureSpecs(mSecondaryOrientation.getTotalSpace()); updateLayoutState(anchorInfo.mPosition, state); if (anchorInfo.mLayoutFromEnd) { // Layout start. @@ -609,6 +660,8 @@ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State fill(recycler, mLayoutState, state); } + repositionToWrapContentIfNecessary(); + if (getChildCount() > 0) { if (mShouldReverseLayout) { fixEndGap(recycler, state, true); @@ -618,14 +671,16 @@ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State fixEndGap(recycler, state, false); } } - - if (!state.isPreLayout()) { + boolean hasGaps = false; + if (shouldCheckForGaps && !state.isPreLayout()) { final boolean needToCheckForGaps = mGapStrategy != GAP_HANDLING_NONE && getChildCount() > 0 && (mLaidOutInvalidFullSpan || hasGapsToFix() != null); if (needToCheckForGaps) { removeCallbacks(mCheckForGapsRunnable); - postOnAnimation(mCheckForGapsRunnable); + if (checkForGaps()) { + hasGaps = true; + } } mPendingScrollPosition = NO_POSITION; mPendingScrollPositionOffset = INVALID_OFFSET; @@ -633,6 +688,58 @@ && getChildCount() > 0 mLastLayoutFromEnd = anchorInfo.mLayoutFromEnd; mLastLayoutRTL = isLayoutRTL(); mPendingSavedState = null; // we don't need this anymore + if (hasGaps) { + onLayoutChildren(recycler, state, false); + } + } + + private void repositionToWrapContentIfNecessary() { + if (mSecondaryOrientation.getMode() == View.MeasureSpec.EXACTLY) { + return; // nothing to do + } + float maxSize = 0; + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i ++) { + View child = getChildAt(i); + float size = mSecondaryOrientation.getDecoratedMeasurement(child); + if (size < maxSize) { + continue; + } + LayoutParams layoutParams = (LayoutParams) child.getLayoutParams(); + if (layoutParams.isFullSpan()) { + size = 1f * size / mSpanCount; + } + maxSize = Math.max(maxSize, size); + } + int before = mSizePerSpan; + int desired = Math.round(maxSize * mSpanCount); + if (mSecondaryOrientation.getMode() == View.MeasureSpec.AT_MOST) { + desired = Math.min(desired, mSecondaryOrientation.getTotalSpace()); + } + updateMeasureSpecs(desired); + if (mSizePerSpan == before) { + return; // nothing has changed + } + for (int i = 0; i < childCount; i ++) { + View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (lp.mFullSpan) { + continue; + } + if (isLayoutRTL() && mOrientation == VERTICAL) { + int newOffset = -(mSpanCount - 1 - lp.mSpan.mIndex) * mSizePerSpan; + int prevOffset = -(mSpanCount - 1 - lp.mSpan.mIndex) * before; + child.offsetLeftAndRight(newOffset - prevOffset); + } else { + int newOffset = lp.mSpan.mIndex * mSizePerSpan; + int prevOffset = lp.mSpan.mIndex * before; + if (mOrientation == VERTICAL) { + child.offsetLeftAndRight(newOffset - prevOffset); + } else { + child.offsetTopAndBottom(newOffset - prevOffset); + } + } + } } private void applyPendingSavedState(AnchorInfo anchorInfo) { @@ -780,17 +887,11 @@ boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorI return true; } - void updateMeasureSpecs() { - mSizePerSpan = mSecondaryOrientation.getTotalSpace() / mSpanCount; + void updateMeasureSpecs(int totalSpace) { + mSizePerSpan = totalSpace / mSpanCount; + //noinspection ResourceType mFullSizeSpec = View.MeasureSpec.makeMeasureSpec( - mSecondaryOrientation.getTotalSpace(), View.MeasureSpec.EXACTLY); - if (mOrientation == VERTICAL) { - mWidthSpec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan, View.MeasureSpec.EXACTLY); - mHeightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - } else { - mHeightSpec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan, View.MeasureSpec.EXACTLY); - mWidthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - } + totalSpace, mSecondaryOrientation.getMode()); } @Override @@ -989,43 +1090,48 @@ public int computeVerticalScrollRange(RecyclerView.State state) { return computeScrollRange(state); } - private void measureChildWithDecorationsAndMargin(View child, LayoutParams lp) { + private void measureChildWithDecorationsAndMargin(View child, LayoutParams lp, + boolean alreadyMeasured) { if (lp.mFullSpan) { if (mOrientation == VERTICAL) { measureChildWithDecorationsAndMargin(child, mFullSizeSpec, - getSpecForDimension(lp.height, mHeightSpec)); + getChildMeasureSpec(getHeight(), getHeightMode(), 0, lp.height, true), + alreadyMeasured); } else { measureChildWithDecorationsAndMargin(child, - getSpecForDimension(lp.width, mWidthSpec), mFullSizeSpec); + getChildMeasureSpec(getWidth(), getWidthMode(), 0, lp.width, true), + mFullSizeSpec, alreadyMeasured); } } else { if (mOrientation == VERTICAL) { - measureChildWithDecorationsAndMargin(child, mWidthSpec, - getSpecForDimension(lp.height, mHeightSpec)); + measureChildWithDecorationsAndMargin(child, + getChildMeasureSpec(mSizePerSpan, getWidthMode(), 0, lp.width, false), + getChildMeasureSpec(getHeight(), getHeightMode(), 0, lp.height, true), + alreadyMeasured); } else { measureChildWithDecorationsAndMargin(child, - getSpecForDimension(lp.width, mWidthSpec), mHeightSpec); + getChildMeasureSpec(getWidth(), getWidthMode(), 0, lp.width, true), + getChildMeasureSpec(mSizePerSpan, getHeightMode(), 0, lp.height, false), + alreadyMeasured); } } } - private int getSpecForDimension(int dim, int defaultSpec) { - if (dim < 0) { - return defaultSpec; - } else { - return View.MeasureSpec.makeMeasureSpec(dim, View.MeasureSpec.EXACTLY); - } - } - private void measureChildWithDecorationsAndMargin(View child, int widthSpec, - int heightSpec) { + int heightSpec, boolean alreadyMeasured) { calculateItemDecorationsForChild(child, mTmpRect); LayoutParams lp = (LayoutParams) child.getLayoutParams(); widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + mTmpRect.left, lp.rightMargin + mTmpRect.right); heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mTmpRect.top, lp.bottomMargin + mTmpRect.bottom); - child.measure(widthSpec, heightSpec); + final boolean measure = alreadyMeasured + ? shouldReMeasureChild(child, widthSpec, heightSpec, lp) + : shouldMeasureChild(child, widthSpec, heightSpec, lp); + if (measure) { + child.measure(widthSpec, heightSpec); + } + } private int updateSpecWithExtra(int spec, int startInset, int endInset) { @@ -1035,7 +1141,7 @@ private int updateSpecWithExtra(int spec, int startInset, int endInset) { final int mode = View.MeasureSpec.getMode(spec); if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) { return View.MeasureSpec.makeMeasureSpec( - View.MeasureSpec.getSize(spec) - startInset - endInset, mode); + Math.max(0, View.MeasureSpec.getSize(spec) - startInset - endInset), mode); } return spec; } @@ -1238,7 +1344,10 @@ View findFirstVisibleItemClosestToEnd(boolean fullyVisible, boolean acceptPartia private void fixEndGap(RecyclerView.Recycler recycler, RecyclerView.State state, boolean canOffsetChildren) { - final int maxEndLine = getMaxEnd(mPrimaryOrientation.getEndAfterPadding()); + final int maxEndLine = getMaxEnd(Integer.MIN_VALUE); + if (maxEndLine == Integer.MIN_VALUE) { + return; + } int gap = mPrimaryOrientation.getEndAfterPadding() - maxEndLine; int fixOffset; if (gap > 0) { @@ -1254,7 +1363,10 @@ private void fixEndGap(RecyclerView.Recycler recycler, RecyclerView.State state, private void fixStartGap(RecyclerView.Recycler recycler, RecyclerView.State state, boolean canOffsetChildren) { - final int minStartLine = getMinStart(mPrimaryOrientation.getStartAfterPadding()); + final int minStartLine = getMinStart(Integer.MAX_VALUE); + if (minStartLine == Integer.MAX_VALUE) { + return; + } int gap = minStartLine - mPrimaryOrientation.getStartAfterPadding(); int fixOffset; if (gap > 0) { @@ -1293,6 +1405,9 @@ private void updateLayoutState(int anchorPosition, RecyclerView.State state) { mLayoutState.mEndLine = mPrimaryOrientation.getEnd() + endExtra; mLayoutState.mStartLine = -startExtra; } + mLayoutState.mStopInFocusable = false; + mLayoutState.mRecycle = true; + mLayoutState.mInfinite = mPrimaryOrientation.getMode() == View.MeasureSpec.UNSPECIFIED; } private void setLayoutStateDirection(int direction) { @@ -1397,10 +1512,18 @@ private int fill(RecyclerView.Recycler recycler, LayoutState layoutState, final int targetLine; // Line of the furthest row. - if (layoutState.mLayoutDirection == LAYOUT_END) { - targetLine = layoutState.mEndLine + layoutState.mAvailable; - } else { // LAYOUT_START - targetLine = layoutState.mStartLine - layoutState.mAvailable; + if (mLayoutState.mInfinite) { + if (layoutState.mLayoutDirection == LAYOUT_END) { + targetLine = Integer.MAX_VALUE; + } else { // LAYOUT_START + targetLine = Integer.MIN_VALUE; + } + } else { + if (layoutState.mLayoutDirection == LAYOUT_END) { + targetLine = layoutState.mEndLine + layoutState.mAvailable; + } else { // LAYOUT_START + targetLine = layoutState.mStartLine - layoutState.mAvailable; + } } updateAllRemainingSpans(layoutState.mLayoutDirection, targetLine); @@ -1414,7 +1537,8 @@ private int fill(RecyclerView.Recycler recycler, LayoutState layoutState, ? mPrimaryOrientation.getEndAfterPadding() : mPrimaryOrientation.getStartAfterPadding(); boolean added = false; - while (layoutState.hasMore(state) && !mRemainingSpans.isEmpty()) { + while (layoutState.hasMore(state) + && (mLayoutState.mInfinite || !mRemainingSpans.isEmpty())) { View view = layoutState.next(recycler); LayoutParams lp = ((LayoutParams) view.getLayoutParams()); final int position = lp.getViewLayoutPosition(); @@ -1440,7 +1564,7 @@ private int fill(RecyclerView.Recycler recycler, LayoutState layoutState, } else { addView(view, 0); } - measureChildWithDecorationsAndMargin(view, lp); + measureChildWithDecorationsAndMargin(view, lp, false); final int start; final int end; @@ -1488,13 +1612,22 @@ private int fill(RecyclerView.Recycler recycler, LayoutState layoutState, mLaidOutInvalidFullSpan = true; } } - } attachViewToSpans(view, lp, layoutState); - final int otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding() - : currentSpan.mIndex * mSizePerSpan + - mSecondaryOrientation.getStartAfterPadding(); - final int otherEnd = otherStart + mSecondaryOrientation.getDecoratedMeasurement(view); + final int otherStart; + final int otherEnd; + if (isLayoutRTL() && mOrientation == VERTICAL) { + otherEnd = lp.mFullSpan ? mSecondaryOrientation.getEndAfterPadding() : + mSecondaryOrientation.getEndAfterPadding() + - (mSpanCount - 1 - currentSpan.mIndex) * mSizePerSpan; + otherStart = otherEnd - mSecondaryOrientation.getDecoratedMeasurement(view); + } else { + otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding() + : currentSpan.mIndex * mSizePerSpan + + mSecondaryOrientation.getStartAfterPadding(); + otherEnd = otherStart + mSecondaryOrientation.getDecoratedMeasurement(view); + } + if (mOrientation == VERTICAL) { layoutDecoratedWithMargins(view, otherStart, start, otherEnd, end); } else { @@ -1507,6 +1640,13 @@ private int fill(RecyclerView.Recycler recycler, LayoutState layoutState, updateRemainingSpans(currentSpan, mLayoutState.mLayoutDirection, targetLine); } recycle(recycler, mLayoutState); + if (mLayoutState.mStopInFocusable && view.isFocusable()) { + if (lp.mFullSpan) { + mRemainingSpans.clear(); + } else { + mRemainingSpans.set(currentSpan.mIndex, false); + } + } added = true; } if (!added) { @@ -1558,6 +1698,9 @@ private void attachViewToSpans(View view, LayoutParams lp, LayoutState layoutSta } private void recycle(RecyclerView.Recycler recycler, LayoutState layoutState) { + if (!layoutState.mRecycle || layoutState.mInfinite) { + return; + } if (layoutState.mAvailable == 0) { // easy, recycle line is still valid if (layoutState.mLayoutDirection == LAYOUT_START) { @@ -1913,6 +2056,7 @@ int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) { layoutDir = LAYOUT_START; referenceChildPosition = getFirstChildPosition(); } + mLayoutState.mRecycle = true; updateLayoutState(referenceChildPosition, state); setLayoutStateDirection(layoutDir); mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection; @@ -1982,8 +2126,13 @@ private int findLastReferenceChildPosition(int itemCount) { @Override public RecyclerView.LayoutParams generateDefaultLayoutParams() { - return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT); + if (mOrientation == HORIZONTAL) { + return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.FILL_PARENT); + } else { + return new LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + } } @Override @@ -2009,6 +2158,105 @@ public int getOrientation() { return mOrientation; } + @Nullable + @Override + public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler, + RecyclerView.State state) { + if (getChildCount() == 0) { + return null; + } + + final View directChild = findContainingItemView(focused); + if (directChild == null) { + return null; + } + + ensureOrientationHelper(); + resolveShouldLayoutReverse(); + final int layoutDir = convertFocusDirectionToLayoutDirection(direction); + if (layoutDir == LayoutState.INVALID_LAYOUT) { + return null; + } + LayoutParams prevFocusLayoutParams = (LayoutParams) directChild.getLayoutParams(); + boolean prevFocusFullSpan = prevFocusLayoutParams.mFullSpan; + final Span prevFocusSpan = prevFocusLayoutParams.mSpan; + final int referenceChildPosition; + if (layoutDir == LAYOUT_END) { // layout towards end + referenceChildPosition = getLastChildPosition(); + } else { + referenceChildPosition = getFirstChildPosition(); + } + updateLayoutState(referenceChildPosition, state); + setLayoutStateDirection(layoutDir); + + mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection; + mLayoutState.mAvailable = (int) (MAX_SCROLL_FACTOR * mPrimaryOrientation.getTotalSpace()); + mLayoutState.mStopInFocusable = true; + mLayoutState.mRecycle = false; + fill(recycler, mLayoutState, state); + mLastLayoutFromEnd = mShouldReverseLayout; + if (!prevFocusFullSpan) { + View view = prevFocusSpan.getFocusableViewAfter(referenceChildPosition, layoutDir); + if (view != null && view != directChild) { + return view; + } + } + // either could not find from the desired span or prev view is full span. + // traverse all spans + if (preferLastSpan(layoutDir)) { + for (int i = mSpanCount - 1; i >= 0; i --) { + View view = mSpans[i].getFocusableViewAfter(referenceChildPosition, layoutDir); + if (view != null && view != directChild) { + return view; + } + } + } else { + for (int i = 0; i < mSpanCount; i ++) { + View view = mSpans[i].getFocusableViewAfter(referenceChildPosition, layoutDir); + if (view != null && view != directChild) { + return view; + } + } + } + return null; + } + + /** + * Converts a focusDirection to orientation. + * + * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, + * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, + * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} + * or 0 for not applicable + * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction + * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise. + */ + private int convertFocusDirectionToLayoutDirection(int focusDirection) { + switch (focusDirection) { + case View.FOCUS_BACKWARD: + return LayoutState.LAYOUT_START; + case View.FOCUS_FORWARD: + return LayoutState.LAYOUT_END; + case View.FOCUS_UP: + return mOrientation == VERTICAL ? LayoutState.LAYOUT_START + : LayoutState.INVALID_LAYOUT; + case View.FOCUS_DOWN: + return mOrientation == VERTICAL ? LayoutState.LAYOUT_END + : LayoutState.INVALID_LAYOUT; + case View.FOCUS_LEFT: + return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START + : LayoutState.INVALID_LAYOUT; + case View.FOCUS_RIGHT: + return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END + : LayoutState.INVALID_LAYOUT; + default: + if (DEBUG) { + Log.d(TAG, "Unknown focus request:" + focusDirection); + } + return LayoutState.INVALID_LAYOUT; + } + + } /** * LayoutParams used by StaggeredGridLayoutManager. @@ -2089,7 +2337,7 @@ public final int getSpanIndex() { class Span { static final int INVALID_LINE = Integer.MIN_VALUE; - private ArrayList mViews = new ArrayList(); + private ArrayList mViews = new ArrayList<>(); int mCachedStart = INVALID_LINE; int mCachedEnd = INVALID_LINE; int mDeletedSize = 0; @@ -2273,45 +2521,6 @@ void onOffset(int dt) { } } - // normalized offset is how much this span can scroll - int getNormalizedOffset(int dt, int targetStart, int targetEnd) { - if (mViews.size() == 0) { - return 0; - } - if (dt < 0) { - final int endSpace = getEndLine() - targetEnd; - if (endSpace <= 0) { - return 0; - } - return -dt > endSpace ? -endSpace : dt; - } else { - final int startSpace = targetStart - getStartLine(); - if (startSpace <= 0) { - return 0; - } - return startSpace < dt ? startSpace : dt; - } - } - - /** - * Returns if there is no child between start-end lines - * - * @param start The start line - * @param end The end line - * @return true if a new child can be added between start and end - */ - boolean isEmpty(int start, int end) { - final int count = mViews.size(); - for (int i = 0; i < count; i++) { - final View view = mViews.get(i); - if (mPrimaryOrientation.getDecoratedStart(view) < end && - mPrimaryOrientation.getDecoratedEnd(view) > start) { - return false; - } - } - return true; - } - public int findFirstVisibleItemPosition() { return mReverseLayout ? findOneVisibleChild(mViews.size() - 1, -1, false) @@ -2356,6 +2565,36 @@ int findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) { } return NO_POSITION; } + + /** + * Depending on the layout direction, returns the View that is after the given position. + */ + public View getFocusableViewAfter(int referenceChildPosition, int layoutDir) { + View candidate = null; + if (layoutDir == LAYOUT_START) { + final int limit = mViews.size(); + for (int i = 0; i < limit; i++) { + final View view = mViews.get(i); + if (view.isFocusable() && + (getPosition(view) > referenceChildPosition == mReverseLayout) ) { + candidate = view; + } else { + break; + } + } + } else { + for (int i = mViews.size() - 1; i >= 0; i--) { + final View view = mViews.get(i); + if (view.isFocusable() && + (getPosition(view) > referenceChildPosition == !mReverseLayout)) { + candidate = view; + } else { + break; + } + } + } + return candidate; + } } /** @@ -2532,7 +2771,7 @@ private int invalidateFullSpansAfter(int position) { public void addFullSpanItem(FullSpanItem fullSpanItem) { if (mFullSpanItems == null) { - mFullSpanItems = new ArrayList(); + mFullSpanItems = new ArrayList<>(); } final int size = mFullSpanItems.size(); for (int i = 0; i < size; i++) { @@ -2624,10 +2863,6 @@ int getGapForSpan(int spanIndex) { return mGapPerSpan == null ? 0 : mGapPerSpan[spanIndex]; } - public void invalidateSpanGaps() { - mGapPerSpan = null; - } - @Override public int describeContents() { return 0; @@ -2671,7 +2906,10 @@ public FullSpanItem[] newArray(int size) { } } - static class SavedState implements Parcelable { + /** + * @hide + */ + public static class SavedState implements Parcelable { int mAnchorPosition; int mVisibleAnchorPosition; // Replacement for span info when spans are invalidated @@ -2704,6 +2942,7 @@ public SavedState() { mReverseLayout = in.readInt() == 1; mAnchorLayoutFromEnd = in.readInt() == 1; mLastLayoutRTL = in.readInt() == 1; + //noinspection unchecked mFullSpanItems = in.readArrayList( LazySpanLookup.FullSpanItem.class.getClassLoader()); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ViewInfoStore.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ViewInfoStore.java new file mode 100644 index 0000000000..923319f4e6 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ViewInfoStore.java @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2015 The Android Open Source 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 org.telegram.messenger.support.widget; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import android.support.v4.util.ArrayMap; +import android.support.v4.util.LongSparseArray; +import android.support.v4.util.Pools; + +import static org.telegram.messenger.support.widget.RecyclerView.ViewHolder; +import static org.telegram.messenger.support.widget.RecyclerView.ItemAnimator.ItemHolderInfo; + +import static org.telegram.messenger.support.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_PRE_AND_POST; +import static org.telegram.messenger.support.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_AND_DISAPPEAR; +import static org.telegram.messenger.support.widget.ViewInfoStore.InfoRecord.FLAG_PRE_AND_POST; +import static org.telegram.messenger.support.widget.ViewInfoStore.InfoRecord.FLAG_DISAPPEARED; +import static org.telegram.messenger.support.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR; +import static org.telegram.messenger.support.widget.ViewInfoStore.InfoRecord.FLAG_PRE; +import static org.telegram.messenger.support.widget.ViewInfoStore.InfoRecord.FLAG_POST; + +class ViewInfoStore { + + private static final boolean DEBUG = false; + + /** + * View data records for pre-layout + */ + @VisibleForTesting + final ArrayMap mLayoutHolderMap = new ArrayMap<>(); + + @VisibleForTesting + final LongSparseArray mOldChangedHolders = new LongSparseArray<>(); + + /** + * Clears the state and all existing tracking data + */ + void clear() { + mLayoutHolderMap.clear(); + mOldChangedHolders.clear(); + } + + /** + * Adds the item information to the prelayout tracking + * @param holder The ViewHolder whose information is being saved + * @param info The information to save + */ + void addToPreLayout(ViewHolder holder, ItemHolderInfo info) { + InfoRecord record = mLayoutHolderMap.get(holder); + if (record == null) { + record = InfoRecord.obtain(); + mLayoutHolderMap.put(holder, record); + } + record.preInfo = info; + record.flags |= FLAG_PRE; + } + + boolean isDisappearing(ViewHolder holder) { + final InfoRecord record = mLayoutHolderMap.get(holder); + return record != null && ((record.flags & FLAG_DISAPPEARED) != 0); + } + + /** + * Finds the ItemHolderInfo for the given ViewHolder in preLayout list and removes it. + * + * @param vh The ViewHolder whose information is being queried + * @return The ItemHolderInfo for the given ViewHolder or null if it does not exist + */ + @Nullable + ItemHolderInfo popFromPreLayout(ViewHolder vh) { + return popFromLayoutStep(vh, FLAG_PRE); + } + + /** + * Finds the ItemHolderInfo for the given ViewHolder in postLayout list and removes it. + * + * @param vh The ViewHolder whose information is being queried + * @return The ItemHolderInfo for the given ViewHolder or null if it does not exist + */ + @Nullable + ItemHolderInfo popFromPostLayout(ViewHolder vh) { + return popFromLayoutStep(vh, FLAG_POST); + } + + private ItemHolderInfo popFromLayoutStep(ViewHolder vh, int flag) { + int index = mLayoutHolderMap.indexOfKey(vh); + if (index < 0) { + return null; + } + final InfoRecord record = mLayoutHolderMap.valueAt(index); + if (record != null && (record.flags & flag) != 0) { + record.flags &= ~flag; + final ItemHolderInfo info; + if (flag == FLAG_PRE) { + info = record.preInfo; + } else if (flag == FLAG_POST) { + info = record.postInfo; + } else { + throw new IllegalArgumentException("Must provide flag PRE or POST"); + } + // if not pre-post flag is left, clear. + if ((record.flags & (FLAG_PRE | FLAG_POST)) == 0) { + mLayoutHolderMap.removeAt(index); + InfoRecord.recycle(record); + } + return info; + } + return null; + } + + /** + * Adds the given ViewHolder to the oldChangeHolders list + * @param key The key to identify the ViewHolder. + * @param holder The ViewHolder to store + */ + void addToOldChangeHolders(long key, ViewHolder holder) { + mOldChangedHolders.put(key, holder); + } + + /** + * Adds the given ViewHolder to the appeared in pre layout list. These are Views added by the + * LayoutManager during a pre-layout pass. We distinguish them from other views that were + * already in the pre-layout so that ItemAnimator can choose to run a different animation for + * them. + * + * @param holder The ViewHolder to store + * @param info The information to save + */ + void addToAppearedInPreLayoutHolders(ViewHolder holder, ItemHolderInfo info) { + InfoRecord record = mLayoutHolderMap.get(holder); + if (record == null) { + record = InfoRecord.obtain(); + mLayoutHolderMap.put(holder, record); + } + record.flags |= FLAG_APPEAR; + record.preInfo = info; + } + + /** + * Checks whether the given ViewHolder is in preLayout list + * @param viewHolder The ViewHolder to query + * + * @return True if the ViewHolder is present in preLayout, false otherwise + */ + boolean isInPreLayout(ViewHolder viewHolder) { + final InfoRecord record = mLayoutHolderMap.get(viewHolder); + return record != null && (record.flags & FLAG_PRE) != 0; + } + + /** + * Queries the oldChangeHolder list for the given key. If they are not tracked, simply returns + * null. + * @param key The key to be used to find the ViewHolder. + * + * @return A ViewHolder if exists or null if it does not exist. + */ + ViewHolder getFromOldChangeHolders(long key) { + return mOldChangedHolders.get(key); + } + + /** + * Adds the item information to the post layout list + * @param holder The ViewHolder whose information is being saved + * @param info The information to save + */ + void addToPostLayout(ViewHolder holder, ItemHolderInfo info) { + InfoRecord record = mLayoutHolderMap.get(holder); + if (record == null) { + record = InfoRecord.obtain(); + mLayoutHolderMap.put(holder, record); + } + record.postInfo = info; + record.flags |= FLAG_POST; + } + + /** + * A ViewHolder might be added by the LayoutManager just to animate its disappearance. + * This list holds such items so that we can animate / recycle these ViewHolders properly. + * + * @param holder The ViewHolder which disappeared during a layout. + */ + void addToDisappearedInLayout(ViewHolder holder) { + InfoRecord record = mLayoutHolderMap.get(holder); + if (record == null) { + record = InfoRecord.obtain(); + mLayoutHolderMap.put(holder, record); + } + record.flags |= FLAG_DISAPPEARED; + } + + /** + * Removes a ViewHolder from disappearing list. + * @param holder The ViewHolder to be removed from the disappearing list. + */ + void removeFromDisappearedInLayout(ViewHolder holder) { + InfoRecord record = mLayoutHolderMap.get(holder); + if (record == null) { + return; + } + record.flags &= ~FLAG_DISAPPEARED; + } + + void process(ProcessCallback callback) { + for (int index = mLayoutHolderMap.size() - 1; index >= 0; index --) { + final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index); + final InfoRecord record = mLayoutHolderMap.removeAt(index); + if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) { + // Appeared then disappeared. Not useful for animations. + callback.unused(viewHolder); + } else if ((record.flags & FLAG_DISAPPEARED) != 0) { + // Set as "disappeared" by the LayoutManager (addDisappearingView) + if (record.preInfo == null) { + // similar to appear disappear but happened between different layout passes. + // this can happen when the layout manager is using auto-measure + callback.unused(viewHolder); + } else { + callback.processDisappeared(viewHolder, record.preInfo, record.postInfo); + } + } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) { + // Appeared in the layout but not in the adapter (e.g. entered the viewport) + callback.processAppeared(viewHolder, record.preInfo, record.postInfo); + } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) { + // Persistent in both passes. Animate persistence + callback.processPersistent(viewHolder, record.preInfo, record.postInfo); + } else if ((record.flags & FLAG_PRE) != 0) { + // Was in pre-layout, never been added to post layout + callback.processDisappeared(viewHolder, record.preInfo, null); + } else if ((record.flags & FLAG_POST) != 0) { + // Was not in pre-layout, been added to post layout + callback.processAppeared(viewHolder, record.preInfo, record.postInfo); + } else if ((record.flags & FLAG_APPEAR) != 0) { + // Scrap view. RecyclerView will handle removing/recycling this. + } else if (DEBUG) { + throw new IllegalStateException("record without any reasonable flag combination:/"); + } + InfoRecord.recycle(record); + } + } + + /** + * Removes the ViewHolder from all list + * @param holder The ViewHolder which we should stop tracking + */ + void removeViewHolder(ViewHolder holder) { + for (int i = mOldChangedHolders.size() - 1; i >= 0; i--) { + if (holder == mOldChangedHolders.valueAt(i)) { + mOldChangedHolders.removeAt(i); + break; + } + } + final InfoRecord info = mLayoutHolderMap.remove(holder); + if (info != null) { + InfoRecord.recycle(info); + } + } + + void onDetach() { + InfoRecord.drainCache(); + } + + public void onViewDetached(ViewHolder viewHolder) { + removeFromDisappearedInLayout(viewHolder); + } + + interface ProcessCallback { + void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo, + @Nullable ItemHolderInfo postInfo); + void processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo, + ItemHolderInfo postInfo); + void processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo, + @NonNull ItemHolderInfo postInfo); + void unused(ViewHolder holder); + } + + static class InfoRecord { + // disappearing list + static final int FLAG_DISAPPEARED = 1; + // appear in pre layout list + static final int FLAG_APPEAR = 1 << 1; + // pre layout, this is necessary to distinguish null item info + static final int FLAG_PRE = 1 << 2; + // post layout, this is necessary to distinguish null item info + static final int FLAG_POST = 1 << 3; + static final int FLAG_APPEAR_AND_DISAPPEAR = FLAG_APPEAR | FLAG_DISAPPEARED; + static final int FLAG_PRE_AND_POST = FLAG_PRE | FLAG_POST; + static final int FLAG_APPEAR_PRE_AND_POST = FLAG_APPEAR | FLAG_PRE | FLAG_POST; + int flags; + @Nullable ItemHolderInfo preInfo; + @Nullable ItemHolderInfo postInfo; + static Pools.Pool sPool = new Pools.SimplePool<>(20); + + private InfoRecord() { + } + + static InfoRecord obtain() { + InfoRecord record = sPool.acquire(); + return record == null ? new InfoRecord() : record; + } + + static void recycle(InfoRecord record) { + record.flags = 0; + record.preInfo = null; + record.postInfo = null; + sPool.release(record); + } + + static void drainCache() { + //noinspection StatementWithEmptyBody + while (sPool.acquire() != null); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/helper/ItemTouchHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/helper/ItemTouchHelper.java index 3aece7ccb6..4d458fed85 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/helper/ItemTouchHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/helper/ItemTouchHelper.java @@ -16,13 +16,15 @@ package org.telegram.messenger.support.widget.helper; +import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Rect; import android.os.Build; -import android.support.v4.animation.ValueAnimatorCompat; +import android.support.annotation.Nullable; import android.support.v4.animation.AnimatorCompatHelper; import android.support.v4.animation.AnimatorListenerCompat; import android.support.v4.animation.AnimatorUpdateListenerCompat; +import android.support.v4.animation.ValueAnimatorCompat; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.VelocityTrackerCompat; @@ -31,6 +33,8 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.messenger.support.widget.RecyclerView.OnItemTouchListener; +import org.telegram.messenger.support.widget.RecyclerView.ViewHolder; import android.util.Log; import android.view.GestureDetector; import android.view.HapticFeedbackConstants; @@ -39,14 +43,11 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.ViewParent; +import android.view.animation.Interpolator; import java.util.ArrayList; import java.util.List; -import org.telegram.messenger.support.widget.RecyclerView.OnItemTouchListener; -import org.telegram.messenger.support.widget.RecyclerView.ViewHolder; -import android.view.animation.Interpolator; - /** * This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView. *

@@ -156,6 +157,11 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration private static final int ACTION_MODE_DRAG_MASK = ACTION_MODE_SWIPE_MASK << DIRECTION_FLAG_COUNT; + /** + * The unit we are using to track velocity + */ + private static final int PIXELS_PER_SECOND = 1000; + /** * Views, whose state should be cleared after they are detached from RecyclerView. * This is necessary after swipe dismissing an item. We wait until animator finishes its job @@ -181,6 +187,16 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration float mInitialTouchY; + /** + * Set when ItemTouchHelper is assigned to a RecyclerView. + */ + float mSwipeEscapeVelocity; + + /** + * Set when ItemTouchHelper is assigned to a RecyclerView. + */ + float mMaxSwipeVelocity; + /** * The diff between the last event and initial touch. */ @@ -367,11 +383,11 @@ public void onTouchEvent(RecyclerView recyclerView, MotionEvent event) { break; } case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: if (mVelocityTracker != null) { - mVelocityTracker - .computeCurrentVelocity(1000, mRecyclerView.getMaxFlingVelocity()); + mVelocityTracker.clear(); } + // fall through + case MotionEvent.ACTION_UP: select(null, ACTION_STATE_IDLE); mActivePointerId = ACTIVE_POINTER_ID_NONE; break; @@ -379,11 +395,6 @@ public void onTouchEvent(RecyclerView recyclerView, MotionEvent event) { final int pointerIndex = MotionEventCompat.getActionIndex(event); final int pointerId = MotionEventCompat.getPointerId(event, pointerIndex); if (pointerId == mActivePointerId) { - if (mVelocityTracker != null) { - mVelocityTracker - .computeCurrentVelocity(1000, - mRecyclerView.getMaxFlingVelocity()); - } // This was our active pointer going up. Choose a new // active pointer and adjust accordingly. final int newPointerIndex = pointerIndex == 0 ? 1 : 0; @@ -436,12 +447,14 @@ private static boolean hitTest(View child, float x, float y, float left, float t /** * Attaches the ItemTouchHelper to the provided RecyclerView. If TouchHelper is already - * attached - * to a RecyclerView, it will first detach from the previous one. + * attached to a RecyclerView, it will first detach from the previous one. You can call this + * method with {@code null} to detach it from the current RecyclerView. * - * @param recyclerView The RecyclerView instance to which you want to add this helper. + * @param recyclerView The RecyclerView instance to which you want to add this helper or + * {@code null} if you want to remove ItemTouchHelper from the current + * RecyclerView. */ - public void attachToRecyclerView(RecyclerView recyclerView) { + public void attachToRecyclerView(@Nullable RecyclerView recyclerView) { if (mRecyclerView == recyclerView) { return; // nothing to do } @@ -450,6 +463,9 @@ public void attachToRecyclerView(RecyclerView recyclerView) { } mRecyclerView = recyclerView; if (mRecyclerView != null) { + final Resources resources = recyclerView.getResources(); + mSwipeEscapeVelocity = AndroidUtilities.dp(120); + mMaxSwipeVelocity = AndroidUtilities.dp(800); setupCallbacks(); } } @@ -874,7 +890,6 @@ private int endRecoverAnimation(ViewHolder viewHolder, boolean override) { anim.cancel(); } mRecoverAnimations.remove(i); - anim.mViewHolder.setIsRecyclable(true); return anim.mAnimationType; } } @@ -1010,7 +1025,7 @@ private View findChildView(MotionEvent event) { /** * Starts dragging the provided ViewHolder. By default, ItemTouchHelper starts a drag when a - * View is long pressed. You can disable that behavior via + * View is long pressed. You can disable that behavior by overriding * {@link ItemTouchHelper.Callback#isLongPressDragEnabled()}. *

* For this method to work: @@ -1189,11 +1204,17 @@ private int checkHorizontalSwipe(ViewHolder viewHolder, int flags) { if ((flags & (LEFT | RIGHT)) != 0) { final int dirFlag = mDx > 0 ? RIGHT : LEFT; if (mVelocityTracker != null && mActivePointerId > -1) { + mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND, + mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity)); final float xVelocity = VelocityTrackerCompat .getXVelocity(mVelocityTracker, mActivePointerId); + final float yVelocity = VelocityTrackerCompat + .getYVelocity(mVelocityTracker, mActivePointerId); final int velDirFlag = xVelocity > 0f ? RIGHT : LEFT; + final float absXVelocity = Math.abs(xVelocity); if ((velDirFlag & flags) != 0 && dirFlag == velDirFlag && - Math.abs(xVelocity) >= mRecyclerView.getMinFlingVelocity()) { + absXVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity) && + absXVelocity > Math.abs(yVelocity)) { return velDirFlag; } } @@ -1212,11 +1233,17 @@ private int checkVerticalSwipe(ViewHolder viewHolder, int flags) { if ((flags & (UP | DOWN)) != 0) { final int dirFlag = mDy > 0 ? DOWN : UP; if (mVelocityTracker != null && mActivePointerId > -1) { + mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND, + mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity)); + final float xVelocity = VelocityTrackerCompat + .getXVelocity(mVelocityTracker, mActivePointerId); final float yVelocity = VelocityTrackerCompat .getYVelocity(mVelocityTracker, mActivePointerId); final int velDirFlag = yVelocity > 0f ? DOWN : UP; + final float absYVelocity = Math.abs(yVelocity); if ((velDirFlag & flags) != 0 && velDirFlag == dirFlag && - Math.abs(yVelocity) >= mRecyclerView.getMinFlingVelocity()) { + absYVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity) && + absYVelocity > Math.abs(xVelocity)) { return velDirFlag; } } @@ -1357,7 +1384,7 @@ public float getInterpolation(float t) { /** * Drag scroll speed keeps accelerating until this many milliseconds before being capped. */ - private static final long DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS = 500; + private static final long DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS = 2000; private int mCachedMaxScrollSpeed = -1; @@ -1372,7 +1399,8 @@ public float getInterpolation(float t) { } /** - * Returns the {@link ItemTouchUIUtil} that is used by the {@link Callback} class for visual + * Returns the {@link ItemTouchUIUtil} that is used by the {@link Callback} class for + * visual * changes on Views in response to user interactions. {@link ItemTouchUIUtil} has different * implementations for different platform versions. *

@@ -1661,6 +1689,54 @@ public float getMoveThreshold(ViewHolder viewHolder) { return .5f; } + /** + * Defines the minimum velocity which will be considered as a swipe action by the user. + *

+ * You can increase this value to make it harder to swipe or decrease it to make it easier. + * Keep in mind that ItemTouchHelper also checks the perpendicular velocity and makes sure + * current direction velocity is larger then the perpendicular one. Otherwise, user's + * movement is ambiguous. You can change the threshold by overriding + * {@link #getSwipeVelocityThreshold(float)}. + *

+ * The velocity is calculated in pixels per second. + *

+ * The default framework value is passed as a parameter so that you can modify it with a + * multiplier. + * + * @param defaultValue The default value (in pixels per second) used by the + * ItemTouchHelper. + * @return The minimum swipe velocity. The default implementation returns the + * defaultValue parameter. + * @see #getSwipeVelocityThreshold(float) + * @see #getSwipeThreshold(ViewHolder) + */ + public float getSwipeEscapeVelocity(float defaultValue) { + return defaultValue; + } + + /** + * Defines the maximum velocity ItemTouchHelper will ever calculate for pointer movements. + *

+ * To consider a movement as swipe, ItemTouchHelper requires it to be larger than the + * perpendicular movement. If both directions reach to the max threshold, none of them will + * be considered as a swipe because it is usually an indication that user rather tried to + * scroll then swipe. + *

+ * The velocity is calculated in pixels per second. + *

+ * You can customize this behavior by changing this method. If you increase the value, it + * will be easier for the user to swipe diagonally and if you decrease the value, user will + * need to make a rather straight finger movement to trigger a swipe. + * + * @param defaultValue The default value(in pixels per second) used by the ItemTouchHelper. + * @return The velocity cap for pointer movements. The default implementation returns the + * defaultValue parameter. + * @see #getSwipeEscapeVelocity(float) + */ + public float getSwipeVelocityThreshold(float defaultValue) { + return defaultValue; + } + /** * Called by ItemTouchHelper to select a drop target from the list of ViewHolders that * are under the dragged View. @@ -1779,7 +1855,6 @@ public ViewHolder chooseDropTarget(ViewHolder selected, * @param actionState One of {@link ItemTouchHelper#ACTION_STATE_IDLE}, * {@link ItemTouchHelper#ACTION_STATE_SWIPE} or * {@link ItemTouchHelper#ACTION_STATE_DRAG}. - * * @see #clearView(RecyclerView, RecyclerView.ViewHolder) */ public void onSelectedChanged(ViewHolder viewHolder, int actionState) { @@ -1902,7 +1977,6 @@ private void onDrawOver(Canvas c, RecyclerView parent, ViewHolder selected, final RecoverAnimation anim = recoverAnimationList.get(i); if (anim.mEnded && !anim.mIsPendingCleanup) { recoverAnimationList.remove(i); - anim.mViewHolder.setIsRecyclable(true); } else if (!anim.mEnded) { hasRunningAnimation = true; } @@ -2038,15 +2112,14 @@ public long getAnimationDuration(RecyclerView recyclerView, int animationType, * the faster the list will scroll. Similarly, the larger portion of the View is out of * bounds, the faster the RecyclerView will scroll. * - * @param recyclerView The RecyclerView instance to which ItemTouchHelper is attached - * to. + * @param recyclerView The RecyclerView instance to which ItemTouchHelper is + * attached to. * @param viewSize The total size of the View in scroll direction, excluding * item decorations. * @param viewSizeOutOfBounds The total size of the View that is out of bounds. This value * is negative if the View is dragged towards left or top edge. * @param totalSize The total size of RecyclerView in the scroll direction. * @param msSinceStartScroll The time passed since View is kept out of bounds. - * * @return The amount that RecyclerView should scroll. Keep in mind that this value will * be passed to {@link RecyclerView#scrollBy(int, int)} method. */ @@ -2314,6 +2387,9 @@ public void onAnimationStart(ValueAnimatorCompat animation) { @Override public void onAnimationEnd(ValueAnimatorCompat animation) { + if (!mEnded) { + mViewHolder.setIsRecyclable(true); + } mEnded = true; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/helper/ItemTouchUIUtilImpl.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/helper/ItemTouchUIUtilImpl.java index f0d631f5d3..c0e8915988 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/helper/ItemTouchUIUtilImpl.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/helper/ItemTouchUIUtilImpl.java @@ -21,16 +21,16 @@ import org.telegram.messenger.support.widget.RecyclerView; import android.view.View; + /** * Package private class to keep implementations. Putting them inside ItemTouchUIUtil makes them * public API, which is not desired in this case. */ class ItemTouchUIUtilImpl { - static class Lollipop extends Honeycomb { @Override public void onDraw(Canvas c, RecyclerView recyclerView, View view, - float dX, float dY, int actionState, boolean isCurrentlyActive) { + float dX, float dY, int actionState, boolean isCurrentlyActive) { if (isCurrentlyActive) { Object originalElevation = view.getTag(); if (originalElevation == null) { @@ -85,14 +85,14 @@ public void onSelected(View view) { @Override public void onDraw(Canvas c, RecyclerView recyclerView, View view, - float dX, float dY, int actionState, boolean isCurrentlyActive) { + float dX, float dY, int actionState, boolean isCurrentlyActive) { ViewCompat.setTranslationX(view, dX); ViewCompat.setTranslationY(view, dY); } @Override public void onDrawOver(Canvas c, RecyclerView recyclerView, - View view, float dX, float dY, int actionState, boolean isCurrentlyActive) { + View view, float dX, float dY, int actionState, boolean isCurrentlyActive) { } } @@ -100,7 +100,7 @@ public void onDrawOver(Canvas c, RecyclerView recyclerView, static class Gingerbread implements ItemTouchUIUtil { private void draw(Canvas c, RecyclerView parent, View view, - float dX, float dY) { + float dX, float dY) { c.save(); c.translate(dX, dY); parent.drawChild(c, view, 0); @@ -119,7 +119,7 @@ public void onSelected(View view) { @Override public void onDraw(Canvas c, RecyclerView recyclerView, View view, - float dX, float dY, int actionState, boolean isCurrentlyActive) { + float dX, float dY, int actionState, boolean isCurrentlyActive) { if (actionState != ItemTouchHelper.ACTION_STATE_DRAG) { draw(c, recyclerView, view, dX, dY); } @@ -127,8 +127,8 @@ public void onDraw(Canvas c, RecyclerView recyclerView, View view, @Override public void onDrawOver(Canvas c, RecyclerView recyclerView, - View view, float dX, float dY, - int actionState, boolean isCurrentlyActive) { + View view, float dX, float dY, + int actionState, boolean isCurrentlyActive) { if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { draw(c, recyclerView, view, dX, dY); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/Track.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/Track.java index a9614e1b41..22762d7d03 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/video/Track.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/Track.java @@ -67,8 +67,9 @@ public class Track { samplingFrequencyIndexMap.put(8000, 0xb); } - public Track(int id, MediaFormat format, boolean isAudio) throws Exception { + public Track(int id, MediaFormat format, boolean audio) throws Exception { trackId = id; + isAudio = audio; if (!isAudio) { sampleDurations.add((long) 3015); duration = 3015; @@ -136,7 +137,6 @@ public Track(int id, MediaFormat format, boolean isAudio) throws Exception { } else { sampleDurations.add((long) 1024); duration = 1024; - isAudio = true; volume = 1; timeScale = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); handler = "soun"; @@ -184,15 +184,18 @@ public long getTrackId() { } public void addSample(long offset, MediaCodec.BufferInfo bufferInfo) { + long delta = bufferInfo.presentationTimeUs - lastPresentationTimeUs; + if (delta < 0) { + return; + } boolean isSyncFrame = !isAudio && (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0; samples.add(new Sample(offset, bufferInfo.size)); if (syncSamples != null && isSyncFrame) { syncSamples.add(samples.size()); } - long delta = bufferInfo.presentationTimeUs - lastPresentationTimeUs; - lastPresentationTimeUs = bufferInfo.presentationTimeUs; delta = (delta * timeScale + 500000L) / 1000000L; + lastPresentationTimeUs = bufferInfo.presentationTimeUs; if (!first) { sampleDurations.add(sampleDurations.size() - 1, delta); duration += delta; diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java b/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java index ee31d06b5b..09729b9804 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java @@ -56,7 +56,7 @@ public class TLRPC { public static final int MESSAGE_FLAG_HAS_BOT_ID = 0x00000800; public static final int MESSAGE_FLAG_MEGAGROUP = 0x80000000; - public static final int LAYER = 48; + public static final int LAYER = 50; public static class ChatPhoto extends TLObject { public FileLocation photo_small; @@ -2129,6 +2129,123 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class auth_SentCodeType extends TLObject { + public int length; + public String pattern; + + public static auth_SentCodeType TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + auth_SentCodeType result = null; + switch(constructor) { + case 0x3dbb5986: + result = new TL_auth_sentCodeTypeApp(); + break; + case 0x5353e5a7: + result = new TL_auth_sentCodeTypeCall(); + break; + case 0xab03c6d9: + result = new TL_auth_sentCodeTypeFlashCall(); + break; + case 0xc000bba2: + result = new TL_auth_sentCodeTypeSms(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in auth_SentCodeType", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_auth_sentCodeTypeApp extends auth_SentCodeType { + public static int constructor = 0x3dbb5986; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + length = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(length); + } + } + + public static class TL_auth_sentCodeTypeCall extends auth_SentCodeType { + public static int constructor = 0x5353e5a7; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + length = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(length); + } + } + + public static class TL_auth_sentCodeTypeFlashCall extends auth_SentCodeType { + public static int constructor = 0xab03c6d9; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + pattern = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(pattern); + } + } + + public static class TL_auth_sentCodeTypeSms extends auth_SentCodeType { + public static int constructor = 0xc000bba2; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + length = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(length); + } + } + + public static class TL_peerSettings extends TLObject { + public static int constructor = 0x818426cd; + + public int flags; + public boolean report_spam; + + public static TL_peerSettings TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_peerSettings.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_peerSettings", constructor)); + } else { + return null; + } + } + TL_peerSettings result = new TL_peerSettings(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + report_spam = (flags & 1) != 0; + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = report_spam ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); + } + } + public static class FoundGif extends TLObject { public String url; public Photo photo; @@ -2573,69 +2690,54 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class auth_SentCode extends TLObject { + public static class TL_auth_sentCode extends TLObject { + public static int constructor = 0x5e002502; + + public int flags; public boolean phone_registered; + public auth_SentCodeType type; public String phone_code_hash; - public int send_call_timeout; - public boolean is_password; + public auth_CodeType next_type; + public int timeout; - public static auth_SentCode TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - auth_SentCode result = null; - switch(constructor) { - case 0xe325edcf: - result = new TL_auth_sentAppCode(); - break; - case 0xefed51d9: - result = new TL_auth_sentCode(); - break; - } - if (result == null && exception) { - throw new RuntimeException(String.format("can't parse magic %x in auth_SentCode", constructor)); - } - if (result != null) { - result.readParams(stream, exception); + public static TL_auth_sentCode TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_auth_sentCode.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_auth_sentCode", constructor)); + } else { + return null; + } } + TL_auth_sentCode result = new TL_auth_sentCode(); + result.readParams(stream, exception); return result; } - } - - public static class TL_auth_sentAppCode extends auth_SentCode { - public static int constructor = 0xe325edcf; - public void readParams(AbstractSerializedData stream, boolean exception) { - phone_registered = stream.readBool(exception); - phone_code_hash = stream.readString(exception); - send_call_timeout = stream.readInt32(exception); - is_password = stream.readBool(exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeBool(phone_registered); - stream.writeString(phone_code_hash); - stream.writeInt32(send_call_timeout); - stream.writeBool(is_password); - } - } - - public static class TL_auth_sentCode extends auth_SentCode { - public static int constructor = 0xefed51d9; - - - public void readParams(AbstractSerializedData stream, boolean exception) { - phone_registered = stream.readBool(exception); + flags = stream.readInt32(exception); + phone_registered = (flags & 1) != 0; + type = auth_SentCodeType.TLdeserialize(stream, stream.readInt32(exception), exception); phone_code_hash = stream.readString(exception); - send_call_timeout = stream.readInt32(exception); - is_password = stream.readBool(exception); + if ((flags & 2) != 0) { + next_type = auth_CodeType.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 4) != 0) { + timeout = stream.readInt32(exception); + } } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeBool(phone_registered); + stream.writeInt32(flags); + flags = phone_registered ? (flags | 1) : (flags &~ 1); + type.serializeToStream(stream); stream.writeString(phone_code_hash); - stream.writeInt32(send_call_timeout); - stream.writeBool(is_password); + if ((flags & 2) != 0) { + next_type.serializeToStream(stream); + } + if ((flags & 4) != 0) { + stream.writeInt32(timeout); + } } } @@ -3388,20 +3490,22 @@ public void serializeToStream(AbstractSerializedData stream) { public static class BotInfo extends TLObject { public int user_id; - public int version; - public String share_text; public String description; public ArrayList commands = new ArrayList<>(); + public int version; public static BotInfo TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { BotInfo result = null; switch(constructor) { case 0xbb2e37ce: - result = new TL_botInfoEmpty(); + result = new TL_botInfoEmpty_layer48(); break; - case 0x9cf585d: + case 0x98e81d3a: result = new TL_botInfo(); break; + case 0x9cf585d: + result = new TL_botInfo_layer48(); + break; } if (result == null && exception) { throw new RuntimeException(String.format("can't parse magic %x in BotInfo", constructor)); @@ -3413,7 +3517,7 @@ public static BotInfo TLdeserialize(AbstractSerializedData stream, int construct } } - public static class TL_botInfoEmpty extends BotInfo { + public static class TL_botInfoEmpty_layer48 extends TL_botInfo { public static int constructor = 0xbb2e37ce; @@ -3423,13 +3527,50 @@ public void serializeToStream(AbstractSerializedData stream) { } public static class TL_botInfo extends BotInfo { + public static int constructor = 0x98e81d3a; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + user_id = stream.readInt32(exception); + description = stream.readString(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TL_botCommand object = TL_botCommand.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + commands.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(user_id); + stream.writeString(description); + stream.writeInt32(0x1cb5c415); + int count = commands.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + commands.get(a).serializeToStream(stream); + } + } + } + + public static class TL_botInfo_layer48 extends TL_botInfo { public static int constructor = 0x9cf585d; public void readParams(AbstractSerializedData stream, boolean exception) { user_id = stream.readInt32(exception); version = stream.readInt32(exception); - share_text = stream.readString(exception); + stream.readString(exception); description = stream.readString(exception); int magic = stream.readInt32(exception); if (magic != 0x1cb5c415) { @@ -3452,7 +3593,7 @@ public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); stream.writeInt32(user_id); stream.writeInt32(version); - stream.writeString(share_text); + stream.writeString(""); stream.writeString(description); stream.writeInt32(0x1cb5c415); int count = commands.size(); @@ -3833,6 +3974,7 @@ public void serializeToStream(AbstractSerializedData stream) { public static class ChatFull extends TLObject { public int flags; public boolean can_view_participants; + public boolean can_set_username; public int id; public String about; public int participants_count; @@ -3844,22 +3986,26 @@ public static class ChatFull extends TLObject { public Photo chat_photo; public PeerNotifySettings notify_settings; public ExportedChatInvite exported_invite; - public ChatParticipants participants; public ArrayList bot_info = new ArrayList<>(); public int migrated_from_chat_id; public int migrated_from_max_id; + public int pinned_msg_id; + public ChatParticipants participants; public static ChatFull TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { ChatFull result = null; switch(constructor) { - case 0xfab31aa3: - result = new TL_channelFull_old(); + case 0x97bee562: + result = new TL_channelFull(); break; case 0x2e02a614: result = new TL_chatFull(); break; case 0x9e341ddf: - result = new TL_channelFull(); + result = new TL_channelFull_layer48(); + break; + case 0xfab31aa3: + result = new TL_channelFull_old(); break; } if (result == null && exception) { @@ -3872,13 +4018,14 @@ public static ChatFull TLdeserialize(AbstractSerializedData stream, int construc } } - public static class TL_channelFull_old extends TL_channelFull { - public static int constructor = 0xfab31aa3; + public static class TL_channelFull extends ChatFull { + public static int constructor = 0x97bee562; public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); can_view_participants = (flags & 8) != 0; + can_set_username = (flags & 64) != 0; id = stream.readInt32(exception); about = stream.readString(exception); if ((flags & 1) != 0) { @@ -3896,11 +4043,36 @@ public void readParams(AbstractSerializedData stream, boolean exception) { chat_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); notify_settings = PeerNotifySettings.TLdeserialize(stream, stream.readInt32(exception), exception); exported_invite = ExportedChatInvite.TLdeserialize(stream, stream.readInt32(exception), exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + BotInfo object = BotInfo.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + bot_info.add(object); + } + if ((flags & 16) != 0) { + migrated_from_chat_id = stream.readInt32(exception); + } + if ((flags & 16) != 0) { + migrated_from_max_id = stream.readInt32(exception); + } + if ((flags & 32) != 0) { + pinned_msg_id = stream.readInt32(exception); + } } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); flags = can_view_participants ? (flags | 8) : (flags &~ 8); + flags = can_set_username ? (flags | 64) : (flags &~ 64); stream.writeInt32(flags); stream.writeInt32(id); stream.writeString(about); @@ -3919,6 +4091,21 @@ public void serializeToStream(AbstractSerializedData stream) { chat_photo.serializeToStream(stream); notify_settings.serializeToStream(stream); exported_invite.serializeToStream(stream); + stream.writeInt32(0x1cb5c415); + int count = bot_info.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + bot_info.get(a).serializeToStream(stream); + } + if ((flags & 16) != 0) { + stream.writeInt32(migrated_from_chat_id); + } + if ((flags & 16) != 0) { + stream.writeInt32(migrated_from_max_id); + } + if ((flags & 32) != 0) { + stream.writeInt32(pinned_msg_id); + } } } @@ -3965,7 +4152,7 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_channelFull extends ChatFull { + public static class TL_channelFull_layer48 extends TL_channelFull { public static int constructor = 0x9e341ddf; @@ -4014,7 +4201,7 @@ public void readParams(AbstractSerializedData stream, boolean exception) { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - flags = can_view_participants ? (flags | 8) : (flags &~8); + flags = can_view_participants ? (flags | 8) : (flags &~ 8); stream.writeInt32(flags); stream.writeInt32(id); stream.writeString(about); @@ -4048,6 +4235,56 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_channelFull_old extends TL_channelFull { + public static int constructor = 0xfab31aa3; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + can_view_participants = (flags & 8) != 0; + id = stream.readInt32(exception); + about = stream.readString(exception); + if ((flags & 1) != 0) { + participants_count = stream.readInt32(exception); + } + if ((flags & 2) != 0) { + admins_count = stream.readInt32(exception); + } + if ((flags & 4) != 0) { + kicked_count = stream.readInt32(exception); + } + read_inbox_max_id = stream.readInt32(exception); + unread_count = stream.readInt32(exception); + unread_important_count = stream.readInt32(exception); + chat_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + notify_settings = PeerNotifySettings.TLdeserialize(stream, stream.readInt32(exception), exception); + exported_invite = ExportedChatInvite.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = can_view_participants ? (flags | 8) : (flags &~ 8); + stream.writeInt32(flags); + stream.writeInt32(id); + stream.writeString(about); + if ((flags & 1) != 0) { + stream.writeInt32(participants_count); + } + if ((flags & 2) != 0) { + stream.writeInt32(admins_count); + } + if ((flags & 4) != 0) { + stream.writeInt32(kicked_count); + } + stream.writeInt32(read_inbox_max_id); + stream.writeInt32(unread_count); + stream.writeInt32(unread_important_count); + chat_photo.serializeToStream(stream); + notify_settings.serializeToStream(stream); + exported_invite.serializeToStream(stream); + } + } + public static class TL_inputPeerNotifySettings extends TLObject { public static int constructor = 0x38935eb2; @@ -5772,6 +6009,9 @@ public static MessageAction TLdeserialize(AbstractSerializedData stream, int con case 0x95d2ac92: result = new TL_messageActionChannelCreate(); break; + case 0x94bd38ed: + result = new TL_messageActionPinMessage(); + break; case 0x95e3fbef: result = new TL_messageActionChatDeletePhoto(); break; @@ -6040,6 +6280,15 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_messageActionPinMessage extends MessageAction { + public static int constructor = 0x94bd38ed; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + public static class TL_messageActionChatDeletePhoto extends MessageAction { public static int constructor = 0x95e3fbef; @@ -8116,6 +8365,7 @@ public static class Update extends TLObject { public ArrayList rules = new ArrayList<>(); public UserStatus status; public int views; + public int flags; public String type; public MessageMedia media; public boolean popup; @@ -8208,7 +8458,7 @@ public static Update TLdeserialize(AbstractSerializedData stream, int constructo case 0x98a12b4b: result = new TL_updateChannelMessageViews(); break; - case 0x60946422: + case 0xeb0467fb: result = new TL_updateChannelTooLong(); break; case 0x382dd3e4: @@ -8232,6 +8482,9 @@ public static Update TLdeserialize(AbstractSerializedData stream, int constructo case 0xa7332b73: result = new TL_updateUserName(); break; + case 0x98592475: + result = new TL_updateChannelPinnedMessage(); + break; case 0xc37521c9: result = new TL_updateDeleteChannelMessages(); break; @@ -8687,16 +8940,24 @@ public void serializeToStream(AbstractSerializedData stream) { } public static class TL_updateChannelTooLong extends Update { - public static int constructor = 0x60946422; + public static int constructor = 0xeb0467fb; public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); channel_id = stream.readInt32(exception); + if ((flags & 1) != 0) { + pts = stream.readInt32(exception); + } } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeInt32(flags); stream.writeInt32(channel_id); + if ((flags & 1) != 0) { + stream.writeInt32(pts); + } } } @@ -8830,6 +9091,22 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_updateChannelPinnedMessage extends Update { + public static int constructor = 0x98592475; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + channel_id = stream.readInt32(exception); + id = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(channel_id); + stream.writeInt32(id); + } + } + public static class TL_updateDeleteChannelMessages extends Update { public static int constructor = 0xc37521c9; @@ -12310,6 +12587,7 @@ public static class Chat extends TLObject { public boolean democracy; public boolean signatures; public String restriction_reason; + public boolean min; public InputChannel migrated_to; public String address; public String venue; @@ -12338,7 +12616,7 @@ public static Chat TLdeserialize(AbstractSerializedData stream, int constructor, result = new TL_chatEmpty(); break; case 0x4b1b7506: - result = new TL_channel(); + result = new TL_channel_layer48(); break; case 0x75eaea5a: result = new TL_geoChat(); @@ -12346,6 +12624,9 @@ public static Chat TLdeserialize(AbstractSerializedData stream, int constructor, case 0x2d85832c: result = new TL_channelForbidden(); break; + case 0xa14dca52: + result = new TL_channel(); + break; case 0x6e9c9bc7: result = new TL_chat_old(); break; @@ -12413,6 +12694,71 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_channel extends Chat { + public static int constructor = 0xa14dca52; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + creator = (flags & 1) != 0; + kicked = (flags & 2) != 0; + left = (flags & 4) != 0; + editor = (flags & 8) != 0; + moderator = (flags & 16) != 0; + broadcast = (flags & 32) != 0; + verified = (flags & 128) != 0; + megagroup = (flags & 256) != 0; + restricted = (flags & 512) != 0; + democracy = (flags & 1024) != 0; + signatures = (flags & 2048) != 0; + min = (flags & 4096) != 0; + id = stream.readInt32(exception); + if ((flags & 8192) != 0) { + access_hash = stream.readInt64(exception); + } + title = stream.readString(exception); + if ((flags & 64) != 0) { + username = stream.readString(exception); + } + photo = ChatPhoto.TLdeserialize(stream, stream.readInt32(exception), exception); + date = stream.readInt32(exception); + version = stream.readInt32(exception); + if ((flags & 512) != 0) { + restriction_reason = stream.readString(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = creator ? (flags | 1) : (flags &~ 1); + flags = kicked ? (flags | 2) : (flags &~ 2); + flags = left ? (flags | 4) : (flags &~ 4); + flags = editor ? (flags | 8) : (flags &~ 8); + flags = moderator ? (flags | 16) : (flags &~ 16); + flags = broadcast ? (flags | 32) : (flags &~ 32); + flags = verified ? (flags | 128) : (flags &~ 128); + flags = megagroup ? (flags | 256) : (flags &~ 256); + flags = restricted ? (flags | 512) : (flags &~ 512); + flags = democracy ? (flags | 1024) : (flags &~ 1024); + flags = signatures ? (flags | 2048) : (flags &~ 2048); + flags = min ? (flags | 4096) : (flags &~ 4096); + stream.writeInt32(flags); + stream.writeInt32(id); + if ((flags & 8192) != 0) { + stream.writeInt64(access_hash); + } + stream.writeString(title); + if ((flags & 64) != 0) { + stream.writeString(username); + } + photo.serializeToStream(stream); + stream.writeInt32(date); + stream.writeInt32(version); + if ((flags & 512) != 0) { + stream.writeString(restriction_reason); + } + } + } + public static class TL_channel_old extends TL_channel { public static int constructor = 0x678e9587; @@ -12600,7 +12946,7 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_channel extends Chat { + public static class TL_channel_layer48 extends TL_channel { public static int constructor = 0x4b1b7506; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -12879,6 +13225,58 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class auth_CodeType extends TLObject { + + public static auth_CodeType TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + auth_CodeType result = null; + switch(constructor) { + case 0x72a3158c: + result = new TL_auth_codeTypeSms(); + break; + case 0x741cd3e3: + result = new TL_auth_codeTypeCall(); + break; + case 0x226ccefb: + result = new TL_auth_codeTypeFlashCall(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in auth_CodeType", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_auth_codeTypeSms extends auth_CodeType { + public static int constructor = 0x72a3158c; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_auth_codeTypeCall extends auth_CodeType { + public static int constructor = 0x741cd3e3; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_auth_codeTypeFlashCall extends auth_CodeType { + public static int constructor = 0x226ccefb; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + public static class MessagesFilter extends TLObject { public static MessagesFilter TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { @@ -13326,37 +13724,6 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_account_sentChangePhoneCode extends TLObject { - public static int constructor = 0xa4f58c4c; - - public String phone_code_hash; - public int send_call_timeout; - - public static TL_account_sentChangePhoneCode TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - if (TL_account_sentChangePhoneCode.constructor != constructor) { - if (exception) { - throw new RuntimeException(String.format("can't parse magic %x in TL_account_sentChangePhoneCode", constructor)); - } else { - return null; - } - } - TL_account_sentChangePhoneCode result = new TL_account_sentChangePhoneCode(); - result.readParams(stream, exception); - return result; - } - - public void readParams(AbstractSerializedData stream, boolean exception) { - phone_code_hash = stream.readString(exception); - send_call_timeout = stream.readInt32(exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeString(phone_code_hash); - stream.writeInt32(send_call_timeout); - } - } - public static class messages_SavedGifs extends TLObject { public int hash; public ArrayList gifs = new ArrayList<>(); @@ -13707,13 +14074,15 @@ public void serializeToStream(AbstractSerializedData stream) { } public static class TL_userFull extends TLObject { - public static int constructor = 0x5a89ac5b; + public static int constructor = 0x5932fc03; + public int flags; + public boolean blocked; public User user; + public String about; public TL_contacts_link link; public Photo profile_photo; public PeerNotifySettings notify_settings; - public boolean blocked; public BotInfo bot_info; public static TL_userFull TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { @@ -13730,22 +14099,38 @@ public static TL_userFull TLdeserialize(AbstractSerializedData stream, int const } public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + blocked = (flags & 1) != 0; user = User.TLdeserialize(stream, stream.readInt32(exception), exception); - link = TL_contacts_link.TLdeserialize(stream, stream.readInt32(exception), exception); - profile_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 2) != 0) { + about = stream.readString(exception); + } + link = TL_contacts_link.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 4) != 0) { + profile_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + } notify_settings = PeerNotifySettings.TLdeserialize(stream, stream.readInt32(exception), exception); - blocked = stream.readBool(exception); - bot_info = BotInfo.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 8) != 0) { + bot_info = BotInfo.TLdeserialize(stream, stream.readInt32(exception), exception); + } } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - user.serializeToStream(stream); + flags = blocked ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); + user.serializeToStream(stream); + if ((flags & 2) != 0) { + stream.writeString(about); + } link.serializeToStream(stream); - profile_photo.serializeToStream(stream); + if ((flags & 4) != 0) { + profile_photo.serializeToStream(stream); + } notify_settings.serializeToStream(stream); - stream.writeBool(blocked); - bot_info.serializeToStream(stream); + if ((flags & 8) != 0) { + bot_info.serializeToStream(stream); + } } } @@ -15114,45 +15499,34 @@ public void serializeToStream(AbstractSerializedData stream) { } public static class TL_auth_sendCode extends TLObject { - public static int constructor = 0x768d5f4d; + public static int constructor = 0xccfd70cf; + public int flags; + public boolean allow_flashcall; public String phone_number; - public int sms_type; + public boolean current_number; public int api_id; public String api_hash; public String lang_code; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return auth_SentCode.TLdeserialize(stream, constructor, exception); + return TL_auth_sentCode.TLdeserialize(stream, constructor, exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = allow_flashcall ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); stream.writeString(phone_number); - stream.writeInt32(sms_type); + if ((flags & 1) != 0) { + stream.writeBool(current_number); + } stream.writeInt32(api_id); stream.writeString(api_hash); stream.writeString(lang_code); } } - public static class TL_auth_sendCall extends TLObject { - public static int constructor = 0x3c51564; - - public String phone_number; - public String phone_code_hash; - - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return Bool.TLdeserialize(stream, constructor, exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeString(phone_number); - stream.writeString(phone_code_hash); - } - } - public static class TL_auth_signUp extends TLObject { public static int constructor = 0x1b067634; @@ -15386,19 +15760,29 @@ public void serializeToStream(AbstractSerializedData stream) { } public static class TL_account_updateProfile extends TLObject { - public static int constructor = 0xf0888d68; + public static int constructor = 0x78515775; + public int flags; public String first_name; public String last_name; + public String about; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return User.TLdeserialize(stream, constructor, exception); + return User.TLdeserialize(stream, constructor, exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeString(first_name); - stream.writeString(last_name); + stream.writeInt32(flags); + if ((flags & 1) != 0) { + stream.writeString(first_name); + } + if ((flags & 2) != 0) { + stream.writeString(last_name); + } + if ((flags & 4) != 0) { + stream.writeString(about); + } } } @@ -16084,6 +16468,36 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_messages_hideReportSpam extends TLObject { + public static int constructor = 0xa8f1709b; + + public InputPeer peer; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + } + } + + public static class TL_messages_getPeerSettings extends TLObject { + public static int constructor = 0x3672e09c; + + public InputPeer peer; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_peerSettings.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + } + } + public static class TL_messages_getChats extends TLObject { public static int constructor = 0x3c6aa187; @@ -16864,23 +17278,6 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_auth_sendSms extends TLObject { - public static int constructor = 0xda9f3e8; - - public String phone_number; - public String phone_code_hash; - - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return Bool.TLdeserialize(stream, constructor, exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeString(phone_number); - stream.writeString(phone_code_hash); - } - } - public static class TL_messages_readMessageContents extends TLObject { public static int constructor = 0x36a73f77; @@ -17044,17 +17441,25 @@ public void serializeToStream(AbstractSerializedData stream) { } public static class TL_account_sendChangePhoneCode extends TLObject { - public static int constructor = 0xa407a8f4; + public static int constructor = 0x8e57deb; + public int flags; + public boolean allow_flashcall; public String phone_number; + public boolean current_number; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return TL_account_sentChangePhoneCode.TLdeserialize(stream, constructor, exception); + return TL_auth_sentCode.TLdeserialize(stream, constructor, exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = allow_flashcall ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); stream.writeString(phone_number); + if ((flags & 1) != 0) { + stream.writeBool(current_number); + } } } @@ -17255,6 +17660,40 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_auth_resendCode extends TLObject { + public static int constructor = 0x3ef1a9bf; + + public String phone_number; + public String phone_code_hash; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_auth_sentCode.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(phone_number); + stream.writeString(phone_code_hash); + } + } + + public static class TL_auth_cancelCode extends TLObject { + public static int constructor = 0x1f040578; + + public String phone_number; + public String phone_code_hash; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(phone_number); + stream.writeString(phone_code_hash); + } + } + public static class TL_messages_exportChatInvite extends TLObject { public static int constructor = 0x7d885289; @@ -18137,6 +18576,27 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_channels_updatePinnedMessage extends TLObject { + public static int constructor = 0xa72ded52; + + public int flags; + public boolean silent; + public InputChannel channel; + public int id; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + flags = silent ? (flags | 1) : (flags &~ 1); + channel.serializeToStream(stream); + stream.writeInt32(id); + } + } + //manually created //MessageMedia start @@ -18345,8 +18805,6 @@ public static class Message extends TLObject { public Peer to_id; public int date; public MessageAction action; - //public Peer fwd_from_id; - //public int fwd_date; public int reply_to_msg_id; public long reply_to_random_id; public String message; @@ -18404,7 +18862,7 @@ public static Message TLdeserialize(AbstractSerializedData stream, int construct result = new TL_message_old7(); break; case 0xc06b9607: - result = new TL_messageService(); + result = new TL_messageService_layer48(); break; case 0x83e5de54: result = new TL_messageEmpty(); @@ -18430,6 +18888,9 @@ public static Message TLdeserialize(AbstractSerializedData stream, int construct case 0x555555F8: result = new TL_message_secret_old(); //custom break; + case 0x9e19a1f6: + result = new TL_messageService(); + break; case 0xf07814c8: result = new TL_message_old5(); //custom break; @@ -19152,7 +19613,7 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_messageService extends Message { + public static class TL_messageService_layer48 extends TL_messageService { public static int constructor = 0xc06b9607; @@ -19576,6 +20037,51 @@ public void serializeToStream(AbstractSerializedData stream) { stream.writeString(attachPath); } } + + public static class TL_messageService extends Message { + public static int constructor = 0x9e19a1f6; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + unread = (flags & 1) != 0; + out = (flags & 2) != 0; + mentioned = (flags & 16) != 0; + media_unread = (flags & 32) != 0; + silent = (flags & 8192) != 0; + post = (flags & 16384) != 0; + id = stream.readInt32(exception); + if ((flags & 256) != 0) { + from_id = stream.readInt32(exception); + } + to_id = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 8) != 0) { + reply_to_msg_id = stream.readInt32(exception); + } + date = stream.readInt32(exception); + action = MessageAction.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = unread ? (flags | 1) : (flags &~ 1); + flags = out ? (flags | 2) : (flags &~ 2); + flags = mentioned ? (flags | 16) : (flags &~ 16); + flags = media_unread ? (flags | 32) : (flags &~ 32); + flags = silent ? (flags | 8192) : (flags &~ 8192); + flags = post ? (flags | 16384) : (flags &~ 16384); + stream.writeInt32(flags); + stream.writeInt32(id); + if ((flags & 256) != 0) { + stream.writeInt32(from_id); + } + to_id.serializeToStream(stream); + if ((flags & 8) != 0) { + stream.writeInt32(reply_to_msg_id); + } + stream.writeInt32(date); + action.serializeToStream(stream); + } + } //Message end //TL_dialog start diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java index a3aa8f8797..0bc3537ed5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java @@ -443,11 +443,6 @@ public boolean onTouchEvent(MotionEvent ev) { public void onAnimationEnd(Object animator) { onSlideAnimationEnd(backAnimation); } - - @Override - public void onAnimationCancel(Object animator) { - onSlideAnimationEnd(backAnimation); - } }); animatorSet.start(); animationInProgress = true; @@ -693,11 +688,6 @@ public void run() { public void onAnimationEnd(Object animation) { onAnimationEndCheck(false); } - - @Override - public void onAnimationCancel(Object animation) { - onAnimationEndCheck(false); - } }); currentAnimation.start(); } else { @@ -927,11 +917,6 @@ public void onAnimationStart(Object animation) { public void onAnimationEnd(Object animation) { onAnimationEndCheck(false); } - - @Override - public void onAnimationCancel(Object animation) { - onAnimationEndCheck(false); - } }); currentAnimation.start(); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java index 270add2ec6..b201c06f1c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java @@ -610,11 +610,6 @@ public void onAnimationEnd(Animator animation) { } } } - - @Override - public void onAnimationCancel(Animator animation) { - onAnimationEnd(animation); - } }); animatorSet.start(); } @@ -706,11 +701,6 @@ public void run() { } }); } - - @Override - public void onAnimationCancel(Object animation) { - onAnimationEnd(animation); - } }); animatorSetProxy.start(); } @@ -745,11 +735,6 @@ public void run() { } }); } - - @Override - public void onAnimationCancel(Object animation) { - onAnimationEnd(animation); - } }); animatorSetProxy.start(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DrawerLayoutContainer.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DrawerLayoutContainer.java index 55dcbc3e0d..bb8e63493d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DrawerLayoutContainer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DrawerLayoutContainer.java @@ -187,11 +187,6 @@ public void openDrawer(boolean fast) { public void onAnimationEnd(Object animator) { onDrawerAnimationEnd(true); } - - @Override - public void onAnimationCancel(Object animator) { - onDrawerAnimationEnd(true); - } }); animatorSet.start(); currentAnimation = animatorSet; @@ -214,11 +209,6 @@ public void closeDrawer(boolean fast) { public void onAnimationEnd(Object animator) { onDrawerAnimationEnd(false); } - - @Override - public void onAnimationCancel(Object animator) { - onDrawerAnimationEnd(false); - } }); animatorSet.start(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ContactsAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ContactsAdapter.java index bcee796f07..13f79f1ff2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ContactsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ContactsAdapter.java @@ -238,7 +238,7 @@ public View getItemView(int section, int position, View convertView, ViewGroup p } } else if (type == 0) { if (convertView == null) { - convertView = new UserCell(mContext, 58, 1); + convertView = new UserCell(mContext, 58, 1, false); ((UserCell) convertView).setStatusColors(0xffa8a8a8, 0xff3b84c0); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java index 257dcc8f11..7901531d19 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java @@ -94,6 +94,7 @@ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewT } else if (viewType == 1) { view = new LoadingCell(mContext); } + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); return new Holder(view); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java index 9bb8a06422..e3233f9111 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java @@ -885,6 +885,7 @@ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType view = new HashtagSearchCell(mContext); break; } + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); return new Holder(view); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java index b4d8dffe47..bed8d26b3e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java @@ -419,6 +419,7 @@ public void run() { boolean added = false; if (searchResultBotContext == null || offset.length() == 0) { searchResultBotContext = res.results; + contextMedia = res.gallery; } else { added = true; searchResultBotContext.addAll(res.results); @@ -426,7 +427,6 @@ public void run() { nextQueryOffset = ""; } } - contextMedia = res.gallery; searchResultHashtags = null; searchResultUsernames = null; searchResultCommands = null; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java index 08ae3a64d0..11a5651b2b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java @@ -238,7 +238,7 @@ public View getView(int i, View view, ViewGroup viewGroup) { } else { if (view == null) { if (useUserCell) { - view = new UserCell(mContext, 1, 1); + view = new UserCell(mContext, 1, 1, false); if (checkedMap != null) { ((UserCell) view).setChecked(false, false); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/BlockedUsersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/BlockedUsersActivity.java index 33d2252dcc..efde21e42f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/BlockedUsersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/BlockedUsersActivity.java @@ -263,11 +263,19 @@ public View getView(int i, View view, ViewGroup viewGroup) { int type = getItemViewType(i); if (type == 0) { if (view == null) { - view = new UserCell(mContext, 1, 0); + view = new UserCell(mContext, 1, 0, false); } TLRPC.User user = MessagesController.getInstance().getUser(MessagesController.getInstance().blockedUsers.get(i)); if (user != null) { - ((UserCell) view).setData(user, null, user.phone != null && user.phone.length() != 0 ? PhoneFormat.getInstance().format("+" + user.phone) : LocaleController.getString("NumberUnknown", R.string.NumberUnknown), 0); + String number; + if (user.bot) { + number = LocaleController.getString("Bot", R.string.Bot).substring(0, 1).toUpperCase() + LocaleController.getString("Bot", R.string.Bot).substring(1); + } else if (user.phone != null && user.phone.length() != 0) { + number = PhoneFormat.getInstance().format("+" + user.phone); + } else { + number = LocaleController.getString("NumberUnknown", R.string.NumberUnknown); + } + ((UserCell) view).setData(user, null, number, 0); } } else if (type == 1) { if (view == null) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java index 4339be2416..307d13c22d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java @@ -413,10 +413,9 @@ public void run() { NativeByteBuffer data = new NativeByteBuffer(cursor2.byteArrayLength(0)); if (data != null && cursor2.byteBufferValue(0, data) != 0) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - if (message == null) { - continue; + if (message != null) { + arrayList.add(message); } - arrayList.add(message); } data.reuse(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/BotHelpCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/BotHelpCell.java index 799fa319f8..41568c1f68 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/BotHelpCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/BotHelpCell.java @@ -100,12 +100,16 @@ public void setText(String text) { MessageObject.addLinks(stringBuilder); stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")), 0, help.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Emoji.replaceEmoji(stringBuilder, textPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); - textLayout = new StaticLayout(stringBuilder, textPaint, width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - width = 0; - height = textLayout.getHeight() + AndroidUtilities.dp(4 + 18); - int count = textLayout.getLineCount(); - for (int a = 0; a < count; a++) { - width = (int) Math.ceil(Math.max(width, textLayout.getLineWidth(a) + textLayout.getLineLeft(a))); + try { + textLayout = new StaticLayout(stringBuilder, textPaint, width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + width = 0; + height = textLayout.getHeight() + AndroidUtilities.dp(4 + 18); + int count = textLayout.getLineCount(); + for (int a = 0; a < count; a++) { + width = (int) Math.ceil(Math.max(width, textLayout.getLineWidth(a) + textLayout.getLineLeft(a))); + } + } catch (Exception e) { + FileLog.e("tmessage", e); } width += AndroidUtilities.dp(4 + 18); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java index 56228aec30..bc73969a5c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java @@ -57,6 +57,8 @@ public interface ChatActionCellDelegate { private int previousWidth = 0; private boolean imagePressed = false; + private boolean hasReplyMessage; + private MessageObject currentMessageObject; private ChatActionCellDelegate delegate; @@ -79,10 +81,11 @@ public void setDelegate(ChatActionCellDelegate delegate) { } public void setMessageObject(MessageObject messageObject) { - if (currentMessageObject == messageObject) { + if (currentMessageObject == messageObject && (hasReplyMessage || messageObject.replyMessageObject == null)) { return; } currentMessageObject = messageObject; + hasReplyMessage = messageObject.replyMessageObject != null; previousWidth = 0; if (currentMessageObject.type == 11) { int id = 0; @@ -220,8 +223,8 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = Math.max(AndroidUtilities.dp(30), MeasureSpec.getSize(widthMeasureSpec)); if (width != previousWidth) { previousWidth = width; - - textLayout = new StaticLayout(currentMessageObject.messageText, textPaint, width - AndroidUtilities.dp(30), Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); + int maxWidth = width - AndroidUtilities.dp(30); + textLayout = new StaticLayout(currentMessageObject.messageText, textPaint, maxWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); textHeight = 0; textWidth = 0; try { @@ -230,6 +233,9 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { float lineWidth; try { lineWidth = textLayout.getLineWidth(a); + if (lineWidth > maxWidth) { + lineWidth = maxWidth; + } textHeight = (int)Math.max(textHeight, Math.ceil(textLayout.getLineBottom(a))); } catch (Exception e) { FileLog.e("tmessages", e); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatBaseCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatBaseCell.java index ed1b12906d..9c32ab0d1b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatBaseCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatBaseCell.java @@ -1,5 +1,5 @@ /* - * This is the source code of Telegram for Android v. 1.3.x. + * This is the source code of Telegram for Android v. 3.x.x. * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * @@ -39,7 +39,6 @@ import org.telegram.messenger.MessageObject; import org.telegram.messenger.ImageReceiver; import org.telegram.ui.Components.AvatarDrawable; -import org.telegram.ui.Components.LinkPath; import org.telegram.ui.Components.ResourceLoader; import org.telegram.ui.Components.StaticLayoutEx; import org.telegram.ui.Components.TypefaceSpan; @@ -52,25 +51,22 @@ public interface ChatBaseCellDelegate { void didPressedChannelAvatar(ChatBaseCell cell, TLRPC.Chat chat, int postId); void didPressedCancelSendButton(ChatBaseCell cell); void didLongPressed(ChatBaseCell cell); - void didPressReplyMessage(ChatBaseCell cell, int id); - void didPressUrl(MessageObject messageObject, ClickableSpan url, boolean longPress); + void didPressedReplyMessage(ChatBaseCell cell, int id); + void didPressedUrl(MessageObject messageObject, ClickableSpan url, boolean longPress); void needOpenWebView(String url, String title, String originalUrl, int w, int h); - void didClickedImage(ChatBaseCell cell); - void didPressShare(ChatBaseCell cell); + void didPressedImage(ChatBaseCell cell); + void didPressedShare(ChatBaseCell cell); + void didPressedOther(ChatBaseCell cell); boolean canPerformActions(); } - protected ClickableSpan pressedLink; - protected boolean linkPreviewPressed; - protected LinkPath urlPath = new LinkPath(); - protected static Paint urlPaint; private int TAG; public boolean isChat; protected boolean isPressed; protected boolean forwardName; protected boolean isHighlighted; - protected boolean media; + protected boolean mediaBackground; protected boolean isCheckPressed = true; private boolean wasLayout; protected boolean isAvatarVisible; @@ -180,9 +176,6 @@ public ChatBaseCell(Context context) { replyTextPaint.linkColor = 0xff316f9f; replyLinePaint = new Paint(); - - urlPaint = new Paint(); - urlPaint.setColor(0x33316f9f); } avatarImage = new ImageReceiver(this); avatarImage.setRoundRadius(AndroidUtilities.dp(21)); @@ -211,14 +204,6 @@ public void setPressed(boolean pressed) { invalidate(); } - protected void resetPressedLink() { - if (pressedLink != null) { - pressedLink = null; - } - linkPreviewPressed = false; - invalidate(); - } - public void setDelegate(ChatBaseCellDelegate delegate) { this.delegate = delegate; } @@ -487,7 +472,7 @@ public void setMessageObject(MessageObject messageObject) { } } try { - nameLayout = new StaticLayout(nameStringFinal, namePaint, nameWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + nameLayout = new StaticLayout(nameStringFinal, namePaint, nameWidth + AndroidUtilities.dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (nameLayout != null && nameLayout.getLineCount() > 0) { nameWidth = (int)Math.ceil(nameLayout.getLineWidth(0)); namesOffset += AndroidUtilities.dp(19); @@ -557,7 +542,7 @@ public void setMessageObject(MessageObject messageObject) { namesOffset += AndroidUtilities.dp(42); if (messageObject.contentType == 2 || messageObject.contentType == 3) { namesOffset += AndroidUtilities.dp(4); - } else if (messageObject.contentType == 1) { + } else if (messageObject.type != 0) { if (messageObject.type == 13) { namesOffset -= AndroidUtilities.dp(42); } else { @@ -590,7 +575,7 @@ public void setMessageObject(MessageObject messageObject) { } else { maxWidth = getMaxNameWidth() - AndroidUtilities.dp(22); } - if (!media && messageObject.contentType != 0) { + if (!mediaBackground) { maxWidth -= AndroidUtilities.dp(8); } @@ -772,7 +757,7 @@ public boolean onTouchEvent(MotionEvent event) { replyPressed = false; playSoundEffect(SoundEffectConstants.CLICK); if (delegate != null) { - delegate.didPressReplyMessage(this, currentMessageObject.messageOwner.reply_to_msg_id); + delegate.didPressedReplyMessage(this, currentMessageObject.messageOwner.reply_to_msg_id); } } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { replyPressed = false; @@ -786,7 +771,7 @@ public boolean onTouchEvent(MotionEvent event) { sharePressed = false; playSoundEffect(SoundEffectConstants.CLICK); if (delegate != null) { - delegate.didPressShare(this); + delegate.didPressedShare(this); } } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { sharePressed = false; @@ -812,9 +797,11 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto if (changed || !wasLayout) { layoutWidth = getMeasuredWidth(); layoutHeight = getMeasuredHeight(); - + if (timeTextWidth < 0) { + timeTextWidth = AndroidUtilities.dp(10); + } timeLayout = new StaticLayout(currentTimeString, timePaint, timeTextWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - if (!media) { + if (!mediaBackground) { if (!currentMessageObject.isOutOwner()) { timeX = backgroundWidth - AndroidUtilities.dp(9) - timeWidth + (isChat && currentMessageObject.isFromUser() ? AndroidUtilities.dp(52) : 0); } else { @@ -876,7 +863,7 @@ protected void onDraw(Canvas canvas) { avatarImage.draw(canvas); } - if (media) { + if (mediaBackground) { timePaint.setColor(0xffffffff); } else { if (currentMessageObject.isOutOwner()) { @@ -889,37 +876,37 @@ protected void onDraw(Canvas canvas) { Drawable currentBackgroundDrawable; if (currentMessageObject.isOutOwner()) { if (isDrawSelectedBackground()) { - if (!media) { + if (!mediaBackground) { currentBackgroundDrawable = ResourceLoader.backgroundDrawableOutSelected; } else { currentBackgroundDrawable = ResourceLoader.backgroundMediaDrawableOutSelected; } } else { - if (!media) { + if (!mediaBackground) { currentBackgroundDrawable = ResourceLoader.backgroundDrawableOut; } else { currentBackgroundDrawable = ResourceLoader.backgroundMediaDrawableOut; } } - setDrawableBounds(currentBackgroundDrawable, layoutWidth - backgroundWidth - (!media ? 0 : AndroidUtilities.dp(9)), AndroidUtilities.dp(1), backgroundWidth, layoutHeight - AndroidUtilities.dp(2)); + setDrawableBounds(currentBackgroundDrawable, layoutWidth - backgroundWidth - (!mediaBackground ? 0 : AndroidUtilities.dp(9)), AndroidUtilities.dp(1), backgroundWidth, layoutHeight - AndroidUtilities.dp(2)); } else { if (isDrawSelectedBackground()) { - if (!media) { + if (!mediaBackground) { currentBackgroundDrawable = ResourceLoader.backgroundDrawableInSelected; } else { currentBackgroundDrawable = ResourceLoader.backgroundMediaDrawableInSelected; } } else { - if (!media) { + if (!mediaBackground) { currentBackgroundDrawable = ResourceLoader.backgroundDrawableIn; } else { currentBackgroundDrawable = ResourceLoader.backgroundMediaDrawableIn; } } if (isChat && currentMessageObject.isFromUser()) { - setDrawableBounds(currentBackgroundDrawable, AndroidUtilities.dp(52 + (!media ? 0 : 9)), AndroidUtilities.dp(1), backgroundWidth, layoutHeight - AndroidUtilities.dp(2)); + setDrawableBounds(currentBackgroundDrawable, AndroidUtilities.dp(52 + (!mediaBackground ? 0 : 9)), AndroidUtilities.dp(1), backgroundWidth, layoutHeight - AndroidUtilities.dp(2)); } else { - setDrawableBounds(currentBackgroundDrawable, (!media ? 0 : AndroidUtilities.dp(9)), AndroidUtilities.dp(1), backgroundWidth, layoutHeight - AndroidUtilities.dp(2)); + setDrawableBounds(currentBackgroundDrawable, (!mediaBackground ? 0 : AndroidUtilities.dp(9)), AndroidUtilities.dp(1), backgroundWidth, layoutHeight - AndroidUtilities.dp(2)); } } if (drawBackground && currentBackgroundDrawable != null) { @@ -935,7 +922,7 @@ protected void onDraw(Canvas canvas) { if (drawName && nameLayout != null) { canvas.save(); - if (media || currentMessageObject.isOutOwner()) { + if (mediaBackground || currentMessageObject.isOutOwner()) { canvas.translate(nameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(10) - nameOffsetX, AndroidUtilities.dp(10)); } else { canvas.translate(nameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(19) - nameOffsetX, AndroidUtilities.dp(10)); @@ -962,7 +949,7 @@ protected void onDraw(Canvas canvas) { forwardNameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(10); } else { forwardNamePaint.setColor(0xff006fc8); - if (media) { + if (mediaBackground) { forwardNameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(10); } else { forwardNameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(19); @@ -972,10 +959,6 @@ protected void onDraw(Canvas canvas) { canvas.translate(forwardNameX - forwardNameOffsetX, forwardNameY); forwardedNameLayout.draw(canvas); canvas.restore(); - - /*if (viaWidth != 0) { - canvas.drawRect(forwardNameX + viaNameWidth, forwardNameY, forwardNameX + viaNameWidth + viaWidth, forwardNameY + AndroidUtilities.dp(32), namePaint); - }*/ } if (currentMessageObject.isReply()) { @@ -1018,7 +1001,7 @@ protected void onDraw(Canvas canvas) { } else { replyTextPaint.setColor(0xff999999); } - if (currentMessageObject.contentType == 1 && media) { + if (mediaBackground) { replyStartX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(11); } else { replyStartX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(20); @@ -1045,8 +1028,8 @@ protected void onDraw(Canvas canvas) { } } - if (drawTime || !media) { - if (media) { + if (drawTime || !mediaBackground) { + if (mediaBackground) { setDrawableBounds(ResourceLoader.mediaBackgroundDrawable, timeX - AndroidUtilities.dp(3), layoutHeight - AndroidUtilities.dp(27.5f), timeWidth + AndroidUtilities.dp(6 + (currentMessageObject.isOutOwner() ? 20 : 0)), AndroidUtilities.dp(16.5f)); ResourceLoader.mediaBackgroundDrawable.draw(canvas); @@ -1152,7 +1135,7 @@ protected void onDraw(Canvas canvas) { } if (drawClock) { - if (!media) { + if (!mediaBackground) { setDrawableBounds(ResourceLoader.clockDrawable, layoutWidth - AndroidUtilities.dp(18.5f) - ResourceLoader.clockDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.5f) - ResourceLoader.clockDrawable.getIntrinsicHeight()); ResourceLoader.clockDrawable.draw(canvas); } else { @@ -1162,7 +1145,7 @@ protected void onDraw(Canvas canvas) { } if (isBroadcast) { if (drawCheck1 || drawCheck2) { - if (!media) { + if (!mediaBackground) { setDrawableBounds(ResourceLoader.broadcastDrawable, layoutWidth - AndroidUtilities.dp(20.5f) - ResourceLoader.broadcastDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - ResourceLoader.broadcastDrawable.getIntrinsicHeight()); ResourceLoader.broadcastDrawable.draw(canvas); } else { @@ -1172,7 +1155,7 @@ protected void onDraw(Canvas canvas) { } } else { if (drawCheck2) { - if (!media) { + if (!mediaBackground) { if (drawCheck1) { setDrawableBounds(ResourceLoader.checkDrawable, layoutWidth - AndroidUtilities.dp(22.5f) - ResourceLoader.checkDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - ResourceLoader.checkDrawable.getIntrinsicHeight()); } else { @@ -1189,7 +1172,7 @@ protected void onDraw(Canvas canvas) { } } if (drawCheck1) { - if (!media) { + if (!mediaBackground) { setDrawableBounds(ResourceLoader.halfCheckDrawable, layoutWidth - AndroidUtilities.dp(18) - ResourceLoader.halfCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - ResourceLoader.halfCheckDrawable.getIntrinsicHeight()); ResourceLoader.halfCheckDrawable.draw(canvas); } else { @@ -1199,7 +1182,7 @@ protected void onDraw(Canvas canvas) { } } if (drawError) { - if (!media) { + if (!mediaBackground) { setDrawableBounds(ResourceLoader.errorDrawable, layoutWidth - AndroidUtilities.dp(18) - ResourceLoader.errorDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(6.5f) - ResourceLoader.errorDrawable.getIntrinsicHeight()); ResourceLoader.errorDrawable.draw(canvas); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMediaCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMediaCell.java deleted file mode 100644 index 8f4e8a54cc..0000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMediaCell.java +++ /dev/null @@ -1,1309 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Cells; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.text.Layout; -import android.text.Spannable; -import android.text.StaticLayout; -import android.text.TextPaint; -import android.text.TextUtils; -import android.text.style.ClickableSpan; -import android.text.style.URLSpan; -import android.view.MotionEvent; -import android.view.SoundEffectConstants; - -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.ImageLoader; -import org.telegram.messenger.LocaleController; -import org.telegram.messenger.SendMessagesHelper; -import org.telegram.messenger.FileLoader; -import org.telegram.messenger.MediaController; -import org.telegram.messenger.FileLog; -import org.telegram.messenger.R; -import org.telegram.tgnet.ConnectionsManager; -import org.telegram.tgnet.TLRPC; -import org.telegram.messenger.MessageObject; -import org.telegram.ui.Components.RadialProgress; -import org.telegram.ui.Components.ResourceLoader; -import org.telegram.ui.Components.StaticLayoutEx; -import org.telegram.ui.Components.URLSpanBotCommand; -import org.telegram.ui.Components.URLSpanNoUnderline; -import org.telegram.ui.PhotoViewer; -import org.telegram.messenger.ImageReceiver; - -import java.io.File; -import java.util.HashMap; -import java.util.Locale; - -public class ChatMediaCell extends ChatBaseCell { - - public interface ChatMediaCellDelegate { - void didPressedOther(ChatMediaCell cell); - } - - private static TextPaint infoPaint; - private static TextPaint namePaint; - private static Paint docBackPaint; - private static Paint deleteProgressPaint; - private static TextPaint locationTitlePaint; - private static TextPaint locationAddressPaint; - - private RadialProgress radialProgress; - - private int photoWidth; - private int photoHeight; - private TLRPC.PhotoSize currentPhotoObject; - private TLRPC.PhotoSize currentPhotoObjectThumb; - private String currentUrl; - private String currentPhotoFilter; - private ImageReceiver photoImage; - private boolean photoNotSet = false; - private boolean cancelLoading = false; - private int additionHeight; - - private boolean allowedToSetPhoto = true; - - private int buttonState = 0; - private int buttonPressed = 0; - private boolean imagePressed = false; - private boolean otherPressed = false; - private int buttonX; - private int buttonY; - - private StaticLayout infoLayout; - private int infoWidth; - private int infoOffset = 0; - private String currentInfoString; - - private StaticLayout nameLayout; - private int nameWidth = 0; - private int nameOffsetX = 0; - private String currentNameString; - - private ChatMediaCellDelegate mediaDelegate = null; - private RectF deleteProgressRect = new RectF(); - - private int captionX; - private int captionY; - private int captionHeight; - - public ChatMediaCell(Context context) { - super(context); - - if (infoPaint == null) { - infoPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - infoPaint.setTextSize(AndroidUtilities.dp(12)); - - namePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - namePaint.setColor(0xff212121); - namePaint.setTextSize(AndroidUtilities.dp(16)); - - docBackPaint = new Paint(); - - deleteProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - deleteProgressPaint.setColor(0xffe4e2e0); - - locationTitlePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - locationTitlePaint.setTextSize(AndroidUtilities.dp(14)); - locationTitlePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - - locationAddressPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - locationAddressPaint.setTextSize(AndroidUtilities.dp(14)); - } - - photoImage = new ImageReceiver(this); - radialProgress = new RadialProgress(this); - } - - public void setMediaDelegate(ChatMediaCellDelegate delegate) { - this.mediaDelegate = delegate; - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - photoImage.onDetachedFromWindow(); - MediaController.getInstance().removeLoadingFileObserver(this); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - if (photoImage.onAttachedToWindow()) { - updateButtonState(false); - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - float x = event.getX(); - float y = event.getY(); - - boolean result = false; - int side = AndroidUtilities.dp(48); - if (currentMessageObject.caption instanceof Spannable && delegate.canPerformActions()) { - if (event.getAction() == MotionEvent.ACTION_DOWN || (linkPreviewPressed || pressedLink != null) && event.getAction() == MotionEvent.ACTION_UP) { - if (nameLayout != null && x >= captionX && x <= captionX + backgroundWidth && y >= captionY && y <= captionY + captionHeight) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - resetPressedLink(); - try { - int x2 = (int) (x - captionX); - int y2 = (int) (y - captionY); - final int line = nameLayout.getLineForVertical(y2); - final int off = nameLayout.getOffsetForHorizontal(line, x2); - - final float left = nameLayout.getLineLeft(line); - if (left <= x2 && left + nameLayout.getLineWidth(line) >= x2) { - Spannable buffer = (Spannable) currentMessageObject.caption; - ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); - boolean ignore = false; - if (link.length == 0 || link.length != 0 && link[0] instanceof URLSpanBotCommand && !URLSpanBotCommand.enabled) { - ignore = true; - } - if (!ignore) { - resetPressedLink(); - pressedLink = link[0]; - linkPreviewPressed = true; - result = true; - try { - int start = buffer.getSpanStart(pressedLink); - urlPath.setCurrentLayout(nameLayout, start); - nameLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink), urlPath); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } else { - resetPressedLink(); - } - } else { - resetPressedLink(); - } - } catch (Exception e) { - resetPressedLink(); - FileLog.e("tmessages", e); - } - } else if (linkPreviewPressed) { - try { - delegate.didPressUrl(currentMessageObject, pressedLink, false); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - resetPressedLink(); - result = true; - } - } else { - resetPressedLink(); - } - } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { - resetPressedLink(); - } - - if (result && event.getAction() == MotionEvent.ACTION_DOWN) { - startCheckLongPress(); - } - if (event.getAction() != MotionEvent.ACTION_DOWN && event.getAction() != MotionEvent.ACTION_MOVE) { - cancelCheckLongPress(); - } - if (result) { - return true; - } - } - if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (delegate == null || delegate.canPerformActions()) { - if (buttonState != -1 && x >= buttonX && x <= buttonX + side && y >= buttonY && y <= buttonY + side) { - buttonPressed = 1; - invalidate(); - result = true; - } else { - if (currentMessageObject.type == 9) { - if (x >= photoImage.getImageX() && x <= photoImage.getImageX() + backgroundWidth - AndroidUtilities.dp(50) && y >= photoImage.getImageY() && y <= photoImage.getImageY() + photoImage.getImageHeight()) { - imagePressed = true; - result = true; - } else if (x >= photoImage.getImageX() + backgroundWidth - AndroidUtilities.dp(50) && x <= photoImage.getImageX() + backgroundWidth && y >= photoImage.getImageY() && y <= photoImage.getImageY() + photoImage.getImageHeight()) { - otherPressed = true; - result = true; - } - } else if (currentMessageObject.type != 13) { - if (x >= photoImage.getImageX() && x <= photoImage.getImageX() + backgroundWidth && y >= photoImage.getImageY() && y <= photoImage.getImageY() + photoImage.getImageHeight()) { - imagePressed = true; - result = true; - } - } - } - if (imagePressed && currentMessageObject.isSecretPhoto()) { - imagePressed = false; - } else if (imagePressed && currentMessageObject.isSendError()) { - imagePressed = false; - result = false; - } else if (imagePressed && currentMessageObject.type == 8 && buttonState == -1 && MediaController.getInstance().canAutoplayGifs()) { - imagePressed = false; - result = false; - } else if (result) { - startCheckLongPress(); - } - } - } else { - if (event.getAction() != MotionEvent.ACTION_MOVE) { - cancelCheckLongPress(); - } - if (buttonPressed == 1) { - if (event.getAction() == MotionEvent.ACTION_UP) { - buttonPressed = 0; - playSoundEffect(SoundEffectConstants.CLICK); - didPressedButton(false); - invalidate(); - } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { - buttonPressed = 0; - invalidate(); - } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (!(x >= buttonX && x <= buttonX + side && y >= buttonY && y <= buttonY + side)) { - buttonPressed = 0; - invalidate(); - } - } - } else if (imagePressed) { - if (event.getAction() == MotionEvent.ACTION_UP) { - imagePressed = false; - if (buttonState == -1 || buttonState == 2 || buttonState == 3) { - playSoundEffect(SoundEffectConstants.CLICK); - didClickedImage(); - } - invalidate(); - } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { - imagePressed = false; - invalidate(); - } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (currentMessageObject.type == 9) { - if (!(x >= photoImage.getImageX() && x <= photoImage.getImageX() + backgroundWidth - AndroidUtilities.dp(50) && y >= photoImage.getImageY() && y <= photoImage.getImageY() + photoImage.getImageHeight())) { - imagePressed = false; - invalidate(); - } - } else { - if (!photoImage.isInsideImage(x, y)) { - imagePressed = false; - invalidate(); - } - } - } - } else if (otherPressed) { - if (event.getAction() == MotionEvent.ACTION_UP) { - otherPressed = false; - playSoundEffect(SoundEffectConstants.CLICK); - if (mediaDelegate != null) { - mediaDelegate.didPressedOther(this); - } - } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { - otherPressed = false; - } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (currentMessageObject.type == 9) { - if (!(x >= photoImage.getImageX() + backgroundWidth - AndroidUtilities.dp(50) && x <= photoImage.getImageX() + backgroundWidth && y >= photoImage.getImageY() && y <= photoImage.getImageY() + photoImage.getImageHeight())) { - otherPressed = false; - } - } - } - } - } - if (!result) { - result = super.onTouchEvent(event); - } - - return result; - } - - private void didClickedImage() { - if (currentMessageObject.type == 1) { - if (buttonState == -1) { - if (delegate != null) { - delegate.didClickedImage(this); - } - } else if (buttonState == 0) { - didPressedButton(false); - } - } else if (currentMessageObject.type == 8) { - if (buttonState == -1) { - buttonState = 2; - currentMessageObject.audioProgress = 1; - photoImage.setAllowStartAnimation(false); - photoImage.stopAnimation(); - radialProgress.setBackground(getDrawableForCurrentState(), false, false); - invalidate(); - } else if (buttonState == 2 || buttonState == 0) { - didPressedButton(false); - } - } else if (currentMessageObject.type == 3) { - if (buttonState == 0 || buttonState == 3) { - didPressedButton(false); - } - } else if (currentMessageObject.type == 4) { - if (delegate != null) { - delegate.didClickedImage(this); - } - } else if (currentMessageObject.type == 9) { - if (buttonState == -1) { - if (delegate != null) { - delegate.didClickedImage(this); - } - } - } - } - - @Override - public void setCheckPressed(boolean value, boolean pressed) { - super.setCheckPressed(value, pressed); - if (radialProgress.swapBackground(getDrawableForCurrentState())) { - invalidate(); - } - } - - @Override - public void setHighlighted(boolean value) { - super.setHighlighted(value); - if (radialProgress.swapBackground(getDrawableForCurrentState())) { - invalidate(); - } - } - - @Override - public void setPressed(boolean pressed) { - super.setPressed(pressed); - if (radialProgress.swapBackground(getDrawableForCurrentState())) { - invalidate(); - } - } - - private Drawable getDrawableForCurrentState() { - if (buttonState >= 0 && buttonState < 4) { - if (currentMessageObject.type == 9) { - if (buttonState == 1 && !currentMessageObject.isSending()) { - return ResourceLoader.buttonStatesDrawablesDoc[2][currentMessageObject.isOutOwner() ? 1 : (isDrawSelectedBackground() ? 2 : 0)]; - } else { - return ResourceLoader.buttonStatesDrawablesDoc[buttonState][currentMessageObject.isOutOwner() ? 1 : (isDrawSelectedBackground() ? 2 : 0)]; - } - } else { - if (buttonState == 1 && !currentMessageObject.isSending()) { - return ResourceLoader.buttonStatesDrawables[4]; - } else { - return ResourceLoader.buttonStatesDrawables[buttonState]; - } - } - } else if (buttonState == -1) { - if (currentMessageObject.type == 9) { - return ResourceLoader.placeholderDocDrawable[currentMessageObject.isOutOwner() ? 1 : (isDrawSelectedBackground() ? 2 : 0)]; - } - } - return null; - } - - private void didPressedButton(boolean animated) { - if (buttonState == 0) { - cancelLoading = false; - radialProgress.setProgress(0, false); - if (currentMessageObject.type == 1) { - photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilter, currentPhotoObject.size, null, false); - } else if (currentMessageObject.type == 8) { - currentMessageObject.audioProgress = 2; - photoImage.setImage(currentMessageObject.messageOwner.media.document, null, currentPhotoObject != null ? currentPhotoObject.location : null, currentPhotoFilter, currentMessageObject.messageOwner.media.document.size, null, false); - } else if (currentMessageObject.type == 9) { - FileLoader.getInstance().loadFile(currentMessageObject.messageOwner.media.document, false, false); - } else if (currentMessageObject.type == 3) { - FileLoader.getInstance().loadFile(currentMessageObject.messageOwner.media.document, true, false); - } - buttonState = 1; - radialProgress.setBackground(getDrawableForCurrentState(), true, animated); - invalidate(); - } else if (buttonState == 1) { - if (currentMessageObject.isOut() && currentMessageObject.isSending()) { - if (delegate != null) { - delegate.didPressedCancelSendButton(this); - } - } else { - cancelLoading = true; - if (currentMessageObject.type == 1 || currentMessageObject.type == 8) { - photoImage.cancelLoadImage(); - } else if (currentMessageObject.type == 9 || currentMessageObject.type == 3) { - FileLoader.getInstance().cancelLoadFile(currentMessageObject.messageOwner.media.document); - } - buttonState = 0; - radialProgress.setBackground(getDrawableForCurrentState(), false, animated); - invalidate(); - } - } else if (buttonState == 2) { - photoImage.setAllowStartAnimation(true); - photoImage.startAnimation(); - currentMessageObject.audioProgress = 0; - buttonState = -1; - radialProgress.setBackground(getDrawableForCurrentState(), false, animated); - } else if (buttonState == 3) { - if (delegate != null) { - delegate.didClickedImage(this); - } - } - } - - private boolean isPhotoDataChanged(MessageObject object) { - if (object.type == 4) { - if (currentUrl == null) { - return true; - } - double lat = object.messageOwner.media.geo.lat; - double lon = object.messageOwner.media.geo._long; - String url = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=100x100&maptype=roadmap&scale=%d&markers=color:red|size:big|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(AndroidUtilities.density)), lat, lon); - if (!url.equals(currentUrl)) { - return true; - } - } else if (currentPhotoObject == null || currentPhotoObject.location instanceof TLRPC.TL_fileLocationUnavailable) { - return true; - } else if (currentMessageObject != null && photoNotSet) { - File cacheFile = FileLoader.getPathToMessage(currentMessageObject.messageOwner); - if (cacheFile.exists()) { - return true; - } - } - return false; - } - - @Override - protected void onLongPress() { - if (pressedLink instanceof URLSpanNoUnderline) { - - } else if (pressedLink instanceof URLSpan) { - delegate.didPressUrl(currentMessageObject, pressedLink, true); - return; - } - super.onLongPress(); - } - - @Override - public void setMessageObject(MessageObject messageObject) { - boolean messageChanged = currentMessageObject != messageObject; - boolean dataChanged = currentMessageObject == messageObject && (isUserDataChanged() || photoNotSet); - if (currentMessageObject != messageObject || isPhotoDataChanged(messageObject) || dataChanged) { - drawForwardedName = messageObject.messageOwner.fwd_from != null && messageObject.type != 13; - media = messageObject.type != 9; - cancelLoading = false; - additionHeight = 0; - resetPressedLink(); - if (messageObject.audioProgress != 2 && !MediaController.getInstance().canAutoplayGifs() && messageObject.type == 8) { - messageObject.audioProgress = 1; - } - - buttonState = -1; - currentPhotoObject = null; - currentPhotoObjectThumb = null; - currentUrl = null; - photoNotSet = false; - drawBackground = true; - drawName = false; - photoImage.setAllowStartAnimation(messageObject.audioProgress == 0); - - photoImage.setForcePreview(messageObject.isSecretPhoto()); - if (messageObject.type == 9) { - String name = messageObject.getDocumentName(); - if (name == null || name.length() == 0) { - name = LocaleController.getString("AttachDocument", R.string.AttachDocument); - } - int maxWidth; - if (AndroidUtilities.isTablet()) { - maxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(122 + 86 + 24); - } else { - maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(122 + 86 + 24); - } - if (checkNeedDrawShareButton(messageObject)) { - maxWidth -= AndroidUtilities.dp(20); - } - if (currentNameString == null || !currentNameString.equals(name)) { - currentNameString = name; - nameLayout = StaticLayoutEx.createStaticLayout(currentNameString, namePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.MIDDLE, maxWidth, 3); - nameOffsetX = Integer.MIN_VALUE; - if (nameLayout != null && nameLayout.getLineCount() > 0) { - int maxLineWidth = 0; - int maxLeft = 0; - for (int a = 0; a < nameLayout.getLineCount(); a++) { - maxLineWidth = Math.max(maxLineWidth, (int) Math.ceil(nameLayout.getLineWidth(a))); - nameOffsetX = Math.max(maxLeft, (int) Math.ceil(-nameLayout.getLineLeft(a))); - } - nameWidth = Math.min(maxWidth, maxLineWidth); - } else { - nameWidth = maxWidth; - nameOffsetX = 0; - } - } - - String str = AndroidUtilities.formatFileSize(messageObject.messageOwner.media.document.size) + " " + messageObject.getExtension(); - - if (currentInfoString == null || !currentInfoString.equals(str)) { - currentInfoString = str; - infoOffset = 0; - infoWidth = Math.min(maxWidth, (int) Math.ceil(infoPaint.measureText(currentInfoString))); - CharSequence str2 = TextUtils.ellipsize(currentInfoString, infoPaint, infoWidth, TextUtils.TruncateAt.END); - try { - if (infoWidth < 0) { - infoWidth = AndroidUtilities.dp(10); - } - infoLayout = new StaticLayout(str2, infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - } else if (messageObject.type == 8) { - String str = AndroidUtilities.formatFileSize(messageObject.messageOwner.media.document.size); - if (currentInfoString == null || !currentInfoString.equals(str)) { - currentInfoString = str; - infoOffset = 0; - infoWidth = (int) Math.ceil(infoPaint.measureText(currentInfoString)); - infoLayout = new StaticLayout(currentInfoString, infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - } - nameLayout = null; - currentNameString = null; - } else if (messageObject.type == 3) { - int duration = 0; - for (int a = 0; a < messageObject.messageOwner.media.document.attributes.size(); a++) { - TLRPC.DocumentAttribute attribute = messageObject.messageOwner.media.document.attributes.get(a); - if (attribute instanceof TLRPC.TL_documentAttributeVideo) { - duration = attribute.duration; - break; - } - } - int minutes = duration / 60; - int seconds = duration - minutes * 60; - String str = String.format("%d:%02d, %s", minutes, seconds, AndroidUtilities.formatFileSize(messageObject.messageOwner.media.document.size)); - if (currentInfoString == null || !currentInfoString.equals(str)) { - currentInfoString = str; - infoOffset = ResourceLoader.videoIconDrawable.getIntrinsicWidth() + AndroidUtilities.dp(4); - infoWidth = (int) Math.ceil(infoPaint.measureText(currentInfoString)); - infoLayout = new StaticLayout(currentInfoString, infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - } - nameLayout = null; - currentNameString = null; - } else { - currentInfoString = null; - currentNameString = null; - infoLayout = null; - nameLayout = null; - updateSecretTimeText(messageObject); - } - if (messageObject.type == 9) { - photoWidth = AndroidUtilities.dp(86); - photoHeight = AndroidUtilities.dp(86); - availableTimeWidth = Math.max(nameWidth, infoWidth) + AndroidUtilities.dp(37); - backgroundWidth = photoWidth + availableTimeWidth + AndroidUtilities.dp(31); - currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); - photoImage.setNeedsQualityThumb(true); - photoImage.setShouldGenerateQualityThumb(true); - photoImage.setParentMessageObject(messageObject); - if (currentPhotoObject != null) { - currentPhotoFilter = String.format(Locale.US, "%d_%d_b", photoWidth, photoHeight); - photoImage.setImage(null, null, null, null, currentPhotoObject.location, currentPhotoFilter, 0, null, true); - } else { - photoImage.setImageBitmap((BitmapDrawable) null); - } - } else if (messageObject.type == 4) { //geo - double lat = messageObject.messageOwner.media.geo.lat; - double lon = messageObject.messageOwner.media.geo._long; - - if (messageObject.messageOwner.media.title != null && messageObject.messageOwner.media.title.length() > 0) { - int maxWidth = (AndroidUtilities.isTablet() ? AndroidUtilities.getMinTabletSide() : Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y)) - AndroidUtilities.dp((isChat && !messageObject.isOutOwner() ? 102 : 40) + 86 + 24); - nameLayout = StaticLayoutEx.createStaticLayout(messageObject.messageOwner.media.title, locationTitlePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.END, maxWidth - AndroidUtilities.dp(4), 3); - int lineCount = nameLayout.getLineCount(); - if (messageObject.messageOwner.media.address != null && messageObject.messageOwner.media.address.length() > 0) { - infoLayout = StaticLayoutEx.createStaticLayout(messageObject.messageOwner.media.address, locationAddressPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.END, maxWidth - AndroidUtilities.dp(4), Math.min(3, 4 - lineCount)); - } else { - infoLayout = null; - } - - media = false; - availableTimeWidth = maxWidth - AndroidUtilities.dp(7); - measureTime(messageObject); - photoWidth = AndroidUtilities.dp(86); - photoHeight = AndroidUtilities.dp(86); - maxWidth = timeWidth + AndroidUtilities.dp(messageObject.isOutOwner() ? 29 : 9); - for (int a = 0; a < lineCount; a++) { - maxWidth = (int) Math.max(maxWidth, nameLayout.getLineWidth(a) + AndroidUtilities.dp(16)); - } - if (infoLayout != null) { - for (int a = 0; a < infoLayout.getLineCount(); a++) { - maxWidth = (int) Math.max(maxWidth, infoLayout.getLineWidth(a) + AndroidUtilities.dp(16)); - } - } - backgroundWidth = photoWidth + AndroidUtilities.dp(21) + maxWidth; - currentUrl = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=72x72&maptype=roadmap&scale=%d&markers=color:red|size:big|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(AndroidUtilities.density)), lat, lon); - } else { - availableTimeWidth = AndroidUtilities.dp(200 - 14); - photoWidth = AndroidUtilities.dp(200); - photoHeight = AndroidUtilities.dp(100); - backgroundWidth = photoWidth + AndroidUtilities.dp(12); - currentUrl = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=200x100&maptype=roadmap&scale=%d&markers=color:red|size:big|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(AndroidUtilities.density)), lat, lon); - } - - photoImage.setNeedsQualityThumb(false); - photoImage.setShouldGenerateQualityThumb(false); - photoImage.setParentMessageObject(null); - photoImage.setImage(currentUrl, null, messageObject.isOutOwner() ? ResourceLoader.geoOutDrawable : ResourceLoader.geoInDrawable, null, 0); - } else if (messageObject.type == 13) { //webp - drawBackground = false; - for (int a = 0; a < messageObject.messageOwner.media.document.attributes.size(); a++) { - TLRPC.DocumentAttribute attribute = messageObject.messageOwner.media.document.attributes.get(a); - if (attribute instanceof TLRPC.TL_documentAttributeImageSize) { - photoWidth = attribute.w; - photoHeight = attribute.h; - break; - } - } - float maxHeight = AndroidUtilities.displaySize.y * 0.4f; - float maxWidth; - if (AndroidUtilities.isTablet()) { - maxWidth = AndroidUtilities.getMinTabletSide() * 0.5f; - } else { - maxWidth = AndroidUtilities.displaySize.x * 0.5f; - } - if (photoWidth == 0) { - photoHeight = (int) maxHeight; - photoWidth = photoHeight + AndroidUtilities.dp(100); - } - if (photoHeight > maxHeight) { - photoWidth *= maxHeight / photoHeight; - photoHeight = (int) maxHeight; - } - if (photoWidth > maxWidth) { - photoHeight *= maxWidth / photoWidth; - photoWidth = (int) maxWidth; - } - availableTimeWidth = photoWidth - AndroidUtilities.dp(14); - backgroundWidth = photoWidth + AndroidUtilities.dp(12); - currentPhotoObjectThumb = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 80); - photoImage.setNeedsQualityThumb(false); - photoImage.setShouldGenerateQualityThumb(false); - photoImage.setParentMessageObject(null); - if (messageObject.messageOwner.attachPath != null && messageObject.messageOwner.attachPath.length() > 0) { - File f = new File(messageObject.messageOwner.attachPath); - if (f.exists()) { - photoImage.setImage(null, messageObject.messageOwner.attachPath, - String.format(Locale.US, "%d_%d", photoWidth, photoHeight), - null, - currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, - "b1", - messageObject.messageOwner.media.document.size, "webp", true); - } - } else if (messageObject.messageOwner.media.document.id != 0) { - photoImage.setImage(messageObject.messageOwner.media.document, null, - String.format(Locale.US, "%d_%d", photoWidth, photoHeight), - null, - currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, - "b1", - messageObject.messageOwner.media.document.size, "webp", true); - } - } else { - int maxPhotoWidth; - if (AndroidUtilities.isTablet()) { - maxPhotoWidth = photoWidth = (int) (AndroidUtilities.getMinTabletSide() * 0.7f); - } else { - maxPhotoWidth = photoWidth = (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.7f); - } - photoHeight = photoWidth + AndroidUtilities.dp(100); - - if (photoWidth > AndroidUtilities.getPhotoSize()) { - photoWidth = AndroidUtilities.getPhotoSize(); - } - if (photoHeight > AndroidUtilities.getPhotoSize()) { - photoHeight = AndroidUtilities.getPhotoSize(); - } - - if (messageObject.type == 1) { - photoImage.setNeedsQualityThumb(false); - photoImage.setShouldGenerateQualityThumb(false); - photoImage.setParentMessageObject(null); - currentPhotoObjectThumb = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 80); - } else if (messageObject.type == 3) { - photoImage.setNeedsQualityThumb(true); - photoImage.setShouldGenerateQualityThumb(true); - photoImage.setParentMessageObject(messageObject); - } else if (messageObject.type == 8) { - photoImage.setNeedsQualityThumb(true); - photoImage.setShouldGenerateQualityThumb(true); - photoImage.setParentMessageObject(messageObject); - } - //8 - gif, 1 - photo, 3 - video - - if (messageObject.caption != null) { - media = false; - } - - currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); - - int w = 0; - int h = 0; - - if (currentPhotoObject != null && currentPhotoObject == currentPhotoObjectThumb) { - currentPhotoObjectThumb = null; - } - - if (currentPhotoObject != null) { - float scale = (float) currentPhotoObject.w / (float) photoWidth; - w = (int) (currentPhotoObject.w / scale); - h = (int) (currentPhotoObject.h / scale); - if (w == 0) { - if (messageObject.type == 3) { - w = infoWidth + infoOffset + AndroidUtilities.dp(16); - } else { - w = AndroidUtilities.dp(100); - } - } - if (h == 0) { - h = AndroidUtilities.dp(100); - } - if (h > photoHeight) { - float scale2 = h; - h = photoHeight; - scale2 /= h; - w = (int) (w / scale2); - } else if (h < AndroidUtilities.dp(120)) { - h = AndroidUtilities.dp(120); - float hScale = (float) currentPhotoObject.h / h; - if (currentPhotoObject.w / hScale < photoWidth) { - w = (int) (currentPhotoObject.w / hScale); - } - } - } - - if ((w == 0 || h == 0) && messageObject.type == 8) { - for (int a = 0; a < messageObject.messageOwner.media.document.attributes.size(); a++) { - TLRPC.DocumentAttribute attribute = messageObject.messageOwner.media.document.attributes.get(a); - if (attribute instanceof TLRPC.TL_documentAttributeImageSize || attribute instanceof TLRPC.TL_documentAttributeVideo) { - float scale = (float) attribute.w / (float) photoWidth; - w = (int) (attribute.w / scale); - h = (int) (attribute.h / scale); - if (h > photoHeight) { - float scale2 = h; - h = photoHeight; - scale2 /= h; - w = (int) (w / scale2); - } else if (h < AndroidUtilities.dp(120)) { - h = AndroidUtilities.dp(120); - float hScale = (float) attribute.h / h; - if (attribute.w / hScale < photoWidth) { - w = (int) (attribute.w / hScale); - } - } - break; - } - } - } - - - if (w == 0 || h == 0) { - w = h = AndroidUtilities.dp(100); - } - - availableTimeWidth = maxPhotoWidth - AndroidUtilities.dp(14); - measureTime(messageObject); - int timeWidthTotal = timeWidth + AndroidUtilities.dp(14 + (messageObject.isOutOwner() ? 20 : 0)); - if (w < timeWidthTotal) { - w = timeWidthTotal; - } - - if (messageObject.isSecretPhoto()) { - if (AndroidUtilities.isTablet()) { - w = h = (int) (AndroidUtilities.getMinTabletSide() * 0.5f); - } else { - w = h = (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.5f); - } - } - - photoWidth = w; - photoHeight = h; - backgroundWidth = w + AndroidUtilities.dp(12); - if (!media) { - backgroundWidth += AndroidUtilities.dp(9); - } - if (messageObject.caption != null) { - try { - nameLayout = new StaticLayout(messageObject.caption, MessageObject.textPaint, photoWidth - AndroidUtilities.dp(10), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - if (nameLayout != null && nameLayout.getLineCount() > 0) { - captionHeight = nameLayout.getHeight(); - additionHeight += captionHeight + AndroidUtilities.dp(9); - float lastLineWidth = nameLayout.getLineWidth(nameLayout.getLineCount() - 1) + nameLayout.getLineLeft(nameLayout.getLineCount() - 1); - if (photoWidth - AndroidUtilities.dp(8) - lastLineWidth < timeWidthTotal) { - additionHeight += AndroidUtilities.dp(14); - } - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - - currentPhotoFilter = String.format(Locale.US, "%d_%d", (int) (w / AndroidUtilities.density), (int) (h / AndroidUtilities.density)); - if (messageObject.photoThumbs != null && messageObject.photoThumbs.size() > 1 || messageObject.type == 3 || messageObject.type == 8) { - if (messageObject.isSecretPhoto()) { - currentPhotoFilter += "_b2"; - } else { - currentPhotoFilter += "_b"; - } - } - - boolean noSize = false; - if (messageObject.type == 3 || messageObject.type == 8) { - noSize = true; - } - if (currentPhotoObject != null && !noSize && currentPhotoObject.size == 0) { - currentPhotoObject.size = -1; - } - - if (messageObject.type == 1) { - if (currentPhotoObject != null) { - String fileName = FileLoader.getAttachFileName(currentPhotoObject); - boolean photoExist = true; - File cacheFile = FileLoader.getPathToMessage(messageObject.messageOwner); - if (!cacheFile.exists()) { - photoExist = false; - } else { - MediaController.getInstance().removeLoadingFileObserver(this); - } - - if (photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) || FileLoader.getInstance().isLoadingFile(fileName)) { - if (allowedToSetPhoto || ImageLoader.getInstance().getImageFromMemory(currentPhotoObject.location, null, currentPhotoFilter) != null) { - allowedToSetPhoto = true; - photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilter, noSize ? 0 : currentPhotoObject.size, null, false); - } else if (currentPhotoObjectThumb != null) { - photoImage.setImage(null, null, currentPhotoObjectThumb.location, currentPhotoFilter, 0, null, false); - } else { - photoImage.setImageBitmap((Drawable) null); - } - } else { - photoNotSet = true; - if (currentPhotoObjectThumb != null) { - photoImage.setImage(null, null, currentPhotoObjectThumb.location, currentPhotoFilter, 0, null, false); - } else { - photoImage.setImageBitmap((Drawable) null); - } - } - } else { - photoImage.setImageBitmap((Bitmap) null); - } - } else if (messageObject.type == 8) { - String fileName = FileLoader.getAttachFileName(messageObject.messageOwner.media.document); - File cacheFile = null; - boolean localFile = false; - if (messageObject.messageOwner.attachPath != null && messageObject.messageOwner.attachPath.length() != 0) { - cacheFile = new File(messageObject.messageOwner.attachPath); - if (!cacheFile.exists()) { - cacheFile = null; - } else { - MediaController.getInstance().removeLoadingFileObserver(this); - localFile = true; - } - } - if (cacheFile == null) { - cacheFile = FileLoader.getPathToMessage(messageObject.messageOwner); - if (!cacheFile.exists()) { - cacheFile = null; - } - } - if (!messageObject.isSending() && (cacheFile != null || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_GIF) && MessageObject.isNewGifDocument(messageObject.messageOwner.media.document) || FileLoader.getInstance().isLoadingFile(fileName))) { - if (localFile) { - photoImage.setImage(null, messageObject.isSendError() ? null : cacheFile.getAbsolutePath(), null, null, currentPhotoObject != null ? currentPhotoObject.location : null, currentPhotoFilter, 0, null, false); - } else { - photoImage.setImage(messageObject.messageOwner.media.document, null, currentPhotoObject != null ? currentPhotoObject.location : null, currentPhotoFilter, messageObject.messageOwner.media.document.size, null, false); - } - } else { - photoNotSet = true; - photoImage.setImage(null, null, currentPhotoObject != null ? currentPhotoObject.location : null, currentPhotoFilter, 0, null, false); - } - } else { - photoImage.setImage(null, null, currentPhotoObject != null ? currentPhotoObject.location : null, currentPhotoFilter, 0, null, false); - } - } - super.setMessageObject(messageObject); - - if (drawForwardedName) { - namesOffset += AndroidUtilities.dp(5); - } else if (drawName && messageObject.messageOwner.reply_to_msg_id == 0) { - namesOffset += AndroidUtilities.dp(7); - } - - invalidate(); - } - updateButtonState(dataChanged); - } - - @Override - protected int getMaxNameWidth() { - return backgroundWidth - AndroidUtilities.dp(14); - } - - @Override - public ImageReceiver getPhotoImage() { - return photoImage; - } - - public void updateButtonState(boolean animated) { - String fileName = null; - File cacheFile = null; - if (currentMessageObject.type == 1) { - if (currentPhotoObject == null) { - return; - } - fileName = FileLoader.getAttachFileName(currentPhotoObject); - cacheFile = FileLoader.getPathToMessage(currentMessageObject.messageOwner); - } else if (currentMessageObject.type == 8 || currentMessageObject.type == 3 || currentMessageObject.type == 9) { - if (currentMessageObject.messageOwner.attachPath != null && currentMessageObject.messageOwner.attachPath.length() != 0) { - File f = new File(currentMessageObject.messageOwner.attachPath); - if (f.exists()) { - fileName = currentMessageObject.messageOwner.attachPath; - cacheFile = f; - } - } - if (fileName == null) { - if (!currentMessageObject.isSendError()) { - fileName = currentMessageObject.getFileName(); - cacheFile = FileLoader.getPathToMessage(currentMessageObject.messageOwner); - } - } - } - if (fileName == null || fileName.length() == 0) { - radialProgress.setBackground(null, false, false); - return; - } - if (currentMessageObject.isOut() && currentMessageObject.isSending()) { - if (currentMessageObject.messageOwner.attachPath != null && currentMessageObject.messageOwner.attachPath.length() > 0) { - MediaController.getInstance().addLoadingFileObserver(currentMessageObject.messageOwner.attachPath, this); - boolean needProgress = currentMessageObject.messageOwner.attachPath == null || !currentMessageObject.messageOwner.attachPath.startsWith("http"); - HashMap params = currentMessageObject.messageOwner.params; - if (currentMessageObject.messageOwner.message != null && params != null && (params.containsKey("url") || params.containsKey("bot"))) { - needProgress = false; - buttonState = -1; - } else { - buttonState = 1; - } - radialProgress.setBackground(getDrawableForCurrentState(), needProgress, animated); - if (needProgress) { - Float progress = ImageLoader.getInstance().getFileProgress(currentMessageObject.messageOwner.attachPath); - if (progress == null && SendMessagesHelper.getInstance().isSendingMessage(currentMessageObject.getId())) { - progress = 1.0f; - } - radialProgress.setProgress(progress != null ? progress : 0, false); - } else { - radialProgress.setProgress(0, false); - } - invalidate(); - } - } else { - if (currentMessageObject.messageOwner.attachPath != null && currentMessageObject.messageOwner.attachPath.length() != 0) { - MediaController.getInstance().removeLoadingFileObserver(this); - } - if (cacheFile.exists() && cacheFile.length() == 0) { - cacheFile.delete(); - } - if (!cacheFile.exists()) { - MediaController.getInstance().addLoadingFileObserver(fileName, this); - float setProgress = 0; - boolean progressVisible = false; - if (!FileLoader.getInstance().isLoadingFile(fileName)) { - if (!cancelLoading && - (currentMessageObject.type == 1 && MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) || - currentMessageObject.type == 8 && MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_GIF) && MessageObject.isNewGifDocument(currentMessageObject.messageOwner.media.document)) ) { - progressVisible = true; - buttonState = 1; - } else { - buttonState = 0; - } - } else { - progressVisible = true; - buttonState = 1; - Float progress = ImageLoader.getInstance().getFileProgress(fileName); - setProgress = progress != null ? progress : 0; - } - radialProgress.setProgress(setProgress, false); - radialProgress.setBackground(getDrawableForCurrentState(), progressVisible, animated); - invalidate(); - } else { - MediaController.getInstance().removeLoadingFileObserver(this); - if (currentMessageObject.type == 8 && !photoImage.isAllowStartAnimation()) { - buttonState = 2; - } else if (currentMessageObject.type == 3) { - buttonState = 3; - } else { - buttonState = -1; - } - radialProgress.setBackground(getDrawableForCurrentState(), false, animated); - if (photoNotSet) { - setMessageObject(currentMessageObject); - } - invalidate(); - } - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), photoHeight + AndroidUtilities.dp(14) + namesOffset + additionHeight); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - - int x; - if (currentMessageObject.isOutOwner()) { - if (media) { - x = layoutWidth - backgroundWidth - AndroidUtilities.dp(3); - } else { - x = layoutWidth - backgroundWidth + AndroidUtilities.dp(6); - } - } else { - if (isChat && currentMessageObject.isFromUser()) { - x = AndroidUtilities.dp(67); - } else { - x = AndroidUtilities.dp(15); - } - } - photoImage.setImageCoords(x, AndroidUtilities.dp(7) + namesOffset, photoWidth, photoHeight); - int size = AndroidUtilities.dp(48); - buttonX = (int) (x + (photoWidth - size) / 2.0f); - buttonY = (int) (AndroidUtilities.dp(7) + (photoHeight - size) / 2.0f) + namesOffset; - - radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(48), buttonY + AndroidUtilities.dp(48)); - deleteProgressRect.set(buttonX + AndroidUtilities.dp(3), buttonY + AndroidUtilities.dp(3), buttonX + AndroidUtilities.dp(45), buttonY + AndroidUtilities.dp(45)); - } - - private void updateSecretTimeText(MessageObject messageObject) { - if (messageObject == null || messageObject.isOut()) { - return; - } - String str = messageObject.getSecretTimeString(); - if (str == null) { - infoLayout = null; - return; - } - if (currentInfoString == null || !currentInfoString.equals(str)) { - currentInfoString = str; - infoOffset = 0; - infoWidth = (int) Math.ceil(infoPaint.measureText(currentInfoString)); - CharSequence str2 = TextUtils.ellipsize(currentInfoString, infoPaint, infoWidth, TextUtils.TruncateAt.END); - infoLayout = new StaticLayout(str2, infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - invalidate(); - } - } - - public void setAllowedToSetPhoto(boolean value) { - if (allowedToSetPhoto == value) { - return; - } - if (currentMessageObject != null && currentMessageObject.type == 1) { - allowedToSetPhoto = value; - if (value) { - MessageObject temp = currentMessageObject; - currentMessageObject = null; - setMessageObject(temp); - } - } - } - - @Override - protected void onAfterBackgroundDraw(Canvas canvas) { - photoImage.setPressed(isDrawSelectedBackground()); - photoImage.setVisible(!PhotoViewer.getInstance().isShowingImage(currentMessageObject), false); - boolean imageDrawn = photoImage.draw(canvas); - drawTime = photoImage.getVisible(); - - radialProgress.setHideCurrentDrawable(false); - - if (currentMessageObject.type == 9) { - Drawable menuDrawable; - if (currentMessageObject.isOutOwner()) { - infoPaint.setColor(0xff70b15c); - docBackPaint.setColor(isDrawSelectedBackground() ? 0xffc5eca7 : 0xffdaf5c3); - menuDrawable = ResourceLoader.docMenuDrawable[1]; - } else { - infoPaint.setColor(isDrawSelectedBackground() ? 0xff89b4c1 : 0xffa1aab3); - docBackPaint.setColor(isDrawSelectedBackground() ? 0xffcbeaf6 : 0xffebf0f5); - menuDrawable = ResourceLoader.docMenuDrawable[isDrawSelectedBackground() ? 2 : 0]; - } - - setDrawableBounds(menuDrawable, photoImage.getImageX() + backgroundWidth - AndroidUtilities.dp(44), AndroidUtilities.dp(10) + namesOffset); - menuDrawable.draw(canvas); - - if (buttonState >= 0 && buttonState < 4) { - if (!imageDrawn) { - if (buttonState == 1 && !currentMessageObject.isSending()) { - radialProgress.swapBackground(ResourceLoader.buttonStatesDrawablesDoc[2][currentMessageObject.isOutOwner() ? 1 : (isDrawSelectedBackground() ? 2 : 0)]); - } else { - radialProgress.swapBackground(ResourceLoader.buttonStatesDrawablesDoc[buttonState][currentMessageObject.isOutOwner() ? 1 : (isDrawSelectedBackground() ? 2 : 0)]); - } - } else { - if (buttonState == 1 && !currentMessageObject.isSending()) { - radialProgress.swapBackground(ResourceLoader.buttonStatesDrawables[4]); - } else { - radialProgress.swapBackground(ResourceLoader.buttonStatesDrawables[buttonState]); - } - } - } - - if (!imageDrawn) { - canvas.drawRect(photoImage.getImageX(), photoImage.getImageY(), photoImage.getImageX() + photoImage.getImageWidth(), photoImage.getImageY() + photoImage.getImageHeight(), docBackPaint); - if (currentMessageObject.isOutOwner()) { - radialProgress.setProgressColor(0xff81bd72); - } else { - radialProgress.setProgressColor(isDrawSelectedBackground() ? 0xff83b2c2 : 0xffadbdcc); - } - } else { - if (buttonState == -1) { - radialProgress.setHideCurrentDrawable(true); - } - radialProgress.setProgressColor(0xffffffff); - } - } else { - radialProgress.setProgressColor(0xffffffff); - } - - if (buttonState == -1 && currentMessageObject.isSecretPhoto()) { - int drawable = 5; - if (currentMessageObject.messageOwner.destroyTime != 0) { - if (currentMessageObject.isOutOwner()) { - drawable = 7; - } else { - drawable = 6; - } - } - setDrawableBounds(ResourceLoader.buttonStatesDrawables[drawable], buttonX, buttonY); - ResourceLoader.buttonStatesDrawables[drawable].setAlpha((int) (255 * (1.0f - radialProgress.getAlpha()))); - ResourceLoader.buttonStatesDrawables[drawable].draw(canvas); - if (!currentMessageObject.isOutOwner() && currentMessageObject.messageOwner.destroyTime != 0) { - long msTime = System.currentTimeMillis() + ConnectionsManager.getInstance().getTimeDifference() * 1000; - float progress = Math.max(0, (long) currentMessageObject.messageOwner.destroyTime * 1000 - msTime) / (currentMessageObject.messageOwner.ttl * 1000.0f); - canvas.drawArc(deleteProgressRect, -90, -360 * progress, true, deleteProgressPaint); - if (progress != 0) { - int offset = AndroidUtilities.dp(2); - invalidate((int) deleteProgressRect.left - offset, (int) deleteProgressRect.top - offset, (int) deleteProgressRect.right + offset * 2, (int) deleteProgressRect.bottom + offset * 2); - } - updateSecretTimeText(currentMessageObject); - } - } - - radialProgress.draw(canvas); - - if (currentMessageObject.type == 1 || currentMessageObject.type == 3) { - if (nameLayout != null) { - canvas.save(); - canvas.translate(captionX = photoImage.getImageX() + AndroidUtilities.dp(5), captionY = photoImage.getImageY() + photoHeight + AndroidUtilities.dp(6)); - if (pressedLink != null) { - canvas.drawPath(urlPath, urlPaint); - } - try { - nameLayout.draw(canvas); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - canvas.restore(); - } - if (infoLayout != null && (buttonState == 1 || buttonState == 0 || buttonState == 3 || currentMessageObject.isSecretPhoto())) { - infoPaint.setColor(0xffffffff); - setDrawableBounds(ResourceLoader.mediaBackgroundDrawable, photoImage.getImageX() + AndroidUtilities.dp(4), photoImage.getImageY() + AndroidUtilities.dp(4), infoWidth + AndroidUtilities.dp(8) + infoOffset, AndroidUtilities.dp(16.5f)); - ResourceLoader.mediaBackgroundDrawable.draw(canvas); - - if (currentMessageObject.type == 3) { - setDrawableBounds(ResourceLoader.videoIconDrawable, photoImage.getImageX() + AndroidUtilities.dp(8), photoImage.getImageY() + AndroidUtilities.dp(7.5f)); - ResourceLoader.videoIconDrawable.draw(canvas); - } - - canvas.save(); - canvas.translate(photoImage.getImageX() + AndroidUtilities.dp(8) + infoOffset, photoImage.getImageY() + AndroidUtilities.dp(5.5f)); - infoLayout.draw(canvas); - canvas.restore(); - } - } else if (currentMessageObject.type == 4) { - if (nameLayout != null) { - locationAddressPaint.setColor(currentMessageObject.isOutOwner() ? 0xff70b15c : (isDrawSelectedBackground() ? 0xff89b4c1 : 0xff999999)); - - canvas.save(); - canvas.translate(nameOffsetX + photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + AndroidUtilities.dp(3)); - nameLayout.draw(canvas); - canvas.restore(); - - if (infoLayout != null) { - canvas.save(); - canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + AndroidUtilities.dp(nameLayout.getLineCount() * 16 + 5)); - infoLayout.draw(canvas); - canvas.restore(); - } - } - } else if (currentMessageObject.type == 8) { - if (nameLayout != null) { - canvas.save(); - canvas.translate(captionX = photoImage.getImageX() + AndroidUtilities.dp(5), captionY = photoImage.getImageY() + photoHeight + AndroidUtilities.dp(6)); - if (pressedLink != null) { - canvas.drawPath(urlPath, urlPaint); - } - try { - nameLayout.draw(canvas); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - canvas.restore(); - } - } else if (nameLayout != null) { - canvas.save(); - canvas.translate(nameOffsetX + photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + AndroidUtilities.dp(8)); - nameLayout.draw(canvas); - canvas.restore(); - - try { - if (infoLayout != null) { - canvas.save(); - canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + nameLayout.getLineBottom(nameLayout.getLineCount() - 1) + AndroidUtilities.dp(10)); - infoLayout.draw(canvas); - canvas.restore(); - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - } - - @Override - public void onFailedDownload(String fileName) { - updateButtonState(false); - } - - @Override - public void onSuccessDownload(String fileName) { - radialProgress.setProgress(1, true); - if (!photoNotSet || currentMessageObject.type == 8 && currentMessageObject.audioProgress != 1) { - if (currentMessageObject.type == 8 && currentMessageObject.audioProgress != 1) { - photoNotSet = false; - buttonState = 2; - didPressedButton(true); - } else { - updateButtonState(true); - } - } - if (photoNotSet) { - setMessageObject(currentMessageObject); - } - } - - @Override - public void onProgressDownload(String fileName, float progress) { - radialProgress.setProgress(progress, true); - if (buttonState != 1) { - updateButtonState(false); - } - } - - @Override - public void onProgressUpload(String fileName, float progress, boolean isEncrypted) { - radialProgress.setProgress(progress, true); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java index b07d425a90..978d7c064d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java @@ -1,5 +1,5 @@ /* - * This is the source code of Telegram for Android v. 1.3.x. + * This is the source code of Telegram for Android v. 3.x.x. * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * @@ -11,6 +11,8 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.text.Layout; @@ -28,37 +30,45 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ImageLoader; import org.telegram.messenger.ImageReceiver; +import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaController; import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.MessageObject; import org.telegram.messenger.R; +import org.telegram.messenger.SendMessagesHelper; +import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.Components.LinkPath; import org.telegram.ui.Components.RadialProgress; import org.telegram.ui.Components.ResourceLoader; import org.telegram.ui.Components.StaticLayoutEx; import org.telegram.ui.Components.URLSpanBotCommand; import org.telegram.ui.Components.URLSpanNoUnderline; +import org.telegram.ui.PhotoViewer; import java.io.File; +import java.util.HashMap; import java.util.Locale; public class ChatMessageCell extends ChatBaseCell { - private int textX, textY; - private int totalHeight = 0; + private int textX; + private int textY; + private int totalHeight; private int linkBlockNum; - private int lastVisibleBlockNum = 0; - private int firstVisibleBlockNum = 0; - private int totalVisibleBlocksCount = 0; + private int lastVisibleBlockNum; + private int firstVisibleBlockNum; + private int totalVisibleBlocksCount; private RadialProgress radialProgress; - private ImageReceiver linkImageView; + private ImageReceiver photoImage; + private boolean isSmallImage; private boolean drawImageButton; - private boolean isGifDocument; - private boolean drawLinkImageView; + private int isDocument; + private boolean drawPhotoImage; private boolean hasLinkPreview; private int linkPreviewHeight; private boolean isInstagram; @@ -67,18 +77,36 @@ public class ChatMessageCell extends ChatBaseCell { private int descriptionX; private int titleX; private int authorX; - private StaticLayout siteNameLayout; + private StaticLayout sitecaptionLayout; private StaticLayout titleLayout; private StaticLayout descriptionLayout; private StaticLayout durationLayout; private StaticLayout authorLayout; private static TextPaint durationPaint; + private StaticLayout captionLayout; + private int captionX; + private int captionY; + private int captionHeight; + private int nameOffsetX; + + private StaticLayout infoLayout; + private int infoWidth; + private int infoOffset; + + private String currentUrl; + + private boolean allowedToSetPhoto = true; + private int buttonX; private int buttonY; private int buttonState; - private boolean buttonPressed; + private int buttonPressed; + private int otherX; + private boolean imagePressed; + private boolean otherPressed; private boolean photoNotSet; + private RectF deleteProgressRect = new RectF(); private TLRPC.PhotoSize currentPhotoObject; private TLRPC.PhotoSize currentPhotoObjectThumb; private String currentPhotoFilter; @@ -86,198 +114,371 @@ public class ChatMessageCell extends ChatBaseCell { private boolean cancelLoading; private static Drawable igvideoDrawable; + private static TextPaint infoPaint; + private static TextPaint namePaint; + private static Paint docBackPaint; + private static Paint deleteProgressPaint; + private static TextPaint locationTitlePaint; + private static TextPaint locationAddressPaint; + private static Paint urlPaint; + + private ClickableSpan pressedLink; + private int pressedLinkType; + private boolean linkPreviewPressed; + private LinkPath urlPath = new LinkPath(); public ChatMessageCell(Context context) { super(context); - drawForwardedName = true; - linkImageView = new ImageReceiver(this); + photoImage = new ImageReceiver(this); radialProgress = new RadialProgress(this); + + if (infoPaint == null) { + infoPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + infoPaint.setTextSize(AndroidUtilities.dp(12)); + + namePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + namePaint.setColor(0xff212121); + namePaint.setTextSize(AndroidUtilities.dp(16)); + + docBackPaint = new Paint(); + + deleteProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + deleteProgressPaint.setColor(0xffe4e2e0); + + locationTitlePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + locationTitlePaint.setTextSize(AndroidUtilities.dp(14)); + locationTitlePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + + locationAddressPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + locationAddressPaint.setTextSize(AndroidUtilities.dp(14)); + + igvideoDrawable = getResources().getDrawable(R.drawable.igvideo); + + urlPaint = new Paint(); + urlPaint.setColor(0x33316f9f); + } } - @Override - public boolean onTouchEvent(MotionEvent event) { - boolean result = false; - if (currentMessageObject != null && currentMessageObject.textLayoutBlocks != null && !currentMessageObject.textLayoutBlocks.isEmpty() && currentMessageObject.messageText instanceof Spannable && delegate.canPerformActions()) { - if (event.getAction() == MotionEvent.ACTION_DOWN || (linkPreviewPressed || pressedLink != null || buttonPressed) && event.getAction() == MotionEvent.ACTION_UP) { - int x = (int) event.getX(); - int y = (int) event.getY(); - if (x >= textX && y >= textY && x <= textX + currentMessageObject.textWidth && y <= textY + currentMessageObject.textHeight) { - y -= textY; - int blockNum = Math.max(0, y / currentMessageObject.blockHeight); - if (blockNum < currentMessageObject.textLayoutBlocks.size()) { - try { - MessageObject.TextLayoutBlock block = currentMessageObject.textLayoutBlocks.get(blockNum); - x -= textX - (int) Math.ceil(block.textXOffset); - y -= block.textYOffset; - final int line = block.textLayout.getLineForVertical(y); - final int off = block.textLayout.getOffsetForHorizontal(line, x) + block.charactersOffset; - - final float left = block.textLayout.getLineLeft(line); - if (left <= x && left + block.textLayout.getLineWidth(line) >= x) { - Spannable buffer = (Spannable) currentMessageObject.messageText; - ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); - boolean ignore = false; - if (link.length == 0 || link.length != 0 && link[0] instanceof URLSpanBotCommand && !URLSpanBotCommand.enabled) { - ignore = true; - } - if (!ignore) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - resetPressedLink(); - pressedLink = link[0]; - linkBlockNum = blockNum; - try { - int start = buffer.getSpanStart(pressedLink) - block.charactersOffset; - urlPath.setCurrentLayout(block.textLayout, start); - block.textLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink) - block.charactersOffset, urlPath); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - result = true; - } else { - if (link[0] == pressedLink) { - try { - delegate.didPressUrl(currentMessageObject, pressedLink, false); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - resetPressedLink(); - result = true; - } - } - } else { - resetPressedLink(); + private void resetPressedLink(int type) { + if (pressedLink == null || pressedLinkType != type && type != -1) { + return; + } + pressedLink = null; + pressedLinkType = -1; + invalidate(); + } + + private boolean checkTextBlockMotionEvent(MotionEvent event) { + if (currentMessageObject.type != 0 || currentMessageObject.textLayoutBlocks == null || currentMessageObject.textLayoutBlocks.isEmpty() || !(currentMessageObject.messageText instanceof Spannable)) { + return false; + } + if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_UP && pressedLinkType == 1) { + int x = (int) event.getX(); + int y = (int) event.getY(); + if (x >= textX && y >= textY && x <= textX + currentMessageObject.textWidth && y <= textY + currentMessageObject.textHeight) { + y -= textY; + int blockNum = 0; + for (int a = 0; a < currentMessageObject.textLayoutBlocks.size(); a++) { + if (currentMessageObject.textLayoutBlocks.get(a).textYOffset > y) { + break; + } + blockNum = a; + } + try { + MessageObject.TextLayoutBlock block = currentMessageObject.textLayoutBlocks.get(blockNum); + x -= textX - (int) Math.ceil(block.textXOffset); + y -= block.textYOffset; + final int line = block.textLayout.getLineForVertical(y); + final int off = block.textLayout.getOffsetForHorizontal(line, x) + block.charactersOffset; + + final float left = block.textLayout.getLineLeft(line); + if (left <= x && left + block.textLayout.getLineWidth(line) >= x) { + Spannable buffer = (Spannable) currentMessageObject.messageText; + ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); + boolean ignore = false; + if (link.length == 0 || link.length != 0 && link[0] instanceof URLSpanBotCommand && !URLSpanBotCommand.enabled) { + ignore = true; + } + if (!ignore) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + pressedLink = link[0]; + linkBlockNum = blockNum; + pressedLinkType = 1; + try { + int start = buffer.getSpanStart(pressedLink) - block.charactersOffset; + urlPath.setCurrentLayout(block.textLayout, start); + block.textLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink) - block.charactersOffset, urlPath); + } catch (Exception e) { + FileLog.e("tmessages", e); } + invalidate(); + return true; } else { - resetPressedLink(); + if (link[0] == pressedLink) { + delegate.didPressedUrl(currentMessageObject, pressedLink, false); + resetPressedLink(1); + return true; + } } - } catch (Exception e) { - resetPressedLink(); - FileLog.e("tmessages", e); } - } else { - resetPressedLink(); - } - } else if (hasLinkPreview && x >= textX && x <= textX + backgroundWidth && y >= textY + currentMessageObject.textHeight && y <= textY + currentMessageObject.textHeight + linkPreviewHeight + AndroidUtilities.dp(8)) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - resetPressedLink(); - if (drawLinkImageView && linkImageView.isInsideImage(x, y)) { - if (drawImageButton && buttonState != -1 && x >= buttonX && x <= buttonX + AndroidUtilities.dp(48) && y >= buttonY && y <= buttonY + AndroidUtilities.dp(48)) { - buttonPressed = true; - result = true; - } else { - linkPreviewPressed = true; - result = true; - } - if (linkPreviewPressed && isGifDocument && buttonState == -1 && MediaController.getInstance().canAutoplayGifs()) { - linkPreviewPressed = false; - result = false; + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } else { + resetPressedLink(1); + } + } + return false; + } + + private boolean chechCaptionMotionEvent(MotionEvent event) { + if (!(currentMessageObject.caption instanceof Spannable) || captionLayout == null) { + return false; + } + if (event.getAction() == MotionEvent.ACTION_DOWN || (linkPreviewPressed || pressedLink != null) && event.getAction() == MotionEvent.ACTION_UP) { + int x = (int) event.getX(); + int y = (int) event.getY(); + if (x >= captionX && x <= captionX + backgroundWidth && y >= captionY && y <= captionY + captionHeight) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + try { + x -= captionX; + y -= captionY; + final int line = captionLayout.getLineForVertical(y); + final int off = captionLayout.getOffsetForHorizontal(line, x); + + final float left = captionLayout.getLineLeft(line); + if (left <= x && left + captionLayout.getLineWidth(line) >= x) { + Spannable buffer = (Spannable) currentMessageObject.caption; + ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); + boolean ignore = false; + if (link.length == 0 || link.length != 0 && link[0] instanceof URLSpanBotCommand && !URLSpanBotCommand.enabled) { + ignore = true; } - } else { - if (descriptionLayout != null && y >= descriptionY) { + if (!ignore) { + pressedLink = link[0]; + pressedLinkType = 3; try { - x -= textX + AndroidUtilities.dp(10) + descriptionX; - y -= descriptionY; - final int line = descriptionLayout.getLineForVertical(y); - final int off = descriptionLayout.getOffsetForHorizontal(line, x); - - final float left = descriptionLayout.getLineLeft(line); - if (left <= x && left + descriptionLayout.getLineWidth(line) >= x) { - Spannable buffer = (Spannable) currentMessageObject.linkDescription; - ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); - boolean ignore = false; - if (link.length == 0 || link.length != 0 && link[0] instanceof URLSpanBotCommand && !URLSpanBotCommand.enabled) { - ignore = true; - } - if (!ignore) { - resetPressedLink(); - pressedLink = link[0]; - linkPreviewPressed = true; - linkBlockNum = -10; - result = true; - try { - int start = buffer.getSpanStart(pressedLink); - urlPath.setCurrentLayout(descriptionLayout, start); - descriptionLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink), urlPath); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } else { - resetPressedLink(); - } - } else { - resetPressedLink(); - } + int start = buffer.getSpanStart(pressedLink); + urlPath.setCurrentLayout(captionLayout, start); + captionLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink), urlPath); } catch (Exception e) { - resetPressedLink(); FileLog.e("tmessages", e); } + invalidate(); + return true; } } - } else if (linkPreviewPressed) { - try { - if (pressedLink != null) { - if (pressedLink instanceof URLSpan) { - AndroidUtilities.openUrl(getContext(), ((URLSpan) pressedLink).getURL()); - } else { - pressedLink.onClick(this); - } - } else { - if (drawImageButton && delegate != null) { - if (isGifDocument) { - if (buttonState == -1) { - buttonState = 2; - currentMessageObject.audioProgress = 1; - linkImageView.setAllowStartAnimation(false); - linkImageView.stopAnimation(); - radialProgress.setBackground(getDrawableForCurrentState(), false, false); - invalidate(); - playSoundEffect(SoundEffectConstants.CLICK); - } else if (buttonState == 2 || buttonState == 0) { - didPressedButton(false); - playSoundEffect(SoundEffectConstants.CLICK); - } - } else if (buttonState == -1) { - delegate.didClickedImage(this); - playSoundEffect(SoundEffectConstants.CLICK); - } - } else { - TLRPC.WebPage webPage = currentMessageObject.messageOwner.media.webpage; - if (Build.VERSION.SDK_INT >= 16 && webPage.embed_url != null && webPage.embed_url.length() != 0) { - delegate.needOpenWebView(webPage.embed_url, webPage.site_name, webPage.url, webPage.embed_width, webPage.embed_height); - } else { - AndroidUtilities.openUrl(getContext(), webPage.url); - } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } else if (pressedLinkType == 3) { + delegate.didPressedUrl(currentMessageObject, pressedLink, false); + resetPressedLink(3); + return true; + } + } else { + resetPressedLink(3); + } + } + return false; + } + + private boolean checkLinkPreviewMotionEvent(MotionEvent event) { + if (currentMessageObject.type != 0 || !hasLinkPreview) { + return false; + } + int x = (int) event.getX(); + int y = (int) event.getY(); + + if (x >= textX && x <= textX + backgroundWidth && y >= textY + currentMessageObject.textHeight && y <= textY + currentMessageObject.textHeight + linkPreviewHeight + AndroidUtilities.dp(8)) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (isDocument != 1 && drawPhotoImage && photoImage.isInsideImage(x, y)) { + if (drawImageButton && buttonState != -1 && x >= buttonX && x <= buttonX + AndroidUtilities.dp(48) && y >= buttonY && y <= buttonY + AndroidUtilities.dp(48)) { + buttonPressed = 1; + return true; + } else if (isDocument == 2 && buttonState == -1 && MediaController.getInstance().canAutoplayGifs()) { + linkPreviewPressed = false; + return false; + } else { + linkPreviewPressed = true; + return true; + } + } else if (descriptionLayout != null && y >= descriptionY) { + try { + x -= textX + AndroidUtilities.dp(10) + descriptionX; + y -= descriptionY; + final int line = descriptionLayout.getLineForVertical(y); + final int off = descriptionLayout.getOffsetForHorizontal(line, x); + + final float left = descriptionLayout.getLineLeft(line); + if (left <= x && left + descriptionLayout.getLineWidth(line) >= x) { + Spannable buffer = (Spannable) currentMessageObject.linkDescription; + ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); + boolean ignore = false; + if (link.length == 0 || link.length != 0 && link[0] instanceof URLSpanBotCommand && !URLSpanBotCommand.enabled) { + ignore = true; + } + if (!ignore) { + pressedLink = link[0]; + linkBlockNum = -10; + pressedLinkType = 2; + try { + int start = buffer.getSpanStart(pressedLink); + urlPath.setCurrentLayout(descriptionLayout, start); + descriptionLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink), urlPath); + } catch (Exception e) { + FileLog.e("tmessages", e); } + invalidate(); + return true; } - } catch (Exception e) { - FileLog.e("tmessages", e); } - resetPressedLink(); - result = true; - } else if (buttonPressed) { - if (event.getAction() == MotionEvent.ACTION_UP) { - buttonPressed = false; - playSoundEffect(SoundEffectConstants.CLICK); - didPressedButton(false); - invalidate(); - } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { - buttonPressed = false; - invalidate(); - } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (!(x >= buttonX && x <= buttonX + AndroidUtilities.dp(48) && y >= buttonY && y <= buttonY + AndroidUtilities.dp(48))) { - buttonPressed = false; + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + } else if (event.getAction() == MotionEvent.ACTION_UP && (pressedLinkType == 2 || buttonPressed != 0 || linkPreviewPressed)) { + if (buttonPressed != 0) { + if (event.getAction() == MotionEvent.ACTION_UP) { + buttonPressed = 0; + playSoundEffect(SoundEffectConstants.CLICK); + didPressedButton(false); + invalidate(); + } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { + buttonPressed = 0; + invalidate(); + } + } else if (pressedLink != null) { + if (pressedLink instanceof URLSpan) { + AndroidUtilities.openUrl(getContext(), ((URLSpan) pressedLink).getURL()); + } else { + pressedLink.onClick(this); + } + } else { + if (drawImageButton) { + if (isDocument == 2) { + if (buttonState == -1) { + buttonState = 2; + currentMessageObject.audioProgress = 1; + photoImage.setAllowStartAnimation(false); + photoImage.stopAnimation(); + radialProgress.setBackground(getDrawableForCurrentState(), false, false); invalidate(); + playSoundEffect(SoundEffectConstants.CLICK); + } else if (buttonState == 2 || buttonState == 0) { + didPressedButton(false); + playSoundEffect(SoundEffectConstants.CLICK); } + } else if (buttonState == -1) { + delegate.didPressedImage(this); + playSoundEffect(SoundEffectConstants.CLICK); + } + } else { + TLRPC.WebPage webPage = currentMessageObject.messageOwner.media.webpage; + if (Build.VERSION.SDK_INT >= 16 && webPage.embed_url != null && webPage.embed_url.length() != 0) { + delegate.needOpenWebView(webPage.embed_url, webPage.site_name, webPage.url, webPage.embed_width, webPage.embed_height); + } else { + AndroidUtilities.openUrl(getContext(), webPage.url); } } - } else { - resetPressedLink(); + resetPressedLink(2); + return true; + } + } else { + resetPressedLink(2); + } + } + return false; + } + + private boolean checkPhotoImageMotionEvent(MotionEvent event) { + if (!drawPhotoImage) { + return false; + } + + int x = (int) event.getX(); + int y = (int) event.getY(); + + boolean result = false; + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (buttonState != -1 && x >= buttonX && x <= buttonX + AndroidUtilities.dp(48) && y >= buttonY && y <= buttonY + AndroidUtilities.dp(48)) { + buttonPressed = 1; + invalidate(); + result = true; + } else { + if (isDocument == 1) { + if (x >= photoImage.getImageX() && x <= photoImage.getImageX() + backgroundWidth - AndroidUtilities.dp(50) && y >= photoImage.getImageY() && y <= photoImage.getImageY() + photoImage.getImageHeight()) { + imagePressed = true; + result = true; + } else if (x >= otherX - AndroidUtilities.dp(20) && x <= photoImage.getImageX() + backgroundWidth && y >= photoImage.getImageY() && y <= photoImage.getImageY() + photoImage.getImageHeight()) { + otherPressed = true; + result = true; + } + } else if (currentMessageObject.type != 13) { + if (x >= photoImage.getImageX() && x <= photoImage.getImageX() + backgroundWidth && y >= photoImage.getImageY() && y <= photoImage.getImageY() + photoImage.getImageHeight()) { + imagePressed = true; + result = true; + } } - } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { - resetPressedLink(); + } + if (imagePressed && currentMessageObject.isSecretPhoto()) { + imagePressed = false; + } else if (imagePressed && currentMessageObject.isSendError()) { + imagePressed = false; + result = false; + } else if (imagePressed && currentMessageObject.type == 8 && buttonState == -1 && MediaController.getInstance().canAutoplayGifs()) { + imagePressed = false; + result = false; } } else { - resetPressedLink(); + if (event.getAction() == MotionEvent.ACTION_UP) { + if (buttonPressed == 1) { + buttonPressed = 0; + playSoundEffect(SoundEffectConstants.CLICK); + didPressedButton(false); + invalidate(); + } else if (imagePressed) { + imagePressed = false; + if (buttonState == -1 || buttonState == 2 || buttonState == 3) { + playSoundEffect(SoundEffectConstants.CLICK); + didClickedImage(); + } + invalidate(); + } else if (otherPressed) { + otherPressed = false; + playSoundEffect(SoundEffectConstants.CLICK); + delegate.didPressedOther(this); + } + } + } + return result; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (currentMessageObject == null || !delegate.canPerformActions()) { + return super.onTouchEvent(event); + } + + boolean result = checkTextBlockMotionEvent(event); + if (!result) { + result = checkLinkPreviewMotionEvent(event); + } + if (!result) { + result = chechCaptionMotionEvent(event); + } + if (!result) { + result = checkPhotoImageMotionEvent(event); + } + + if (event.getAction() == MotionEvent.ACTION_CANCEL) { + buttonPressed = 0; + linkPreviewPressed = false; + otherPressed = false; + imagePressed = false; + result = false; + resetPressedLink(-1); } if (result && event.getAction() == MotionEvent.ACTION_DOWN) { startCheckLongPress(); @@ -292,12 +493,22 @@ public void setVisiblePart(int position, int height) { if (currentMessageObject == null || currentMessageObject.textLayoutBlocks == null) { return; } + position -= textY; + int newFirst = -1, newLast = -1, newCount = 0; - for (int a = Math.max(0, (position - textY) / currentMessageObject.blockHeight); a < currentMessageObject.textLayoutBlocks.size(); a++) { + int startBlock = 0; + for (int a = 0; a < currentMessageObject.textLayoutBlocks.size(); a++) { + if (currentMessageObject.textLayoutBlocks.get(a).textYOffset > position) { + break; + } + startBlock = a; + } + + for (int a = startBlock; a < currentMessageObject.textLayoutBlocks.size(); a++) { MessageObject.TextLayoutBlock block = currentMessageObject.textLayoutBlocks.get(a); - float y = textY + block.textYOffset; - if (intersect(y, y + currentMessageObject.blockHeight, position, position + height)) { + float y = block.textYOffset; + if (intersect(y, y + block.height, position, position + height)) { if (newFirst == -1) { newFirst = a; } @@ -350,6 +561,86 @@ public static StaticLayout generateStaticLayout(CharSequence text, TextPaint pai return StaticLayoutEx.createStaticLayout(stringBuilder, paint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, AndroidUtilities.dp(1), false, TextUtils.TruncateAt.END, maxWidth, maxLines); } + private void didClickedImage() { + if (currentMessageObject.type == 1) { + if (buttonState == -1) { + delegate.didPressedImage(this); + } else if (buttonState == 0) { + didPressedButton(false); + } + } else if (currentMessageObject.type == 8) { + if (buttonState == -1) { + buttonState = 2; + currentMessageObject.audioProgress = 1; + photoImage.setAllowStartAnimation(false); + photoImage.stopAnimation(); + radialProgress.setBackground(getDrawableForCurrentState(), false, false); + invalidate(); + } else if (buttonState == 2 || buttonState == 0) { + didPressedButton(false); + } + } else if (currentMessageObject.type == 3) { + if (buttonState == 0 || buttonState == 3) { + didPressedButton(false); + } + } else if (currentMessageObject.type == 4) { + delegate.didPressedImage(this); + } else if (isDocument == 1) { + if (buttonState == -1) { + delegate.didPressedImage(this); + } + } else if (isDocument == 2) { + if (buttonState == -1) { + TLRPC.WebPage webPage = currentMessageObject.messageOwner.media.webpage; + if (Build.VERSION.SDK_INT >= 16 && webPage.embed_url != null && webPage.embed_url.length() != 0) { + delegate.needOpenWebView(webPage.embed_url, webPage.site_name, webPage.url, webPage.embed_width, webPage.embed_height); + } else { + AndroidUtilities.openUrl(getContext(), webPage.url); + } + } + } + } + + private void updateSecretTimeText(MessageObject messageObject) { + if (messageObject == null || messageObject.isOut()) { + return; + } + String str = messageObject.getSecretTimeString(); + if (str == null) { + return; + } + infoOffset = 0; + infoWidth = (int) Math.ceil(infoPaint.measureText(str)); + CharSequence str2 = TextUtils.ellipsize(str, infoPaint, infoWidth, TextUtils.TruncateAt.END); + infoLayout = new StaticLayout(str2, infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + invalidate(); + } + + private boolean isPhotoDataChanged(MessageObject object) { + if (object.type == 0) { + return false; + } + if (object.type == 4) { + if (currentUrl == null) { + return true; + } + double lat = object.messageOwner.media.geo.lat; + double lon = object.messageOwner.media.geo._long; + String url = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=100x100&maptype=roadmap&scale=%d&markers=color:red|size:big|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(AndroidUtilities.density)), lat, lon); + if (!url.equals(currentUrl)) { + return true; + } + } else if (currentPhotoObject == null || currentPhotoObject.location instanceof TLRPC.TL_fileLocationUnavailable) { + return true; + } else if (currentMessageObject != null && photoNotSet) { + File cacheFile = FileLoader.getPathToMessage(currentMessageObject.messageOwner); + if (cacheFile.exists()) { + return true; + } + } + return false; + } + @Override protected boolean isUserDataChanged() { if (!hasLinkPreview && currentMessageObject.messageOwner.media != null && currentMessageObject.messageOwner.media.webpage instanceof TLRPC.TL_webPage) { @@ -361,20 +652,20 @@ protected boolean isUserDataChanged() { @Override public ImageReceiver getPhotoImage() { - return linkImageView; + return photoImage; } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - linkImageView.onDetachedFromWindow(); + photoImage.onDetachedFromWindow(); MediaController.getInstance().removeLoadingFileObserver(this); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - if (linkImageView.onAttachedToWindow()) { + if (photoImage.onAttachedToWindow()) { updateButtonState(false); } } @@ -384,427 +675,937 @@ protected void onLongPress() { if (pressedLink instanceof URLSpanNoUnderline) { URLSpanNoUnderline url = (URLSpanNoUnderline) pressedLink; if (url.getURL().startsWith("/")) { - delegate.didPressUrl(currentMessageObject, pressedLink, true); + delegate.didPressedUrl(currentMessageObject, pressedLink, true); return; } } else if (pressedLink instanceof URLSpan) { - delegate.didPressUrl(currentMessageObject, pressedLink, true); + delegate.didPressedUrl(currentMessageObject, pressedLink, true); return; } + resetPressedLink(-1); super.onLongPress(); } + @Override + public void setCheckPressed(boolean value, boolean pressed) { + super.setCheckPressed(value, pressed); + if (radialProgress.swapBackground(getDrawableForCurrentState())) { + invalidate(); + } + } + + @Override + public void setHighlighted(boolean value) { + super.setHighlighted(value); + if (radialProgress.swapBackground(getDrawableForCurrentState())) { + invalidate(); + } + } + + @Override + public void setPressed(boolean pressed) { + super.setPressed(pressed); + if (radialProgress.swapBackground(getDrawableForCurrentState())) { + invalidate(); + } + } + + private int createDocumentLayout(int maxWidth, MessageObject messageObject) { + TLRPC.Document document = null; + if (messageObject.type == 9) { + document = messageObject.messageOwner.media.document; + } else if (messageObject.type == 0) { + document = messageObject.messageOwner.media.webpage.document; + } + if (document == null) { + return 0; + } + isDocument = 1; + String name = FileLoader.getDocumentFileName(document); + if (name == null || name.length() == 0) { + name = LocaleController.getString("AttachDocument", R.string.AttachDocument); + } + captionLayout = StaticLayoutEx.createStaticLayout(name, namePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.MIDDLE, maxWidth, 3); + nameOffsetX = Integer.MIN_VALUE; + int captionWidth; + if (captionLayout != null && captionLayout.getLineCount() > 0) { + int maxLineWidth = 0; + for (int a = 0; a < captionLayout.getLineCount(); a++) { + maxLineWidth = Math.max(maxLineWidth, (int) Math.ceil(captionLayout.getLineWidth(a))); + nameOffsetX = Math.max(nameOffsetX, (int) Math.ceil(-captionLayout.getLineLeft(a))); + } + captionWidth = Math.min(maxWidth, maxLineWidth); + } else { + captionWidth = maxWidth; + nameOffsetX = 0; + } + + String str = AndroidUtilities.formatFileSize(document.size) + " " + FileLoader.getDocumentExtension(document); + infoWidth = Math.min(maxWidth, (int) Math.ceil(infoPaint.measureText(str))); + CharSequence str2 = TextUtils.ellipsize(str, infoPaint, infoWidth, TextUtils.TruncateAt.END); + try { + if (infoWidth < 0) { + infoWidth = AndroidUtilities.dp(10); + } + infoLayout = new StaticLayout(str2, infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + + currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); + photoImage.setNeedsQualityThumb(true); + photoImage.setShouldGenerateQualityThumb(true); + photoImage.setParentMessageObject(messageObject); + if (currentPhotoObject != null) { + currentPhotoFilter = "86_86_b"; + photoImage.setImage(null, null, null, null, currentPhotoObject.location, currentPhotoFilter, 0, null, true); + } else { + photoImage.setImageBitmap((BitmapDrawable) null); + } + return captionWidth; + } + + private void calcBackgroundWidth(int maxWidth, int timeMore, int maxChildWidth) { + if (hasLinkPreview || maxWidth - currentMessageObject.lastLineWidth < timeMore) { + totalHeight += AndroidUtilities.dp(14); + backgroundWidth = Math.max(maxChildWidth, currentMessageObject.lastLineWidth) + AndroidUtilities.dp(29); + backgroundWidth = Math.max(backgroundWidth, timeWidth + AndroidUtilities.dp(29)); + } else { + int diff = maxChildWidth - currentMessageObject.lastLineWidth; + if (diff >= 0 && diff <= timeMore) { + backgroundWidth = maxChildWidth + timeMore - diff + AndroidUtilities.dp(29); + } else { + backgroundWidth = Math.max(maxChildWidth, currentMessageObject.lastLineWidth + timeMore) + AndroidUtilities.dp(29); + } + } + } + @Override public void setMessageObject(MessageObject messageObject) { + boolean messageChanged = currentMessageObject != messageObject; boolean dataChanged = currentMessageObject == messageObject && (isUserDataChanged() || photoNotSet); - if (currentMessageObject != messageObject || dataChanged) { - if (currentMessageObject != messageObject) { - firstVisibleBlockNum = 0; - lastVisibleBlockNum = 0; - } - drawLinkImageView = false; + if (messageChanged || dataChanged || isPhotoDataChanged(messageObject)) { + resetPressedLink(-1); + drawPhotoImage = false; hasLinkPreview = false; - resetPressedLink(); linkPreviewPressed = false; - buttonPressed = false; + buttonPressed = 0; linkPreviewHeight = 0; + infoOffset = 0; isInstagram = false; durationLayout = null; - isGifDocument = false; + isDocument = 0; descriptionLayout = null; titleLayout = null; - siteNameLayout = null; + sitecaptionLayout = null; authorLayout = null; + captionLayout = null; drawImageButton = false; currentPhotoObject = null; currentPhotoObjectThumb = null; currentPhotoFilter = null; - int maxWidth; + infoLayout = null; + cancelLoading = false; + buttonState = -1; + currentUrl = null; + photoNotSet = false; + drawBackground = true; + drawName = false; - if (AndroidUtilities.isTablet()) { - if (isChat && !messageObject.isOutOwner() && messageObject.isFromUser()) { - maxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(122); - drawName = true; - } else { - drawName = messageObject.messageOwner.to_id.channel_id != 0 && !messageObject.isOutOwner(); - maxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(80); - } - } else { - if (isChat && !messageObject.isOutOwner() && messageObject.isFromUser()) { - maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(122); - drawName = true; - } else { - maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(80); - drawName = messageObject.messageOwner.to_id.channel_id != 0 && !messageObject.isOutOwner(); - } + if (messageChanged) { + firstVisibleBlockNum = 0; + lastVisibleBlockNum = 0; } - backgroundWidth = maxWidth; - availableTimeWidth = backgroundWidth - AndroidUtilities.dp(29); - - super.setMessageObject(messageObject); - - backgroundWidth = messageObject.textWidth; - totalHeight = messageObject.textHeight + AndroidUtilities.dp(19.5f) + namesOffset; + if (messageObject.type == 0) { + drawForwardedName = true; + mediaBackground = false; - int maxChildWidth = Math.max(backgroundWidth, nameWidth); - maxChildWidth = Math.max(maxChildWidth, forwardedNameWidth); - maxChildWidth = Math.max(maxChildWidth, replyNameWidth); - maxChildWidth = Math.max(maxChildWidth, replyTextWidth); - int maxWebWidth = 0; - - int timeMore = timeWidth + AndroidUtilities.dp(6); - if (messageObject.isOutOwner()) { - timeMore += AndroidUtilities.dp(20.5f); - } - - if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && messageObject.messageOwner.media.webpage instanceof TLRPC.TL_webPage) { - int linkPreviewMaxWidth; + int maxWidth; if (AndroidUtilities.isTablet()) { - if (messageObject.isFromUser() && (currentMessageObject.messageOwner.to_id.channel_id != 0 || currentMessageObject.messageOwner.to_id.chat_id != 0) && !currentMessageObject.isOut()) { - linkPreviewMaxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(122); + if (isChat && !messageObject.isOutOwner() && messageObject.isFromUser()) { + maxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(122); + drawName = true; } else { - linkPreviewMaxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(80); + drawName = messageObject.messageOwner.to_id.channel_id != 0 && !messageObject.isOutOwner(); + maxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(80); } } else { - if (messageObject.isFromUser() && (currentMessageObject.messageOwner.to_id.channel_id != 0 || currentMessageObject.messageOwner.to_id.chat_id != 0) && !currentMessageObject.isOutOwner()) { - linkPreviewMaxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(122); + if (isChat && !messageObject.isOutOwner() && messageObject.isFromUser()) { + maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(122); + drawName = true; } else { - linkPreviewMaxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(80); + maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(80); + drawName = messageObject.messageOwner.to_id.channel_id != 0 && !messageObject.isOutOwner(); } } - if (drawShareButton) { - linkPreviewMaxWidth -= AndroidUtilities.dp(20); - } - TLRPC.TL_webPage webPage = (TLRPC.TL_webPage) messageObject.messageOwner.media.webpage; + backgroundWidth = maxWidth; + availableTimeWidth = backgroundWidth - AndroidUtilities.dp(29); - if (webPage.site_name != null && webPage.photo != null && webPage.site_name.toLowerCase().equals("instagram")) { - linkPreviewMaxWidth = Math.max(AndroidUtilities.displaySize.y / 3, currentMessageObject.textWidth); - } - - int additinalWidth = AndroidUtilities.dp(10); - int restLinesCount = 3; - int additionalHeight = 0; - linkPreviewMaxWidth -= additinalWidth; + super.setMessageObject(messageObject); - hasLinkPreview = true; - - if (currentMessageObject.photoThumbs == null && webPage.photo != null) { - currentMessageObject.generateThumbs(true); - } + backgroundWidth = messageObject.textWidth; + totalHeight = messageObject.textHeight + AndroidUtilities.dp(19.5f) + namesOffset; - isSmallImage = webPage.description != null && webPage.type != null && (webPage.type.equals("app") || webPage.type.equals("profile") || webPage.type.equals("article")) && currentMessageObject.photoThumbs != null; + int maxChildWidth = Math.max(backgroundWidth, nameWidth); + maxChildWidth = Math.max(maxChildWidth, forwardedNameWidth); + maxChildWidth = Math.max(maxChildWidth, replyNameWidth); + maxChildWidth = Math.max(maxChildWidth, replyTextWidth); + int maxWebWidth = 0; - if (webPage.site_name != null) { - try { - int width = (int) Math.ceil(replyNamePaint.measureText(webPage.site_name)); - siteNameLayout = new StaticLayout(webPage.site_name, replyNamePaint, Math.min(width, linkPreviewMaxWidth), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - int height = siteNameLayout.getLineBottom(siteNameLayout.getLineCount() - 1); - linkPreviewHeight += height; - totalHeight += height; - additionalHeight += height; - width = siteNameLayout.getWidth(); - maxChildWidth = Math.max(maxChildWidth, width + additinalWidth); - maxWebWidth = Math.max(maxWebWidth, width + additinalWidth); - } catch (Exception e) { - FileLog.e("tmessages", e); - } + int timeMore = timeWidth + AndroidUtilities.dp(6); + if (messageObject.isOutOwner()) { + timeMore += AndroidUtilities.dp(20.5f); } - boolean titleIsRTL = false; - if (webPage.title != null) { - try { - titleX = 0; - if (linkPreviewHeight != 0) { - linkPreviewHeight += AndroidUtilities.dp(2); - totalHeight += AndroidUtilities.dp(2); + if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && messageObject.messageOwner.media.webpage instanceof TLRPC.TL_webPage) { + int linkPreviewMaxWidth; + if (AndroidUtilities.isTablet()) { + if (messageObject.isFromUser() && (currentMessageObject.messageOwner.to_id.channel_id != 0 || currentMessageObject.messageOwner.to_id.chat_id != 0) && !currentMessageObject.isOut()) { + linkPreviewMaxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(122); + } else { + linkPreviewMaxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(80); } - int restLines = 0; - if (!isSmallImage || webPage.description == null) { - titleLayout = StaticLayoutEx.createStaticLayout(webPage.title, replyNamePaint, linkPreviewMaxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, AndroidUtilities.dp(1), false, TextUtils.TruncateAt.END, linkPreviewMaxWidth, 4); + } else { + if (messageObject.isFromUser() && (currentMessageObject.messageOwner.to_id.channel_id != 0 || currentMessageObject.messageOwner.to_id.chat_id != 0) && !currentMessageObject.isOutOwner()) { + linkPreviewMaxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(122); } else { - restLines = restLinesCount; - titleLayout = generateStaticLayout(webPage.title, replyNamePaint, linkPreviewMaxWidth, linkPreviewMaxWidth - AndroidUtilities.dp(48 + 2), restLinesCount, 4); - restLinesCount -= titleLayout.getLineCount(); + linkPreviewMaxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(80); } - int height = titleLayout.getLineBottom(titleLayout.getLineCount() - 1); - linkPreviewHeight += height; - totalHeight += height; - for (int a = 0; a < titleLayout.getLineCount(); a++) { - int lineLeft = (int) titleLayout.getLineLeft(a); - if (lineLeft != 0) { - titleIsRTL = true; - if (titleX == 0) { - titleX = -lineLeft; - } else { - titleX = Math.max(titleX, -lineLeft); - } - } - int width; - if (lineLeft != 0) { - width = titleLayout.getWidth() - lineLeft; - } else { - width = (int) Math.ceil(titleLayout.getLineWidth(a)); - } - if (a < restLines || lineLeft != 0 && isSmallImage) { - width += AndroidUtilities.dp(48 + 2); - } + } + if (drawShareButton) { + linkPreviewMaxWidth -= AndroidUtilities.dp(20); + } + + TLRPC.TL_webPage webPage = (TLRPC.TL_webPage) messageObject.messageOwner.media.webpage; + + if (webPage.site_name != null && webPage.photo != null && webPage.site_name.toLowerCase().equals("instagram")) { + linkPreviewMaxWidth = Math.max(AndroidUtilities.displaySize.y / 3, currentMessageObject.textWidth); + } + + int additinalWidth = AndroidUtilities.dp(10); + int restLinesCount = 3; + int additionalHeight = 0; + linkPreviewMaxWidth -= additinalWidth; + + hasLinkPreview = true; + + if (currentMessageObject.photoThumbs == null && webPage.photo != null) { + currentMessageObject.generateThumbs(true); + } + + isSmallImage = webPage.description != null && webPage.type != null && (webPage.type.equals("app") || webPage.type.equals("profile") || webPage.type.equals("article")) && currentMessageObject.photoThumbs != null; + + if (webPage.site_name != null) { + try { + int width = (int) Math.ceil(replyNamePaint.measureText(webPage.site_name)); + sitecaptionLayout = new StaticLayout(webPage.site_name, replyNamePaint, Math.min(width, linkPreviewMaxWidth), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + int height = sitecaptionLayout.getLineBottom(sitecaptionLayout.getLineCount() - 1); + linkPreviewHeight += height; + totalHeight += height; + additionalHeight += height; + width = sitecaptionLayout.getWidth(); maxChildWidth = Math.max(maxChildWidth, width + additinalWidth); maxWebWidth = Math.max(maxWebWidth, width + additinalWidth); + } catch (Exception e) { + FileLog.e("tmessages", e); } - } catch (Exception e) { - FileLog.e("tmessages", e); } - } - boolean authorIsRTL = false; - if (webPage.author != null) { - try { - if (linkPreviewHeight != 0) { - linkPreviewHeight += AndroidUtilities.dp(2); - totalHeight += AndroidUtilities.dp(2); - } - //int width = Math.min((int) Math.ceil(replyNamePaint.measureText(webPage.author)), linkPreviewMaxWidth); - if (restLinesCount == 3 && (!isSmallImage || webPage.description == null)) { - authorLayout = new StaticLayout(webPage.author, replyNamePaint, linkPreviewMaxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - } else { - authorLayout = generateStaticLayout(webPage.author, replyNamePaint, linkPreviewMaxWidth, linkPreviewMaxWidth - AndroidUtilities.dp(48 + 2), restLinesCount, 1); - restLinesCount -= authorLayout.getLineCount(); - } - int height = authorLayout.getLineBottom(authorLayout.getLineCount() - 1); - linkPreviewHeight += height; - totalHeight += height; - int lineLeft = (int) authorLayout.getLineLeft(0); - authorX = -lineLeft; - int width; - if (lineLeft != 0) { - width = authorLayout.getWidth() - lineLeft; - authorIsRTL = true; - } else { - width = (int) Math.ceil(authorLayout.getLineWidth(0)); + boolean titleIsRTL = false; + if (webPage.title != null) { + try { + titleX = 0; + if (linkPreviewHeight != 0) { + linkPreviewHeight += AndroidUtilities.dp(2); + totalHeight += AndroidUtilities.dp(2); + } + int restLines = 0; + if (!isSmallImage || webPage.description == null) { + titleLayout = StaticLayoutEx.createStaticLayout(webPage.title, replyNamePaint, linkPreviewMaxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, AndroidUtilities.dp(1), false, TextUtils.TruncateAt.END, linkPreviewMaxWidth, 4); + } else { + restLines = restLinesCount; + titleLayout = generateStaticLayout(webPage.title, replyNamePaint, linkPreviewMaxWidth, linkPreviewMaxWidth - AndroidUtilities.dp(48 + 2), restLinesCount, 4); + restLinesCount -= titleLayout.getLineCount(); + } + int height = titleLayout.getLineBottom(titleLayout.getLineCount() - 1); + linkPreviewHeight += height; + totalHeight += height; + for (int a = 0; a < titleLayout.getLineCount(); a++) { + int lineLeft = (int) titleLayout.getLineLeft(a); + if (lineLeft != 0) { + titleIsRTL = true; + if (titleX == 0) { + titleX = -lineLeft; + } else { + titleX = Math.max(titleX, -lineLeft); + } + } + int width; + if (lineLeft != 0) { + width = titleLayout.getWidth() - lineLeft; + } else { + width = (int) Math.ceil(titleLayout.getLineWidth(a)); + } + if (a < restLines || lineLeft != 0 && isSmallImage) { + width += AndroidUtilities.dp(48 + 2); + } + maxChildWidth = Math.max(maxChildWidth, width + additinalWidth); + maxWebWidth = Math.max(maxWebWidth, width + additinalWidth); + } + } catch (Exception e) { + FileLog.e("tmessages", e); } - maxChildWidth = Math.max(maxChildWidth, width + additinalWidth); - maxWebWidth = Math.max(maxWebWidth, width + additinalWidth); - } catch (Exception e) { - FileLog.e("tmessages", e); } - } - if (webPage.description != null) { - try { - descriptionX = 0; - currentMessageObject.generateLinkDescription(); - if (linkPreviewHeight != 0) { - linkPreviewHeight += AndroidUtilities.dp(2); - totalHeight += AndroidUtilities.dp(2); - } - int restLines = 0; - if (restLinesCount == 3 && !isSmallImage) { - descriptionLayout = StaticLayoutEx.createStaticLayout(messageObject.linkDescription, replyTextPaint, linkPreviewMaxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, AndroidUtilities.dp(1), false, TextUtils.TruncateAt.END, linkPreviewMaxWidth, 6); - } else { - restLines = restLinesCount; - descriptionLayout = generateStaticLayout(messageObject.linkDescription, replyTextPaint, linkPreviewMaxWidth, linkPreviewMaxWidth - AndroidUtilities.dp(48 + 2), restLinesCount, 6); + boolean authorIsRTL = false; + if (webPage.author != null) { + try { + if (linkPreviewHeight != 0) { + linkPreviewHeight += AndroidUtilities.dp(2); + totalHeight += AndroidUtilities.dp(2); + } + //int width = Math.min((int) Math.ceil(replyNamePaint.measureText(webPage.author)), linkPreviewMaxWidth); + if (restLinesCount == 3 && (!isSmallImage || webPage.description == null)) { + authorLayout = new StaticLayout(webPage.author, replyNamePaint, linkPreviewMaxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + } else { + authorLayout = generateStaticLayout(webPage.author, replyNamePaint, linkPreviewMaxWidth, linkPreviewMaxWidth - AndroidUtilities.dp(48 + 2), restLinesCount, 1); + restLinesCount -= authorLayout.getLineCount(); + } + int height = authorLayout.getLineBottom(authorLayout.getLineCount() - 1); + linkPreviewHeight += height; + totalHeight += height; + int lineLeft = (int) authorLayout.getLineLeft(0); + authorX = -lineLeft; + int width; + if (lineLeft != 0) { + width = authorLayout.getWidth() - lineLeft; + authorIsRTL = true; + } else { + width = (int) Math.ceil(authorLayout.getLineWidth(0)); + } + maxChildWidth = Math.max(maxChildWidth, width + additinalWidth); + maxWebWidth = Math.max(maxWebWidth, width + additinalWidth); + } catch (Exception e) { + FileLog.e("tmessages", e); } - int height = descriptionLayout.getLineBottom(descriptionLayout.getLineCount() - 1); - linkPreviewHeight += height; - totalHeight += height; + } - boolean hasRTL = false; - for (int a = 0; a < descriptionLayout.getLineCount(); a++) { - int lineLeft = (int) Math.ceil(descriptionLayout.getLineLeft(a)); - if (lineLeft != 0) { - hasRTL = true; - if (descriptionX == 0) { - descriptionX = -lineLeft; + if (webPage.description != null) { + try { + descriptionX = 0; + currentMessageObject.generateLinkDescription(); + if (linkPreviewHeight != 0) { + linkPreviewHeight += AndroidUtilities.dp(2); + totalHeight += AndroidUtilities.dp(2); + } + int restLines = 0; + if (restLinesCount == 3 && !isSmallImage) { + descriptionLayout = StaticLayoutEx.createStaticLayout(messageObject.linkDescription, replyTextPaint, linkPreviewMaxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, AndroidUtilities.dp(1), false, TextUtils.TruncateAt.END, linkPreviewMaxWidth, 6); + } else { + restLines = restLinesCount; + descriptionLayout = generateStaticLayout(messageObject.linkDescription, replyTextPaint, linkPreviewMaxWidth, linkPreviewMaxWidth - AndroidUtilities.dp(48 + 2), restLinesCount, 6); + } + int height = descriptionLayout.getLineBottom(descriptionLayout.getLineCount() - 1); + linkPreviewHeight += height; + totalHeight += height; + + boolean hasRTL = false; + for (int a = 0; a < descriptionLayout.getLineCount(); a++) { + int lineLeft = (int) Math.ceil(descriptionLayout.getLineLeft(a)); + if (lineLeft != 0) { + hasRTL = true; + if (descriptionX == 0) { + descriptionX = -lineLeft; + } else { + descriptionX = Math.max(descriptionX, -lineLeft); + } + } + } + + for (int a = 0; a < descriptionLayout.getLineCount(); a++) { + int lineLeft = (int) Math.ceil(descriptionLayout.getLineLeft(a)); + if (lineLeft == 0 && descriptionX != 0) { + descriptionX = 0; + } + + int width; + if (lineLeft != 0) { + width = descriptionLayout.getWidth() - lineLeft; } else { - descriptionX = Math.max(descriptionX, -lineLeft); + width = hasRTL ? descriptionLayout.getWidth() : (int) Math.ceil(descriptionLayout.getLineWidth(a)); + } + if (a < restLines || restLines != 0 && lineLeft != 0 && isSmallImage) { + width += AndroidUtilities.dp(48 + 2); + } + if (maxWebWidth < width + additinalWidth) { + if (titleIsRTL) { + titleX += (width + additinalWidth - maxWebWidth); + } + if (authorIsRTL) { + authorX += (width + additinalWidth - maxWebWidth); + } + maxWebWidth = width + additinalWidth; + } + if (restLines == 0 || !isSmallImage) { + if (titleIsRTL) { + titleX = -AndroidUtilities.dp(4); + } + if (authorIsRTL) { + authorX = -AndroidUtilities.dp(4); + } + } + maxChildWidth = Math.max(maxChildWidth, width + additinalWidth); + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + + boolean smallImage = webPage.type != null && (webPage.type.equals("app") || webPage.type.equals("profile") || webPage.type.equals("article")); + if (smallImage && (descriptionLayout == null || descriptionLayout != null && descriptionLayout.getLineCount() == 1)) { + smallImage = false; + isSmallImage = false; + } + int maxPhotoWidth = smallImage ? AndroidUtilities.dp(48) : linkPreviewMaxWidth; + + if (webPage.document != null) { + if (MessageObject.isGifDocument(webPage.document)){ + if (!MediaController.getInstance().canAutoplayGifs()) { + messageObject.audioProgress = 1; + } + photoImage.setAllowStartAnimation(messageObject.audioProgress != 1); + currentPhotoObject = webPage.document.thumb; + if (currentPhotoObject != null && (currentPhotoObject.w == 0 || currentPhotoObject.h == 0)) { + for (int a = 0; a < webPage.document.attributes.size(); a++) { + TLRPC.DocumentAttribute attribute = webPage.document.attributes.get(a); + if (attribute instanceof TLRPC.TL_documentAttributeImageSize || attribute instanceof TLRPC.TL_documentAttributeVideo) { + currentPhotoObject.w = attribute.w; + currentPhotoObject.h = attribute.h; + break; + } + } + if (currentPhotoObject.w == 0 || currentPhotoObject.h == 0) { + currentPhotoObject.w = currentPhotoObject.h = AndroidUtilities.dp(100); } } + isDocument = 2; + } else { + TLRPC.Document document = messageObject.messageOwner.media.webpage.document; + if (!MessageObject.isStickerDocument(document) && !MessageObject.isVoiceDocument(document)) { + calcBackgroundWidth(maxWidth, timeMore, maxChildWidth); + if (backgroundWidth < maxWidth + AndroidUtilities.dp(20)) { + backgroundWidth = maxWidth + AndroidUtilities.dp(20); + } + createDocumentLayout(backgroundWidth - AndroidUtilities.dp(86 + 24 + 58), messageObject); + drawPhotoImage = true; + drawImageButton = true; + photoImage.setImageCoords(0, totalHeight + namesOffset, AndroidUtilities.dp(86), AndroidUtilities.dp(86)); + totalHeight += AndroidUtilities.dp(86); + linkPreviewHeight += AndroidUtilities.dp(86); + } } + } else if (webPage.photo != null) { + currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, drawImageButton ? AndroidUtilities.getPhotoSize() : maxPhotoWidth, !drawImageButton); + currentPhotoObjectThumb = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 80); + if (currentPhotoObjectThumb == currentPhotoObject) { + currentPhotoObjectThumb = null; + } + } + + if (isDocument != 1) { + if (currentPhotoObject != null) { + drawImageButton = webPage.type != null && (webPage.type.equals("photo") || webPage.type.equals("document") || webPage.type.equals("gif")); + if (linkPreviewHeight != 0) { + linkPreviewHeight += AndroidUtilities.dp(2); + totalHeight += AndroidUtilities.dp(2); + } - for (int a = 0; a < descriptionLayout.getLineCount(); a++) { - int lineLeft = (int) Math.ceil(descriptionLayout.getLineLeft(a)); - if (lineLeft == 0 && descriptionX != 0) { - descriptionX = 0; + maxChildWidth = Math.max(maxChildWidth, maxPhotoWidth + additinalWidth); + currentPhotoObject.size = -1; + if (currentPhotoObjectThumb != null) { + currentPhotoObjectThumb.size = -1; } int width; - if (lineLeft != 0) { - width = descriptionLayout.getWidth() - lineLeft; + int height; + if (smallImage) { + width = height = maxPhotoWidth; } else { - width = hasRTL ? descriptionLayout.getWidth() : (int) Math.ceil(descriptionLayout.getLineWidth(a)); + width = currentPhotoObject.w; + height = currentPhotoObject.h; + float scale = width / (float) maxPhotoWidth; + width /= scale; + height /= scale; + if (webPage.site_name == null || webPage.site_name != null && !webPage.site_name.toLowerCase().equals("instagram") && isDocument == 0) { + if (height > AndroidUtilities.displaySize.y / 3) { + height = AndroidUtilities.displaySize.y / 3; + } + } } - if (a < restLines || restLines != 0 && lineLeft != 0 && isSmallImage) { - width += AndroidUtilities.dp(48 + 2); + if (isSmallImage) { + if (AndroidUtilities.dp(50) + additionalHeight > linkPreviewHeight) { + totalHeight += AndroidUtilities.dp(50) + additionalHeight - linkPreviewHeight + AndroidUtilities.dp(8); + linkPreviewHeight = AndroidUtilities.dp(50) + additionalHeight; + } + linkPreviewHeight -= AndroidUtilities.dp(8); + } else { + totalHeight += height + AndroidUtilities.dp(12); + linkPreviewHeight += height; } - if (maxWebWidth < width + additinalWidth) { - if (titleIsRTL) { - titleX += (width + additinalWidth - maxWebWidth); + + photoImage.setImageCoords(0, 0, width, height); + + currentPhotoFilter = String.format(Locale.US, "%d_%d", width, height); + currentPhotoFilterThumb = String.format(Locale.US, "%d_%d_b", width, height); + + if (isDocument == 2) { + boolean photoExist = true; + File cacheFile = FileLoader.getPathToAttach(webPage.document); + if (!cacheFile.exists()) { + photoExist = false; + } + String fileName = FileLoader.getAttachFileName(webPage.document); + if (photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_GIF) || FileLoader.getInstance().isLoadingFile(fileName)) { + photoNotSet = false; + photoImage.setImage(webPage.document, null, currentPhotoObject.location, currentPhotoFilter, webPage.document.size, null, false); + } else { + photoNotSet = true; + photoImage.setImage(null, null, currentPhotoObject.location, currentPhotoFilter, 0, null, false); } - if (authorIsRTL) { - authorX += (width + additinalWidth - maxWebWidth); + } else { + boolean photoExist = true; + File cacheFile = FileLoader.getPathToAttach(currentPhotoObject, true); + if (!cacheFile.exists()) { + photoExist = false; + } + String fileName = FileLoader.getAttachFileName(currentPhotoObject); + if (photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) || FileLoader.getInstance().isLoadingFile(fileName)) { + photoNotSet = false; + photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilterThumb, 0, null, false); + } else { + photoNotSet = true; + if (currentPhotoObjectThumb != null) { + photoImage.setImage(null, null, currentPhotoObjectThumb.location, String.format(Locale.US, "%d_%d_b", width, height), 0, null, false); + } else { + photoImage.setImageBitmap((Drawable) null); + } } - maxWebWidth = width + additinalWidth; } - if (restLines == 0 || !isSmallImage) { - if (titleIsRTL) { - titleX = -AndroidUtilities.dp(4); + drawPhotoImage = true; + + if (webPage.site_name != null) { + if (webPage.site_name.toLowerCase().equals("instagram") && webPage.type != null && webPage.type.equals("video")) { + isInstagram = true; } - if (authorIsRTL) { - authorX = -AndroidUtilities.dp(4); + } + + if (webPage.type != null && webPage.type.equals("video") && webPage.duration != 0) { + if (durationPaint == null) { + durationPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + durationPaint.setTextSize(AndroidUtilities.dp(12)); + durationPaint.setColor(0xffffffff); } + int minutes = webPage.duration / 60; + int seconds = webPage.duration - minutes * 60; + String str = String.format("%d:%02d", minutes, seconds); + durationWidth = (int) Math.ceil(durationPaint.measureText(str)); + durationLayout = new StaticLayout(str, durationPaint, durationWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } - maxChildWidth = Math.max(maxChildWidth, width + additinalWidth); + } else { + photoImage.setImageBitmap((Drawable) null); + linkPreviewHeight -= AndroidUtilities.dp(6); + totalHeight += AndroidUtilities.dp(4); } - } catch (Exception e) { - FileLog.e("tmessages", e); + calcBackgroundWidth(maxWidth, timeMore, maxChildWidth); } + } else { + photoImage.setImageBitmap((Drawable) null); + calcBackgroundWidth(maxWidth, timeMore, maxChildWidth); } + } else { + drawForwardedName = messageObject.messageOwner.fwd_from != null && messageObject.type != 13; + mediaBackground = messageObject.type != 9; + drawImageButton = true; + + int photoWidth = 0; + int photoHeight = 0; + int additionHeight = 0; - boolean smallImage = webPage.type != null && (webPage.type.equals("app") || webPage.type.equals("profile") || webPage.type.equals("article")); - if (smallImage && (descriptionLayout == null || descriptionLayout != null && descriptionLayout.getLineCount() == 1)) { - smallImage = false; - isSmallImage = false; + if (messageObject.audioProgress != 2 && !MediaController.getInstance().canAutoplayGifs() && messageObject.type == 8) { + messageObject.audioProgress = 1; } - int maxPhotoWidth = smallImage ? AndroidUtilities.dp(48) : linkPreviewMaxWidth; - if (webPage.document != null && MessageObject.isGifDocument(webPage.document)) { - if (!MediaController.getInstance().canAutoplayGifs()) { - messageObject.audioProgress = 1; + photoImage.setAllowStartAnimation(messageObject.audioProgress == 0); + + photoImage.setForcePreview(messageObject.isSecretPhoto()); + if (messageObject.type == 9) { + int maxWidth; + if (AndroidUtilities.isTablet()) { + maxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(122 + 86 + 24); + } else { + maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(122 + 86 + 24); } - linkImageView.setAllowStartAnimation(messageObject.audioProgress != 1); - currentPhotoObject = webPage.document.thumb; - isGifDocument = true; - } else if (webPage.photo != null) { - currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, drawImageButton ? AndroidUtilities.getPhotoSize() : maxPhotoWidth, !drawImageButton); - currentPhotoObjectThumb = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 80); - if (currentPhotoObjectThumb == currentPhotoObject) { - currentPhotoObjectThumb = null; + if (checkNeedDrawShareButton(messageObject)) { + maxWidth -= AndroidUtilities.dp(20); } - } + int captionWidth = createDocumentLayout(maxWidth, messageObject); + photoWidth = AndroidUtilities.dp(86); + photoHeight = AndroidUtilities.dp(86); + availableTimeWidth = Math.max(captionWidth, infoWidth) + AndroidUtilities.dp(37); + backgroundWidth = photoWidth + availableTimeWidth + AndroidUtilities.dp(31); + } else if (messageObject.type == 4) { //geo + double lat = messageObject.messageOwner.media.geo.lat; + double lon = messageObject.messageOwner.media.geo._long; + + if (messageObject.messageOwner.media.title != null && messageObject.messageOwner.media.title.length() > 0) { + int maxWidth = (AndroidUtilities.isTablet() ? AndroidUtilities.getMinTabletSide() : Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y)) - AndroidUtilities.dp((isChat && !messageObject.isOutOwner() ? 102 : 40) + 86 + 24); + captionLayout = StaticLayoutEx.createStaticLayout(messageObject.messageOwner.media.title, locationTitlePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.END, maxWidth - AndroidUtilities.dp(4), 3); + int lineCount = captionLayout.getLineCount(); + if (messageObject.messageOwner.media.address != null && messageObject.messageOwner.media.address.length() > 0) { + infoLayout = StaticLayoutEx.createStaticLayout(messageObject.messageOwner.media.address, locationAddressPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.END, maxWidth - AndroidUtilities.dp(4), Math.min(3, 4 - lineCount)); + } else { + infoLayout = null; + } - if (currentPhotoObject != null) { - drawImageButton = webPage.type != null && (webPage.type.equals("photo") || webPage.type.equals("document") || webPage.type.equals("gif")); - if (linkPreviewHeight != 0) { - linkPreviewHeight += AndroidUtilities.dp(2); - totalHeight += AndroidUtilities.dp(2); + mediaBackground = false; + availableTimeWidth = maxWidth - AndroidUtilities.dp(7); + measureTime(messageObject); + photoWidth = AndroidUtilities.dp(86); + photoHeight = AndroidUtilities.dp(86); + maxWidth = timeWidth + AndroidUtilities.dp(messageObject.isOutOwner() ? 29 : 9); + for (int a = 0; a < lineCount; a++) { + maxWidth = (int) Math.max(maxWidth, captionLayout.getLineWidth(a) + AndroidUtilities.dp(16)); + } + if (infoLayout != null) { + for (int a = 0; a < infoLayout.getLineCount(); a++) { + maxWidth = (int) Math.max(maxWidth, infoLayout.getLineWidth(a) + AndroidUtilities.dp(16)); + } + } + backgroundWidth = photoWidth + AndroidUtilities.dp(21) + maxWidth; + currentUrl = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=72x72&maptype=roadmap&scale=%d&markers=color:red|size:big|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(AndroidUtilities.density)), lat, lon); + } else { + availableTimeWidth = AndroidUtilities.dp(200 - 14); + photoWidth = AndroidUtilities.dp(200); + photoHeight = AndroidUtilities.dp(100); + backgroundWidth = photoWidth + AndroidUtilities.dp(12); + currentUrl = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=200x100&maptype=roadmap&scale=%d&markers=color:red|size:big|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(AndroidUtilities.density)), lat, lon); } - maxChildWidth = Math.max(maxChildWidth, maxPhotoWidth + additinalWidth); - currentPhotoObject.size = -1; - if (currentPhotoObjectThumb != null) { - currentPhotoObjectThumb.size = -1; + photoImage.setNeedsQualityThumb(false); + photoImage.setShouldGenerateQualityThumb(false); + photoImage.setParentMessageObject(null); + photoImage.setImage(currentUrl, null, messageObject.isOutOwner() ? ResourceLoader.geoOutDrawable : ResourceLoader.geoInDrawable, null, 0); + } else if (messageObject.type == 13) { //webp + drawBackground = false; + for (int a = 0; a < messageObject.messageOwner.media.document.attributes.size(); a++) { + TLRPC.DocumentAttribute attribute = messageObject.messageOwner.media.document.attributes.get(a); + if (attribute instanceof TLRPC.TL_documentAttributeImageSize) { + photoWidth = attribute.w; + photoHeight = attribute.h; + break; + } } - - int width; - int height; - if (smallImage) { - width = height = maxPhotoWidth; + float maxHeight = AndroidUtilities.displaySize.y * 0.4f; + float maxWidth; + if (AndroidUtilities.isTablet()) { + maxWidth = AndroidUtilities.getMinTabletSide() * 0.5f; } else { - width = currentPhotoObject.w; - height = currentPhotoObject.h; - float scale = width / (float) maxPhotoWidth; - width /= scale; - height /= scale; - if (webPage.site_name == null || webPage.site_name != null && !webPage.site_name.toLowerCase().equals("instagram") && !isGifDocument) { - if (height > AndroidUtilities.displaySize.y / 3) { - height = AndroidUtilities.displaySize.y / 3; - } - } + maxWidth = AndroidUtilities.displaySize.x * 0.5f; } - if (isSmallImage) { - if (AndroidUtilities.dp(50) + additionalHeight > linkPreviewHeight) { - totalHeight += AndroidUtilities.dp(50) + additionalHeight - linkPreviewHeight + AndroidUtilities.dp(8); - linkPreviewHeight = AndroidUtilities.dp(50) + additionalHeight; + if (photoWidth == 0) { + photoHeight = (int) maxHeight; + photoWidth = photoHeight + AndroidUtilities.dp(100); + } + if (photoHeight > maxHeight) { + photoWidth *= maxHeight / photoHeight; + photoHeight = (int) maxHeight; + } + if (photoWidth > maxWidth) { + photoHeight *= maxWidth / photoWidth; + photoWidth = (int) maxWidth; + } + availableTimeWidth = photoWidth - AndroidUtilities.dp(14); + backgroundWidth = photoWidth + AndroidUtilities.dp(12); + currentPhotoObjectThumb = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 80); + photoImage.setNeedsQualityThumb(false); + photoImage.setShouldGenerateQualityThumb(false); + photoImage.setParentMessageObject(null); + if (messageObject.messageOwner.attachPath != null && messageObject.messageOwner.attachPath.length() > 0) { + File f = new File(messageObject.messageOwner.attachPath); + if (f.exists()) { + photoImage.setImage(null, messageObject.messageOwner.attachPath, + String.format(Locale.US, "%d_%d", photoWidth, photoHeight), + null, + currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, + "b1", + messageObject.messageOwner.media.document.size, "webp", true); } - linkPreviewHeight -= AndroidUtilities.dp(8); + } else if (messageObject.messageOwner.media.document.id != 0) { + photoImage.setImage(messageObject.messageOwner.media.document, null, + String.format(Locale.US, "%d_%d", photoWidth, photoHeight), + null, + currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, + "b1", + messageObject.messageOwner.media.document.size, "webp", true); + } + } else { + int maxPhotoWidth; + if (AndroidUtilities.isTablet()) { + maxPhotoWidth = photoWidth = (int) (AndroidUtilities.getMinTabletSide() * 0.7f); } else { - totalHeight += height + AndroidUtilities.dp(12); - linkPreviewHeight += height; + maxPhotoWidth = photoWidth = (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.7f); + } + photoHeight = photoWidth + AndroidUtilities.dp(100); + if (checkNeedDrawShareButton(messageObject)) { + maxPhotoWidth -= AndroidUtilities.dp(20); + photoWidth -= AndroidUtilities.dp(20); + } + + if (photoWidth > AndroidUtilities.getPhotoSize()) { + photoWidth = AndroidUtilities.getPhotoSize(); + } + if (photoHeight > AndroidUtilities.getPhotoSize()) { + photoHeight = AndroidUtilities.getPhotoSize(); + } + + if (messageObject.type == 1) { //photo + updateSecretTimeText(messageObject); + photoImage.setNeedsQualityThumb(false); + photoImage.setShouldGenerateQualityThumb(false); + photoImage.setParentMessageObject(null); + currentPhotoObjectThumb = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 80); + } else if (messageObject.type == 3) { //video + int duration = 0; + for (int a = 0; a < messageObject.messageOwner.media.document.attributes.size(); a++) { + TLRPC.DocumentAttribute attribute = messageObject.messageOwner.media.document.attributes.get(a); + if (attribute instanceof TLRPC.TL_documentAttributeVideo) { + duration = attribute.duration; + break; + } + } + int minutes = duration / 60; + int seconds = duration - minutes * 60; + String str = String.format("%d:%02d, %s", minutes, seconds, AndroidUtilities.formatFileSize(messageObject.messageOwner.media.document.size)); + infoOffset = ResourceLoader.videoIconDrawable.getIntrinsicWidth() + AndroidUtilities.dp(4); + infoWidth = (int) Math.ceil(infoPaint.measureText(str)); + infoLayout = new StaticLayout(str, infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + + photoImage.setNeedsQualityThumb(true); + photoImage.setShouldGenerateQualityThumb(true); + photoImage.setParentMessageObject(messageObject); + } else if (messageObject.type == 8) { //gif + String str = AndroidUtilities.formatFileSize(messageObject.messageOwner.media.document.size); + infoWidth = (int) Math.ceil(infoPaint.measureText(str)); + infoLayout = new StaticLayout(str, infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + + photoImage.setNeedsQualityThumb(true); + photoImage.setShouldGenerateQualityThumb(true); + photoImage.setParentMessageObject(messageObject); } - linkImageView.setImageCoords(0, 0, width, height); + if (messageObject.caption != null) { + mediaBackground = false; + } + + currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); + + int w = 0; + int h = 0; - currentPhotoFilter = String.format(Locale.US, "%d_%d", width, height); - currentPhotoFilterThumb = String.format(Locale.US, "%d_%d_b", width, height); + if (currentPhotoObject != null && currentPhotoObject == currentPhotoObjectThumb) { + currentPhotoObjectThumb = null; + } - if (isGifDocument) { - boolean photoExist = true; - File cacheFile = FileLoader.getPathToAttach(webPage.document); - if (!cacheFile.exists()) { - photoExist = false; + if (currentPhotoObject != null) { + float scale = (float) currentPhotoObject.w / (float) photoWidth; + w = (int) (currentPhotoObject.w / scale); + h = (int) (currentPhotoObject.h / scale); + if (w == 0) { + if (messageObject.type == 3) { + w = infoWidth + infoOffset + AndroidUtilities.dp(16); + } else { + w = AndroidUtilities.dp(100); + } } - String fileName = FileLoader.getAttachFileName(webPage.document); - if (photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_GIF) || FileLoader.getInstance().isLoadingFile(fileName)) { - photoNotSet = false; - linkImageView.setImage(webPage.document, null, currentPhotoObject.location, currentPhotoFilter, webPage.document.size, null, false); - } else { - photoNotSet = true; - linkImageView.setImage(null, null, currentPhotoObject.location, currentPhotoFilter, 0, null, false); + if (h == 0) { + h = AndroidUtilities.dp(100); } - } else { - boolean photoExist = true; - File cacheFile = FileLoader.getPathToAttach(currentPhotoObject, true); - if (!cacheFile.exists()) { - photoExist = false; + if (h > photoHeight) { + float scale2 = h; + h = photoHeight; + scale2 /= h; + w = (int) (w / scale2); + } else if (h < AndroidUtilities.dp(120)) { + h = AndroidUtilities.dp(120); + float hScale = (float) currentPhotoObject.h / h; + if (currentPhotoObject.w / hScale < photoWidth) { + w = (int) (currentPhotoObject.w / hScale); + } } - String fileName = FileLoader.getAttachFileName(currentPhotoObject); - if (photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) || FileLoader.getInstance().isLoadingFile(fileName)) { - photoNotSet = false; - linkImageView.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilterThumb, 0, null, false); - } else { - photoNotSet = true; - if (currentPhotoObjectThumb != null) { - linkImageView.setImage(null, null, currentPhotoObjectThumb.location, String.format(Locale.US, "%d_%d_b", width, height), 0, null, false); - } else { - linkImageView.setImageBitmap((Drawable) null); + } + + if ((w == 0 || h == 0) && messageObject.type == 8) { + for (int a = 0; a < messageObject.messageOwner.media.document.attributes.size(); a++) { + TLRPC.DocumentAttribute attribute = messageObject.messageOwner.media.document.attributes.get(a); + if (attribute instanceof TLRPC.TL_documentAttributeImageSize || attribute instanceof TLRPC.TL_documentAttributeVideo) { + float scale = (float) attribute.w / (float) photoWidth; + w = (int) (attribute.w / scale); + h = (int) (attribute.h / scale); + if (h > photoHeight) { + float scale2 = h; + h = photoHeight; + scale2 /= h; + w = (int) (w / scale2); + } else if (h < AndroidUtilities.dp(120)) { + h = AndroidUtilities.dp(120); + float hScale = (float) attribute.h / h; + if (attribute.w / hScale < photoWidth) { + w = (int) (attribute.w / hScale); + } + } + break; } } } - drawLinkImageView = true; - if (webPage.site_name != null) { - if (webPage.site_name.toLowerCase().equals("instagram") && webPage.type != null && webPage.type.equals("video")) { - isInstagram = true; - if (igvideoDrawable == null) { - igvideoDrawable = getResources().getDrawable(R.drawable.igvideo); + + if (w == 0 || h == 0) { + w = h = AndroidUtilities.dp(100); + } + + availableTimeWidth = maxPhotoWidth - AndroidUtilities.dp(14); + measureTime(messageObject); + int timeWidthTotal = timeWidth + AndroidUtilities.dp(14 + (messageObject.isOutOwner() ? 20 : 0)); + if (w < timeWidthTotal) { + w = timeWidthTotal; + } + + if (messageObject.isSecretPhoto()) { + if (AndroidUtilities.isTablet()) { + w = h = (int) (AndroidUtilities.getMinTabletSide() * 0.5f); + } else { + w = h = (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.5f); + } + } + + photoWidth = w; + photoHeight = h; + backgroundWidth = w + AndroidUtilities.dp(12); + if (!mediaBackground) { + backgroundWidth += AndroidUtilities.dp(9); + } + if (messageObject.caption != null) { + try { + captionLayout = new StaticLayout(messageObject.caption, MessageObject.getTextPaint(), photoWidth - AndroidUtilities.dp(10), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (captionLayout != null && captionLayout.getLineCount() > 0) { + captionHeight = captionLayout.getHeight(); + additionHeight += captionHeight + AndroidUtilities.dp(9); + float lastLineWidth = captionLayout.getLineWidth(captionLayout.getLineCount() - 1) + captionLayout.getLineLeft(captionLayout.getLineCount() - 1); + if (photoWidth - AndroidUtilities.dp(8) - lastLineWidth < timeWidthTotal) { + additionHeight += AndroidUtilities.dp(14); + } } + } catch (Exception e) { + FileLog.e("tmessages", e); } } - if (webPage.type != null && webPage.type.equals("video") && webPage.duration != 0) { - if (durationPaint == null) { - durationPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - durationPaint.setTextSize(AndroidUtilities.dp(12)); - durationPaint.setColor(0xffffffff); + currentPhotoFilter = String.format(Locale.US, "%d_%d", (int) (w / AndroidUtilities.density), (int) (h / AndroidUtilities.density)); + if (messageObject.photoThumbs != null && messageObject.photoThumbs.size() > 1 || messageObject.type == 3 || messageObject.type == 8) { + if (messageObject.isSecretPhoto()) { + currentPhotoFilter += "_b2"; + } else { + currentPhotoFilter += "_b"; } - int minutes = webPage.duration / 60; - int seconds = webPage.duration - minutes * 60; - String str = String.format("%d:%02d", minutes, seconds); - durationWidth = (int) Math.ceil(durationPaint.measureText(str)); - durationLayout = new StaticLayout(str, durationPaint, durationWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } - } else { - linkImageView.setImageBitmap((Drawable) null); - linkPreviewHeight -= AndroidUtilities.dp(6); - totalHeight += AndroidUtilities.dp(4); + + boolean noSize = false; + if (messageObject.type == 3 || messageObject.type == 8) { + noSize = true; + } + if (currentPhotoObject != null && !noSize && currentPhotoObject.size == 0) { + currentPhotoObject.size = -1; + } + + if (messageObject.type == 1) { + if (currentPhotoObject != null) { + String fileName = FileLoader.getAttachFileName(currentPhotoObject); + boolean photoExist = true; + File cacheFile = FileLoader.getPathToMessage(messageObject.messageOwner); + if (!cacheFile.exists()) { + photoExist = false; + } else { + MediaController.getInstance().removeLoadingFileObserver(this); + } + + if (photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) || FileLoader.getInstance().isLoadingFile(fileName)) { + if (allowedToSetPhoto || ImageLoader.getInstance().getImageFromMemory(currentPhotoObject.location, null, currentPhotoFilter) != null) { + allowedToSetPhoto = true; + photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilter, noSize ? 0 : currentPhotoObject.size, null, false); + } else if (currentPhotoObjectThumb != null) { + photoImage.setImage(null, null, currentPhotoObjectThumb.location, currentPhotoFilter, 0, null, false); + } else { + photoImage.setImageBitmap((Drawable) null); + } + } else { + photoNotSet = true; + if (currentPhotoObjectThumb != null) { + photoImage.setImage(null, null, currentPhotoObjectThumb.location, currentPhotoFilter, 0, null, false); + } else { + photoImage.setImageBitmap((Drawable) null); + } + } + } else { + photoImage.setImageBitmap((BitmapDrawable) null); + } + } else if (messageObject.type == 8) { + String fileName = FileLoader.getAttachFileName(messageObject.messageOwner.media.document); + File cacheFile = null; + boolean localFile = false; + if (messageObject.messageOwner.attachPath != null && messageObject.messageOwner.attachPath.length() != 0) { + cacheFile = new File(messageObject.messageOwner.attachPath); + if (!cacheFile.exists()) { + cacheFile = null; + } else { + MediaController.getInstance().removeLoadingFileObserver(this); + localFile = true; + } + } + if (cacheFile == null) { + cacheFile = FileLoader.getPathToMessage(messageObject.messageOwner); + if (!cacheFile.exists()) { + cacheFile = null; + } + } + if (!messageObject.isSending() && (cacheFile != null || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_GIF) && MessageObject.isNewGifDocument(messageObject.messageOwner.media.document) || FileLoader.getInstance().isLoadingFile(fileName))) { + if (localFile) { + photoImage.setImage(null, messageObject.isSendError() ? null : cacheFile.getAbsolutePath(), null, null, currentPhotoObject != null ? currentPhotoObject.location : null, currentPhotoFilter, 0, null, false); + } else { + photoImage.setImage(messageObject.messageOwner.media.document, null, currentPhotoObject != null ? currentPhotoObject.location : null, currentPhotoFilter, messageObject.messageOwner.media.document.size, null, false); + } + } else { + photoNotSet = true; + photoImage.setImage(null, null, currentPhotoObject != null ? currentPhotoObject.location : null, currentPhotoFilter, 0, null, false); + } + } else { + photoImage.setImage(null, null, currentPhotoObject != null ? currentPhotoObject.location : null, currentPhotoFilter, 0, null, false); + } } - } else { - linkImageView.setImageBitmap((Drawable) null); - } + super.setMessageObject(messageObject); - if (hasLinkPreview || maxWidth - messageObject.lastLineWidth < timeMore) { - totalHeight += AndroidUtilities.dp(14); - backgroundWidth = Math.max(maxChildWidth, messageObject.lastLineWidth) + AndroidUtilities.dp(29); - backgroundWidth = Math.max(backgroundWidth, timeWidth + AndroidUtilities.dp(29)); - } else { - int diff = maxChildWidth - messageObject.lastLineWidth; - if (diff >= 0 && diff <= timeMore) { - backgroundWidth = maxChildWidth + timeMore - diff + AndroidUtilities.dp(29); - } else { - backgroundWidth = Math.max(maxChildWidth, messageObject.lastLineWidth + timeMore) + AndroidUtilities.dp(29); + if (drawForwardedName) { + namesOffset += AndroidUtilities.dp(5); + } else if (drawName && messageObject.messageOwner.reply_to_msg_id == 0) { + namesOffset += AndroidUtilities.dp(7); } + + invalidate(); + + drawPhotoImage = true; + photoImage.setImageCoords(0, AndroidUtilities.dp(7) + namesOffset, photoWidth, photoHeight); + totalHeight = photoHeight + AndroidUtilities.dp(14) + namesOffset + additionHeight; } } updateButtonState(dataChanged); @@ -819,217 +1620,542 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - if (currentMessageObject.isOutOwner()) { - textX = layoutWidth - backgroundWidth + AndroidUtilities.dp(10); - textY = AndroidUtilities.dp(10) + namesOffset; + if (currentMessageObject.type == 0) { + if (currentMessageObject.isOutOwner()) { + textX = layoutWidth - backgroundWidth + AndroidUtilities.dp(10); + textY = AndroidUtilities.dp(10) + namesOffset; + } else { + textX = AndroidUtilities.dp(19) + (isChat && currentMessageObject.isFromUser() ? AndroidUtilities.dp(52) : 0); + textY = AndroidUtilities.dp(10) + namesOffset; + } } else { - textX = AndroidUtilities.dp(19) + (isChat && currentMessageObject.isFromUser() ? AndroidUtilities.dp(52) : 0); - textY = AndroidUtilities.dp(10) + namesOffset; + int x; + if (currentMessageObject.isOutOwner()) { + if (mediaBackground) { + x = layoutWidth - backgroundWidth - AndroidUtilities.dp(3); + } else { + x = layoutWidth - backgroundWidth + AndroidUtilities.dp(6); + } + } else { + if (isChat && currentMessageObject.isFromUser()) { + x = AndroidUtilities.dp(67); + } else { + x = AndroidUtilities.dp(15); + } + } + photoImage.setImageCoords(x, photoImage.getImageY(), photoImage.getImageWidth(), photoImage.getImageHeight()); + buttonX = (int) (x + (photoImage.getImageWidth() - AndroidUtilities.dp(48)) / 2.0f); + buttonY = (int) (AndroidUtilities.dp(7) + (photoImage.getImageHeight() - AndroidUtilities.dp(48)) / 2.0f) + namesOffset; + radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(48), buttonY + AndroidUtilities.dp(48)); + deleteProgressRect.set(buttonX + AndroidUtilities.dp(3), buttonY + AndroidUtilities.dp(3), buttonX + AndroidUtilities.dp(45), buttonY + AndroidUtilities.dp(45)); } } @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - if (currentMessageObject == null || currentMessageObject.textLayoutBlocks == null || currentMessageObject.textLayoutBlocks.isEmpty()) { - return; - } + protected void onAfterBackgroundDraw(Canvas canvas) { + + photoImage.setPressed(isDrawSelectedBackground()); + photoImage.setVisible(!PhotoViewer.getInstance().isShowingImage(currentMessageObject), false); + radialProgress.setHideCurrentDrawable(false); + + boolean imageDrawn = false; + if (currentMessageObject.type == 0 && currentMessageObject.textLayoutBlocks != null && !currentMessageObject.textLayoutBlocks.isEmpty()) { + if (currentMessageObject.isOutOwner()) { + textX = layoutWidth - backgroundWidth + AndroidUtilities.dp(10); + textY = AndroidUtilities.dp(10) + namesOffset; + } else { + textX = AndroidUtilities.dp(19) + (isChat && currentMessageObject.isFromUser() ? AndroidUtilities.dp(52) : 0); + textY = AndroidUtilities.dp(10) + namesOffset; + } + + if (firstVisibleBlockNum >= 0) { + for (int a = firstVisibleBlockNum; a <= lastVisibleBlockNum; a++) { + if (a >= currentMessageObject.textLayoutBlocks.size()) { + break; + } + MessageObject.TextLayoutBlock block = currentMessageObject.textLayoutBlocks.get(a); + canvas.save(); + canvas.translate(textX - (int) Math.ceil(block.textXOffset), textY + block.textYOffset); + if (pressedLink != null && a == linkBlockNum) { + canvas.drawPath(urlPath, urlPaint); + } + try { + block.textLayout.draw(canvas); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + canvas.restore(); + } + } + + if (hasLinkPreview) { + int startY = textY + currentMessageObject.textHeight + AndroidUtilities.dp(8); + int linkPreviewY = startY; + int smallImageStartY = 0; + replyLinePaint.setColor(currentMessageObject.isOutOwner() ? 0xff8dc97a : 0xff6c9fd2); + + canvas.drawRect(textX, linkPreviewY - AndroidUtilities.dp(3), textX + AndroidUtilities.dp(2), linkPreviewY + linkPreviewHeight + AndroidUtilities.dp(3), replyLinePaint); + + if (sitecaptionLayout != null) { + replyNamePaint.setColor(currentMessageObject.isOutOwner() ? 0xff70b15c : 0xff4b91cf); + canvas.save(); + canvas.translate(textX + AndroidUtilities.dp(10), linkPreviewY - AndroidUtilities.dp(3)); + sitecaptionLayout.draw(canvas); + canvas.restore(); + linkPreviewY += sitecaptionLayout.getLineBottom(sitecaptionLayout.getLineCount() - 1); + } + + if (titleLayout != null) { + if (linkPreviewY != startY) { + linkPreviewY += AndroidUtilities.dp(2); + } + replyNamePaint.setColor(0xff000000); + smallImageStartY = linkPreviewY - AndroidUtilities.dp(1); + canvas.save(); + canvas.translate(textX + AndroidUtilities.dp(10) + titleX, linkPreviewY - AndroidUtilities.dp(3)); + titleLayout.draw(canvas); + canvas.restore(); + linkPreviewY += titleLayout.getLineBottom(titleLayout.getLineCount() - 1); + } + + if (authorLayout != null) { + if (linkPreviewY != startY) { + linkPreviewY += AndroidUtilities.dp(2); + } + if (smallImageStartY == 0) { + smallImageStartY = linkPreviewY - AndroidUtilities.dp(1); + } + replyNamePaint.setColor(0xff000000); + canvas.save(); + canvas.translate(textX + AndroidUtilities.dp(10) + authorX, linkPreviewY - AndroidUtilities.dp(3)); + authorLayout.draw(canvas); + canvas.restore(); + linkPreviewY += authorLayout.getLineBottom(authorLayout.getLineCount() - 1); + } + + if (descriptionLayout != null) { + if (linkPreviewY != startY) { + linkPreviewY += AndroidUtilities.dp(2); + } + if (smallImageStartY == 0) { + smallImageStartY = linkPreviewY - AndroidUtilities.dp(1); + } + replyTextPaint.setColor(0xff000000); + descriptionY = linkPreviewY - AndroidUtilities.dp(3); + canvas.save(); + canvas.translate(textX + AndroidUtilities.dp(10) + descriptionX, descriptionY); + if (pressedLink != null && linkBlockNum == -10) { + canvas.drawPath(urlPath, urlPaint); + } + descriptionLayout.draw(canvas); + canvas.restore(); + linkPreviewY += descriptionLayout.getLineBottom(descriptionLayout.getLineCount() - 1); + } + + if (drawPhotoImage) { + if (linkPreviewY != startY) { + linkPreviewY += AndroidUtilities.dp(2); + } + + if (isSmallImage) { + photoImage.setImageCoords(textX + backgroundWidth - AndroidUtilities.dp(77), smallImageStartY, photoImage.getImageWidth(), photoImage.getImageHeight()); + } else { + photoImage.setImageCoords(textX + AndroidUtilities.dp(10), linkPreviewY, photoImage.getImageWidth(), photoImage.getImageHeight()); + if (drawImageButton) { + int size = AndroidUtilities.dp(48); + buttonX = (int) (photoImage.getImageX() + (photoImage.getImageWidth() - size) / 2.0f); + buttonY = (int) (photoImage.getImageY() + (photoImage.getImageHeight() - size) / 2.0f); + radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(48), buttonY + AndroidUtilities.dp(48)); + } + } + imageDrawn = photoImage.draw(canvas); + + if (isInstagram && igvideoDrawable != null) { + int x = photoImage.getImageX() + photoImage.getImageWidth() - igvideoDrawable.getIntrinsicWidth() - AndroidUtilities.dp(4); + int y = photoImage.getImageY() + AndroidUtilities.dp(4); + igvideoDrawable.setBounds(x, y, x + igvideoDrawable.getIntrinsicWidth(), y + igvideoDrawable.getIntrinsicHeight()); + igvideoDrawable.draw(canvas); + } - if (currentMessageObject.isOutOwner()) { - textX = layoutWidth - backgroundWidth + AndroidUtilities.dp(10); - textY = AndroidUtilities.dp(10) + namesOffset; + if (durationLayout != null) { + int x = photoImage.getImageX() + photoImage.getImageWidth() - AndroidUtilities.dp(8) - durationWidth; + int y = photoImage.getImageY() + photoImage.getImageHeight() - AndroidUtilities.dp(19); + ResourceLoader.mediaBackgroundDrawable.setBounds(x - AndroidUtilities.dp(4), y - AndroidUtilities.dp(1.5f), x + durationWidth + AndroidUtilities.dp(4), y + AndroidUtilities.dp(14.5f)); + ResourceLoader.mediaBackgroundDrawable.draw(canvas); + + canvas.save(); + canvas.translate(x, y); + durationLayout.draw(canvas); + canvas.restore(); + } + } + } + drawTime = true; } else { - textX = AndroidUtilities.dp(19) + (isChat && currentMessageObject.isFromUser() ? AndroidUtilities.dp(52) : 0); - textY = AndroidUtilities.dp(10) + namesOffset; + imageDrawn = photoImage.draw(canvas); + drawTime = photoImage.getVisible(); + radialProgress.setProgressColor(0xffffffff); } - if (firstVisibleBlockNum >= 0) { - for (int a = firstVisibleBlockNum; a <= lastVisibleBlockNum; a++) { - if (a >= currentMessageObject.textLayoutBlocks.size()) { - break; + if (buttonState == -1 && currentMessageObject.isSecretPhoto()) { + int drawable = 5; + if (currentMessageObject.messageOwner.destroyTime != 0) { + if (currentMessageObject.isOutOwner()) { + drawable = 7; + } else { + drawable = 6; + } + } + setDrawableBounds(ResourceLoader.buttonStatesDrawables[drawable], buttonX, buttonY); + ResourceLoader.buttonStatesDrawables[drawable].setAlpha((int) (255 * (1.0f - radialProgress.getAlpha()))); + ResourceLoader.buttonStatesDrawables[drawable].draw(canvas); + if (!currentMessageObject.isOutOwner() && currentMessageObject.messageOwner.destroyTime != 0) { + long msTime = System.currentTimeMillis() + ConnectionsManager.getInstance().getTimeDifference() * 1000; + float progress = Math.max(0, (long) currentMessageObject.messageOwner.destroyTime * 1000 - msTime) / (currentMessageObject.messageOwner.ttl * 1000.0f); + canvas.drawArc(deleteProgressRect, -90, -360 * progress, true, deleteProgressPaint); + if (progress != 0) { + int offset = AndroidUtilities.dp(2); + invalidate((int) deleteProgressRect.left - offset, (int) deleteProgressRect.top - offset, (int) deleteProgressRect.right + offset * 2, (int) deleteProgressRect.bottom + offset * 2); } - MessageObject.TextLayoutBlock block = currentMessageObject.textLayoutBlocks.get(a); + updateSecretTimeText(currentMessageObject); + } + } + + if (currentMessageObject.type == 1 || currentMessageObject.type == 3) { + if (captionLayout != null) { canvas.save(); - canvas.translate(textX - (int) Math.ceil(block.textXOffset), textY + block.textYOffset); - if (pressedLink != null && a == linkBlockNum) { + canvas.translate(captionX = photoImage.getImageX() + AndroidUtilities.dp(5), captionY = photoImage.getImageY() + photoImage.getImageHeight() + AndroidUtilities.dp(6)); + if (pressedLink != null) { canvas.drawPath(urlPath, urlPaint); } try { - block.textLayout.draw(canvas); + captionLayout.draw(canvas); } catch (Exception e) { FileLog.e("tmessages", e); } canvas.restore(); } - } - - if (hasLinkPreview) { - int startY = textY + currentMessageObject.textHeight + AndroidUtilities.dp(8); - int linkPreviewY = startY; - int smallImageStartY = 0; - replyLinePaint.setColor(currentMessageObject.isOutOwner() ? 0xff8dc97a : 0xff6c9fd2); - - canvas.drawRect(textX, linkPreviewY - AndroidUtilities.dp(3), textX + AndroidUtilities.dp(2), linkPreviewY + linkPreviewHeight + AndroidUtilities.dp(3), replyLinePaint); + if (infoLayout != null && (buttonState == 1 || buttonState == 0 || buttonState == 3 || currentMessageObject.isSecretPhoto())) { + infoPaint.setColor(0xffffffff); + setDrawableBounds(ResourceLoader.mediaBackgroundDrawable, photoImage.getImageX() + AndroidUtilities.dp(4), photoImage.getImageY() + AndroidUtilities.dp(4), infoWidth + AndroidUtilities.dp(8) + infoOffset, AndroidUtilities.dp(16.5f)); + ResourceLoader.mediaBackgroundDrawable.draw(canvas); + + if (currentMessageObject.type == 3) { + setDrawableBounds(ResourceLoader.videoIconDrawable, photoImage.getImageX() + AndroidUtilities.dp(8), photoImage.getImageY() + AndroidUtilities.dp(7.5f)); + ResourceLoader.videoIconDrawable.draw(canvas); + } - if (siteNameLayout != null) { - replyNamePaint.setColor(currentMessageObject.isOutOwner() ? 0xff70b15c : 0xff4b91cf); canvas.save(); - canvas.translate(textX + AndroidUtilities.dp(10), linkPreviewY - AndroidUtilities.dp(3)); - siteNameLayout.draw(canvas); + canvas.translate(photoImage.getImageX() + AndroidUtilities.dp(8) + infoOffset, photoImage.getImageY() + AndroidUtilities.dp(5.5f)); + infoLayout.draw(canvas); canvas.restore(); - linkPreviewY += siteNameLayout.getLineBottom(siteNameLayout.getLineCount() - 1); } + } else if (currentMessageObject.type == 4) { + if (captionLayout != null) { + locationAddressPaint.setColor(currentMessageObject.isOutOwner() ? 0xff70b15c : (isDrawSelectedBackground() ? 0xff89b4c1 : 0xff999999)); - if (titleLayout != null) { - if (linkPreviewY != startY) { - linkPreviewY += AndroidUtilities.dp(2); - } - replyNamePaint.setColor(0xff000000); - smallImageStartY = linkPreviewY - AndroidUtilities.dp(1); canvas.save(); - canvas.translate(textX + AndroidUtilities.dp(10) + titleX, linkPreviewY - AndroidUtilities.dp(3)); - titleLayout.draw(canvas); + canvas.translate(nameOffsetX + photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + AndroidUtilities.dp(3)); + captionLayout.draw(canvas); canvas.restore(); - linkPreviewY += titleLayout.getLineBottom(titleLayout.getLineCount() - 1); - } - if (authorLayout != null) { - if (linkPreviewY != startY) { - linkPreviewY += AndroidUtilities.dp(2); - } - if (smallImageStartY == 0) { - smallImageStartY = linkPreviewY - AndroidUtilities.dp(1); + if (infoLayout != null) { + canvas.save(); + canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + AndroidUtilities.dp(captionLayout.getLineCount() * 16 + 5)); + infoLayout.draw(canvas); + canvas.restore(); } - replyNamePaint.setColor(0xff000000); - canvas.save(); - canvas.translate(textX + AndroidUtilities.dp(10) + authorX, linkPreviewY - AndroidUtilities.dp(3)); - authorLayout.draw(canvas); - canvas.restore(); - linkPreviewY += authorLayout.getLineBottom(authorLayout.getLineCount() - 1); } - - if (descriptionLayout != null) { - if (linkPreviewY != startY) { - linkPreviewY += AndroidUtilities.dp(2); - } - if (smallImageStartY == 0) { - smallImageStartY = linkPreviewY - AndroidUtilities.dp(1); - } - replyTextPaint.setColor(0xff000000); - descriptionY = linkPreviewY - AndroidUtilities.dp(3); + } else if (currentMessageObject.type == 8) { + if (captionLayout != null) { canvas.save(); - canvas.translate(textX + AndroidUtilities.dp(10) + descriptionX, descriptionY); - if (pressedLink != null && linkBlockNum == -10) { + canvas.translate(captionX = photoImage.getImageX() + AndroidUtilities.dp(5), captionY = photoImage.getImageY() + photoImage.getImageHeight() + AndroidUtilities.dp(6)); + if (pressedLink != null) { canvas.drawPath(urlPath, urlPaint); } - descriptionLayout.draw(canvas); + try { + captionLayout.draw(canvas); + } catch (Exception e) { + FileLog.e("tmessages", e); + } canvas.restore(); - linkPreviewY += descriptionLayout.getLineBottom(descriptionLayout.getLineCount() - 1); } - - if (drawLinkImageView) { - if (linkPreviewY != startY) { - linkPreviewY += AndroidUtilities.dp(2); + } else if (captionLayout != null) { + canvas.save(); + canvas.translate(nameOffsetX + photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + AndroidUtilities.dp(8)); + captionLayout.draw(canvas); + canvas.restore(); + + try { + if (infoLayout != null) { + canvas.save(); + canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + captionLayout.getLineBottom(captionLayout.getLineCount() - 1) + AndroidUtilities.dp(10)); + infoLayout.draw(canvas); + canvas.restore(); } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + if (isDocument == 1) { + Drawable menuDrawable; + if (currentMessageObject.isOutOwner()) { + infoPaint.setColor(0xff70b15c); + docBackPaint.setColor(isDrawSelectedBackground() ? 0xffc5eca7 : 0xffdaf5c3); + menuDrawable = ResourceLoader.docMenuDrawable[1]; + } else { + infoPaint.setColor(isDrawSelectedBackground() ? 0xff89b4c1 : 0xffa1aab3); + docBackPaint.setColor(isDrawSelectedBackground() ? 0xffcbeaf6 : 0xffebf0f5); + menuDrawable = ResourceLoader.docMenuDrawable[isDrawSelectedBackground() ? 2 : 0]; + } + + if (currentMessageObject.type == 0) { + setDrawableBounds(menuDrawable, otherX = photoImage.getImageX() + backgroundWidth - AndroidUtilities.dp(58), photoImage.getImageY() + AndroidUtilities.dp(4)); + } else { + setDrawableBounds(menuDrawable, otherX = photoImage.getImageX() + backgroundWidth - AndroidUtilities.dp(44), photoImage.getImageY() + AndroidUtilities.dp(4)); + } + menuDrawable.draw(canvas); - if (isSmallImage) { - linkImageView.setImageCoords(textX + backgroundWidth - AndroidUtilities.dp(77), smallImageStartY, linkImageView.getImageWidth(), linkImageView.getImageHeight()); + if (buttonState >= 0 && buttonState < 4) { + if (!imageDrawn) { + if (buttonState == 1 && !currentMessageObject.isSending()) { + radialProgress.swapBackground(ResourceLoader.buttonStatesDrawablesDoc[2][currentMessageObject.isOutOwner() ? 1 : (isDrawSelectedBackground() ? 2 : 0)]); + } else { + radialProgress.swapBackground(ResourceLoader.buttonStatesDrawablesDoc[buttonState][currentMessageObject.isOutOwner() ? 1 : (isDrawSelectedBackground() ? 2 : 0)]); + } } else { - linkImageView.setImageCoords(textX + AndroidUtilities.dp(10), linkPreviewY, linkImageView.getImageWidth(), linkImageView.getImageHeight()); - if (drawImageButton) { - int size = AndroidUtilities.dp(48); - buttonX = (int) (linkImageView.getImageX() + (linkImageView.getImageWidth() - size) / 2.0f); - buttonY = (int) (linkImageView.getImageY() + (linkImageView.getImageHeight() - size) / 2.0f); - radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(48), buttonY + AndroidUtilities.dp(48)); + if (buttonState == 1 && !currentMessageObject.isSending()) { + radialProgress.swapBackground(ResourceLoader.buttonStatesDrawables[4]); + } else { + radialProgress.swapBackground(ResourceLoader.buttonStatesDrawables[buttonState]); } } - linkImageView.draw(canvas); - if (drawImageButton) { - radialProgress.draw(canvas); - } + } - if (isInstagram && igvideoDrawable != null) { - int x = linkImageView.getImageX() + linkImageView.getImageWidth() - igvideoDrawable.getIntrinsicWidth() - AndroidUtilities.dp(4); - int y = linkImageView.getImageY() + AndroidUtilities.dp(4); - igvideoDrawable.setBounds(x, y, x + igvideoDrawable.getIntrinsicWidth(), y + igvideoDrawable.getIntrinsicHeight()); - igvideoDrawable.draw(canvas); + if (!imageDrawn) { + canvas.drawRect(photoImage.getImageX(), photoImage.getImageY(), photoImage.getImageX() + photoImage.getImageWidth(), photoImage.getImageY() + photoImage.getImageHeight(), docBackPaint); + if (currentMessageObject.isOutOwner()) { + radialProgress.setProgressColor(0xff81bd72); + } else { + radialProgress.setProgressColor(isDrawSelectedBackground() ? 0xff83b2c2 : 0xffadbdcc); + } + } else { + if (buttonState == -1) { + radialProgress.setHideCurrentDrawable(true); } + radialProgress.setProgressColor(0xffffffff); + } - if (durationLayout != null) { - int x = linkImageView.getImageX() + linkImageView.getImageWidth() - AndroidUtilities.dp(8) - durationWidth; - int y = linkImageView.getImageY() + linkImageView.getImageHeight() - AndroidUtilities.dp(19); - ResourceLoader.mediaBackgroundDrawable.setBounds(x - AndroidUtilities.dp(4), y - AndroidUtilities.dp(1.5f), x + durationWidth + AndroidUtilities.dp(4), y + AndroidUtilities.dp(14.5f)); - ResourceLoader.mediaBackgroundDrawable.draw(canvas); + try { + if (captionLayout != null) { + canvas.save(); + canvas.translate(nameOffsetX + photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + AndroidUtilities.dp(8)); + captionLayout.draw(canvas); + canvas.restore(); + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + try { + if (infoLayout != null) { canvas.save(); - canvas.translate(x, y); - durationLayout.draw(canvas); + canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + captionLayout.getLineBottom(captionLayout.getLineCount() - 1) + AndroidUtilities.dp(10)); + infoLayout.draw(canvas); canvas.restore(); } + } catch (Exception e) { + FileLog.e("tmessages", e); } } + if (drawImageButton) { + radialProgress.draw(canvas); + } } private Drawable getDrawableForCurrentState() { if (buttonState >= 0 && buttonState < 4) { - if (buttonState == 1) { - return ResourceLoader.buttonStatesDrawables[4]; + if (isDocument == 1) { + if (buttonState == 1 && !currentMessageObject.isSending()) { + return ResourceLoader.buttonStatesDrawablesDoc[2][currentMessageObject.isOutOwner() ? 1 : (isDrawSelectedBackground() ? 2 : 0)]; + } else { + return ResourceLoader.buttonStatesDrawablesDoc[buttonState][currentMessageObject.isOutOwner() ? 1 : (isDrawSelectedBackground() ? 2 : 0)]; + } } else { - return ResourceLoader.buttonStatesDrawables[buttonState]; + if (buttonState == 1 && (currentMessageObject.type == 0 || !currentMessageObject.isSending())) { + return ResourceLoader.buttonStatesDrawables[4]; + } else { + return ResourceLoader.buttonStatesDrawables[buttonState]; + } } + } else if (buttonState == -1 && isDocument == 1) { + return ResourceLoader.placeholderDocDrawable[currentMessageObject.isOutOwner() ? 1 : (isDrawSelectedBackground() ? 2 : 0)]; } return null; } - public void updateButtonState(boolean animated) { - if (currentPhotoObject == null || !drawImageButton) { - return; + @Override + protected int getMaxNameWidth() { + if (currentMessageObject.type == 0) { + return super.getMaxNameWidth(); } - String fileName; - File cacheFile; + return backgroundWidth - AndroidUtilities.dp(mediaBackground ? 14 : 26); + } - if (isGifDocument) { + public void updateButtonState(boolean animated) { + String fileName = null; + File cacheFile = null; + if (currentMessageObject.type == 1) { + if (currentPhotoObject == null) { + return; + } + fileName = FileLoader.getAttachFileName(currentPhotoObject); + cacheFile = FileLoader.getPathToMessage(currentMessageObject.messageOwner); + } else if (currentMessageObject.type == 8 || currentMessageObject.type == 3 || currentMessageObject.type == 9) { + if (currentMessageObject.messageOwner.attachPath != null && currentMessageObject.messageOwner.attachPath.length() != 0) { + File f = new File(currentMessageObject.messageOwner.attachPath); + if (f.exists()) { + fileName = currentMessageObject.messageOwner.attachPath; + cacheFile = f; + } + } + if (fileName == null) { + if (!currentMessageObject.isSendError()) { + fileName = currentMessageObject.getFileName(); + cacheFile = FileLoader.getPathToMessage(currentMessageObject.messageOwner); + } + } + } else if (isDocument != 0) { fileName = FileLoader.getAttachFileName(currentMessageObject.messageOwner.media.webpage.document); cacheFile = FileLoader.getPathToAttach(currentMessageObject.messageOwner.media.webpage.document); } else { fileName = FileLoader.getAttachFileName(currentPhotoObject); cacheFile = FileLoader.getPathToAttach(currentPhotoObject, true); } - if (fileName == null) { + if (fileName == null || fileName.length() == 0) { radialProgress.setBackground(null, false, false); return; } - if (!cacheFile.exists()) { - MediaController.getInstance().addLoadingFileObserver(fileName, this); - float setProgress = 0; - boolean progressVisible = false; - if (!FileLoader.getInstance().isLoadingFile(fileName)) { - if (!cancelLoading && - (!isGifDocument && MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) || - isGifDocument && MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_GIF)) ) { + + if (currentMessageObject.type == 0 && isDocument != 1) { + if (currentPhotoObject == null || !drawImageButton) { + return; + } + if (!cacheFile.exists()) { + MediaController.getInstance().addLoadingFileObserver(fileName, this); + float setProgress = 0; + boolean progressVisible = false; + if (!FileLoader.getInstance().isLoadingFile(fileName)) { + if (!cancelLoading && + (isDocument == 0 && MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) || + isDocument == 2 && MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_GIF))) { + progressVisible = true; + buttonState = 1; + } else { + buttonState = 0; + } + } else { progressVisible = true; buttonState = 1; - } else { - buttonState = 0; + Float progress = ImageLoader.getInstance().getFileProgress(fileName); + setProgress = progress != null ? progress : 0; } + radialProgress.setProgress(setProgress, false); + radialProgress.setBackground(getDrawableForCurrentState(), progressVisible, animated); + invalidate(); } else { - progressVisible = true; - buttonState = 1; - Float progress = ImageLoader.getInstance().getFileProgress(fileName); - setProgress = progress != null ? progress : 0; + MediaController.getInstance().removeLoadingFileObserver(this); + if (isDocument == 2 && !photoImage.isAllowStartAnimation()) { + buttonState = 2; + } else { + buttonState = -1; + } + radialProgress.setBackground(getDrawableForCurrentState(), false, animated); + invalidate(); } - radialProgress.setProgress(setProgress, false); - radialProgress.setBackground(getDrawableForCurrentState(), progressVisible, animated); - invalidate(); } else { - MediaController.getInstance().removeLoadingFileObserver(this); - if (isGifDocument && !linkImageView.isAllowStartAnimation()) { - buttonState = 2; + if (currentMessageObject.isOut() && currentMessageObject.isSending()) { + if (currentMessageObject.messageOwner.attachPath != null && currentMessageObject.messageOwner.attachPath.length() > 0) { + MediaController.getInstance().addLoadingFileObserver(currentMessageObject.messageOwner.attachPath, this); + boolean needProgress = currentMessageObject.messageOwner.attachPath == null || !currentMessageObject.messageOwner.attachPath.startsWith("http"); + HashMap params = currentMessageObject.messageOwner.params; + if (currentMessageObject.messageOwner.message != null && params != null && (params.containsKey("url") || params.containsKey("bot"))) { + needProgress = false; + buttonState = -1; + } else { + buttonState = 1; + } + radialProgress.setBackground(getDrawableForCurrentState(), needProgress, animated); + if (needProgress) { + Float progress = ImageLoader.getInstance().getFileProgress(currentMessageObject.messageOwner.attachPath); + if (progress == null && SendMessagesHelper.getInstance().isSendingMessage(currentMessageObject.getId())) { + progress = 1.0f; + } + radialProgress.setProgress(progress != null ? progress : 0, false); + } else { + radialProgress.setProgress(0, false); + } + invalidate(); + } } else { - buttonState = -1; + if (currentMessageObject.messageOwner.attachPath != null && currentMessageObject.messageOwner.attachPath.length() != 0) { + MediaController.getInstance().removeLoadingFileObserver(this); + } + if (cacheFile.exists() && cacheFile.length() == 0) { + cacheFile.delete(); + } + if (!cacheFile.exists()) { + MediaController.getInstance().addLoadingFileObserver(fileName, this); + float setProgress = 0; + boolean progressVisible = false; + if (!FileLoader.getInstance().isLoadingFile(fileName)) { + if (!cancelLoading && + (currentMessageObject.type == 1 && MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) || + currentMessageObject.type == 8 && MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_GIF) && MessageObject.isNewGifDocument(currentMessageObject.messageOwner.media.document)) ) { + progressVisible = true; + buttonState = 1; + } else { + buttonState = 0; + } + } else { + progressVisible = true; + buttonState = 1; + Float progress = ImageLoader.getInstance().getFileProgress(fileName); + setProgress = progress != null ? progress : 0; + } + radialProgress.setProgress(setProgress, false); + radialProgress.setBackground(getDrawableForCurrentState(), progressVisible, animated); + invalidate(); + } else { + MediaController.getInstance().removeLoadingFileObserver(this); + if (currentMessageObject.type == 8 && !photoImage.isAllowStartAnimation()) { + buttonState = 2; + } else if (currentMessageObject.type == 3) { + buttonState = 3; + } else { + buttonState = -1; + } + radialProgress.setBackground(getDrawableForCurrentState(), false, animated); + if (photoNotSet) { + setMessageObject(currentMessageObject); + } + invalidate(); + } + } + } + } + + public void setAllowedToSetPhoto(boolean value) { + if (allowedToSetPhoto == value) { + return; + } + if (currentMessageObject != null && currentMessageObject.type == 1) { + allowedToSetPhoto = value; + if (value) { + MessageObject temp = currentMessageObject; + currentMessageObject = null; + setMessageObject(temp); } - radialProgress.setBackground(getDrawableForCurrentState(), false, animated); - invalidate(); } } @@ -1037,33 +2163,52 @@ private void didPressedButton(boolean animated) { if (buttonState == 0) { cancelLoading = false; radialProgress.setProgress(0, false); - if (isGifDocument) { - linkImageView.setImage(currentMessageObject.messageOwner.media.webpage.document, null, currentPhotoObject.location, currentPhotoFilter, currentMessageObject.messageOwner.media.webpage.document.size, null, false); + if (currentMessageObject.type == 1) { + photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilter, currentPhotoObject.size, null, false); + } else if (currentMessageObject.type == 8) { currentMessageObject.audioProgress = 2; + photoImage.setImage(currentMessageObject.messageOwner.media.document, null, currentPhotoObject != null ? currentPhotoObject.location : null, currentPhotoFilter, currentMessageObject.messageOwner.media.document.size, null, false); + } else if (currentMessageObject.type == 9) { + FileLoader.getInstance().loadFile(currentMessageObject.messageOwner.media.document, false, false); + } else if (currentMessageObject.type == 3) { + FileLoader.getInstance().loadFile(currentMessageObject.messageOwner.media.document, true, false); + } else if (currentMessageObject.type == 0 && isDocument != 0) { + if (isDocument == 2) { + photoImage.setImage(currentMessageObject.messageOwner.media.webpage.document, null, currentPhotoObject.location, currentPhotoFilter, currentMessageObject.messageOwner.media.webpage.document.size, null, false); + currentMessageObject.audioProgress = 2; + } else if (isDocument == 1) { + FileLoader.getInstance().loadFile(currentMessageObject.messageOwner.media.webpage.document, false, false); + } } else { - linkImageView.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilterThumb, 0, null, false); + photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilterThumb, 0, null, false); } buttonState = 1; radialProgress.setBackground(getDrawableForCurrentState(), true, animated); invalidate(); } else if (buttonState == 1) { if (currentMessageObject.isOut() && currentMessageObject.isSending()) { - if (delegate != null) { - delegate.didPressedCancelSendButton(this); - } + delegate.didPressedCancelSendButton(this); } else { cancelLoading = true; - linkImageView.cancelLoadImage(); + if (currentMessageObject.type == 0 && isDocument == 1) { + FileLoader.getInstance().cancelLoadFile(currentMessageObject.messageOwner.media.webpage.document); + } else if (currentMessageObject.type == 0 || currentMessageObject.type == 1 || currentMessageObject.type == 8) { + photoImage.cancelLoadImage(); + } else if (currentMessageObject.type == 9 || currentMessageObject.type == 3) { + FileLoader.getInstance().cancelLoadFile(currentMessageObject.messageOwner.media.document); + } buttonState = 0; radialProgress.setBackground(getDrawableForCurrentState(), false, animated); invalidate(); } } else if (buttonState == 2) { - linkImageView.setAllowStartAnimation(true); - linkImageView.startAnimation(); + photoImage.setAllowStartAnimation(true); + photoImage.startAnimation(); currentMessageObject.audioProgress = 0; buttonState = -1; radialProgress.setBackground(getDrawableForCurrentState(), false, animated); + } else if (buttonState == 3) { + delegate.didPressedImage(this); } } @@ -1075,13 +2220,28 @@ public void onFailedDownload(String fileName) { @Override public void onSuccessDownload(String fileName) { radialProgress.setProgress(1, true); - if (isGifDocument && currentMessageObject.audioProgress != 1) { - buttonState = 2; - didPressedButton(true); - } else if (!photoNotSet) { - updateButtonState(true); + if (currentMessageObject.type == 0) { + if (isDocument == 2 && currentMessageObject.audioProgress != 1) { + buttonState = 2; + didPressedButton(true); + } else if (!photoNotSet) { + updateButtonState(true); + } else { + setMessageObject(currentMessageObject); + } } else { - setMessageObject(currentMessageObject); + if (!photoNotSet || currentMessageObject.type == 8 && currentMessageObject.audioProgress != 1) { + if (currentMessageObject.type == 8 && currentMessageObject.audioProgress != 1) { + photoNotSet = false; + buttonState = 2; + didPressedButton(true); + } else { + updateButtonState(true); + } + } + if (photoNotSet) { + setMessageObject(currentMessageObject); + } } } @@ -1093,11 +2253,20 @@ public void onProgressDownload(String fileName, float progress) { } } + @Override + public void onProgressUpload(String fileName, float progress, boolean isEncrypted) { + radialProgress.setProgress(progress, true); + } + @Override public void onProvideStructure(ViewStructure structure) { super.onProvideStructure(structure); if (allowAssistant && Build.VERSION.SDK_INT >= 23) { - structure.setText(currentMessageObject.messageText); + if (currentMessageObject.messageText != null && currentMessageObject.messageText.length() > 0) { + structure.setText(currentMessageObject.messageText); + } else if (currentMessageObject.caption != null && currentMessageObject.caption.length() > 0) { + structure.setText(currentMessageObject.caption); + } } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerProfileCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerProfileCell.java index 37f2fae983..4504abbcca 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerProfileCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerProfileCell.java @@ -17,6 +17,7 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; +import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; import android.widget.FrameLayout; @@ -66,6 +67,7 @@ public DrawerProfileCell(Context context) { nameTextView.setMaxLines(1); nameTextView.setSingleLine(true); nameTextView.setGravity(Gravity.LEFT); + nameTextView.setEllipsize(TextUtils.TruncateAt.END); addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.BOTTOM, 16, 0, 16, 28)); phoneTextView = new TextView(context); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShadowSectionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShadowSectionCell.java index 6f2257ef4a..c7d9a88ab9 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShadowSectionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShadowSectionCell.java @@ -16,13 +16,19 @@ public class ShadowSectionCell extends View { + private int size = 12; + public ShadowSectionCell(Context context) { super(context); setBackgroundResource(R.drawable.greydivider); } + public void setSize(int value) { + size = value; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(12), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(size), MeasureSpec.EXACTLY)); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextBlockCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextBlockCell.java index c9a9fee641..37b6f197e3 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextBlockCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextBlockCell.java @@ -38,7 +38,7 @@ public TextBlockCell(Context context) { textView.setTextColor(0xff212121); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); - addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 17, 8, 17, 8)); + addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 17, 10, 17, 10)); } public void setTextColor(int color) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckCell.java index 7c343a931b..46c40454aa 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckCell.java @@ -25,6 +25,7 @@ public class TextCheckCell extends FrameLayoutFixed { private TextView textView; + private TextView valueTextView; private Switch checkBox; private static Paint paint; private boolean needDivider; @@ -47,6 +48,16 @@ public TextCheckCell(Context context) { textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 17, 0, 17, 0)); + valueTextView = new TextView(context); + valueTextView.setTextColor(0xff8a8a8a); + valueTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); + valueTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + valueTextView.setLines(1); + valueTextView.setMaxLines(1); + valueTextView.setSingleLine(true); + valueTextView.setPadding(0, 0, 0, 0); + addView(valueTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 17, 35, 17, 0)); + checkBox = new Switch(context); checkBox.setDuplicateParentStateEnabled(false); checkBox.setFocusable(false); @@ -57,7 +68,7 @@ public TextCheckCell(Context context) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(48) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(valueTextView.getVisibility() == VISIBLE ? 64 : 48) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); } public void setTextAndCheck(String text, boolean checked, boolean divider) { @@ -68,6 +79,28 @@ public void setTextAndCheck(String text, boolean checked, boolean divider) { } checkBox.setChecked(checked); needDivider = divider; + valueTextView.setVisibility(GONE); + LayoutParams layoutParams = (LayoutParams) textView.getLayoutParams(); + layoutParams.height = LayoutParams.MATCH_PARENT; + layoutParams.topMargin = 0; + textView.setLayoutParams(layoutParams); + setWillNotDraw(!divider); + } + + public void setTextAndValueAndCheck(String text, String value, boolean checked, boolean divider) { + textView.setText(text); + valueTextView.setText(value); + if (Build.VERSION.SDK_INT < 11) { + checkBox.resetLayout(); + checkBox.requestLayout(); + } + checkBox.setChecked(checked); + needDivider = divider; + valueTextView.setVisibility(VISIBLE); + LayoutParams layoutParams = (LayoutParams) textView.getLayoutParams(); + layoutParams.height = LayoutParams.WRAP_CONTENT; + layoutParams.topMargin = AndroidUtilities.dp(10); + textView.setLayoutParams(layoutParams); setWillNotDraw(!divider); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextSettingsCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextSettingsCell.java index 1d99c59eae..7aa88dc294 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextSettingsCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextSettingsCell.java @@ -87,6 +87,10 @@ public void setTextColor(int color) { textView.setTextColor(color); } + public void setTextValueColor(int color) { + valueTextView.setTextColor(color); + } + public void setText(String text, boolean divider) { textView.setText(text); valueTextView.setVisibility(INVISIBLE); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell.java index 547e8e348d..ecc6b8ae77 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell.java @@ -37,6 +37,7 @@ public class UserCell extends FrameLayout { private ImageView imageView; private CheckBox checkBox; private CheckBoxSquare checkBoxBig; + private ImageView adminImage; private AvatarDrawable avatarDrawable; private TLObject currentObject = null; @@ -52,7 +53,7 @@ public class UserCell extends FrameLayout { private int statusColor = 0xffa8a8a8; private int statusOnlineColor = 0xff3b84c0; - public UserCell(Context context, int padding, int checkbox) { + public UserCell(Context context, int padding, int checkbox, boolean admin) { super(context); avatarDrawable = new AvatarDrawable(); @@ -65,7 +66,7 @@ public UserCell(Context context, int padding, int checkbox) { nameTextView.setTextColor(0xff212121); nameTextView.setTextSize(17); nameTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); - addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 28 : (68 + padding), 11.5f, LocaleController.isRTL ? (68 + padding) : 28, 0)); + addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 28 + (checkbox == 2 ? 18 : 0) : (68 + padding), 11.5f, LocaleController.isRTL ? (68 + padding) : 28 + (checkbox == 2 ? 18 : 0), 0)); statusTextView = new SimpleTextView(context); statusTextView.setTextSize(14); @@ -85,6 +86,20 @@ public UserCell(Context context, int padding, int checkbox) { checkBox.setVisibility(INVISIBLE); addView(checkBox, LayoutHelper.createFrame(22, 22, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 37 + padding, 38, LocaleController.isRTL ? 37 + padding : 0, 0)); } + + if (admin) { + adminImage = new ImageView(context); + adminImage.setImageResource(R.drawable.admin_star); + addView(adminImage, LayoutHelper.createFrame(16, 16, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP, LocaleController.isRTL ? 24 : 0, 13.5f, LocaleController.isRTL ? 0 : 24, 0)); + } + } + + public void setIsAdmin(boolean value) { + if (adminImage == null) { + return; + } + adminImage.setVisibility(value ? VISIBLE : GONE); + nameTextView.setPadding(LocaleController.isRTL && value ? AndroidUtilities.dp(16) : 0, 0, !LocaleController.isRTL && value ? AndroidUtilities.dp(16) : 0, 0); } public void setData(TLObject user, CharSequence name, CharSequence status, int resId) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChangeNameActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChangeNameActivity.java index 2a3f30834f..3af498a7fe 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChangeNameActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChangeNameActivity.java @@ -166,6 +166,7 @@ private void saveName() { return; } TLRPC.TL_account_updateProfile req = new TLRPC.TL_account_updateProfile(); + req.flags = 3; currentUser.first_name = req.first_name = newFirst; currentUser.last_name = req.last_name = newLast; TLRPC.User user = MessagesController.getInstance().getUser(UserConfig.getClientUserId()); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneActivity.java index d4ab922a9a..037b507300 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneActivity.java @@ -1,5 +1,5 @@ /* - * This is the source code of Telegram for Android v. 2.0.x. + * This is the source code of Telegram for Android v. 3.x.x. * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * @@ -8,17 +8,25 @@ package org.telegram.ui; +import android.Manifest; import android.annotation.SuppressLint; +import android.app.Activity; import android.app.AlertDialog; +import android.app.Dialog; import android.app.ProgressDialog; import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.os.Build; import android.os.Bundle; import android.telephony.TelephonyManager; import android.text.Editable; import android.text.InputFilter; import android.text.InputType; -import android.text.SpannableStringBuilder; -import android.text.Spanned; import android.text.TextUtils; import android.text.TextWatcher; import android.util.TypedValue; @@ -30,6 +38,7 @@ import android.widget.AdapterView; import android.widget.EditText; import android.widget.FrameLayout; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; @@ -59,7 +68,6 @@ import org.telegram.ui.Components.HintEditText; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.SlideView; -import org.telegram.ui.Components.TypefaceSpan; import java.io.BufferedReader; import java.io.InputStreamReader; @@ -74,17 +82,21 @@ public class ChangePhoneActivity extends BaseFragment { private int currentViewNum = 0; - private SlideView[] views = new SlideView[2]; + private SlideView[] views = new SlideView[5]; private ProgressDialog progressDialog; + private Dialog permissionsDialog; + private ArrayList permissionsItems = new ArrayList<>(); + private boolean checkPermissions = true; + private View doneButton; private final static int done_button = 1; @Override public void onFragmentDestroy() { super.onFragmentDestroy(); - for (SlideView v : views) { - if (v != null) { - v.onDestroyActivity(); + for (int a = 0; a < views.length; a++) { + if (views[a] != null) { + views[a].onDestroyActivity(); } } if (progressDialog != null) { @@ -114,39 +126,25 @@ public void onItemClick(int id) { }); ActionBarMenu menu = actionBar.createMenu(); - menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); + doneButton = menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); fragmentView = new ScrollView(context); ScrollView scrollView = (ScrollView) fragmentView; scrollView.setFillViewport(true); FrameLayout frameLayout = new FrameLayout(context); - scrollView.addView(frameLayout); - ScrollView.LayoutParams layoutParams = (ScrollView.LayoutParams) frameLayout.getLayoutParams(); - layoutParams.width = ScrollView.LayoutParams.MATCH_PARENT; - layoutParams.height = ScrollView.LayoutParams.WRAP_CONTENT; - layoutParams.gravity = Gravity.TOP | Gravity.LEFT; - frameLayout.setLayoutParams(layoutParams); + scrollView.addView(frameLayout, LayoutHelper.createScroll(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT)); views[0] = new PhoneView(context); - views[0].setVisibility(View.VISIBLE); - frameLayout.addView(views[0], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT, 16, 30, 16, 0)); - - views[1] = new LoginActivitySmsView(context); - views[1].setVisibility(View.GONE); - frameLayout.addView(views[1], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 16, 30, 16, 0)); - - try { - if (views[0] == null || views[1] == null) { - FrameLayout parent = (FrameLayout) ((ScrollView) fragmentView).getChildAt(0); - for (int a = 0; a < views.length; a++) { - if (views[a] == null) { - views[a] = (SlideView) parent.getChildAt(a); - } - } - } - } catch (Exception e) { - FileLog.e("tmessages", e); + views[1] = new LoginActivitySmsView(context, 1); + views[2] = new LoginActivitySmsView(context, 2); + views[3] = new LoginActivitySmsView(context, 3); + views[4] = new LoginActivitySmsView(context, 4); + + for (int a = 0; a < views.length; a++) { + views[a].setVisibility(a == 0 ? View.VISIBLE : View.GONE); + frameLayout.addView(views[a], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, a == 0 ? LayoutHelper.WRAP_CONTENT : LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, AndroidUtilities.isTablet() ? 26 : 18, 30, AndroidUtilities.isTablet() ? 26 : 18, 0)); + //LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT, 16, 30, 16, 0) } actionBar.setTitle(views[0].getHeaderName()); @@ -160,16 +158,34 @@ public void onResume() { AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid); } + @Override + public void onRequestPermissionsResultFragment(int requestCode, String[] permissions, int[] grantResults) { + if (requestCode == 6) { + checkPermissions = false; + if (currentViewNum == 0) { + views[currentViewNum].onNextPressed(); + } + } + } + + @Override + protected void onDialogDismiss(Dialog dialog) { + if (Build.VERSION.SDK_INT >= 23 && dialog == permissionsDialog && !permissionsItems.isEmpty()) { + getParentActivity().requestPermissions(permissionsItems.toArray(new String[permissionsItems.size()]), 6); + } + } + @Override public boolean onBackPressed() { if (currentViewNum == 0) { - for (SlideView v : views) { - if (v != null) { - v.onDestroyActivity(); + for (int a = 0; a < views.length; a++) { + if (views[a] != null) { + views[a].onDestroyActivity(); } } return true; - } else if (currentViewNum == 1) { + } else { + views[currentViewNum].onBackPressed(); setPage(0, true, null, true); } return false; @@ -217,6 +233,14 @@ public void needHideProgress() { } public void setPage(int page, boolean animated, Bundle params, boolean back) { + if (page == 3) { + doneButton.setVisibility(View.GONE); + } else { + if (page == 0) { + checkPermissions = true; + } + doneButton.setVisibility(View.VISIBLE); + } if(android.os.Build.VERSION.SDK_INT > 10) { final SlideView outView = views[currentViewNum]; final SlideView newView = views[page]; @@ -257,6 +281,40 @@ public void onAnimationEnd(Object animation) { } } + private void fillNextCodeParams(Bundle params, TLRPC.TL_auth_sentCode res) { + params.putString("phoneHash", res.phone_code_hash); + if (res.next_type instanceof TLRPC.TL_auth_codeTypeCall) { + params.putInt("nextType", 4); + } else if (res.next_type instanceof TLRPC.TL_auth_codeTypeFlashCall) { + params.putInt("nextType", 3); + } else if (res.next_type instanceof TLRPC.TL_auth_codeTypeSms) { + params.putInt("nextType", 2); + } + if (res.type instanceof TLRPC.TL_auth_sentCodeTypeApp) { + params.putInt("type", 1); + params.putInt("length", res.type.length); + setPage(1, true, params, false); + } else { + if (res.timeout == 0) { + res.timeout = 60; + } + params.putInt("timeout", res.timeout * 1000); + if (res.type instanceof TLRPC.TL_auth_sentCodeTypeCall) { + params.putInt("type", 4); + params.putInt("length", res.type.length); + setPage(4, true, params, false); + } else if (res.type instanceof TLRPC.TL_auth_sentCodeTypeFlashCall) { + params.putInt("type", 3); + params.putString("pattern", res.type.pattern); + setPage(3, true, params, false); + } else if (res.type instanceof TLRPC.TL_auth_sentCodeTypeSms) { + params.putInt("type", 2); + params.putInt("length", res.type.length); + setPage(2, true, params, false); + } + } + } + public class PhoneView extends SlideView implements AdapterView.OnItemSelectedListener { private EditText codeField; @@ -287,9 +345,9 @@ public PhoneView(Context context) { countryButton.setMaxLines(1); countryButton.setSingleLine(true); countryButton.setEllipsize(TextUtils.TruncateAt.END); - countryButton.setGravity(Gravity.LEFT | Gravity.CENTER_HORIZONTAL); + countryButton.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_HORIZONTAL); countryButton.setBackgroundResource(R.drawable.spinner_states); - addView(countryButton, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 20, 0, 20, 14)); + addView(countryButton, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 0, 0, 0, 14)); countryButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { @@ -315,7 +373,7 @@ public void run() { View view = new View(context); view.setPadding(AndroidUtilities.dp(12), 0, AndroidUtilities.dp(12), 0); view.setBackgroundColor(0xffdbdbdb); - addView(view, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 1, 24, -17.5f, 24, 0)); + addView(view, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 1, 4, -17.5f, 4, 0)); LinearLayout linearLayout = new LinearLayout(context); linearLayout.setOrientation(HORIZONTAL); @@ -325,7 +383,7 @@ public void run() { textView.setText("+"); textView.setTextColor(0xff212121); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - linearLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, 24, 0, 0, 0)); + linearLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); codeField = new EditText(context); codeField.setInputType(InputType.TYPE_CLASS_PHONE); @@ -354,7 +412,6 @@ public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { @Override public void afterTextChanged(Editable editable) { if (ignoreOnTextChange) { - ignoreOnTextChange = false; return; } ignoreOnTextChange = true; @@ -414,6 +471,7 @@ public void afterTextChanged(Editable editable) { phoneField.setSelection(phoneField.length()); } } + ignoreOnTextChange = false; } }); codeField.setOnEditorActionListener(new TextView.OnEditorActionListener() { @@ -438,7 +496,7 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { phoneField.setMaxLines(1); phoneField.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); phoneField.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI); - linearLayout.addView(phoneField, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 0, 0, 24, 0)); + linearLayout.addView(phoneField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36)); phoneField.addTextChangedListener(new TextWatcher() { private int characterAction = -1; @@ -528,9 +586,9 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { textView.setText(LocaleController.getString("ChangePhoneHelp", R.string.ChangePhoneHelp)); textView.setTextColor(0xff757575); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - textView.setGravity(Gravity.LEFT); + textView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); textView.setLineSpacing(AndroidUtilities.dp(2), 1.0f); - addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT, 24, 28, 24, 10)); + addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 28, 0, 10)); HashMap languageMap = new HashMap<>(); try { @@ -561,7 +619,7 @@ public int compare(String lhs, String rhs) { String country = null; try { - TelephonyManager telephonyManager = (TelephonyManager)ApplicationLoader.applicationContext.getSystemService(Context.TELEPHONY_SERVICE); + TelephonyManager telephonyManager = (TelephonyManager) ApplicationLoader.applicationContext.getSystemService(Context.TELEPHONY_SERVICE); if (telephonyManager != null) { country = telephonyManager.getSimCountryIso().toUpperCase(); } @@ -605,6 +663,7 @@ public void selectCountry(String name) { String hint = phoneFormatMap.get(code); phoneField.setHintText(hint != null ? hint.replace('X', '–') : null); countryState = 0; + ignoreOnTextChange = false; } } @@ -617,6 +676,7 @@ public void onItemSelected(AdapterView adapterView, View view, int i, long l) ignoreOnTextChange = true; String str = countriesArray.get(i); codeField.setText(countriesMap.get(str)); + ignoreOnTextChange = false; } @Override @@ -626,9 +686,46 @@ public void onNothingSelected(AdapterView adapterView) { @Override public void onNextPressed() { - if (nextPressed) { + if (getParentActivity() == null || nextPressed) { return; } + TelephonyManager tm = (TelephonyManager) ApplicationLoader.applicationContext.getSystemService(Context.TELEPHONY_SERVICE); + boolean simcardAvailable = tm.getSimState() != TelephonyManager.SIM_STATE_ABSENT && tm.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE; + boolean allowCall = true; + if (Build.VERSION.SDK_INT >= 23 && simcardAvailable) { + allowCall = getParentActivity().checkSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; + boolean allowSms = getParentActivity().checkSelfPermission(Manifest.permission.RECEIVE_SMS) == PackageManager.PERMISSION_GRANTED; + if (checkPermissions) { + permissionsItems.clear(); + if (!allowCall) { + permissionsItems.add(Manifest.permission.READ_PHONE_STATE); + } + if (!allowSms) { + permissionsItems.add(Manifest.permission.RECEIVE_SMS); + } + if (!permissionsItems.isEmpty()) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + if (preferences.getBoolean("firstlogin", true) || getParentActivity().shouldShowRequestPermissionRationale(Manifest.permission.READ_PHONE_STATE) || getParentActivity().shouldShowRequestPermissionRationale(Manifest.permission.RECEIVE_SMS)) { + preferences.edit().putBoolean("firstlogin", false).commit(); + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + if (permissionsItems.size() == 2) { + builder.setMessage(LocaleController.getString("AllowReadCallAndSms", R.string.AllowReadCallAndSms)); + } else if (!allowSms) { + builder.setMessage(LocaleController.getString("AllowReadSms", R.string.AllowReadSms)); + } else { + builder.setMessage(LocaleController.getString("AllowReadCall", R.string.AllowReadCall)); + } + permissionsDialog = showDialog(builder.create()); + } else { + getParentActivity().requestPermissions(permissionsItems.toArray(new String[permissionsItems.size()]), 6); + } + return; + } + } + } + if (countryState == 1) { needShowAlert(LocaleController.getString("ChooseCountry", R.string.ChooseCountry)); return; @@ -643,10 +740,20 @@ public void onNextPressed() { TLRPC.TL_account_sendChangePhoneCode req = new TLRPC.TL_account_sendChangePhoneCode(); String phone = PhoneFormat.stripExceptNumbers("" + codeField.getText() + phoneField.getText()); req.phone_number = phone; - final String phone2 = "+" + codeField.getText() + " " + phoneField.getText(); + req.allow_flashcall = simcardAvailable && allowCall; + if (req.allow_flashcall) { + String number = tm.getLine1Number(); + req.current_number = number != null && number.length() != 0 && (phone.contains(number) || number.contains(phone)); + } final Bundle params = new Bundle(); - params.putString("phone", phone2); + params.putString("phone", "+" + codeField.getText() + phoneField.getText()); + try { + params.putString("ephone", "+" + PhoneFormat.stripExceptNumbers(codeField.getText().toString()) + " " + PhoneFormat.stripExceptNumbers(phoneField.getText().toString())); + } catch (Exception e) { + FileLog.e("tmessages", e); + params.putString("ephone", "+" + phone); + } params.putString("phoneFormated", phone); nextPressed = true; needShowProgress(); @@ -658,10 +765,7 @@ public void run(final TLObject response, final TLRPC.TL_error error) { public void run() { nextPressed = false; if (error == null) { - TLRPC.TL_account_sentChangePhoneCode res = (TLRPC.TL_account_sentChangePhoneCode)response; - params.putString("phoneHash", res.phone_code_hash); - params.putInt("calltime", res.send_call_timeout * 1000); - setPage(1, true, params, false); + fillNextCodeParams(params, (TLRPC.TL_auth_sentCode) response); } else { if (error.text != null) { if (error.text.contains("PHONE_NUMBER_INVALID")) { @@ -673,7 +777,7 @@ public void run() { } else if (error.text.startsWith("FLOOD_WAIT")) { needShowAlert(LocaleController.getString("FloodWait", R.string.FloodWait)); } else if (error.text.startsWith("PHONE_NUMBER_OCCUPIED")) { - needShowAlert(LocaleController.formatString("ChangePhoneNumberOccupied", R.string.ChangePhoneNumberOccupied, phone2)); + needShowAlert(LocaleController.formatString("ChangePhoneNumberOccupied", R.string.ChangePhoneNumberOccupied, params.getString("phone"))); } else { needShowAlert(LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred)); } @@ -709,43 +813,88 @@ public String getHeaderName() { public class LoginActivitySmsView extends SlideView implements NotificationCenter.NotificationCenterDelegate { + private class ProgressView extends View { + + private Paint paint = new Paint(); + private Paint paint2 = new Paint(); + private float progress; + + public ProgressView(Context context) { + super(context); + paint.setColor(0xffe1eaf2); + paint2.setColor(0xff62a0d0); + } + + public void setProgress(float value) { + progress = value; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + int start = (int) (getMeasuredWidth() * progress); + canvas.drawRect(0, 0, start, getMeasuredHeight(), paint2); + canvas.drawRect(start, 0, getMeasuredWidth(), getMeasuredHeight(), paint); + } + } + + private String phone; private String phoneHash; private String requestPhone; + private String emailPhone; private EditText codeField; private TextView confirmTextView; private TextView timeText; + private TextView problemText; private Bundle currentParams; + private ProgressView progressView; private Timer timeTimer; private Timer codeTimer; + private int openTime; private final Object timerSync = new Object(); private volatile int time = 60000; private volatile int codeTime = 15000; private double lastCurrentTime; private double lastCodeTime; private boolean ignoreOnTextChange; - private boolean waitingForSms = false; - private boolean nextPressed = false; + private boolean waitingForEvent; + private boolean nextPressed; private String lastError = ""; + private int currentType; + private int nextType; + private String pattern = "*"; + private int length; + private int timeout; - public LoginActivitySmsView(Context context) { + public LoginActivitySmsView(Context context, final int type) { super(context); + currentType = type; setOrientation(VERTICAL); confirmTextView = new TextView(context); confirmTextView.setTextColor(0xff757575); confirmTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - confirmTextView.setGravity(Gravity.LEFT); + confirmTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); confirmTextView.setLineSpacing(AndroidUtilities.dp(2), 1.0f); - addView(confirmTextView); - LayoutParams layoutParams = (LayoutParams) confirmTextView.getLayoutParams(); - layoutParams.width = LayoutHelper.WRAP_CONTENT; - layoutParams.height = LayoutHelper.WRAP_CONTENT; - layoutParams.gravity = Gravity.LEFT; - layoutParams.leftMargin = AndroidUtilities.dp(24); - layoutParams.rightMargin = AndroidUtilities.dp(24); - confirmTextView.setLayoutParams(layoutParams); + + if (currentType == 3) { + FrameLayout frameLayout = new FrameLayout(context); + + ImageView imageView = new ImageView(context); + imageView.setImageResource(R.drawable.phone_activate); + if (LocaleController.isRTL) { + frameLayout.addView(imageView, LayoutHelper.createFrame(64, 76, Gravity.LEFT | Gravity.CENTER_VERTICAL, 2, 2, 0, 0)); + frameLayout.addView(confirmTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 64 + 18, 0, 0, 0)); + } else { + frameLayout.addView(confirmTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 0, 64 + 18, 0)); + frameLayout.addView(imageView, LayoutHelper.createFrame(64, 76, Gravity.RIGHT | Gravity.CENTER_VERTICAL, 0, 2, 0, 2)); + } + addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); + } else { + addView(confirmTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); + } codeField = new EditText(context); codeField.setTextColor(0xff212121); @@ -757,15 +906,7 @@ public LoginActivitySmsView(Context context) { codeField.setInputType(InputType.TYPE_CLASS_PHONE); codeField.setMaxLines(1); codeField.setPadding(0, 0, 0, 0); - addView(codeField); - layoutParams = (LayoutParams) codeField.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = AndroidUtilities.dp(36); - layoutParams.gravity = Gravity.CENTER_HORIZONTAL; - layoutParams.topMargin = AndroidUtilities.dp(20); - layoutParams.leftMargin = AndroidUtilities.dp(24); - layoutParams.rightMargin = AndroidUtilities.dp(24); - codeField.setLayoutParams(layoutParams); + addView(codeField, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, Gravity.CENTER_HORIZONTAL, 0, 20, 0, 0)); codeField.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { @@ -782,7 +923,7 @@ public void afterTextChanged(Editable s) { if (ignoreOnTextChange) { return; } - if (codeField.length() == 5) { + if (length != 0 && codeField.length() == length) { onNextPressed(); } } @@ -797,55 +938,133 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { return false; } }); + if (currentType == 3) { + codeField.setEnabled(false); + codeField.setInputType(InputType.TYPE_NULL); + codeField.setVisibility(GONE); + } timeText = new TextView(context); timeText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); timeText.setTextColor(0xff757575); timeText.setLineSpacing(AndroidUtilities.dp(2), 1.0f); - timeText.setGravity(Gravity.LEFT); - addView(timeText); - layoutParams = (LayoutParams) timeText.getLayoutParams(); - layoutParams.width = LayoutHelper.WRAP_CONTENT; - layoutParams.height = LayoutHelper.WRAP_CONTENT; - layoutParams.gravity = Gravity.LEFT; - layoutParams.topMargin = AndroidUtilities.dp(30); - layoutParams.leftMargin = AndroidUtilities.dp(24); - layoutParams.rightMargin = AndroidUtilities.dp(24); - timeText.setLayoutParams(layoutParams); + timeText.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + addView(timeText, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 30, 0, 0)); + + if (currentType == 3) { + progressView = new ProgressView(context); + addView(progressView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 3, 0, 12, 0, 0)); + } + + problemText = new TextView(context); + problemText.setText(LocaleController.getString("DidNotGetTheCode", R.string.DidNotGetTheCode)); + problemText.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + problemText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + problemText.setTextColor(0xff4d83b3); + problemText.setLineSpacing(AndroidUtilities.dp(2), 1.0f); + problemText.setPadding(0, AndroidUtilities.dp(2), 0, AndroidUtilities.dp(12)); + addView(problemText, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 20, 0, 0)); + problemText.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (nextPressed) { + return; + } + if (nextType != 0 && nextType != 4) { + resendCode(); + } else { + try { + PackageInfo pInfo = ApplicationLoader.applicationContext.getPackageManager().getPackageInfo(ApplicationLoader.applicationContext.getPackageName(), 0); + String version = String.format(Locale.US, "%s (%d)", pInfo.versionName, pInfo.versionCode); + + Intent mailer = new Intent(Intent.ACTION_SEND); + mailer.setType("message/rfc822"); + mailer.putExtra(Intent.EXTRA_EMAIL, new String[]{"sms@stel.com"}); + mailer.putExtra(Intent.EXTRA_SUBJECT, "Android registration/login issue " + version + " " + emailPhone); + mailer.putExtra(Intent.EXTRA_TEXT, "Phone: " + requestPhone + "\nApp version: " + version + "\nOS version: SDK " + Build.VERSION.SDK_INT + "\nDevice Name: " + Build.MANUFACTURER + Build.MODEL + "\nLocale: " + Locale.getDefault() + "\nError: " + lastError); + getContext().startActivity(Intent.createChooser(mailer, "Send email...")); + } catch (Exception e) { + needShowAlert(LocaleController.getString("NoMailInstalled", R.string.NoMailInstalled)); + } + } + } + }); LinearLayout linearLayout = new LinearLayout(context); - linearLayout.setGravity(Gravity.BOTTOM | Gravity.CENTER_VERTICAL); - addView(linearLayout); - layoutParams = (LayoutParams) linearLayout.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - linearLayout.setLayoutParams(layoutParams); + linearLayout.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); + addView(linearLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); TextView wrongNumber = new TextView(context); - wrongNumber.setGravity(Gravity.LEFT | Gravity.CENTER_HORIZONTAL); + wrongNumber.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_HORIZONTAL); wrongNumber.setTextColor(0xff4d83b3); wrongNumber.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); wrongNumber.setLineSpacing(AndroidUtilities.dp(2), 1.0f); wrongNumber.setPadding(0, AndroidUtilities.dp(24), 0, 0); - linearLayout.addView(wrongNumber); - layoutParams = (LayoutParams) wrongNumber.getLayoutParams(); - layoutParams.width = LayoutHelper.WRAP_CONTENT; - layoutParams.height = LayoutHelper.WRAP_CONTENT; - layoutParams.gravity = Gravity.BOTTOM | Gravity.LEFT; - layoutParams.bottomMargin = AndroidUtilities.dp(10); - layoutParams.leftMargin = AndroidUtilities.dp(24); - layoutParams.rightMargin = AndroidUtilities.dp(24); - wrongNumber.setLayoutParams(layoutParams); + linearLayout.addView(wrongNumber, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), 0, 0, 0, 10)); wrongNumber.setText(LocaleController.getString("WrongNumber", R.string.WrongNumber)); wrongNumber.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { + TLRPC.TL_auth_cancelCode req = new TLRPC.TL_auth_cancelCode(); + req.phone_number = requestPhone; + req.phone_code_hash = phoneHash; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }, ConnectionsManager.RequestFlagFailOnServerErrors); onBackPressed(); setPage(0, true, null, true); } }); } + private void resendCode() { + final Bundle params = new Bundle(); + params.putString("phone", phone); + params.putString("ephone", emailPhone); + params.putString("phoneFormated", requestPhone); + + nextPressed = true; + needShowProgress(); + + TLRPC.TL_auth_resendCode req = new TLRPC.TL_auth_resendCode(); + req.phone_number = requestPhone; + req.phone_code_hash = phoneHash; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + nextPressed = false; + if (error == null) { + fillNextCodeParams(params, (TLRPC.TL_auth_sentCode) response); + } else { + if (error.text != null) { + if (error.text.contains("PHONE_NUMBER_INVALID")) { + needShowAlert(LocaleController.getString("InvalidPhoneNumber", R.string.InvalidPhoneNumber)); + } else if (error.text.contains("PHONE_CODE_EMPTY") || error.text.contains("PHONE_CODE_INVALID")) { + needShowAlert(LocaleController.getString("InvalidCode", R.string.InvalidCode)); + } else if (error.text.contains("PHONE_CODE_EXPIRED")) { + onBackPressed(); + setPage(0, true, null, true); + needShowAlert(LocaleController.getString("CodeExpired", R.string.CodeExpired)); + } else if (error.text.startsWith("FLOOD_WAIT")) { + needShowAlert(LocaleController.getString("FloodWait", R.string.FloodWait)); + } else if (error.code != -1000) { + needShowAlert(LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred) + "\n" + error.text); + } + } + } + needHideProgress(); + } + }); + } + }, ConnectionsManager.RequestFlagFailOnServerErrors); + } + @Override public String getHeaderName() { return LocaleController.getString("YourCode", R.string.YourCode); @@ -857,41 +1076,87 @@ public void setParams(Bundle params) { return; } codeField.setText(""); - AndroidUtilities.setWaitingForSms(true); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.didReceiveSmsCode); + waitingForEvent = true; + if (currentType == 2) { + AndroidUtilities.setWaitingForSms(true); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didReceiveSmsCode); + } else if (currentType == 3) { + AndroidUtilities.setWaitingForCall(true); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didReceiveCall); + } + currentParams = params; - waitingForSms = true; - String phone = params.getString("phone"); + phone = params.getString("phone"); + emailPhone = params.getString("ephone"); requestPhone = params.getString("phoneFormated"); phoneHash = params.getString("phoneHash"); - time = params.getInt("calltime"); + timeout = time = params.getInt("timeout"); + openTime = (int) (System.currentTimeMillis() / 1000); + nextType = params.getInt("nextType"); + pattern = params.getString("pattern"); + length = params.getInt("length"); + + if (length != 0) { + InputFilter[] inputFilters = new InputFilter[1]; + inputFilters[0] = new InputFilter.LengthFilter(length); + codeField.setFilters(inputFilters); + } else { + codeField.setFilters(new InputFilter[0]); + } + if (progressView != null) { + progressView.setVisibility(nextType != 0 ? VISIBLE : GONE); + } if (phone == null) { return; } String number = PhoneFormat.getInstance().format(phone); - String str = String.format(Locale.US, LocaleController.getString("SentSmsCode", R.string.SentSmsCode) + " %s", number); - try { - SpannableStringBuilder stringBuilder = new SpannableStringBuilder(str); - TypefaceSpan span = new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - int idx = str.indexOf(number); - stringBuilder.setSpan(span, idx, idx + number.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - confirmTextView.setText(stringBuilder); - } catch (Exception e) { - FileLog.e("tmessages", e); - confirmTextView.setText(str); + CharSequence str = ""; + if (currentType == 1) { + str = AndroidUtilities.replaceTags(LocaleController.getString("SentAppCode", R.string.SentAppCode)); + } else if (currentType == 2) { + str = AndroidUtilities.replaceTags(LocaleController.formatString("SentSmsCode", R.string.SentSmsCode, number)); + } else if (currentType == 3) { + str = AndroidUtilities.replaceTags(LocaleController.formatString("SentCallCode", R.string.SentCallCode, number)); + } else if (currentType == 4) { + str = AndroidUtilities.replaceTags(LocaleController.formatString("SentCallOnly", R.string.SentCallOnly, number)); } + confirmTextView.setText(str); - AndroidUtilities.showKeyboard(codeField); - codeField.requestFocus(); + if (currentType != 3) { + AndroidUtilities.showKeyboard(codeField); + codeField.requestFocus(); + } else { + AndroidUtilities.hideKeyboard(codeField); + } destroyTimer(); destroyCodeTimer(); - timeText.setText(LocaleController.formatString("CallText", R.string.CallText, 1, 0)); - lastCurrentTime = System.currentTimeMillis(); - createTimer(); + lastCurrentTime = System.currentTimeMillis(); + if (currentType == 1) { + problemText.setVisibility(VISIBLE); + timeText.setVisibility(GONE); + } else if (currentType == 3 && (nextType == 4 || nextType == 2)) { + problemText.setVisibility(GONE); + timeText.setVisibility(VISIBLE); + if (nextType == 4) { + timeText.setText(LocaleController.formatString("CallText", R.string.CallText, 1, 0)); + } else if (nextType == 2) { + timeText.setText(LocaleController.formatString("SmsText", R.string.SmsText, 1, 0)); + } + createTimer(); + } else if (currentType == 2 && nextType == 4) { + timeText.setVisibility(VISIBLE); + timeText.setText(LocaleController.formatString("CallText", R.string.CallText, 2, 0)); + problemText.setVisibility(time < 1000 ? VISIBLE : GONE); + createTimer(); + } else { + timeText.setVisibility(GONE); + problemText.setVisibility(GONE); + createCodeTimer(); + } } private void createCodeTimer() { @@ -912,6 +1177,7 @@ public void run() { @Override public void run() { if (codeTime <= 1000) { + problemText.setVisibility(VISIBLE); destroyCodeTimer(); } } @@ -922,7 +1188,7 @@ public void run() { private void destroyCodeTimer() { try { - synchronized(timerSync) { + synchronized (timerSync) { if (codeTimer != null) { codeTimer.cancel(); codeTimer = null; @@ -941,7 +1207,10 @@ private void createTimer() { timeTimer.schedule(new TimerTask() { @Override public void run() { - double currentTime = System.currentTimeMillis(); + if (timeTimer == null) { + return; + } + final double currentTime = System.currentTimeMillis(); double diff = currentTime - lastCurrentTime; time -= diff; lastCurrentTime = currentTime; @@ -951,27 +1220,45 @@ public void run() { if (time >= 1000) { int minutes = time / 1000 / 60; int seconds = time / 1000 - minutes * 60; - timeText.setText(LocaleController.formatString("CallText", R.string.CallText, minutes, seconds)); + if (nextType == 4) { + timeText.setText(LocaleController.formatString("CallText", R.string.CallText, minutes, seconds)); + } else if (nextType == 2) { + timeText.setText(LocaleController.formatString("SmsText", R.string.SmsText, minutes, seconds)); + } + if (progressView != null) { + progressView.setProgress(1.0f - (float) time / (float) timeout); + } } else { - timeText.setText(LocaleController.getString("Calling", R.string.Calling)); + if (progressView != null) { + progressView.setProgress(1.0f); + } destroyTimer(); - createCodeTimer(); - TLRPC.TL_auth_sendCall req = new TLRPC.TL_auth_sendCall(); - req.phone_number = requestPhone; - req.phone_code_hash = phoneHash; - ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { - @Override - public void run(TLObject response, final TLRPC.TL_error error) { - if (error != null && error.text != null) { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - lastError = error.text; - } - }); + if (currentType == 3) { + AndroidUtilities.setWaitingForCall(false); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveCall); + waitingForEvent = false; + destroyCodeTimer(); + resendCode(); + } else { + timeText.setText(LocaleController.getString("Calling", R.string.Calling)); + createCodeTimer(); + TLRPC.TL_auth_resendCode req = new TLRPC.TL_auth_resendCode(); + req.phone_number = requestPhone; + req.phone_code_hash = phoneHash; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, final TLRPC.TL_error error) { + if (error != null && error.text != null) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + lastError = error.text; + } + }); + } } - } - }, ConnectionsManager.RequestFlagFailOnServerErrors); + }, ConnectionsManager.RequestFlagFailOnServerErrors); + } } } }); @@ -981,7 +1268,7 @@ public void run() { private void destroyTimer() { try { - synchronized(timerSync) { + synchronized (timerSync) { if (timeTimer != null) { timeTimer.cancel(); timeTimer = null; @@ -998,9 +1285,14 @@ public void onNextPressed() { return; } nextPressed = true; - waitingForSms = false; - AndroidUtilities.setWaitingForSms(false); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveSmsCode); + if (currentType == 2) { + AndroidUtilities.setWaitingForSms(false); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveSmsCode); + } else if (currentType == 3) { + AndroidUtilities.setWaitingForCall(false); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveCall); + } + waitingForEvent = false; final TLRPC.TL_account_changePhone req = new TLRPC.TL_account_changePhone(); req.phone_number = requestPhone; req.phone_code = codeField.getText().toString(); @@ -1028,17 +1320,29 @@ public void run() { finishFragment(); } else { lastError = error.text; - createTimer(); - if (error.text.contains("PHONE_NUMBER_INVALID")) { - needShowAlert(LocaleController.getString("InvalidPhoneNumber", R.string.InvalidPhoneNumber)); - } else if (error.text.contains("PHONE_CODE_EMPTY") || error.text.contains("PHONE_CODE_INVALID")) { - needShowAlert(LocaleController.getString("InvalidCode", R.string.InvalidCode)); - } else if (error.text.contains("PHONE_CODE_EXPIRED")) { - needShowAlert(LocaleController.getString("CodeExpired", R.string.CodeExpired)); - } else if (error.text.startsWith("FLOOD_WAIT")) { - needShowAlert(LocaleController.getString("FloodWait", R.string.FloodWait)); - } else { - needShowAlert(error.text); + if (currentType == 3 && (nextType == 4 || nextType == 2) || currentType == 2 && nextType == 4) { + createTimer(); + } + if (currentType == 2) { + AndroidUtilities.setWaitingForSms(true); + NotificationCenter.getInstance().addObserver(LoginActivitySmsView.this, NotificationCenter.didReceiveSmsCode); + } else if (currentType == 3) { + AndroidUtilities.setWaitingForCall(true); + NotificationCenter.getInstance().addObserver(LoginActivitySmsView.this, NotificationCenter.didReceiveCall); + } + waitingForEvent = true; + if (currentType != 3) { + if (error.text.contains("PHONE_NUMBER_INVALID")) { + needShowAlert(LocaleController.getString("InvalidPhoneNumber", R.string.InvalidPhoneNumber)); + } else if (error.text.contains("PHONE_CODE_EMPTY") || error.text.contains("PHONE_CODE_INVALID")) { + needShowAlert(LocaleController.getString("InvalidCode", R.string.InvalidCode)); + } else if (error.text.contains("PHONE_CODE_EXPIRED")) { + needShowAlert(LocaleController.getString("CodeExpired", R.string.CodeExpired)); + } else if (error.text.startsWith("FLOOD_WAIT")) { + needShowAlert(LocaleController.getString("FloodWait", R.string.FloodWait)); + } else { + needShowAlert(error.text); + } } } } @@ -1052,19 +1356,29 @@ public void onBackPressed() { destroyTimer(); destroyCodeTimer(); currentParams = null; - AndroidUtilities.setWaitingForSms(false); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveSmsCode); - waitingForSms = false; + if (currentType == 2) { + AndroidUtilities.setWaitingForSms(false); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveSmsCode); + } else if (currentType == 3) { + AndroidUtilities.setWaitingForCall(false); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveCall); + } + waitingForEvent = false; } @Override public void onDestroyActivity() { super.onDestroyActivity(); - AndroidUtilities.setWaitingForSms(false); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveSmsCode); + if (currentType == 2) { + AndroidUtilities.setWaitingForSms(false); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveSmsCode); + } else if (currentType == 3) { + AndroidUtilities.setWaitingForCall(false); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveCall); + } + waitingForEvent = false; destroyTimer(); destroyCodeTimer(); - waitingForSms = false; } @Override @@ -1078,15 +1392,26 @@ public void onShow() { @Override public void didReceivedNotification(int id, final Object... args) { + if (!waitingForEvent || codeField == null) { + return; + } if (id == NotificationCenter.didReceiveSmsCode) { - if (!waitingForSms) { - return; - } - if (codeField != null) { - ignoreOnTextChange = true; - codeField.setText("" + args[0]); - onNextPressed(); + ignoreOnTextChange = true; + codeField.setText("" + args[0]); + ignoreOnTextChange = false; + onNextPressed(); + } else if (id == NotificationCenter.didReceiveCall) { + String num = "" + args[0]; + if (!pattern.equals("*")) { + String patternNumbers = pattern.replace("*", ""); + if (!num.contains(patternNumbers)) { + return; + } } + ignoreOnTextChange = true; + codeField.setText(num); + ignoreOnTextChange = false; + onNextPressed(); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditActivity.java index 32afbcb167..825fb18e1f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditActivity.java @@ -39,9 +39,6 @@ import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; -import org.telegram.tgnet.ConnectionsManager; -import org.telegram.tgnet.RequestDelegate; -import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; @@ -63,25 +60,19 @@ public class ChannelEditActivity extends BaseFragment implements AvatarUpdater.A private View doneButton; private EditText nameTextView; private EditText descriptionTextView; - private EditText userNameTextView; private BackupImageView avatarImage; private AvatarDrawable avatarDrawable; private AvatarUpdater avatarUpdater; - private TextView checkTextView; - private ProgressDialog progressDialog = null; + private ProgressDialog progressDialog; + private TextSettingsCell typeCell; + private TextSettingsCell adminCell; private TLRPC.FileLocation avatar; - private int checkReqId = 0; - private String lastCheckName = null; - private Runnable checkRunnable = null; - private boolean lastNameAvailable = false; private TLRPC.Chat currentChat; private TLRPC.ChatFull info; private int chatId; private boolean allowComments = true; private TLRPC.InputFile uploadedAvatar; - private boolean wasPrivate; - private boolean privateAlertShown; private boolean signMessages; private boolean createAfterUpload; @@ -131,24 +122,23 @@ public void run() { } } } - wasPrivate = currentChat.username == null || currentChat.username.length() == 0; avatarUpdater.parentFragment = this; avatarUpdater.delegate = this; allowComments = !currentChat.broadcast; signMessages = currentChat.signatures; + NotificationCenter.getInstance().addObserver(this, NotificationCenter.chatInfoDidLoaded); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.updateInterfaces); return super.onFragmentCreate(); } - public void setInfo(TLRPC.ChatFull chatFull) { - info = chatFull; - } - @Override public void onFragmentDestroy() { super.onFragmentDestroy(); if (avatarUpdater != null) { avatarUpdater.clear(); } + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.chatInfoDidLoaded); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.updateInterfaces); AndroidUtilities.removeAdjustResize(getParentActivity(), classGuid); } @@ -172,7 +162,6 @@ public void onItemClick(int id) { if (donePressed) { return; } - donePressed = true; if (nameTextView.length() == 0) { Vibrator v = (Vibrator) getParentActivity().getSystemService(Context.VIBRATOR_SERVICE); if (v != null) { @@ -181,18 +170,7 @@ public void onItemClick(int id) { AndroidUtilities.shakeView(nameTextView, 2, 0); return; } - if (userNameTextView != null) { - if ((currentChat.username == null && userNameTextView.length() != 0) || (currentChat.username != null && !currentChat.username.equalsIgnoreCase(userNameTextView.getText().toString()))) { - if (userNameTextView.length() != 0 && !lastNameAvailable) { - Vibrator v = (Vibrator) getParentActivity().getSystemService(Context.VIBRATOR_SERVICE); - if (v != null) { - v.vibrate(200); - } - AndroidUtilities.shakeView(checkTextView, 2, 0); - return; - } - } - } + donePressed = true; if (avatarUpdater.uploadingAvatar != null) { createAfterUpload = true; @@ -226,12 +204,6 @@ public void onClick(DialogInterface dialog, int which) { if (info != null && !info.about.equals(descriptionTextView.getText().toString())) { MessagesController.getInstance().updateChannelAbout(chatId, descriptionTextView.getText().toString(), info); } - if (userNameTextView != null) { - String oldUserName = currentChat.username != null ? currentChat.username : ""; - if (!oldUserName.equals(userNameTextView.getText().toString())) { - MessagesController.getInstance().updateChannelUserName(chatId, userNameTextView.getText().toString()); - } - } if (signMessages != currentChat.signatures) { currentChat.signatures = true; MessagesController.getInstance().toogleChannelSignatures(chatId, signMessages); @@ -346,8 +318,9 @@ public void afterTextChanged(Editable s) { } }); - ShadowSectionCell sectionCell = new ShadowSectionCell(context); - linearLayout.addView(sectionCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + View lineView = new View(context); + lineView.setBackgroundColor(0xffcfcfcf); + linearLayout.addView(lineView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1)); linearLayout2 = new LinearLayout(context); linearLayout2.setOrientation(LinearLayout.VERTICAL); @@ -355,7 +328,7 @@ public void afterTextChanged(Editable s) { linearLayout.addView(linearLayout2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); descriptionTextView = new EditText(context); - descriptionTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + descriptionTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); descriptionTextView.setHintTextColor(0xff979797); descriptionTextView.setTextColor(0xff212121); descriptionTextView.setPadding(0, 0, 0, AndroidUtilities.dp(6)); @@ -364,9 +337,9 @@ public void afterTextChanged(Editable s) { descriptionTextView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); descriptionTextView.setImeOptions(EditorInfo.IME_ACTION_DONE); inputFilters = new InputFilter[1]; - inputFilters[0] = new InputFilter.LengthFilter(120); + inputFilters[0] = new InputFilter.LengthFilter(255); descriptionTextView.setFilters(inputFilters); - descriptionTextView.setHint(LocaleController.getString("DescriptionPlaceholder", R.string.DescriptionPlaceholder)); + descriptionTextView.setHint(LocaleController.getString("DescriptionOptionalPlaceholder", R.string.DescriptionOptionalPlaceholder)); AndroidUtilities.clearCursorDrawable(descriptionTextView); linearLayout2.addView(descriptionTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 17, 12, 17, 6)); descriptionTextView.setOnEditorActionListener(new TextView.OnEditorActionListener() { @@ -396,148 +369,68 @@ public void afterTextChanged(Editable editable) { } }); - TextInfoPrivacyCell infoCell = new TextInfoPrivacyCell(context); - if (currentChat.megagroup) { - infoCell.setText(LocaleController.getString("DescriptionInfoMega", R.string.DescriptionInfoMega)); - infoCell.setBackgroundResource(currentChat.creator ? R.drawable.greydivider : R.drawable.greydivider_bottom); - } else { - infoCell.setText(LocaleController.getString("DescriptionInfo", R.string.DescriptionInfo)); - infoCell.setBackgroundResource(R.drawable.greydivider); - } - linearLayout.addView(infoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - - if (/*BuildVars.DEBUG_VERSION && currentChat.megagroup && currentChat.creator || */!currentChat.megagroup) { - linearLayout2 = new LinearLayout(context); - linearLayout2.setOrientation(LinearLayout.VERTICAL); - linearLayout2.setBackgroundColor(0xffffffff); - linearLayout2.setPadding(0, 0, 0, AndroidUtilities.dp(7)); - linearLayout.addView(linearLayout2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - - LinearLayout publicContainer = new LinearLayout(context); - publicContainer.setOrientation(LinearLayout.HORIZONTAL); - linearLayout2.addView(publicContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 17, 7, 17, 0)); - - EditText editText = new EditText(context); - editText.setText("telegram.me/"); - editText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - editText.setHintTextColor(0xff979797); - editText.setTextColor(0xff212121); - editText.setMaxLines(1); - editText.setLines(1); - editText.setEnabled(false); - editText.setBackgroundDrawable(null); - editText.setPadding(0, 0, 0, 0); - editText.setSingleLine(true); - editText.setInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); - editText.setImeOptions(EditorInfo.IME_ACTION_DONE); - publicContainer.addView(editText, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, 36)); - - userNameTextView = new EditText(context); - userNameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - userNameTextView.setHintTextColor(0xff979797); - userNameTextView.setTextColor(0xff212121); - userNameTextView.setMaxLines(1); - userNameTextView.setLines(1); - userNameTextView.setBackgroundDrawable(null); - userNameTextView.setPadding(0, 0, 0, 0); - userNameTextView.setSingleLine(true); - userNameTextView.setText(currentChat.username); - userNameTextView.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); - userNameTextView.setImeOptions(EditorInfo.IME_ACTION_DONE); - userNameTextView.setHint(LocaleController.getString("ChannelUsernamePlaceholder", R.string.ChannelUsernamePlaceholder)); - userNameTextView.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (wasPrivate && hasFocus && !privateAlertShown) { - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - if (currentChat.megagroup) { - //builder.setMessage(LocaleController.getString("MegaWasPrivateAlert", R.string.MegaWasPrivateAlert)); - } else { - builder.setMessage(LocaleController.getString("ChannelWasPrivateAlert", R.string.ChannelWasPrivateAlert)); - } - builder.setPositiveButton(LocaleController.getString("Close", R.string.Close), null); - showDialog(builder.create()); - } - } - }); - AndroidUtilities.clearCursorDrawable(userNameTextView); - publicContainer.addView(userNameTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36)); - userNameTextView.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { - checkUserName(userNameTextView.getText().toString(), false); - } - - @Override - public void afterTextChanged(Editable editable) { - - } - }); - - checkTextView = new TextView(context); - checkTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); - checkTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); - checkTextView.setVisibility(View.GONE); - linearLayout2.addView(checkTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 17, 3, 17, 7)); - - infoCell = new TextInfoPrivacyCell(context); - infoCell.setBackgroundResource(R.drawable.greydivider); - if (currentChat.megagroup) { - //infoCell.setText(LocaleController.getString("MegaUsernameHelp", R.string.MegaUsernameHelp)); - } else { - infoCell.setText(LocaleController.getString("ChannelUsernameHelp", R.string.ChannelUsernameHelp)); - } - linearLayout.addView(infoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - } + ShadowSectionCell sectionCell = new ShadowSectionCell(context); + sectionCell.setSize(20); + linearLayout.addView(sectionCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - /*frameLayout = new FrameLayoutFixed(context); - frameLayout.setBackgroundColor(0xffffffff); - linearLayout.addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + if (currentChat.megagroup || !currentChat.megagroup) { + frameLayout = new FrameLayoutFixed(context); + frameLayout.setBackgroundColor(0xffffffff); + linearLayout.addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - TextCheckCell commentsCell = new TextCheckCell(context); - commentsCell.setTextAndCheck(LocaleController.getString("Comments", R.string.Comments), allowComments, false); - commentsCell.setBackgroundResource(R.drawable.list_selector); - frameLayout.addView(commentsCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - commentsCell.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - allowComments = !allowComments; - ((TextCheckCell) v).setChecked(allowComments); - } - }); + typeCell = new TextSettingsCell(context); + updateTypeCell(); + typeCell.setBackgroundResource(R.drawable.list_selector); + frameLayout.addView(typeCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + //TODO - infoCell = new TextInfoPrivacyCell(context); - infoCell.setText(LocaleController.getString("CommentsInfo", R.string.CommentsInfo)); - infoCell.setBackgroundResource(R.drawable.greydivider); - linearLayout.addView(infoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));*/ + lineView = new View(context); + lineView.setBackgroundColor(0xffcfcfcf); + linearLayout.addView(lineView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1)); - if (!currentChat.megagroup && currentChat.creator) { frameLayout = new FrameLayoutFixed(context); frameLayout.setBackgroundColor(0xffffffff); linearLayout.addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - TextCheckCell textCell = new TextCheckCell(context); - textCell.setBackgroundResource(R.drawable.list_selector); - textCell.setTextAndCheck(LocaleController.getString("ChannelSignMessages", R.string.ChannelSignMessages), signMessages, false); - frameLayout.addView(textCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - textCell.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - signMessages = !signMessages; - ((TextCheckCell) v).setChecked(signMessages); - } - }); + if (!currentChat.megagroup) { + TextCheckCell textCheckCell = new TextCheckCell(context); + textCheckCell.setBackgroundResource(R.drawable.list_selector); + textCheckCell.setTextAndCheck(LocaleController.getString("ChannelSignMessages", R.string.ChannelSignMessages), signMessages, false); + frameLayout.addView(textCheckCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + textCheckCell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + signMessages = !signMessages; + ((TextCheckCell) v).setChecked(signMessages); + } + }); - infoCell = new TextInfoPrivacyCell(context); - infoCell.setBackgroundResource(R.drawable.greydivider); - infoCell.setText(LocaleController.getString("ChannelSignMessagesInfo", R.string.ChannelSignMessagesInfo)); - linearLayout.addView(infoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + TextInfoPrivacyCell infoCell = new TextInfoPrivacyCell(context); + infoCell.setBackgroundResource(R.drawable.greydivider); + infoCell.setText(LocaleController.getString("ChannelSignMessagesInfo", R.string.ChannelSignMessagesInfo)); + linearLayout.addView(infoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } else { + adminCell = new TextSettingsCell(context); + updateAdminCell(); + adminCell.setBackgroundResource(R.drawable.list_selector); + frameLayout.addView(adminCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + adminCell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Bundle args = new Bundle(); + args.putInt("chat_id", chatId); + args.putInt("type", 1); + presentFragment(new ChannelUsersActivity(args)); + } + }); + + sectionCell = new ShadowSectionCell(context); + sectionCell.setSize(20); + linearLayout.addView(sectionCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + if (!currentChat.creator) { + sectionCell.setBackgroundResource(R.drawable.greydivider_bottom); + } + } } if (currentChat.creator) { @@ -582,7 +475,7 @@ public void onClick(DialogInterface dialogInterface, int i) { } }); - infoCell = new TextInfoPrivacyCell(context); + TextInfoPrivacyCell infoCell = new TextInfoPrivacyCell(context); infoCell.setBackgroundResource(R.drawable.greydivider_bottom); if (currentChat.megagroup) { infoCell.setText(LocaleController.getString("MegaDeleteInfo", R.string.MegaDeleteInfo)); @@ -592,6 +485,27 @@ public void onClick(DialogInterface dialogInterface, int i) { linearLayout.addView(infoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); } + /*frameLayout = new FrameLayoutFixed(context); + frameLayout.setBackgroundColor(0xffffffff); + linearLayout.addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + TextCheckCell commentsCell = new TextCheckCell(context); + commentsCell.setTextAndCheck(LocaleController.getString("Comments", R.string.Comments), allowComments, false); + commentsCell.setBackgroundResource(R.drawable.list_selector); + frameLayout.addView(commentsCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + commentsCell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + allowComments = !allowComments; + ((TextCheckCell) v).setChecked(allowComments); + } + }); + + infoCell = new TextInfoPrivacyCell(context); + infoCell.setText(LocaleController.getString("CommentsInfo", R.string.CommentsInfo)); + infoCell.setBackgroundResource(R.drawable.greydivider); + linearLayout.addView(infoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));*/ + nameTextView.setText(currentChat.title); nameTextView.setSelection(nameTextView.length()); if (info != null) { @@ -616,6 +530,13 @@ public void didReceivedNotification(int id, Object... args) { descriptionTextView.setText(chatFull.about); } info = chatFull; + updateAdminCell(); + updateTypeCell(); + } + } else if (id == NotificationCenter.updateInterfaces) { + int updateMask = (Integer) args[0]; + if ((updateMask & MessagesController.UPDATE_MASK_CHANNEL) != 0) { + updateTypeCell(); } } } @@ -668,153 +589,46 @@ public void restoreSelfArgs(Bundle args) { } } - private boolean checkUserName(final String name, boolean alert) { - if (name != null && name.length() > 0) { - checkTextView.setVisibility(View.VISIBLE); + public void setInfo(TLRPC.ChatFull chatFull) { + info = chatFull; + } + + private void updateTypeCell() { + String type = currentChat.username == null || currentChat.username.length() == 0 ? LocaleController.getString("ChannelTypePrivate", R.string.ChannelTypePrivate) : LocaleController.getString("ChannelTypePublic", R.string.ChannelTypePublic); + if (currentChat.megagroup) { + typeCell.setTextAndValue(LocaleController.getString("GroupType", R.string.GroupType), type, false); } else { - checkTextView.setVisibility(View.GONE); - } - if (alert && name.length() == 0) { - return true; - } - if (checkRunnable != null) { - AndroidUtilities.cancelRunOnUIThread(checkRunnable); - checkRunnable = null; - lastCheckName = null; - if (checkReqId != 0) { - ConnectionsManager.getInstance().cancelRequest(checkReqId, true); - } - } - lastNameAvailable = false; - if (name != null) { - if (name.startsWith("_") || name.endsWith("_")) { - checkTextView.setText(LocaleController.getString("LinkInvalid", R.string.LinkInvalid)); - checkTextView.setTextColor(0xffcf3030); - return false; - } - for (int a = 0; a < name.length(); a++) { - char ch = name.charAt(a); - if (a == 0 && ch >= '0' && ch <= '9') { - if (currentChat.megagroup) { - if (alert) { - //showErrorAlert(LocaleController.getString("LinkInvalidStartNumberMega", R.string.LinkInvalidStartNumberMega)); - } else { - //checkTextView.setText(LocaleController.getString("LinkInvalidStartNumberMega", R.string.LinkInvalidStartNumberMega)); - checkTextView.setTextColor(0xffcf3030); - } - } else { - if (alert) { - showErrorAlert(LocaleController.getString("LinkInvalidStartNumber", R.string.LinkInvalidStartNumber)); - } else { - checkTextView.setText(LocaleController.getString("LinkInvalidStartNumber", R.string.LinkInvalidStartNumber)); - checkTextView.setTextColor(0xffcf3030); - } - } - return false; - } - if (!(ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_')) { - if (alert) { - showErrorAlert(LocaleController.getString("LinkInvalid", R.string.LinkInvalid)); - } else { - checkTextView.setText(LocaleController.getString("LinkInvalid", R.string.LinkInvalid)); - checkTextView.setTextColor(0xffcf3030); - } - return false; - } - } - } - if (name == null || name.length() < 5) { - if (currentChat.megagroup) { - if (alert) { - //showErrorAlert(LocaleController.getString("LinkInvalidShortMega", R.string.LinkInvalidShortMega)); - } else { - //checkTextView.setText(LocaleController.getString("LinkInvalidShortMega", R.string.LinkInvalidShortMega)); - checkTextView.setTextColor(0xffcf3030); - } - } else { - if (alert) { - showErrorAlert(LocaleController.getString("LinkInvalidShort", R.string.LinkInvalidShort)); - } else { - checkTextView.setText(LocaleController.getString("LinkInvalidShort", R.string.LinkInvalidShort)); - checkTextView.setTextColor(0xffcf3030); - } - } - return false; - } - if (name.length() > 32) { - if (alert) { - showErrorAlert(LocaleController.getString("LinkInvalidLong", R.string.LinkInvalidLong)); - } else { - checkTextView.setText(LocaleController.getString("LinkInvalidLong", R.string.LinkInvalidLong)); - checkTextView.setTextColor(0xffcf3030); - } - return false; + typeCell.setTextAndValue(LocaleController.getString("ChannelType", R.string.ChannelType), type, false); } - if (!alert) { - checkTextView.setText(LocaleController.getString("LinkChecking", R.string.LinkChecking)); - checkTextView.setTextColor(0xff6d6d72); - lastCheckName = name; - checkRunnable = new Runnable() { + if (currentChat.creator && (info == null || info.can_set_username)) { + typeCell.setOnClickListener(new View.OnClickListener() { @Override - public void run() { - TLRPC.TL_channels_checkUsername req = new TLRPC.TL_channels_checkUsername(); - req.username = name; - req.channel = MessagesController.getInputChannel(chatId); - checkReqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { - @Override - public void run(final TLObject response, final TLRPC.TL_error error) { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - checkReqId = 0; - if (lastCheckName != null && lastCheckName.equals(name)) { - if (error == null && response instanceof TLRPC.TL_boolTrue) { - checkTextView.setText(LocaleController.formatString("LinkAvailable", R.string.LinkAvailable, name)); - checkTextView.setTextColor(0xff26972c); - lastNameAvailable = true; - } else { - if (error != null && error.text.equals("CHANNELS_ADMIN_PUBLIC_TOO_MUCH")) { - checkTextView.setText(LocaleController.getString("ChannelPublicLimitReached", R.string.ChannelPublicLimitReached)); - } else { - checkTextView.setText(LocaleController.getString("LinkInUse", R.string.LinkInUse)); - } - checkTextView.setTextColor(0xffcf3030); - lastNameAvailable = false; - } - } - } - }); - } - }, ConnectionsManager.RequestFlagFailOnServerErrors); + public void onClick(View v) { + Bundle args = new Bundle(); + args.putInt("chat_id", chatId); + ChannelEditTypeActivity fragment = new ChannelEditTypeActivity(args); + fragment.setInfo(info); + presentFragment(fragment); } - }; - AndroidUtilities.runOnUIThread(checkRunnable, 300); + }); + typeCell.setTextColor(0xff212121); + typeCell.setTextValueColor(0xff2f8cc9); + } else { + typeCell.setOnClickListener(null); + typeCell.setTextColor(0xffa8a8a8); + typeCell.setTextValueColor(0xffa8a8a8); } - return true; } - private void showErrorAlert(String error) { - if (getParentActivity() == null) { + private void updateAdminCell() { + if (adminCell == null) { return; } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - switch (error) { - case "USERNAME_INVALID": - builder.setMessage(LocaleController.getString("LinkInvalid", R.string.LinkInvalid)); - break; - case "USERNAME_OCCUPIED": - builder.setMessage(LocaleController.getString("LinkInUse", R.string.LinkInUse)); - break; - case "USERNAMES_UNAVAILABLE": - builder.setMessage(LocaleController.getString("FeatureUnavailable", R.string.FeatureUnavailable)); - break; - default: - builder.setMessage(LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred)); - break; + if (info != null) { + adminCell.setTextAndValue(LocaleController.getString("ChannelAdministrators", R.string.ChannelAdministrators), String.format("%d", info.admins_count), false); + } else { + adminCell.setText(LocaleController.getString("ChannelAdministrators", R.string.ChannelAdministrators), false); } - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); - showDialog(builder.create()); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditTypeActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditTypeActivity.java new file mode 100644 index 0000000000..a389ec5cf3 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditTypeActivity.java @@ -0,0 +1,544 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2016. + */ + +package org.telegram.ui; + +import android.app.AlertDialog; +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.os.Vibrator; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; +import android.widget.Toast; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.MessagesStorage; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.RequestDelegate; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.Cells.HeaderCell; +import org.telegram.ui.Cells.RadioButtonCell; +import org.telegram.ui.Cells.ShadowSectionCell; +import org.telegram.ui.Cells.TextBlockCell; +import org.telegram.ui.Cells.TextInfoPrivacyCell; +import org.telegram.ui.Components.LayoutHelper; + +import java.util.concurrent.Semaphore; + +public class ChannelEditTypeActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { + + private LinearLayout linkContainer; + private LinearLayout publicContainer; + private TextBlockCell privateContainer; + private RadioButtonCell radioButtonCell1; + private RadioButtonCell radioButtonCell2; + private TextInfoPrivacyCell typeInfoCell; + private TextView checkTextView; + private HeaderCell headerCell; + private EditText nameTextView; + private boolean isPrivate = false; + private boolean loadingInvite; + private TLRPC.ExportedChatInvite invite; + + private int checkReqId = 0; + private String lastCheckName = null; + private Runnable checkRunnable = null; + private boolean lastNameAvailable = false; + private TLRPC.Chat currentChat; + private int chatId; + + private boolean donePressed; + + private final static int done_button = 1; + + public ChannelEditTypeActivity(Bundle args) { + super(args); + chatId = args.getInt("chat_id", 0); + } + + @SuppressWarnings("unchecked") + @Override + public boolean onFragmentCreate() { + currentChat = MessagesController.getInstance().getChat(chatId); + if (currentChat == null) { + final Semaphore semaphore = new Semaphore(0); + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + currentChat = MessagesStorage.getInstance().getChat(chatId); + semaphore.release(); + } + }); + try { + semaphore.acquire(); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + if (currentChat != null) { + MessagesController.getInstance().putChat(currentChat, true); + } else { + return false; + } + } + isPrivate = currentChat.username == null || currentChat.username.length() == 0; + NotificationCenter.getInstance().addObserver(this, NotificationCenter.chatInfoDidLoaded); + return super.onFragmentCreate(); + } + + @Override + public void onFragmentDestroy() { + super.onFragmentDestroy(); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.chatInfoDidLoaded); + AndroidUtilities.removeAdjustResize(getParentActivity(), classGuid); + } + + @Override + public void onResume() { + super.onResume(); + AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid); + } + + @Override + public View createView(Context context) { + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setAllowOverlayTitle(true); + + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } else if (id == done_button) { + if (donePressed) { + return; + } + + if (!isPrivate && ((currentChat.username == null && nameTextView.length() != 0) || (currentChat.username != null && !currentChat.username.equalsIgnoreCase(nameTextView.getText().toString())))) { + if (nameTextView.length() != 0 && !lastNameAvailable) { + Vibrator v = (Vibrator) getParentActivity().getSystemService(Context.VIBRATOR_SERVICE); + if (v != null) { + v.vibrate(200); + } + AndroidUtilities.shakeView(checkTextView, 2, 0); + return; + } + } + donePressed = true; + + String oldUserName = currentChat.username != null ? currentChat.username : ""; + String newUserName = isPrivate ? "" : nameTextView.getText().toString(); + if (!oldUserName.equals(newUserName)) { + MessagesController.getInstance().updateChannelUserName(chatId, newUserName); + } + finishFragment(); + } + } + }); + + ActionBarMenu menu = actionBar.createMenu(); + menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); + + LinearLayout linearLayout; + + fragmentView = new ScrollView(context); + fragmentView.setBackgroundColor(0xfff0f0f0); + ScrollView scrollView = (ScrollView) fragmentView; + scrollView.setFillViewport(true); + linearLayout = new LinearLayout(context); + scrollView.addView(linearLayout, new ScrollView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + linearLayout.setOrientation(LinearLayout.VERTICAL); + + if (currentChat.megagroup) { + actionBar.setTitle(LocaleController.getString("GroupType", R.string.GroupType)); + } else { + actionBar.setTitle(LocaleController.getString("ChannelType", R.string.ChannelType)); + } + + LinearLayout linearLayout2 = new LinearLayout(context); + linearLayout2.setOrientation(LinearLayout.VERTICAL); + linearLayout2.setBackgroundColor(0xffffffff); + linearLayout.addView(linearLayout2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + radioButtonCell1 = new RadioButtonCell(context); + radioButtonCell1.setBackgroundResource(R.drawable.list_selector); + if (currentChat.megagroup) { + radioButtonCell1.setTextAndValue(LocaleController.getString("MegaPublic", R.string.MegaPublic), LocaleController.getString("MegaPublicInfo", R.string.MegaPublicInfo), !isPrivate, false); + } else { + radioButtonCell1.setTextAndValue(LocaleController.getString("ChannelPublic", R.string.ChannelPublic), LocaleController.getString("ChannelPublicInfo", R.string.ChannelPublicInfo), !isPrivate, false); + } + linearLayout2.addView(radioButtonCell1, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + radioButtonCell1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!isPrivate) { + return; + } + isPrivate = false; + updatePrivatePublic(); + } + }); + + radioButtonCell2 = new RadioButtonCell(context); + radioButtonCell2.setBackgroundResource(R.drawable.list_selector); + if (currentChat.megagroup) { + radioButtonCell2.setTextAndValue(LocaleController.getString("MegaPrivate", R.string.MegaPrivate), LocaleController.getString("MegaPrivateInfo", R.string.MegaPrivateInfo), isPrivate, false); + } else { + radioButtonCell2.setTextAndValue(LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate), LocaleController.getString("ChannelPrivateInfo", R.string.ChannelPrivateInfo), isPrivate, false); + } + linearLayout2.addView(radioButtonCell2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + radioButtonCell2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (isPrivate) { + return; + } + isPrivate = true; + updatePrivatePublic(); + } + }); + + ShadowSectionCell sectionCell = new ShadowSectionCell(context); + linearLayout.addView(sectionCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + linkContainer = new LinearLayout(context); + linkContainer.setOrientation(LinearLayout.VERTICAL); + linkContainer.setBackgroundColor(0xffffffff); + linearLayout.addView(linkContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + headerCell = new HeaderCell(context); + linkContainer.addView(headerCell); + + publicContainer = new LinearLayout(context); + publicContainer.setOrientation(LinearLayout.HORIZONTAL); + linkContainer.addView(publicContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 17, 7, 17, 0)); + + EditText editText = new EditText(context); + editText.setText("telegram.me/"); + editText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + editText.setHintTextColor(0xff979797); + editText.setTextColor(0xff212121); + editText.setMaxLines(1); + editText.setLines(1); + editText.setEnabled(false); + editText.setBackgroundDrawable(null); + editText.setPadding(0, 0, 0, 0); + editText.setSingleLine(true); + editText.setInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); + editText.setImeOptions(EditorInfo.IME_ACTION_DONE); + publicContainer.addView(editText, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, 36)); + + nameTextView = new EditText(context); + nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + if (!isPrivate) { + nameTextView.setText(currentChat.username); + } + nameTextView.setHintTextColor(0xff979797); + nameTextView.setTextColor(0xff212121); + nameTextView.setMaxLines(1); + nameTextView.setLines(1); + nameTextView.setBackgroundDrawable(null); + nameTextView.setPadding(0, 0, 0, 0); + nameTextView.setSingleLine(true); + nameTextView.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); + nameTextView.setImeOptions(EditorInfo.IME_ACTION_DONE); + nameTextView.setHint(LocaleController.getString("ChannelUsernamePlaceholder", R.string.ChannelUsernamePlaceholder)); + AndroidUtilities.clearCursorDrawable(nameTextView); + publicContainer.addView(nameTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36)); + nameTextView.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + checkUserName(nameTextView.getText().toString(), false); + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); + + privateContainer = new TextBlockCell(context); + privateContainer.setBackgroundResource(R.drawable.list_selector); + linkContainer.addView(privateContainer); + privateContainer.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (invite == null) { + return; + } + try { + if (Build.VERSION.SDK_INT < 11) { + android.text.ClipboardManager clipboard = (android.text.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); + clipboard.setText(invite.link); + } else { + android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); + android.content.ClipData clip = android.content.ClipData.newPlainText("label", invite.link); + clipboard.setPrimaryClip(clip); + } + Toast.makeText(getParentActivity(), LocaleController.getString("LinkCopied", R.string.LinkCopied), Toast.LENGTH_SHORT).show(); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + }); + + checkTextView = new TextView(context); + checkTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + checkTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + checkTextView.setVisibility(View.GONE); + linkContainer.addView(checkTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 17, 3, 17, 7)); + + typeInfoCell = new TextInfoPrivacyCell(context); + typeInfoCell.setBackgroundResource(R.drawable.greydivider_bottom); + linearLayout.addView(typeInfoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + updatePrivatePublic(); + + return fragmentView; + } + + @Override + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.chatInfoDidLoaded) { + TLRPC.ChatFull chatFull = (TLRPC.ChatFull) args[0]; + if (chatFull.id == chatId) { + invite = chatFull.exported_invite; + updatePrivatePublic(); + } + } + } + + public void setInfo(TLRPC.ChatFull chatFull) { + if (chatFull != null) { + if (chatFull.exported_invite instanceof TLRPC.TL_chatInviteExported) { + invite = chatFull.exported_invite; + } else { + generateLink(); + } + } + } + + private void updatePrivatePublic() { + radioButtonCell1.setChecked(!isPrivate, true); + radioButtonCell2.setChecked(isPrivate, true); + if (currentChat.megagroup) { + typeInfoCell.setText(isPrivate ? LocaleController.getString("MegaPrivateLinkHelp", R.string.MegaPrivateLinkHelp) : LocaleController.getString("MegaUsernameHelp", R.string.MegaUsernameHelp)); + headerCell.setText(isPrivate ? LocaleController.getString("ChannelInviteLinkTitle", R.string.ChannelInviteLinkTitle) : LocaleController.getString("ChannelLinkTitle", R.string.ChannelLinkTitle)); + } else { + typeInfoCell.setText(isPrivate ? LocaleController.getString("ChannelPrivateLinkHelp", R.string.ChannelPrivateLinkHelp) : LocaleController.getString("ChannelUsernameHelp", R.string.ChannelUsernameHelp)); + headerCell.setText(isPrivate ? LocaleController.getString("ChannelInviteLinkTitle", R.string.ChannelInviteLinkTitle) : LocaleController.getString("ChannelLinkTitle", R.string.ChannelLinkTitle)); + } + publicContainer.setVisibility(isPrivate ? View.GONE : View.VISIBLE); + privateContainer.setVisibility(isPrivate ? View.VISIBLE : View.GONE); + linkContainer.setPadding(0, 0, 0, isPrivate ? 0 : AndroidUtilities.dp(7)); + privateContainer.setText(invite != null ? invite.link : LocaleController.getString("Loading", R.string.Loading), false); + nameTextView.clearFocus(); + checkTextView.setVisibility(!isPrivate && checkTextView.length() != 0 ? View.VISIBLE : View.GONE); + AndroidUtilities.hideKeyboard(nameTextView); + } + + private boolean checkUserName(final String name, boolean alert) { + if (name != null && name.length() > 0) { + checkTextView.setVisibility(View.VISIBLE); + } else { + checkTextView.setVisibility(View.GONE); + } + if (alert && name.length() == 0) { + return true; + } + if (checkRunnable != null) { + AndroidUtilities.cancelRunOnUIThread(checkRunnable); + checkRunnable = null; + lastCheckName = null; + if (checkReqId != 0) { + ConnectionsManager.getInstance().cancelRequest(checkReqId, true); + } + } + lastNameAvailable = false; + if (name != null) { + if (name.startsWith("_") || name.endsWith("_")) { + checkTextView.setText(LocaleController.getString("LinkInvalid", R.string.LinkInvalid)); + checkTextView.setTextColor(0xffcf3030); + return false; + } + for (int a = 0; a < name.length(); a++) { + char ch = name.charAt(a); + if (a == 0 && ch >= '0' && ch <= '9') { + if (currentChat.megagroup) { + if (alert) { + showErrorAlert(LocaleController.getString("LinkInvalidStartNumberMega", R.string.LinkInvalidStartNumberMega)); + } else { + checkTextView.setText(LocaleController.getString("LinkInvalidStartNumberMega", R.string.LinkInvalidStartNumberMega)); + checkTextView.setTextColor(0xffcf3030); + } + } else { + if (alert) { + showErrorAlert(LocaleController.getString("LinkInvalidStartNumber", R.string.LinkInvalidStartNumber)); + } else { + checkTextView.setText(LocaleController.getString("LinkInvalidStartNumber", R.string.LinkInvalidStartNumber)); + checkTextView.setTextColor(0xffcf3030); + } + } + return false; + } + if (!(ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_')) { + if (alert) { + showErrorAlert(LocaleController.getString("LinkInvalid", R.string.LinkInvalid)); + } else { + checkTextView.setText(LocaleController.getString("LinkInvalid", R.string.LinkInvalid)); + checkTextView.setTextColor(0xffcf3030); + } + return false; + } + } + } + if (name == null || name.length() < 5) { + if (currentChat.megagroup) { + if (alert) { + showErrorAlert(LocaleController.getString("LinkInvalidShortMega", R.string.LinkInvalidShortMega)); + } else { + checkTextView.setText(LocaleController.getString("LinkInvalidShortMega", R.string.LinkInvalidShortMega)); + checkTextView.setTextColor(0xffcf3030); + } + } else { + if (alert) { + showErrorAlert(LocaleController.getString("LinkInvalidShort", R.string.LinkInvalidShort)); + } else { + checkTextView.setText(LocaleController.getString("LinkInvalidShort", R.string.LinkInvalidShort)); + checkTextView.setTextColor(0xffcf3030); + } + } + return false; + } + if (name.length() > 32) { + if (alert) { + showErrorAlert(LocaleController.getString("LinkInvalidLong", R.string.LinkInvalidLong)); + } else { + checkTextView.setText(LocaleController.getString("LinkInvalidLong", R.string.LinkInvalidLong)); + checkTextView.setTextColor(0xffcf3030); + } + return false; + } + + if (!alert) { + checkTextView.setText(LocaleController.getString("LinkChecking", R.string.LinkChecking)); + checkTextView.setTextColor(0xff6d6d72); + lastCheckName = name; + checkRunnable = new Runnable() { + @Override + public void run() { + TLRPC.TL_channels_checkUsername req = new TLRPC.TL_channels_checkUsername(); + req.username = name; + req.channel = MessagesController.getInputChannel(chatId); + checkReqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + checkReqId = 0; + if (lastCheckName != null && lastCheckName.equals(name)) { + if (error == null && response instanceof TLRPC.TL_boolTrue) { + checkTextView.setText(LocaleController.formatString("LinkAvailable", R.string.LinkAvailable, name)); + checkTextView.setTextColor(0xff26972c); + lastNameAvailable = true; + } else { + if (error != null && error.text.equals("CHANNELS_ADMIN_PUBLIC_TOO_MUCH")) { + checkTextView.setText(LocaleController.getString("ChangePublicLimitReached", R.string.ChangePublicLimitReached)); + } else { + checkTextView.setText(LocaleController.getString("LinkInUse", R.string.LinkInUse)); + } + checkTextView.setTextColor(0xffcf3030); + lastNameAvailable = false; + } + } + } + }); + } + }, ConnectionsManager.RequestFlagFailOnServerErrors); + } + }; + AndroidUtilities.runOnUIThread(checkRunnable, 300); + } + return true; + } + + private void generateLink() { + if (loadingInvite || invite != null) { + return; + } + loadingInvite = true; + TLRPC.TL_channels_exportInvite req = new TLRPC.TL_channels_exportInvite(); + req.channel = MessagesController.getInputChannel(chatId); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (error == null) { + invite = (TLRPC.ExportedChatInvite) response; + } + loadingInvite = false; + privateContainer.setText(invite != null ? invite.link : LocaleController.getString("Loading", R.string.Loading), false); + } + }); + } + }); + } + + private void showErrorAlert(String error) { + if (getParentActivity() == null) { + return; + } + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + switch (error) { + case "USERNAME_INVALID": + builder.setMessage(LocaleController.getString("LinkInvalid", R.string.LinkInvalid)); + break; + case "USERNAME_OCCUPIED": + builder.setMessage(LocaleController.getString("LinkInUse", R.string.LinkInUse)); + break; + case "USERNAMES_UNAVAILABLE": + builder.setMessage(LocaleController.getString("FeatureUnavailable", R.string.FeatureUnavailable)); + break; + default: + builder.setMessage(LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred)); + break; + } + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + showDialog(builder.create()); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelUsersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelUsersActivity.java index 736be77692..fe51b1ce5c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelUsersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelUsersActivity.java @@ -560,7 +560,7 @@ public View getView(int i, View view, ViewGroup viewGroup) { int viewType = getItemViewType(i); if (viewType == 0) { if (view == null) { - view = new UserCell(mContext, 1, 0); + view = new UserCell(mContext, 1, 0, false); view.setBackgroundColor(0xffffffff); } UserCell userCell = (UserCell) view; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java index 9cd8bbc006..899037f597 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java @@ -41,6 +41,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.view.WindowManager; import android.webkit.MimeTypeMap; import android.widget.EditText; import android.widget.FrameLayout; @@ -66,7 +67,7 @@ import org.telegram.messenger.VideoEditedInfo; import org.telegram.messenger.query.BotQuery; import org.telegram.messenger.query.MessagesSearchQuery; -import org.telegram.messenger.query.ReplyMessageQuery; +import org.telegram.messenger.query.MessagesQuery; import org.telegram.messenger.query.StickersQuery; import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.messenger.support.widget.RecyclerView; @@ -98,13 +99,13 @@ import org.telegram.ui.Cells.ChatBaseCell; import org.telegram.ui.Cells.ChatContactCell; import org.telegram.ui.Cells.ChatLoadingCell; -import org.telegram.ui.Cells.ChatMediaCell; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; import org.telegram.ui.Cells.ChatMessageCell; import org.telegram.ui.Cells.ChatMusicCell; import org.telegram.ui.Cells.ChatUnreadCell; +import org.telegram.ui.Cells.CheckBoxCell; import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; @@ -149,7 +150,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private boolean userBlocked = false; private ArrayList chatMessageCellsCache = new ArrayList<>(); - private ArrayList chatMediaCellsCache = new ArrayList<>(); private Dialog closeChatDialog; private FrameLayout progressView; @@ -201,23 +201,31 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private ChatAttachView chatAttachView; private BottomSheet chatAttachViewSheet; private LinearLayout reportSpamView; + private AnimatorSetProxy reportSpamViewAnimator; private TextView addToContactsButton; private TextView reportSpamButton; private FrameLayout reportSpamContainer; private PlayerView playerView; private TextView gifHintTextView; private View emojiButtonRed; + private FrameLayout pinnedMessageView; + private AnimatorSetProxy pinnedMessageViewAnimator; + private TextView pinnedMessageNameTextView; + private TextView pinnedMessageTextView; + + private MessageObject pinnedMessageObject; + private int loadingPinnedMessage; private ObjectAnimatorProxy pagedownButtonAnimation; private AnimatorSetProxy replyButtonAnimation; - private TLRPC.User reportSpamUser; - private boolean openSearchKeyboard; private int channelMessagesImportant; private boolean waitingForImportantLoad; + private boolean waitingForReplyMessageLoad; + private boolean allowStickersPanel; private boolean allowContextBotPanel; private boolean allowContextBotPanelSecond = true; @@ -271,6 +279,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private int startLoadFromMessageId; private boolean needSelectFromMessageId; private int returnToMessageId; + private int returnToLoadIndex; private boolean first = true; private int unread_to_load; @@ -319,6 +328,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private final static int mute = 18; private final static int reply = 19; private final static int edit_done = 20; + private final static int report = 21; private final static int bot_help = 30; private final static int bot_settings = 31; @@ -521,6 +531,8 @@ public void run() { NotificationCenter.getInstance().addObserver(this, NotificationCenter.chatSearchResultsAvailable); NotificationCenter.getInstance().addObserver(this, NotificationCenter.didUpdatedMessagesViews); NotificationCenter.getInstance().addObserver(this, NotificationCenter.chatInfoCantLoad); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didLoadedPinnedMessage); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.peerSettingsDidLoaded); super.onFragmentCreate(); @@ -529,6 +541,7 @@ public void run() { } loading = true; + MessagesController.getInstance().loadPeerSettings(dialog_id, currentUser, currentChat); MessagesController.getInstance().setLastCreatedDialogId(dialog_id, true); if (startLoadFromMessageId != 0) { needSelectFromMessageId = true; @@ -638,12 +651,21 @@ public void onFragmentDestroy() { NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioPlayStateChanged); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didUpdatedMessagesViews); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.chatInfoCantLoad); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didLoadedPinnedMessage); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.peerSettingsDidLoaded); if (AndroidUtilities.isTablet()) { NotificationCenter.getInstance().postNotificationName(NotificationCenter.openedChatChanged, dialog_id, true); } if (currentEncryptedChat != null) { MediaController.getInstance().stopMediaObserver(); + try { + if (Build.VERSION.SDK_INT >= 14) { + getParentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); + } + } catch (Throwable e) { + FileLog.e("tmessages", e); + } } if (currentUser != null) { MessagesController.getInstance().cancelLoadFullUser(currentUser.id); @@ -673,11 +695,6 @@ public View createView(Context context) { chatMessageCellsCache.add(new ChatMessageCell(context)); } } - if (chatMediaCellsCache.isEmpty()) { - for (int a = 0; a < 4; a++) { - chatMediaCellsCache.add(new ChatMediaCell(context)); - } - } for (int a = 1; a >= 0; a--) { selectedMessagesIds[a].clear(); selectedMessagesCanCopyIds[a].clear(); @@ -705,6 +722,7 @@ public void onItemClick(final int id) { cantDeleteMessagesCount = 0; chatActivityEnterView.setEditinigMessageObject(null, false); actionBar.hideActionMode(); + updatePinnedMessageView(true); updateVisibleRows(); } else { finishFragment(); @@ -724,8 +742,10 @@ public void onItemClick(final int id) { if (str.length() != 0) { str += "\n"; } - if (messageObject.messageOwner.message != null) { + if (messageObject.type == 0 && messageObject.messageOwner.message != null) { str += messageObject.messageOwner.message; + } else if (messageObject.messageOwner.media != null && messageObject.messageOwner.media.caption != null) { + str += messageObject.messageOwner.media.caption; } else { str += messageObject.messageText; } @@ -751,48 +771,19 @@ public void onItemClick(final int id) { } cantDeleteMessagesCount = 0; actionBar.hideActionMode(); + updatePinnedMessageView(true); updateVisibleRows(); } else if (id == edit_done) { if (chatActivityEnterView != null && (chatActivityEnterView.isEditingCaption() || chatActivityEnterView.hasText())) { chatActivityEnterView.doneEditingMessage(); actionBar.hideActionMode(); + updatePinnedMessageView(true); } } else if (id == delete) { if (getParentActivity() == null) { return; } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setMessage(LocaleController.formatString("AreYouSureDeleteMessages", R.string.AreYouSureDeleteMessages, LocaleController.formatPluralString("messages", selectedMessagesIds[0].size() + selectedMessagesIds[1].size()))); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - for (int a = 1; a >= 0; a--) { - ArrayList ids = new ArrayList<>(selectedMessagesIds[a].keySet()); - ArrayList random_ids = null; - int channelId = 0; - if (!ids.isEmpty()) { - MessageObject msg = selectedMessagesIds[a].get(ids.get(0)); - if (channelId == 0 && msg.messageOwner.to_id.channel_id != 0) { - channelId = msg.messageOwner.to_id.channel_id; - } - } - if (currentEncryptedChat != null) { - random_ids = new ArrayList<>(); - for (HashMap.Entry entry : selectedMessagesIds[a].entrySet()) { - MessageObject msg = entry.getValue(); - if (msg.messageOwner.random_id != 0 && msg.type != 10) { - random_ids.add(msg.messageOwner.random_id); - } - } - } - MessagesController.getInstance().deleteMessages(ids, random_ids, currentEncryptedChat, channelId); - } - actionBar.hideActionMode(); - } - }); - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); - showDialog(builder.create()); + createDeleteMessagesAlert(null); } else if (id == forward) { Bundle args = new Bundle(); args.putBoolean("onlySelect", true); @@ -868,6 +859,8 @@ public void onClick(DialogInterface dialogInterface, int i) { } } else if (id == mute) { toggleMute(false); + } else if (id == report) { + showDialog(AlertsCreator.createReportAlert(getParentActivity(), dialog_id, ChatActivity.this)); } else if (id == reply) { MessageObject messageObject = null; for (int a = 1; a >= 0; a--) { @@ -883,6 +876,7 @@ public void onClick(DialogInterface dialogInterface, int i) { } cantDeleteMessagesCount = 0; actionBar.hideActionMode(); + updatePinnedMessageView(true); updateVisibleRows(); } else if (id == chat_menu_attach) { if (getParentActivity() == null) { @@ -1133,7 +1127,7 @@ public void run() { searchItem.getSearchField().requestFocus(); AndroidUtilities.showKeyboard(searchItem.getSearchField()); } - }, 300); //TODO find a better way to open keyboard + }, 300); } @Override @@ -1158,6 +1152,9 @@ public void onSearchPressed(EditText editText) { if (searchItem != null) { headerItem.addSubItem(search, LocaleController.getString("Search", R.string.Search), 0); } + if (ChatObject.isChannel(currentChat) && !currentChat.creator && (!currentChat.megagroup || currentChat.username != null && currentChat.username.length() > 0)) { + headerItem.addSubItem(report, LocaleController.getString("ReportChat", R.string.ReportChat), 0); + } if (currentUser != null) { addContactItem = headerItem.addSubItem(share_contact, "", 0); } @@ -1587,10 +1584,10 @@ public boolean onInterceptTouchEvent(MotionEvent event) { if (top > y || bottom < y) { continue; } - if (!(view instanceof ChatMediaCell)) { + if (!(view instanceof ChatMessageCell)) { break; } - final ChatMediaCell cell = (ChatMediaCell) view; + final ChatMessageCell cell = (ChatMessageCell) view; final MessageObject messageObject = cell.getMessageObject(); if (messageObject == null || messageObject.isSending() || !messageObject.isSecretPhoto() || !cell.getPhotoImage().isInsideImage(x, y - top)) { break; @@ -1645,13 +1642,84 @@ public void run() { AndroidUtilities.setProgressBarAnimationDuration(progressBar, 1500); progressView.addView(progressBar, LayoutHelper.createFrame(32, 32, Gravity.CENTER)); + if (ChatObject.isChannel(currentChat)) { + pinnedMessageView = new FrameLayoutFixed(context); + pinnedMessageView.setTag(1); + ViewProxy.setTranslationY(pinnedMessageView, -AndroidUtilities.dp(50)); + pinnedMessageView.clearAnimation(); + pinnedMessageView.setVisibility(View.GONE); + pinnedMessageView.setBackgroundResource(R.drawable.blockpanel); + contentView.addView(pinnedMessageView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 50, Gravity.TOP | Gravity.LEFT)); + pinnedMessageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + scrollToMessageId(info.pinned_msg_id, 0, true, 0); + } + }); + + View lineView = new View(context); + lineView.setBackgroundColor(0xff6c9fd2); + pinnedMessageView.addView(lineView, LayoutHelper.createFrame(2, 32, Gravity.LEFT | Gravity.TOP, 8, 8, 0, 0)); + + pinnedMessageNameTextView = new TextView(context); + pinnedMessageNameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + pinnedMessageNameTextView.setTextColor(0xff377aae); + pinnedMessageNameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + pinnedMessageNameTextView.setSingleLine(true); + pinnedMessageNameTextView.setEllipsize(TextUtils.TruncateAt.END); + pinnedMessageNameTextView.setMaxLines(1); + pinnedMessageView.addView(pinnedMessageNameTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT, 18, 5, 52, 0)); + + pinnedMessageTextView = new TextView(context); + pinnedMessageTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + pinnedMessageTextView.setTextColor(0xff999999); + pinnedMessageTextView.setSingleLine(true); + pinnedMessageTextView.setEllipsize(TextUtils.TruncateAt.END); + pinnedMessageTextView.setMaxLines(1); + pinnedMessageView.addView(pinnedMessageTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT, 18, 23, 52, 0)); + + ImageView closePinned = new ImageView(context); + closePinned.setImageResource(R.drawable.miniplayer_close); + closePinned.setScaleType(ImageView.ScaleType.CENTER); + pinnedMessageView.addView(closePinned, LayoutHelper.createFrame(48, 48, Gravity.RIGHT | Gravity.TOP)); + closePinned.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (getParentActivity() == null) { + return; + } + if (currentChat.creator || currentChat.editor) { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setMessage(LocaleController.getString("UnpinMessageAlert", R.string.UnpinMessageAlert)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + MessagesController.getInstance().pinChannelMessage(currentChat, 0, false); + } + }); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + } else { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + preferences.edit().putInt("pin_" + dialog_id, info.pinned_msg_id).commit(); + updatePinnedMessageView(true); + } + } + }); + } + reportSpamView = new LinearLayout(context); + reportSpamView.setTag(1); + ViewProxy.setTranslationY(reportSpamView, -AndroidUtilities.dp(50)); + reportSpamView.clearAnimation(); reportSpamView.setVisibility(View.GONE); reportSpamView.setBackgroundResource(R.drawable.blockpanel); contentView.addView(reportSpamView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 50, Gravity.TOP | Gravity.LEFT)); addToContactsButton = new TextView(context); addToContactsButton.setTextColor(0xff4a82b5); + addToContactsButton.setVisibility(View.GONE); addToContactsButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); addToContactsButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); addToContactsButton.setSingleLine(true); @@ -1671,7 +1739,7 @@ public void onClick(View v) { }); reportSpamContainer = new FrameLayout(context); - reportSpamView.addView(reportSpamContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 0.5f, Gravity.LEFT | Gravity.TOP)); + reportSpamView.addView(reportSpamContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 1.0f, Gravity.LEFT | Gravity.TOP, 0, 0, 0, AndroidUtilities.dp(1))); reportSpamButton = new TextView(context); reportSpamButton.setTextColor(0xffcf5957); @@ -1679,18 +1747,24 @@ public void onClick(View v) { reportSpamButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); reportSpamButton.setSingleLine(true); reportSpamButton.setMaxLines(1); - reportSpamButton.setText(LocaleController.getString("ReportSpam", R.string.ReportSpam)); + if (currentChat != null) { + reportSpamButton.setText(LocaleController.getString("ReportSpamAndLeave", R.string.ReportSpamAndLeave)); + } else { + reportSpamButton.setText(LocaleController.getString("ReportSpam", R.string.ReportSpam)); + } reportSpamButton.setGravity(Gravity.CENTER); - reportSpamButton.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(50), 0); + reportSpamButton.setPadding(AndroidUtilities.dp(50), 0, AndroidUtilities.dp(50), 0); reportSpamContainer.addView(reportSpamButton, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP)); reportSpamButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (reportSpamUser == null || getParentActivity() == null) { + if (getParentActivity() == null) { return; } AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - if (currentChat != null) { + if (ChatObject.isChannel(currentChat) && !currentChat.megagroup) { + builder.setMessage(LocaleController.getString("ReportSpamAlertChannel", R.string.ReportSpamAlertChannel)); + } else if (currentChat != null) { builder.setMessage(LocaleController.getString("ReportSpamAlertGroup", R.string.ReportSpamAlertGroup)); } else { builder.setMessage(LocaleController.getString("ReportSpamAlert", R.string.ReportSpamAlert)); @@ -1699,31 +1773,19 @@ public void onClick(View v) { builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { - if (reportSpamUser == null) { - return; + if (currentUser != null) { + MessagesController.getInstance().blockUser(currentUser.id); } - TLRPC.TL_messages_reportSpam req = new TLRPC.TL_messages_reportSpam(); + MessagesController.getInstance().reportSpam(dialog_id, currentUser, currentChat); + updateSpamView(); if (currentChat != null) { - req.peer = MessagesController.getInputPeer(-currentChat.id); - } else if (currentUser != null) { - req.peer = MessagesController.getInputPeer(currentUser.id); - } - MessagesController.getInstance().blockUser(reportSpamUser.id); - ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { - @Override - public void run(TLObject response, TLRPC.TL_error error) { - if (error == null) { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - preferences.edit().putBoolean("spam_" + dialog_id, true).commit(); - updateSpamView(); - } - }); - } + if (ChatObject.isNotInChat(currentChat)) { + MessagesController.getInstance().deleteDialog(dialog_id, 0); + } else { + MessagesController.getInstance().deleteUserFromChat((int) -dialog_id, MessagesController.getInstance().getUser(UserConfig.getClientUserId()), null); } - }, ConnectionsManager.RequestFlagFailOnServerErrors); + finishFragment(); + } } }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); @@ -1732,14 +1794,13 @@ public void run() { }); ImageView closeReportSpam = new ImageView(context); - closeReportSpam.setImageResource(R.drawable.delete_reply); + closeReportSpam.setImageResource(R.drawable.miniplayer_close); closeReportSpam.setScaleType(ImageView.ScaleType.CENTER); reportSpamContainer.addView(closeReportSpam, LayoutHelper.createFrame(48, 48, Gravity.RIGHT | Gravity.TOP)); closeReportSpam.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - preferences.edit().putBoolean("spam_" + dialog_id, true).commit(); + MessagesController.getInstance().hideReportSpam(dialog_id, currentUser, currentChat); updateSpamView(); } }); @@ -1993,7 +2054,7 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { @Override public void onClick(View view) { if (returnToMessageId > 0) { - scrollToMessageId(returnToMessageId, 0, true, 0); + scrollToMessageId(returnToMessageId, 0, true, returnToLoadIndex); } else { scrollToLastMessage(true); } @@ -2261,7 +2322,7 @@ public void onClick(DialogInterface dialogInterface, int i) { botUser = null; updateBottomOverlay(); } else { - if (ChatObject.isChannel(currentChat) && !currentChat.megagroup && !(currentChat instanceof TLRPC.TL_channelForbidden)) { + if (ChatObject.isChannel(currentChat) && !(currentChat instanceof TLRPC.TL_channelForbidden)) { if (ChatObject.isNotInChat(currentChat)) { MessagesController.getInstance().addUserToChat(currentChat.id, UserConfig.getCurrentUser(), null, 0, null, null); } else { @@ -2311,6 +2372,15 @@ public void onClick(DialogInterface dialogInterface, int i) { updateBottomOverlay(); updateSecretStatus(); updateSpamView(); + updatePinnedMessageView(true); + + try { + if (currentEncryptedChat != null && Build.VERSION.SDK_INT >= 14) { + getParentActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); + } + } catch (Throwable e) { + FileLog.e("tmessages", e); + } return fragmentView; } @@ -3206,6 +3276,7 @@ private void clearChatData() { firstLoading = true; loading = true; waitingForImportantLoad = false; + waitingForReplyMessageLoad = false; startLoadFromMessageId = 0; last_message_id = 0; needSelectFromMessageId = false; @@ -3328,21 +3399,24 @@ private void scrollToMessageId(int id, int fromMessageId, boolean select, int lo if (currentEncryptedChat != null && !MessagesStorage.getInstance().checkMessageId(dialog_id, startLoadFromMessageId)) { return; } - clearChatData(); + /*clearChatData(); loadsCount = 0; unread_to_load = 0; first_unread_id = 0; loadingForward = false; unreadMessageObject = null; - scrollToMessage = null; + scrollToMessage = null;*/ + + waitingForReplyMessageLoad = true; highlightMessageId = Integer.MAX_VALUE; scrollToMessagePosition = -10000; startLoadFromMessageId = id; waitingForLoad.add(lastLoadIndex); MessagesController.getInstance().loadMessages(loadIndex == 0 ? dialog_id : mergeDialogId, AndroidUtilities.isTablet() ? 30 : 20, startLoadFromMessageId, true, 0, classGuid, 3, 0, loadIndex == 0 ? channelMessagesImportant : 0, lastLoadIndex++); - emptyViewContainer.setVisibility(View.INVISIBLE); + //emptyViewContainer.setVisibility(View.INVISIBLE); } returnToMessageId = fromMessageId; + returnToLoadIndex = loadIndex; needSelectFromMessageId = select; } @@ -3463,15 +3537,14 @@ private void checkActionBarMenu() { private int updateOnlineCount() { onlineCount = 0; - if (!(info instanceof TLRPC.TL_chatFull)) { - return 0; - } int currentTime = ConnectionsManager.getInstance().getCurrentTime(); - for (int a = 0; a < info.participants.participants.size(); a++) { - TLRPC.ChatParticipant participant = info.participants.participants.get(a); - TLRPC.User user = MessagesController.getInstance().getUser(participant.user_id); - if (user != null && user.status != null && (user.status.expires > currentTime || user.id == UserConfig.getClientUserId()) && user.status.expires > 10000) { - onlineCount++; + if (info instanceof TLRPC.TL_chatFull || info instanceof TLRPC.TL_channelFull && info.participants_count <= 200 && info.participants != null) { + for (int a = 0; a < info.participants.participants.size(); a++) { + TLRPC.ChatParticipant participant = info.participants.participants.get(a); + TLRPC.User user = MessagesController.getInstance().getUser(participant.user_id); + if (user != null && user.status != null && (user.status.expires > currentTime || user.id == UserConfig.getClientUserId()) && user.status.expires > 10000) { + onlineCount++; + } } } return onlineCount; @@ -3617,7 +3690,7 @@ private void addToSelectedMessages(MessageObject messageObject) { int index = messageObject.getDialogId() == dialog_id ? 0 : 1; if (selectedMessagesIds[index].containsKey(messageObject.getId())) { selectedMessagesIds[index].remove(messageObject.getId()); - if (messageObject.type == 0) { + if (messageObject.type == 0 || messageObject.caption != null) { selectedMessagesCanCopyIds[index].remove(messageObject.getId()); } if (!messageObject.canDeleteMessage(currentChat)) { @@ -3625,7 +3698,7 @@ private void addToSelectedMessages(MessageObject messageObject) { } } else { selectedMessagesIds[index].put(messageObject.getId(), messageObject); - if (messageObject.type == 0) { + if (messageObject.type == 0 || messageObject.caption != null) { selectedMessagesCanCopyIds[index].put(messageObject.getId(), messageObject); } if (!messageObject.canDeleteMessage(currentChat)) { @@ -3635,6 +3708,7 @@ private void addToSelectedMessages(MessageObject messageObject) { if (actionBar.isActionModeShowed()) { if (selectedMessagesIds[0].isEmpty() && selectedMessagesIds[1].isEmpty()) { actionBar.hideActionMode(); + updatePinnedMessageView(true); } else { int copyVisible = actionBar.createActionMode().getItem(copy).getVisibility(); actionBar.createActionMode().getItem(copy).setVisibility(selectedMessagesCanCopyIds[0].size() + selectedMessagesCanCopyIds[1].size() != 0 ? View.VISIBLE : View.GONE); @@ -3812,10 +3886,18 @@ private void updateSubtitle() { } } else { if (info != null && info.participants_count != 0) { - int result[] = new int[1]; - String shortNumber = LocaleController.formatShortNumber(info.participants_count, result); - String text = LocaleController.formatPluralString("Members", result[0]).replace(String.format("%d", result[0]), shortNumber); - onlineTextView.setText(text); + if (currentChat.megagroup && info.participants_count <= 200) { + if (onlineCount > 1 && info.participants_count != 0) { + onlineTextView.setText(String.format("%s, %s", LocaleController.formatPluralString("Members", info.participants_count), LocaleController.formatPluralString("Online", onlineCount))); + } else { + onlineTextView.setText(LocaleController.formatPluralString("Members", info.participants_count)); + } + } else { + int result[] = new int[1]; + String shortNumber = LocaleController.formatShortNumber(info.participants_count, result); + String text = LocaleController.formatPluralString("Members", result[0]).replace(String.format("%d", result[0]), shortNumber); + onlineTextView.setText(text); + } } else { if (currentChat.megagroup) { onlineTextView.setText(LocaleController.getString("Loading", R.string.Loading).toLowerCase()); @@ -4082,10 +4164,21 @@ public void sendButtonPressed(int index) { showReplyPanel(false, null, null, null, false, true); } else if (requestCode == 2) { String videoPath = null; + FileLog.d("tmessages", "pic path " + currentPicturePath); + if (data != null && currentPicturePath != null) { + if (new File(currentPicturePath).exists()) { + data = null; + } + } if (data != null) { Uri uri = data.getData(); if (uri != null) { - videoPath = uri.getPath(); + FileLog.d("tmessages", "video record uri " + uri.toString()); + videoPath = AndroidUtilities.getPath(uri); + FileLog.d("tmessages", "resolved path = " + videoPath); + if (!(new File(videoPath).exists())) { + videoPath = currentPicturePath; + } } else { videoPath = currentPicturePath; } @@ -4134,7 +4227,7 @@ public void sendButtonPressed(int index) { String originalPath = tempPath; if (tempPath == null) { originalPath = data.toString(); - tempPath = MediaController.copyDocumentToCache(data.getData(), "file"); + tempPath = MediaController.copyFileToCache(data.getData(), "file"); } if (tempPath == null) { showAttachmentError(); @@ -4223,10 +4316,26 @@ public void didReceivedNotification(int id, final Object... args) { } else { waitingForLoad.remove(index); } - if (waitingForImportantLoad) { + ArrayList messArr = (ArrayList) args[2]; + if (waitingForImportantLoad || waitingForReplyMessageLoad) { + if (waitingForReplyMessageLoad) { + boolean found = false; + for (int a = 0; a < messArr.size(); a++) { + if (messArr.get(a).getId() == startLoadFromMessageId) { + found = true; + break; + } + } + if (!found) { + startLoadFromMessageId = 0; + return; + } + } int startLoadFrom = startLoadFromMessageId; + boolean needSelect = needSelectFromMessageId; clearChatData(); startLoadFromMessageId = startLoadFrom; + needSelectFromMessageId = needSelect; } loadsCount++; @@ -4245,7 +4354,6 @@ public void didReceivedNotification(int id, final Object... args) { } else if (startLoadFromMessageId != 0 && load_type == 3) { last_message_id = (Integer) args[5]; } - ArrayList messArr = (ArrayList) args[2]; ArrayList groups = (ArrayList) args[9]; SparseArray groupsByStart = null; if (groups != null && !groups.isEmpty()) { @@ -4292,7 +4400,7 @@ public void didReceivedNotification(int id, final Object... args) { Collections.reverse(messArr); } if (currentEncryptedChat == null) { - ReplyMessageQuery.loadReplyMessagesForMessages(messArr, dialog_id); + MessagesQuery.loadReplyMessagesForMessages(messArr, dialog_id); } for (int a = 0; a < messArr.size(); a++) { MessageObject obj = messArr.get(a); @@ -4422,7 +4530,7 @@ public void didReceivedNotification(int id, final Object... args) { } if (loadsCount <= 2) { - if (messages.size() >= 20 || !isCache) { + if (!isCache) { updateSpamView(); } } @@ -4438,13 +4546,13 @@ public void didReceivedNotification(int id, final Object... args) { } startLoadFromMessageId = 0; } - if (newRowsCount != 0) { + if (newRowsCount > 0) { int firstVisPos = chatLayoutManager.findLastVisibleItemPosition(); int top = 0; if (firstVisPos != chatLayoutManager.getItemCount() - 1) { firstVisPos = RecyclerView.NO_POSITION; } else { - View firstVisView = chatListView.getChildAt(chatListView.getChildCount() - 1); + View firstVisView = chatLayoutManager.findViewByPosition(firstVisPos); top = ((firstVisView == null) ? 0 : firstVisView.getTop()) - chatListView.getPaddingTop(); } chatAdapter.notifyItemRangeInserted(chatAdapter.getItemCount() - 1, newRowsCount); @@ -4502,7 +4610,7 @@ public void didReceivedNotification(int id, final Object... args) { chatAdapter.notifyItemRangeChanged(chatAdapter.isBot ? 1 : 0, 2); } int firstVisPos = chatLayoutManager.findLastVisibleItemPosition(); - View firstVisView = chatListView.getChildAt(chatListView.getChildCount() - 1); + View firstVisView = chatLayoutManager.findViewByPosition(firstVisPos); int top = ((firstVisView == null) ? 0 : firstVisView.getTop()) - chatListView.getPaddingTop(); if (newRowsCount - (end ? 1 : 0) > 0) { chatAdapter.notifyItemRangeInserted((chatAdapter.isBot ? 2 : 1) + (end ? 0 : 1), newRowsCount - (end ? 1 : 0)); @@ -4623,7 +4731,6 @@ public void run() { } if ((updateMask & MessagesController.UPDATE_MASK_USER_PHONE) != 0) { updateContactStatus(); - updateSpamView(); } } else if (id == NotificationCenter.didReceivedNewMessages) { long did = (Long) args[0]; @@ -4647,8 +4754,26 @@ obj.messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActi } } } + if (currentChat != null) { + for (int a = 0; a < arr.size(); a++) { + MessageObject messageObject = arr.get(a); + if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionChatDeleteUser && messageObject.messageOwner.action.user_id == UserConfig.getClientUserId() || + messageObject.messageOwner.action instanceof TLRPC.TL_messageActionChatAddUser && messageObject.messageOwner.action.users.contains(UserConfig.getClientUserId())) { + TLRPC.Chat newChat = MessagesController.getInstance().getChat(currentChat.id); + if (newChat != null) { + currentChat = newChat; + updateBottomOverlay(); + updateSubtitle(); + } + } else if (messageObject.messageOwner.reply_to_msg_id != 0 && messageObject.replyMessageObject == null) { + messageObject.replyMessageObject = messagesDict[0].get(messageObject.messageOwner.reply_to_msg_id); + if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPinMessage) { + messageObject.generatePinMessageText(null, null); + } + } + } + } - ReplyMessageQuery.loadReplyMessagesForMessages(arr, dialog_id); boolean reloadMegagroup = false; if (!forwardEndReached[0]) { int currentMaxDate = Integer.MIN_VALUE; @@ -5002,6 +5127,12 @@ public void run() { for (int a = 0; a < markAsDeletedMessages.size(); a++) { Integer ids = markAsDeletedMessages.get(a); MessageObject obj = messagesDict[loadIndex].get(ids); + if (loadIndex == 0 && info != null && info.pinned_msg_id == ids) { + pinnedMessageObject = null; + info.pinned_msg_id = 0; + MessagesStorage.getInstance().updateChannelPinnedMessage(channelId, 0); + updatePinnedMessageView(true); + } if (obj != null) { int index = messages.indexOf(obj); if (index != -1) { @@ -5098,7 +5229,7 @@ public void run() { ArrayList messArr = new ArrayList<>(); messArr.add(obj); if (currentEncryptedChat == null) { - ReplyMessageQuery.loadReplyMessagesForMessages(messArr, dialog_id); + MessagesQuery.loadReplyMessagesForMessages(messArr, dialog_id); } if (chatAdapter != null) { chatAdapter.updateRowWithMessageObject(obj); @@ -5147,6 +5278,12 @@ public void run() { if (mentionsAdapter != null) { mentionsAdapter.setChatInfo(info); } + if (args[3] instanceof MessageObject) { + pinnedMessageObject = (MessageObject) args[3]; + updatePinnedMessageView(false); + } else { + updatePinnedMessageView(true); + } updateOnlineCount(); updateSubtitle(); if (isBroadcast) { @@ -5206,12 +5343,19 @@ public void run() { } else if (id == NotificationCenter.chatInfoCantLoad) { int chatId = (Integer) args[0]; if (currentChat != null && currentChat.id == chatId) { + int reason = (Integer) args[1]; if (getParentActivity() == null || closeChatDialog != null) { return; } AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - builder.setMessage(LocaleController.getString("ChannelCantOpenPrivate", R.string.ChannelCantOpenPrivate)); + if (reason == 0) { + builder.setMessage(LocaleController.getString("ChannelCantOpenPrivate", R.string.ChannelCantOpenPrivate)); + } else if (reason == 1) { + builder.setMessage(LocaleController.getString("ChannelCantOpenNa", R.string.ChannelCantOpenNa)); + } else if (reason == 2) { + builder.setMessage(LocaleController.getString("ChannelCantOpenBanned", R.string.ChannelCantOpenBanned)); + } builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); showDialog(closeChatDialog = builder.create()); @@ -5226,7 +5370,6 @@ public void run() { } else if (id == NotificationCenter.contactsDidLoaded) { updateContactStatus(); updateSubtitle(); - updateSpamView(); } else if (id == NotificationCenter.encryptedChatUpdated) { TLRPC.EncryptedChat chat = (TLRPC.EncryptedChat) args[0]; if (currentEncryptedChat != null && chat.id == currentEncryptedChat.id) { @@ -5330,6 +5473,7 @@ public void run() { } cantDeleteMessagesCount = 0; actionBar.hideActionMode(); + updatePinnedMessageView(true); if (botButtons != null) { botButtons = null; @@ -5378,7 +5522,6 @@ public void run() { userBlocked = MessagesController.getInstance().blockedUsers.contains(currentUser.id); if (oldValue != userBlocked) { updateBottomOverlay(); - updateSpamView(); } } } else if (id == NotificationCenter.FileNewChunkAvailable) { @@ -5449,6 +5592,10 @@ public void run() { for (int a = 0; a < messageObjects.size(); a++) { MessageObject messageObject = messageObjects.get(a); MessageObject old = messagesDict[loadIndex].get(messageObject.getId()); + if (pinnedMessageObject != null && pinnedMessageObject.getId() == messageObject.getId()) { + pinnedMessageObject = messageObject; + updatePinnedMessageView(true); + } if (old != null) { if (!mediaUpdated && messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) { mediaUpdated = true; @@ -5482,6 +5629,13 @@ public void run() { if (did == dialog_id) { updateVisibleRows(); } + } else if (id == NotificationCenter.didLoadedPinnedMessage) { + MessageObject message = (MessageObject) args[0]; + if (message.getDialogId() == dialog_id && info != null && info.pinned_msg_id == message.getId()) { + pinnedMessageObject = message; + loadingPinnedMessage = 0; + updatePinnedMessageView(true); + } } else if (id == NotificationCenter.didReceivedWebpages) { ArrayList arrayList = (ArrayList) args[0]; boolean updated = false; @@ -5520,7 +5674,7 @@ public void run() { boolean updated = false; for (int a = 0; a < arrayList.size(); a++) { long mid = arrayList.get(a); - MessageObject currentMessage = messagesDict[0].get((int) mid); + MessageObject currentMessage = messagesDict[mergeDialogId == 0 ? 0 : 1].get((int) mid); if (currentMessage != null) { currentMessage.setContentIsRead(); updated = true; @@ -5611,6 +5765,11 @@ public void run() { updateVisibleRows(); } } + } else if (id == NotificationCenter.peerSettingsDidLoaded) { + long did = (Long) args[0]; + if (did == dialog_id) { + updateSpamView(); + } } } @@ -5639,14 +5798,13 @@ public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { int count = chatListView.getChildCount(); for (int a = 0; a < count; a++) { View view = chatListView.getChildAt(a); - if (view instanceof ChatMediaCell) { - ChatMediaCell cell = (ChatMediaCell) view; - cell.setAllowedToSetPhoto(true); + if (view instanceof ChatMessageCell) { + ((ChatMessageCell) view).setAllowedToSetPhoto(true); } } if (currentUser != null) { - MessagesController.getInstance().loadFullUser(currentUser, classGuid); + MessagesController.getInstance().loadFullUser(currentUser, classGuid, false); } } } @@ -5655,7 +5813,13 @@ public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { protected void onDialogDismiss(Dialog dialog) { if (closeChatDialog != null && dialog == closeChatDialog) { MessagesController.getInstance().deleteDialog(dialog_id, 0); - finishFragment(); + if (parentLayout != null && !parentLayout.fragmentsStack.isEmpty() && parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 1) != this) { + BaseFragment fragment = parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 1); + removeSelfFromStack(); + fragment.finishFragment(); + } else { + finishFragment(); + } } } @@ -5664,7 +5828,7 @@ private void updateBottomOverlay() { return; } if (currentChat != null) { - if (ChatObject.isChannel(currentChat) && !currentChat.megagroup && !(currentChat instanceof TLRPC.TL_channelForbidden)) { + if (ChatObject.isChannel(currentChat) && !(currentChat instanceof TLRPC.TL_channelForbidden)) { if (ChatObject.isNotInChat(currentChat)) { bottomOverlayChatText.setText(LocaleController.getString("ChannelJoin", R.string.ChannelJoin)); } else { @@ -5723,79 +5887,186 @@ private void updateBottomOverlay() { checkRaiseSensors(); } + private void hidePinnedMessageView(boolean animated) { + if (pinnedMessageView.getTag() == null) { + pinnedMessageView.setTag(1); + if (pinnedMessageViewAnimator != null) { + pinnedMessageViewAnimator.cancel(); + pinnedMessageViewAnimator = null; + } + if (Build.VERSION.SDK_INT >= 11 && animated) { + pinnedMessageViewAnimator = new AnimatorSetProxy(); + pinnedMessageViewAnimator.playTogether(ObjectAnimatorProxy.ofFloat(pinnedMessageView, "translationY", -AndroidUtilities.dp(50))); + pinnedMessageViewAnimator.setDuration(200); + pinnedMessageViewAnimator.addListener(new AnimatorListenerAdapterProxy() { + @Override + public void onAnimationEnd(Object animation) { + if (pinnedMessageViewAnimator != null && pinnedMessageViewAnimator.equals(animation)) { + pinnedMessageView.clearAnimation(); + pinnedMessageView.setVisibility(View.GONE); + pinnedMessageViewAnimator = null; + } + } + + @Override + public void onAnimationCancel(Object animation) { + if (pinnedMessageViewAnimator != null && pinnedMessageViewAnimator.equals(animation)) { + pinnedMessageViewAnimator = null; + } + } + }); + pinnedMessageViewAnimator.start(); + } else { + ViewProxy.setTranslationY(pinnedMessageView, -AndroidUtilities.dp(50)); + pinnedMessageView.clearAnimation(); + pinnedMessageView.setVisibility(View.GONE); + } + } + } + + private void updatePinnedMessageView(boolean animated) { + if (pinnedMessageView == null) { + return; + } + if (info != null) { + if (pinnedMessageObject != null && info.pinned_msg_id != pinnedMessageObject.getId()) { + pinnedMessageObject = null; + } + if (info.pinned_msg_id != 0 && pinnedMessageObject == null) { + pinnedMessageObject = messagesDict[0].get(info.pinned_msg_id); + } + } + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + if (info == null || info.pinned_msg_id == 0 || info.pinned_msg_id == preferences.getInt("pin_" + dialog_id, 0) || actionBar != null && actionBar.isActionModeShowed()) { + hidePinnedMessageView(animated); + } else { + if (pinnedMessageObject != null) { + if (pinnedMessageView.getTag() != null) { + pinnedMessageView.setTag(null); + if (pinnedMessageViewAnimator != null) { + pinnedMessageViewAnimator.cancel(); + pinnedMessageViewAnimator = null; + } + if (Build.VERSION.SDK_INT >= 11 && animated) { + pinnedMessageView.setVisibility(View.VISIBLE); + pinnedMessageViewAnimator = new AnimatorSetProxy(); + pinnedMessageViewAnimator.playTogether(ObjectAnimatorProxy.ofFloat(pinnedMessageView, "translationY", 0)); + pinnedMessageViewAnimator.setDuration(200); + pinnedMessageViewAnimator.addListener(new AnimatorListenerAdapterProxy() { + @Override + public void onAnimationEnd(Object animation) { + if (pinnedMessageViewAnimator != null && pinnedMessageViewAnimator.equals(animation)) { + pinnedMessageView.clearAnimation(); + pinnedMessageViewAnimator = null; + } + } + + @Override + public void onAnimationCancel(Object animation) { + if (pinnedMessageViewAnimator != null && pinnedMessageViewAnimator.equals(animation)) { + pinnedMessageViewAnimator = null; + } + } + }); + pinnedMessageViewAnimator.start(); + } else { + ViewProxy.setTranslationY(pinnedMessageView, 0); + pinnedMessageView.clearAnimation(); + pinnedMessageView.setVisibility(View.VISIBLE); + } + } + pinnedMessageNameTextView.setText(LocaleController.getString("PinnedMessage", R.string.PinnedMessage)); + if (pinnedMessageObject.messageText != null) { + String mess = pinnedMessageObject.messageText.toString(); + if (mess.length() > 150) { + mess = mess.substring(0, 150); + } + mess = mess.replace("\n", " "); + pinnedMessageTextView.setText(Emoji.replaceEmoji(mess, pinnedMessageTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(14), false)); + } + } else { + hidePinnedMessageView(animated); + if (loadingPinnedMessage != info.pinned_msg_id) { + loadingPinnedMessage = info.pinned_msg_id; + MessagesQuery.loadPinnedMessage(currentChat.id, info.pinned_msg_id, true); + } + } + } + checkListViewPaddings(); + } + private void updateSpamView() { if (reportSpamView == null) { return; } - reportSpamUser = null; SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - if (!messages.isEmpty() && !preferences.getBoolean("spam_" + dialog_id, false)) { - if (currentChat != null) { + boolean show = preferences.getInt("spam3_" + dialog_id, 0) == 2; + if (show) { + if (messages.isEmpty()) { + show = false; + } else { int count = messages.size() - 1; for (int a = count; a >= Math.max(count - 50, 0); a--) { MessageObject messageObject = messages.get(a); if (messageObject.isOut()) { - reportSpamUser = null; - } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionChatCreate) { - reportSpamUser = MessagesController.getInstance().getUser(messageObject.messageOwner.from_id); - } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionChatAddUser) { - if (messageObject.messageOwner.action.user_id == UserConfig.getClientUserId() || messageObject.messageOwner.action.users.contains(UserConfig.getClientUserId())) { - reportSpamUser = MessagesController.getInstance().getUser(messageObject.messageOwner.from_id); - } + show = false; + break; } } - if (reportSpamUser != null && ContactsController.getInstance().contactsDict.get(reportSpamUser.id) != null) { - reportSpamUser = null; - } - if (reportSpamUser != null) { - addToContactsButton.setVisibility(View.GONE); - reportSpamButton.setPadding(AndroidUtilities.dp(50), 0, AndroidUtilities.dp(50), 0); - reportSpamContainer.setLayoutParams(LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 1.0f, Gravity.LEFT | Gravity.TOP, 0, 0, 0, AndroidUtilities.dp(1))); - } - } else if (currentUser != null) { - if (!currentUser.bot && - currentUser.id / 1000 != 333 && currentUser.id / 1000 != 777 - && !UserObject.isDeleted(currentUser) - && !userBlocked - && !ContactsController.getInstance().isLoadingContacts() - && (currentUser.phone == null || currentUser.phone.length() == 0 || ContactsController.getInstance().contactsDict.get(currentUser.id) == null)) { - if (currentUser.phone != null && currentUser.phone.length() != 0) { - reportSpamButton.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(50), 0); - addToContactsButton.setVisibility(View.VISIBLE); - reportSpamContainer.setLayoutParams(LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 0.5f, Gravity.LEFT | Gravity.TOP, 0, 0, 0, AndroidUtilities.dp(1))); - } else { - reportSpamButton.setPadding(AndroidUtilities.dp(50), 0, AndroidUtilities.dp(50), 0); - addToContactsButton.setVisibility(View.GONE); - reportSpamContainer.setLayoutParams(LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 1.0f, Gravity.LEFT | Gravity.TOP, 0, 0, 0, AndroidUtilities.dp(1))); - } - reportSpamUser = currentUser; - } - if (reportSpamUser != null) { - int count = messages.size() - 1; - for (int a = count; a >= Math.max(count - 50, 0); a--) { - MessageObject messageObject = messages.get(a); - if (messageObject.isOut()) { - reportSpamUser = null; - break; + } + } + if (!show) { + if (reportSpamView.getTag() == null) { + reportSpamView.setTag(1); + if (Build.VERSION.SDK_INT >= 11) { + if (reportSpamViewAnimator != null) { + reportSpamViewAnimator.cancel(); + } + reportSpamViewAnimator = new AnimatorSetProxy(); + reportSpamViewAnimator.playTogether(ObjectAnimatorProxy.ofFloat(reportSpamView, "translationY", -AndroidUtilities.dp(50))); + reportSpamViewAnimator.setDuration(200); + reportSpamViewAnimator.addListener(new AnimatorListenerAdapterProxy() { + @Override + public void onAnimationEnd(Object animation) { + if (reportSpamViewAnimator != null && reportSpamViewAnimator.equals(animation)) { + reportSpamView.clearAnimation(); + reportSpamView.setVisibility(View.GONE); + reportSpamViewAnimator = null; + } } + }); + reportSpamViewAnimator.start(); + } else { + reportSpamView.setVisibility(View.GONE); + } + } + } else { + if (reportSpamView.getTag() != null) { + reportSpamView.setTag(null); + if (Build.VERSION.SDK_INT >= 11) { + reportSpamView.setVisibility(View.VISIBLE); + if (reportSpamViewAnimator != null) { + reportSpamViewAnimator.cancel(); } + reportSpamViewAnimator = new AnimatorSetProxy(); + reportSpamViewAnimator.playTogether(ObjectAnimatorProxy.ofFloat(reportSpamView, "translationY", 0)); + reportSpamViewAnimator.setDuration(200); + reportSpamViewAnimator.addListener(new AnimatorListenerAdapterProxy() { + @Override + public void onAnimationEnd(Object animation) { + if (reportSpamViewAnimator != null && reportSpamViewAnimator.equals(animation)) { + reportSpamView.clearAnimation(); + reportSpamViewAnimator = null; + } + } + }); + reportSpamViewAnimator.start(); + } else { + reportSpamView.setVisibility(View.VISIBLE); } } } - if (reportSpamUser != null) { - if (reportSpamView.getVisibility() != View.VISIBLE) { - reportSpamView.setVisibility(View.VISIBLE); - reportSpamView.setTag(1); - chatListView.setTopGlowOffset(AndroidUtilities.dp(48)); - chatListView.setPadding(0, AndroidUtilities.dp(52), 0, AndroidUtilities.dp(3)); - } - } else if (reportSpamView.getVisibility() != View.GONE) { - reportSpamView.setVisibility(View.GONE); - reportSpamView.setTag(null); - chatListView.setPadding(0, AndroidUtilities.dp(4), 0, AndroidUtilities.dp(3)); - chatListView.setTopGlowOffset(0); - chatLayoutManager.scrollToPositionWithOffset(messages.size() - 1, -100000 - chatListView.getPaddingTop()); - } + checkListViewPaddings(); } private void updateContactStatus() { @@ -5815,27 +6086,54 @@ private void updateContactStatus() { || ContactsController.getInstance().isLoadingContacts() || (currentUser.phone != null && currentUser.phone.length() != 0 && ContactsController.getInstance().contactsDict.get(currentUser.id) != null && (ContactsController.getInstance().contactsDict.size() != 0 || !ContactsController.getInstance().isLoadingContacts()))) { addContactItem.setVisibility(View.GONE); - reportSpamView.setVisibility(View.GONE); - chatListView.setTopGlowOffset(0); - chatListView.setPadding(0, AndroidUtilities.dp(4), 0, AndroidUtilities.dp(3)); } else { addContactItem.setVisibility(View.VISIBLE); - if (reportSpamView.getTag() != null) { - reportSpamView.setVisibility(View.VISIBLE); - chatListView.setPadding(0, AndroidUtilities.dp(52), 0, AndroidUtilities.dp(3)); - chatListView.setTopGlowOffset(AndroidUtilities.dp(48)); - } if (currentUser.phone != null && currentUser.phone.length() != 0) { addContactItem.setText(LocaleController.getString("AddToContacts", R.string.AddToContacts)); + reportSpamButton.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(50), 0); addToContactsButton.setVisibility(View.VISIBLE); reportSpamContainer.setLayoutParams(LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 0.5f, Gravity.LEFT | Gravity.TOP, 0, 0, 0, AndroidUtilities.dp(1))); } else { addContactItem.setText(LocaleController.getString("ShareMyContactInfo", R.string.ShareMyContactInfo)); addToContactsButton.setVisibility(View.GONE); + reportSpamButton.setPadding(AndroidUtilities.dp(50), 0, AndroidUtilities.dp(50), 0); reportSpamContainer.setLayoutParams(LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 1.0f, Gravity.LEFT | Gravity.TOP, 0, 0, 0, AndroidUtilities.dp(1))); } } } + checkListViewPaddings(); + } + + private void checkListViewPaddings() { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + try { + int firstVisPos = chatLayoutManager.findLastVisibleItemPosition(); + int top = 0; + if (firstVisPos != RecyclerView.NO_POSITION) { + View firstVisView = chatLayoutManager.findViewByPosition(firstVisPos); + top = ((firstVisView == null) ? 0 : firstVisView.getTop()) - chatListView.getPaddingTop(); + } + if (chatListView.getPaddingTop() != AndroidUtilities.dp(52) && (pinnedMessageView != null && pinnedMessageView.getTag() == null || reportSpamView != null && reportSpamView.getTag() == null)) { + chatListView.setPadding(0, AndroidUtilities.dp(52), 0, AndroidUtilities.dp(3)); + chatListView.setTopGlowOffset(AndroidUtilities.dp(48)); + top -= AndroidUtilities.dp(48); + } else if (chatListView.getPaddingTop() != AndroidUtilities.dp(4) && (pinnedMessageView == null || pinnedMessageView.getTag() != null) && (reportSpamView == null || reportSpamView.getTag() != null)) { + chatListView.setPadding(0, AndroidUtilities.dp(4), 0, AndroidUtilities.dp(3)); + chatListView.setTopGlowOffset(0); + top += AndroidUtilities.dp(48); + } else { + firstVisPos = RecyclerView.NO_POSITION; + } + if (firstVisPos != RecyclerView.NO_POSITION) { + chatLayoutManager.scrollToPositionWithOffset(firstVisPos, top); + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + }); } private void checkRaiseSensors() { @@ -5859,7 +6157,7 @@ public void onResume() { replyImageView.setImage(replyImageLocation, "50_50", (Drawable) null); } - NotificationsController.getInstance().setOpennedDialogId(dialog_id); + NotificationsController.getInstance().setOpenedDialogId(dialog_id); if (scrollToTopOnResume) { if (scrollToTopUnReadOnResume && scrollToMessage != null) { if (chatListView != null) { @@ -5980,7 +6278,7 @@ public void onPause() { } paused = true; wasPaused = true; - NotificationsController.getInstance().setOpennedDialogId(0); + NotificationsController.getInstance().setOpenedDialogId(0); if (chatActivityEnterView != null) { chatActivityEnterView.onPause(); if (!chatActivityEnterView.isEditingMessage()) { @@ -6162,6 +6460,141 @@ private void switchImportantMode(MessageObject searchBeforeMessage) { } } + private void createDeleteMessagesAlert(final MessageObject finalSelectedObject) { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setMessage(LocaleController.formatString("AreYouSureDeleteMessages", R.string.AreYouSureDeleteMessages, LocaleController.formatPluralString("messages", finalSelectedObject != null ? 1 : selectedMessagesIds[0].size() + selectedMessagesIds[1].size()))); + builder.setTitle(LocaleController.getString("Message", R.string.Message)); + + final boolean[] checks = new boolean[3]; + TLRPC.User user = null; + if (currentChat != null && currentChat.megagroup) { + if (finalSelectedObject != null) { + if (finalSelectedObject.messageOwner.action == null || finalSelectedObject.messageOwner.action instanceof TLRPC.TL_messageActionEmpty) { + user = MessagesController.getInstance().getUser(finalSelectedObject.messageOwner.from_id); + } + } else { + int from_id = -1; + for (int a = 1; a >= 0; a--) { + int channelId = 0; + for (HashMap.Entry entry : selectedMessagesIds[a].entrySet()) { + MessageObject msg = entry.getValue(); + if (from_id == -1) { + from_id = msg.messageOwner.from_id; + } + if (from_id < 0 || from_id != msg.messageOwner.from_id) { + from_id = -2; + break; + } + } + if (from_id == -2) { + break; + } + } + if (from_id != -1) { + user = MessagesController.getInstance().getUser(from_id); + } + } + if (user != null && user.id != UserConfig.getClientUserId()) { + FrameLayout frameLayout = new FrameLayout(getParentActivity()); + if (Build.VERSION.SDK_INT >= 21) { + frameLayout.setPadding(0, AndroidUtilities.dp(8), 0, 0); + } + for (int a = 0; a < 3; a++) { + CheckBoxCell cell = new CheckBoxCell(getParentActivity()); + cell.setBackgroundResource(R.drawable.list_selector); + cell.setTag(a); + if (Build.VERSION.SDK_INT < 11) { + cell.setTextColor(0xffffffff); + } + if (a == 0) { + cell.setText(LocaleController.getString("DeleteBanUser", R.string.DeleteBanUser), "", false, false); + } else if (a == 1) { + cell.setText(LocaleController.getString("DeleteReportSpam", R.string.DeleteReportSpam), "", false, false); + } else if (a == 2) { + cell.setText(LocaleController.formatString("DeleteAllFrom", R.string.DeleteAllFrom, ContactsController.formatName(user.first_name, user.last_name)), "", false, false); + } + cell.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(8) : 0, 0, LocaleController.isRTL ? 0 : AndroidUtilities.dp(8), 0); + frameLayout.addView(cell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.TOP | Gravity.LEFT, 8, 48 * a, 8, 0)); + cell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + CheckBoxCell cell = (CheckBoxCell) v; + Integer num = (Integer) cell.getTag(); + checks[num] = !checks[num]; + cell.setChecked(checks[num], true); + } + }); + } + builder.setView(frameLayout); + } else { + user = null; + } + } + final TLRPC.User userFinal = user; + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + ArrayList ids = null; + if (finalSelectedObject != null) { + ids = new ArrayList<>(); + ids.add(finalSelectedObject.getId()); + ArrayList random_ids = null; + if (currentEncryptedChat != null && finalSelectedObject.messageOwner.random_id != 0 && finalSelectedObject.type != 10) { + random_ids = new ArrayList<>(); + random_ids.add(finalSelectedObject.messageOwner.random_id); + } + MessagesController.getInstance().deleteMessages(ids, random_ids, currentEncryptedChat, finalSelectedObject.messageOwner.to_id.channel_id); + } else { + for (int a = 1; a >= 0; a--) { + ids = new ArrayList<>(selectedMessagesIds[a].keySet()); + ArrayList random_ids = null; + int channelId = 0; + if (!ids.isEmpty()) { + MessageObject msg = selectedMessagesIds[a].get(ids.get(0)); + if (channelId == 0 && msg.messageOwner.to_id.channel_id != 0) { + channelId = msg.messageOwner.to_id.channel_id; + } + } + if (currentEncryptedChat != null) { + random_ids = new ArrayList<>(); + for (HashMap.Entry entry : selectedMessagesIds[a].entrySet()) { + MessageObject msg = entry.getValue(); + if (msg.messageOwner.random_id != 0 && msg.type != 10) { + random_ids.add(msg.messageOwner.random_id); + } + } + } + MessagesController.getInstance().deleteMessages(ids, random_ids, currentEncryptedChat, channelId); + } + actionBar.hideActionMode(); + updatePinnedMessageView(true); + } + if (userFinal != null) { + if (checks[0]) { + MessagesController.getInstance().deleteUserFromChat(currentChat.id, userFinal, info); + } + if (checks[1]) { + TLRPC.TL_channels_reportSpam req = new TLRPC.TL_channels_reportSpam(); + req.channel = MessagesController.getInputChannel(currentChat); + req.user_id = MessagesController.getInputUser(userFinal); + req.id = ids; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }); + } + if (checks[2]) { + MessagesController.getInstance().deleteUserChannelHistory(currentChat, userFinal, 0); + } + } + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + } + private void createMenu(View v, boolean single) { if (actionBar.isActionModeShowed()) { return; @@ -6181,6 +6614,10 @@ private void createMenu(View v, boolean single) { switchImportantMode(message); return; } + if (single && message.messageOwner.action instanceof TLRPC.TL_messageActionPinMessage) { + scrollToMessageId(message.messageOwner.reply_to_msg_id, 0, true, 0); + return; + } selectedObject = null; forwaringMessage = null; @@ -6190,8 +6627,12 @@ private void createMenu(View v, boolean single) { } cantDeleteMessagesCount = 0; actionBar.hideActionMode(); + updatePinnedMessageView(true); boolean allowChatActions = true; + boolean allowPin = message.getDialogId() != mergeDialogId && message.getId() > 0 && ChatObject.isChannel(currentChat) && currentChat.megagroup && (currentChat.creator || currentChat.editor) && (message.messageOwner.action == null || message.messageOwner.action instanceof TLRPC.TL_messageActionEmpty); + boolean allowUnpin = message.getDialogId() != mergeDialogId && info != null && info.pinned_msg_id == message.getId() && (currentChat.creator || currentChat.editor); + boolean allowEdit = message.canEditMessage(currentChat) && !chatActivityEnterView.hasAudioToSend(); if (currentEncryptedChat != null && AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) < 46 || type == 1 && message.getDialogId() == mergeDialogId || currentEncryptedChat == null && message.getId() < 0 || isBroadcast || currentChat != null && (ChatObject.isNotInChat(currentChat) || ChatObject.isChannel(currentChat) && !currentChat.creator && !currentChat.editor && !currentChat.megagroup)) { allowChatActions = false; } @@ -6218,7 +6659,14 @@ private void createMenu(View v, boolean single) { items.add(LocaleController.getString("Reply", R.string.Reply)); options.add(8); } - if (message.canEditMessage(currentChat)) { + if (allowUnpin) { + items.add(LocaleController.getString("UnpinMessage", R.string.UnpinMessage)); + options.add(14); + } else if (allowPin) { + items.add(LocaleController.getString("PinMessage", R.string.PinMessage)); + options.add(13); + } + if (allowEdit) { items.add(LocaleController.getString("Edit", R.string.Edit)); options.add(12); } @@ -6245,9 +6693,11 @@ private void createMenu(View v, boolean single) { items.add(LocaleController.getString("Reply", R.string.Reply)); options.add(8); } - if (type == 3) { + if (selectedObject.type == 0 || selectedObject.caption != null) { items.add(LocaleController.getString("Copy", R.string.Copy)); options.add(3); + } + if (type == 3) { if (selectedObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && MessageObject.isNewGifDocument(selectedObject.messageOwner.media.webpage.document)) { items.add(LocaleController.getString("SaveToGIFs", R.string.SaveToGIFs)); options.add(11); @@ -6289,7 +6739,14 @@ private void createMenu(View v, boolean single) { } items.add(LocaleController.getString("Forward", R.string.Forward)); options.add(2); - if (message.canEditMessage(currentChat)) { + if (allowUnpin) { + items.add(LocaleController.getString("UnpinMessage", R.string.UnpinMessage)); + options.add(14); + } else if (allowPin) { + items.add(LocaleController.getString("PinMessage", R.string.PinMessage)); + options.add(13); + } + if (allowEdit) { items.add(LocaleController.getString("Edit", R.string.Edit)); options.add(12); } @@ -6302,10 +6759,11 @@ private void createMenu(View v, boolean single) { items.add(LocaleController.getString("Reply", R.string.Reply)); options.add(8); } - if (type == 3) { + if (selectedObject.type == 0 || selectedObject.caption != null) { items.add(LocaleController.getString("Copy", R.string.Copy)); options.add(3); - } else if (type == 4) { + } + if (type == 4) { if (selectedObject.isVideo()) { items.add(LocaleController.getString("SaveToGallery", R.string.SaveToGallery)); options.add(4); @@ -6367,6 +6825,7 @@ public void onClick(DialogInterface dialogInterface, int i) { } actionBar.showActionMode(); + updatePinnedMessageView(true); if (Build.VERSION.SDK_INT >= 11) { AnimatorSetProxy animatorSet = new AnimatorSetProxy(); @@ -6399,26 +6858,7 @@ private void processSelectedOption(int option) { selectedObject = null; return; } - final MessageObject finalSelectedObject = selectedObject; - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setMessage(LocaleController.formatString("AreYouSureDeleteMessages", R.string.AreYouSureDeleteMessages, LocaleController.formatPluralString("messages", 1))); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - ArrayList ids = new ArrayList<>(); - ids.add(finalSelectedObject.getId()); - removeUnreadPlane(); - ArrayList random_ids = null; - if (currentEncryptedChat != null && finalSelectedObject.messageOwner.random_id != 0 && finalSelectedObject.type != 10) { - random_ids = new ArrayList<>(); - random_ids.add(finalSelectedObject.messageOwner.random_id); - } - MessagesController.getInstance().deleteMessages(ids, random_ids, currentEncryptedChat, finalSelectedObject.messageOwner.to_id.channel_id); - } - }); - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); - showDialog(builder.create()); + createDeleteMessagesAlert(selectedObject); } else if (option == 2) { forwaringMessage = selectedObject; Bundle args = new Bundle(); @@ -6429,12 +6869,20 @@ public void onClick(DialogInterface dialogInterface, int i) { presentFragment(fragment); } else if (option == 3) { try { + CharSequence str; + if (selectedObject.type == 0 && selectedObject.messageOwner.message != null) { + str = selectedObject.messageOwner.message; + } else if (selectedObject.messageOwner.media != null && selectedObject.messageOwner.media.caption != null) { + str = selectedObject.messageOwner.media.caption; + } else { + str = selectedObject.messageText; + } if (Build.VERSION.SDK_INT < 11) { android.text.ClipboardManager clipboard = (android.text.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); - clipboard.setText(selectedObject.messageText); + clipboard.setText(str); } else { android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); - android.content.ClipData clip = android.content.ClipData.newPlainText("label", selectedObject.messageText); + android.content.ClipData clip = android.content.ClipData.newPlainText("label", str); clipboard.setPrimaryClip(clip); } } catch (Exception e) { @@ -6546,32 +6994,13 @@ public void onClick(DialogInterface dialogInterface, int i) { } MediaController.saveFile(path, getParentActivity(), selectedObject.isMusic() ? 3 : 2, fileName); } else if (option == 11) { - MediaController.SearchImage searchImage = new MediaController.SearchImage(); - searchImage.type = 2; + MediaController.SearchImage searchImage; if (selectedObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) { - searchImage.document = selectedObject.messageOwner.media.webpage.document; + searchImage = MessagesController.getInstance().saveGif(selectedObject.messageOwner.media.webpage.document); } else { - searchImage.document = selectedObject.messageOwner.media.document; - } - searchImage.date = (int) (System.currentTimeMillis() / 1000); - searchImage.id = "" + searchImage.document.id; - - ArrayList arrayList = new ArrayList<>(); - arrayList.add(searchImage); - MessagesStorage.getInstance().putWebRecent(arrayList); - TLRPC.TL_messages_saveGif req = new TLRPC.TL_messages_saveGif(); - req.id = new TLRPC.TL_inputDocument(); - req.id.id = searchImage.document.id; - req.id.access_hash = searchImage.document.access_hash; - req.unsave = false; - ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { - @Override - public void run(TLObject response, TLRPC.TL_error error) { - - } - }); + searchImage = MessagesController.getInstance().saveGif(selectedObject.messageOwner.media.document); + } showGifHint(); - chatActivityEnterView.addRecentGif(searchImage); } else if (option == 12) { if (getParentActivity() == null) { @@ -6620,6 +7049,7 @@ public void run() { actionMode.getItem(delete).setVisibility(View.GONE); actionMode.getItem(edit_done).setVisibility(View.VISIBLE); actionBar.showActionMode(); + updatePinnedMessageView(true); } } else { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); @@ -6648,6 +7078,54 @@ public void onClick(DialogInterface dialog, int which) { } catch (Exception e) { //don't promt } + } else if (option == 13) { + final int mid = selectedObject.getId(); + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setMessage(LocaleController.getString("PinMessageAlert", R.string.PinMessageAlert)); + + final boolean[] checks = new boolean[]{true}; + FrameLayout frameLayout = new FrameLayout(getParentActivity()); + if (Build.VERSION.SDK_INT >= 21) { + frameLayout.setPadding(0, AndroidUtilities.dp(8), 0, 0); + } + CheckBoxCell cell = new CheckBoxCell(getParentActivity()); + cell.setBackgroundResource(R.drawable.list_selector); + if (Build.VERSION.SDK_INT < 11) { + cell.setTextColor(0xffffffff); + } + cell.setText(LocaleController.getString("PinNotify", R.string.PinNotify), "", true, false); + cell.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(8) : 0, 0, LocaleController.isRTL ? 0 : AndroidUtilities.dp(8), 0); + frameLayout.addView(cell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.TOP | Gravity.LEFT, 8, 0, 8, 0)); + cell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + CheckBoxCell cell = (CheckBoxCell) v; + checks[0] = !checks[0]; + cell.setChecked(checks[0], true); + } + }); + builder.setView(frameLayout); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + MessagesController.getInstance().pinChannelMessage(currentChat, mid, checks[0]); + } + }); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + } else if (option == 14) { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setMessage(LocaleController.getString("UnpinMessageAlert", R.string.UnpinMessageAlert)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + MessagesController.getInstance().pinChannelMessage(currentChat, 0, false); + } + }); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); } selectedObject = null; } @@ -6675,6 +7153,7 @@ public void didSelectDialog(DialogsActivity activity, long did, boolean param) { } cantDeleteMessagesCount = 0; actionBar.hideActionMode(); + updatePinnedMessageView(true); } if (did != dialog_id) { @@ -6687,6 +7166,10 @@ public void didSelectDialog(DialogsActivity activity, long did, boolean param) { } else if (lower_part < 0) { args.putInt("chat_id", -lower_part); } + if (!MessagesController.checkCanOpenChat(args, activity)) { + return; + } + ChatActivity chatActivity = new ChatActivity(args); if (presentFragment(chatActivity, true)) { chatActivity.showReplyPanel(true, null, fmessages, null, false, false); @@ -6705,6 +7188,7 @@ public void didSelectDialog(DialogsActivity activity, long did, boolean param) { showReplyPanel(true, null, fmessages, null, false, AndroidUtilities.isTablet()); if (AndroidUtilities.isTablet()) { actionBar.hideActionMode(); + updatePinnedMessageView(true); } updateVisibleRows(); } @@ -6720,6 +7204,7 @@ public boolean onBackPressed() { } chatActivityEnterView.setEditinigMessageObject(null, false); actionBar.hideActionMode(); + updatePinnedMessageView(true); cantDeleteMessagesCount = 0; updateVisibleRows(); return false; @@ -6786,6 +7271,9 @@ private void updateVisibleRows() { cell.setMessageObject(cell.getMessageObject()); cell.setCheckPressed(!disableSelection, disableSelection && selected); cell.setHighlighted(highlightMessageId != Integer.MAX_VALUE && cell.getMessageObject() != null && cell.getMessageObject().getId() == highlightMessageId); + } else if (view instanceof ChatActionCell) { + ChatActionCell cell = (ChatActionCell) view; + cell.setMessageObject(cell.getMessageObject()); } } } @@ -6845,6 +7333,10 @@ public void updatePhotoAtIndex(int index) { } + public boolean isSecretChat() { + return currentEncryptedChat != null; + } + @Override public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { if (messageObject == null) { @@ -6882,6 +7374,9 @@ public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObj object.imageReceiver = imageReceiver; object.thumb = imageReceiver.getBitmap(); object.radius = imageReceiver.getRoundRadius(); + if (pinnedMessageView != null && pinnedMessageView.getTag() == null || reportSpamView != null && reportSpamView.getTag() == null) { + object.clipTopAddition = AndroidUtilities.dp(48); + } return object; } } @@ -6996,13 +7491,6 @@ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType } else { view = new ChatMessageCell(mContext); } - } else if (viewType == 1) { - if (!chatMediaCellsCache.isEmpty()) { - view = chatMediaCellsCache.get(0); - chatMediaCellsCache.remove(0); - } else { - view = new ChatMediaCell(mContext); - } } else if (viewType == 2) { view = new ChatAudioCell(mContext); ((ChatAudioCell) view).setAudioDelegate(new ChatAudioCell.ChatAudioCellDelegate() { @@ -7047,7 +7535,7 @@ public void didPressUrl(String url) { } ((ChatBaseCell) view).setDelegate(new ChatBaseCell.ChatBaseCellDelegate() { @Override - public void didPressShare(ChatBaseCell cell) { + public void didPressedShare(ChatBaseCell cell) { if (getParentActivity() == null) { return; } @@ -7072,10 +7560,17 @@ public void didPressedChannelAvatar(ChatBaseCell cell, TLRPC.Chat chat, int post if (postId != 0) { args.putInt("message_id", postId); } - presentFragment(new ChatActivity(args), true); + if (MessagesController.checkCanOpenChat(args, ChatActivity.this)) { + presentFragment(new ChatActivity(args), true); + } } } + @Override + public void didPressedOther(ChatBaseCell cell) { + createMenu(cell, true); + } + @Override public void didPressedUserAvatar(ChatBaseCell cell, TLRPC.User user) { if (actionBar.isActionModeShowed()) { @@ -7110,7 +7605,10 @@ public boolean canPerformActions() { } @Override - public void didPressUrl(MessageObject messageObject, final ClickableSpan url, boolean longPress) { + public void didPressedUrl(MessageObject messageObject, final ClickableSpan url, boolean longPress) { + if (url == null) { + return; + } if (url instanceof URLSpanNoUnderline) { String str = ((URLSpanNoUnderline) url).getURL(); if (str.startsWith("@")) { @@ -7191,7 +7689,7 @@ public void needOpenWebView(String url, String title, String originalUrl, int w, } @Override - public void didPressReplyMessage(ChatBaseCell cell, int id) { + public void didPressedReplyMessage(ChatBaseCell cell, int id) { MessageObject messageObject = cell.getMessageObject(); if (messageObject.replyMessageObject != null && !messageObject.replyMessageObject.isImportant() && channelMessagesImportant == 2) { channelMessagesImportant = 1; @@ -7212,7 +7710,7 @@ public void didPressedViaBot(ChatBaseCell cell, String username) { } @Override - public void didClickedImage(ChatBaseCell cell) { + public void didPressedImage(ChatBaseCell cell) { MessageObject message = cell.getMessageObject(); if (message.isSendError()) { createMenu(cell, false); @@ -7220,9 +7718,9 @@ public void didClickedImage(ChatBaseCell cell) { } else if (message.isSending()) { return; } - if (message.type == 1 || message.type == 0) { + if (message.type == 1 || message.type == 0 && !message.isWebpageDocument()) { PhotoViewer.getInstance().setParentActivity(getParentActivity()); - PhotoViewer.getInstance().openPhoto(message, message.contentType == 1 ? dialog_id : 0, message.contentType == 1 ? mergeDialogId : 0, ChatActivity.this); + PhotoViewer.getInstance().openPhoto(message, message.type != 0 ? dialog_id : 0, message.type != 0 ? mergeDialogId : 0, ChatActivity.this); } else if (message.type == 3) { sendSecretMessageRead(message); try { @@ -7246,7 +7744,7 @@ public void didClickedImage(ChatBaseCell cell) { LocationActivity fragment = new LocationActivity(); fragment.setMessageObject(message); presentFragment(fragment); - } else if (message.type == 9) { + } else if (message.type == 9 || message.type == 0) { File f = null; String fileName = message.getFileName(); if (message.messageOwner.attachPath != null && message.messageOwner.attachPath.length() != 0) { @@ -7259,26 +7757,28 @@ public void didClickedImage(ChatBaseCell cell) { String realMimeType = null; try { Intent intent = new Intent(Intent.ACTION_VIEW); - if (message.type == 8 || message.type == 9) { - MimeTypeMap myMime = MimeTypeMap.getSingleton(); - int idx = fileName.lastIndexOf("."); - if (idx != -1) { - String ext = fileName.substring(idx + 1); - realMimeType = myMime.getMimeTypeFromExtension(ext.toLowerCase()); - if (realMimeType == null) { + MimeTypeMap myMime = MimeTypeMap.getSingleton(); + int idx = fileName.lastIndexOf("."); + if (idx != -1) { + String ext = fileName.substring(idx + 1); + realMimeType = myMime.getMimeTypeFromExtension(ext.toLowerCase()); + if (realMimeType == null) { + if (message.type == 9) { realMimeType = message.messageOwner.media.document.mime_type; - if (realMimeType == null || realMimeType.length() == 0) { - realMimeType = null; - } + } else if (message.type == 0) { + realMimeType = message.messageOwner.media.webpage.document.mime_type; } - if (realMimeType != null) { - intent.setDataAndType(Uri.fromFile(f), realMimeType); - } else { - intent.setDataAndType(Uri.fromFile(f), "text/plain"); + if (realMimeType == null || realMimeType.length() == 0) { + realMimeType = null; } + } + if (realMimeType != null) { + intent.setDataAndType(Uri.fromFile(f), realMimeType); } else { intent.setDataAndType(Uri.fromFile(f), "text/plain"); } + } else { + intent.setDataAndType(Uri.fromFile(f), "text/plain"); } if (realMimeType != null) { try { @@ -7297,14 +7797,8 @@ public void didClickedImage(ChatBaseCell cell) { } } }); - if (view instanceof ChatMediaCell) { - ((ChatMediaCell) view).setAllowedToSetPhoto(openAnimationEnded); - ((ChatMediaCell) view).setMediaDelegate(new ChatMediaCell.ChatMediaCellDelegate() { - @Override - public void didPressedOther(ChatMediaCell cell) { - createMenu(cell, true); - } - }); + if (view instanceof ChatMessageCell) { + ((ChatMessageCell) view).setAllowedToSetPhoto(openAnimationEnded); } else if (view instanceof ChatContactCell) { ((ChatContactCell) view).setContactDelegate(new ChatContactCell.ChatContactCellDelegate() { @Override @@ -7390,7 +7884,9 @@ public void needOpenUserProfile(int uid) { if (uid < 0) { Bundle args = new Bundle(); args.putInt("chat_id", -uid); - presentFragment(new ChatActivity(args), true); + if (MessagesController.checkCanOpenChat(args, ChatActivity.this)) { + presentFragment(new ChatActivity(args), true); + } } else if (uid != UserConfig.getClientUserId()) { Bundle args = new Bundle(); args.putInt("user_id", uid); @@ -7404,7 +7900,7 @@ public void needOpenUserProfile(int uid) { } }); } - + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); return new Holder(view); } @@ -7526,7 +8022,7 @@ public void notifyItemChanged(int position) { public void notifyItemRangeChanged(int positionStart, int itemCount) { updateRows(); try { - super.notifyItemRangeChanged(positionStart, itemCount); + super.notifyItemRangeChanged(positionStart, itemCount); } catch (Exception e) { FileLog.e("tmessages", e); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java index c77407c64e..ed2c6c4cf1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java @@ -14,6 +14,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; +import android.os.Bundle; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; @@ -22,10 +23,14 @@ import org.telegram.messenger.NotificationsController; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.R; +import org.telegram.messenger.Utilities; import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.RequestDelegate; +import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ReportOtherActivity; public class AlertsCreator { @@ -82,6 +87,68 @@ public void onClick(DialogInterface dialogInterface, int i) { return builder.create(); } + public static Dialog createReportAlert(Context context, final long dialog_id, final BaseFragment parentFragment) { + if (context == null || parentFragment == null) { + return null; + } + + BottomSheet.Builder builder = new BottomSheet.Builder(context); + builder.setTitle(LocaleController.getString("ReportChat", R.string.ReportChat)); + CharSequence[] items = new CharSequence[]{ + LocaleController.getString("ReportChatSpam", R.string.ReportChatSpam), + LocaleController.getString("ReportChatViolence", R.string.ReportChatViolence), + LocaleController.getString("ReportChatPornography", R.string.ReportChatPornography), + LocaleController.getString("ReportChatOther", R.string.ReportChatOther) + }; + builder.setItems(items, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + if (i == 3) { + Bundle args = new Bundle(); + args.putLong("dialog_id", dialog_id); + parentFragment.presentFragment(new ReportOtherActivity(args)); + return; + } + TLRPC.TL_account_reportPeer req = new TLRPC.TL_account_reportPeer(); + req.peer = MessagesController.getInputPeer((int) dialog_id); + if (i == 0) { + req.reason = new TLRPC.TL_inputReportReasonSpam(); + } else if (i == 1) { + req.reason = new TLRPC.TL_inputReportReasonViolence(); + } else if (i == 2) { + req.reason = new TLRPC.TL_inputReportReasonPornography(); + } + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }); + } + } + ); + return builder.create(); + } + + public static void showFloodWaitAlert(String error, final BaseFragment fragment) { + if (error == null || !error.startsWith("FLOOD_WAIT") || fragment == null || fragment.getParentActivity() == null) { + return; + } + int time = Utilities.parseInt(error); + String timeString; + if (time < 60) { + timeString = LocaleController.formatPluralString("Seconds", time); + } else { + timeString = LocaleController.formatPluralString("Minutes", time / 60); + } + + AlertDialog.Builder builder = new AlertDialog.Builder(fragment.getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setMessage(LocaleController.formatString("FloodWaitTime", R.string.FloodWaitTime, timeString)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + fragment.showDialog(builder.create(), true); + } + public static void showAddUserAlert(String error, final BaseFragment fragment, boolean isChannel) { if (error == null || fragment == null || fragment.getParentActivity() == null) { return; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java index 7a8bda10ba..299508c7c8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatActivityEnterView.java @@ -11,7 +11,9 @@ import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; +import android.app.AlertDialog; import android.content.Context; +import android.content.DialogInterface; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.graphics.Canvas; @@ -482,8 +484,13 @@ public ChatActivityEnterView(Activity context, SizeNotifierFrameLayout parent, B emojiButton = new ImageView(context); emojiButton.setImageResource(R.drawable.ic_msg_panel_smiles); emojiButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - emojiButton.setPadding(AndroidUtilities.dp(4), AndroidUtilities.dp(1), 0, 0); - frameLayout.addView(emojiButton, LayoutHelper.createFrame(48, 48, Gravity.BOTTOM)); + emojiButton.setPadding(0, AndroidUtilities.dp(1), 0, 0); + if (Build.VERSION.SDK_INT >= 21) { + emojiButton.setBackgroundResource(R.drawable.circle_selector); + frameLayout.addView(emojiButton, LayoutHelper.createFrame(44, 44, Gravity.BOTTOM | Gravity.LEFT, 4, 0, 0, 2)); + } else { + frameLayout.addView(emojiButton, LayoutHelper.createFrame(48, 48, Gravity.BOTTOM | Gravity.LEFT, 3, 0, 0, 0)); + } emojiButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { @@ -583,7 +590,7 @@ public void onTextChanged(CharSequence charSequence, int start, int before, int if (innerTextChange != 2 && before != count && (count - before) > 1) { processChange = true; } - if (!isAsAdmin && message.length() != 0 && lastTypingTimeSend < System.currentTimeMillis() - 5000 && !ignoreTextChange) { + if (editingMessageObject == null && !isAsAdmin && message.length() != 0 && lastTypingTimeSend < System.currentTimeMillis() - 5000 && !ignoreTextChange) { int currentTime = ConnectionsManager.getInstance().getCurrentTime(); TLRPC.User currentUser = null; if ((int) dialog_id > 0) { @@ -633,7 +640,12 @@ public void afterTextChanged(Editable editable) { botButton.setImageResource(R.drawable.bot_keyboard2); botButton.setScaleType(ImageView.ScaleType.CENTER); botButton.setVisibility(GONE); - attachButton.addView(botButton, LayoutHelper.createLinear(48, 48)); + if (Build.VERSION.SDK_INT >= 21) { + botButton.setBackgroundResource(R.drawable.circle_selector); + attachButton.addView(botButton, LayoutHelper.createLinear(44, 44, Gravity.CENTER_VERTICAL, 2, 0, 2, 0)); + } else { + attachButton.addView(botButton, LayoutHelper.createLinear(48, 48)); + } botButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -660,7 +672,12 @@ public void onClick(View v) { asAdminButton.setImageResource(isAsAdmin ? R.drawable.publish_active : R.drawable.publish); asAdminButton.setScaleType(ImageView.ScaleType.CENTER); asAdminButton.setVisibility(adminModeAvailable ? VISIBLE : GONE); - attachButton.addView(asAdminButton, LayoutHelper.createLinear(48, 48)); + if (Build.VERSION.SDK_INT >= 21) { + asAdminButton.setBackgroundResource(R.drawable.circle_selector); + attachButton.addView(asAdminButton, LayoutHelper.createLinear(44, 44, Gravity.CENTER_VERTICAL, 2, 0, 2, 0)); + } else { + attachButton.addView(asAdminButton, LayoutHelper.createLinear(48, 48)); + } asAdminButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -676,7 +693,12 @@ public void onClick(View v) { notifyButton.setImageResource(silent ? R.drawable.notify_members_off : R.drawable.notify_members_on); notifyButton.setScaleType(ImageView.ScaleType.CENTER); notifyButton.setVisibility(canWriteToChannel ? VISIBLE : GONE); - attachButton.addView(notifyButton, LayoutHelper.createLinear(48, 48)); + if (Build.VERSION.SDK_INT >= 21) { + notifyButton.setBackgroundResource(R.drawable.circle_selector); + attachButton.addView(notifyButton, LayoutHelper.createLinear(44, 44, Gravity.CENTER_VERTICAL, 2, 0, 2, 0)); + } else { + attachButton.addView(notifyButton, LayoutHelper.createLinear(48, 48)); + } notifyButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -689,6 +711,7 @@ public void onClick(View v) { } else { Toast.makeText(parentActivity, LocaleController.getString("ChannelNotifyMembersInfoOn", R.string.ChannelNotifyMembersInfoOn), Toast.LENGTH_SHORT).show(); } + updateFieldHint(); } }); } @@ -802,11 +825,9 @@ public void onClick(View v) { public boolean onTouch(View view, MotionEvent motionEvent) { if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { if (parentFragment != null) { - if (Build.VERSION.SDK_INT >= 23) { - if (parentActivity.checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { - parentActivity.requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 3); - return false; - } + if (Build.VERSION.SDK_INT >= 23 && parentActivity.checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + parentActivity.requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 3); + return false; } String action; @@ -1171,7 +1192,15 @@ private void updateFieldHint() { if (editingMessageObject != null) { messageEditText.setHint(editingCaption ? LocaleController.getString("Caption", R.string.Caption) : LocaleController.getString("TypeMessage", R.string.TypeMessage)); } else { - messageEditText.setHint(isAsAdmin ? LocaleController.getString("ChannelBroadcast", R.string.ChannelBroadcast) : LocaleController.getString("ChannelComment", R.string.ChannelComment)); + if (isAsAdmin) { + if (silent) { + messageEditText.setHint(LocaleController.getString("ChannelSilentBroadcast", R.string.ChannelSilentBroadcast)); + } else { + messageEditText.setHint(LocaleController.getString("ChannelBroadcast", R.string.ChannelBroadcast)); + } + } else { + messageEditText.setHint(LocaleController.getString("ChannelComment", R.string.ChannelComment)); + } } } else { messageEditText.setHint(LocaleController.getString("TypeMessage", R.string.TypeMessage)); @@ -1764,7 +1793,12 @@ public void addToAttachLayout(View view) { ViewGroup viewGroup = (ViewGroup) view.getParent(); viewGroup.removeView(view); } - attachButton.addView(view, LayoutHelper.createLinear(48, 48)); + if (Build.VERSION.SDK_INT >= 21) { + view.setBackgroundResource(R.drawable.circle_selector); + attachButton.addView(view, LayoutHelper.createLinear(44, 44, Gravity.CENTER_VERTICAL, 2, 0, 2, 0)); + } else { + attachButton.addView(view, LayoutHelper.createLinear(48, 48)); + } } private void updateBotButton() { @@ -1920,7 +1954,10 @@ public void onStickersSettingsClick() { @Override public void onGifSelected(TLRPC.Document gif) { - SendMessagesHelper.getInstance().sendMessage((TLRPC.TL_document) gif, null, null, dialog_id, replyingMessageObject, asAdmin(), null); + SendMessagesHelper.getInstance().sendSticker(gif, dialog_id, replyingMessageObject, asAdmin()); + if ((int) dialog_id == 0) { + MessagesController.getInstance().saveGif(gif); + } if (delegate != null) { delegate.onMessageSend(null); } @@ -1944,6 +1981,24 @@ public void onGifTab(boolean opened) { public void onStickersTab(boolean opened) { delegate.onStickersTab(opened); } + + @Override + public void onClearEmojiRecent() { + if (parentFragment == null || parentActivity == null) { + return; + } + AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setMessage(LocaleController.getString("ClearRecentEmoji", R.string.ClearRecentEmoji)); + builder.setPositiveButton(LocaleController.getString("ClearButton", R.string.ClearButton).toUpperCase(), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + emojiView.clearRecentEmoji(); + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + parentFragment.showDialog(builder.create()); + } }); emojiView.setVisibility(GONE); sizeNotifierLayout.addView(emojiView); @@ -2066,6 +2121,10 @@ public boolean isEditingCaption() { return editingCaption; } + public boolean hasAudioToSend() { + return audioToSendMessageObject != null; + } + public void openKeyboard() { AndroidUtilities.showKeyboard(messageEditText); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java index 7877474c28..63c0773bf4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java @@ -48,6 +48,7 @@ import org.telegram.messenger.MediaController; import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.Utilities; import org.telegram.messenger.query.StickersQuery; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; @@ -77,6 +78,7 @@ public interface Listener { void onGifSelected(TLRPC.Document gif); void onGifTab(boolean opened); void onStickersTab(boolean opened); + void onClearEmojiRecent(); } private static final Field superListenerField; @@ -160,6 +162,8 @@ public boolean onLongClick(View view) { pickerViewPopup.showAsDropDown(view, xOffset, -view.getMeasuredHeight() - popupHeight + (view.getMeasuredHeight() - emojiSize) / 2 - yOffset); view.getParent().requestDisallowInterceptTouchEvent(true); return true; + } else if (pager.getCurrentItem() == 0) { + listener.onClearEmojiRecent(); } return false; } @@ -519,7 +523,6 @@ protected void onDraw(Canvas canvas) { private GifsAdapter gifsAdapter; private AdapterView.OnItemClickListener stickersOnItemClickListener; - private EmojiColorPickerView pickerView; private EmojiPopupWindow pickerViewPopup; private int popupWidth; @@ -531,8 +534,6 @@ protected void onDraw(Canvas canvas) { private int gifTabBum = -2; private boolean switchToGifTab; - - private int oldWidth; private int lastNotifyWidth; @@ -667,7 +668,7 @@ public void onItemClick(View view, int position) { return; } TLRPC.Document document = recentImages.get(position).document; - listener.onStickerSelected(document); + listener.onGifSelected(document); } }); gifsGridView.setOnItemLongClickListener(new RecyclerListView.OnItemLongClickListener() { @@ -966,6 +967,15 @@ public boolean onKey(View v, int keyCode, KeyEvent event) { loadRecents(); } + public void clearRecentEmoji() { + SharedPreferences preferences = getContext().getSharedPreferences("emoji", Activity.MODE_PRIVATE); + preferences.edit().putBoolean("filled_default", true).commit(); + emojiUseHistory.clear(); + recentEmoji.clear(); + saveRecentEmoji(); + adapters.get(0).notifyDataSetChanged(); + } + private void showGifTab() { gifsGridView.setVisibility(VISIBLE); stickersGridView.setVisibility(GONE); @@ -1268,7 +1278,7 @@ public void loadRecents() { String[] args = str.split(","); for (String arg : args) { String[] args2 = arg.split("="); - long value = Long.parseLong(args2[0]); + long value = Utilities.parseLong(args2[0]); String string = ""; for (int a = 0; a < 4; a++) { char ch = (char) value; @@ -1279,7 +1289,7 @@ public void loadRecents() { } } if (string.length() > 0) { - emojiUseHistory.put(string, Integer.parseInt(args2[1])); + emojiUseHistory.put(string, Utilities.parseInt(args2[1])); } } } @@ -1291,22 +1301,25 @@ public void loadRecents() { String[] args = str.split(","); for (String arg : args) { String[] args2 = arg.split("="); - emojiUseHistory.put(args2[0], Integer.parseInt(args2[1])); + emojiUseHistory.put(args2[0], Utilities.parseInt(args2[1])); } } } if (emojiUseHistory.isEmpty()) { - String[] newRecent = new String[]{ - "\uD83D\uDE02", "\uD83D\uDE18", "\u2764", "\uD83D\uDE0D", "\uD83D\uDE0A", "\uD83D\uDE01", - "\uD83D\uDC4D", "\u263A", "\uD83D\uDE14", "\uD83D\uDE04", "\uD83D\uDE2D", "\uD83D\uDC8B", - "\uD83D\uDE12", "\uD83D\uDE33", "\uD83D\uDE1C", "\uD83D\uDE48", "\uD83D\uDE09", "\uD83D\uDE03", - "\uD83D\uDE22", "\uD83D\uDE1D", "\uD83D\uDE31", "\uD83D\uDE21", "\uD83D\uDE0F", "\uD83D\uDE1E", - "\uD83D\uDE05", "\uD83D\uDE1A", "\uD83D\uDE4A", "\uD83D\uDE0C", "\uD83D\uDE00", "\uD83D\uDE0B", - "\uD83D\uDE06", "\uD83D\uDC4C", "\uD83D\uDE10", "\uD83D\uDE15"}; - for (int i = 0; i < newRecent.length; i++) { - emojiUseHistory.put(newRecent[i], newRecent.length - i); + if (!preferences.getBoolean("filled_default", false)) { + String[] newRecent = new String[]{ + "\uD83D\uDE02", "\uD83D\uDE18", "\u2764", "\uD83D\uDE0D", "\uD83D\uDE0A", "\uD83D\uDE01", + "\uD83D\uDC4D", "\u263A", "\uD83D\uDE14", "\uD83D\uDE04", "\uD83D\uDE2D", "\uD83D\uDC8B", + "\uD83D\uDE12", "\uD83D\uDE33", "\uD83D\uDE1C", "\uD83D\uDE48", "\uD83D\uDE09", "\uD83D\uDE03", + "\uD83D\uDE22", "\uD83D\uDE1D", "\uD83D\uDE31", "\uD83D\uDE21", "\uD83D\uDE0F", "\uD83D\uDE1E", + "\uD83D\uDE05", "\uD83D\uDE1A", "\uD83D\uDE4A", "\uD83D\uDE0C", "\uD83D\uDE00", "\uD83D\uDE0B", + "\uD83D\uDE06", "\uD83D\uDC4C", "\uD83D\uDE10", "\uD83D\uDE15"}; + for (int i = 0; i < newRecent.length; i++) { + emojiUseHistory.put(newRecent[i], newRecent.length - i); + } + preferences.edit().putBoolean("filled_default", true).commit(); + saveRecentEmoji(); } - saveRecentEmoji(); } sortEmoji(); adapters.get(0).notifyDataSetChanged(); @@ -1338,8 +1351,8 @@ public void loadRecents() { for (int a = 0; a < args.length; a++) { String arg = args[a]; String[] args2 = arg.split("="); - Long key = Long.parseLong(args2[0]); - stickersUseHistory.put(key, Integer.parseInt(args2[1])); + Long key = Utilities.parseLong(args2[0]); + stickersUseHistory.put(key, Utilities.parseInt(args2[1])); newRecentStickers.add(key); } Collections.sort(newRecentStickers, new Comparator() { @@ -1367,7 +1380,13 @@ public int compare(Long lhs, Long rhs) { str = preferences.getString("stickers2", ""); String[] args = str.split(","); for (int a = 0; a < args.length; a++) { - newRecentStickers.add(Long.parseLong(args[a])); + if (args[a].length() == 0) { + continue; + } + long id = Utilities.parseLong(args[a]); + if (id != 0) { + newRecentStickers.add(id); + } } } sortStickers(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/FrameLayoutFixed.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/FrameLayoutFixed.java index 930ad1917c..f558d9d2d6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/FrameLayoutFixed.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/FrameLayoutFixed.java @@ -14,6 +14,7 @@ import android.view.View; import android.widget.FrameLayout; +import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLog; import java.util.ArrayList; @@ -155,6 +156,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } catch (Exception e2) { FileLog.e("tmessages", e2); + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(10), MeasureSpec.EXACTLY)); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoCropView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoCropView.java index 3c12182b8f..be8516efa4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoCropView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoCropView.java @@ -24,6 +24,7 @@ public class PhotoCropView extends FrameLayout { public interface PhotoCropViewDelegate { void needMoveImageTo(float x, float y, float s, boolean animated); + Bitmap getBitmap(); } private boolean freeformCrop = true; @@ -38,11 +39,11 @@ public interface PhotoCropViewDelegate { private float oldX = 0, oldY = 0; private int bitmapWidth = 1, bitmapHeight = 1, bitmapX, bitmapY; private float rectX = -1, rectY = -1; - private Bitmap bitmapToEdit; private float bitmapGlobalScale = 1; private float bitmapGlobalX = 0; private float bitmapGlobalY = 0; private PhotoCropViewDelegate delegate; + private Bitmap bitmapToEdit; private RectF animationStartValues; private RectF animationEndValues; @@ -472,6 +473,11 @@ public float getLimitHeight() { } private Bitmap createBitmap(int x, int y, int w, int h) { + Bitmap newBimap = delegate.getBitmap(); + if (newBimap != null) { + bitmapToEdit = newBimap; + } + Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG); @@ -495,6 +501,11 @@ private Bitmap createBitmap(int x, int y, int w, int h) { } public Bitmap getBitmap() { + Bitmap newBimap = delegate.getBitmap(); + if (newBimap != null) { + bitmapToEdit = newBimap; + } + float bitmapScaledWidth = bitmapWidth * bitmapGlobalScale; float bitmapScaledHeight = bitmapHeight * bitmapGlobalScale; float bitmapStartX = (getWidth() - AndroidUtilities.dp(28) - bitmapScaledWidth) / 2 + bitmapGlobalX + AndroidUtilities.dp(14); @@ -658,6 +669,11 @@ public void setDelegate(PhotoCropViewDelegate delegate) { protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); + Bitmap newBimap = delegate.getBitmap(); + if (newBimap != null) { + bitmapToEdit = newBimap; + } + if (bitmapToEdit == null) { return; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterView.java index 2eb4445a1b..c7825462ef 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterView.java @@ -2006,7 +2006,9 @@ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, final int width, eglThread.postRunnable(new Runnable() { @Override public void run() { - eglThread.requestRender(false); + if (eglThread != null) { + eglThread.requestRender(false); + } } }); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java index 505cbb9011..ffbf634669 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java @@ -343,6 +343,11 @@ public void onGifTab(boolean opened) { public void onStickersTab(boolean opened) { } + + @Override + public void onClearEmojiRecent() { + + } }); sizeNotifierLayout.addView(emojiView); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SimpleTextView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SimpleTextView.java index e955b85470..4b135739f9 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SimpleTextView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SimpleTextView.java @@ -84,6 +84,7 @@ private void createLayout(int width) { } else { offsetX = 0; } + offsetX += getPaddingLeft(); } } catch (Exception e) { //ignore @@ -94,7 +95,7 @@ private void createLayout(int width) { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (changed) { - createLayout(right - left); + createLayout(right - left - getPaddingLeft() - getPaddingRight()); invalidate(); wasLayout = true; } @@ -103,7 +104,7 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto public void setText(CharSequence value) { text = value; if (wasLayout) { - createLayout(getMeasuredWidth()); + createLayout(getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); invalidate(); } else { requestLayout(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Switch.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Switch.java index 2f93be6367..217e6b3683 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Switch.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Switch.java @@ -391,6 +391,7 @@ public void toggle() { protected void onAttachedToWindow() { super.onAttachedToWindow(); attachedToWindow = true; + requestLayout(); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java index 2b7e44622d..6785b376db 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java @@ -40,6 +40,7 @@ import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.SecretChatHelper; +import org.telegram.messenger.UserConfig; import org.telegram.messenger.UserObject; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.ContactsController; @@ -289,12 +290,17 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { didSelectResult(user, true, null); } else { if (createSecretChat) { + if (user.id == UserConfig.getClientUserId()) { + return; + } creatingChat = true; SecretChatHelper.getInstance().startSecretChat(getParentActivity(), user); } else { Bundle args = new Bundle(); args.putInt("user_id", user.id); - presentFragment(new ChatActivity(args), true); + if (MessagesController.checkCanOpenChat(args, ContactsActivity.this)) { + presentFragment(new ChatActivity(args), true); + } } } } else { @@ -330,6 +336,7 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { args.putBoolean("onlyUsers", true); args.putBoolean("destroyAfterSelect", true); args.putBoolean("createSecretChat", true); + args.putBoolean("allowBots", false); presentFragment(new ContactsActivity(args), false); } else if (row == 2) { if (!MessagesController.isFeatureEnabled("broadcast_create", ContactsActivity.this)) { @@ -363,7 +370,9 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { } else { Bundle args = new Bundle(); args.putInt("user_id", user.id); - presentFragment(new ChatActivity(args), true); + if (MessagesController.checkCanOpenChat(args, ContactsActivity.this)) { + presentFragment(new ChatActivity(args), true); + } } } } else if (item instanceof ContactsController.Contact) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ConvertGroupActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ConvertGroupActivity.java new file mode 100644 index 0000000000..3af4bb4ca0 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/ConvertGroupActivity.java @@ -0,0 +1,216 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2016. + */ + +package org.telegram.ui; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.FrameLayout; +import android.widget.ListView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.Adapters.BaseFragmentAdapter; +import org.telegram.ui.Cells.TextInfoPrivacyCell; +import org.telegram.ui.Cells.TextSettingsCell; +import org.telegram.ui.Components.LayoutHelper; + +public class ConvertGroupActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { + + private ListAdapter listAdapter; + + private int convertInfoRow; + private int convertRow; + private int convertDetailRow; + private int rowCount; + + private int chat_id; + + public ConvertGroupActivity(Bundle args) { + super(args); + chat_id = args.getInt("chat_id"); + } + + @Override + public boolean onFragmentCreate() { + super.onFragmentCreate(); + + convertInfoRow = rowCount++; + convertRow = rowCount++; + convertDetailRow = rowCount++; + + NotificationCenter.getInstance().addObserver(this, NotificationCenter.closeChats); + + return true; + } + + @Override + public void onFragmentDestroy() { + super.onFragmentDestroy(); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.closeChats); + } + + @Override + public View createView(Context context) { + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setAllowOverlayTitle(true); + actionBar.setTitle(LocaleController.getString("ConvertGroup", R.string.ConvertGroup)); + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } + } + }); + + listAdapter = new ListAdapter(context); + + fragmentView = new FrameLayout(context); + FrameLayout frameLayout = (FrameLayout) fragmentView; + frameLayout.setBackgroundColor(0xfff0f0f0); + + ListView listView = new ListView(context); + listView.setDivider(null); + listView.setDividerHeight(0); + listView.setVerticalScrollBarEnabled(false); + listView.setDrawSelectorOnTop(true); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + listView.setAdapter(listAdapter); + listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(final AdapterView adapterView, View view, final int i, long l) { + if (i == convertRow) { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setMessage(LocaleController.getString("ConvertGroupAlert", R.string.ConvertGroupAlert)); + builder.setTitle(LocaleController.getString("ConvertGroupAlertWarning", R.string.ConvertGroupAlertWarning)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + MessagesController.getInstance().convertToMegaGroup(getParentActivity(), chat_id); + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + } + } + }); + + return fragmentView; + } + + @Override + public void onResume() { + super.onResume(); + if (listAdapter != null) { + listAdapter.notifyDataSetChanged(); + } + } + + @Override + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.closeChats) { + removeSelfFromStack(); + } + } + + private class ListAdapter extends BaseFragmentAdapter { + private Context mContext; + + public ListAdapter(Context context) { + mContext = context; + } + + @Override + public boolean areAllItemsEnabled() { + return false; + } + + @Override + public boolean isEnabled(int i) { + return i == convertRow; + } + + @Override + public int getCount() { + return rowCount; + } + + @Override + public Object getItem(int i) { + return null; + } + + @Override + public long getItemId(int i) { + return i; + } + + @Override + public boolean hasStableIds() { + return false; + } + + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + int type = getItemViewType(i); + if (type == 0) { + if (view == null) { + view = new TextSettingsCell(mContext); + view.setBackgroundColor(0xffffffff); + } + TextSettingsCell textCell = (TextSettingsCell) view; + if (i == convertRow) { + textCell.setText(LocaleController.getString("ConvertGroup", R.string.ConvertGroup), false); + } + } else if (type == 1) { + if (view == null) { + view = new TextInfoPrivacyCell(mContext); + } + if (i == convertInfoRow) { + ((TextInfoPrivacyCell) view).setText(AndroidUtilities.replaceTags(LocaleController.getString("ConvertGroupInfo2", R.string.ConvertGroupInfo2))); + view.setBackgroundResource(R.drawable.greydivider); + } else if (i == convertDetailRow) { + ((TextInfoPrivacyCell) view).setText(AndroidUtilities.replaceTags(LocaleController.getString("ConvertGroupInfo3", R.string.ConvertGroupInfo3))); + view.setBackgroundResource(R.drawable.greydivider_bottom); + } + } + return view; + } + + @Override + public int getItemViewType(int i) { + if (i == convertRow) { + return 0; + } else if (i == convertInfoRow || i == convertDetailRow) { + return 1; + } + return 0; + } + + @Override + public int getViewTypeCount() { + return 2; + } + + @Override + public boolean isEmpty() { + return false; + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java index 15cd7859ae..a6b256bfe9 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java @@ -147,6 +147,7 @@ public boolean onFragmentCreate() { NotificationCenter.getInstance().addObserver(this, NotificationCenter.messageSendError); NotificationCenter.getInstance().addObserver(this, NotificationCenter.didSetPasscode); NotificationCenter.getInstance().addObserver(this, NotificationCenter.needReloadRecentDialogsSearch); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didLoadedReplyMessages); } @@ -175,6 +176,7 @@ public void onFragmentDestroy() { NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messageSendError); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didSetPasscode); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.needReloadRecentDialogsSearch); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didLoadedReplyMessages); } delegate = null; } @@ -429,10 +431,14 @@ public void onItemClick(View view, int position) { } } if (searchString != null) { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); - presentFragment(new ChatActivity(args)); + if (MessagesController.checkCanOpenChat(args, DialogsActivity.this)) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); + presentFragment(new ChatActivity(args)); + } } else { - presentFragment(new ChatActivity(args)); + if (MessagesController.checkCanOpenChat(args, DialogsActivity.this)) { + presentFragment(new ChatActivity(args)); + } } } } @@ -896,9 +902,7 @@ public void didReceivedNotification(int id, Object... args) { } } } else if (id == NotificationCenter.emojiDidLoaded) { - if (listView != null) { - updateVisibleRows(0); - } + updateVisibleRows(0); } else if (id == NotificationCenter.updateInterfaces) { updateVisibleRows((Integer) args[0]); } else if (id == NotificationCenter.appDidLogout) { @@ -933,6 +937,8 @@ public void didReceivedNotification(int id, Object... args) { if (dialogsSearchAdapter != null) { dialogsSearchAdapter.loadRecentSearch(); } + } else if (id == NotificationCenter.didLoadedReplyMessages) { + updateVisibleRows(0); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java index 5339c3d002..469f469ff6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java @@ -78,7 +78,7 @@ public interface GroupCreateActivityDelegate { private int beforeChangeIndex; private boolean ignoreChange; private CharSequence changeString; - private int maxCount = 1000; + private int maxCount = 5000; private int chatType = ChatObject.CHAT_TYPE_CHAT; private boolean isAlwaysShare; private boolean isNeverShare; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java index c2c220b7be..4cde07fd7b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java @@ -450,7 +450,7 @@ public boolean isEnabled(int position) { @Override public View getView(int i, View view, ViewGroup viewGroup) { if (view == null) { - view = new UserCell(mContext, 1, 0); + view = new UserCell(mContext, 1, 0, false); } TLRPC.User user = MessagesController.getInstance().getUser(selectedContacts.get(i)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java index 213d5155a7..e42fabcfe8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java @@ -127,7 +127,7 @@ protected void onCreate(Bundle savedInstanceState) { return; } if (intent != null && !intent.getBooleanExtra("fromIntro", false)) { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("logininfo", MODE_PRIVATE); + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("logininfo2", MODE_PRIVATE); Map state = preferences.getAll(); if (state.isEmpty()) { Intent intent2 = new Intent(this, IntroActivity.class); @@ -295,6 +295,7 @@ public void onItemClick(AdapterView parent, View view, int position, long id) args.putBoolean("onlyUsers", true); args.putBoolean("destroyAfterSelect", true); args.putBoolean("createSecretChat", true); + args.putBoolean("allowBots", false); presentFragment(new ContactsActivity(args)); drawerLayoutContainer.closeDrawer(false); } else if (position == 4) { @@ -768,7 +769,7 @@ private boolean handleIntent(Intent intent, boolean isNew, boolean restore, bool if (scheme != null) { if ((scheme.equals("http") || scheme.equals("https"))) { String host = data.getHost().toLowerCase(); - if (host.equals("telegram.me")) { + if (host.equals("telegram.me") || host.equals("telegram.dog")) { String path = data.getPath(); if (path != null && path.length() > 1) { path = path.substring(1); @@ -882,16 +883,20 @@ private boolean handleIntent(Intent intent, boolean isNew, boolean restore, bool if (push_user_id != 0) { Bundle args = new Bundle(); args.putInt("user_id", push_user_id); - ChatActivity fragment = new ChatActivity(args); - if (actionBarLayout.presentFragment(fragment, false, true, true)) { - pushOpened = true; + if (mainFragmentsStack.isEmpty() || MessagesController.checkCanOpenChat(args, mainFragmentsStack.get(mainFragmentsStack.size() - 1))) { + ChatActivity fragment = new ChatActivity(args); + if (actionBarLayout.presentFragment(fragment, false, true, true)) { + pushOpened = true; + } } } else if (push_chat_id != 0) { Bundle args = new Bundle(); args.putInt("chat_id", push_chat_id); - ChatActivity fragment = new ChatActivity(args); - if (actionBarLayout.presentFragment(fragment, false, true, true)) { - pushOpened = true; + if (mainFragmentsStack.isEmpty() || MessagesController.checkCanOpenChat(args, mainFragmentsStack.get(mainFragmentsStack.size() - 1))) { + ChatActivity fragment = new ChatActivity(args); + if (actionBarLayout.presentFragment(fragment, false, true, true)) { + pushOpened = true; + } } } else if (push_enc_id != 0) { Bundle args = new Bundle(); @@ -1071,12 +1076,14 @@ public void run() { fragment.setDelegate(new DialogsActivity.MessagesActivityDelegate() { @Override public void didSelectDialog(DialogsActivity fragment, long did, boolean param) { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); - MessagesController.getInstance().addUserToChat(-(int) did, user, null, 0, botChat, null); Bundle args = new Bundle(); args.putBoolean("scrollToTopOnResume", true); args.putInt("chat_id", -(int) did); - actionBarLayout.presentFragment(new ChatActivity(args), true, false, true); + if (mainFragmentsStack.isEmpty() || MessagesController.checkCanOpenChat(args, mainFragmentsStack.get(mainFragmentsStack.size() - 1))) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); + MessagesController.getInstance().addUserToChat(-(int) did, user, null, 0, botChat, null); + actionBarLayout.presentFragment(new ChatActivity(args), true, false, true); + } } }); presentFragment(fragment); @@ -1093,9 +1100,11 @@ public void didSelectDialog(DialogsActivity fragment, long did, boolean param) { if (messageId != null) { args.putInt("message_id", messageId); } - ChatActivity fragment = new ChatActivity(args); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); - actionBarLayout.presentFragment(fragment, false, true, true); + if (mainFragmentsStack.isEmpty() || MessagesController.checkCanOpenChat(args, mainFragmentsStack.get(mainFragmentsStack.size() - 1))) { + ChatActivity fragment = new ChatActivity(args); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); + actionBarLayout.presentFragment(fragment, false, true, true); + } } } else { try { @@ -1134,9 +1143,11 @@ public void run() { MessagesStorage.getInstance().putUsersAndChats(null, chats, false, true); Bundle args = new Bundle(); args.putInt("chat_id", invite.chat.id); - ChatActivity fragment = new ChatActivity(args); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); - actionBarLayout.presentFragment(fragment, false, true, true); + if (mainFragmentsStack.isEmpty() || MessagesController.checkCanOpenChat(args, mainFragmentsStack.get(mainFragmentsStack.size() - 1))) { + ChatActivity fragment = new ChatActivity(args); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); + actionBarLayout.presentFragment(fragment, false, true, true); + } } else { AlertDialog.Builder builder = new AlertDialog.Builder(LaunchActivity.this); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); @@ -1200,9 +1211,11 @@ public void run() { MessagesController.getInstance().putChats(updates.chats, false); Bundle args = new Bundle(); args.putInt("chat_id", chat.id); - ChatActivity fragment = new ChatActivity(args); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); - actionBarLayout.presentFragment(fragment, false, true, true); + if (mainFragmentsStack.isEmpty() || MessagesController.checkCanOpenChat(args, mainFragmentsStack.get(mainFragmentsStack.size() - 1))) { + ChatActivity fragment = new ChatActivity(args); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); + actionBarLayout.presentFragment(fragment, false, true, true); + } } } } else { @@ -1238,11 +1251,6 @@ public void run() { fragment.setDelegate(new DialogsActivity.MessagesActivityDelegate() { @Override public void didSelectDialog(DialogsActivity fragment, long did, boolean param) { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); - SharedPreferences.Editor editor = preferences.edit(); - editor.putString("dialog_" + did, message); - editor.commit(); Bundle args = new Bundle(); args.putBoolean("scrollToTopOnResume", true); args.putBoolean("hasUrl", hasUrl); @@ -1261,7 +1269,14 @@ public void didSelectDialog(DialogsActivity fragment, long did, boolean param) { } else { args.putInt("enc_id", high_id); } - actionBarLayout.presentFragment(new ChatActivity(args), true, false, true); + if (MessagesController.checkCanOpenChat(args, fragment)) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putString("dialog_" + did, message); + editor.commit(); + actionBarLayout.presentFragment(new ChatActivity(args), true, false, true); + } } }); presentFragment(fragment, false, true); @@ -1339,6 +1354,9 @@ public void didSelectDialog(DialogsActivity dialogsFragment, long dialog_id, boo } else { args.putInt("enc_id", high_id); } + if (!MessagesController.checkCanOpenChat(args, dialogsFragment)) { + return; + } ChatActivity fragment = new ChatActivity(args); if (videoPath != null) { @@ -1495,7 +1513,12 @@ public void fixLayout() { actionBarLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { - needLayout(); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + needLayout(); + } + }); if (actionBarLayout != null) { if (Build.VERSION.SDK_INT < 16) { actionBarLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java index 51e5088b2d..5c8e981302 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java @@ -929,7 +929,11 @@ public void onResume() { super.onResume(); AndroidUtilities.removeAdjustResize(getParentActivity(), classGuid); if (mapView != null) { - mapView.onResume(); + try { + mapView.onResume(); + } catch (Throwable e) { + FileLog.e("tmessages", e); + } } updateUserData(); fixLayoutInternal(true); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java index 358bc11274..bb64916882 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java @@ -8,8 +8,10 @@ package org.telegram.ui; +import android.Manifest; import android.animation.Animator; import android.annotation.SuppressLint; +import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.ProgressDialog; @@ -18,6 +20,9 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.Typeface; import android.os.Build; import android.os.Bundle; @@ -26,8 +31,6 @@ import android.text.Editable; import android.text.InputFilter; import android.text.InputType; -import android.text.SpannableStringBuilder; -import android.text.Spanned; import android.text.TextUtils; import android.text.TextWatcher; import android.text.method.PasswordTransformationMethod; @@ -40,6 +43,7 @@ import android.widget.AdapterView; import android.widget.EditText; import android.widget.FrameLayout; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; @@ -67,7 +71,6 @@ import org.telegram.ui.Components.HintEditText; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.SlideView; -import org.telegram.ui.Components.TypefaceSpan; import java.io.BufferedReader; import java.io.InputStreamReader; @@ -84,17 +87,21 @@ public class LoginActivity extends BaseFragment { private int currentViewNum = 0; - private SlideView[] views = new SlideView[5]; + private SlideView[] views = new SlideView[8]; private ProgressDialog progressDialog; + private Dialog permissionsDialog; + private ArrayList permissionsItems = new ArrayList<>(); + private boolean checkPermissions = true; + private View doneButton; private final static int done_button = 1; @Override public void onFragmentDestroy() { super.onFragmentDestroy(); - for (SlideView v : views) { - if (v != null) { - v.onDestroyActivity(); + for (int a = 0; a < views.length; a++) { + if (views[a] != null) { + views[a].onDestroyActivity(); } } if (progressDialog != null) { @@ -123,7 +130,7 @@ public void onItemClick(int id) { }); ActionBarMenu menu = actionBar.createMenu(); - menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); + doneButton = menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); fragmentView = new ScrollView(context); ScrollView scrollView = (ScrollView) fragmentView; @@ -133,12 +140,15 @@ public void onItemClick(int id) { scrollView.addView(frameLayout, LayoutHelper.createScroll(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT)); views[0] = new PhoneView(context); - views[1] = new LoginActivitySmsView(context); - views[2] = new LoginActivityRegisterView(context); - views[3] = new LoginActivityPasswordView(context); - views[4] = new LoginActivityRecoverView(context); + views[1] = new LoginActivitySmsView(context, 1); + views[2] = new LoginActivitySmsView(context, 2); + views[3] = new LoginActivitySmsView(context, 3); + views[4] = new LoginActivitySmsView(context, 4); + views[5] = new LoginActivityRegisterView(context); + views[6] = new LoginActivityPasswordView(context); + views[7] = new LoginActivityRecoverView(context); - for (int a = 0; a < 5; a++) { + for (int a = 0; a < views.length; a++) { views[a].setVisibility(a == 0 ? View.VISIBLE : View.GONE); frameLayout.addView(views[a], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, a == 0 ? LayoutHelper.WRAP_CONTENT : LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, AndroidUtilities.isTablet() ? 26 : 18, 30, AndroidUtilities.isTablet() ? 26 : 18, 0)); } @@ -146,7 +156,7 @@ public void onItemClick(int id) { Bundle savedInstanceState = loadCurrentState(); if (savedInstanceState != null) { currentViewNum = savedInstanceState.getInt("currentViewNum", 0); - if (currentViewNum == 1) { + if (currentViewNum >= 1 && currentViewNum <= 4) { int time = savedInstanceState.getInt("open"); if (time != 0 && Math.abs(System.currentTimeMillis() / 1000 - time) >= 24 * 60 * 60) { currentViewNum = 0; @@ -158,12 +168,21 @@ public void onItemClick(int id) { actionBar.setTitle(views[currentViewNum].getHeaderName()); for (int a = 0; a < views.length; a++) { if (savedInstanceState != null) { - views[a].restoreStateParams(savedInstanceState); + if (a >= 1 && a <= 4) { + if (a == currentViewNum) { + views[a].restoreStateParams(savedInstanceState); + } + } else { + views[a].restoreStateParams(savedInstanceState); + } } if (currentViewNum == a) { actionBar.setBackButtonImage(views[a].needBackButton() ? R.drawable.ic_ab_back : 0); views[a].setVisibility(View.VISIBLE); views[a].onShow(); + if (a == 3) { + doneButton.setVisibility(View.GONE); + } } else { views[a].setVisibility(View.GONE); } @@ -183,10 +202,10 @@ public void onResume() { super.onResume(); AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid); try { - if (currentViewNum == 1 && views[1] instanceof LoginActivitySmsView) { - int time = ((LoginActivitySmsView) views[1]).openTime; + if (currentViewNum >= 1 && currentViewNum <= 4 && views[currentViewNum] instanceof LoginActivitySmsView) { + int time = ((LoginActivitySmsView) views[currentViewNum]).openTime; if (time != 0 && Math.abs(System.currentTimeMillis() / 1000 - time) >= 24 * 60 * 60) { - views[1].onBackPressed(); + views[currentViewNum].onBackPressed(); setPage(0, false, null, true); } } @@ -195,10 +214,20 @@ public void onResume() { } } + @Override + public void onRequestPermissionsResultFragment(int requestCode, String[] permissions, int[] grantResults) { + if (requestCode == 6) { + checkPermissions = false; + if (currentViewNum == 0) { + views[currentViewNum].onNextPressed(); + } + } + } + private Bundle loadCurrentState() { try { Bundle bundle = new Bundle(); - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("logininfo", Context.MODE_PRIVATE); + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("logininfo2", Context.MODE_PRIVATE); Map params = preferences.getAll(); for (Map.Entry entry : params.entrySet()) { String key = entry.getKey(); @@ -231,7 +260,7 @@ private Bundle loadCurrentState() { } private void clearCurrentState() { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("logininfo", Context.MODE_PRIVATE); + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("logininfo2", Context.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); editor.clear(); editor.commit(); @@ -259,22 +288,29 @@ private void putBundleToEditor(Bundle bundle, SharedPreferences.Editor editor, S } } + @Override + protected void onDialogDismiss(Dialog dialog) { + if (Build.VERSION.SDK_INT >= 23 && dialog == permissionsDialog && !permissionsItems.isEmpty()) { + getParentActivity().requestPermissions(permissionsItems.toArray(new String[permissionsItems.size()]), 6); + } + } + @Override public boolean onBackPressed() { if (currentViewNum == 0) { - for (SlideView v : views) { - if (v != null) { - v.onDestroyActivity(); + for (int a = 0; a < views.length; a++) { + if (views[a] != null) { + views[a].onDestroyActivity(); } } clearCurrentState(); return true; - } else if (currentViewNum == 3) { + } else if (currentViewNum == 6) { views[currentViewNum].onBackPressed(); setPage(0, true, null, true); - } else if (currentViewNum == 4) { + } else if (currentViewNum == 7) { views[currentViewNum].onBackPressed(); - setPage(3, true, null, true); + setPage(6, true, null, true); } return false; } @@ -314,6 +350,14 @@ public void needHideProgress() { } public void setPage(int page, boolean animated, Bundle params, boolean back) { + if (page == 3) { + doneButton.setVisibility(View.GONE); + } else { + if (page == 0) { + checkPermissions = true; + } + doneButton.setVisibility(View.VISIBLE); + } if (android.os.Build.VERSION.SDK_INT > 13 && animated) { final SlideView outView = views[currentViewNum]; final SlideView newView = views[page]; @@ -384,7 +428,7 @@ public void saveSelfArgs(Bundle outState) { v.saveStateParams(bundle); } } - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("logininfo", Context.MODE_PRIVATE); + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("logininfo2", Context.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); editor.clear(); putBundleToEditor(bundle, editor, null); @@ -394,13 +438,47 @@ public void saveSelfArgs(Bundle outState) { } } - public void needFinishActivity() { + private void needFinishActivity() { clearCurrentState(); presentFragment(new DialogsActivity(null), true); NotificationCenter.getInstance().postNotificationName(NotificationCenter.mainUserInfoChanged); } - public class PhoneView extends SlideView implements AdapterView.OnItemSelectedListener, NotificationCenter.NotificationCenterDelegate { + private void fillNextCodeParams(Bundle params, TLRPC.TL_auth_sentCode res) { + params.putString("phoneHash", res.phone_code_hash); + if (res.next_type instanceof TLRPC.TL_auth_codeTypeCall) { + params.putInt("nextType", 4); + } else if (res.next_type instanceof TLRPC.TL_auth_codeTypeFlashCall) { + params.putInt("nextType", 3); + } else if (res.next_type instanceof TLRPC.TL_auth_codeTypeSms) { + params.putInt("nextType", 2); + } + if (res.type instanceof TLRPC.TL_auth_sentCodeTypeApp) { + params.putInt("type", 1); + params.putInt("length", res.type.length); + setPage(1, true, params, false); + } else { + if (res.timeout == 0) { + res.timeout = 60; + } + params.putInt("timeout", res.timeout * 1000); + if (res.type instanceof TLRPC.TL_auth_sentCodeTypeCall) { + params.putInt("type", 4); + params.putInt("length", res.type.length); + setPage(4, true, params, false); + } else if (res.type instanceof TLRPC.TL_auth_sentCodeTypeFlashCall) { + params.putInt("type", 3); + params.putString("pattern", res.type.pattern); + setPage(3, true, params, false); + } else if (res.type instanceof TLRPC.TL_auth_sentCodeTypeSms) { + params.putInt("type", 2); + params.putInt("length", res.type.length); + setPage(2, true, params, false); + } + } + } + + public class PhoneView extends SlideView implements AdapterView.OnItemSelectedListener { private EditText codeField; private HintEditText phoneField; @@ -418,7 +496,7 @@ public class PhoneView extends SlideView implements AdapterView.OnItemSelectedLi private boolean ignoreOnPhoneChange = false; private boolean nextPressed = false; - public PhoneView(final Context context) { + public PhoneView(Context context) { super(context); setOrientation(VERTICAL); @@ -729,11 +807,9 @@ public int compare(String lhs, String rhs) { } if (codeField.length() != 0) { - AndroidUtilities.showKeyboard(phoneField); phoneField.requestFocus(); phoneField.setSelection(phoneField.length()); } else { - AndroidUtilities.showKeyboard(codeField); codeField.requestFocus(); } } @@ -752,17 +828,6 @@ public void selectCountry(String name) { } } - @Override - public void didReceivedNotification(int id, final Object... args) { - /*if (id == NotificationCenter.didReceiveCall) { - if (codeField != null) { - String phone = (String) args[0]; - phone = PhoneFormat.stripExceptNumbers(phone); - codeField.setText(phone); - } - }*/ - } - @Override public void onItemSelected(AdapterView adapterView, View view, int i, long l) { if (ignoreSelection) { @@ -782,9 +847,46 @@ public void onNothingSelected(AdapterView adapterView) { @Override public void onNextPressed() { - if (nextPressed) { + if (getParentActivity() == null || nextPressed) { return; } + TelephonyManager tm = (TelephonyManager) ApplicationLoader.applicationContext.getSystemService(Context.TELEPHONY_SERVICE); + boolean simcardAvailable = tm.getSimState() != TelephonyManager.SIM_STATE_ABSENT && tm.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE; + boolean allowCall = true; + if (Build.VERSION.SDK_INT >= 23 && simcardAvailable) { + allowCall = getParentActivity().checkSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; + boolean allowSms = getParentActivity().checkSelfPermission(Manifest.permission.RECEIVE_SMS) == PackageManager.PERMISSION_GRANTED; + if (checkPermissions) { + permissionsItems.clear(); + if (!allowCall) { + permissionsItems.add(Manifest.permission.READ_PHONE_STATE); + } + if (!allowSms) { + permissionsItems.add(Manifest.permission.RECEIVE_SMS); + } + if (!permissionsItems.isEmpty()) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + if (preferences.getBoolean("firstlogin", true) || getParentActivity().shouldShowRequestPermissionRationale(Manifest.permission.READ_PHONE_STATE) || getParentActivity().shouldShowRequestPermissionRationale(Manifest.permission.RECEIVE_SMS)) { + preferences.edit().putBoolean("firstlogin", false).commit(); + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + if (permissionsItems.size() == 2) { + builder.setMessage(LocaleController.getString("AllowReadCallAndSms", R.string.AllowReadCallAndSms)); + } else if (!allowSms) { + builder.setMessage(LocaleController.getString("AllowReadSms", R.string.AllowReadSms)); + } else { + builder.setMessage(LocaleController.getString("AllowReadCall", R.string.AllowReadCall)); + } + permissionsDialog = showDialog(builder.create()); + } else { + getParentActivity().requestPermissions(permissionsItems.toArray(new String[permissionsItems.size()]), 6); + } + return; + } + } + } + if (countryState == 1) { needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("ChooseCountry", R.string.ChooseCountry)); return; @@ -796,7 +898,6 @@ public void onNextPressed() { needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("InvalidPhoneNumber", R.string.InvalidPhoneNumber)); return; } - //NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveCall); ConnectionsManager.getInstance().cleanUp(); TLRPC.TL_auth_sendCode req = new TLRPC.TL_auth_sendCode(); @@ -804,12 +905,16 @@ public void onNextPressed() { ConnectionsManager.getInstance().applyCountryPortNumber(phone); req.api_hash = BuildVars.APP_HASH; req.api_id = BuildVars.APP_ID; - req.sms_type = 0; req.phone_number = phone; req.lang_code = LocaleController.getLocaleString(LocaleController.getInstance().getSystemDefaultLocale()); if (req.lang_code.length() == 0) { req.lang_code = "en"; } + req.allow_flashcall = simcardAvailable && allowCall; + if (req.allow_flashcall) { + String number = tm.getLine1Number(); + req.current_number = number != null && number.length() != 0 && (phone.contains(number) || number.contains(phone)); + } final Bundle params = new Bundle(); params.putString("phone", "+" + codeField.getText() + phoneField.getText()); @@ -830,10 +935,7 @@ public void run(final TLObject response, final TLRPC.TL_error error) { public void run() { nextPressed = false; if (error == null) { - final TLRPC.TL_auth_sentCode res = (TLRPC.TL_auth_sentCode) response; - params.putString("phoneHash", res.phone_code_hash); - params.putInt("calltime", res.send_call_timeout * 1000); - setPage(1, true, params, false); + fillNextCodeParams(params, (TLRPC.TL_auth_sentCode) response); } else { if (error.text != null) { if (error.text.contains("PHONE_NUMBER_INVALID")) { @@ -869,7 +971,6 @@ public void onShow() { codeField.requestFocus(); } } - //NotificationCenter.getInstance().addObserver(this, NotificationCenter.didReceiveCall); } @Override @@ -904,6 +1005,32 @@ public void restoreStateParams(Bundle bundle) { public class LoginActivitySmsView extends SlideView implements NotificationCenter.NotificationCenterDelegate { + private class ProgressView extends View { + + private Paint paint = new Paint(); + private Paint paint2 = new Paint(); + private float progress; + + public ProgressView(Context context) { + super(context); + paint.setColor(0xffe1eaf2); + paint2.setColor(0xff62a0d0); + } + + public void setProgress(float value) { + progress = value; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + int start = (int) (getMeasuredWidth() * progress); + canvas.drawRect(0, 0, start, getMeasuredHeight(), paint2); + canvas.drawRect(start, 0, getMeasuredWidth(), getMeasuredHeight(), paint); + } + } + + private String phone; private String phoneHash; private String requestPhone; private String emailPhone; @@ -912,6 +1039,7 @@ public class LoginActivitySmsView extends SlideView implements NotificationCente private TextView timeText; private TextView problemText; private Bundle currentParams; + private ProgressView progressView; private Timer timeTimer; private Timer codeTimer; @@ -921,14 +1049,20 @@ public class LoginActivitySmsView extends SlideView implements NotificationCente private volatile int codeTime = 15000; private double lastCurrentTime; private double lastCodeTime; - private boolean ignoreOnTextChange = false; - private boolean waitingForSms = false; - private boolean nextPressed = false; + private boolean ignoreOnTextChange; + private boolean waitingForEvent; + private boolean nextPressed; private String lastError = ""; + private int currentType; + private int nextType; + private String pattern = "*"; + private int length; + private int timeout; - public LoginActivitySmsView(Context context) { + public LoginActivitySmsView(Context context, final int type) { super(context); + currentType = type; setOrientation(VERTICAL); confirmTextView = new TextView(context); @@ -936,7 +1070,23 @@ public LoginActivitySmsView(Context context) { confirmTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); confirmTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); confirmTextView.setLineSpacing(AndroidUtilities.dp(2), 1.0f); - addView(confirmTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); + + if (currentType == 3) { + FrameLayout frameLayout = new FrameLayout(context); + + ImageView imageView = new ImageView(context); + imageView.setImageResource(R.drawable.phone_activate); + if (LocaleController.isRTL) { + frameLayout.addView(imageView, LayoutHelper.createFrame(64, 76, Gravity.LEFT | Gravity.CENTER_VERTICAL, 2, 2, 0, 0)); + frameLayout.addView(confirmTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 64 + 18, 0, 0, 0)); + } else { + frameLayout.addView(confirmTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 0, 64 + 18, 0)); + frameLayout.addView(imageView, LayoutHelper.createFrame(64, 76, Gravity.RIGHT | Gravity.CENTER_VERTICAL, 0, 2, 0, 2)); + } + addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); + } else { + addView(confirmTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); + } codeField = new EditText(context); codeField.setTextColor(0xff212121); @@ -948,9 +1098,6 @@ public LoginActivitySmsView(Context context) { codeField.setInputType(InputType.TYPE_CLASS_PHONE); codeField.setMaxLines(1); codeField.setPadding(0, 0, 0, 0); - InputFilter[] inputFilters = new InputFilter[1]; - inputFilters[0] = new InputFilter.LengthFilter(5); - codeField.setFilters(inputFilters); addView(codeField, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, Gravity.CENTER_HORIZONTAL, 0, 20, 0, 0)); codeField.addTextChangedListener(new TextWatcher() { @Override @@ -968,7 +1115,7 @@ public void afterTextChanged(Editable s) { if (ignoreOnTextChange) { return; } - if (codeField.length() == 5) { + if (length != 0 && codeField.length() == length) { onNextPressed(); } } @@ -983,6 +1130,11 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { return false; } }); + if (currentType == 3) { + codeField.setEnabled(false); + codeField.setInputType(InputType.TYPE_NULL); + codeField.setVisibility(GONE); + } timeText = new TextView(context); timeText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); @@ -991,9 +1143,13 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { timeText.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); addView(timeText, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 30, 0, 0)); + if (currentType == 3) { + progressView = new ProgressView(context); + addView(progressView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 3, 0, 12, 0, 0)); + } + problemText = new TextView(context); problemText.setText(LocaleController.getString("DidNotGetTheCode", R.string.DidNotGetTheCode)); - problemText.setVisibility(time < 1000 ? VISIBLE : GONE); problemText.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); problemText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); problemText.setTextColor(0xff4d83b3); @@ -1003,18 +1159,25 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { problemText.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - try { - PackageInfo pInfo = ApplicationLoader.applicationContext.getPackageManager().getPackageInfo(ApplicationLoader.applicationContext.getPackageName(), 0); - String version = String.format(Locale.US, "%s (%d)", pInfo.versionName, pInfo.versionCode); - - Intent mailer = new Intent(Intent.ACTION_SEND); - mailer.setType("message/rfc822"); - mailer.putExtra(Intent.EXTRA_EMAIL, new String[]{"sms@stel.com"}); - mailer.putExtra(Intent.EXTRA_SUBJECT, "Android registration/login issue " + version + " " + emailPhone); - mailer.putExtra(Intent.EXTRA_TEXT, "Phone: " + requestPhone + "\nApp version: " + version + "\nOS version: SDK " + Build.VERSION.SDK_INT + "\nDevice Name: " + Build.MANUFACTURER + Build.MODEL + "\nLocale: " + Locale.getDefault() + "\nError: " + lastError); - getContext().startActivity(Intent.createChooser(mailer, "Send email...")); - } catch (Exception e) { - needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("NoMailInstalled", R.string.NoMailInstalled)); + if (nextPressed) { + return; + } + if (nextType != 0 && nextType != 4) { + resendCode(); + } else { + try { + PackageInfo pInfo = ApplicationLoader.applicationContext.getPackageManager().getPackageInfo(ApplicationLoader.applicationContext.getPackageName(), 0); + String version = String.format(Locale.US, "%s (%d)", pInfo.versionName, pInfo.versionCode); + + Intent mailer = new Intent(Intent.ACTION_SEND); + mailer.setType("message/rfc822"); + mailer.putExtra(Intent.EXTRA_EMAIL, new String[]{"sms@stel.com"}); + mailer.putExtra(Intent.EXTRA_SUBJECT, "Android registration/login issue " + version + " " + emailPhone); + mailer.putExtra(Intent.EXTRA_TEXT, "Phone: " + requestPhone + "\nApp version: " + version + "\nOS version: SDK " + Build.VERSION.SDK_INT + "\nDevice Name: " + Build.MANUFACTURER + Build.MODEL + "\nLocale: " + Locale.getDefault() + "\nError: " + lastError); + getContext().startActivity(Intent.createChooser(mailer, "Send email...")); + } catch (Exception e) { + needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("NoMailInstalled", R.string.NoMailInstalled)); + } } } }); @@ -1034,12 +1197,66 @@ public void onClick(View v) { wrongNumber.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { + TLRPC.TL_auth_cancelCode req = new TLRPC.TL_auth_cancelCode(); + req.phone_number = requestPhone; + req.phone_code_hash = phoneHash; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }, ConnectionsManager.RequestFlagFailOnServerErrors | ConnectionsManager.RequestFlagWithoutLogin); onBackPressed(); setPage(0, true, null, true); } }); } + private void resendCode() { + final Bundle params = new Bundle(); + params.putString("phone", phone); + params.putString("ephone", emailPhone); + params.putString("phoneFormated", requestPhone); + + nextPressed = true; + needShowProgress(); + + TLRPC.TL_auth_resendCode req = new TLRPC.TL_auth_resendCode(); + req.phone_number = requestPhone; + req.phone_code_hash = phoneHash; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + nextPressed = false; + if (error == null) { + fillNextCodeParams(params, (TLRPC.TL_auth_sentCode) response); + } else { + if (error.text != null) { + if (error.text.contains("PHONE_NUMBER_INVALID")) { + needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("InvalidPhoneNumber", R.string.InvalidPhoneNumber)); + } else if (error.text.contains("PHONE_CODE_EMPTY") || error.text.contains("PHONE_CODE_INVALID")) { + needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("InvalidCode", R.string.InvalidCode)); + } else if (error.text.contains("PHONE_CODE_EXPIRED")) { + onBackPressed(); + setPage(0, true, null, true); + needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("CodeExpired", R.string.CodeExpired)); + } else if (error.text.startsWith("FLOOD_WAIT")) { + needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("FloodWait", R.string.FloodWait)); + } else if (error.code != -1000) { + needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred) + "\n" + error.text); + } + } + } + needHideProgress(); + } + }); + } + }, ConnectionsManager.RequestFlagFailOnServerErrors | ConnectionsManager.RequestFlagWithoutLogin); + } + @Override public String getHeaderName() { return LocaleController.getString("YourCode", R.string.YourCode); @@ -1051,49 +1268,87 @@ public void setParams(Bundle params) { return; } codeField.setText(""); - AndroidUtilities.setWaitingForSms(true); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.didReceiveSmsCode); + waitingForEvent = true; + if (currentType == 2) { + AndroidUtilities.setWaitingForSms(true); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didReceiveSmsCode); + } else if (currentType == 3) { + AndroidUtilities.setWaitingForCall(true); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didReceiveCall); + } + currentParams = params; - waitingForSms = true; - String phone = params.getString("phone"); + phone = params.getString("phone"); emailPhone = params.getString("ephone"); requestPhone = params.getString("phoneFormated"); phoneHash = params.getString("phoneHash"); - time = params.getInt("calltime"); + timeout = time = params.getInt("timeout"); openTime = (int) (System.currentTimeMillis() / 1000); + nextType = params.getInt("nextType"); + pattern = params.getString("pattern"); + length = params.getInt("length"); + + if (length != 0) { + InputFilter[] inputFilters = new InputFilter[1]; + inputFilters[0] = new InputFilter.LengthFilter(length); + codeField.setFilters(inputFilters); + } else { + codeField.setFilters(new InputFilter[0]); + } + if (progressView != null) { + progressView.setVisibility(nextType != 0 ? VISIBLE : GONE); + } if (phone == null) { return; } String number = PhoneFormat.getInstance().format(phone); - String str = String.format(LocaleController.getString("SentSmsCode", R.string.SentSmsCode) + " %s", number); - try { - SpannableStringBuilder stringBuilder = new SpannableStringBuilder(str); - TypefaceSpan span = new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - int idx = str.indexOf(number); - stringBuilder.setSpan(span, idx, idx + number.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - confirmTextView.setText(stringBuilder); - } catch (Exception e) { - FileLog.e("tmessages", e); - confirmTextView.setText(str); + CharSequence str = ""; + if (currentType == 1) { + str = AndroidUtilities.replaceTags(LocaleController.getString("SentAppCode", R.string.SentAppCode)); + } else if (currentType == 2) { + str = AndroidUtilities.replaceTags(LocaleController.formatString("SentSmsCode", R.string.SentSmsCode, number)); + } else if (currentType == 3) { + str = AndroidUtilities.replaceTags(LocaleController.formatString("SentCallCode", R.string.SentCallCode, number)); + } else if (currentType == 4) { + str = AndroidUtilities.replaceTags(LocaleController.formatString("SentCallOnly", R.string.SentCallOnly, number)); + } + confirmTextView.setText(str); + + if (currentType != 3) { + AndroidUtilities.showKeyboard(codeField); + codeField.requestFocus(); + } else { + AndroidUtilities.hideKeyboard(codeField); } - AndroidUtilities.showKeyboard(codeField); - codeField.requestFocus(); - destroyTimer(); destroyCodeTimer(); - if (time >= 3600 * 1000) { + + lastCurrentTime = System.currentTimeMillis(); + if (currentType == 1) { + problemText.setVisibility(VISIBLE); timeText.setVisibility(GONE); + } else if (currentType == 3 && (nextType == 4 || nextType == 2)) { problemText.setVisibility(GONE); - } else { - timeText.setText(LocaleController.formatString("CallText", R.string.CallText, 1, 0)); - lastCurrentTime = System.currentTimeMillis(); + timeText.setVisibility(VISIBLE); + if (nextType == 4) { + timeText.setText(LocaleController.formatString("CallText", R.string.CallText, 1, 0)); + } else if (nextType == 2) { + timeText.setText(LocaleController.formatString("SmsText", R.string.SmsText, 1, 0)); + } + createTimer(); + } else if (currentType == 2 && nextType == 4) { + timeText.setVisibility(VISIBLE); + timeText.setText(LocaleController.formatString("CallText", R.string.CallText, 2, 0)); problemText.setVisibility(time < 1000 ? VISIBLE : GONE); + createTimer(); + } else { + timeText.setVisibility(GONE); + problemText.setVisibility(GONE); + createCodeTimer(); } - - createTimer(); } private void createCodeTimer() { @@ -1137,14 +1392,17 @@ private void destroyCodeTimer() { } private void createTimer() { - if (timeTimer != null || time >= 3600 * 1000) { + if (timeTimer != null) { return; } timeTimer = new Timer(); timeTimer.schedule(new TimerTask() { @Override public void run() { - double currentTime = System.currentTimeMillis(); + if (timeTimer == null) { + return; + } + final double currentTime = System.currentTimeMillis(); double diff = currentTime - lastCurrentTime; time -= diff; lastCurrentTime = currentTime; @@ -1154,27 +1412,45 @@ public void run() { if (time >= 1000) { int minutes = time / 1000 / 60; int seconds = time / 1000 - minutes * 60; - timeText.setText(LocaleController.formatString("CallText", R.string.CallText, minutes, seconds)); + if (nextType == 4) { + timeText.setText(LocaleController.formatString("CallText", R.string.CallText, minutes, seconds)); + } else if (nextType == 2) { + timeText.setText(LocaleController.formatString("SmsText", R.string.SmsText, minutes, seconds)); + } + if (progressView != null) { + progressView.setProgress(1.0f - (float) time / (float) timeout); + } } else { - timeText.setText(LocaleController.getString("Calling", R.string.Calling)); + if (progressView != null) { + progressView.setProgress(1.0f); + } destroyTimer(); - createCodeTimer(); - TLRPC.TL_auth_sendCall req = new TLRPC.TL_auth_sendCall(); - req.phone_number = requestPhone; - req.phone_code_hash = phoneHash; - ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { - @Override - public void run(TLObject response, final TLRPC.TL_error error) { - if (error != null && error.text != null) { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - lastError = error.text; - } - }); + if (currentType == 3) { + AndroidUtilities.setWaitingForCall(false); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveCall); + waitingForEvent = false; + destroyCodeTimer(); + resendCode(); + } else { + timeText.setText(LocaleController.getString("Calling", R.string.Calling)); + createCodeTimer(); + TLRPC.TL_auth_resendCode req = new TLRPC.TL_auth_resendCode(); + req.phone_number = requestPhone; + req.phone_code_hash = phoneHash; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, final TLRPC.TL_error error) { + if (error != null && error.text != null) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + lastError = error.text; + } + }); + } } - } - }, ConnectionsManager.RequestFlagFailOnServerErrors | ConnectionsManager.RequestFlagWithoutLogin); + }, ConnectionsManager.RequestFlagFailOnServerErrors | ConnectionsManager.RequestFlagWithoutLogin); + } } } }); @@ -1201,9 +1477,14 @@ public void onNextPressed() { return; } nextPressed = true; - waitingForSms = false; - AndroidUtilities.setWaitingForSms(false); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveSmsCode); + if (currentType == 2) { + AndroidUtilities.setWaitingForSms(false); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveSmsCode); + } else if (currentType == 3) { + AndroidUtilities.setWaitingForCall(false); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveCall); + } + waitingForEvent = false; final TLRPC.TL_auth_signIn req = new TLRPC.TL_auth_signIn(); req.phone_number = requestPhone; req.phone_code = codeField.getText().toString(); @@ -1244,7 +1525,7 @@ public void run() { params.putString("phoneFormated", requestPhone); params.putString("phoneHash", phoneHash); params.putString("code", req.phone_code); - setPage(2, true, params, false); + setPage(5, true, params, false); destroyTimer(); destroyCodeTimer(); } else if (error.text.contains("SESSION_PASSWORD_NEEDED")) { @@ -1266,7 +1547,7 @@ public void run() { bundle.putString("phoneHash", phoneHash); bundle.putString("code", req.phone_code); bundle.putInt("has_recovery", password.has_recovery ? 1 : 0); - setPage(3, true, bundle, false); + setPage(6, true, bundle, false); } else { needShowAlert(LocaleController.getString("AppName", R.string.AppName), error.text); } @@ -1278,19 +1559,31 @@ public void run() { destroyCodeTimer(); } else { needHideProgress(); - createTimer(); - if (error.text.contains("PHONE_NUMBER_INVALID")) { - needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("InvalidPhoneNumber", R.string.InvalidPhoneNumber)); - } else if (error.text.contains("PHONE_CODE_EMPTY") || error.text.contains("PHONE_CODE_INVALID")) { - needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("InvalidCode", R.string.InvalidCode)); - } else if (error.text.contains("PHONE_CODE_EXPIRED")) { - onBackPressed(); - setPage(0, true, null, true); - needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("CodeExpired", R.string.CodeExpired)); - } else if (error.text.startsWith("FLOOD_WAIT")) { - needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("FloodWait", R.string.FloodWait)); - } else { - needShowAlert(LocaleController.getString("AppName", R.string.AppName), error.text); + if (currentType == 3 && (nextType == 4 || nextType == 2) || currentType == 2 && nextType == 4) { + createTimer(); + } + if (currentType == 2) { + AndroidUtilities.setWaitingForSms(true); + NotificationCenter.getInstance().addObserver(LoginActivitySmsView.this, NotificationCenter.didReceiveSmsCode); + } else if (currentType == 3) { + AndroidUtilities.setWaitingForCall(true); + NotificationCenter.getInstance().addObserver(LoginActivitySmsView.this, NotificationCenter.didReceiveCall); + } + waitingForEvent = true; + if (currentType != 3) { + if (error.text.contains("PHONE_NUMBER_INVALID")) { + needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("InvalidPhoneNumber", R.string.InvalidPhoneNumber)); + } else if (error.text.contains("PHONE_CODE_EMPTY") || error.text.contains("PHONE_CODE_INVALID")) { + needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("InvalidCode", R.string.InvalidCode)); + } else if (error.text.contains("PHONE_CODE_EXPIRED")) { + onBackPressed(); + setPage(0, true, null, true); + needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("CodeExpired", R.string.CodeExpired)); + } else if (error.text.startsWith("FLOOD_WAIT")) { + needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("FloodWait", R.string.FloodWait)); + } else { + needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred) + "\n" + error.text); + } } } } @@ -1305,19 +1598,29 @@ public void onBackPressed() { destroyTimer(); destroyCodeTimer(); currentParams = null; - AndroidUtilities.setWaitingForSms(false); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveSmsCode); - waitingForSms = false; + if (currentType == 2) { + AndroidUtilities.setWaitingForSms(false); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveSmsCode); + } else if (currentType == 3) { + AndroidUtilities.setWaitingForCall(false); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveCall); + } + waitingForEvent = false; } @Override public void onDestroyActivity() { super.onDestroyActivity(); - AndroidUtilities.setWaitingForSms(false); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveSmsCode); + if (currentType == 2) { + AndroidUtilities.setWaitingForSms(false); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveSmsCode); + } else if (currentType == 3) { + AndroidUtilities.setWaitingForCall(false); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveCall); + } + waitingForEvent = false; destroyTimer(); destroyCodeTimer(); - waitingForSms = false; } @Override @@ -1331,16 +1634,26 @@ public void onShow() { @Override public void didReceivedNotification(int id, final Object... args) { + if (!waitingForEvent || codeField == null) { + return; + } if (id == NotificationCenter.didReceiveSmsCode) { - if (!waitingForSms) { - return; - } - if (codeField != null) { - ignoreOnTextChange = true; - codeField.setText("" + args[0]); - ignoreOnTextChange = false; - onNextPressed(); + ignoreOnTextChange = true; + codeField.setText("" + args[0]); + ignoreOnTextChange = false; + onNextPressed(); + } else if (id == NotificationCenter.didReceiveCall) { + String num = "" + args[0]; + if (!pattern.equals("*")) { + String patternNumbers = pattern.replace("*", ""); + if (!num.contains(patternNumbers)) { + return; + } } + ignoreOnTextChange = true; + codeField.setText(num); + ignoreOnTextChange = false; + onNextPressed(); } } @@ -1348,10 +1661,10 @@ public void didReceivedNotification(int id, final Object... args) { public void saveStateParams(Bundle bundle) { String code = codeField.getText().toString(); if (code.length() != 0) { - bundle.putString("smsview_code", code); + bundle.putString("smsview_code_" + currentType, code); } if (currentParams != null) { - bundle.putBundle("smsview_params", currentParams); + bundle.putBundle("smsview_params_" + currentType, currentParams); } if (time != 0) { bundle.putInt("time", time); @@ -1363,11 +1676,11 @@ public void saveStateParams(Bundle bundle) { @Override public void restoreStateParams(Bundle bundle) { - currentParams = bundle.getBundle("smsview_params"); + currentParams = bundle.getBundle("smsview_params_" + currentType); if (currentParams != null) { setParams(currentParams); } - String code = bundle.getString("smsview_code"); + String code = bundle.getString("smsview_code_" + currentType); if (code != null) { codeField.setText(code); } @@ -1468,7 +1781,7 @@ public void run() { public void onClick(DialogInterface dialogInterface, int i) { Bundle bundle = new Bundle(); bundle.putString("email_unconfirmed_pattern", res.email_pattern); - setPage(4, true, bundle, false); + setPage(7, true, bundle, false); } }); Dialog dialog = showDialog(builder.create()); @@ -1537,7 +1850,7 @@ public void run() { params.putString("phoneFormated", requestPhone); params.putString("phoneHash", phoneHash); params.putString("code", phoneCode); - setPage(2, true, params, false); + setPage(5, true, params, false); } else { needShowAlert(LocaleController.getString("AppName", R.string.AppName), error.text); } @@ -1794,7 +2107,7 @@ public void onClick(View view) { builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { - setPage(3, true, new Bundle(), true); + setPage(6, true, new Bundle(), true); } }); Dialog dialog = showDialog(builder.create()); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java index e3d856b3fd..76553ab3fd 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java @@ -348,6 +348,9 @@ public void didSelectDialog(DialogsActivity fragment, long did, boolean param) { } else if (lower_part < 0) { args.putInt("chat_id", -lower_part); } + if (!MessagesController.checkCanOpenChat(args, fragment)) { + return; + } ArrayList fmessages = new ArrayList<>(); for (int a = 1; a >= 0; a--) { @@ -364,6 +367,7 @@ public void didSelectDialog(DialogsActivity fragment, long did, boolean param) { actionBar.hideActionMode(); NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); + ChatActivity chatActivity = new ChatActivity(args); presentFragment(chatActivity, true); chatActivity.showReplyPanel(true, null, fmessages, null, false, false); @@ -901,7 +905,11 @@ private void switchToCurrentSelectedMode() { listView.setAdapter(photoVideoAdapter); dropDown.setText(LocaleController.getString("SharedMediaTitle", R.string.SharedMediaTitle)); emptyImageView.setImageResource(R.drawable.tip1); - emptyTextView.setText(LocaleController.getString("NoMedia", R.string.NoMedia)); + if ((int) dialog_id == 0) { + emptyTextView.setText(LocaleController.getString("NoMediaSecret", R.string.NoMediaSecret)); + } else { + emptyTextView.setText(LocaleController.getString("NoMedia", R.string.NoMedia)); + } searchItem.setVisibility(View.GONE); if (sharedMediaData[selectedMode].loading && sharedMediaData[selectedMode].messages.isEmpty()) { progressView.setVisibility(View.VISIBLE); @@ -918,12 +926,20 @@ private void switchToCurrentSelectedMode() { listView.setAdapter(documentsAdapter); dropDown.setText(LocaleController.getString("DocumentsTitle", R.string.DocumentsTitle)); emptyImageView.setImageResource(R.drawable.tip2); - emptyTextView.setText(LocaleController.getString("NoSharedFiles", R.string.NoSharedFiles)); + if ((int) dialog_id == 0) { + emptyTextView.setText(LocaleController.getString("NoSharedFilesSecret", R.string.NoSharedFilesSecret)); + } else { + emptyTextView.setText(LocaleController.getString("NoSharedFiles", R.string.NoSharedFiles)); + } } else if (selectedMode == 4) { listView.setAdapter(audioAdapter); dropDown.setText(LocaleController.getString("AudioTitle", R.string.AudioTitle)); emptyImageView.setImageResource(R.drawable.tip4); - emptyTextView.setText(LocaleController.getString("NoSharedAudio", R.string.NoSharedAudio)); + if ((int) dialog_id == 0) { + emptyTextView.setText(LocaleController.getString("NoSharedAudioSecret", R.string.NoSharedAudioSecret)); + } else { + emptyTextView.setText(LocaleController.getString("NoSharedAudio", R.string.NoSharedAudio)); + } } searchItem.setVisibility(!sharedMediaData[selectedMode].messages.isEmpty() ? View.VISIBLE : View.GONE); if (!sharedMediaData[selectedMode].loading && !sharedMediaData[selectedMode].endReached[0] && sharedMediaData[selectedMode].messages.isEmpty()) { @@ -944,7 +960,11 @@ private void switchToCurrentSelectedMode() { listView.setAdapter(linksAdapter); dropDown.setText(LocaleController.getString("LinksTitle", R.string.LinksTitle)); emptyImageView.setImageResource(R.drawable.tip3); - emptyTextView.setText(LocaleController.getString("NoSharedLinks", R.string.NoSharedLinks)); + if ((int) dialog_id == 0) { + emptyTextView.setText(LocaleController.getString("NoSharedLinksSecret", R.string.NoSharedLinksSecret)); + } else { + emptyTextView.setText(LocaleController.getString("NoSharedLinks", R.string.NoSharedLinks)); + } searchItem.setVisibility(!sharedMediaData[3].messages.isEmpty() ? View.VISIBLE : View.GONE); if (!sharedMediaData[selectedMode].loading && !sharedMediaData[selectedMode].endReached[0] && sharedMediaData[selectedMode].messages.isEmpty()) { sharedMediaData[selectedMode].loading = true; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoCropActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoCropActivity.java index f51a2ebbf1..95c30acc16 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoCropActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoCropActivity.java @@ -15,7 +15,6 @@ import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.os.Bundle; -import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; @@ -58,16 +57,6 @@ public PhotoCropView(Context context) { init(); } - public PhotoCropView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - public PhotoCropView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(); - } - private void init() { rectPaint = new Paint(); rectPaint.setColor(0x3ffafafa); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java index 50e819baf7..9d4b170aba 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java @@ -914,6 +914,9 @@ public boolean dispatchKeyEventPreIme(KeyEvent event) { }; windowView.setBackgroundDrawable(backgroundDrawable); windowView.setFocusable(false); + if (Build.VERSION.SDK_INT >= 23) { + windowView.setFitsSystemWindows(true); //TODO ? + } animatingImageView = new ClippingImageView(activity); animatingImageView.setAnimationValues(animationValues); @@ -1206,10 +1209,10 @@ public void onClick(View v) { if (currentMessageObject != null) { isVideo = currentMessageObject.isVideo(); - if (currentMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) { + /*if (currentMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) { AndroidUtilities.openUrl(parentActivity, currentMessageObject.messageOwner.media.webpage.url); return; - } + }*/ f = FileLoader.getPathToMessage(currentMessageObject.messageOwner); } else if (currentFileLocation != null) { f = FileLoader.getPathToAttach(currentFileLocation, avatarsUserId != 0); @@ -1800,6 +1803,11 @@ public void needMoveImageTo(float x, float y, float s, boolean animated) { containerView.invalidate(); } } + + @Override + public Bitmap getBitmap() { + return centerImage.getBitmap(); + } }); } @@ -2579,7 +2587,7 @@ private void setCurrentCaption(final CharSequence caption) { captionTextViewNew = captionTextView; captionItem.setIcon(R.drawable.photo_text2); - CharSequence str = Emoji.replaceEmoji(new SpannableStringBuilder(caption.toString()), MessageObject.textPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); + CharSequence str = Emoji.replaceEmoji(new SpannableStringBuilder(caption.toString()), MessageObject.getTextPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false); captionTextView.setTag(str); captionTextView.setText(str); ViewProxy.setAlpha(captionTextView, bottomLayout.getVisibility() == View.VISIBLE || pickerView.getVisibility() == View.VISIBLE ? 1.0f : 0.0f); @@ -2973,11 +2981,6 @@ public void run() { } }); } - - @Override - public void onAnimationCancel(Object animation) { - onAnimationEnd(animation); - } }); transitionAnimationStartTime = System.currentTimeMillis(); AndroidUtilities.runOnUIThread(new Runnable() { @@ -3178,11 +3181,6 @@ public void run() { } }); } - - @Override - public void onAnimationCancel(Object animation) { - onAnimationEnd(animation); - } }); transitionAnimationStartTime = System.currentTimeMillis(); if (Build.VERSION.SDK_INT >= 18) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java index 3ff965bbe4..2dd66ce530 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java @@ -285,7 +285,7 @@ public View getView(int i, View view, ViewGroup viewGroup) { int type = getItemViewType(i); if (type == 0) { if (view == null) { - view = new UserCell(mContext, 1, 0); + view = new UserCell(mContext, 1, 0, false); } TLRPC.User user = MessagesController.getInstance().getUser(uidArray.get(i)); ((UserCell)view).setData(user, null, user.phone != null && user.phone.length() != 0 ? PhoneFormat.getInstance().format("+" + user.phone) : LocaleController.getString("NumberUnknown", R.string.NumberUnknown), 0); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java index 57d5e9e108..16ebe2f886 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java @@ -119,8 +119,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private long mergeDialogId; private boolean loadingUsers; - private ArrayList participants = new ArrayList<>(); - private HashMap participantsMap = new HashMap<>(); + private HashMap participantsMap = new HashMap<>(); private boolean usersEndReached; private boolean openAnimationInProgress; @@ -153,6 +152,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private final static int share = 10; private final static int set_admins = 11; private final static int edit_channel = 12; + private final static int convert_to_supergroup = 13; private int overscrollRow; private int emptyRow; @@ -174,8 +174,8 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private int leaveChannelRow; private int startSecretChatRow; private int sectionRow; - private int botSectionRow; - private int botInfoRow; + private int userSectionRow; + private int userInfoRow; private int membersSectionRow; private int membersEndRow; private int loadMoreMembersRow; @@ -205,6 +205,7 @@ public boolean onFragmentCreate() { NotificationCenter.getInstance().addObserver(this, NotificationCenter.encryptedChatUpdated); NotificationCenter.getInstance().addObserver(this, NotificationCenter.blockedUsersDidLoaded); NotificationCenter.getInstance().addObserver(this, NotificationCenter.botInfoDidLoaded); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.userInfoDidLoaded); if (currentEncryptedChat != null) { NotificationCenter.getInstance().addObserver(this, NotificationCenter.didReceivedNewMessages); } @@ -212,8 +213,7 @@ public boolean onFragmentCreate() { if (user.bot) { BotQuery.loadBotInfo(user.id, true, classGuid); } - MessagesController.getInstance().loadFullUser(MessagesController.getInstance().getUser(user_id), classGuid); - participants = null; + MessagesController.getInstance().loadFullUser(MessagesController.getInstance().getUser(user_id), classGuid, true); participantsMap = null; } else if (chat_id != 0) { currentChat = MessagesController.getInstance().getChat(chat_id); @@ -241,7 +241,6 @@ public void run() { if (currentChat.megagroup) { getChannelParticipants(true); } else { - participants = null; participantsMap = null; } NotificationCenter.getInstance().addObserver(this, NotificationCenter.chatInfoDidLoaded); @@ -259,6 +258,10 @@ public void didUploadedPhoto(TLRPC.InputFile file, TLRPC.PhotoSize small, TLRPC. } }; avatarUpdater.parentFragment = this; + + if (ChatObject.isChannel(currentChat)) { + MessagesController.getInstance().loadFullChat(chat_id, classGuid, true); + } } else { return false; } @@ -294,6 +297,7 @@ public void onFragmentDestroy() { NotificationCenter.getInstance().removeObserver(this, NotificationCenter.encryptedChatUpdated); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.blockedUsersDidLoaded); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.botInfoDidLoaded); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.userInfoDidLoaded); MessagesController.getInstance().cancelLoadFullUser(user_id); if (currentEncryptedChat != null) { NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceivedNewMessages); @@ -420,12 +424,16 @@ public void onClick(DialogInterface dialogInterface, int i) { fragment.setDelegate(new DialogsActivity.MessagesActivityDelegate() { @Override public void didSelectDialog(DialogsActivity fragment, long did, boolean param) { - NotificationCenter.getInstance().removeObserver(ProfileActivity.this, NotificationCenter.closeChats); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); - MessagesController.getInstance().addUserToChat(-(int) did, user, null, 0, null, ProfileActivity.this); Bundle args = new Bundle(); args.putBoolean("scrollToTopOnResume", true); args.putInt("chat_id", -(int) did); + if (!MessagesController.checkCanOpenChat(args, fragment)) { + return; + } + + NotificationCenter.getInstance().removeObserver(ProfileActivity.this, NotificationCenter.closeChats); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); + MessagesController.getInstance().addUserToChat(-(int) did, user, null, 0, null, ProfileActivity.this); presentFragment(new ChatActivity(args), true); removeSelfFromStack(); } @@ -439,8 +447,9 @@ public void didSelectDialog(DialogsActivity fragment, long did, boolean param) { } Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); - if (botInfo != null && botInfo.share_text != null && botInfo.share_text.length() > 0) { - intent.putExtra(Intent.EXTRA_TEXT, String.format("%s https://telegram.me/%s", botInfo.share_text, user.username)); + String about = MessagesController.getInstance().getUserAbout(botInfo.user_id); + if (botInfo != null && about != null) { + intent.putExtra(Intent.EXTRA_TEXT, String.format("%s https://telegram.me/%s", about, user.username)); } else { intent.putExtra(Intent.EXTRA_TEXT, String.format("https://telegram.me/%s", user.username)); } @@ -454,6 +463,10 @@ public void didSelectDialog(DialogsActivity fragment, long did, boolean param) { SetAdminsActivity fragment = new SetAdminsActivity(args); fragment.setChatInfo(info); presentFragment(fragment); + } else if (id == convert_to_supergroup) { + Bundle args = new Bundle(); + args.putInt("chat_id", chat_id); + presentFragment(new ConvertGroupActivity(args)); } } }); @@ -600,10 +613,10 @@ public void onClick(DialogInterface dialogInterface, int i) { showDialog(builder.create()); } else if (position > emptyRowChat2 && position < membersEndRow) { int user_id; - if (participants != null) { - user_id = participants.get(position - emptyRowChat2 - 1).user_id; - } else { + if (!sortedUsers.isEmpty()) { user_id = info.participants.participants.get(sortedUsers.get(position - emptyRowChat2 - 1)).user_id; + } else { + user_id = info.participants.participants.get(position - emptyRowChat2 - 1).user_id; } if (user_id == UserConfig.getClientUserId()) { return; @@ -662,7 +675,7 @@ public void onClick(DialogInterface dialogInterface, int i) { } else if (position == convertRow) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setMessage(LocaleController.getString("ConvertGroupAlert", R.string.ConvertGroupAlert)); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setTitle(LocaleController.getString("ConvertGroupAlertWarning", R.string.ConvertGroupAlertWarning)); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { @@ -685,10 +698,18 @@ public boolean onItemClick(View view, int position) { } boolean allowKick = false; boolean allowSetAdmin = false; - TLRPC.ChannelParticipant channelParticipant = null; + + final TLRPC.ChatParticipant user; + if (!sortedUsers.isEmpty()) { + user = info.participants.participants.get(sortedUsers.get(position - emptyRowChat2 - 1)); + } else { + user = info.participants.participants.get(position - emptyRowChat2 - 1); + } + selectedUser = user.user_id; + if (ChatObject.isChannel(currentChat)) { - channelParticipant = participants.get(position - emptyRowChat2 - 1); - if (channelParticipant.user_id != UserConfig.getClientUserId()) { + TLRPC.ChannelParticipant channelParticipant = ((TLRPC.TL_chatChannelParticipant) user).channelParticipant; + if (user.user_id != UserConfig.getClientUserId()) { if (currentChat.creator) { allowKick = true; } else if (channelParticipant instanceof TLRPC.TL_channelParticipant) { @@ -697,11 +718,9 @@ public boolean onItemClick(View view, int position) { } } } - TLRPC.User u = MessagesController.getInstance().getUser(channelParticipant.user_id); + TLRPC.User u = MessagesController.getInstance().getUser(user.user_id); allowSetAdmin = channelParticipant instanceof TLRPC.TL_channelParticipant && !u.bot; - selectedUser = channelParticipant.user_id; } else { - TLRPC.ChatParticipant user = info.participants.participants.get(sortedUsers.get(position - emptyRowChat2 - 1)); if (user.user_id != UserConfig.getClientUserId()) { if (currentChat.creator) { allowKick = true; @@ -711,27 +730,24 @@ public boolean onItemClick(View view, int position) { } } } - selectedUser = user.user_id; } if (!allowKick) { return false; } AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); if (currentChat.megagroup && currentChat.creator && allowSetAdmin) { - final TLRPC.ChannelParticipant channelParticipantFinal = channelParticipant; CharSequence[] items = new CharSequence[]{LocaleController.getString("SetAsAdmin", R.string.SetAsAdmin), LocaleController.getString("KickFromGroup", R.string.KickFromGroup)}; builder.setItems(items, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { if (i == 0) { - int index = participants.indexOf(channelParticipantFinal); - if (index != -1) { - TLRPC.TL_channelParticipantEditor editor = new TLRPC.TL_channelParticipantEditor(); - editor.inviter_id = UserConfig.getClientUserId(); - editor.user_id = channelParticipantFinal.user_id; - editor.date = channelParticipantFinal.date; - participants.set(index, editor); - } + TLRPC.TL_chatChannelParticipant channelParticipant = ((TLRPC.TL_chatChannelParticipant) user); + + channelParticipant.channelParticipant = new TLRPC.TL_channelParticipantEditor(); + channelParticipant.channelParticipant.inviter_id = UserConfig.getClientUserId(); + channelParticipant.channelParticipant.user_id = user.user_id; + channelParticipant.channelParticipant.date = user.date; + TLRPC.TL_channels_editAdmin req = new TLRPC.TL_channels_editAdmin(); req.channel = MessagesController.getInputChannel(chat_id); req.user_id = MessagesController.getInputUser(selectedUser); @@ -897,10 +913,13 @@ public void onClick(View v) { if (user == null || user instanceof TLRPC.TL_userEmpty) { return; } - NotificationCenter.getInstance().removeObserver(ProfileActivity.this, NotificationCenter.closeChats); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); Bundle args = new Bundle(); args.putInt("user_id", user_id); + if (!MessagesController.checkCanOpenChat(args, ProfileActivity.this)) { + return; + } + NotificationCenter.getInstance().removeObserver(ProfileActivity.this, NotificationCenter.closeChats); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); presentFragment(new ChatActivity(args), true); } } else if (chat_id != 0) { @@ -909,10 +928,13 @@ public void onClick(View v) { if (playProfileAnimation && parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 2) instanceof ChatActivity) { finishFragment(); } else { - NotificationCenter.getInstance().removeObserver(ProfileActivity.this, NotificationCenter.closeChats); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); Bundle args = new Bundle(); args.putInt("chat_id", currentChat.id); + if (!MessagesController.checkCanOpenChat(args, ProfileActivity.this)) { + return; + } + NotificationCenter.getInstance().removeObserver(ProfileActivity.this, NotificationCenter.closeChats); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); presentFragment(new ChatActivity(args), true); } } else { @@ -954,7 +976,7 @@ public void onScrollStateChanged(RecyclerView recyclerView, int newState) { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { checkListViewScroll(); - if (participants != null && loadMoreMembersRow != -1 && layoutManager.findLastVisibleItemPosition() > loadMoreMembersRow - 8) { + if (participantsMap != null && loadMoreMembersRow != -1 && layoutManager.findLastVisibleItemPosition() > loadMoreMembersRow - 8) { getChannelParticipants(false); } } @@ -1008,17 +1030,17 @@ public void onActivityResultFragment(int requestCode, int resultCode, Intent dat } private void getChannelParticipants(boolean reload) { - if (loadingUsers || participants == null) { + if (loadingUsers || participantsMap == null || info == null) { return; } loadingUsers = true; - final int delay = Build.VERSION.SDK_INT >= 11 && !participants.isEmpty() && reload ? 300 : 0; + final int delay = Build.VERSION.SDK_INT >= 11 && !participantsMap.isEmpty() && reload ? 300 : 0; final TLRPC.TL_channels_getParticipants req = new TLRPC.TL_channels_getParticipants(); req.channel = MessagesController.getInputChannel(chat_id); req.filter = new TLRPC.TL_channelParticipantsRecent(); - req.offset = reload ? 0 : participants.size(); - req.limit = 33; + req.offset = reload ? 0 : participantsMap.size(); + req.limit = 200; int reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(final TLObject response, final TLRPC.TL_error error) { @@ -1028,25 +1050,28 @@ public void run() { if (error == null) { TLRPC.TL_channels_channelParticipants res = (TLRPC.TL_channels_channelParticipants) response; MessagesController.getInstance().putUsers(res.users, false); - if (res.participants.size() == 33) { - res.participants.remove(32); - } else { + if (res.users.size() != 200) { usersEndReached = true; } if (req.offset == 0) { - participants.clear(); participantsMap.clear(); + info.participants = new TLRPC.TL_chatParticipants(); MessagesStorage.getInstance().putUsersAndChats(res.users, null, true, true); MessagesStorage.getInstance().updateChannelUsers(chat_id, res.participants); } for (int a = 0; a < res.participants.size(); a++) { - TLRPC.ChannelParticipant participant = res.participants.get(a); + TLRPC.TL_chatChannelParticipant participant = new TLRPC.TL_chatChannelParticipant(); + participant.channelParticipant = res.participants.get(a); + participant.inviter_id = participant.channelParticipant.inviter_id; + participant.user_id = participant.channelParticipant.user_id; + participant.date = participant.channelParticipant.date; if (!participantsMap.containsKey(participant.user_id)) { - participants.add(participant); + info.participants.participants.add(participant); participantsMap.put(participant.user_id, participant); } } } + updateOnlineCount(); loadingUsers = false; updateRowsIds(); if (listAdapter != null) { @@ -1079,18 +1104,12 @@ public void didSelectContact(TLRPC.User user, String param) { MessagesController.getInstance().addUserToChat(chat_id, user, info, param != null ? Utilities.parseInt(param) : 0, null, ProfileActivity.this); } }); - if (info instanceof TLRPC.TL_chatFull) { + if (info != null && info.participants != null) { HashMap users = new HashMap<>(); for (int a = 0; a < info.participants.participants.size(); a++) { users.put(info.participants.participants.get(a).user_id, null); } fragment.setIgnoreUsers(users); - } else if (participants != null) { - HashMap users = new HashMap<>(); - for (int a = 0; a < participants.size(); a++) { - users.put(participants.get(a).user_id, null); - } - fragment.setIgnoreUsers(users); } presentFragment(fragment); } @@ -1388,6 +1407,7 @@ public void run() { chatFull.participants = info.participants; } } + boolean loadChannelParticipants = info == null && chatFull instanceof TLRPC.TL_channelFull; info = chatFull; if (mergeDialogId == 0 && info.migrated_from_chat_id != 0) { mergeDialogId = -info.migrated_from_chat_id; @@ -1405,7 +1425,7 @@ public void run() { currentChat = newChat; createActionBarMenu(); } - if (currentChat.megagroup && !byChannelUsers) { + if (currentChat.megagroup && (loadChannelParticipants || !byChannelUsers)) { getChannelParticipants(true); } } @@ -1421,6 +1441,15 @@ public void run() { checkListViewScroll(); } } + } else if (id == NotificationCenter.userInfoDidLoaded) { + int uid = (Integer) args[0]; + if (uid == user_id) { + updateRowsIds(); + if (listAdapter != null) { + listAdapter.notifyDataSetChanged(); + checkListViewScroll(); + } + } } else if (id == NotificationCenter.didReceivedNewMessages) { long did = (Long) args[0]; if (did == dialog_id) { @@ -1602,13 +1631,6 @@ public void onAnimationEnd(Object animation) { } callback.run(); } - - @Override - public void onAnimationCancel(Object animation) { - if (Build.VERSION.SDK_INT > 15) { - listView.setLayerType(View.LAYER_TYPE_NONE, null); - } - } }); animatorSet.setInterpolator(new DecelerateInterpolator()); @@ -1696,71 +1718,69 @@ public void sendButtonPressed(int index) { } private void updateOnlineCount() { onlineCount = 0; - if (!(info instanceof TLRPC.TL_chatFull)) { - return; - } int currentTime = ConnectionsManager.getInstance().getCurrentTime(); sortedUsers.clear(); - int i = 0; - for (TLRPC.ChatParticipant participant : info.participants.participants) { - TLRPC.User user = MessagesController.getInstance().getUser(participant.user_id); - if (user != null && user.status != null && (user.status.expires > currentTime || user.id == UserConfig.getClientUserId()) && user.status.expires > 10000) { - onlineCount++; - } - sortedUsers.add(i); - i++; - } + if (info instanceof TLRPC.TL_chatFull || info instanceof TLRPC.TL_channelFull && info.participants_count <= 200 && info.participants != null) { + for (int a = 0; a < info.participants.participants.size(); a++) { + TLRPC.ChatParticipant participant = info.participants.participants.get(a); + TLRPC.User user = MessagesController.getInstance().getUser(participant.user_id); + if (user != null && user.status != null && (user.status.expires > currentTime || user.id == UserConfig.getClientUserId()) && user.status.expires > 10000) { + onlineCount++; + } + sortedUsers.add(a); + } - try { - Collections.sort(sortedUsers, new Comparator() { - @Override - public int compare(Integer lhs, Integer rhs) { - TLRPC.User user1 = MessagesController.getInstance().getUser(info.participants.participants.get(rhs).user_id); - TLRPC.User user2 = MessagesController.getInstance().getUser(info.participants.participants.get(lhs).user_id); - int status1 = 0; - int status2 = 0; - if (user1 != null && user1.status != null) { - if (user1.id == UserConfig.getClientUserId()) { - status1 = ConnectionsManager.getInstance().getCurrentTime() + 50000; - } else { - status1 = user1.status.expires; + try { + Collections.sort(sortedUsers, new Comparator() { + @Override + public int compare(Integer lhs, Integer rhs) { + TLRPC.User user1 = MessagesController.getInstance().getUser(info.participants.participants.get(rhs).user_id); + TLRPC.User user2 = MessagesController.getInstance().getUser(info.participants.participants.get(lhs).user_id); + int status1 = 0; + int status2 = 0; + if (user1 != null && user1.status != null) { + if (user1.id == UserConfig.getClientUserId()) { + status1 = ConnectionsManager.getInstance().getCurrentTime() + 50000; + } else { + status1 = user1.status.expires; + } } - } - if (user2 != null && user2.status != null) { - if (user2.id == UserConfig.getClientUserId()) { - status2 = ConnectionsManager.getInstance().getCurrentTime() + 50000; - } else { - status2 = user2.status.expires; + if (user2 != null && user2.status != null) { + if (user2.id == UserConfig.getClientUserId()) { + status2 = ConnectionsManager.getInstance().getCurrentTime() + 50000; + } else { + status2 = user2.status.expires; + } } - } - if (status1 > 0 && status2 > 0) { - if (status1 > status2) { - return 1; - } else if (status1 < status2) { + if (status1 > 0 && status2 > 0) { + if (status1 > status2) { + return 1; + } else if (status1 < status2) { + return -1; + } + return 0; + } else if (status1 < 0 && status2 < 0) { + if (status1 > status2) { + return 1; + } else if (status1 < status2) { + return -1; + } + return 0; + } else if (status1 < 0 && status2 > 0 || status1 == 0 && status2 != 0) { return -1; - } - return 0; - } else if (status1 < 0 && status2 < 0) { - if (status1 > status2) { + } else if (status2 < 0 && status1 > 0 || status2 == 0 && status1 != 0) { return 1; - } else if (status1 < status2) { - return -1; } return 0; - } else if (status1 < 0 && status2 > 0 || status1 == 0 && status2 != 0) { - return -1; - } else if (status2 < 0 && status1 > 0 || status2 == 0 && status1 != 0) { - return 1; } - return 0; - } - }); - } catch (Exception e) { - FileLog.e("tmessages", e); //TODO find crash - } + }); + } catch (Exception e) { + FileLog.e("tmessages", e); + } - if (listAdapter != null) { - listAdapter.notifyItemRangeChanged(emptyRowChat2 + 1, sortedUsers.size()); + if (listAdapter != null) { + listAdapter.notifyItemRangeChanged(emptyRowChat2 + 1, sortedUsers.size()); + } } } @@ -1773,14 +1793,10 @@ public void setChatInfo(TLRPC.ChatFull chatInfo) { } private void fetchUsersFromChannelInfo() { - if (info != null && info instanceof TLRPC.TL_channelFull && info.participants != null && participants != null && participants.isEmpty()) { + if (info instanceof TLRPC.TL_channelFull && info.participants != null) { for (int a = 0; a < info.participants.participants.size(); a++) { TLRPC.ChatParticipant chatParticipant = info.participants.participants.get(a); - if (chatParticipant instanceof TLRPC.TL_chatChannelParticipant) { - TLRPC.ChannelParticipant channelParticipant = ((TLRPC.TL_chatChannelParticipant) chatParticipant).channelParticipant; - participants.add(channelParticipant); - participantsMap.put(channelParticipant.user_id, channelParticipant); - } + participantsMap.put(chatParticipant.user_id, chatParticipant); } } } @@ -1788,15 +1804,15 @@ private void fetchUsersFromChannelInfo() { private void kickUser(int uid) { if (uid != 0) { MessagesController.getInstance().deleteUserFromChat(chat_id, MessagesController.getInstance().getUser(uid), info); - if (currentChat.megagroup && participants != null) { + if (currentChat.megagroup && info != null && info.participants != null) { boolean changed = false; - for (int a = 0; a < participants.size(); a++) { - TLRPC.ChannelParticipant p = participants.get(a); + for (int a = 0; a < info.participants.participants.size(); a++) { + TLRPC.ChannelParticipant p = ((TLRPC.TL_chatChannelParticipant) info.participants.participants.get(a)).channelParticipant; if (p.user_id == uid) { if (info != null) { info.participants_count--; } - participants.remove(a); + info.participants.participants.remove(a); changed = true; break; } @@ -1812,6 +1828,7 @@ private void kickUser(int uid) { } } if (changed) { + updateOnlineCount(); updateRowsIds(); listAdapter.notifyDataSetChanged(); } @@ -1834,16 +1851,35 @@ public boolean isChat() { } private void updateRowsIds() { + emptyRow = -1; + phoneRow = -1; + userInfoRow = -1; + userSectionRow = -1; + sectionRow = -1; + sharedMediaRow = -1; + settingsNotificationsRow = -1; + usernameRow = -1; + settingsTimerRow = -1; + settingsKeyRow = -1; + startSecretChatRow = -1; + membersEndRow = -1; + emptyRowChat2 = -1; + addMemberRow = -1; + channelInfoRow = -1; + channelNameRow = -1; + convertRow = -1; + convertHelpRow = -1; + emptyRowChat = -1; + membersSectionRow = -1; + membersRow = -1; + managementRow = -1; + leaveChannelRow = -1; + loadMoreMembersRow = -1; + blockedUsersRow = -1; + rowCount = 0; overscrollRow = rowCount++; if (user_id != 0) { - phoneRow = -1; - usernameRow = -1; - settingsTimerRow = -1; - settingsKeyRow = -1; - startSecretChatRow = -1; - blockedUsersRow = -1; - TLRPC.User user = MessagesController.getInstance().getUser(user_id); emptyRow = rowCount++; if (user == null || !user.bot) { @@ -1852,9 +1888,13 @@ private void updateRowsIds() { if (user != null && user.username != null && user.username.length() > 0) { usernameRow = rowCount++; } - if (botInfo != null && botInfo.share_text != null && botInfo.share_text.length() > 0) { - botSectionRow = rowCount++; - botInfoRow = rowCount++; + String about = MessagesController.getInstance().getUserAbout(user.id); + if (about != null) { + userSectionRow = rowCount++; + userInfoRow = rowCount++; + } else { + userSectionRow = -1; + userInfoRow = -1; } sectionRow = rowCount++; settingsNotificationsRow = rowCount++; @@ -1863,26 +1903,10 @@ private void updateRowsIds() { settingsTimerRow = rowCount++; settingsKeyRow = rowCount++; } - if (user != null && !user.bot && currentEncryptedChat == null) { + if (user != null && !user.bot && currentEncryptedChat == null && user.id != UserConfig.getClientUserId()) { startSecretChatRow = rowCount++; } } else if (chat_id != 0) { - membersEndRow = -1; - membersSectionRow = -1; - emptyRowChat2 = -1; - addMemberRow = -1; - channelInfoRow = -1; - channelNameRow = -1; - convertRow = -1; - convertHelpRow = -1; - emptyRowChat = -1; - membersSectionRow = -1; - membersRow = -1; - managementRow = -1; - leaveChannelRow = -1; - loadMoreMembersRow = -1; - blockedUsersRow = -1; - if (chat_id > 0) { emptyRow = rowCount++; if (ChatObject.isChannel(currentChat) && (info != null && info.about != null && info.about.length() > 0 || currentChat.username != null && currentChat.username.length() > 0)) { @@ -1900,7 +1924,7 @@ private void updateRowsIds() { if (!currentChat.megagroup && info != null && (currentChat.creator || info.can_view_participants)) { membersRow = rowCount++; } - if (!ChatObject.isNotInChat(currentChat) && (currentChat.creator || currentChat.editor || currentChat.moderator)) { + if (!ChatObject.isNotInChat(currentChat) && !currentChat.megagroup && (currentChat.creator || currentChat.editor || currentChat.moderator)) { managementRow = rowCount++; } if (!ChatObject.isNotInChat(currentChat) && currentChat.megagroup && (currentChat.editor || currentChat.creator)) { @@ -1914,11 +1938,11 @@ private void updateRowsIds() { addMemberRow = rowCount++; } } - if (participants != null && !participants.isEmpty()) { + if (info != null && info.participants != null && !info.participants.participants.isEmpty()) { emptyRowChat = rowCount++; membersSectionRow = rowCount++; emptyRowChat2 = rowCount++; - rowCount += participants.size(); + rowCount += info.participants.participants.size(); membersEndRow = rowCount; if (!usersEndReached) { loadMoreMembersRow = rowCount++; @@ -2033,15 +2057,27 @@ private void updateProfileData() { String newString; if (ChatObject.isChannel(chat)) { if (info == null || !currentChat.megagroup && (info.participants_count == 0 || (currentChat.admin || info.can_view_participants))) { - if ((chat.flags & TLRPC.CHAT_FLAG_IS_PUBLIC) != 0) { - newString = LocaleController.getString("ChannelPublic", R.string.ChannelPublic).toLowerCase(); + if (currentChat.megagroup) { + newString = LocaleController.getString("Loading", R.string.Loading).toLowerCase(); } else { - newString = LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate).toLowerCase(); + if ((chat.flags & TLRPC.CHAT_FLAG_IS_PUBLIC) != 0) { + newString = LocaleController.getString("ChannelPublic", R.string.ChannelPublic).toLowerCase(); + } else { + newString = LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate).toLowerCase(); + } } } else { - int result[] = new int[1]; - String shortNumber = LocaleController.formatShortNumber(info.participants_count, result); - newString = LocaleController.formatPluralString("Members", result[0]).replace(String.format("%d", result[0]), shortNumber); + if (currentChat.megagroup && info.participants_count <= 200) { + if (onlineCount > 1 && info.participants_count != 0) { + newString = String.format("%s, %s", LocaleController.formatPluralString("Members", info.participants_count), LocaleController.formatPluralString("Online", onlineCount)); + } else { + newString = LocaleController.formatPluralString("Members", info.participants_count); + } + } else { + int result[] = new int[1]; + String shortNumber = LocaleController.formatShortNumber(info.participants_count, result); + newString = LocaleController.formatPluralString("Members", result[0]).replace(String.format("%d", result[0]), shortNumber); + } } } else { int count = chat.participants_count; @@ -2075,11 +2111,14 @@ private void updateProfileData() { } else { nameTextView[a].setCompoundDrawablesWithIntrinsicBounds(0, 0, MessagesController.getInstance().isDialogMuted((long) -chat_id) ? R.drawable.mute_fixed : 0, 0); } - if (a == 0 && ChatObject.isChannel(currentChat) && info != null && info.participants_count != 0 && (currentChat.megagroup || currentChat.broadcast)) { + if (currentChat.megagroup && info != null && info.participants_count <= 200 && onlineCount > 0) { + if (!onlineTextView[a].getText().equals(newString)) { + onlineTextView[a].setText(newString); + } + } else if (a == 0 && ChatObject.isChannel(currentChat) && info != null && info.participants_count != 0 && (currentChat.megagroup || currentChat.broadcast)) { int result[] = new int[1]; String shortNumber = LocaleController.formatShortNumber(info.participants_count, result); - String text = LocaleController.formatPluralString("Members", result[0]).replace(String.format("%d", result[0]), shortNumber); - onlineTextView[a].setText(text); + onlineTextView[a].setText(LocaleController.formatPluralString("Members", result[0]).replace(String.format("%d", result[0]), shortNumber)); } else { if (!onlineTextView[a].getText().equals(newString)) { onlineTextView[a].setText(newString); @@ -2168,6 +2207,9 @@ private void createActionBarMenu() { if (!chat.admins_enabled || chat.creator || chat.admin) { item.addSubItem(edit_name, LocaleController.getString("EditName", R.string.EditName), 0); } + if (chat.creator && (info == null || info.participants.participants.size() > 1)) { + item.addSubItem(convert_to_supergroup, LocaleController.getString("ConvertGroupMenu", R.string.ConvertGroupMenu), 0); + } item.addSubItem(leave_group, LocaleController.getString("DeleteAndExit", R.string.DeleteAndExit), 0); } } else { @@ -2185,12 +2227,10 @@ protected void onDialogDismiss(Dialog dialog) { } @Override - public void didSelectDialog(DialogsActivity messageFragment, long dialog_id, boolean param) { + public void didSelectDialog(DialogsActivity fragment, long dialog_id, boolean param) { if (dialog_id != 0) { Bundle args = new Bundle(); args.putBoolean("scrollToTopOnResume", true); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.closeChats); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); int lower_part = (int) dialog_id; if (lower_part != 0) { if (lower_part > 0) { @@ -2201,6 +2241,12 @@ public void didSelectDialog(DialogsActivity messageFragment, long dialog_id, boo } else { args.putInt("enc_id", (int) (dialog_id >> 32)); } + if (!MessagesController.checkCanOpenChat(args, fragment)) { + return; + } + + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.closeChats); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); presentFragment(new ChatActivity(args), true); removeSelfFromStack(); TLRPC.User user = MessagesController.getInstance().getUser(user_id); @@ -2260,7 +2306,7 @@ public boolean onTouchEvent(MotionEvent event) { }; break; case 4: - view = new UserCell(mContext, 61, 0) { + view = new UserCell(mContext, 61, 0, true) { @Override public boolean onTouchEvent(MotionEvent event) { if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { @@ -2308,6 +2354,7 @@ public void didPressUrl(String url) { }); break; } + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); return new Holder(view); } @@ -2389,7 +2436,7 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) { textCell.setTextColor(0xffed3d39); textCell.setText(LocaleController.getString("LeaveChannel", R.string.LeaveChannel)); } else if (i == convertRow) { - textCell.setText(LocaleController.getString("ConvertGroup", R.string.ConvertGroup)); + textCell.setText(LocaleController.getString("UpgradeGroup", R.string.UpgradeGroup)); textCell.setTextColor(0xff37a919); } else if (i == membersRow) { if (info != null) { @@ -2418,18 +2465,28 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) { } break; case 4: - if (participants != null) { - TLRPC.ChannelParticipant part = participants.get(i - emptyRowChat2 - 1); - ((UserCell) holder.itemView).setData(MessagesController.getInstance().getUser(part.user_id), null, null, i == emptyRowChat2 + 1 ? R.drawable.menu_newgroup : 0); + UserCell userCell = ((UserCell) holder.itemView); + TLRPC.ChatParticipant part; + if (!sortedUsers.isEmpty()) { + part = info.participants.participants.get(sortedUsers.get(i - emptyRowChat2 - 1)); } else { - TLRPC.ChatParticipant part = info.participants.participants.get(sortedUsers.get(i - emptyRowChat2 - 1)); - ((UserCell) holder.itemView).setData(MessagesController.getInstance().getUser(part.user_id), null, null, i == emptyRowChat2 + 1 ? R.drawable.menu_newgroup : 0); + part = info.participants.participants.get(i - emptyRowChat2 - 1); + } + if (part != null) { + if (part instanceof TLRPC.TL_chatChannelParticipant) { + TLRPC.ChannelParticipant channelParticipant = ((TLRPC.TL_chatChannelParticipant) part).channelParticipant; + userCell.setIsAdmin(channelParticipant instanceof TLRPC.TL_channelParticipantCreator || channelParticipant instanceof TLRPC.TL_channelParticipantEditor || channelParticipant instanceof TLRPC.TL_channelParticipantModerator); + } else { + userCell.setIsAdmin(part instanceof TLRPC.TL_chatParticipantAdmin || part instanceof TLRPC.TL_chatParticipantCreator); + } + userCell.setData(MessagesController.getInstance().getUser(part.user_id), null, null, i == emptyRowChat2 + 1 ? R.drawable.menu_newgroup : 0); } break; case 8: AboutLinkCell aboutLinkCell = (AboutLinkCell) holder.itemView; - if (i == botInfoRow) { - aboutLinkCell.setTextAndIcon(botInfo.share_text, R.drawable.bot_info); + if (i == userInfoRow) { + String about = MessagesController.getInstance().getUserAbout(user_id); + aboutLinkCell.setTextAndIcon(about, R.drawable.bot_info); } else if (i == channelInfoRow) { String text = info.about; while (text.contains("\n\n\n")) { @@ -2469,7 +2526,7 @@ public int getItemCount() { public int getItemViewType(int i) { if (i == emptyRow || i == overscrollRow || i == emptyRowChat || i == emptyRowChat2) { return 0; - } else if (i == sectionRow || i == botSectionRow) { + } else if (i == sectionRow || i == userSectionRow) { return 1; } else if (i == phoneRow || i == usernameRow || i == channelNameRow) { return 2; @@ -2483,7 +2540,7 @@ public int getItemViewType(int i) { return 6; } else if (i == loadMoreMembersRow) { return 7; - } else if (i == botInfoRow || i == channelInfoRow) { + } else if (i == userInfoRow || i == channelInfoRow) { return 8; } return 0; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ReportOtherActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ReportOtherActivity.java new file mode 100644 index 0000000000..e169186424 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/ReportOtherActivity.java @@ -0,0 +1,151 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2016. + */ + +package org.telegram.ui; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.text.InputType; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.R; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.RequestDelegate; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.Components.LayoutHelper; + +public class ReportOtherActivity extends BaseFragment { + + private EditText firstNameField; + private View headerLabelView; + private long dialog_id; + private View doneButton; + + private final static int done_button = 1; + + public ReportOtherActivity(Bundle args) { + super(args); + dialog_id = getArguments().getLong("dialog_id", 0); + } + + @Override + public View createView(Context context) { + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setAllowOverlayTitle(true); + actionBar.setTitle(LocaleController.getString("ReportChat", R.string.ReportChat)); + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } else if (id == done_button) { + if (firstNameField.getText().length() != 0) { + TLRPC.TL_account_reportPeer req = new TLRPC.TL_account_reportPeer(); + req.peer = MessagesController.getInputPeer((int) dialog_id); + req.reason = new TLRPC.TL_inputReportReasonOther(); + req.reason.text = firstNameField.getText().toString(); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }); + finishFragment(); + } + } + } + }); + + ActionBarMenu menu = actionBar.createMenu(); + doneButton = menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); + + LinearLayout linearLayout = new LinearLayout(context); + fragmentView = linearLayout; + fragmentView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + ((LinearLayout) fragmentView).setOrientation(LinearLayout.VERTICAL); + fragmentView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + return true; + } + }); + + firstNameField = new EditText(context); + firstNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + firstNameField.setHintTextColor(0xff979797); + firstNameField.setTextColor(0xff212121); + firstNameField.setMaxLines(3); + firstNameField.setPadding(0, 0, 0, 0); + firstNameField.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + firstNameField.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); + firstNameField.setImeOptions(EditorInfo.IME_ACTION_DONE); + firstNameField.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + AndroidUtilities.clearCursorDrawable(firstNameField); + firstNameField.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { + if (i == EditorInfo.IME_ACTION_DONE && doneButton != null) { + doneButton.performClick(); + return true; + } + return false; + } + }); + + linearLayout.addView(firstNameField, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 24, 24, 24, 0)); + firstNameField.setHint(LocaleController.getString("ReportChatDescription", R.string.ReportChatDescription)); + firstNameField.setSelection(firstNameField.length()); + + return fragmentView; + } + + @Override + public void onResume() { + super.onResume(); + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + boolean animations = preferences.getBoolean("view_animations", true); + if (!animations) { + firstNameField.requestFocus(); + AndroidUtilities.showKeyboard(firstNameField); + } + } + + @Override + public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + if (isOpen) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (firstNameField != null) { + firstNameField.requestFocus(); + AndroidUtilities.showKeyboard(firstNameField); + } + } + }, 100); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SecretPhotoViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/SecretPhotoViewer.java index edcc97be78..b7dbcd7cd8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SecretPhotoViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SecretPhotoViewer.java @@ -202,6 +202,9 @@ public void setParentActivity(Activity activity) { windowView.setBackgroundColor(0xff000000); windowView.setFocusable(true); windowView.setFocusableInTouchMode(true); + if (Build.VERSION.SDK_INT >= 23) { + windowView.setFitsSystemWindows(true); //TODO ? + } containerView = new FrameLayoutDrawer(activity); containerView.setFocusable(false); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SetAdminsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/SetAdminsActivity.java index 8c9e4d7cf8..614c5bab71 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SetAdminsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SetAdminsActivity.java @@ -442,7 +442,7 @@ public View getView(int i, View view, ViewGroup viewGroup) { } } else if (type == 2) { if (view == null) { - view = new UserCell(mContext, 1, 2); + view = new UserCell(mContext, 1, 2, false); view.setBackgroundColor(0xffffffff); } UserCell userCell = (UserCell) view; @@ -629,7 +629,7 @@ public boolean hasStableIds() { @Override public View getView(int i, View view, ViewGroup viewGroup) { if (view == null) { - view = new UserCell(mContext, 1, 2); + view = new UserCell(mContext, 1, 2, false); } TLRPC.ChatParticipant participant = getItem(i); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java index 5f389d7718..afdfe696a0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java @@ -133,6 +133,8 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter private int saveToGalleryRow; private int messagesSectionRow; private int messagesSectionRow2; + private int customTabsRow; + private int directShareRow; private int textSizeRow; private int stickersRow; private int cacheRow; @@ -142,6 +144,7 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter private int supportSectionRow2; private int askQuestionRow; private int telegramFaqRow; + private int privacyPolicyRow; private int sendLogsRow; private int clearLogsRow; private int switchBackendButtonRow; @@ -250,6 +253,10 @@ public void run() { saveToGalleryRow = rowCount++; messagesSectionRow = rowCount++; messagesSectionRow2 = rowCount++; + customTabsRow = rowCount++; + if (Build.VERSION.SDK_INT >= 23) { + directShareRow = rowCount++; + } textSizeRow = rowCount++; stickersRow = rowCount++; cacheRow = rowCount++; @@ -259,6 +266,7 @@ public void run() { supportSectionRow2 = rowCount++; askQuestionRow = rowCount++; telegramFaqRow = rowCount++; + privacyPolicyRow = rowCount++; if (BuildVars.DEBUG_VERSION) { sendLogsRow = rowCount++; clearLogsRow = rowCount++; @@ -269,7 +277,7 @@ public void run() { //contactsReimportRow = rowCount++; //contactsSortRow = rowCount++; - MessagesController.getInstance().loadFullUser(UserConfig.getCurrentUser(), classGuid); + MessagesController.getInstance().loadFullUser(UserConfig.getCurrentUser(), classGuid, true); return true; } @@ -454,6 +462,16 @@ public void onClick(DialogInterface dialogInterface, int i) { if (view instanceof TextCheckCell) { ((TextCheckCell) view).setChecked(MediaController.getInstance().canSaveToGallery()); } + } else if (i == customTabsRow) { + MediaController.getInstance().toggleCustomTabs(); + if (view instanceof TextCheckCell) { + ((TextCheckCell) view).setChecked(MediaController.getInstance().canCustomTabs()); + } + } else if(i == directShareRow) { + MediaController.getInstance().toggleDirectShare(); + if (view instanceof TextCheckCell) { + ((TextCheckCell) view).setChecked(MediaController.getInstance().canDirectShare()); + } } else if (i == privacyRow) { presentFragment(new PrivacySettingsActivity()); } else if (i == languageRow) { @@ -475,6 +493,8 @@ public void onClick(DialogInterface dialogInterface, int i) { showDialog(builder.create()); } else if (i == telegramFaqRow) { AndroidUtilities.openUrl(getParentActivity(), LocaleController.getString("TelegramFaqUrl", R.string.TelegramFaqUrl)); + } else if (i == privacyPolicyRow) { + AndroidUtilities.openUrl(getParentActivity(), LocaleController.getString("PrivacyPolicyUrl", R.string.PrivacyPolicyUrl)); } else if (i == contactsReimportRow) { //not implemented } else if (i == contactsSortRow) { @@ -644,7 +664,7 @@ public void onClick(View v) { @Override public void onClick(View v) { TLRPC.User user = MessagesController.getInstance().getUser(UserConfig.getClientUserId()); - if (user.photo != null && user.photo.photo_big != null) { + if (user != null && user.photo != null && user.photo.photo_big != null) { PhotoViewer.getInstance().setParentActivity(getParentActivity()); PhotoViewer.getInstance().openPhoto(user.photo.photo_big, SettingsActivity.this); } @@ -1130,7 +1150,7 @@ public boolean isEnabled(int i) { i == askQuestionRow || i == sendLogsRow || i == sendByEnterRow || i == autoplayGifsRow || i == privacyRow || i == wifiDownloadRow || i == mobileDownloadRow || i == clearLogsRow || i == roamingDownloadRow || i == languageRow || i == usernameRow || i == switchBackendButtonRow || i == telegramFaqRow || i == contactsSortRow || i == contactsReimportRow || i == saveToGalleryRow || - i == stickersRow || i == cacheRow || i == raiseToSpeakRow; + i == stickersRow || i == cacheRow || i == raiseToSpeakRow || i == privacyPolicyRow || i == customTabsRow || i == directShareRow; } @Override @@ -1214,6 +1234,8 @@ public View getView(int i, View view, ViewGroup viewGroup) { textCell.setText(LocaleController.getString("Stickers", R.string.Stickers), true); } else if (i == cacheRow) { textCell.setText(LocaleController.getString("CacheSettings", R.string.CacheSettings), true); + } else if (i == privacyPolicyRow) { + textCell.setText(LocaleController.getString("PrivacyPolicy", R.string.PrivacyPolicy), true); } } else if (type == 3) { if (view == null) { @@ -1232,6 +1254,10 @@ public View getView(int i, View view, ViewGroup viewGroup) { textCell.setTextAndCheck(LocaleController.getString("AutoplayGifs", R.string.AutoplayGifs), MediaController.getInstance().canAutoplayGifs(), true); } else if (i == raiseToSpeakRow) { textCell.setTextAndCheck(LocaleController.getString("RaiseToSpeak", R.string.RaiseToSpeak), MediaController.getInstance().canRaiseToSpeak(), true); + } else if (i == customTabsRow) { + textCell.setTextAndValueAndCheck(LocaleController.getString("ChromeCustomTabs", R.string.ChromeCustomTabs), LocaleController.getString("ChromeCustomTabsInfo", R.string.ChromeCustomTabsInfo), MediaController.getInstance().canCustomTabs(), true); + } else if (i == directShareRow) { + textCell.setTextAndValueAndCheck(LocaleController.getString("DirectShare", R.string.DirectShare), LocaleController.getString("DirectShareInfo", R.string.DirectShareInfo), MediaController.getInstance().canDirectShare(), true); } } else if (type == 4) { if (view == null) { @@ -1253,7 +1279,23 @@ public View getView(int i, View view, ViewGroup viewGroup) { view = new TextInfoCell(mContext); try { PackageInfo pInfo = ApplicationLoader.applicationContext.getPackageManager().getPackageInfo(ApplicationLoader.applicationContext.getPackageName(), 0); - ((TextInfoCell) view).setText(String.format(Locale.US, "Telegram for Android v%s (%d)", pInfo.versionName, pInfo.versionCode)); + int code = pInfo.versionCode / 10; + String abi = ""; + switch (pInfo.versionCode % 10) { + case 0: + abi = "arm"; + break; + case 1: + abi = "arm-v7a"; + break; + case 2: + abi = "x86"; + break; + case 3: + abi = "universal"; + break; + } + ((TextInfoCell) view).setText(String.format(Locale.US, "Telegram for Android v%s (%d) %s", pInfo.versionName, code, abi)); } catch (Exception e) { FileLog.e("tmessages", e); } @@ -1348,9 +1390,9 @@ public int getItemViewType(int i) { } if (i == settingsSectionRow || i == supportSectionRow || i == messagesSectionRow || i == mediaDownloadSection || i == contactsSectionRow) { return 1; - } else if (i == enableAnimationsRow || i == sendByEnterRow || i == saveToGalleryRow || i == autoplayGifsRow || i == raiseToSpeakRow) { + } else if (i == enableAnimationsRow || i == sendByEnterRow || i == saveToGalleryRow || i == autoplayGifsRow || i == raiseToSpeakRow || i == customTabsRow || i == directShareRow) { return 3; - } else if (i == notificationRow || i == backgroundRow || i == askQuestionRow || i == sendLogsRow || i == privacyRow || i == clearLogsRow || i == switchBackendButtonRow || i == telegramFaqRow || i == contactsReimportRow || i == textSizeRow || i == languageRow || i == contactsSortRow || i == stickersRow || i == cacheRow) { + } else if (i == notificationRow || i == backgroundRow || i == askQuestionRow || i == sendLogsRow || i == privacyRow || i == clearLogsRow || i == switchBackendButtonRow || i == telegramFaqRow || i == contactsReimportRow || i == textSizeRow || i == languageRow || i == contactsSortRow || i == stickersRow || i == cacheRow || i == privacyPolicyRow) { return 2; } else if (i == versionRow) { return 5; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/StickerPreviewViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/StickerPreviewViewer.java index e2bfaaa22c..7abf9ddf9d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/StickerPreviewViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/StickerPreviewViewer.java @@ -259,6 +259,9 @@ public void setParentActivity(Activity activity) { windowView = new FrameLayout(activity); windowView.setFocusable(true); windowView.setFocusableInTouchMode(true); + if (Build.VERSION.SDK_INT >= 23) { + windowView.setFitsSystemWindows(true); //TODO ? + } containerView = new FrameLayoutDrawer(activity); containerView.setFocusable(false); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java index 56bfeafd87..1c6b009170 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java @@ -375,6 +375,7 @@ public void onClick(View widget) { view.setBackgroundResource(R.drawable.greydivider_bottom); break; } + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); return new Holder(view); } diff --git a/TMessagesProj/src/main/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png b/TMessagesProj/src/main/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png new file mode 100644 index 0000000000..ee40812968 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/admin_star.png b/TMessagesProj/src/main/res/drawable-hdpi/admin_star.png new file mode 100755 index 0000000000..2f6db99edf Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/admin_star.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_launcher.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_launcher.png index a819fd9f11..6251674224 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_launcher.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/intro1.png b/TMessagesProj/src/main/res/drawable-hdpi/intro1.png index a8d1b924cf..a1f0b063c6 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/intro1.png and b/TMessagesProj/src/main/res/drawable-hdpi/intro1.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/phone_activate.png b/TMessagesProj/src/main/res/drawable-hdpi/phone_activate.png new file mode 100755 index 0000000000..52b4de8d6d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/phone_activate.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png b/TMessagesProj/src/main/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png new file mode 100644 index 0000000000..d05f969e99 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/admin_star.png b/TMessagesProj/src/main/res/drawable-mdpi/admin_star.png new file mode 100755 index 0000000000..d676d01e48 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/admin_star.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_launcher.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_launcher.png index e245bb9c5e..b74069ad07 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_launcher.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/intro1.png b/TMessagesProj/src/main/res/drawable-mdpi/intro1.png index 9acf067915..3c3317fcec 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/intro1.png and b/TMessagesProj/src/main/res/drawable-mdpi/intro1.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/phone_activate.png b/TMessagesProj/src/main/res/drawable-mdpi/phone_activate.png new file mode 100755 index 0000000000..62dcc7aeee Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/phone_activate.png differ diff --git a/TMessagesProj/src/main/res/drawable-v21/circle_selector.xml b/TMessagesProj/src/main/res/drawable-v21/circle_selector.xml new file mode 100644 index 0000000000..25423fabb8 --- /dev/null +++ b/TMessagesProj/src/main/res/drawable-v21/circle_selector.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png b/TMessagesProj/src/main/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png new file mode 100644 index 0000000000..b57ee1935e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/admin_star.png b/TMessagesProj/src/main/res/drawable-xhdpi/admin_star.png new file mode 100755 index 0000000000..370787e2fc Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/admin_star.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_launcher.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_launcher.png index 1c307e8e12..b03cd17af8 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_launcher.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/intro1.png b/TMessagesProj/src/main/res/drawable-xhdpi/intro1.png index 526e860d93..11d506d7da 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/intro1.png and b/TMessagesProj/src/main/res/drawable-xhdpi/intro1.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/notify_members_off.png b/TMessagesProj/src/main/res/drawable-xhdpi/notify_members_off.png index 41dcd5a948..7fcaef929d 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/notify_members_off.png and b/TMessagesProj/src/main/res/drawable-xhdpi/notify_members_off.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/notify_members_on.png b/TMessagesProj/src/main/res/drawable-xhdpi/notify_members_on.png index 56a18f14aa..74270aae75 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/notify_members_on.png and b/TMessagesProj/src/main/res/drawable-xhdpi/notify_members_on.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/phone_activate.png b/TMessagesProj/src/main/res/drawable-xhdpi/phone_activate.png new file mode 100755 index 0000000000..f97ecc3bf9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/phone_activate.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png b/TMessagesProj/src/main/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png new file mode 100644 index 0000000000..a1866ba45f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/admin_star.png b/TMessagesProj/src/main/res/drawable-xxhdpi/admin_star.png new file mode 100755 index 0000000000..8724c05b1e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/admin_star.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_launcher.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_launcher.png index d48a051a83..0fff9d390d 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_launcher.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/intro1.png b/TMessagesProj/src/main/res/drawable-xxhdpi/intro1.png index b8633f7498..41511be208 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/intro1.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/intro1.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/notify_members_off.png b/TMessagesProj/src/main/res/drawable-xxhdpi/notify_members_off.png index 64b6e2e83b..3097e16f15 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/notify_members_off.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/notify_members_off.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/notify_members_on.png b/TMessagesProj/src/main/res/drawable-xxhdpi/notify_members_on.png index d7f1b935c3..b468befd5c 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/notify_members_on.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/notify_members_on.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/phone_activate.png b/TMessagesProj/src/main/res/drawable-xxhdpi/phone_activate.png new file mode 100755 index 0000000000..d422603db6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/phone_activate.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxxhdpi/ic_launcher.png b/TMessagesProj/src/main/res/drawable-xxxhdpi/ic_launcher.png old mode 100644 new mode 100755 index e3bcba53c7..e659827f87 Binary files a/TMessagesProj/src/main/res/drawable-xxxhdpi/ic_launcher.png and b/TMessagesProj/src/main/res/drawable-xxxhdpi/ic_launcher.png differ diff --git a/TMessagesProj/src/main/res/values-ar/strings.xml b/TMessagesProj/src/main/res/values-ar/strings.xml index 8f1bb60c8d..ecb46923a4 100644 --- a/TMessagesProj/src/main/res/values-ar/strings.xml +++ b/TMessagesProj/src/main/res/values-ar/strings.xml @@ -14,9 +14,13 @@ اختر دولة رمز البلد خاطئ - رمز التفعيل - تم إرسال رسالة قصيرة تحتوي على رمز التفعيل الخاص بك + تحقق الهاتف + تم إرسال رسالة قصيرة تحتوي على رمز التفعيل الخاص بك لرقمك ]]>%1$s]]>. + لقد قمنا بإرسال رمز الدخول إلى تطبيق ]]>تيليجرام]]> على جهازك الآخر + تم إرسال مكالمة تفعيل على رقمك ]]>%1$s]]>.\n\nلا داعي للرد عليها، تيليجرام سيقوم بعملية التفعيل تلقائيًا. + جاري الاتصال على رقمك ]]>%1$s]]> لإيصال رمز التفعيل. سنتصل بك خلال %1$d:%2$02d + سنقوم بإرسال رسالة قصيرة خلال %1$d:%2$02d جاري الاتصال بك ... رمز التفعيل الرقم خاطئ؟ @@ -64,6 +68,10 @@ حديث معاينة الرابط + نوع المجموعة + نوع القناة + عامة + خاصة ترقية ليكون مشرف يمكنك كتابة وصف اختياري لمجموعتك. مغادرة المجموعة @@ -81,6 +89,17 @@ المعذرة, هذا المستخدم قرر مغادرة المجموعة, لا يمكنك دعوته مرة أخرى للمجموعة. المعذرة، يوجد الكثير من المشرفين في هذه المجموعة. المعذرة، يوجد الكثير من حسابات البوت في هذه المجموعة. + un1 ثبت \"%1$s\" + un1 ثبت رسالة + un1 ثبت صورة + un1 ثبت فيديو + un1 ثبت ملف + un1 ثبت ملصق + un1 ثبت رسالة صوتية + un1 ثبت جهة اتصال + un1 ثبت خريطة + un1 ثبت صورة متحركة + un1 ثبت مقطع صوتي تم ترقية هذه المجموعة لتصبح مجموعة خارقة تم ترقية المجموعة %1$s لتصبح مجموعة خارقة أعضاء القائمة السوداء هم أعضاء تم حذفهم من المجموعة ولا يمكنهم العودة لها إلى بدعوة من المشرف. روابط الدعوة لن تمكنهم من العودة للمجموعة. @@ -90,15 +109,21 @@ إذا قمت بتفعيل التعليقات، الأعضاء سيتمكنون من مناقشة ما تنشره في قناتك. إضافة جهات اتصال لقناتك يستطيع الناس مشاركة هذا الرابط مع غيرهم ويمكنهم إيجاد قناتك من خلال البحث في تيليجرام. - + يستطيع الناس مشاركة هذا الرابط مع غيرهم ويمكنهم إيجاد مجموعتك من خلال البحث في تيليجرام. الرابط يستطيع الناس الدخول إلى قناتك فقط من خلال الرابط أدناه. يمكنك تعطيل هذا الرابط في أي وقت. + يستطيع الناس الدخول إلى مجموعتك فقط من خلال الرابط أدناه. يمكنك تعطيل هذا الرابط في أي وقت. + الوصف (اختياري) الوصف يمكنك كتابة وصف اختياري لقناتك. قناة عامة + مجموعة عامة القنوات العامة يمكن إيجادها من الخلال البحث، أي شخص يستطيع الدخول إليها. + المجموعات العامة يمكن إيجادها من الخلال البحث، سجل المحادثات متاح للجميع وأي شخص يستطيع الدخول إليها. قناة خاصة + مجموعة خاصة القنوات الخاصة يمكن الدخول إليها فقط عن طريق رابط الدعوة. + المجموعات الخاصة يمكن الدخول إليها فقط عن طريق دعوة من أعضائها أو رابط الدعوة. الرابط رابط دعوة إضافة مشارك @@ -108,6 +133,7 @@ اشترك معلومات القناة رسالة جماعية + رسالة جماعية صامتة تعليق إظهار التعليقات ما هي القنوات؟ @@ -118,8 +144,8 @@ اسم القناة يجب أن يتكوّن من ٥ حروف على الأقل. الاسم يجب ألا يتخطى ٣٢ حرف كحد أقصى. أسماء القنوات لا يمكن أن تبدأ برقم. - - + أسماء المجموعات يجب أن تتكوّن من ٥ حروف على الأقل. + أسماء المجموعات لا يمكن أن تبدأ برقم. جاري مراجعة الاسم... %1$s متاح. أعضاء @@ -131,7 +157,7 @@ هل أنت متأكد من رغبتك في مغادرة القناة؟ ستخسر كافة الرسائل في هذه القناة. تعديل - + يرجى ملاحظة أنه في حال اخترت رابط عام لمجموعتك، سيستطيع أي شخص إيجادها من خلال البحث والدخول إليها.\n\nلا تقم بإنشاء هذا الرابط إذا رغبت في أن تستمر قناتك خاصة. يرجى ملاحظة أنه في حال اخترت رابط عام لقناتك، سيستطيع أي شخص إيجادها من خلال البحث والدخول إليها.\n\nلا تقم بإنشاء هذا الرابط إذا رغبت في أن تستمر قناتك خاصة. فضلًا اختر رابط لقناتك العامة ليتمكن الناس من إيجادها من خلال البحث ومشاركتها مع غيرهم.\n\nإلى لم ترغب بذلك، يفضّل إنشاء قناة خاصة بدلًا من العامة. تم إنشاء القناة @@ -139,6 +165,7 @@ تم حذف صورة القناة تم تغيير اسم القناة إلى un2 المعذرة، قمت بإنشاء قنوات عامة كثيرة. يمكنك إنشاء قناة خاصة أو حذف أحد القنوات العامة أولًا. + المعذرة، قمت بإنشاء روابط عامة كثيرة. يمكنك حذف بعض مجموعاتك أو قنواتك العامة أو جعلها خاصة. المراقب المنشئ إداري @@ -154,6 +181,8 @@ يمكنك إضافة إداريّون لمساعدتك في إدارة القناة. اضغط باستمرار لحذف الإداريين. هل ترغب في الدخول لقناة \'%1$s\'؟ المعذرة، هذه المحادثة لم تعد متاحة. + للأسف، تم حظرك عن المشاركة في المجموعات العامة. + المعذرة، هذه المحادثة لم تعد متاحة. هل ترغب بإضافة %1$s للقناة؟ المعذرة، هذا المستخدم قرر مغادرة القناة, لا يمكنك دعوته مرة أخرى للقناة. المعذرة، لا يمكنك إضافة هذا المستخدم للقنوات. @@ -161,7 +190,8 @@ المعذرة، يوجد الكثير من حسابات البوت في هذه القناة. المعذرة، يمكنك إضافة أول ٢٠٠ عضو للقناة فقط. يمكن لعدد غير محدود من الأعضاء الدخول للقناة عن طريق رابط القناة. un1 قام بإضافتك لهذه القناة - لقد قمت بالدخول للقناة. + لقد قمت بالإشتراك في لقناة. + لقد قمت بالدخول لهذه المجموعة حذف من القناة المعذرة، لا يمكنك إرسال رسائل لهذه القناة. %1$s قام بإضافتك لقناة %2$s @@ -174,6 +204,7 @@ %1$s قام بإرسال ملف للقناة %2$s %1$s أرسل صورة متحركة للقناة %2$s %1$s أرسل رسالة صوتية للقناة %2$s + %1$s قام بإرسال مقطع صوتي للقناة %2$s %1$s قام بإرسال ملصق للقناة %2$s %1$s أرسل رسالة %1$s أرسل صورة @@ -183,6 +214,7 @@ %1$s أرسل ملف %1$s قام بنشر صورة متحركة %1$s أرسل رسالة صوتية + %1$s أرسل مقطع صوتي %1$s أرسل ملصق من يستطيع إضافة أعضاء؟ جميع الأعضاء @@ -271,16 +303,34 @@ انسخ الرابط أرسل %1$s هل ترغب في فتح الرابط باستخدام %1$s ؟ - الإبلاغ عن الرسائل الغير مرغوب فيها + الإبلاغ عن الرسائل المزعجة + أبلغ عن الإزعاج وغادر إضافة جهة اتصال هل أنت متأكد من رغبتك في الإبلاغ عن هذا المستخدم كغير مرغوب به؟ هل أنت متأكد من رغبتك في الإبلاغ عن هذه المجموعة كغير مرغوب بها؟ + هل أنت متأكد من رغبتك في الإبلاغ عن هذه القناة كغير مرغوب بها؟ المعذرة، يمكنك فقط إرسال رسائل لمن يمتلك رقمك وتمتلك رقمه في الوقت الحالي. المعذرة، يمكنك فقط إضافة من يمتلك رقمك وتمتلك رقمه للمجموعة في الوقت الحالي. https://telegram.org/faq/can-39t-send-messages-to-non-contacts ملعومات إضافية أرسل إلى... اضغط هنا للوصول للصور المتحركة المحفوظة + تثبيت + أشعر جميع الأعضاء + إزالة التثبيت + هل ترغب في تثبيت هذه الرسالة في هذه المجموعة؟ + هل ترغب في إزالة تثبيت هذه الرسالة؟ + حظر المستخدم + الإبلاغ عن إزعاج + مسح الكل من %1$s + هل ترغب في مسح سجل الإيموجي؟ + إبلاغ + مزعج + عنف + إباحية + أخرى + الوصف + رسالة مثبتة %1$s قام بتعيين عداد التدمير الذاتي إلى to %2$s لقد قمت بتعيين التدمير الذاتي إلى %1$s @@ -296,6 +346,7 @@ %1$s قام بإرسال ملف لك %1$s أرسل لك صورة متحركة %1$s قام بإرسال رسالة صوتية + %1$s أرسل مقطع صوتي لك %1$s قام بإرسال ملصق %1$s @ %2$s: %3$s %1$s قام بإرسال رسالة للمجموعة %2$s @@ -306,12 +357,14 @@ %1$s قام بإرسال ملف للمجموعة %2$s %1$s أرسل صورة متحركة للمجموعة %2$s %1$s أرسل رسالة صوتية للمجموعة %2$s + %1$s أرسل مقطع صوتي للمجموعة %2$s %1$s قام بإرسال ملصق للمجموعة %2$s %1$s قام بدعوتك للمجموعة %2$s %1$s قام بتعديل اسم المجموعة %2$s %1$s قام بتغيير صورة المجموعة %2$s %1$s قام بدعوة %3$s للمجموعة %2$s %1$s عاد إلى المجموعة %2$s + %1$s انضم للمجموعة %2$s %1$s قام بإخراج %3$s من المجموعة %2$s %1$s قام بإخراجك من المجموعة %2$s %1$s قام بمغادرة المجموعة %2$s @@ -323,6 +376,28 @@ الرد على %1$s الرد على %1$s %1$s %2$s + %1$s ثبت \"%2$s\" في المجموعة %3$s + %1$s ثبت رسالة في المجموعة %2$s + %1$s ثبت صورة في المجموعة %2$s + %1$s ثبت فيديو في المجموعة %2$s + %1$s ثبت ملف في المجموعة %2$s + %1$s ثبت ملصق في المجموعة %2$s + %1$s ثبت رسالة صوتية في المجموعة %2$s + %1$s ثبت جهة اتصال في المجموعة %2$s + %1$s ثبت خريطة في المجموعة %2$s + %1$s ثبت صورة متحركة في المجموعة %2$s + %1$s ثبت مقطع صوتي في المجموعة %2$s + %1$s ثبت \"%2$s\" + %1$s ثبت رسالة + %1$s ثبت صورة + %1$s ثبت فيديو + %1$s ثبت ملف + %1$s ثبت ملصق + %1$s ثبت رسالة صوتية + %1$s ثبت جهة اتصال + %1$s ثبت خريطة + %1$s ثبت صورة متحركة + %1$s ثبت مقطع صوتي اختر جهة اتصال لا توجد جهات اتصال بعد @@ -372,9 +447,14 @@ مغادرة المجموعة وحذفها الإشعارات إخراج من المجموعة - قم بالتحديث لمجموعة خارقة - فضلًا تذكر أن أعضاء هذه المجموعة يلزمهم تحديث تطبيقات تيليجرام لأحدث النسخ ليتمكنوا من الإستفادة من المجموعات الخارقة. هل أنت متأكد من رغبتك في ترقية المجموعة؟ - ]]>تم الوصول للحد الأعلى للأعضاء]]>\n\nيمكنك ترقية مجموعتك لتصبح مجموعة خارقة لتتمكن من إضافة أعضاء أكثر من الحد الأعلى وخصائص مثل:\n\n• الحد الأعلى للأعضاء يصبح %1$s عضو\n• الأعضاء الجدد يرون تاريخ محادثات المجموعة بشكل كامل\n• المشرفون يمكنهم حذف رسائل كافة الأعضاء\n• الإشعارات على وضع الصامت بشكل تلقائي + قم بالتحديث لمجموعة خارقة + التحويل إلى مجموعة خارقة + التحويل إلى مجموعة خارقة + تحذير + هذا القرار لا يمكن الرجوع عنه. لن تتمكن من إرجاع المجموعات الخارقة لتصبح مجموعات عادية. + ]]>تم الوصول للحد القصى.]]>\n\nلتتمكن من تخطي هذا الحد والحصول على مميزات إضافية، يمكنك الترقية لمجموعة خارقة:\n\n• المجموعات الخارقة يمكن أن تصل إلى %1$s عضو\n• الأعضاء الجدد يمكنهم رؤية سجل المحادثات بالكامل \n• الرسائل المحذوفة ستختفي عن كل الأعضاء\n• يستطيع الأعضاء التعديل على رسائلهم\n• يمكن لمنشئ المجموعة إنشاء رابط دعوة لها + ]]>في المجموعات الخارقة]]>\n\n• الأعضاء الجدد يمكنهم رؤية سجل المحادثات بالكامل \n• الرسائل المحذوفة ستختفي عن كل الأعضاء\n• يستطيع الأعضاء التعديل على رسائلهم\n• يمكن لمنشئ المجموعة إنشاء رابط دعوة لها + ]]>ملاحظة:]]> لا يمكنك الرجوع عن هذا القرار. مشاركة إضافة @@ -464,6 +544,8 @@ اسأل أحد المتطوعين الأسئلة الشائعة عن تيليجرام https://telegram.org/faq/ar + سياسة الخصوصية + https://telegram.org/privacy حذف التعريب؟ ملف التعريب غير صحيح تمكين @@ -524,6 +606,10 @@ دقائق معاينة الرابط المحادثات السرية + علامات تبويب كروم المخصصة + فتح الروابط الخارجية داخل التطبيق + مشاركة مباشرة + اظهار المحادثات الأخيرة في قائمة المشاركة إعدادات الذاكرة المخبئية قاعدة البيانات على الجهاز @@ -575,14 +661,18 @@ حساس اللمس لم يتم التعرف على البصمة. حاول مرة أخرى - شارك المقاطع المرئية والصور في هذه المحادثة لتستطيع الوصول إليها من أية جهاز من أجهزتك الملفات المشاركة الوسائط المشتركة الروابط المشاركة الموسيقى المشتركة + شارك المقاطع المرئية والصور في هذه المحادثة لتستطيع الوصول إليها من أية جهاز من أجهزتك قم بإرسال مقاطع موسيقية لهذه المحادثة ليمكنك الوصول إليها من أجهزتك الأخرى. شارك الملفات والمستندات في هذه المحادثة لتستطيع الوصول إليها من أية جهاز من أجهزتك شارك الروابط في هذه المحادثة لتستطيع الوصول إليها من أية جهاز من أجهزتك + الصور والفيديو من هذه المحادثة سيتم عرضها هنا. + المقاطع الصوتية من هذه المحادثة سيتم عرضها هنا. + الملفات والمستندات من هذه المحادثة سيتم عرضها هنا. + الروابط المشاركة من هذه المحادثة سيتم عرضها هنا. الخريطة قمر صناعي @@ -687,7 +777,7 @@ إعادة تعيين حسابي إذا قمت بإعادة تعيين حسابك، ستفقد كافّة محادثاتك ورسائلك، بالإضافة إلى الوسائط والملفات التي تمت مشاركتها. تحذير - لا يمكن الرجوع عن هذا الخيار.\n\nإذا قمت بإعادة تعيين حسابك، كافة رسائلك ومحادثاتك سيتم حذفها. + لا يمكنك الرجوع عن هذا القرار\n\nإذا قمت بإعادة تعيين حسابك، كافة رسائلك ومحادثاتك سيتم حذفها. إعادة تعيين كلمة المرور تم تفعيل التحقق بخطوتين، لذلك حسابك محميّ بكلمة مرور إضافية. @@ -803,6 +893,7 @@ un1 قام بإخراجك un1 قام بإضافتك un1 عاد إلى المجموعة + un1 انضم للمجموعة لقد عدت إلى المجموعة نسخة تيليجرام الموجودة لديك لا تدعم هذه الرسالة. الرجاء التحديث لأحدث نسخة: https://telegram.org/update صورة @@ -849,7 +940,7 @@ هل أنت متأكد من رغبتك في إلغاء التسجيل؟ هل أنت متأكد من رغبتك في حذف سجل المحادثات؟ حذف كافة المحادثات والوسائط المتعلقة بهذه القناة من الذاكرة المخبئية؟ - حذف كافة المحادثات والوسائط المتعلقة بهذه المجموعة الخارقة من الذاكرة المخبئية؟ + حذف كافة المحادثات والوسائط المتعلقة بهذه المجموعة من الذاكرة المخبئية؟ هل أنت متأكد من رغبتك في حذف %1$s؟ هل ترغب في إرسال رسالة إلى %1$s؟ أرسل جهة الاتصال إلى %1$s؟ @@ -861,6 +952,9 @@ يرجى ملاحظة أن البوتات أثناء الكتابة يتم تطويرها من مطورين مستقلين. لكي تعمل هذه البوتات، ما تكتبه بعد معرف البوت يذهب لمطور البوت. هل ترغب في تفعيل خاصية رفع الجهاز لإرسال الرسائل الصوتية؟ المعذرة، لا يمكنك التعديل على هذه الرسالة. + فصلًا قم بالسماح لتيليجرام باستقبال رسائل قصيرة ليتمكن من إدخال الرمز لك تلقائيًا. + فصلًا قم بالسماح لتيليجرام باستقبال اتصالات ليتمكن من إدخال الرمز لك تلقائيًا. + فصلًا قم بالسماح لتيليجرام باستقبال اتصالات ورسائل قصيرة ليتمكن من إدخال الرمز لك تلقائيًا. تيليجرام يحتاج للسماح له بالوصول لجهات الاتصال الخاصة بك لتتمكن من محادثة أصدقائك من كافة أجهزتك. تيليجرام يحتاج للسماح له بالوصول للذاكرة الخاصة بك لتتمكن من إرسال وحفظ الصور، المقاطع المرئية، الموسيقى وغيرها من الوسائط. diff --git a/TMessagesProj/src/main/res/values-de/strings.xml b/TMessagesProj/src/main/res/values-de/strings.xml index 7868b705f8..080418ecc2 100644 --- a/TMessagesProj/src/main/res/values-de/strings.xml +++ b/TMessagesProj/src/main/res/values-de/strings.xml @@ -14,9 +14,13 @@ Wähle ein Land Falsche Landesvorwahl - Dein Code - Wir haben dir eine SMS mit einem Aktivierungscode zugeschickt + Nummer verifizieren + Wir haben dir eine SMS mit Aktivierungscode an ]]>%1$s]]> gesendet. + Code wurde dir per ]]>Telegram]]> an deine aktive App geschickt. + Wir rufen dich jetzt unter ]]>%1$s]]> an. Bitte den Anruf nicht annehmen, Telegram kümmert sich um alles. + Wir rufen ]]>%1$s]]>an, um dir einen Code zu diktieren. Wir rufen dich an in %1$d:%2$02d + Wir senden Dir eine SMS in %1$d:%2$02d Wir rufen dich an… Code Falsche Nummer? @@ -52,7 +56,7 @@ Gelöschtes Konto Chat auswählen Tippen und Halten - %1$s benutzt eine ältere Version von Telegram, sodass Fotos in Geheimen Chats im Kompatibilitätsmodus angezeigt werden.\n\nSobald %2$s Telegram aktualisiert, werden Fotos mit Timern von 1 Minute und kürzer per \"Tippen und Halten\" angezeigt. Du wirst benachrichtigt, sobald dein Chatpartner ein Bildschirmfoto macht. + %1$s benutzt eine ältere Version von Telegram, sodass Bilder in Geheimen Chats im Kompatibilitätsmodus angezeigt werden.\n\nSobald %2$s Telegram aktualisiert, werden Bilder mit Timern von 1 Minute und kürzer per \"Tippen und Halten\" angezeigt. Du wirst benachrichtigt, sobald dein Chatpartner ein Bildschirmfoto macht. NACHRICHTEN Suche Stummschalten @@ -64,6 +68,10 @@ LETZTE Linkvorschau + Gruppenart + Kanalart + Öffentlich + Privat Zum Admin machen Beschreibe deine Gruppe (optional). Gruppe verlassen @@ -81,6 +89,17 @@ Dieser Nutzer hat die Gruppe zu verlassen, deshalb kannst du ihn nicht wieder einladen. Es gibt bereits zu viele Administratoren. Es gibt bereits zu viele Bots. + un1 hat \"%1$s\" angeheftet + un1 hat eine Nachricht angeheftet + un1 hat ein Bild angeheftet + un1 hat ein Video angeheftet + un1 hat eine Datei angeheftet + un1 hat einen Sticker angeheftet + un1 hat eine Sprachnachricht angeheftet + un1 hat einen Kontakt angeheftet + un1 hat einen Standort angeheftet + un1 hat ein GIF angeheftet + un1 hat ein Musikstück angeheftet Gruppe wurde in eine Supergruppe geändert %1$s wurde in eine Supergruppe geändert Blockierte Nutzer können nur durch Admins erneut hinzugefügt werden. Einladungslinks funktionieren nicht. @@ -90,15 +109,21 @@ Wenn du Kommentare aktivierst, können sich alle an der Diskussion beteiligen. Kontakte zum Kanal hinzufügen Jeder kann diesen Link teilen und deinen Kanal in der Telegramsuche finden. - + Jeder kann diesen Link teilen und deine Gruppe in der Telegramsuche finden. Link Alle Telegramnutzer können mit diesem Link deinem Kanal beitreten, du kannst ihn aber jederzeit widerrufen. + Alle können mit diesem Link deiner Gruppe beitreten, du kannst ihn aber jederzeit widerrufen. + Beschreibung (optional) Beschreibung Beschreibe deinen Kanal (optional). öffentlich + Öffentliche Gruppe Kann jeder über die Suche finden + Öffentliche Gruppen kann jeder über die Suche finden, gesamter Chatverlauf ist für alle einsehbar und jeder kann der Gruppe beitreten. privat + Private Gruppe Kann man nur per Einladungslink finden + Private Gruppen können nur nur durch direkte Einladungen oder über einen Einladungslink betreten werden. Link Einladungslink Mitglieder hinzufügen @@ -108,6 +133,7 @@ BEITRETEN Info Broadcast + Lautloser Broadcast Kommentar Kommentare zeigen Was ist ein Kanal? @@ -118,8 +144,8 @@ Kanalnamen benötigen mindestens 5 Zeichen. Der Name darf maximal 32 Zeichen haben. Kanalnamen dürfen nicht mit einer Zahl anfangen. - - + Gruppennamen benötigen mindestens 5 Zeichen. + Gruppennamen dürfen nicht mit einer Zahl anfangen. Überprüfe Namen... %1$s ist verfügbar. Mitglieder @@ -131,7 +157,7 @@ Möchtest du wirklich diesen Kanal verlassen? Du verlierst dadurch alle Nachrichten des Kanals. Bearbeiten - + Wenn du einen öffentlichen Link für deinen Kanal festlegst, kann ihn jeder über die Suche finden und beitreten.\n\nWenn du das nicht möchtest, erstelle besser keinen Link. Wenn du einen öffentlichen Link für deinen Kanal festlegst, kann ihn jeder über die Suche finden und beitreten.\n\nWenn du das nicht möchtest, erstelle besser keinen Link. Bitte wähle einen Link für deinen öffentlichen Kanal, damit andere ihn finden und weiter verbreiten können.\n\nWenn du das nicht möchtest, empfehlen wir dir einen privaten Kanal. Kanal erstellt @@ -139,6 +165,7 @@ Bild gelöscht Kanalname zu un2 geändert Du hast zu viele öffentliche Kanäle erstellt. Du kannst entweder einen privaten Kanal erstellen oder einen bestehenden Kanal löschen. + Du hast zu viele öffentliche Links erstellt. Lösche bitte welche oder stelle einige auf privat um. Moderator Gründer Administrator @@ -154,6 +181,8 @@ Administratoren helfen dir, deinen Kanal zu verwalten. Tippen und halten um sie zu löschen. Möchtest du dem Kanal \'%1$s\' beitreten? Dieser Chat ist nicht mehr zugänglich. + Du wurdest gesperrt und kannst öffentliche Gruppen nicht betreten. + Dieser Chat ist nicht mehr zugänglich. %1$s zum Kanal hinzufügen? Dieser Nutzer hat die Gruppe zu verlassen, deshalb kannst du ihn nicht wieder einladen. Du kannst diesen Nutzer nicht einladen. @@ -162,6 +191,7 @@ Du kannst nur die ersten 200 Leute einladen, aber unbegrenzt viele können dem Kanal über den Einladungslink beitreten. un1 hat dich hinzugefügt Du bist dem Kanal beigetreten + Du bist der Gruppe beigetreten Aus Kanal entfernen Du darfst in diesem Kanal nichts schreiben. %1$s hat dich dem Kanal %2$s hinzugefügt @@ -174,6 +204,7 @@ %1$s hat eine Datei an den Kanal %2$s gesendet %1$s hat ein GIF an den Kanal %2$s gesendet %1$s hat eine Sprachnachricht an den Kanal %2$s gesendet + %1$s hat ein Musikstück an den Kanal %2$s gesendet %1$s hat einen Sticker an den Kanal %2$s gesendet %1$s hat eine Nachricht gesendet %1$s hat ein Bild gesendet @@ -183,6 +214,7 @@ %1$s hat eine Datei gesendet %1$s hat ein GIF gesendet %1$s hat eine Sprachnachricht gesendet + %1$s hat ein Musikstück gesendet %1$s hat einen Sticker gesendet Wer kann Mitglieder einladen? Alle Mitglieder @@ -219,8 +251,8 @@ Bilder ohne Komprimierung senden unsichtbar - schreibt… - schreibt... + tippt… + tippt... tippen… %1$s nimmt etwas auf... %1$s schickt Bild... @@ -231,7 +263,7 @@ schickt Video... schickt Datei... Hast du eine Frage\nzu Telegram? - Foto aufnehmen + Bild aufnehmen Galerie Standort Video @@ -272,15 +304,33 @@ %1$s senden URL %1$s öffnen? SPAM MELDEN + SPAM MELDEN UND VERLASSEN KONTAKT HINZUFÜGEN Sicher, dass du Spam von diesem Nutzer melden willst? Sicher, dass du Spam von dieser Gruppe melden willst? + Sicher. dass du Spam von diesem Kanal melden möchtest? Derzeit kannst du nur Kontakten schreiben, die auch deine Nummer haben. Derzeit kannst du nur Kontakte hinzufügen, die auch deine Nummer haben. https://telegram.org/faq/de#kann-keine-nachrichten-an-nicht-kontakte-senden Mehr Infos Sende an... Hier tippen um gespeicherte GIFs zu sehen + Anheften + Mitglieder benachrichtigen + Entfernen + Nachricht in der Gruppe wirklich anheften? + Angeheftete Nachricht wieder entfernen? + Nutzer sperren + Spam melden + Lösche alles von %1$s + Zuletzt benutzte Emoji leeren? + Melden + Spam + Gewalt + Pornografie + Sonstiges + Beschreibung + Angeheftete Nachricht %1$s hat den Selbstzerstörungs-Timer auf %2$s gesetzt Du hast den Selbstzerstörungs-Timer auf %1$s gesetzt @@ -289,29 +339,32 @@ Du hast eine neue Nachricht %1$s: %2$s %1$s hat dir eine Nachricht gesendet - %1$s hat dir ein Foto gesendet + %1$s hat dir ein Bild gesendet %1$s hat dir ein Video gesendet %1$s hat dir einen Kontakt gesendet %1$s hat dir einen Standort gesendet %1$s hat dir eine Datei gesendet %1$s hat dir ein GIF gesendet %1$s hat dir eine Sprachnachricht gesendet + %1$s hat dir ein Musikstück gesendet %1$s hat dir einen Sticker gesendet %1$s @ %2$s: %3$s %1$s hat eine Nachricht an die Gruppe %2$s gesendet - %1$s hat ein Foto an die Gruppe %2$s gesendet + %1$s hat ein Bild an die Gruppe %2$s gesendet %1$s hat ein Video an die Gruppe %2$s gesendet %1$s hat einen Kontakt an die Gruppe %2$s gesendet %1$s hat einen Standort an die Gruppe %2$s gesendet %1$s hat eine Datei an die Gruppe %2$s gesendet %1$s hat ein GIF an die Gruppe %2$s gesendet %1$s hat eine Sprachnachricht an die Gruppe %2$s gesendet + %1$s hat ein Musikstück an die Gruppe %2$s gesendet %1$s hat einen Sticker an die Gruppe %2$s gesendet %1$s hat dich in die Gruppe %2$s eingeladen %1$s hat den Namen der Gruppe %2$s geändert %1$s hat das Bild der Gruppe %2$s geändert %1$s hat %3$s in die Gruppe %2$s eingeladen %1$s ist in die Gruppe %2$s zurückgekehrt + %1$s ist der Gruppe %2$s beigetreten %1$s hat %3$s aus der Gruppe %2$s entfernt %1$s hat dich aus der Gruppe %2$s entfernt %1$s hat die Gruppe %2$s verlassen @@ -323,6 +376,28 @@ %1$s antworten %1$s antworten %1$s %2$s + %1$s hat \"%2$s\" in der Gruppe %3$s angeheftet + %1$s hat eine Nachricht in der der Gruppe %2$s angeheftet + %1$s hat ein Bild in der Gruppe %2$s angeheftet + %1$s hat ein Video in der Gruppe %2$s angeheftet + %1$s hat eine Datei in der Gruppe %2$s angeheftet + %1$s hat einen Sticker in der Gruppe %2$s angeheftet + %1$s hat eine Sprachnachricht in der Gruppe %2$s angeheftet + %1$s hat einen Kontakt in der Gruppe %2$s angeheftet + %1$s hat einen Standort in der Gruppe %2$s angeheftet + %1$s hat ein GIF in der Gruppe %2$s angeheftet + %1$s hat ein Musikstück in der Gruppe %2$s angeheftet + %1$s hat \"%2$s\" angeheftet + %1$s hat eine Nachricht angeheftet + %1$s hat ein Bild angeheftet + %1$s hat ein Video angeheftet + %1$s hat eine Datei angeheftet + %1$s hat einen Sticker angeheftet + %1$s hat eine Sprachnachricht angeheftet + %1$s hat einen Kontakt angeheftet + %1$s hat einen Stamdort angeheftet + %1$s hat ein GIF angeheftet + %1$s hat ein Musikstück angeheftet Kontakt auswählen Noch keine Kontakte @@ -372,9 +447,14 @@ Löschen und Gruppe verlassen Mitteilungen Aus der Gruppe entfernen + In Supergruppe ändern In Supergruppe ändern - Gruppenmitglieder müssen ihre Telegram-App aktualisieren um diese Supergruppe benutzen zu können. Wirklich diese Gruppe in eine Supergruppe ändern? - ]]>Gruppenlimit erreicht.]]>\n\nFür weitere Funktionen und um das Limit aufzuheben in Supergruppe ändern:\n\n• Bis zu %1$s sind nun möglich\n• Neue Mitglieder sehen gesamten Verlauf\n• Mitteilungen sind standardmäßig stumm\n• Admins können alle Nachrichten löschen + In Supergruppe ändern + Warnung + Du kannst die Supergruppe nicht mehr in eine normale Gruppe ändern. + ]]>Gruppenlimit erreicht.]]>\n\nFür weitere Funktionen und um das Limit aufzuheben in Supergruppe ändern:\n\n• Bis zu %1$s sind nun möglich\n• Neue Mitglieder sehen gesamten Verlauf\n• Mitglieder können eigene Nachrichten bearbeiten\n• Gruppenersteller kann die Gruppe öffentlich machen + ]]>In Supergruppen:]]>\n\n• Neue Mitglieder sehen gesamten Verlauf\n• Nachrichten werden bei allen gelöscht\n• Jeder kann eigene Nachrichten bearbeiten\n• Gründer kann Gruppe öffentlich machen + ]]>Wichtig:]]> Die Änderung kann nicht rückgängig gemacht werden. Teilen Hinzufügen @@ -464,6 +544,8 @@ Eine Frage stellen Fragen und Antworten https://telegram.org/faq/de + Datenschutzerklärung + https://telegram.org/privacy Lokalisierung löschen? Falsche Sprachdatei Aktiviert @@ -524,6 +606,10 @@ Minuten Linkvorschau Geheime Chats + In-App Browser + Externe Links innerhalb der App öffnen + Direktes Teilen + Letzte Chats im Teilen-Menü anzeigen Cache-Einstellungen Lokale Datenbank @@ -575,14 +661,18 @@ Berührungssensor Abdruck nicht erkannt; erneut versuchen - Die hier geteilten Bilder und Videos kannst du von jedem deiner Geräte aufrufen. Geteilte Dateien Geteilte Medien Geteilte Links Geteilte Musik + Die hier geteilten Bilder und Videos kannst du von jedem deiner Geräte aufrufen. Die hier geteilten Lieder kannst du von jedem deiner Geräte aufrufen. Die hier geteilten Dateien kannst du von jedem deiner Geräte aufrufen. Die hier geteilten Links kannst du von jedem deiner Geräte aufrufen. + Geteilte Bilder und Videos werden hier angezeigt. + Musik aus dem Chat wird hier angezeigt. + Dateien und Dokumente werden hier angezeigt. + Alle geteilten Links werden hier angezeigt. Karte Satellit @@ -599,9 +689,9 @@ In der Galerie speichern %1$d von %2$d Galerie - Alle Fotos + Alle Bilder Alle Videos - Noch keine Fotos + Noch keine Bilder Noch keine Videos Medien bitte zuerst herunterladen Suchverlauf @@ -779,7 +869,7 @@ Erneut versuchen Von der Kamera Aus der Galerie - Foto löschen + Bild löschen Wählen OK SCHNEIDEN @@ -803,9 +893,10 @@ un1 hat dich aus der Gruppe entfernt un1 hat dich hinzugefügt un1 ist in die Gruppe zurückgekehrt + un1 ist der Gruppe beigetreten Du bist in die Gruppe zurückgekehrt Diese Nachricht wird von deiner Telegram-Version nicht unterstützt. Bitte aktualisiere Telegram um sie zu sehen: https://telegram.org/update - Foto + Bild Video GIF Standort @@ -849,7 +940,7 @@ Bist du dir sicher, dass du die Registrierung abbrechen willst? Möchtest du wirklich den Verlauf löschen? Cache des Kanals wirklich löschen? - Cache der Supergruppe wirklich löschen? + Cache dieser Gruppe wirklich löschen? Sicher, dass du %1$s löschen willst? Nachricht an %1$s senden? Kontakt senden an %1$s? @@ -861,6 +952,9 @@ Inline Bots werden von Drittentwicklern erstellt. Symbole, die du nach dem Botnamen eingibst, werden an den jeweiligen Entwickler geschickt, damit der Bot funktioniert. Möchtest du \"Zum Sprechen ans Ohr\" für Sprachnachrichten aktivieren? Du kannst diese Nachricht nicht bearbeiten. + Bitte erlaube Telegram den Zugriff auf SMS, so dass wir den Code automatisch in der App für dich eingeben können. + Bitte erlaube Telegram den Zugriff auf Anrufe, so dass wir den Code automatisch in der App eingeben können. + Bitte erlaube Telegram den Zugriff auf SMS und Anrufe, so dass wir den Code automatisch in der App eingeben können. Telegram benötigt Zugriff auf deine Kontakte um dich auf all denen Geräten mit deinen Freunden zu verbinden. Telegram benötigt Zugriff auf deinen Speicher, damit du Bilder, Videos und Musik senden und speichern kannst. diff --git a/TMessagesProj/src/main/res/values-es/strings.xml b/TMessagesProj/src/main/res/values-es/strings.xml index 74d816424e..9e80947eea 100644 --- a/TMessagesProj/src/main/res/values-es/strings.xml +++ b/TMessagesProj/src/main/res/values-es/strings.xml @@ -14,9 +14,13 @@ Elige un país Código de país incorrecto - Tu código - Enviamos un SMS con el código de activación al número + Verificación + Enviamos un SMS con el código de activación al ]]>%1$s]]>. + Enviamos el código a la aplicación ]]>Telegram]]> en tu otro dispositivo. + Enviamos una llamada de activación al ]]>%1$s]]>.\n\nNo la contestes. Telegram hará todo el proceso automáticamente. + Estamos llamando al ]]>%1$s]]> para dictar un código. Te llamaremos en %1$d:%2$02d + Te enviaremos un SMS en %1$d:%2$02d Llamándote... Código ¿Número incorrecto? @@ -64,6 +68,10 @@ RECIENTES Vista previa del enlace + Tipo de grupo + Tipo de canal + Público + Privado Nombrar como administrador Puedes poner una descripción para tu grupo. Dejar el grupo @@ -71,7 +79,7 @@ Dejar el grupo Eliminar grupo Perderás todos los mensajes en este grupo. - Puedes añadir administradores para que te ayuden a dirigir el canal. Mantén pulsado para eliminarlos. + Puedes añadir administradores para que te ayuden en el grupo. Mantén pulsado para eliminarlos. ¡Espera! Al eliminar este grupo, todos los miembros y los mensajes se perderán. ¿Quieres eliminarlo? Grupo creado un1 te añadió a este grupo @@ -79,8 +87,19 @@ Lo sentimos, no puedes añadir este usuario a grupos. Lo sentimos, el grupo está lleno. Lo sentimos, este usuario decidió dejar el grupo, así que no puedes invitarlo otra vez. - Lo sentimos, hay demasiados administradores en el grupo. + Hay demasiados administradores en el grupo. Lo sentimos, hay demasiados bots en el grupo. + un1 ancló \"%1$s\" + un1 ancló un mensaje + un1 ancló una foto + un1 ancló un vídeo + un1 ancló un archivo + un1 ancló un sticker + un1 ancló un mensaje de voz + un1 ancló un contacto + un1 ancló un mapa + un1 ancló un GIF + un1 ancló una pista Este grupo fue convertido en un supergrupo %1$s fue convertido en un supergrupo Los usuarios bloqueados son eliminados del grupo y sólo pueden volver si son invitados por un administrador. Los enlaces de invitación no funcionan para ellos. @@ -90,15 +109,21 @@ Si activas los comentarios, las personas podrán hablar de tus mensajes en el canal. Añadir contactos a tu canal Las personas pueden compartir este enlace con los demás y encontrar tu canal usando la búsqueda de Telegram. - + Las personas pueden compartir este enlace con los demás y encontrar tu grupo usando la búsqueda de Telegram. Enlace Las personas pueden unirse a tu canal siguiendo este enlace. Puedes anular el enlace en cualquier momento. + Las personas pueden unirse a tu canal siguiendo este enlace. Puedes anular el enlace en cualquier momento. + Descripción (opcional) Descripción Puedes poner una descripción para tu canal. Canal público + Grupo público Cualquiera puede unirse a los canales públicos, tras encontrarlos en la búsqueda. + Los grupos públicos pueden encontrarse en la búsqueda, su historial está disponible para todos y cualquiera puede unirse. Canal privado + Grupo privado Puedes unirte a canales privados sólo con enlaces de invitación. + Puedes unirte a un grupo privado sólo si te invitan o tienes un enlace de invitación. Enlace Enlace de invitación Añadir miembros @@ -108,6 +133,7 @@ UNIRSE Información Difundir + Difusión en silencio Comentario mostrar comentarios ¿Qué es un canal? @@ -118,8 +144,8 @@ El nombre del canal debe tener al menos 5 caracteres. El nombre no debe exceder los 32 caracteres. El nombre del canal no puede comenzar con un número. - - + El nombre del grupo debe tener al menos 5 caracteres. + El nombre del grupo no puede comenzar con un número. Verificando nombre... %1$s está disponible. Miembros @@ -131,14 +157,15 @@ ¿Quieres dejar este canal? Perderás todos los mensajes en este canal. Editar - - Por favor, ten en cuenta que si eliges un enlace público para tu canal, cualquiera podrá encontrarlo en la búsqueda y unirse.\n\nNo crees este enlace si quieres que tu canal sea privado. + Ten en cuenta que, si eliges un enlace público para tu grupo, cualquiera podrá encontrarlo en la búsqueda y unirse.\n\nNo crees este enlace si quieres que tu supergrupo sea privado. + Ten en cuenta que, si eliges un enlace público para tu canal, cualquiera podrá encontrarlo en la búsqueda y unirse.\n\nNo crees este enlace si quieres que tu canal sea privado. Por favor, elige un enlace para tu canal público. Así, las personas podrán encontrarlo en la búsqueda y compartirlo con otros.\n\nSi no estás interesado, te sugerimos crear un canal privado. Canal creado Foto del canal cambiada Foto del canal eliminada Nombre del canal cambiado a un2 Lo sentimos, has creado demasiados canales públicos. Puedes crear un canal privado o eliminar uno de tus canales existentes primero. + Has creado demasiados canales públicos. Prueba eliminando o haciendo privado uno de tus grupos o canales. Moderador Creador Administrador @@ -151,9 +178,11 @@ Sólo los administradores del canal pueden ver esta lista. Este usuario aún no se ha unido al canal. ¿Quieres invitarlo? Cualquiera que tenga Telegram instalada podrá unirse a tu canal siguiendo este enlace. - Puedes añadir administradores para que te ayuden en el canal. Mantén pulsado para eliminar un administrador. + Puedes añadir administradores para que te ayuden en el canal. Mantén pulsado para eliminarlos. ¿Quieres unirte al canal \'%1$s\'? - Lo sentimos, este canal ya no es accesible. + Lo sentimos, este chat ya no es accesible. + Lamentablemente, fuiste suspendido de participar en grupos públicos. + Lo sentimos, este chat ya no es accesible. ¿Añadir a %1$s al canal? Lo sentimos, este usuario decidió dejar el canal, así que no puedes invitarlo otra vez. Lo sentimos, no puedes añadir a este usuario a canales. @@ -161,7 +190,8 @@ Lo sentimos, hay demasiados bots en el canal. Lo sentimos, sólo puedes añadir a los primeros 200 miembros a un canal. Sin embargo, una cantidad ilimitada de personas pueden unirse por el enlace del canal. un1 te añadió a este canal - Te uniste al canal + Te uniste a este canal + Te uniste a este grupo Eliminar del canal Lo sentimos, no puedes enviar mensajes en este canal. %1$s te añadió al canal %2$s @@ -174,6 +204,7 @@ %1$s envió un archivo al canal %2$s %1$s envió un GIF al canal %2$s %1$s envió un mensaje de voz al canal %2$s + %1$s envió una pista al canal %2$s %1$s envió un sticker al canal %2$s %1$s publicó un mensaje %1$s publicó una foto @@ -183,6 +214,7 @@ %1$s publicó un archivo %1$s publicó un GIF %1$s publicó un mensaje de voz + %1$s publicó una pista %1$s publicó un sticker ¿Quién puede invitar? Todos @@ -272,15 +304,33 @@ Enviar %1$s ¿Abrir %1$s? REPORTAR SPAM + REPORTAR SPAM Y SALIR AÑADIR CONTACTO ¿Quieres reportar a este usuario como spam? ¿Quieres reportar a este grupo como spam? + ¿Quieres reportar spam de este canal? Lo sentimos, por ahora puedes enviar mensajes sólo a contactos mutuos. Lo sentimos, por ahora sólo puedes añadir contactos mutuos a un grupo. https://telegram.org/faq/es#no-puedo-enviar-mensajes-a-quienes-no-son-mis-contactos Más información Enviar a... Pulsa y ve los GIF guardados + Anclar + Notificar a los miembros + Desanclar + ¿Quieres anclar este mensaje en el grupo? + ¿Quieres desanclar este mensaje? + Suspender usuario + Reportar spam + Eliminar todo lo de %1$s + ¿Borrar los emojis recientes? + Reportar + Spam + Violencia + Pornografía + Otros + Descripción + Mensaje anclado %1$s activó la autodestrucción en %2$s Activaste la autodestrucción en %1$s @@ -296,6 +346,7 @@ %1$s te envió un archivo %1$s te envió un GIF %1$s te envió un mensaje de voz + %1$s te envió una pista %1$s te envió un sticker %1$s @ %2$s: %3$s %1$s envió un mensaje al grupo %2$s @@ -306,12 +357,14 @@ %1$s envió un archivo al grupo %2$s %1$s envió un GIF al grupo %2$s %1$s envió un mensaje de voz al grupo %2$s + %1$s envió una pista al grupo %2$s %1$s envió un sticker al grupo %2$s %1$s te invitó al grupo %2$s %1$s cambió el nombre del grupo %2$s %1$s cambió la foto del grupo %2$s %1$s invitó a %3$s al grupo %2$s %1$s volvió al grupo %2$s + %1$s se unió al grupo %2$s %1$s eliminó a %3$s del grupo %2$s %1$s te eliminó del grupo %2$s %1$s dejó el grupo %2$s @@ -323,6 +376,28 @@ Responder a %1$s Responder a %1$s %1$s %2$s + %1$s ancló \"%2$s\" en el grupo %3$s + %1$s ancló un mensaje en el grupo %2$s + %1$s ancló una foto en el grupo %2$s + %1$s ancló un vídeo en el grupo %2$s + %1$s ancló un archivo en el grupo %2$s + %1$s ancló un sticker en el grupo %2$s + %1$s ancló un mensaje de voz en el grupo %2$s + %1$s ancló un contacto en el grupo %2$s + %1$s ancló un mapa en el grupo %2$s + %1$s ancló un GIF en el grupo %2$s + %1$s ancló una pista en el grupo %2$s + %1$s ancló \"%2$s\" + %1$s ancló un mensaje + %1$s ancló una foto + %1$s ancló un vídeo + %1$s ancló un archivo + %1$s ancló un sticker + %1$s ancló un mensaje de voz + %1$s ancló un contacto + %1$s ancló un mapa + %1$s ancló un GIF + %1$s ancló una pista Elegir contacto Aún sin contactos @@ -372,9 +447,14 @@ Eliminar y dejar el grupo Notificaciones Eliminar del grupo + Convertir en supergrupo Convertir en supergrupo - Por favor, ten en cuenta que los miembros del grupo tendrán que actualizar Telegram a la última versión para ver tu supergrupo. ¿Quieres convertir el grupo? - ]]>Límite de miembros alcanzado.]]>\n\nPara superar el límite y tener características adicionales, conviértelo en un supergrupo:\n\n• Permiten hasta %1$s\n• Nuevos miembros ven todo el historial\n• Un admin. borra mensajes para todos\n• Notificaciones silenciadas por defecto + Convertir en supergrupo + Advertencia + Esta acción es irreversible. No puedes convertir un supergrupo en un grupo normal. + ]]>Límite de miembros alcanzado.]]>\n\nPara superar el límite y tener características adicionales, conviértelo en un supergrupo:\n\n• Permiten hasta %1$s\n• Nuevos miembros ven todo el historial\n• Un mensaje eliminado se borra para todos\n• Un miembro puede editar sus mensajes\n• El creador puede generar un enlace público + ]]>En los supergrupos:]]>\n\n• Los nuevos miembros ven todo el historial\n• Un mensaje eliminado se borra para todos\n• Un miembro puede editar sus mensajes\n• El creador puede generar un enlace público + ]]>Importante:]]> Esta acción no se puede deshacer. Compartir Añadir @@ -464,6 +544,8 @@ Preguntar Preguntas frecuentes https://telegram.org/faq/es + Política de privacidad + https://telegram.org/privacy ¿Eliminar traducción? Archivo de traducción incorrecto Activadas @@ -524,6 +606,10 @@ minutos Vistas previas de enlaces Chats secretos + Usar navegador interno + Abrir enlaces externos en la aplicación + Direct Share + Mostrar los chats recientes al compartir Ajustes de caché Base de datos local @@ -575,14 +661,18 @@ Sensor táctil Huella digital no reconocida. Reinténtalo - Comparte fotos y vídeos en este chat y accede a ellos desde cualquier dispositivo. Archivos Multimedia Enlaces Música + Comparte fotos y vídeos en este chat y accede a ellos desde cualquier dispositivo. Comparte música en este chat y accede a ella desde cualquier dispositivo. Comparte archivos en este chat y accede a ellos desde cualquier dispositivo. Comparte enlaces en este chat y accede a ellos desde cualquiera de tus dispositivos. + Las fotos y vídeos de este chat aparecerán aquí. + La música de este chat aparecerá aquí. + Los archivos de este chat aparecerán aquí. + Los enlaces de este chat aparecerán aquí. Mapa Satélite @@ -687,7 +777,7 @@ RESTABLECER MI CUENTA Si continúas con el reinicio de tu cuenta, perderás todos tus chats y mensajes, junto con toda la multimedia y archivos que compartiste. Advertencia - Esta acción no puede deshacerse.\n\nSi reinicias tu cuenta, todos tus mensajes y chats se eliminarán. + Esta acción no se puede deshacer.\n\nSi restableces tu cuenta, todos tus mensajes y chats se eliminarán. Restablecer Contraseña Activaste la verificación en dos pasos, así que tu cuenta está protegida con una contraseña adicional. @@ -803,6 +893,7 @@ un1 te eliminó un1 te añadió un1 volvió al grupo + un1 se unió al grupo Volviste al grupo Tu versión de Telegram no soporta este mensaje. Por favor, actualiza tu app para verlo: https://telegram.org/update Foto @@ -849,7 +940,7 @@ ¿Quieres cancelar el registro? ¿Quieres eliminar el historial? ¿Eliminar de la caché los mensajes y multimedia de este canal? - ¿Eliminar de la caché los mensajes y multimedia de este supergrupo? + ¿Eliminar de la caché los mensajes y multimedia de este grupo? ¿Quieres eliminar %1$s? ¿Enviar mensajes a %1$s? ¿Enviar contacto a %1$s? @@ -861,6 +952,9 @@ Ten en cuenta que los bots integrados son hechos por terceros. Para que funcione, los símbolos escritos después del alias del bot, son enviados al desarrollador respectivo. ¿Quieres habilitar \"Elevar para hablar\" para mensajes de voz? No puedes editar este mensaje. + Por favor, permite a Telegram recibir SMS, para ingresar el código automáticamente. + Por favor, permite a Telegram recibir llamadas, para ingresar el código automáticamente. + Por favor, permite a Telegram recibir llamadas y SMS, para ingresar el código automáticamente. Telegram necesita el acceso a tus contactos, para que puedas comunicarte con ellos en todos tus dispositivos. Telegram necesita acceso a tu almacenamiento, para que puedas enviar y guardar fotos, vídeos, música y otros archivos. diff --git a/TMessagesProj/src/main/res/values-it/strings.xml b/TMessagesProj/src/main/res/values-it/strings.xml index bcae59fba7..45334c650e 100644 --- a/TMessagesProj/src/main/res/values-it/strings.xml +++ b/TMessagesProj/src/main/res/values-it/strings.xml @@ -14,9 +14,13 @@ Scegli una nazione Prefisso errato - Il tuo codice - Abbiamo inviato un SMS con il codice di attivazione al tuo telefono + Verifica numero + Abbiamo inviato un SMS con un codice di attivazione al tuo numero ]]>%1$s]]>. + Abbiamo inviato il codice su ]]>Telegram]]> nell\'altro tuo dispositivo + Abbiamo inviato una chiamata di attivazione al tuo numero ]]>%1$s]]>.\n\nNon rispondere, Telegram farà tutto in automatico. + Stiamo chiamando il tuo numero ]]>%1$s]]> per dettarti un codice. Ti telefoneremo tra %1$d:%2$02d + Ti invieremo un SMS tra %1$d:%2$02d Ti stiamo chiamando… Codice Numero errato? @@ -64,6 +68,10 @@ RECENTI Anteprima link + Tipo di gruppo + Tipo di canale + Pubblico + Privato Rendi amministratore Puoi inserire una descrizione opzionale per il tuo gruppo. Lascia il gruppo @@ -81,6 +89,17 @@ Spiacenti, questo utente ha deciso di lasciare il gruppo, quindi non puoi reinvitarlo. Spiacenti, troppi amministratori in questo gruppo. Spiacenti, troppi bot in questo gruppo. + un1 ha fissato %1$s\" + un1 ha fissato un messaggio + un1 ha fissato una foto + un1 ha fissato un video + un1 ha fissato un file + un1 ha fissato uno sticker + un1 ha fissato un messaggio vocale + un1 ha fissato un contatto + un1 ha fissato una posizione + un1 ha fissato una GIF + un1 ha fissato una traccia Questo gruppo è stato aggiornato a supergruppo %1$s è stato aggiornato a supergruppo. Gli utenti in lista nera sono rimossi dal gruppo e possono tornare solo se invitati da un amministratore. I link di invito non funzionano per loro. @@ -90,15 +109,21 @@ Se attivi i commenti, i membri potranno discutere quello che pubblichi nel canale. Aggiungi contatti al tuo canale Le persone possono condividere questo link con gli altri e trovare il tuo canale usando la ricerca di Telegram. - + Le persone possono condividere questo link con gli altri e trovare il tuo gruppo usando la ricerca di Telegram. link - Le persone possono unirsi al tuo canale tramite questo link. Puoi revocare il link in ogni momento. + Le persone possono unirsi al tuo canale seguendo questo link. Puoi revocare il link in ogni momento. + Le persone possono unirsi al tuo gruppo seguendo questo link. Puoi revocare il link in ogni momento. + Descrizione (opzionale) Descrizione Puoi inserire una descrizione opzionale per il tuo canale. Canale pubblico + Gruppo pubblico I canali pubblici possono essere trovati nella ricerca, chiunque può unirsi. + I gruppi pubblici possono essere trovati nella ricerca, la cronologia è disponibile per tutti e chiunque può unirsi. Canale privato - Ci si può unire ai canali privati solo via link di invito. + Gruppo privato + È possibile unirsi ai canali privati solo via link di invito. + È possibile unirsi ai gruppi privati solo se sei stato invitato o se hai un link di invito. Link Link di invito Aggiungi membri @@ -107,7 +132,8 @@ Impostazioni UNISCITI Info canale - Broadcast + Post + Post silenzioso Commento mostra commenti Cos\'è un canale? @@ -115,11 +141,11 @@ CREA CANALE Spiacenti, questo nome è già stato preso. Spiacenti, questo nome non è valido. - I nomi devi canali devono avere almeno 5 caratteri. + I nomi dei canali devono avere almeno 5 caratteri. Il nome non può superare i 32 caratteri. I nomi dei canali non possono iniziare con un numero. - - + I nomi dei gruppi devono avere almeno 5 caratteri. + I nomi dei gruppi non possono iniziare con un numero. Controllo il nome... %1$s è disponibile. Membri @@ -131,7 +157,7 @@ Sei sicuro di voler lasciare il canale? Perderai tutti i messaggi in questo canale. Modifica - + Per favore ricorda che se scegli un link pubblico per il tuo gruppo, chiunque sarà in grado di trovarlo nella ricerca e unirsi.\n\nNon creare questo link se vuoi che il tuo supergruppo rimanga privato. Per favore ricorda che se scegli un link pubblico per il tuo canale, chiunque sarà in grado di trovarlo nella ricerca e unirsi.\n\nNon creare questo link se vuoi che il tuo canale rimanga privato. Per favore scegli un link per il tuo canale pubblico, in modo che possa essere trovato nella ricerca e condiviso con altri.\n\nSe non sei interessato, ti consigliamo di creare un canale privato. Canale creato @@ -139,6 +165,7 @@ Foto del canale rimossa Nome del canale cambiato in un2 Spiacenti, hai creato troppi canali pubblici. Puoi creare un canale privato o eliminare un tuo canale pubblico. + Spiacenti, hai creato troppi link pubblici. Prova a eliminarne o a rendere qualche gruppo o canale privato. Moderatore Creatore Amministratore @@ -154,6 +181,8 @@ Puoi aggiungere amministratori per farti aiutare a gestire il tuo canale. Tieni premuto per rimuovere gli amministratori. Vuoi unirti al canale \'%1$s\'? Spiacenti, questa chat non è più accessibile. + Sfortunatamente, non ti è permesso partecipare ai gruppi pubblici. + Spiacenti, questa chat non è più accessibile. Aggiungere %1$s al canale? Spiacenti, questo utente ha deciso di lasciare il canale, quindi non puoi reinvitarlo. Spiacenti, non puoi aggiungere questo utente ai canali. @@ -161,7 +190,8 @@ Spiacenti, troppi bot in questo canale. Spiacenti, puoi aggiungere solo i primi 200 membri a un canale. Ricorda che un numero illimitato di persone potrebbe unirsi tramite il link del canale. un1 ti ha aggiunto a questo canale - Ti sei unito al canale + Ti sei unito a questo canale + Ti sei unito a questo gruppo Rimuovi dal canale Spiacenti, non puoi inviare messaggi in questo canale %1$s ti ha aggiunto al canale %2$s @@ -174,6 +204,7 @@ %1$s ha inviato un file al canale %2$s %1$s ha inviato una GIF al canale %2$s %1$s ha inviato un messaggio vocale al canale %2$s + %1$s ha inviato una traccia al canale %2$s %1$s ha inviato uno sticker al canale %2$s %1$s ha pubblicato un messaggio %1$s ha pubblicato una foto @@ -183,14 +214,15 @@ %1$s ha pubblicato un file %1$s ha pubblicato una GIF %1$s ha pubblicato un messaggio vocale + %1$s ha pubblicato una traccia %1$s ha pubblicato uno sticker Chi può aggiungere nuovi membri? Tutti i membri Solo gli amministratori - Notifica i membri quando pubblichi - Non notifica i membri quando pubblichi + I post saranno notificati ai membri + I post non saranno notificati ai membri Firma messaggi - Aggiungi i nomi degli amministratori nei messaggi che pubblicano. + Aggiungi i nomi degli amministratori nei messaggi da loro pubblicati. Nuova lista broadcast Inserisci il nome della lista @@ -272,15 +304,33 @@ Invia %1$s Aprire url %1$s? SEGNALA SPAM + SEGNALA SPAM E LASCIA AGGIUNGI CONTATTO Sei sicuro di voler segnalare questo utente come spam? - Sei sicuro di voler segnalare dello spam in questo gruppo? - Spiacenti, ma al momento puoi scrivere solo contatti in comune. - Spiacenti, ma al momento puoi aggiungere ai gruppi solo contatti in comune. + Sei sicuro di voler segnalare dello spam da questo gruppo? + Sei sicuro di voler segnalare dello spam da questo canale? + Spiacenti, ma al momento puoi scrivere solo ai contatti reciproci. + Spiacenti, ma al momento puoi aggiungere ai gruppi solo contatti reciproci. https://telegram.org/faq/it#non-posso-inviare-messaggi-a-chi-non-far-parte-dei-miei-contatti Più info Invia a... Premi qui per accedere alle GIF salvate + Fissa + Notifica tutti i membri + Togli + Vuoi fissare questo messaggio in questo gruppo? + Vuoi togliere questo messaggio? + Rimuovi utente + Segnala spam + Elimina tutto da %1$s + Cancellare le emoji recenti? + Segnala + Spam + Violenza + Pornografia + Altro + Descrizione + Messaggio fissato %1$s ha impostato il timer di autodistruzione a %2$s Hai impostato il timer di autodistruzione a %1$s @@ -296,6 +346,7 @@ %1$s ti ha inviato un file %1$s ti ha inviato una GIF %1$s ti ha inviato un messaggio vocale + %1$s ti ha inviato una traccia %1$s ti ha inviato uno sticker %1$s @ %2$s: %3$s %1$s ha inviato un messaggio al gruppo %2$s @@ -306,12 +357,14 @@ %1$s ha inviato un file al gruppo %2$s %1$s ha inviato una GIF al gruppo %2$s %1$s ha inviato un messaggio vocale al gruppo %2$s + %1$s ha inviato una traccia al gruppo %2$s %1$s ha inviato uno sticker al gruppo %2$s %1$s ti ha invitato nel gruppo %2$s %1$s ha modificato il nome del gruppo %2$s %1$s ha modificato la foto del gruppo %2$s %1$s ha invitato %3$s nel gruppo %2$s %1$s è tornato nel gruppo %2$s + %1$s si è unito al gruppo %2$s %1$s ha rimosso %3$s dal gruppo %2$s %1$s ti ha rimosso dal gruppo %2$s %1$s ha lasciato il gruppo %2$s @@ -323,6 +376,28 @@ Rispondi a %1$s Rispondi a %1$s %1$s %2$s + %1$s ha fissato %3$s nel gruppo %2$s + %1$s ha fissato un messaggio nel gruppo %2$s + %1$s ha fissato una foto nel gruppo %2$s + %1$s ha fissato un video nel gruppo %2$s + %1$s ha fissato un file nel gruppo %2$s + %1$s ha fissato uno sticker nel gruppo %2$s + %1$s ha fissato un messaggio vocale nel gruppo %2$s + %1$s ha fissato un contatto nel gruppo %2$s + %1$s ha fissato una posizione nel gruppo %2$s + %1$s ha fissato una GIF nel gruppo %2$s + %1$s ha fissato una traccia nel gruppo %2$s + %1$s ha fissato \"%2$s\" + %1$s ha fissato un messaggio + %1$s ha fissato una foto + %1$s ha fissato un video + %1$s ha fissato un file + %1$s ha fissato uno sticker + %1$s ha fissato un messaggio vocale + %1$s ha fissato un contatto + %1$s ha fissato una posizione + %1$s ha fissato una GIF + %1$s ha fissato una traccia Seleziona contatto Ancora nessun contatto @@ -372,9 +447,14 @@ Elimina e lascia il gruppo Notifiche Rimuovi dal gruppo - Aggiorna a supergruppo - Per favore ricorda che i membri del gruppo dovranno aggiornare Telegram all\'ultima versione per vedere il tuo supergruppo. Sei sicuro di voler aggiornare questo gruppo? - ]]>Limite di membri raggiunto.]]>\n\nPer superare il limite ed avere ulteriori funzioni, aggiorna a un supergruppo:\n\n• I supergruppi hanno massimo %1$s\n• I nuovi membri vedono tutta la cronologia\n• Gli amministratori eliminano i messaggi per tutti\n• Le notifiche saranno silenziate di default. + Aggiorna a supergruppo + Converti in supergruppo + Converti in supergruppo + Attenzione + Questa azione è irreversibile. Non è possibile trasformare un supergruppo in un gruppo normale. + ]]>Limite membri raggiunto.]]>\n\nPer superare il limite e sbloccare nuove funzioni, aggiorna a supergruppo:\n\n• Il supergruppi hanno massimo %1$s\n• I nuovi membri vedono tutta la cronologia\n• I messaggi eliminati scompaiono per tutti\n• I membri possono modificare i loro messaggi\n• Il creatore può creare un link pubblico per il gruppo + ]]>Nei supergruppi:]]>\n\n• I nuovi membri vedono tutta la cronologia\n• I messaggi eliminati scompaiono per tutti\n• I membri possono modificare i loro messaggi\n• Il creatore può creare un link pubblico per il gruppo + ]]>Nota:]]> questa azione non può essere annullata. Condividi Aggiungi @@ -455,15 +535,17 @@ Solo se silenzioso Sfondo chat Messaggi - Spedisci con invio + Invia con tasto invio Termina le altre sessioni Eventi Un contatto si è unito a Telegram Lingua - Nota che il supporto di Telegram è fornito da volontari. Proviamo a rispondere non appena possibile, ma potrebbe volerci un pò.
]]>Dai un\'occhiata alle FAQ di Telegram]]>: troverai risposte alla maggior parte delle domande e suggerimenti importanti per l\'individuazione del problema]]>
+ Nota che il supporto di Telegram è fornito da volontari. Proviamo a rispondere quanto prima, ma potrebbe volerci del tempo.
]]>Dai un\'occhiata alle Domande frequenti]]>: troverai risposte alla maggior parte delle domande e suggerimenti importanti per l\'individuazione del problema]]>
Chiedi a un volontario - FAQ di Telegram + Domande frequenti https://telegram.org/faq/it + Informativa sulla privacy + https://telegram.org/privacy Eliminare la traduzione? File di traduzione non valido Abilitate @@ -524,6 +606,10 @@ minuti Anteprime link Chat segrete + Browser in-app + Apri i link esterni all\'interno dell\'app + Condivisione diretta + Mostra le chat recenti nel menu condividi Impostazioni cache Database locale @@ -575,14 +661,18 @@ Sensore touch Impronta digitale non riconosciuta. Riprova - Condividi foto e video in questa chat e accedi ad essi da ogni tuo dispositivo. File condivisi Media condivisi Link condivisi Musica condivisa + Condividi foto e video in questa chat e accedi ad essi da ogni tuo dispositivo. Condividi musica in questa chat e accedi ad essa da ogni tuo dispositivo. Condividi file e documenti in questa chat e accedi ad essi da ogni tuo dispositivo. Condividi link in questa chat ed accedi ad essi da ogni tuo dispositivo. + Le foto e i video di questa chat appariranno qui. + La musica di questa chat apparirà qui. + I file e i documenti di questa chat appariranno qui. + I link condivisi di questa chat appariranno qui. Mappa Satellite @@ -687,7 +777,7 @@ RIPRISTINA IL MIO ACCOUNT Perderai tutte le chat e i messaggi, insieme ai media e ai file condivisi, se procederai a ripristinare il tuo account. Attenzione - Questa azione non può essere annullata.\n\n Se ripristini il tuo account, tutti i tuoi messaggi e chat saranno eliminati. + Questa azione non può essere annullata.\n\nSe ripristini il tuo account, tutti i tuoi messaggi e le tue chat saranno eliminati. Ripristina Password Hai attivato la verifica in due passaggi, così il tuo account è protetto con una password aggiuntiva. @@ -729,7 +819,7 @@ Spiacenti, troppe richieste. Impossibile cambiare le impostazioni di privacy ora, attendi. Disconnette tutti i dispositivi tranne questo. Tieni premuto sull\'utente per eliminarlo. - Gruppo + Gruppi Chi può aggiungermi ai gruppi? Puoi decidere chi può aggiungerti a gruppi e canali con precisione granulare. Consenti sempre @@ -803,6 +893,7 @@ un1 ti ha rimosso un1 ti ha aggiunto un1 è tornato nel gruppo + un1 si è unito al gruppo Sei tornato nel gruppo Questo messaggio non è supportato dalla tua versione di Telegram. Aggiorna l\'app per visualizzarlo: https://telegram.org/update Foto @@ -848,8 +939,8 @@ Iniziare una chat segreta? Sei sicuro di volere eliminare questa registrazione? Sei sicuro di volere eliminare la cronologia? - Eliminare tutti i messaggi e i media salvati nella cache per questo canale? - Eliminare tutti i messaggi e i media salvati nella cache per questo supergruppo? + Eliminare tutti i testi e i media nella cache per questo canale? + Eliminare tutti i testi e i media nella cache per questo gruppo? Sei sicuro di voler eliminare %1$s? Inviare messaggi a %1$s? Inviare contatto a %1$s? @@ -861,6 +952,9 @@ Per favore ricorda che i bot inline sono forniti da sviluppatori di terze parti. Per far funzionare il bot, i simboli che digiti dopo l\'username del bot sono inviati al rispettivo sviluppatore. Vuoi attivare \"Alza per parlare\" per i messaggi vocali? Spiacenti, non puoi modificare questo messaggio. + Per favore consenti a Telegram di ricevere SMS così potremo inserire in automatico il codice per te. + Per favore consenti a Telegram di ricevere chiamate così potremo inserire in automatico il codice per te. + Per favore consenti a Telegram di ricevere chiamate ed SMS così potremo inserire in automatico il codice per te. Telegram deve accedere ai tuoi contatti per poterti connettere con i tuoi amici su tutti i tuoi dispositivi. Telegram deve accedere alla tua memoria per poter inviare e salvare foto,video, musica e altri media. diff --git a/TMessagesProj/src/main/res/values-ko/strings.xml b/TMessagesProj/src/main/res/values-ko/strings.xml index 12ce89add9..121577771f 100644 --- a/TMessagesProj/src/main/res/values-ko/strings.xml +++ b/TMessagesProj/src/main/res/values-ko/strings.xml @@ -14,9 +14,13 @@ 국가를 선택하세요 올바른 국가번호를 입력하세요 - 인증코드 입력 - 인증코드 메시지를 아래 번호로 전송했습니다 + 휴대폰 인증 + 인증코드 문자를 전송하였습니다.]]>%1$s]]>. + 회원님 기기에 설치된 ]]>Telegram]]>앱으로 코드를 전송했습니다. + ]]>%1$s]]> 번호로 인증 전화를 걸었습니다. \n\n텔레그램이 자동으로 인증을 처리하기에 전화를 안 받으셔도 됩니다. + ]]>%1$s]]> 번호로 인증코드 안내 전화를 걸고 있습니다. 텔레그램이 %1$d:%2$02d 후에는 전화를 겁니다. + %1$d:%2$02d 에 SMS를 보낼 예정입니다. 텔레그램이 전화 거는 중... 코드 전화번호가 틀렸나요? @@ -64,11 +68,15 @@ 최신 링크 미리복 + 그룹 종류 + 채널 종류 + 공개 + 비공개 관리자로 지명 그룹에 추가 설명을 제공 할 수 있습니다. 그룹 나가기 그룹 삭제 - 그룹 나각 + 그룹 나가기 그룹 삭제 그룹에 있는 모든 메시지가 삭제됩니다. 그룹방 관리를 도울 수 있는 관리자를 추가 할 수 있습니다. 길게 탭을하면 관리자 삭제가 가능합니다. @@ -81,6 +89,17 @@ 해당 유저가 스스로 그룹방에서 퇴장을 하여 다시 초대할 수 없습니다. 죄송합니다, 그룹방에 너무 많은 관리자가 있습니다. 죄송합니다, 그룹방에 너무 많은 봇이 있습니다. + un1 님이 \"%1$s\" 를 고정함 + un1 님이 메시지를 고정함 + un1 님이 사진을 고정함 + un1 님이 비디오를 고정함 + un1 님이 파일을 고정함 + un1 님이 스티커를 고정함 + un1 님이 음성메시지를 고정함 + un1 님이 연락처를 고정함 + un1 님이 위치를 고정함 + un1 님이 GIF를 고정함 + un1 님이 트랙을 고정함 이 그룹방은 슈퍼그룹방으로 업그레이드 되었습니다. %1$s 그룹방은 슈퍼그룹방으로 업그레이드 되었습니다. 그룹방에서 차단되어 퇴장당한 사용자는 관리자가 초대해야지만 그룹방에 입장이 가능합니다. 초대링크로는 초대가 되지 않습니다. @@ -90,15 +109,21 @@ 코멘트를 허용할 경우, 유저들이 회원님 글에 대하여 코멘트 등록이 가능합니다. 채널에 친구 추가 텔레그램 검색을 통하여 다른 유저들이 채널을 찾을 수 있습니다. - + 텔레그램 검색을 통하여 다른 유저들이 그룹을 찾을 수 있습니다. 링크 이 링크를 통하여 다른 유저들이 채널에 입장 할 수 있습니다. 이 링크는 언제든지 폐기 가능합니다. + 이 링크를 통하여 다른 유저들이 그룹방에 입장 할 수 있습니다. 이 링크는 언제든지 폐기 가능합니다. + 설명(선택) 설명 채널에 추가 설명을 제공 할 수 있습니다. 공개 채널 + 공개 그룹방 공개 채널은 검색이 가능하며, 누구나 입장 가능합니다 + 공개 그룹은 검색이 가능하며, 누구나 입장 가능합니다 비공개 채널 + 비공개 그룹방 비공개 채널은 초대 링크로만 입장 가능합니다. + 비공개 그룹은 초대 링크로만 입장 가능합니다. 링크 초대링크 구성원 추가 @@ -108,6 +133,7 @@ 입장 채널 정보 모두에게 메시지 전달 + 조용한 공지 코멘트 코멘트 보기 채널이 무엇인가요? @@ -118,8 +144,8 @@ 채널명은 최소 5 글자 이상 입력해야 합니다. 이름은 최대 32자까지만 가능합니다. 채널명은 숫자로 시작 할 수 없습니다. - - + 그룹명은 최소 5 글자 이상 입력해야 합니다. + 그룹명은 숫자로 시작 할 수 없습니다. 이름 확인 중.. %1$s은 사용 가능합니다. 구성원 @@ -131,7 +157,7 @@ 채널에서 나가시겠습니까? 채널에 있는 모든 메시지가 삭제됩니다. 편집 - + 그룹방에 대한 공개링크를 선택하신 경우, 누구나 검색을 통하여 입장 가능합니다.\n\n슈퍼그룹을 비공개로 유지를 하시고 싶으실 경우 링크 생성을 하지 말아주세요. 채널에 대한 공개링크를 선택하신 경우, 누구나 검색을 통하여 입장 가능합니다.\n\n비공개 채널로 유지를 하시고 싶으실 경우 링크 생성을 하지 말아주세요 유저들이 공개 채널에 대하여 검색 및 공유가 가능하도록 링크를 선택하여 주세요.\n\n채널을 공개하시지 싫으실 경우, 비공개 채널을 추천드립니다. 채널 생성됨 @@ -139,6 +165,7 @@ 채널 사진 삭제됨 채널명이 un2로 변경됨 죄송하지만, 너무 많은 공개 채널을 생성하였습니다. 기존 공개 채널을 삭제하시거나 비공개 채널을 생성할 수 있습니다. + 죄송하지만, 너무 많은 공개링크를 생성하였습니다. 기존 공개 채널 혹은 그룹방을 비공개로 전환하거나 삭제해주세요. 관리자 생성자 관리자 @@ -154,6 +181,8 @@ 채널 관리를 도울 수 있는 관리자를 추가 할 수 있습니다. 길게 탭을하면 관리자 삭제가 가능합니다. \'%1$s\'채널에 참여하시겠습니까? 죄송합니다, 이 채팅방에 더 이상 접근이 불가능 합니다. + 안타깝지만, 공개그룹 참여에 제한 되었습니다. + 죄송합니다, 이 채팅방에 더 이상 접근이 불가능 합니다. %1$s 님을 이 채널에 추가할까요 해당 사용자가 스스로 채널에서 퇴장을 하여 다시 초대할 수 없습니다. 죄송합니다, 이 유저를 채널에 추가 할 수 없습니다. @@ -162,6 +191,7 @@ 죄송합니다, 채널에는 첫 200명까지만 초대가 가능합니다. 채널 링크를 통하여 무제한 입장이 가능합니다. 이 채널에 un1님이 초대하였습니다. 채널에 참여하였습니다. + 그룹에 참여하였습니다. 채널에서 내보내기 채널에 글을 쓸 수 없습니다. %1$s님이 %2$s 채널에 초대했습니다 @@ -174,6 +204,7 @@ %1$s님이 %2$s 채널에 파일을 보냈습니다 %1$s님이 %2$s 채널에 GIF파일을 보냈습니다 %1$s님이 %2$s 채널에 음성메시지를 보냈습니다 + %1$s님이 %2$s 채널에 트랙을 보냈습니다 %1$s님이 %2$s 채널에 스티커를 보냈습니다 %1$s 님이 메시지를 보냈습니다 %1$s 님이 사진을 보냈습니다 @@ -183,6 +214,7 @@ %1$s 님이 파일을 보냈습니다 %1$s 님이 GIF파일을 보냈습니다 %1$s님이 음성메시지를 보냈습니다 + %1$s님이 트랙을 보냈습니다 %1$s님이 스티커를 보냈습니다 초대가 가능한 구성원 모든 구성원 @@ -272,15 +304,33 @@ %1$s 전송 %1$s 링크를 여시겠습니까? 스팸 신고 + 스팸 신고 및 나가기 주소록에 추가 이 유저 메시지를 스팸신고 하시겠습니까? 이 그룹 메시지를 스팸신고 하시겠습니까? + 이 채널을 스팸신고 하시겠습니까? 죄송합니다, 서로 연락처가 추가된 경우에만 메시지 전송이 가능합니다. 죄송합니다, 서로 연락처가 추가된 경우에만 그룹에 구성원을 추가 할 수 있습니다. https://telegram.org/faq#can-39t-send-messages-to-non-contacts 더 보기 다음에게 보내기.. 저장된 GIF 파일을 보려면 탭하세요. + 고정 + 모두에게 알림 + 고정제거 + 그룹에 이 메시지를 고정하시겠습니까? + 메시지를 고정 해제하시겠습니까? + 사용자 차단 + 스팸 신고 + %1$s 전부 삭제 + 최근 사용한 이모티콘 삭제? + 신고하기 + 스팸 + 폭력적 + 음란물 + 기타 + 설명 + 메시지 고정 %1$s님이 자동삭제를 %2$s 후로 설정했습니다 자동삭제를 %1$s 후로 설정했습니다 @@ -296,6 +346,7 @@ %1$s님이 파일을 보냈습니다 %1$s 님께서 GIF파일을 보내셨습니다 %1$s님이 음성메시지를 보냈습니다 + %1$s 님이 트랙을 보냈습니다 %1$s님이 스티커를 보냈습니다 %1$s @ %2$s: %3$s %1$s님이 %2$s 그룹에 메시지를 보냈습니다 @@ -306,12 +357,14 @@ %1$s님이 %2$s 그룹에 파일을 보냈습니다 %1$s 님께서 %2$s 그룹에 GIF파일을 보냈습니다 %1$s님이 %2$s 그룹에 음성메시지를 보냈습니다 + %1$s님이 %2$s 그룹에 트랙을 보냈습니다 %1$s님이 %2$s 그룹에 스티커를 보냈습니다 %1$s님이 %2$s 그룹에 초대했습니다 %1$s님이 그룹 이름을 %2$s 그룹으로 변경했습니다 %1$s님이 %2$s 그룹 사진을 변경했습니다 %1$s님이 %3$s님을 %2$s 그룹에 초대했습니다 %1$s 님이 %2$s 그룹으로 되돌아왔습니다 + %1$s 님이 %2$s 그룹에 참여했습니다 %1$s님이 %3$s님을 %2$s 그룹에서 퇴장당했습니다. %1$s님이 %2$s 그룹에서 퇴장당했습니다. %1$s님이 %2$s 그룹을 떠났습니다 @@ -323,6 +376,28 @@ %1$s 그룹에 답장하기 %1$s님에게 답장하기 %2$s %1$s + %1$s 님이 \"%2$s\" 메시지를 %3$s 그룹방에 고정함 + %1$s 님이 메시지를 %2$s 그룹방에 고정함 + %1$s 님이 사진을 %2$s 그룹방에 고정함 + %1$s 님이 비디오를 %2$s 그룹방에 고정함 + %1$s 님이 파일을 %2$s 그룹방에 고정함 + %1$s 님이 스티커를 %2$s 그룹방에 고정함 + %1$s 님이 음성메시지를 %2$s 그룹방에 고정함 + %1$s 님이 연락처를 %2$s 그룹방에 고정함 + %1$s 님이 지도를 %2$s 그룹방에 고정함 + %1$s 님이 GIF를 %2$s 그룹방에 고정함 + %1$s 님이 트랙을 %2$s 그룹방에 고정함 + %1$s 님이 \"%2$s\" 를 고정함 + %1$s 님이 메시지를 고정함 + %1$s 님이 사진을 고정함 + %1$s 님이 비디오를 고정함 + %1$s 님이 파일을 고정함 + %1$s 님이 스티커를 고정함 + %1$s 님이 음성메시지를 고정함 + %1$s 님이 연락처를 고정함 + %1$s 님이 지도를 고정함 + %1$s 님이 GIF를 고정함 + %1$s 님이 트랙을 고정함 대화상대 선택 대화상대가 없습니다 @@ -372,9 +447,14 @@ 그룹에서 나가기 알림 그룹에서 내보내기 - 슈퍼그룹방으로 업그레이드하기 - 슈퍼그룹방을 보려면 구성원들이 최신 텔레그램 버전으로 업데이트 해야합니다. 저암ㄹ로 그룹방을 업그레이드 하시겠습니까? - ]]>구성원이 최대치입니다.]]>\n\n추가 기능 및 더 많은 구성원을 추가하려면 슈퍼그룹방으로 업그레이드하세요:\n\n• 슈퍼그룹방은 %1$s명까지 초대가능합니다.\n• 새로운 구성원은 모든 대화내역을 볼 수 있습니다.\n• 관리자는 모두에게 메시지 삭제를 할 수 있습니다.\n• 기본값으로 알림이 음소거 됩니다. + 슈퍼그룹으로 업그레이드 + 슈퍼그룹으로 변환 + 슈퍼그룹으로 변환 + 경고 + 이 작업은 되돌릴 수 없습니다. 슈퍼그룹에서 일반그룹방으로 다운 그레이드는 불가능 합니다. + ]]>구성원이 최대치입니다.]]>\n\n추가 기능 및 더 많은 구성원을 추가하려면 슈퍼그룹방으로 업그레이드하세요:\n\n• 슈퍼그룹방은 %1$s명까지 초대가능합니다.\n• 새로운 구성원은 모든 대화내역을 볼 수 있습니다.\n• 메시지 삭제시 모두에게 삭제가 됩니다.\n• 방 생성자는 그룹방 공개링크 생성이 가능합니다. + ]]>슈퍼그룹:]]>\n\n• 새로운 구성원은 모든 대화내역을 볼 수 있습니다.\n• 메시지 삭제시 모두에게 삭제가 됩니다.\n• 메시지를 작성자가 수정 가능합니다.\n• 방 생성자는 그룹방 공개링크 생성이 가능합니다. + ]]>주위:]]> 이 작업은 되돌릴 수 없습니다. 공유 추가 @@ -464,6 +544,8 @@ 질문하기 자주 묻는 질문 https://telegram.org/faq/ko + 개인정보 정책 + https://telegram.org/privacy 언어를 삭제할까요? 언어 파일이 올바르지 않습니다. 켜기 @@ -524,6 +606,10 @@ 링크 프리뷰 비밀대화 + 크롬 커스텀 탭 + 앱내에서 외부 링크 열기 + 직접 공유 + 공유 메뉴에서 최근 대화 보기 캐시 설정 로컬 데이터베이스 @@ -575,14 +661,18 @@ 터치 센서 지문인식이 실패하였습니다. 다시 시도해주세요. - 이 채팅방에서 사진이나 동영상을 공유하면 다른 기기에서도 보실 수 있습니다. 공유한 파일 공유된 미디어 공유한 링크 공유된 음악 + 이 채팅방에서 사진이나 동영상을 공유하면 다른 기기에서도 보실 수 있습니다. 이 채팅방에서 음악을 공유하면 다른 기기에서도 보실 수 있습니다. 이 채팅방에서 파일이나 문서를 공유하면 다른 기기에서도 보실 수 있습니다. 이 채팅방에서 파일이나 문서를 공유하면 다른 기기에서도 보실 수 있습니다. + 이 대화에서 공유한 사진과 비디오파일이 표시됩니다. + 이 대화에서 공유한 음악파일이 표시됩니다. + 이 대화에서 공유한 문서파일이 표시됩니다. + 이 대화에서 공유한 링크가 표시됩니다. 지도 위성 @@ -803,6 +893,7 @@ un1님이 퇴장당했습니다. un1님이 그룹에 초대했습니다 un1 님께서 그룹에 돌아오셨습니다 + un1 님이 그룹에 참여했습니다 그룹에 돌아오셨습니다. 이 메시지는 현재 사용 중인 버전의 Telegram에서 지원되지 않습니다. 메시지를 보려면 http://telegram.org/update에서 앱을 업데이트하세요. 사진 @@ -849,7 +940,7 @@ 정말로 가입을 취소하시겠습니까? 정말로 대화내용을 지우시겠습니까? 채널에서 캐시된 모든 텍스트 및 미디어를 삭제하시겠습니까? - 슈커그룹에서 캐시된 모든 텍스트 및 미디어를 삭제하시겠습니까? + 그룹에서 캐시된 모든 텍스트 및 미디어를 삭제하시겠습니까? %1$s: 정말로 삭제하시겠습니까? %1$s 그룹에 메시지를 보낼까요? %1$s에게 연락처를 보내시겠습니까? @@ -861,6 +952,9 @@ 인라인 봇은 제3자 개발자로 부터 제공이 됩니다. 봇이 작동을 하려면 봇의 아이디 및 뒤의 메시지가 담당 개발자에게 전송이 됩니다. \"기기를 들어 말하기\"기능을 음성 메시지에 활성화 하시겠습니까? 죄송합니다, 메시지 수정을 할 수 없습니다. + 텔레그램이 SMS를 수신 할 수 있도록 설정해주셔야 자동으로 코드 입력이 가능합니다. + 텔레그램이 전화를 수신 할 수 있도록 설정해주셔야 자동으로 코드 입력이 가능합니다. + 텔레그램이 SMS 및 전화를 수신 할 수 있도록 설정해주셔야 자동으로 코드 입력이 가능합니다. Telegram은 여러 기기에서 친구와 메시지를 주고받을 수 있도록 회원님의 연락처 접근이 필요합니다. Telegram은 사진, 비디오, 음악 및 다양한 미디어를 공유 및 저장하기 위하여 스토리지 접근이 필요합니다. diff --git a/TMessagesProj/src/main/res/values-nl/strings.xml b/TMessagesProj/src/main/res/values-nl/strings.xml index b0b9bd7619..39682c9b04 100644 --- a/TMessagesProj/src/main/res/values-nl/strings.xml +++ b/TMessagesProj/src/main/res/values-nl/strings.xml @@ -14,9 +14,13 @@ Kies een land Onjuist landnummer - Je code - We hebben een sms-bericht met een activatiecode verzonden naar je telefoon + Telefoonverificatie + We hebben een sms-bericht met activatiecode verzonden naar ]]>%1$s]]>. + We hebben de code naar je andere ]]>Telegram]]>-app gestuurd. + We doen een activatie-oproep naar ]]>%1$s]]>.\n\nNeem niet op, Telegram verwerkt de oproep automatisch. + We bellen je op ]]>%1$s]]> om een code te dicteren. We bellen je over %1$d:%2$02d + We sturen je een SMS over %1$d:%2$02d We bellen je Code Verkeerd nummer? @@ -64,6 +68,10 @@ RECENT Link-voorvertoning + Groepsvorm + Kanaaltype + Publiek + Privé Promoveren tot beheerder Optioneel kun je een groepsbeschrijving geven. Groep verlaten @@ -72,7 +80,7 @@ Groep verwijderen Je raakt alle berichten in deze groep kwijt. Je kunt beheerders toevoegen om je te helpen je groep te beheren. Druk en houd ingedrukt om beheerders te verwijderen. - Groep echt verwijderen? Berichten worden gewist en alle deelnemers verwijderd. + Groep echt verwijderen? Berichten worden gewist en alle leden verwijderd. Groep gemaakt un1 heeft je toegevoegd aan deze groep Groep echt verlaten? @@ -81,33 +89,51 @@ Deze gebruiker heeft de groep verlaten. Je kunt hem/haar niet meer uitnodigen. Maximaal aantal beheerders bereikt. Maximaal aantal bots bereikt. + un1 heeft \"%1$s\" vastgezet + un1 heeft bericht vastgezet + un1 heeft foto vastgezet + un1 heeft video vastgezet + un1 heeft bestand vastgezet + un1 heeft sticker vastgezet + un1 heeft spraakbericht vastgezet + un1 heeft contact vastgezet + un1 heeft locatie vastgezet + un1 heeft GIF vastgezet + un1 heeft muziekbestand vastgezet De groep is opgewaardeerd naar een supergroep %1$s is opgewaardeerd naar een supergroep Geblokkeerde gebruikers kunnen alleen worden uitgenodigd door beheerders, uitnodigingslinks werken niet voor hen. Nieuw kanaal Kanaalnaam Reacties - Als je reacties inschakelt kunnen deelnemers reageren op je bericht in het kanaal. + Als je reacties inschakelt kunnen leden reageren op je bericht in het kanaal. Contacten aan je kanaal toevoegen Deze link kan gedeeld worden met anderen en je kanaal kan worden gevonden via de zoekfunctie. - + Deze link kan gedeeld worden met anderen en je groep kan worden gevonden via de zoekfunctie. link - Deelnemen aan je kanaal kan door deze link te volgen, je kunt de link altijd intrekken. + Lid worden van je kanaal kan door deze link te volgen, je kunt de link altijd intrekken. + Lid worden van je groep kan door deze link te volgen, je kunt de link altijd intrekken. + Beschrijving (optioneel) Beschrijving Optioneel kun je een kanaalbeschrijving geven. Publiek kanaal - Publieke kanalen zijn te vinden via de zoekfunctie, iedereen kan eraan deelnemen. + Publieke groep + Publieke kanalen zijn te vinden via de zoekfunctie, iedereen kan er lid van worden. + Publieke groepen zijn te vinden via de zoekfunctie, iedereen kan er lid van worden en de geschiedenis zien. Privé-kanaal - Deelnemen aan privé-kanalen kan alleen via de uitnodigingslink. + Privé-groep + Lid worden van privé-kanalen kan alleen per uitnodigingslink. + Lid worden van privé-groepen kan alleen via uitnodiging of per uitnodigingslink. Link Uitnodigingslink - Deelnemers toevoegen + Leden toevoegen Kanaal verlaten Kanaal verlaten Instellingen - DEELNEMEN + LID WORDEN Kanaalinformatie Massabericht + Stil massabericht Reactie reacties weergeven Wat is een kanaal? @@ -118,27 +144,28 @@ Een kanaalnaam moet minimaal 5 tekens hebben. De naam mag niet langer zijn dan 32 tekens. Sorry, begincijfers zijn niet toegestaan. - - + Een groepsnaam moet minimaal 5 tekens hebben. + Begincijfers zijn niet toegestaan. Naam controleren... %1$s is beschikbaar. - Deelnemers + Leden Geblokkeerde gebruikers Beheerders Kanaal verwijderen Kanaal verwijderen - Kanaal echt verwijderen? Berichten worden gewist en alle deelnemers verwijderd. + Kanaal echt verwijderen? Berichten worden gewist en alle leden verwijderd. Kanaal echt verlaten? Je raakt alle berichten in dit kanaal kwijt. Wijzig - - Als je een publieke link voor je kanaal instelt kan iedereen deze vinden en deelnemen via de zoekfunctie.\n\nStel geen link in als je je kanaal privé wilt houden. + Als je een publieke link voor je groep instelt kan iedereen deze vinden en lid worden via de zoekfunctie.\n\nStel geen link in als je je supergroep privé wilt houden. + Als je een publieke link voor je kanaal instelt kan iedereen deze vinden en lid worden via de zoekfunctie.\n\nStel geen link in als je je kanaal privé wilt houden. Stel een link in voor je publieke kanaal, om deze vindbaar te maken via de zoekfunctie en te delen met anderen.\n\nWil je dit niet dan kun je een privé-kanaal aanmaken. Kanaal gemaakt Kanaalfoto bijgewerkt Kanaalfoto verwijderd Kanaalnaam gewijzigd naar un2 Het maximale aantal publieke kanalen is bereikt. Je kunt een privé-kanaal maken of een kanaal verwijderen om een nieuwe te maken. + Het maximale aantal publieke links is bereikt. Maak een groep of kanaal privé of verwijder er 1. Moderator Maker Beheerder @@ -149,19 +176,22 @@ %1$s echt als beheerder toevoegen? Verwijder Alleen kanaal-beheerders zien deze lijst. - Deze gebruiker is nog geen deelnemer, uitnodigen? - Andere Telegram-gebruikers kunnen aan je groep deelnemen door deze link te openen. + Deze gebruiker is nog geen lid, uitnodigen? + Andere Telegram-gebruikers kunnen lid worden van je groep door deze link te openen. Je kunt beheerders toevoegen om je te helpen je kanaal te beheren. Druk en houd ingedrukt om beheerders te verwijderen. - Deelnemen aan kanaal \'%1$s\'? + Lid worden van kanaal \'%1$s\'? Sorry, deze chat is niet beschikbaar. + Helaas ben je geblokkeerd van deelname in publieke groepen. + Sorry, deze chat is niet beschikbaar. %1$s toevoegen aan het kanaal? Deze gebruiker heeft het kanaal verlaten. Je kunt hem/haar niet meer uitnodigen. Je kunt deze gebruiker niet toevoegen aan kanalen. Maximaal aantal beheerders bereikt. Maximaal aantal bots bereikt. - Je kunt 200 deelnemers handmatig toevoegen aan een kanaal. Een ongelimiteerd aantal mensen kan deelnemen via de link van het kanaal. + Je kunt 200 leden handmatig toevoegen aan een kanaal. Een ongelimiteerd aantal mensen kan lid worden via de link van het kanaal. un1 heeft je toegevoegd aan dit kanaal - Je neemt deel aan het kanaal + Je bent nu lid van dit kanaal + Je bent nu lid van deze groep Verwijderen uit kanaal Je hebt alleen leesrechten in dit kanaal. %1$s heeft je toegevoegd aan het kanaal %2$s @@ -174,6 +204,7 @@ %1$s heeft een bestand gestuurd naar het kanaal %2$s %1$s heeft een GIF gestuurd naar het kanaal %2$s %1$s heeft een spraakbericht gestuurd naar het kanaal %2$s + %1$s heeft een muziekbestand gestuurd naar het kanaal %2$s %1$s heeft een sticker gestuurd naar het kanaal %2$s %1$s plaatste een bericht %1$s plaatste een foto @@ -183,12 +214,13 @@ %1$s plaatste een bestand %1$s plaatste een GIF %1$s plaatste een spraakbericht + %1$s plaatste een muziekbestand %1$s plaatste een sticker - Wie kan deelnemers toevoegen? - Alle deelnemers + Wie kan leden toevoegen? + Alle leden Alleen beheerders - Berichtgeving voor deelnemers - Geen berichtgeving voor deelnemers + Berichtgeving voor leden + Geen berichtgeving voor leden Ondertekenen Beheerdersnaam bij alle uitgaande berichten. @@ -272,15 +304,33 @@ %1$s versturen URL %1$s openen? SPAM MELDEN + SPAM MELDEN EN VERLATEN CONTACT TOEVOEGEN Spam van deze gebruiker echt melden? Spam van deze groep echt melden? + Spam van dit kanaal echt melden? Je kunt momenteel alleen berichten sturen aan onderlingen contacten. Je kunt momenteel alleen onderlinge contacten aan groepen toevoegen https://telegram.org/faq#can-39t-send-messages-to-non-contacts Meer informatie Versturen naar... Tik hier om opgeslagen GIF\'s te bekijken + Vastzetten + Alle leden informeren + Losmaken + Wil je dit bericht vastzetten? + Wil je dit bericht losmaken? + Blacklist gebruiker + Spam melden + Alles verwijderen van %1$s + Recente emoji\'s wissen? + Melden + Spam + Geweld + Pornografie + Overig + Beschrijving + Vastgezet bericht %1$s heeft de zelfvernietigingstimer ingesteld op %2$s Je hebt de zelfvernietigingstimer ingesteld op %1$s @@ -296,6 +346,7 @@ %1$s heeft je een bestand gestuurd %1$s heeft je een GIF gestuurd %1$s heeft je een spraakbericht gestuurd + %1$s heeft je een muziekbestand gestuurd %1$s heeft je een sticker gestuurd %1$s @ %2$s: %3$s %1$s heeft een bericht gestuurd naar de groep %2$s @@ -306,23 +357,47 @@ %1$s heeft een bestand gestuurd naar de groep %2$s %1$s heeft een GIF gestuurd naar de groep %2$s %1$s heeft een spraakbericht gestuurd naar de groep %2$s + %1$s heeft een muziekbestand gestuurd naar de groep %2$s %1$s heeft een sticker gestuurd naar de groep %2$s %1$s heeft je uitgenodigd voor de groep %2$s %1$s heeft de naam van de groep %2$s gewijzigd %1$s heeft de afbeelding van de groep %2$s gewijzigd %1$s heeft %3$s uitgenodigd voor de groep %2$s %1$s is terug in de groep %2$s + %1$s is nu lid van de groep %2$s %1$s heeft %3$s verwijderd uit de groep %2$s %1$s heeft je verwijderd uit de groep %2$s %1$s heeft de groep %2$s verlaten %1$s heeft nu Telegram! %1$s,\nEr is op je account ingelogd vanaf een nieuw apparaat op %2$s\n\nApparaat: %3$s\nLocatie: %4$s\n\nAls jij dit niet was, kun je die sessie beëindigen via Instellingen - Privacy en veiligheid - Sessies.\n\nAls je dat denkt dat iemand anders zonder jouw toestemming is ingelogd kun je twee-staps-verificatie activeren via instellingen - privacy en veiligheid.\n\nBedankt,\nHet Telegram-team %1$s heeft zijn/haar profielfoto gewijzigd - %1$s neemt deel aan de groep %2$s via uitnodigingslink + %1$s is nu lid van de groep %2$s via uitnodigingslink Antwoord Antwoord op %1$s Antwoord op %1$s %1$s %2$s + %1$s heeft \"%2$s\" vastgezet in de groep %3$s + %1$s heeft bericht vastgezet in de groep %2$s + %1$s heeft foto vastgezet in de groep %2$s + %1$s heeft video vastgezet in de groep %2$s + %1$s heeft bestand vastgezet in de groep %2$s + %1$s heeft sticker vastgezet in de groep %2$s + %1$s heeft spraakbericht vastgezet in de groep %2$s + %1$s heeft contact vastgezet in de groep %2$s + %1$s heeft locatie vastgezet in de groep %2$s + %1$s heeft GIF vastgezet in de groep %2$s + %1$s heeft muziekbestand vastgezet in de groep %2$s + %1$s heeft \"%2$s\" vastgezet + %1$s heeft bericht vastgezet + %1$s heeft foto vastgezet + %1$s heeft video vastgezet + %1$s heeft bestand vastgezet + %1$s heeft sticker vastgezet + %1$s heeft spraakbericht vastgezet + %1$s heeft contact vastgezet + %1$s heeft locatie vastgezet + %1$s heeft GIF vastgezet + %1$s heeft muziekbestand vastgezet Contact kiezen Nog geen contacten @@ -345,8 +420,8 @@ Nadat je de groep hebt aangemaakt kun je meer gebruikers toevoegen en omzetten naar een supergroep. Groepsnaam Groepsnaam - %1$d/%2$d deelnemers - Wil je deelnemen aan de groep \"%1$s\"? + %1$d/%2$d leden + Wil je lid worden van de groep \"%1$s\"? Sorry, deze groep is al vol. Sorry, deze groep bestaat niet. Link gekopieerd naar klembord. @@ -358,23 +433,28 @@ Link intrekken Link kopiëren Link delen - Andere Telegram-gebruikers kunnen aan je groep deelnemen door deze link te openen. + Andere Telegram-gebruikers kunnen lid worden van je groep door deze link te openen. Beheerders Iedereen is beheerder - Iedereen mag deelnemers toevoegen en de groepsfoto of naam wijzigen. - Beheerders mogen deelnemers beheren en de groepsfoto of naam wijzigen. + Iedereen mag leden toevoegen en de groepsfoto of naam wijzigen. + Beheerders mogen leden beheren en de groepsfoto of naam wijzigen. Gedeelde media Instellingen - Deelnemer toevoegen + Lid toevoegen Beheerders instellen Groep verwijderen en verlaten Meldingen Verwijderen uit groep + Opwaarderen naar supergroep Opwaarderen naar supergroep - Groepsdeelnemers moeten updaten naar de meest recente Telegram om je supergroep te kunnen zien. Groep echt opwaarderen? - ]]>Deelnemerslimiet bereikt.]]>\n\nWil je extra functies en een hogere limiet? Waardeer op naar een supergroep:\n\n• Supergroepen hebben tot %1$s\n• Nieuwe leden zien de hele geschiedenis\n• Beheerder wist berichten voor iedereen\n• Meldingen staan standaard uit + Opwaarderen naar supergroep + Waarschuwing + Groep echt omzetten naar supergroep? Je kunt dit niet ongedaan maken. + ]]>Ledenlimiet bereikt.]]>\n\nWil je extra functies en een hogere limiet? Waardeer op naar een supergroep:\n\n• Supergroepen hebben tot %1$s\n• Nieuwe leden zien de hele geschiedenis\n•Gewiste berichten gelden voor iedereen\n• Leden kunnen eigen berichten bewerken\n• Maker kan een publieke groepslink instellen + ]]>Supergroepen:]]>\n\n• Nieuwe leden zien de hele geschiedenis\n• Gewiste berichten gelden voor iedereen\n• Leden kunnen eigen berichten bewerken\n• Maker kan een publieke groepslink instellen + ]]>Let op:]]> Je kunt dit niet ongedaan maken. Delen Toevoegen @@ -464,6 +544,8 @@ Vraag een vrijwilliger Veelgestelde vragen https://telegram.org/faq + Privacybeleid + https://telegram.org/privacy Vertaling verwijderen? Ongeldig vertalingsbestand Inschakelen @@ -524,6 +606,10 @@ minuten Linkvoorvertoningen Geheime chats + In-app browser + Externe links in de app openen + Snel delen + Recente chats in snel delen weergeven Cache-instellingen Lokale database @@ -575,14 +661,18 @@ Vingerafdruksensor Vingerafdruk niet herkend, probeer opnieuw - Deel foto\'s en video\'s in deze chat om ze op al je apparaten te kunnen benaderen. Gedeelde bestanden Gedeelde media Gedeelde links Gedeelde muziek + Deel foto\'s en video\'s in deze chat om ze op al je apparaten te kunnen benaderen. Deel muziek in deze chat om ze op al je apparaten te kunnen benaderen. Deel bestanden en documenten in deze chat om ze op al je apparaten te kunnen benaderen. Deel links in deze chat om ze op al je apparaten te kunnen benaderen. + Foto\'s en video\'s van deze chat verschijnen hier + Muziek van deze chat verschijnt hier + Bestanden van deze chat verschijnen hier + Gedeelde links can deze chat verschijnen hier Kaart Satelliet @@ -687,7 +777,7 @@ ACCOUNT RESETTEN Al je chats, berichten en alle andere data gaan verloren als je verder gaat met de account-reset. Waarschuwing - Deze actie kan niet worden hersteld.\n\nAl je chats, berichten en data gaan verloren als je je account reset. + Je kunt dit niet ongedaan maken.\n\nAl je chats, berichten en data gaan verloren als je je account reset. Resetten Wachtwoord Twee-staps-verificatie ingeschakeld. Je account is met een extra wachtwoord beveiligd. @@ -784,8 +874,8 @@ OK BIJSNIJDEN - Je neemt deel aan de groep via uitnodigingslink - un1 neemt deel aan de groep via uitnodigingslink + Je bent nu lid van de groep via uitnodigingslink + un1 is nu lid van de groep via uitnodigingslink un1 heeft un2 verwijderd un1 heeft de groep verlaten un1 heeft un2 toegevoegd @@ -803,6 +893,7 @@ un1 heeft je verwijderd un1 heeft je toegevoegd un1 is terug in de groep + un1 is lid van de groep Je keerde terug naar de groep Dit bericht wordt niet ondersteund door jouw versie van Telegram. Werk Telegram bij om dit bericht te bekijken: https://telegram.org/update Foto @@ -833,7 +924,7 @@ %1$s toevoegen aan de groep %2$s? Aantal recente berichten om door te sturen: %1$s toevoegen aan de groep? - Gebruiker neemt al deel aan de groep + Gebruiker is al een groepslid Berichten doorsturen naar %1$s? Berichten naar %1$s versturen? Contact delen met %1$s? @@ -849,7 +940,7 @@ Weet je zeker dat je de registratie wilt annuleren? Geschiedenis echt wissen? Kanaalcache opschonen? - Supergroepcache echt opschonen? + Groepscache echt opschonen? %1$s echt verwijderen? Berichten naar %1$s versturen? Contact delen met %1$s? @@ -861,6 +952,9 @@ Let op: inline-bots worden aangeboden door externe ontwikkelaars. Voor de werking van de bot worden de karakters die je na de botnaam typt naar deze ontwikkelaar verstuurd. Wil je \"Houd bij oor\" inschakelen voor spraakberichten? Je mag dit bericht niet bewerken. + Sta het ontvangen van SMS toe zodat we automatisch je inlogcode kunnen invoeren. + Sta het ontvangen van oproepen toe zodat we automatisch je inlogcode kunnen invoeren. + Sta het ontvangen van oproepen en SMS toe zodat we automatisch je inlogcode kunnen invoeren. Telegram heeft toegang tot je contacten nodig zodat je kan chatten met je vrienden vanaf al je apparaten. Telegram heeft toegang tot je opslaggeheugen nodig zodat je foto\'s, video\'s, muziek en andere media kunt opslaan en versturen. @@ -890,12 +984,12 @@ %1$d online %1$d online %1$d online - %1$d deelnemers - %1$d deelnemer - %1$d deelnemers - %1$d deelnemers - %1$d deelnemers - %1$d deelnemers + %1$d leden + %1$d leden + %1$d leden + %1$d leden + %1$d leden + %1$d leden en nog %1$d personen zijn aan het typen en nog %1$d persoon is aan het typen en nog %1$d personen zijn aan het typen diff --git a/TMessagesProj/src/main/res/values-pt-rBR/strings.xml b/TMessagesProj/src/main/res/values-pt-rBR/strings.xml index 77a6fee482..7a98dad6fc 100644 --- a/TMessagesProj/src/main/res/values-pt-rBR/strings.xml +++ b/TMessagesProj/src/main/res/values-pt-rBR/strings.xml @@ -14,9 +14,13 @@ Escolha um país Código do país incorreto - Seu código - Enviamos uma SMS com um código de ativação para o seu telefone + Verificação do tel. + Enviamos uma SMS com um código de ativação para ]]>%1$s]]>. + Nós enviamos o código para o aplicativo do ]]>Telegram]]> em seu outro dispositivo. + Enviamos uma ligação de ativação para ]]>%1$s]]>.\n\nNão atenda, o Telegram processará automaticamente. + Nós te ligaremos em ]]>%1$s]]> para ditar o código. Vamos te ligar em %1$d:%2$02d + Vamos te enviar uma SMS em %1$d:%2$02d Estamos te ligando... Código Número incorreto? @@ -64,6 +68,10 @@ RECENTE Prévia do link + Tipo de Grupo + Tipo de canal + Público + Privado Promover a administrador Você pode fornecer uma descrição opcional para seu grupo. Sair do Grupo @@ -81,6 +89,17 @@ Desculpe, este usuário decidiu sair deste grupo, de maneira que você não pode convidá-lo de volta. Desculpe, há administradores demais neste grupo. Desculpe, há bots demais neste grupo. + un1 fixou \"%1$s\" + un1 ficou uma mensagem + un1 ficou uma foto + un1 ficou um vídeo + un1 ficou um arquivo + un1 ficou um sticker + un1 fixou uma mensagem de voz + un1 ficou um contato + un1 ficou um mapa + un1 ficou um GIF + un1 ficou uma música Este grupo foi atualizado para um supergrupo %1$s foi atualizado para um supergrupo Usuários bloqueados são removidos do grupo e só podem voltar se convidados por um administrador. Convites por link não funcionam para eles. @@ -90,15 +109,21 @@ Se você habilitar comentários, pessoas poderão discutir seu post no canal. Adicionar contatos no canal Pessoas podem compartilhar esse link com outros e encontrar seu canal usando a busca do Telegram. - + Pessoas podem compartilhar esse link com outros e encontrar o seu grupo usando a busca do Telegram. link Pessoas podem entrar em seu canal com este link. Você pode desativar o link quando quiser. + Pessoas podem entrar em seu canal com este link. Você pode desativar o link quando quiser. + Descrição (opcional) Descrição Você pode providenciar uma descrição opcional para o seu canal. Canal Público + Grupo Público Canais públicos podem ser encontrados na busca, qualquer um pode entrar. + Grupos públicos podem ser encontrados na busca, o histórico é disponível para todos e qualquer um pode entrar. Canal Privado - Canais privados só podem entrar aqueles que possuírem um link de convite. + Grupo Privado + Canais privados só podem ser aderidos através de um link de convite. + Grupos privados só podem ser aderidos através de um link de convite. Link Link de Convite Adicionar membros @@ -108,6 +133,7 @@ ENTRAR Info do Canal Transmissão + Transmissão Silenciosa Comentário mostrar comentários O que é um Canal? @@ -118,8 +144,8 @@ Nome do canal deve ter pelo menos 5 caracteres. O nome não pode exceder 32 caracteres. Nome do canal não pode iniciar com número. - - + Nome do grupo deve ter pelo menos 5 caracteres. + Nome do grupo não pode iniciar com número. Verificando nome... %1$s está disponível. Membros @@ -131,7 +157,7 @@ Você tem certeza que deseja sair do canal? Você perderá todas as mensagens desse canal. Editar - + Por favor, note que ao escolher um link público para o seu grupo, qualquer um poderá encontrá-lo na busca e entrar.\n\nNão crie um link se você deseja que seu supergrupo seja privado. Por favor, note que ao escolher um link público para o seu canal, qualquer um poderá encontrá-lo na busca e entrar.\n\nNão crie um link se você deseja que seu canal seja privado. Por favor, escolha um link para o seu canal público, assim as pessoas poderão encontrá-lo na busca e compartilhar com outros.\n\nSe não estiver interessado, sugerimos que crie um canal privado. Canal criado @@ -139,6 +165,7 @@ Foto do canal removida Nome do canal alterado para un2 Desculpe, você criou muitos canais públicos. Você pode criar um canal privado ou apagar um de seus canais existentes primeiro. + Desculpe, você possui muitos links públicos. Tente apagar um ou tornar um de seus grupos ou canais privados. Moderador Criador Administrador @@ -154,6 +181,8 @@ Você pode adicionar administradores para ajudar você a gerenciar seu canal. Aperte e segure para removê-los. Você deseja entrar no canal \'%1$s\'? Desculpe, esta conversa não pode mais ser acessada. + Infelizmente você foi banido de participar de grupos públicos. + Desculpe, esse chat não está mais acessível. Adicionar %1$s ao canal? Desculpe, este usuário decidiu sair deste canal, então você não pode convidá-lo de volta. Desculpe, você não pode adicionar esse usuário em canais. @@ -161,7 +190,8 @@ Desculpe, há bots demais neste canal. Desculpe, você só pode adicionar os primeiros 200 membros ao canal. Note que um número ilimitado de pessoas podem entrar via link do canal. un1 adicionou você ao canal - Você entrou no canal + Você entrou nesse canal + Você entrou nesse grupo Remover do canal Desculp, você não pode enviar mensagens para esse canal. %1$s adicionou você ao canal %2$s @@ -174,6 +204,7 @@ %1$s enviou um arquivo ao canal %2$s %1$s enviou um GIF ao canal %2$s %1$s enviou uma mensagem ao canal %2$s + %1$s enviou uma música ao canal %2$s %1$s enviou um sticker ao canal %2$s %1$s postou uma mensagem %1$s postou uma foto @@ -183,6 +214,7 @@ %1$s postou um arquivo %1$s postou um GIF %1$s postou uma mensagem de voz + %1$s postou uma música %1$s postou um sticker Quem pode adicionar novos membros? Todos os Membros @@ -272,15 +304,33 @@ Enviar %1$s Abrir URL em %1$s? REPORTAR SPAM + REPORTAR SPAM E SAIR ADICIONAR CONTATO Você tem certeza que deseja reportar esse usuário por spam? Você tem certeza que deseja reportar esse grupo por spam? + Você tem certeza que deseja reportar esse canal por spam? Desculpe, você pode enviar mensagens somente para contatos mútuos no momento. Desculpe, você só pode adicionar contatos mútuos à grupos no momento. https://telegram.org/faq/br#no-consigo-enviar-mensagens-para-no-contatos Mais informações Enviar para... Toque aqui para acessar os GIFs salvos + Fixar + Notificar todos os membros + Desafixar + Você deseja fixar essa mensagem no grupo? + Você deseja desafixar essa mensagem? + Banir usuário + Reportar spam + Apagar tudo de %1$s + Limpar emojis recentes? + Reportar + Spam + Violência + Pornografia + Outro + Descrição + Mensagem Fixada %1$s estabeleceu o tempo de autodestruição para %2$s Você estabeleceu o tempo de autodestruição para %1$s @@ -296,6 +346,7 @@ %1$s lhe enviou um arquivo %1$s te enviou um GIF %1$s enviou uma mensagem de voz + %1$s enviou uma música %1$s lhe enviou um sticker %1$s @ %2$s: %3$s %1$s enviou uma mensagem para o grupo %2$s @@ -306,12 +357,14 @@ %1$s enviou um arquivo para o grupo %2$s %1$s enviou um GIF para o grupo %2$s %1$s enviou uma mensagem para o grupo %2$s + %1$s enviou uma música ao grupo %2$s %1$s enviou um sticker ao grupo %2$s %1$s convidou você para o grupo %2$s %1$s editou o nome do grupo %2$s %1$s editou a foto do grupo %2$s %1$s convidou %3$s para o grupo %2$s %1$s retornou ao grupo %2$s + %1$s entrou no grupo %2$s %1$s removeu %3$s do grupo %2$s %1$s removeu você do grupo %2$s %1$s saiu do grupo %2$s @@ -323,6 +376,28 @@ Responder para %1$s Responder para %1$s %1$s %2$s + %1$s fixou \"%2$s\" no grupo %3$s + %1$s fixou uma mensagem no grupo %2$s + %1$s fixou uma foto no grupo %2$s + %1$s fixou um vídeo no grupo %2$s + %1$s fixou um arquivo no grupo %2$s + %1$s fixou um sticker no grupo %2$s + %1$s fixou uma mensagem de voz no grupo %2$s + %1$s fixou um contato no grupo %2$s + %1$s fixou um mapa no grupo %2$s + %1$s fixou um GIF no grupo %2$s + %1$s fixou uma música no grupo %2$s + %1$s fixou \"%2$s\" + %1$s fixou uma mensagem + %1$s fixou uma foto + %1$s fixou um vídeo + %1$s fixou um arquivo + %1$s fixou um sticker + %1$s fixou uma mensagem de voz + %1$s fixou um contato + %1$s fixou um mapa + %1$s fixou um GIF + %1$s fixou uma música Selecionar Contato Ainda não há contatos @@ -372,9 +447,14 @@ Apagar e sair do grupo Notificações Remover do grupo - Atualizar para Supergrupo - Por favor note que os membros do grupo precisarão atualizar o aplicativo do Telegram até a última versão para verem seu supergrupo. Você tem certeza que deseja atualizar este grupo? - ]]>Limite de membros atingido.]]>\n\nPara ir além do limite e ter funções adcionais, atualize para um supergrupo:\n\n• Supergrupos podem ter até %1$s\n• Novos membros veêm todo o histórico de conversas\n• Administradores deletam mensagens para todos\n• Notificações são silenciadas por padrão + Atualizar para Supergrupo + Converter a Supergrupo + Converter a supergrupo + Atenção + Essa ação é irreversível. Não é possível voltar de um supergrupo para um grupo normal. + ]]>Limite de membros alcançado.]]>\n\nPara ir além do limite e ter funções adicionais, atualize para um supergrupo:\n\n• Supergrupos podem ter até %1$s\n• Novos membros podem visualizar todo o histórico\n• Mensagens apagadas desaparecerão para todos\n• Membros podem editar as próprias mensagens\n• Criador pode definir um link público para o grupo + ]]>Em supergroupos:]]>\n\n• Novos membros podem visualizar todo o histórico\n• Mensagens apagadas desaparecerão para todos\n• Membros podem editar as próprias mensagens\n• Criador pode definir um link público para o grupo + ]]>Nota:]]> essa ação não pode ser desfeita. Compartilhar Adicionar @@ -444,7 +524,7 @@ Vibrar Visualização no Aplicativo Limpar - Limpar todas as notificações + Restaurar configurações Desfazer todas as configurações de notificação para todos os seus contatos e grupos Notificações e Sons Usuários bloqueados @@ -464,6 +544,8 @@ Pergunte a um voluntário Perguntas frequentes https://telegram.org/faq + Política de Privacidade + https://telegram.org/privacy Apagar localização? Arquivo de localização incorreto Ativado @@ -524,6 +606,10 @@ minutos Pré-visualização de Link Chats secretos + Navegador no app + Abrir links externos com o aplicativo + Compartilhamento Direto + Mostrar chats recentes no menu compartilhar Configurações de Cache Banco de Dados Local @@ -575,14 +661,18 @@ Toque o sensor Impressão digital não reconhecida. - Compartilhar fotos e vídeos no chat e acessá-los em qualquer um de seus dispositivos. Arquivos Compartilhados Mídia Compartilhada Links Compartilhados Música Compartilhada + Compartilhar fotos e vídeos no chat e acessá-los em qualquer um de seus dispositivos. Compartilhe músicas nesse chat e os acesse de qualquer um de seus dispositivos. Compartilhar arquivos e documentos no chat e acessá-los de qualquer um de seus dispositivos. Compartilhe links nesse chat e os acesse de qualquer um de seus dispositivos + Fotos e vídeos desse chat serão mostrados aqui. + Músicas desse chat serão mostradas aqui. + Arquivos e documentos desse chat serão mostradas aqui. + Links compartilhados desse chat serão mostrados aqui. Mapa Satélite @@ -687,7 +777,7 @@ APAGAR MINHA CONTA Se você prosseguir e apagar a sua conta, você perderá todos os seus chats e mensagens, assim como todas as suas mídias e arquivos compartilhados. Aviso - Essa ação não pode ser revertida ou desfeita.\n\nSe você apagar a sua conta, todas as suas mensagens e chats serão apagados. + Essa ação não pode ser desfeita.\n\nSe você apagar a sua conta, todas as suas mensagens e chats serão apagados. Apagar Senha Você habilitou a verificação em duas etapas, a sua conta está protegida com uma senha adicional. @@ -803,6 +893,7 @@ un1 removeu você un1 adicionou você un1 retornou ao grupo + un1 entrou no grupo Você retornou ao grupo Esta mensagem não é suportada na sua versão do Telegram. Para visualizá-la atualize seu aplicativo em https://telegram.org/update Foto @@ -849,7 +940,7 @@ Você tem certeza que deseja cancelar o registro? Você tem certeza que deseja limpar o histórico? Apagar todos os textos e mídias em cache desse canal? - Apagar todos os textos e mídias em cache desse supergrupo? + Apagar todos os textos e mídias em cache desse grupo? Você tem certeza que deseja apagar %1$s? Enviar mensagens para %1$s? Enviar contato para %1$s? @@ -861,6 +952,9 @@ Os bots integrados são fornecidos por desenvolvedores terceiros. Para o bot funcionar, os símbolos que você digita depois do nome de usuário do bot são enviados para o respectivo desenvolvedor. Gostaria de habilitar o \"Levantar para Falar\" para mensagens de voz? Desculpe, você não pode editar essa mensagem. + Permita o acesso às SMS ao Telegram, assim podemos automaticamente adicionar o código para você. + Permita o acesso às ligações ao Telegram, assim podemos automaticamente adicionar o código para você. + Permita o acesso às SMS e ligações ao Telegram, assim podemos automaticamente adicionar o código para você. Telegram precisa acessar seus contatos para que você possa se conectar aos seus amigos em todos os seus dispositivos. Telegram precisa acessar seu armazenamento para que você possa enviar e salvar fotos, vídeos, músicas e outras mídias. diff --git a/TMessagesProj/src/main/res/values-pt-rPT/strings.xml b/TMessagesProj/src/main/res/values-pt-rPT/strings.xml index afd42f4695..a0870484a1 100644 --- a/TMessagesProj/src/main/res/values-pt-rPT/strings.xml +++ b/TMessagesProj/src/main/res/values-pt-rPT/strings.xml @@ -14,9 +14,13 @@ Escolha um país Código do país incorreto - Seu código - Enviamos uma SMS com um código de ativação para o seu telefone + Verificação do tel. + Enviamos uma SMS com um código de ativação para ]]>%1$s]]>. + Nós enviamos o código para o aplicativo do ]]>Telegram]]> em seu outro dispositivo. + Enviamos uma ligação de ativação para ]]>%1$s]]>.\n\nNão atenda, o Telegram processará automaticamente. + Nós te ligaremos em ]]>%1$s]]> para ditar o código. Vamos te ligar em %1$d:%2$02d + Vamos te enviar uma SMS em %1$d:%2$02d Estamos te ligando... Código Número incorreto? @@ -64,6 +68,10 @@ RECENTE Prévia do link + Tipo de Grupo + Tipo de canal + Público + Privado Promover a administrador Você pode fornecer uma descrição opcional para seu grupo. Sair do Grupo @@ -81,6 +89,17 @@ Desculpe, este usuário decidiu sair deste grupo, de maneira que você não pode convidá-lo de volta. Desculpe, há administradores demais neste grupo. Desculpe, há bots demais neste grupo. + un1 fixou \"%1$s\" + un1 ficou uma mensagem + un1 ficou uma foto + un1 ficou um vídeo + un1 ficou um arquivo + un1 ficou um sticker + un1 fixou uma mensagem de voz + un1 ficou um contato + un1 ficou um mapa + un1 ficou um GIF + un1 ficou uma música Este grupo foi atualizado para um supergrupo %1$s foi atualizado para um supergrupo Usuários bloqueados são removidos do grupo e só podem voltar se convidados por um administrador. Convites por link não funcionam para eles. @@ -90,15 +109,21 @@ Se você habilitar comentários, pessoas poderão discutir seu post no canal. Adicionar contatos no canal Pessoas podem compartilhar esse link com outros e encontrar seu canal usando a busca do Telegram. - + Pessoas podem compartilhar esse link com outros e encontrar o seu grupo usando a busca do Telegram. link Pessoas podem entrar em seu canal com este link. Você pode desativar o link quando quiser. + Pessoas podem entrar em seu canal com este link. Você pode desativar o link quando quiser. + Descrição (opcional) Descrição Você pode providenciar uma descrição opcional para o seu canal. Canal Público + Grupo Público Canais públicos podem ser encontrados na busca, qualquer um pode entrar. + Grupos públicos podem ser encontrados na busca, o histórico é disponível para todos e qualquer um pode entrar. Canal Privado - Canais privados só podem entrar aqueles que possuírem um link de convite. + Grupo Privado + Canais privados só podem ser aderidos através de um link de convite. + Grupos privados só podem ser aderidos através de um link de convite. Link Link de Convite Adicionar membros @@ -108,6 +133,7 @@ ENTRAR Info do Canal Transmissão + Transmissão Silenciosa Comentário mostrar comentários O que é um Canal? @@ -118,8 +144,8 @@ Nome do canal deve ter pelo menos 5 caracteres. O nome não pode exceder 32 caracteres. Nome do canal não pode iniciar com número. - - + Nome do grupo deve ter pelo menos 5 caracteres. + Nome do grupo não pode iniciar com número. Verificando nome... %1$s está disponível. Membros @@ -131,7 +157,7 @@ Você tem certeza que deseja sair do canal? Você perderá todas as mensagens desse canal. Editar - + Por favor, note que ao escolher um link público para o seu grupo, qualquer um poderá encontrá-lo na busca e entrar.\n\nNão crie um link se você deseja que seu supergrupo seja privado. Por favor, note que ao escolher um link público para o seu canal, qualquer um poderá encontrá-lo na busca e entrar.\n\nNão crie um link se você deseja que seu canal seja privado. Por favor, escolha um link para o seu canal público, assim as pessoas poderão encontrá-lo na busca e compartilhar com outros.\n\nSe não estiver interessado, sugerimos que crie um canal privado. Canal criado @@ -139,6 +165,7 @@ Foto do canal removida Nome do canal alterado para un2 Desculpe, você criou muitos canais públicos. Você pode criar um canal privado ou apagar um de seus canais existentes primeiro. + Desculpe, você possui muitos links públicos. Tente apagar um ou tornar um de seus grupos ou canais privados. Moderador Criador Administrador @@ -154,6 +181,8 @@ Você pode adicionar administradores para ajudar você a gerenciar seu canal. Aperte e segure para removê-los. Você deseja entrar no canal \'%1$s\'? Desculpe, esta conversa não pode mais ser acessada. + Infelizmente você foi banido de participar de grupos públicos. + Desculpe, esse chat não está mais acessível. Adicionar %1$s ao canal? Desculpe, este usuário decidiu sair deste canal, então você não pode convidá-lo de volta. Desculpe, você não pode adicionar esse usuário em canais. @@ -161,7 +190,8 @@ Desculpe, há bots demais neste canal. Desculpe, você só pode adicionar os primeiros 200 membros ao canal. Note que um número ilimitado de pessoas podem entrar via link do canal. un1 adicionou você ao canal - Você entrou no canal + Você entrou nesse canal + Você entrou nesse grupo Remover do canal Desculp, você não pode enviar mensagens para esse canal. %1$s adicionou você ao canal %2$s @@ -174,6 +204,7 @@ %1$s enviou um arquivo ao canal %2$s %1$s enviou um GIF ao canal %2$s %1$s enviou uma mensagem ao canal %2$s + %1$s enviou uma música ao canal %2$s %1$s enviou um sticker ao canal %2$s %1$s postou uma mensagem %1$s postou uma foto @@ -183,6 +214,7 @@ %1$s postou um arquivo %1$s postou um GIF %1$s postou uma mensagem de voz + %1$s postou uma música %1$s postou um sticker Quem pode adicionar novos membros? Todos os Membros @@ -272,15 +304,33 @@ Enviar %1$s Abrir URL em %1$s? REPORTAR SPAM + REPORTAR SPAM E SAIR ADICIONAR CONTATO Você tem certeza que deseja reportar esse usuário por spam? Você tem certeza que deseja reportar esse grupo por spam? + Você tem certeza que deseja reportar esse canal por spam? Desculpe, você pode enviar mensagens somente para contatos mútuos no momento. Desculpe, você só pode adicionar contatos mútuos à grupos no momento. https://telegram.org/faq/br#no-consigo-enviar-mensagens-para-no-contatos Mais informações Enviar para... Toque aqui para acessar os GIFs salvos + Fixar + Notificar todos os membros + Desafixar + Você deseja fixar essa mensagem no grupo? + Você deseja desafixar essa mensagem? + Banir usuário + Reportar spam + Apagar tudo de %1$s + Limpar emojis recentes? + Reportar + Spam + Violência + Pornografia + Outro + Descrição + Mensagem Fixada %1$s estabeleceu o tempo de autodestruição para %2$s Você estabeleceu o tempo de autodestruição para %1$s @@ -296,6 +346,7 @@ %1$s lhe enviou um arquivo %1$s te enviou um GIF %1$s enviou uma mensagem de voz + %1$s enviou uma música %1$s lhe enviou um sticker %1$s @ %2$s: %3$s %1$s enviou uma mensagem para o grupo %2$s @@ -306,12 +357,14 @@ %1$s enviou um arquivo para o grupo %2$s %1$s enviou um GIF para o grupo %2$s %1$s enviou uma mensagem para o grupo %2$s + %1$s enviou uma música ao grupo %2$s %1$s enviou um sticker ao grupo %2$s %1$s convidou você para o grupo %2$s %1$s editou o nome do grupo %2$s %1$s editou a foto do grupo %2$s %1$s convidou %3$s para o grupo %2$s %1$s retornou ao grupo %2$s + %1$s entrou no grupo %2$s %1$s removeu %3$s do grupo %2$s %1$s removeu você do grupo %2$s %1$s saiu do grupo %2$s @@ -323,6 +376,28 @@ Responder para %1$s Responder para %1$s %1$s %2$s + %1$s fixou \"%2$s\" no grupo %3$s + %1$s fixou uma mensagem no grupo %2$s + %1$s fixou uma foto no grupo %2$s + %1$s fixou um vídeo no grupo %2$s + %1$s fixou um arquivo no grupo %2$s + %1$s fixou um sticker no grupo %2$s + %1$s fixou uma mensagem de voz no grupo %2$s + %1$s fixou um contato no grupo %2$s + %1$s fixou um mapa no grupo %2$s + %1$s fixou um GIF no grupo %2$s + %1$s fixou uma música no grupo %2$s + %1$s fixou \"%2$s\" + %1$s fixou uma mensagem + %1$s fixou uma foto + %1$s fixou um vídeo + %1$s fixou um arquivo + %1$s fixou um sticker + %1$s fixou uma mensagem de voz + %1$s fixou um contato + %1$s fixou um mapa + %1$s fixou um GIF + %1$s fixou uma música Selecionar Contato Ainda não há contatos @@ -372,9 +447,14 @@ Apagar e sair do grupo Notificações Remover do grupo - Atualizar para Supergrupo - Por favor note que os membros do grupo precisarão atualizar o aplicativo do Telegram até a última versão para verem seu supergrupo. Você tem certeza que deseja atualizar este grupo? - ]]>Limite de membros atingido.]]>\n\nPara ir além do limite e ter funções adcionais, atualize para um supergrupo:\n\n• Supergrupos podem ter até %1$s\n• Novos membros veêm todo o histórico de conversas\n• Administradores deletam mensagens para todos\n• Notificações são silenciadas por padrão + Atualizar para Supergrupo + Converter a Supergrupo + Converter a supergrupo + Atenção + Essa ação é irreversível. Não é possível voltar de um supergrupo para um grupo normal. + ]]>Limite de membros alcançado.]]>\n\nPara ir além do limite e ter funções adicionais, atualize para um supergrupo:\n\n• Supergrupos podem ter até %1$s\n• Novos membros podem visualizar todo o histórico\n• Mensagens apagadas desaparecerão para todos\n• Membros podem editar as próprias mensagens\n• Criador pode definir um link público para o grupo + ]]>Em supergroupos:]]>\n\n• Novos membros podem visualizar todo o histórico\n• Mensagens apagadas desaparecerão para todos\n• Membros podem editar as próprias mensagens\n• Criador pode definir um link público para o grupo + ]]>Nota:]]> essa ação não pode ser desfeita. Compartilhar Adicionar @@ -444,7 +524,7 @@ Vibrar Visualização no Aplicativo Limpar - Limpar todas as notificações + Restaurar configurações Desfazer todas as configurações de notificação para todos os seus contatos e grupos Notificações e Sons Usuários bloqueados @@ -464,6 +544,8 @@ Pergunte a um voluntário Perguntas frequentes https://telegram.org/faq + Política de Privacidade + https://telegram.org/privacy Apagar localização? Arquivo de localização incorreto Ativado @@ -524,6 +606,10 @@ minutos Pré-visualização de Link Chats secretos + Navegador no app + Abrir links externos com o aplicativo + Compartilhamento Direto + Mostrar chats recentes no menu compartilhar Configurações de Cache Banco de Dados Local @@ -575,14 +661,18 @@ Toque o sensor Impressão digital não reconhecida. - Compartilhar fotos e vídeos no chat e acessá-los em qualquer um de seus dispositivos. Arquivos Compartilhados Mídia Compartilhada Links Compartilhados Música Compartilhada + Compartilhar fotos e vídeos no chat e acessá-los em qualquer um de seus dispositivos. Compartilhe músicas nesse chat e os acesse de qualquer um de seus dispositivos. Compartilhar arquivos e documentos no chat e acessá-los de qualquer um de seus dispositivos. Compartilhe links nesse chat e os acesse de qualquer um de seus dispositivos + Fotos e vídeos desse chat serão mostrados aqui. + Músicas desse chat serão mostradas aqui. + Arquivos e documentos desse chat serão mostradas aqui. + Links compartilhados desse chat serão mostrados aqui. Mapa Satélite @@ -687,7 +777,7 @@ APAGAR MINHA CONTA Se você prosseguir e apagar a sua conta, você perderá todos os seus chats e mensagens, assim como todas as suas mídias e arquivos compartilhados. Aviso - Essa ação não pode ser revertida ou desfeita.\n\nSe você apagar a sua conta, todas as suas mensagens e chats serão apagados. + Essa ação não pode ser desfeita.\n\nSe você apagar a sua conta, todas as suas mensagens e chats serão apagados. Apagar Senha Você habilitou a verificação em duas etapas, a sua conta está protegida com uma senha adicional. @@ -803,6 +893,7 @@ un1 removeu você un1 adicionou você un1 retornou ao grupo + un1 entrou no grupo Você retornou ao grupo Esta mensagem não é suportada na sua versão do Telegram. Para visualizá-la atualize seu aplicativo em https://telegram.org/update Foto @@ -849,7 +940,7 @@ Você tem certeza que deseja cancelar o registro? Você tem certeza que deseja limpar o histórico? Apagar todos os textos e mídias em cache desse canal? - Apagar todos os textos e mídias em cache desse supergrupo? + Apagar todos os textos e mídias em cache desse grupo? Você tem certeza que deseja apagar %1$s? Enviar mensagens para %1$s? Enviar contato para %1$s? @@ -861,6 +952,9 @@ Os bots integrados são fornecidos por desenvolvedores terceiros. Para o bot funcionar, os símbolos que você digita depois do nome de usuário do bot são enviados para o respectivo desenvolvedor. Gostaria de habilitar o \"Levantar para Falar\" para mensagens de voz? Desculpe, você não pode editar essa mensagem. + Permita o acesso às SMS ao Telegram, assim podemos automaticamente adicionar o código para você. + Permita o acesso às ligações ao Telegram, assim podemos automaticamente adicionar o código para você. + Permita o acesso às SMS e ligações ao Telegram, assim podemos automaticamente adicionar o código para você. Telegram precisa acessar seus contatos para que você possa se conectar aos seus amigos em todos os seus dispositivos. Telegram precisa acessar seu armazenamento para que você possa enviar e salvar fotos, vídeos, músicas e outras mídias. diff --git a/TMessagesProj/src/main/res/values/strings.xml b/TMessagesProj/src/main/res/values/strings.xml index ae88925807..8046380c19 100644 --- a/TMessagesProj/src/main/res/values/strings.xml +++ b/TMessagesProj/src/main/res/values/strings.xml @@ -14,9 +14,13 @@ Choose a country Wrong country code - Your code - We\'ve sent an SMS with an activation code to your phone + Phone verification + We\'ve sent an SMS with an activation code to your phone ]]>%1$s]]>. + We\'ve sent the code to the ]]>Telegram]]> app on your other device. + We\'ve sent an activation call to your phone ]]>%1$s]]>.\n\nDon\'t take it, Telegram will process everything automatically. + We are calling your phone ]]>%1$s]]> to dictate a code. We will call you in %1$d:%2$02d + We will send you an SMS in %1$d:%2$02d Calling you... Code Wrong number? @@ -64,6 +68,10 @@ RECENT Link preview + Group Type + Channel Type + Public + Private Promote to admin You can provide an optional description for your group. Leave Group @@ -79,8 +87,19 @@ Sorry, you can\'t add this user to groups. Sorry, this group is full. Sorry, this user decided to leave this group, so you cannot invite them back here. - Sorry, too many adminstrators in this group. + Sorry, too many administrators in this group. Sorry, too many bots in this group. + un1 pinned \"%1$s\" + un1 pinned a message + un1 pinned a photo + un1 pinned a video + un1 pinned a file + un1 pinned a sticker + un1 pinned a voice message + un1 pinned a contact + un1 pinned a map + un1 pinned a GIF + un1 pinned a track This group was upgraded to a supergroup %1$s was upgraded to a supergroup Blocked users are removed from the group and can only come back if invited by an admin. Invite links don\'t work for them. @@ -90,15 +109,21 @@ If you enable comments, people will be able to discuss your posts in the channel. Add contacts to your channel People can share this link with others and find your channel using Telegram search. - + People can share this link with others and find your group using Telegram search. link People can join your channel by following this link. You can revoke the link any time. + People can join your group by following this link. You can revoke the link any time. + Description (optional) Description You can provide an optional description for your channel. Public Channel + Public Group Public channels can be found in search, anyone can join them. + Public groups can be found in search, chat history is available to everyone and anyone can join. Private Channel + Private Group Private channels can only be joined via an invite link. + Private groups can only be joined if you were invited or have an invite link. Link Invite Link Add members @@ -108,6 +133,7 @@ JOIN Channel info Broadcast + Silent Broadcast Comment show comments What is a Channel? @@ -118,8 +144,8 @@ Channel names must have at least 5 characters. The name must not exceed 32 characters. Channel names can\'t start with a number. - - + Group names must have at least 5 characters. + Group names can\'t start with a number. Checking name… %1$s is available. Members @@ -131,7 +157,7 @@ Are you sure you want to leave the channel? You will lose all messages in this channel. Edit - + Please note that if you choose a public link for your group, anyone will be able to find it in search and join.\n\nDo not create this link if you want your supergroup to stay private. Please note that if you choose a public link for your channel, anyone will be able to find it in search and join.\n\nDo not create this link if you want your channel to stay private. Please choose a link for your public channel, so that people can find it in search and share with others.\n\nIf you\'re not interested, we suggest creating a private channel instead. Channel created @@ -139,6 +165,7 @@ Channel photo removed Channel name changed to un2 Sorry, you have created too many public channels. You can either create a private channel or delete one of your existing channels first. + Sorry, you have created too many public links. Try deleting or making some of your groups or channels private. Moderator Creator Administrator @@ -154,6 +181,8 @@ You can add administrators to help you manage your channel. Tap and hold to remove admins. Do you want to join the channel \'%1$s\'? Sorry, this chat is no longer accessible. + Unfortunately, you were banned from participating in public groups. + Sorry, this chat is no longer accessible. Add %1$s to the channel? Sorry, this user decided to leave this channel, so you cannot invite them back here. Sorry, you can\'t add this user to channels. @@ -161,7 +190,9 @@ Sorry, too many bots in this channel. Sorry, you can only add the first 200 members to a channel. Note that an unlimited number of people may join via the channel\'s link. un1 added you to this channel - You joined the channel + You joined this channel + You joined this group + "%@ joined the group" Remove from channel Sorry, you can\'t send messages to this channel. %1$s added you to the channel %2$s @@ -174,6 +205,7 @@ %1$s sent a file to the channel %2$s %1$s sent a GIF to the channel %2$s %1$s sent a voice message to the channel %2$s + %1$s sent a track to the channel %2$s %1$s sent a sticker to the channel %2$s %1$s posted a message %1$s posted a photo @@ -183,6 +215,7 @@ %1$s posted a file %1$s posted a GIF %1$s posted a voice message + %1$s posted a track %1$s posted a sticker Who can add new members? All Members @@ -272,15 +305,33 @@ Send %1$s Open url %1$s? REPORT SPAM + REPORT SPAM AND LEAVE ADD CONTACT Are you sure you want to report spam from this user? Are you sure you want to report spam from this group? + Are you sure you want to report spam from this channel? Sorry, you can only send messages to mutual contacts at the moment. Sorry, you can only add mutual contacts to groups at the moment. https://telegram.org/faq#can-39t-send-messages-to-non-contacts More info Send to... Tap here to access saved GIFs + Pin + Notify all members + Unpin + Do you want to pin this message in this group? + Do you want to unpin this message? + Ban user + Report spam + Delete all from %1$s + Clear recent emoji? + Report + Spam + Violence + Pornography + Other + Description + Pinned Message %1$s set the self-destruct timer to %2$s You set the self-destruct timer to %1$s @@ -296,6 +347,7 @@ %1$s sent you a file %1$s sent you a GIF %1$s sent you a voice message + %1$s sent you a track %1$s sent you a sticker %1$s @ %2$s: %3$s %1$s sent a message to the group %2$s @@ -306,12 +358,14 @@ %1$s sent a file to the group %2$s %1$s sent a GIF to the group %2$s %1$s sent a voice message to the group %2$s + %1$s sent a track to the group %2$s %1$s sent a sticker to the group %2$s %1$s invited you to the group %2$s %1$s edited the group\'s %2$s name %1$s edited the group\'s %2$s photo %1$s invited %3$s to the group %2$s %1$s returned to the group %2$s + %1$s joined the group %2$s %1$s removed %3$s from the group %2$s %1$s removed you from the group %2$s %1$s has left the group %2$s @@ -323,6 +377,28 @@ Reply to %1$s Reply to %1$s %1$s %2$s + %1$s pinned \"%2$s\" in the group %3$s + %1$s pinned a message in the group %2$s + %1$s pinned a photo in the group %2$s + %1$s pinned a video in the group %2$s + %1$s pinned a file in the group %2$s + %1$s pinned a sticker in the group %2$s + %1$s pinned a voice message in the group %2$s + %1$s pinned a contact in the group %2$s + %1$s pinned a map in the group %2$s + %1$s pinned a GIF in the group %2$s + %1$s pinned a track in the group %2$s + %1$s pinned \"%2$s\" + %1$s pinned a message + %1$s pinned a photo + %1$s pinned a video + %1$s pinned a file + %1$s pinned a sticker + %1$s pinned a voice message + %1$s pinned a contact + %1$s pinned a map + %1$s pinned a GIF + %1$s pinned a track Select Contact No contacts yet @@ -372,9 +448,14 @@ Delete and leave group Notifications Remove from group - Upgrade to Supergroup - Please note that group members will need to update their Telegram apps to the latest version to see your supergroup. Are you sure you want to upgrade this group? - ]]>Members limit reached.]]>\n\nTo go over the limit and get additional features, upgrade to a supergroup:\n\n• Supergroups can get up to %1$s\n• New members see the entire chat history\n• Admins delete messages for everyone\n• Notifications are muted by default + Upgrade to Supergroup + Convert to Supergroup + Convert to supergroup + Warning + This action is irreversible. It is not possible to downgrade a supergroup to a regular group. + ]]>Members limit reached.]]>\n\nTo go over the limit and get additional features, upgrade to a supergroup:\n\n• Supergroups can get up to %1$s\n• New members can see the full message history\n• Deleted messages will disappear for all members\n• Members can edit their own messages\n• Creator can set a public link for the group + ]]>In supergroups:]]>\n\n• New members can see the full message history\n• Deleted messages will disappear for all members\n• Members can edit their own messages\n• Creator can set a public link for the group + ]]>Note:]]> this action can\'t be undone. Share Add @@ -464,6 +545,8 @@ Ask a volunteer Telegram FAQ https://telegram.org/faq + Privacy Policy + https://telegram.org/privacy Delete localization? Incorrect localization file Enabled @@ -524,6 +607,10 @@ minutes Link Previews Secret chats + In-App Browser + Open external links within the app + Direct Share + Show recent chats in share menu Cache Settings Local Database @@ -575,14 +662,18 @@ Touch sensor Fingerprint not recognized. Try again - Share photos and videos in this chat and access them on any of your devices. Shared Files Shared Media Shared Links Shared Music + Share photos and videos in this chat and access them on any of your devices. Share music in this chat and access them on any of your devices. Share files and documents in this chat and access them on any of your devices. Share links in this chat and access them on any of your devices. + Photos and videos from this chat will be shown here. + Music from this chat will be shown here. + Files and documents from this chat will be shown here. + Shared links from this chat will be shown here. Map Satellite @@ -687,7 +778,7 @@ RESET MY ACCOUNT You will lose all your chats and messages, along with any media and files you shared, if you proceed with resetting your account. Warning - This action can not be undone.\n\nIf you reset your account, all your messages and chats will be deleted. + This action can\'t be undone.\n\nIf you reset your account, all your messages and chats will be deleted. Reset Password You have enabled Two-Step Verification, so your account is protected with an additional password. @@ -803,6 +894,7 @@ un1 removed you un1 added you un1 returned to the group + un1 joined the group You returned to the group This message is not supported on your version of Telegram. Update the app to view: https://telegram.org/update Photo @@ -849,7 +941,7 @@ Are you sure you want to cancel registration? Are you sure you want to clear history? Delete all cached text and media from this channel? - Delete all cached text and media from this supergroup? + Delete all cached text and media from this group? Are you sure you want to delete %1$s? Send messages to %1$s? Send contact to %1$s? @@ -861,6 +953,9 @@ Please note that inline bots are provided by third-party developers. For the bot to work, the symbols you type after the bot\'s username are sent to the respective developer. Would you like to enable "Raise to speak" for voice messages? Sorry, you can\'t edit this message. + Please allow Telegram to receive SMS so that we can automatically enter your code for you. + Please allow Telegram to receive calls so that we can automatically enter your code for you. + Please allow Telegram to receive calls and SMS so that we can automatically enter your code for you. Telegram needs access to your contacts so that you can connect with your friends across all your devices. Telegram needs access to your storage so that you can send and save photos, videos, music and other media. diff --git a/build.gradle b/build.gradle index 26aaecfb09..2c397bc2a1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { + jcenter() mavenCentral() } dependencies { diff --git a/gradle.properties b/gradle.properties index 01520498d7..08f287cfb4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,19 @@ -RELEASE_STORE_PASSWORD=password +## Project-wide Gradle settings. +# +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +# +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +#Sat Mar 12 05:53:50 MSK 2016 +RELEASE_KEY_PASSWORD=password RELEASE_KEY_ALIAS=alias -RELEASE_KEY_PASSWORD=password \ No newline at end of file +RELEASE_STORE_PASSWORD=password +android.useDeprecatedNdk=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 89ecdcf968..b0b8557e8b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Jun 16 02:56:07 KRAT 2015 +#Fri Dec 04 13:44:40 MSK 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.11-all.zip