diff --git a/README.md b/README.md index 7b569217a..8e616ad5e 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 34a38990d..bd3d3a7f4 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 995a53ee6..b783ad8eb 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 0460b3340..bd546a41a 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 87f946d4d..c6fd3650a 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 2b17fd617..711b91c90 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 2fc4ef337..4957f790b 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 3a9e7a3af..1a51abe54 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 92d9ab34a..5490180be 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 d73a9affa..766336f01 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 c0dece7b2..4dd43713c 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 @@ package org.telegram.messenger; 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.ActivityInfo; 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.Typeface; 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 class AndroidUtilities { } } + 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 class AndroidUtilities { 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 class AndroidUtilities { 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 e511f2b16..944477f0d 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 org.telegram.ui.Components.ForegroundDetector; 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 561857a6c..74e2ab8aa 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 @@ package org.telegram.messenger; 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 2aae70ab0..58eeacd26 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 @@ package org.telegram.messenger; 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 49c0dc44f..7a5328550 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 class Emoji { 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 6e8d4f3fe..a902ad76f 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 class FileLoader { 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 class FileLoader { 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 class FileLoader { 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 580f3ac99..f39780957 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 class FileLog { 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 (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(); } - if (getInstance().networkFile != null && file.getAbsolutePath().equals(getInstance().networkFile.getAbsolutePath())) { - continue; - } - 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 68efb2c5f..52d87aa35 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.net.Uri; 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 org.telegram.ui.Components.AnimatedFileDrawable; 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 class ImageLoader { 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 class ImageLoader { } 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 class ImageLoader { } } 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 class ImageLoader { 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 97ea54fdf..1968f81e6 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 class LocaleController { 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 class LocaleController { } 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 class LocaleController { } 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 5d5cb3297..b3b8517c4 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.ConnectivityManager; 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 org.telegram.ui.PhotoViewer; 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 class MediaController implements AudioManager.OnAudioFocusChangeListener, 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 class MediaController implements AudioManager.OnAudioFocusChangeListener, 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 class MediaController implements AudioManager.OnAudioFocusChangeListener, 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 class MediaController implements AudioManager.OnAudioFocusChangeListener, 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 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, 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 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, 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 class MediaController implements AudioManager.OnAudioFocusChangeListener, } } - public boolean playAudio(MessageObject messageObject) { + public boolean playAudio(final MessageObject messageObject) { if (messageObject == null) { return false; } @@ -2171,7 +2191,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, if (!playlist.isEmpty() && playlist.size() > 1) { playNextMessage(true); } else { - cleanupPlayer(true, true); + cleanupPlayer(true, true, messageObject != null && messageObject.isVoice()); } } }); @@ -2201,8 +2221,8 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, 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 class MediaController implements AudioManager.OnAudioFocusChangeListener, } 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 class MediaController implements AudioManager.OnAudioFocusChangeListener, 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 class MediaController implements AudioManager.OnAudioFocusChangeListener, 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 class MediaController implements AudioManager.OnAudioFocusChangeListener, } 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 class MediaController implements AudioManager.OnAudioFocusChangeListener, 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 class MediaController implements AudioManager.OnAudioFocusChangeListener, } 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,15 +2818,8 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, 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); @@ -2836,33 +2828,58 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, return false; } - public static String copyDocumentToCache(Uri uri, String ext) { - ParcelFileDescriptor parcelFD = null; - FileInputStream input = null; + 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 (cursor.moveToFirst()) { + result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + if (result == null) { + result = uri.getPath(); + int cut = result.lastIndexOf('/'); + if (cut != -1) { + result = result.substring(cut + 1); + } + } + return result; + } + + 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 class MediaController implements AudioManager.OnAudioFocusChangeListener, 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 class MediaController implements AudioManager.OnAudioFocusChangeListener, 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 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, long startTime = -1; checkConversionCanceled(); + long lastTimestamp = -100; while (!inputDone) { checkConversionCanceled(); @@ -3254,28 +3296,37 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, 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 aa34a3758..4033311a7 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 class MessageObject { } 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 class MessageObject { 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 class MessageObject { 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 class MessageObject { 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); - } else { - messageText = replaceWithLink(LocaleController.getString("ChannelAddedBy", R.string.ChannelAddedBy), "un1", whoUser); - } - } else { + 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("ActionAddUserSelf", R.string.ActionAddUserSelf), "un1", fromUser); + } } } 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 class MessageObject { } } } 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 class MessageObject { 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 class MessageObject { 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 class MessageObject { 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 class MessageObject { 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 class MessageObject { 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 class MessageObject { 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 class MessageObject { 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 class MessageObject { 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 class MessageObject { } } 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 class MessageObject { 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 class MessageObject { 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 class MessageObject { return FileLoader.getAttachFileName(sizeFull); } } + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) { + return FileLoader.getAttachFileName(messageOwner.media.webpage.document); } return ""; } @@ -950,7 +957,7 @@ public class MessageObject { 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 @@ public class MessageObject { 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 @@ public class MessageObject { linesOffset += currentBlockLinesCount; } - if (blockHeight == 0) { - blockHeight = 1; - } } public boolean isOut() { @@ -1421,7 +1419,11 @@ public class MessageObject { } 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 class MessageObject { } 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 c8080cc57..c38da3644 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 android.widget.Toast; 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 class MessagesController implements NotificationCenter.NotificationCenter } 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 class MessagesController implements NotificationCenter.NotificationCenter } 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 class MessagesController implements NotificationCenter.NotificationCenter dialogs_dict.clear(); dialogs_read_inbox_max.clear(); exportedChats.clear(); + fullUsersAbout.clear(); dialogs.clear(); joiningToChannels.clear(); channelViewsToSend.clear(); @@ -486,6 +491,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter printingStrings.clear(); printingStringsTypes.clear(); onlinePrivacy.clear(); + loadingPeerSettings.clear(); lastPrintingStringCount = 0; nextDialogsCacheOffset = 0; Utilities.stageQueue.postRunnable(new Runnable() { @@ -497,7 +503,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter updatesStartWaitTimeSeq = 0; updatesStartWaitTimePts = 0; updatesStartWaitTimeQts = 0; - lastCreatedDialogId = 0; + createdDialogIds.clear(); gettingDifference = false; } }); @@ -596,9 +602,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter @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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter } } 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter } } + public String getUserAbout(int uid) { + return fullUsersAbout.get(uid); + } + public void cancelLoadFullUser(int uid) { loadingFullUsers.remove((Integer) uid); } @@ -748,10 +835,6 @@ public class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter } } - 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter }); } + 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter @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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter } 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 class MessagesController implements NotificationCenter.NotificationCenter @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 class MessagesController implements NotificationCenter.NotificationCenter } } + 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 class MessagesController implements NotificationCenter.NotificationCenter } } } 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 class MessagesController implements NotificationCenter.NotificationCenter } } + 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 class MessagesController implements NotificationCenter.NotificationCenter } } + 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter @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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter } 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter } 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 class MessagesController implements NotificationCenter.NotificationCenter } 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 class MessagesController implements NotificationCenter.NotificationCenter } } } 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter } }); } - }); + }, 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 class MessagesController implements NotificationCenter.NotificationCenter } 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 class MessagesController implements NotificationCenter.NotificationCenter } }); } - }); + }, ConnectionsManager.RequestFlagFailOnServerErrors); } return 0; } @@ -3475,7 +3748,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter }); 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - MessagesController.getInstance().deleteDialog(-chat_id, 0); + deleteDialog(-chat_id, 0); } }); } @@ -3763,7 +4047,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } 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 class MessagesController implements NotificationCenter.NotificationCenter channelsPts.put(channelId, channelPts); } if (channelPts == 0 && newDialogType == 2) { - channelPts = 1; - limit = 1; + return; } } if (channelPts == 0) { @@ -4269,7 +4552,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter } } + MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true); AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { @@ -4368,7 +4652,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter } }); } - 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter } }); } 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 class MessagesController implements NotificationCenter.NotificationCenter } } - 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter } } } 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); + 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 (existChat == null || existChat.min) { + if (minChannels == null) { + minChannels = new HashMap<>(); + } + minChannels.put(chat.id, chat); } - 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; - } - } - 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; - } - } - } 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 (minChannels != null) { + for (int a = 0; a < updates.updates.size(); a++) { + TLRPC.Update update = updates.updates.get(a); 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 { + 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; } } - 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); - } + } + } + 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; } - } else { - break; + }); + 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; + } + } + 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; + } + } + } 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 { + 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 (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; + } + 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 class MessagesController implements NotificationCenter.NotificationCenter } 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter } 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter } 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 class MessagesController implements NotificationCenter.NotificationCenter } 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 class MessagesController implements NotificationCenter.NotificationCenter 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 class MessagesController implements NotificationCenter.NotificationCenter 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 @@ public class MessagesController implements NotificationCenter.NotificationCenter addNewGifToRecent(message.messageOwner.media.document, message.messageOwner.date); } } - + MessagesQuery.loadReplyMessagesForMessages(messages, uid); NotificationCenter.getInstance().postNotificationName(NotificationCenter.didReceivedNewMessages, uid, messages); if (lastMessage == null) { @@ -6435,19 +6783,91 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } + private static String getRestrictionReason(String reason) { + if (reason == null || reason.length() == 0) { + return null; + } + int index = reason.indexOf(": "); + if (index > 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; + } + 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(); + 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 instanceof ChatActivity*/); + } + } + } + public static void openByUserName(String username, final BaseFragment fragment, final int type) { if (username == null || fragment == null) { return; } - TLRPC.User user = MessagesController.getInstance().getUser(username); + TLRPC.User user = getInstance().getUser(username); if (user != null) { - Bundle args = new Bundle(); - args.putInt("user_id", user.id); - if (type == 0) { - fragment.presentFragment(new ProfileActivity(args)); - } else { - fragment.presentFragment(new ChatActivity(args)); - } + openChatOrProfileWith(user, null, fragment, type); } else { if (fragment.getParentActivity() == null) { return; @@ -6473,21 +6893,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter 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 15079dbb0..a1dd92f52 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.SQLiteCursor; 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 class MessagesStorage { 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 class MessagesStorage { 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 class MessagesStorage { } } int version = database.executeInt("PRAGMA user_version"); - if (version < 30) { + if (version < 31) { updateDbToLastVersion(version); } } @@ -487,7 +494,16 @@ public class MessagesStorage { 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 class MessagesStorage { }); } - 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 class MessagesStorage { } } } + } + 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 class MessagesStorage { 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 class MessagesStorage { 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 class MessagesStorage { @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 class MessagesStorage { 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 class MessagesStorage { 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 class MessagesStorage { }); } + 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 class MessagesStorage { 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 class MessagesStorage { @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 class MessagesStorage { 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 class MessagesStorage { holeMessageMinId |= ((long) channelId) << 32; } } - /*if (holeMessageMaxId == holeMessageMinId) { TODO ??? + /*if (holeMessageMaxId == holeMessageMinId) { holeMessageMaxId = 0; holeMessageMinId = 1; }*/ @@ -2422,19 +2559,17 @@ public class MessagesStorage { 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 @@ public class MessagesStorage { 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 @@ public class MessagesStorage { 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 @@ public class MessagesStorage { oldUser.photo = null; oldUser.flags = oldUser.flags &~ 32; } + user = oldUser; } - user = oldUser; } data.reuse(); } catch (Exception e) { @@ -3099,6 +3241,36 @@ public class MessagesStorage { 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 @@ public class MessagesStorage { } catch (Exception e2) { FileLog.e("tmessages", e2); } - FileLog.e("tmessages", e); } finally { if (state != null) { state.dispose(); @@ -4138,7 +4309,6 @@ public class MessagesStorage { } catch (Exception e2) { FileLog.e("tmessages", e2); } - FileLog.e("tmessages", e); } finally { if (state != null) { state.dispose(); @@ -4388,24 +4558,23 @@ public class MessagesStorage { 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 class MessagesStorage { 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 class MessagesStorage { 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 class MessagesStorage { } 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 46251dfbd..79f523711 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 @@ import java.util.zip.ZipFile; 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 6fd1db240..6a37d46ae 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 5276b742f..865cd616e 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 class NotificationsController { 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 class NotificationsController { 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 class NotificationsController { } 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 class NotificationsController { 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 class NotificationsController { 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 @@ public class NotificationsController { 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 @@ public class NotificationsController { 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 @@ public class NotificationsController { 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 @@ public class NotificationsController { 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 @@ public class NotificationsController { 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 @@ public class NotificationsController { 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 @@ public class NotificationsController { 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 @@ public class NotificationsController { } } - 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 05c96ff7a..35c22b854 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 class SendMessagesHelper implements NotificationCenter.NotificationCenter 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 class SendMessagesHelper implements NotificationCenter.NotificationCenter 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 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter 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 class SendMessagesHelper implements NotificationCenter.NotificationCenter 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 class SendMessagesHelper implements NotificationCenter.NotificationCenter } 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 6f017e673..7f89fcb80 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 class TgChooserTargetService extends ChooserTargetService { 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 ab3f8a9f8..03646523b 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 class BotQuery { } 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 4ef51007c..d7e767ab3 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.ArrayList; 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 class ReplyMessageQuery { 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 class ReplyMessageQuery { 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 class ReplyMessageQuery { } } }); - } else { final ArrayList replyMessages = new ArrayList<>(); final HashMap> replyMessageOwners = new HashMap<>(); @@ -157,7 +304,7 @@ public class ReplyMessageQuery { 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 class ReplyMessageQuery { 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 class ReplyMessageQuery { 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 class ReplyMessageQuery { 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 class ReplyMessageQuery { 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 class ReplyMessageQuery { 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 f16cad0c4..70e53dd4a 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.Log; 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 @@ import org.telegram.messenger.support.util.TileList; * 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 @@ public class AsyncListUtil { *

* 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 a9d59b1fe..bf78e6aa9 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 @@ package org.telegram.messenger.support.util; 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 @@ class MessageThreadUtil implements ThreadUtil { 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 @@ class MessageThreadUtil implements ThreadUtil { 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); - 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); + while (true) { + SyncQueueItem msg = mQueue.next(); + if (msg == null) { + break; + } + 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 319428f0b..82c555d09 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 @@ import java.util.Comparator; /** * 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 class SortedList { * 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 c9a1583ff..cdb8689fc 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 8ea669731..1c40eafc7 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 @@ package org.telegram.messenger.support.widget; 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 @@ class AdapterHelper implements OpReorderer.Callback { void reset() { recycleUpdateOpsAndClearList(mPendingUpdates); recycleUpdateOpsAndClearList(mPostponedList); + mExistingUpdateTypes = 0; } void preProcess() { @@ -122,6 +122,7 @@ class AdapterHelper implements OpReorderer.Callback { mCallback.onDispatchSecondPass(mPostponedList.get(i)); } recycleUpdateOpsAndClearList(mPostponedList); + mExistingUpdateTypes = 0; } private void applyMove(UpdateOp op) { @@ -460,6 +461,10 @@ class AdapterHelper implements OpReorderer.Callback { return mPendingUpdates.size() > 0; } + boolean hasAnyUpdateTypes(int updateTypes) { + return (mExistingUpdateTypes & updateTypes) != 0; + } + int findPositionOffset(int position) { return findPositionOffset(position, 0); } @@ -498,6 +503,7 @@ class AdapterHelper implements OpReorderer.Callback { */ 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 @@ class AdapterHelper implements OpReorderer.Callback { */ 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 @@ class AdapterHelper implements OpReorderer.Callback { */ 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 @@ class AdapterHelper implements OpReorderer.Callback { */ 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 @@ class AdapterHelper implements OpReorderer.Callback { } } recycleUpdateOpsAndClearList(mPendingUpdates); + mExistingUpdateTypes = 0; } public int applyPendingUpdatesToPosition(int position) { @@ -602,18 +612,22 @@ class AdapterHelper implements OpReorderer.Callback { 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 e247875c3..582b8f245 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 @@ class ChildHelper { 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 @@ class ChildHelper { } } + /** + * 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 ad58b3d6c..546f93f0a 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 @@ import java.util.List; * * @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 class DefaultItemAnimator extends RecyclerView.ItemAnimator { 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 class DefaultItemAnimator extends RecyclerView.ItemAnimator { } // 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 class DefaultItemAnimator extends RecyclerView.ItemAnimator { } // 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 class DefaultItemAnimator extends RecyclerView.ItemAnimator { @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 class DefaultItemAnimator extends RecyclerView.ItemAnimator { 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 class DefaultItemAnimator extends RecyclerView.ItemAnimator { } // 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 @@ public class DefaultItemAnimator extends RecyclerView.ItemAnimator { } } + /** + * {@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 class DefaultItemAnimator extends RecyclerView.ItemAnimator { @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 f347ce3c0..84c3cb49c 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 class GridLayoutManager extends LinearLayoutManager { 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 class GridLayoutManager extends LinearLayoutManager { 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 class GridLayoutManager extends LinearLayoutManager { @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 @@ public class GridLayoutManager extends LinearLayoutManager { calculateItemBorders(totalSpace); } - private void calculateItemBorders(int totalSpace) { - if (mCachedBorders == null || mCachedBorders.length != mSpanCount + 1 - || mCachedBorders[mCachedBorders.length - 1] != totalSpace) { - mCachedBorders = new int[mSpanCount + 1]; + @Override + public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) { + if (mCachedBorders == null) { + super.setMeasuredDimension(childrenBounds, wSpec, hSpec); } - mCachedBorders[0] = 0; - int sizePerSpan = totalSpace / mSpanCount; - int sizePerSpanRemainder = totalSpace % mSpanCount; + 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) { + 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]; + } + 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 @@ public class GridLayoutManager extends LinearLayoutManager { @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 @@ public class GridLayoutManager extends LinearLayoutManager { } 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 @@ public class GridLayoutManager extends LinearLayoutManager { } 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 @@ public class GridLayoutManager extends LinearLayoutManager { 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 @@ public class GridLayoutManager extends LinearLayoutManager { 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 @@ public class GridLayoutManager extends LinearLayoutManager { 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 @@ public class GridLayoutManager extends LinearLayoutManager { 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 class GridLayoutManager extends LinearLayoutManager { } } + @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 e3a9c8b18..9ee6745ea 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 44864b0ff..9d83de9c5 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.os.Parcelable; 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 class LinearLayoutManager extends RecyclerView.LayoutManager implements 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 class LinearLayoutManager extends RecyclerView.LayoutManager implements /** * 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 class LinearLayoutManager extends RecyclerView.LayoutManager implements } /** - * 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 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements * 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 class LinearLayoutManager extends RecyclerView.LayoutManager implements * 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 class LinearLayoutManager extends RecyclerView.LayoutManager implements /** *

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 class LinearLayoutManager extends RecyclerView.LayoutManager implements } 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 class LinearLayoutManager extends RecyclerView.LayoutManager implements /** * 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 class LinearLayoutManager extends RecyclerView.LayoutManager implements 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 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements 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 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements : 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 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements 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 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements /** * 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 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements /** * 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 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements * @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 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements /** * 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 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements } 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 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements 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 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements * @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 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements 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 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements */ 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 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements } } - 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 dbb50209c..e5a611180 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.View; 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 @@ abstract public class LinearSmoothScroller extends RecyclerView.SmoothScroller { 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 @@ abstract public class LinearSmoothScroller extends RecyclerView.SmoothScroller { * @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 @@ abstract public class LinearSmoothScroller extends RecyclerView.SmoothScroller { */ 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 42a04f8b1..1c8aa4a9d 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 @@ package org.telegram.messenger.support.widget; 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 abstract class OrientationHelper { */ 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 abstract class OrientationHelper { 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 abstract class OrientationHelper { 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 6f160865a..8f224ea24 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.Build; 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.AccessibilityNodeInfoCompat; 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.ViewParent; 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } }; + /** + * 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } /** - * 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro */ public void addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) { if (mOnChildAttachStateListeners == null) { - mOnChildAttachStateListeners = new ArrayList(); + mOnChildAttachStateListeners = new ArrayList<>(); } mOnChildAttachStateListeners.add(listener); } @@ -940,6 +996,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro */ public void addOnScrollListener(OnScrollListener listener) { if (mScrollListeners == null) { - mScrollListeners = new ArrayList(); + mScrollListeners = new ArrayList<>(); } mScrollListeners.add(listener); } @@ -1269,14 +1327,6 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } } - /** - * 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro @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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro */ @Override public int computeHorizontalScrollExtent() { + if (mLayout == null) { + return 0; + } return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mState) : 0; } @@ -1503,6 +1592,9 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro */ @Override public int computeHorizontalScrollRange() { + if (mLayout == null) { + return 0; + } return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0; } @@ -1520,11 +1612,14 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro */ @Override public int computeVerticalScrollExtent() { + if (mLayout == null) { + return 0; + } return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mState) : 0; } @@ -1566,31 +1664,50 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro */ @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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro mLayout.dispatchDetachedFromWindow(this, mRecycler); } removeCallbacks(mItemAnimatorRunner); + mViewInfoStore.onDetach(); } /** @@ -2170,6 +2288,9 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro startScroll = true; } if (startScroll) { - final ViewParent parent = getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } setScrollState(SCROLL_STATE_DRAGGING); } } @@ -2338,10 +2455,6 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro startScroll = true; } if (startScroll) { - final ViewParent parent = getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } setScrollState(SCROLL_STATE_DRAGGING); } } @@ -2463,79 +2576,93 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } 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); - } else { - mLayout.onMeasure(mRecycler, mState, 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(); - mState.mInPreLayout = false; // clear + // 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 + } } /** * 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro // 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro // 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro // 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); + final ItemHolderInfo animationInfo = mItemAnimator + .recordPostLayoutInformation(mState, holder); + ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key); + if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) { + // run a change animation - 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(); + // 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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())); - } - } - 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 animateAppearance(@NonNull ViewHolder itemHolder, + @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) { + itemHolder.setIsRecyclable(false); + if (mItemAnimator.animateAppearance(itemHolder, 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(); - } - } else { - if (DEBUG) { - Log.d(TAG, "REMOVED: " + disappearingItem.holder + - " with view " + disappearingItemView); - } - disappearingItem.holder.setIsRecyclable(false); - if (mItemAnimator.animateRemove(disappearingItem.holder)) { - 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 animateChange(ViewHolder oldHolder, ViewHolder newHolder) { + private void animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder, + @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo, + boolean oldHolderDisappearing, boolean newHolderDisappearing) { oldHolder.setIsRecyclable(false); - addAnimatingView(oldHolder); - oldHolder.mShadowedHolder = newHolder; - mRecycler.unscrapView(oldHolder); - if (DEBUG) { - Log.d(TAG, "CHANGED: " + oldHolder + " with view " + oldHolder.itemView); + if (oldHolderDisappearing) { + addAnimatingView(oldHolder); } - 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(); + if (oldHolder != newHolder) { + if (newHolderDisappearing) { + addAnimatingView(newHolder); + } + 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro // 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro @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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } + 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro // 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } } } + + // 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } // 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro void quickRecycleScrapView(View view) { final ViewHolder holder = getChildViewHolderInt(view); holder.mScrapContainer = null; + holder.mInChangeScrap = false; holder.clearReturnedFromScrapFlag(); recycleViewHolderInternal(holder); } @@ -4799,18 +4958,20 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro */ 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro void clearScrap() { mAttachedScrap.clear(); + if (mChangedScrap != null) { + mChangedScrap.clear(); + } } ViewHolder getChangedScrapViewForPosition(int position) { @@ -4905,8 +5070,20 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro mAdapter.onViewRecycled(holder); } if (mState != null) { - mState.onViewRecycled(holder); + mViewInfoStore.removeViewHolder(holder); } if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder); } @@ -5032,7 +5211,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * @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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * *

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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro mOnChildAttachStateListeners.get(i).onChildViewAttachedToWindow(child); } } - } /** @@ -5792,17 +5974,119 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } } + /** + * 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } } + /** + * 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * @return Height in pixels */ public int getHeight() { - return mRecyclerView != null ? mRecyclerView.getHeight() : 0; + return MeasureSpec.getSize(mHeightSpec); } /** @@ -6792,7 +7246,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } final ViewHolder vh = getChildViewHolderInt(view); vh.addFlags(ViewHolder.FLAG_IGNORE); - mRecyclerView.mState.onViewIgnored(vh); + mRecyclerView.mViewInfoStore.removeViewHolder(vh); } /** @@ -6834,13 +7288,14 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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(); @@ -7897,6 +8484,59 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro return false; } + /** + * 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 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; + } + + 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. */ @@ -8077,16 +8717,12 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro /** - * An OnScrollListener can be set on a RecyclerView to receive messages - * when a scrolling event has occurred on that RecyclerView. + * 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() * - * @see RecyclerView#setOnScrollListener(OnScrollListener) and - * RecyclerView#addOnScrollListener(OnScrollListener) - * - * 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro */ 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro */ 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro // 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro mFlags = mFlags & ~FLAG_IGNORE; } - void setScrapContainer(Recycler recycler) { + void setScrapContainer(Recycler recycler, boolean isChangeScrap) { mScrapContainer = recycler; + mInChangeScrap = isChangeScrap; } boolean isInvalid() { @@ -8456,10 +9114,6 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro return (mFlags & FLAG_UPDATE) != 0; } - boolean isChanged() { - return (mFlags & FLAG_CHANGED) != 0; - } - boolean isBound() { return (mFlags & FLAG_BOUND) != 0; } @@ -8563,16 +9217,18 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * @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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } } - 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener { @Override - public void onRemoveFinished(ViewHolder item) { + public void onAnimationFinished(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) { - 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * * 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro 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 @@ -9804,220 +10446,280 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro mListener = listener; } + /** + * 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) + */ + public @NonNull ItemHolderInfo recordPostLayoutInformation(@NonNull State state, + @NonNull ViewHolder viewHolder) { + return obtainHolderInfo().setFrom(viewHolder); + } + + /** + * 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. + */ + public abstract boolean animateDisappearance(@NonNull ViewHolder viewHolder, + @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo); + + /** + * 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)}. + * + * @return true if a later call to {@link #runPendingAnimations()} is requested, + * false otherwise. + */ + public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder, + @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); + + /** + * 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)}. + * + * @return true if a later call to {@link #runPendingAnimations()} is requested, + * false otherwise. + */ + public abstract boolean animatePersistence(@NonNull ViewHolder viewHolder, + @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); + + /** + * 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. + *

+ * 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. + */ + public abstract boolean animateChange(@NonNull ViewHolder oldHolder, + @NonNull ViewHolder newHolder, + @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); + + @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; + } + /** * 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. + * 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. */ abstract public void runPendingAnimations(); - /** - * 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 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 - * @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 - */ - public final void dispatchRemoveFinished(ViewHolder item) { - onRemoveFinished(item); - if (mListener != null) { - mListener.onRemoveFinished(item); - } - } - - /** - * Method to be called by subclasses when a move animation is done. - * - * @param item The item which has been moved - */ - public final void dispatchMoveFinished(ViewHolder item) { - onMoveFinished(item); - if (mListener != null) { - mListener.onMoveFinished(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); - if (mListener != null) { - mListener.onAddFinished(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. - */ - public final void dispatchChangeFinished(ViewHolder item, boolean oldItem) { - onChangeFinished(item, oldItem); - if (mListener != null) { - mListener.onChangeFinished(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); - } - /** * 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. + * 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 for which an animation should be stopped. */ @@ -10029,9 +10731,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * 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. + * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished + * animation since the animations are effectively done when this method is called. */ abstract public void endAnimations(); @@ -10044,6 +10745,82 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro */ abstract public boolean isRunning(); + /** + * 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 viewHolder The ViewHolder whose animation is finished. + * @see #onAnimationFinished(ViewHolder) + */ + public final void dispatchAnimationFinished(ViewHolder viewHolder) { + onAnimationFinished(viewHolder); + if (mListener != null) { + mListener.onAnimationFinished(viewHolder); + } + } + + /** + * Called after {@link #dispatchAnimationFinished(ViewHolder)} is called by the + * ItemAniamtor. + * + * @param viewHolder The ViewHolder whose animation is finished. There might still be other + * animations running on this ViewHolder. + * @see #dispatchAnimationFinished(ViewHolder) + */ + public void onAnimationFinished(ViewHolder viewHolder) { + } + + /** + * 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 viewHolder The ViewHolder whose animation is starting. + * @see #onAnimationStarted(ViewHolder) + */ + public final void dispatchAnimationStarted(ViewHolder viewHolder) { + onAnimationStarted(viewHolder); + } + + /** + * Called when a new animation is started on the given ViewHolder. + * + * @param viewHolder The ViewHolder which started animating. Note that the ViewHolder + * might already be animating and this might be another animation. + * @see #dispatchAnimationStarted(ViewHolder) + */ + public void onAnimationStarted(ViewHolder viewHolder) { + + } + /** * Like {@link #isRunning()}, this method returns whether there are any item * animations currently running. Addtionally, the listener passed in will be called @@ -10073,15 +10850,58 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } /** - * 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); } /** @@ -10096,6 +10916,28 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro mFinishedListeners.clear(); } + /** + * 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. + * + * @return A new {@link ItemHolderInfo}. + */ + public ItemHolderInfo obtainHolderInfo() { + return new ItemHolderInfo(); + } + + /** + * 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. + */ + interface ItemAnimatorListener { + void onAnimationFinished(ViewHolder item); + } + /** * 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 @@ -10108,105 +10950,79 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } /** - * 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. + * 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 onRemoveStarting(ViewHolder item) {} + public static class ItemHolderInfo { - /** - * 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) {} + /** + * The left edge of the View (excluding decorations) + */ + public int left; - /** - * 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. - */ - public void onAddStarting(ViewHolder item) {} + /** + * The top edge of the View (excluding decorations) + */ + public int top; - /** - * 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 right edge of the View (excluding decorations) + */ + public int right; - /** - * 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 bottom edge of the View (excluding decorations) + */ + public int bottom; - /** - * 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 change flags that were passed to + * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)}. + */ + @AdapterChanges + public int changeFlags; - /** - * 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) {} + public ItemHolderInfo() { + } - /** - * 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) {} + /** + * 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); + } - } - - /** - * 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; - - 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 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * 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 class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * * @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 9ffd89f22..dc3b90194 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.support.v4.view.accessibility.AccessibilityNodeInfoCompat; 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 998252584..4dd6308db 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 @@ package org.telegram.messenger.support.widget; 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 000000000..2eb4cce5d --- /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 59e7f59f5..dd24ad425 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.View; 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 class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { } }; + /** + * 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 class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { public StaggeredGridLayoutManager(int spanCount, int orientation) { mOrientation = orientation; setSpanCount(spanCount); + setAutoMeasureEnabled(mGapStrategy != GAP_HANDLING_NONE); } /** @@ -358,10 +374,16 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { 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 class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { + "or GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS"); } mGapStrategy = gapStrategy; + setAutoMeasureEnabled(mGapStrategy != GAP_HANDLING_NONE); requestLayout(); } @@ -541,8 +564,35 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { 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 class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { } } 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 class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { fill(recycler, mLayoutState, state); } + repositionToWrapContentIfNecessary(); + if (getChildCount() > 0) { if (mShouldReverseLayout) { fixEndGap(recycler, state, true); @@ -618,14 +671,16 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { 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 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { 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 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { 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 class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { 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 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { 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 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { 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 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { 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 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { 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 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { 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 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { ? 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 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { } else { addView(view, 0); } - measureChildWithDecorationsAndMargin(view, lp); + measureChildWithDecorationsAndMargin(view, lp, false); final int start; final int end; @@ -1488,13 +1612,22 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { 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 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { 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 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { } 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 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { layoutDir = LAYOUT_START; referenceChildPosition = getFirstChildPosition(); } + mLayoutState.mRecycle = true; updateLayoutState(referenceChildPosition, state); setLayoutStateDirection(layoutDir); mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection; @@ -1982,8 +2126,13 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { @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 class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { 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 class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { 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 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { } } - // 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 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { } 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 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { 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 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { return mGapPerSpan == null ? 0 : mGapPerSpan[spanIndex]; } - public void invalidateSpanGaps() { - mGapPerSpan = null; - } - @Override public int describeContents() { return 0; @@ -2671,7 +2906,10 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { } } - 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 class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { 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 000000000..923319f4e --- /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 3aece7ccb..4d458fed8 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 android.support.v4.view.ViewCompat; 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.VelocityTracker; 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 class ItemTouchHelper extends RecyclerView.ItemDecoration 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 class ItemTouchHelper extends RecyclerView.ItemDecoration 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 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration /** * 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 class ItemTouchHelper extends RecyclerView.ItemDecoration } mRecyclerView = recyclerView; if (mRecyclerView != null) { + final Resources resources = recyclerView.getResources(); + mSwipeEscapeVelocity = AndroidUtilities.dp(120); + mMaxSwipeVelocity = AndroidUtilities.dp(800); setupCallbacks(); } } @@ -874,7 +890,6 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration anim.cancel(); } mRecoverAnimations.remove(i); - anim.mViewHolder.setIsRecyclable(true); return anim.mAnimationType; } } @@ -1010,7 +1025,7 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration /** * 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 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration 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 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration 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 class ItemTouchHelper extends RecyclerView.ItemDecoration /** * 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 class ItemTouchHelper extends RecyclerView.ItemDecoration } /** - * 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 class ItemTouchHelper extends RecyclerView.ItemDecoration 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 class ItemTouchHelper extends RecyclerView.ItemDecoration * @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 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration 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 class ItemTouchHelper extends RecyclerView.ItemDecoration * 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 class ItemTouchHelper extends RecyclerView.ItemDecoration @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 f0d631f5d..c0e891598 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 android.support.v4.view.ViewCompat; 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 @@ class ItemTouchUIUtilImpl { @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 @@ class ItemTouchUIUtilImpl { 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 @@ class ItemTouchUIUtilImpl { @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 @@ class ItemTouchUIUtilImpl { @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 a9614e1b4..22762d7d0 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 class Track { } 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 class Track { } 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 ee31d06b5..09729b980 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 class TLRPC { } } + 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 class TLRPC { } } - public static class auth_SentCode extends TLObject { - public boolean phone_registered; - public String phone_code_hash; - public int send_call_timeout; - public boolean is_password; + public static class TL_auth_sentCode extends TLObject { + public static int constructor = 0x5e002502; - 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 int flags; + public boolean phone_registered; + public auth_SentCodeType type; + public String phone_code_hash; + public auth_CodeType next_type; + public int timeout; + + 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); + 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); - } - } - - 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); - 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); + if ((flags & 2) != 0) { + next_type.serializeToStream(stream); + } + if ((flags & 4) != 0) { + stream.writeInt32(timeout); + } } } @@ -3388,19 +3490,21 @@ public class TLRPC { 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 0x98e81d3a: + result = new TL_botInfo(); break; case 0x9cf585d: - result = new TL_botInfo(); + result = new TL_botInfo_layer48(); break; } if (result == null && exception) { @@ -3413,7 +3517,7 @@ public class TLRPC { } } - 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 class TLRPC { } 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 class TLRPC { 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 class TLRPC { 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 class TLRPC { 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,6 +4018,223 @@ public class TLRPC { } } + 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) { + 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); + 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); + 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); + 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); + } + } + } + + public static class TL_chatFull extends ChatFull { + public static int constructor = 0x2e02a614; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = stream.readInt32(exception); + participants = ChatParticipants.TLdeserialize(stream, stream.readInt32(exception), 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); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(id); + participants.serializeToStream(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); + } + } + } + + public static class TL_channelFull_layer48 extends TL_channelFull { + public static int constructor = 0x9e341ddf; + + + 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); + 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); + } + } + + 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); + 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); + } + } + } + public static class TL_channelFull_old extends TL_channelFull { public static int constructor = 0xfab31aa3; @@ -3922,132 +4285,6 @@ public class TLRPC { } } - public static class TL_chatFull extends ChatFull { - public static int constructor = 0x2e02a614; - - - public void readParams(AbstractSerializedData stream, boolean exception) { - id = stream.readInt32(exception); - participants = ChatParticipants.TLdeserialize(stream, stream.readInt32(exception), 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); - } - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt32(id); - participants.serializeToStream(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); - } - } - } - - public static class TL_channelFull extends ChatFull { - public static int constructor = 0x9e341ddf; - - - 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); - 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); - } - } - - 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); - 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); - } - } - } - public static class TL_inputPeerNotifySettings extends TLObject { public static int constructor = 0x38935eb2; @@ -5772,6 +6009,9 @@ public class TLRPC { 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 class TLRPC { } } + 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 class TLRPC { 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 class TLRPC { case 0x98a12b4b: result = new TL_updateChannelMessageViews(); break; - case 0x60946422: + case 0xeb0467fb: result = new TL_updateChannelTooLong(); break; case 0x382dd3e4: @@ -8232,6 +8482,9 @@ public class TLRPC { 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 class TLRPC { } 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 class TLRPC { } } + 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 class TLRPC { 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 class TLRPC { 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 class TLRPC { 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 class TLRPC { } } + 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 class TLRPC { } } - 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 class TLRPC { } } + 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 class TLRPC { } } - 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 class TLRPC { } 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 class TLRPC { } 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 class TLRPC { } 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 class TLRPC { } 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 class TLRPC { } } + 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 class TLRPC { } } - 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 class TLRPC { } 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 class TLRPC { } } + 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 class TLRPC { } } + 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 class TLRPC { 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 class TLRPC { 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 class TLRPC { 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 class TLRPC { } } - 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 class TLRPC { 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 a3aa8f879..0bc3537ed 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 class ActionBarLayout extends FrameLayout { public void onAnimationEnd(Object animator) { onSlideAnimationEnd(backAnimation); } - - @Override - public void onAnimationCancel(Object animator) { - onSlideAnimationEnd(backAnimation); - } }); animatorSet.start(); animationInProgress = true; @@ -693,11 +688,6 @@ public class ActionBarLayout extends FrameLayout { public void onAnimationEnd(Object animation) { onAnimationEndCheck(false); } - - @Override - public void onAnimationCancel(Object animation) { - onAnimationEndCheck(false); - } }); currentAnimation.start(); } else { @@ -927,11 +917,6 @@ public class ActionBarLayout extends FrameLayout { 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 270add2ec..b201c06f1 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 class BottomSheet extends Dialog { } } } - - @Override - public void onAnimationCancel(Animator animation) { - onAnimationEnd(animation); - } }); animatorSet.start(); } @@ -706,11 +701,6 @@ public class BottomSheet extends Dialog { } }); } - - @Override - public void onAnimationCancel(Object animation) { - onAnimationEnd(animation); - } }); animatorSetProxy.start(); } @@ -745,11 +735,6 @@ public class BottomSheet extends Dialog { } }); } - - @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 55dcbc3e0..bb8e63493 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 class DrawerLayoutContainer extends FrameLayout { public void onAnimationEnd(Object animator) { onDrawerAnimationEnd(true); } - - @Override - public void onAnimationCancel(Object animator) { - onDrawerAnimationEnd(true); - } }); animatorSet.start(); currentAnimation = animatorSet; @@ -214,11 +209,6 @@ public class DrawerLayoutContainer extends FrameLayout { 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 bcee796f0..13f79f1ff 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 class ContactsAdapter extends BaseSectionsAdapter { } } 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 257dcc8f1..7901531d1 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 class DialogsAdapter extends RecyclerView.Adapter { } 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 9bb8a0642..e3233f911 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 class DialogsSearchAdapter extends BaseSearchAdapterRecycler { 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 b4d8dffe4..bed8d26b3 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 class MentionsAdapter extends BaseSearchAdapterRecycler { 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 class MentionsAdapter extends BaseSearchAdapterRecycler { 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 08ae3a64d..11a5651b2 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 class SearchAdapter extends BaseSearchAdapter { } 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 33d2252dc..efde21e42 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 class BlockedUsersActivity extends BaseFragment implements NotificationCe 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 4339be241..307d13c22 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 class CacheControlActivity extends BaseFragment { 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 799fa319f..41568c1f6 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 class BotHelpCell extends View { 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 56228aec3..bc73969a5 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 class ChatActionCell extends BaseCell { private int previousWidth = 0; private boolean imagePressed = false; + private boolean hasReplyMessage; + private MessageObject currentMessageObject; private ChatActionCellDelegate delegate; @@ -79,10 +81,11 @@ public class ChatActionCell extends BaseCell { } 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 @@ public class ChatActionCell extends BaseCell { 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 @@ public class ChatActionCell extends BaseCell { 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 ed1b12906..9c32ab0d1 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.R; 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 class ChatBaseCell extends BaseCell implements MediaController.FileDownlo 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 class ChatBaseCell extends BaseCell implements MediaController.FileDownlo 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 class ChatBaseCell extends BaseCell implements MediaController.FileDownlo 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 class ChatBaseCell extends BaseCell implements MediaController.FileDownlo } } 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 class ChatBaseCell extends BaseCell implements MediaController.FileDownlo 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 class ChatBaseCell extends BaseCell implements MediaController.FileDownlo } else { maxWidth = getMaxNameWidth() - AndroidUtilities.dp(22); } - if (!media && messageObject.contentType != 0) { + if (!mediaBackground) { maxWidth -= AndroidUtilities.dp(8); } @@ -772,7 +757,7 @@ public class ChatBaseCell extends BaseCell implements MediaController.FileDownlo 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 class ChatBaseCell extends BaseCell implements MediaController.FileDownlo 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 @@ public class ChatBaseCell extends BaseCell implements MediaController.FileDownlo 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 @@ public class ChatBaseCell extends BaseCell implements MediaController.FileDownlo avatarImage.draw(canvas); } - if (media) { + if (mediaBackground) { timePaint.setColor(0xffffffff); } else { if (currentMessageObject.isOutOwner()) { @@ -889,37 +876,37 @@ public class ChatBaseCell extends BaseCell implements MediaController.FileDownlo 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 @@ public class ChatBaseCell extends BaseCell implements MediaController.FileDownlo 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 @@ public class ChatBaseCell extends BaseCell implements MediaController.FileDownlo 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 @@ public class ChatBaseCell extends BaseCell implements MediaController.FileDownlo 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 @@ public class ChatBaseCell extends BaseCell implements MediaController.FileDownlo } 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 @@ public class ChatBaseCell extends BaseCell implements MediaController.FileDownlo } } - 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 @@ public class ChatBaseCell extends BaseCell implements MediaController.FileDownlo } 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 @@ public class ChatBaseCell extends BaseCell implements MediaController.FileDownlo } 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 @@ public class ChatBaseCell extends BaseCell implements MediaController.FileDownlo } } 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 @@ public class ChatBaseCell extends BaseCell implements MediaController.FileDownlo } } 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 @@ public class ChatBaseCell extends BaseCell implements MediaController.FileDownlo } } 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 8f4e8a54c..000000000 --- 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 b07d425a9..978d7c064 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 @@ package org.telegram.ui.Cells; 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 android.view.ViewStructure; 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); + } + } + + 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 { + if (link[0] == pressedLink) { + delegate.didPressedUrl(currentMessageObject, pressedLink, false); + resetPressedLink(1); + return true; + } + } + } + } + } 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; + } + if (!ignore) { + pressedLink = link[0]; + pressedLinkType = 3; + try { + int start = buffer.getSpanStart(pressedLink); + urlPath.setCurrentLayout(captionLayout, start); + captionLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink), urlPath); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + invalidate(); + return true; + } + } + } 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); + } + } + } 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); + } + } + 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; + } + } + } + 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 (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) { - 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; + if (currentMessageObject == null || !delegate.canPerformActions()) { + return super.onTouchEvent(event); + } - 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(); - } - } else { - resetPressedLink(); - } - } 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; - } - } 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); + boolean result = checkTextBlockMotionEvent(event); + if (!result) { + result = checkLinkPreviewMotionEvent(event); + } + if (!result) { + result = chechCaptionMotionEvent(event); + } + if (!result) { + result = checkPhotoImageMotionEvent(event); + } - 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(); - } - } catch (Exception e) { - resetPressedLink(); - FileLog.e("tmessages", e); - } - } - } - } 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); - } - 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; - invalidate(); - } - } - } - } else { - resetPressedLink(); - } - } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { - resetPressedLink(); - } - } else { - resetPressedLink(); + 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 class ChatMessageCell extends ChatBaseCell { 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 class ChatMessageCell extends ChatBaseCell { 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 @@ public class ChatMessageCell extends ChatBaseCell { @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 @@ public class ChatMessageCell extends ChatBaseCell { 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 setMessageObject(MessageObject messageObject) { - boolean dataChanged = currentMessageObject == messageObject && (isUserDataChanged() || photoNotSet); - if (currentMessageObject != messageObject || dataChanged) { - if (currentMessageObject != messageObject) { - firstVisibleBlockNum = 0; - lastVisibleBlockNum = 0; + 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))); } - drawLinkImageView = false; + 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 (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); + if (messageObject.type == 0) { + drawForwardedName = true; + mediaBackground = false; - super.setMessageObject(messageObject); - - backgroundWidth = messageObject.textWidth; - totalHeight = messageObject.textHeight + AndroidUtilities.dp(19.5f) + namesOffset; - - 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); - } - } - 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)); - 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); + maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(80); + drawName = messageObject.messageOwner.to_id.channel_id != 0 && !messageObject.isOutOwner(); } } - 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); + backgroundWidth = maxWidth; + availableTimeWidth = backgroundWidth - AndroidUtilities.dp(29); + + super.setMessageObject(messageObject); + + backgroundWidth = messageObject.textWidth; + totalHeight = messageObject.textHeight + AndroidUtilities.dp(19.5f) + namesOffset; + + 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; + 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 { - restLines = restLinesCount; - titleLayout = generateStaticLayout(webPage.title, replyNamePaint, linkPreviewMaxWidth, linkPreviewMaxWidth - AndroidUtilities.dp(48 + 2), restLinesCount, 4); - restLinesCount -= titleLayout.getLineCount(); + linkPreviewMaxWidth = AndroidUtilities.getMinTabletSide() - 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); - } + } 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 { + linkPreviewMaxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(80); + } + } + 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); + } + } + + 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); + } + } + + 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 = titleLayout.getWidth() - lineLeft; + width = authorLayout.getWidth() - lineLeft; + authorIsRTL = true; } else { - width = (int) Math.ceil(titleLayout.getLineWidth(a)); - } - if (a < restLines || lineLeft != 0 && isSmallImage) { - width += AndroidUtilities.dp(48 + 2); + 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); } - } 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)); - } - 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); + } + int height = descriptionLayout.getLineBottom(descriptionLayout.getLineCount() - 1); + linkPreviewHeight += height; + totalHeight += height; - 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); + 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; + 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 { + 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); + } + + 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)); - } - 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); + 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; + } } } - maxChildWidth = Math.max(maxChildWidth, width + additinalWidth); + 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; + } + + 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); + } + } 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); + } + } + } + drawPhotoImage = true; + + if (webPage.site_name != null) { + if (webPage.site_name.toLowerCase().equals("instagram") && webPage.type != null && webPage.type.equals("video")) { + isInstagram = true; + } + } + + 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); + } + } 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; + + if (messageObject.audioProgress != 2 && !MediaController.getInstance().canAutoplayGifs() && messageObject.type == 8) { + messageObject.audioProgress = 1; } - 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; + photoImage.setAllowStartAnimation(messageObject.audioProgress == 0); - if (webPage.document != null && MessageObject.isGifDocument(webPage.document)) { - if (!MediaController.getInstance().canAutoplayGifs()) { - messageObject.audioProgress = 1; + 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); + 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; + } + + 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); + } + + 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); - if (currentPhotoObjectThumb == currentPhotoObject) { + 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 (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); + } + + if (messageObject.caption != null) { + mediaBackground = false; + } + + currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); + + int w = 0; + int h = 0; + + if (currentPhotoObject != null && currentPhotoObject == currentPhotoObjectThumb) { currentPhotoObjectThumb = 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); - } - - maxChildWidth = Math.max(maxChildWidth, maxPhotoWidth + additinalWidth); - currentPhotoObject.size = -1; - if (currentPhotoObjectThumb != null) { - currentPhotoObjectThumb.size = -1; - } - - int width; - int height; - if (smallImage) { - width = height = maxPhotoWidth; - } 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; - } - } - } - 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; - } - - linkImageView.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 (isGifDocument) { - 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; - 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); - } - } 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; - 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); + 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 { - linkImageView.setImageBitmap((Drawable) null); + w = AndroidUtilities.dp(100); } } - } - 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 (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 (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); + 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; + } } - 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); - } - } else { - linkImageView.setImageBitmap((Drawable) null); - } - 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 (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); + } + } + + 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((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); + } } + super.setMessageObject(messageObject); + + 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 @@ public class ChatMessageCell extends ChatBaseCell { 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) { - 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; - } + photoImage.setPressed(isDrawSelectedBackground()); + photoImage.setVisible(!PhotoViewer.getInstance().isShowingImage(currentMessageObject), false); + radialProgress.setHideCurrentDrawable(false); - if (firstVisibleBlockNum >= 0) { - for (int a = firstVisibleBlockNum; a <= lastVisibleBlockNum; a++) { - if (a >= currentMessageObject.textLayoutBlocks.size()) { - break; + 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(); } - MessageObject.TextLayoutBlock block = currentMessageObject.textLayoutBlocks.get(a); + } + + 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 (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 { + imageDrawn = photoImage.draw(canvas); + drawTime = photoImage.getVisible(); + 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); + } + } + + 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 (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 (hasLinkPreview) { - int startY = textY + currentMessageObject.textHeight + AndroidUtilities.dp(8); - int linkPreviewY = startY; - int smallImageStartY = 0; - replyLinePaint.setColor(currentMessageObject.isOutOwner() ? 0xff8dc97a : 0xff6c9fd2); + if (currentMessageObject.type == 3) { + setDrawableBounds(ResourceLoader.videoIconDrawable, photoImage.getImageX() + AndroidUtilities.dp(8), photoImage.getImageY() + AndroidUtilities.dp(7.5f)); + ResourceLoader.videoIconDrawable.draw(canvas); + } - canvas.drawRect(textX, linkPreviewY - AndroidUtilities.dp(3), textX + AndroidUtilities.dp(2), linkPreviewY + linkPreviewHeight + AndroidUtilities.dp(3), replyLinePaint); - - 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); - } - 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 (drawLinkImageView) { - if (linkPreviewY != startY) { - linkPreviewY += AndroidUtilities.dp(2); - } - - if (isSmallImage) { - linkImageView.setImageCoords(textX + backgroundWidth - AndroidUtilities.dp(77), smallImageStartY, linkImageView.getImageWidth(), linkImageView.getImageHeight()); - } 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)); - } - } - 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 (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); + if (infoLayout != null) { canvas.save(); - canvas.translate(x, y); - durationLayout.draw(canvas); + canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + AndroidUtilities.dp(captionLayout.getLineCount() * 16 + 5)); + infoLayout.draw(canvas); canvas.restore(); } } + } else if (currentMessageObject.type == 8) { + if (captionLayout != null) { + canvas.save(); + canvas.translate(captionX = photoImage.getImageX() + AndroidUtilities.dp(5), captionY = photoImage.getImageY() + photoImage.getImageHeight() + AndroidUtilities.dp(6)); + if (pressedLink != null) { + canvas.drawPath(urlPath, urlPaint); + } + try { + captionLayout.draw(canvas); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + canvas.restore(); + } + } 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 (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); + } + + 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(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; + 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 (isDocument == 2 && !photoImage.isAllowStartAnimation()) { + buttonState = 2; } else { - buttonState = 0; + buttonState = -1; + } + radialProgress.setBackground(getDrawableForCurrentState(), false, animated); + invalidate(); + } + } else { + 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 { - progressVisible = true; - buttonState = 1; - Float progress = ImageLoader.getInstance().getFileProgress(fileName); - setProgress = progress != null ? progress : 0; + 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(); + } } - radialProgress.setProgress(setProgress, false); - radialProgress.setBackground(getDrawableForCurrentState(), progressVisible, animated); - invalidate(); - } else { - MediaController.getInstance().removeLoadingFileObserver(this); - if (isGifDocument && !linkImageView.isAllowStartAnimation()) { - buttonState = 2; - } else { - buttonState = -1; + } + } + + 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 @@ public class ChatMessageCell extends ChatBaseCell { 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 class ChatMessageCell extends ChatBaseCell { @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 class ChatMessageCell extends ChatBaseCell { } } + @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 37f2fae98..4504abbcc 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.BitmapDrawable; 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 class DrawerProfileCell extends FrameLayout { 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 6f2257ef4..c7d9a88ab 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 @@ import org.telegram.messenger.R; 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 c9a9fee64..37b6f197e 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 class TextBlockCell extends FrameLayoutFixed { 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 7c343a931..46c40454a 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 @@ import org.telegram.ui.Components.Switch; 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 class TextCheckCell extends FrameLayoutFixed { 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 class TextCheckCell extends FrameLayoutFixed { @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 class TextCheckCell extends FrameLayoutFixed { } 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 1d99c59ea..7aa88dc29 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 class TextSettingsCell extends FrameLayout { 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 547e8e348..ecc6b8ae7 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 class UserCell extends FrameLayout { 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 class UserCell extends FrameLayout { 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 2a3f30834..3af498a7f 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 @@ public class ChangeNameActivity extends BaseFragment { 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 d4ab922a9..037b50730 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.view.inputmethod.EditorInfo; 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.messenger.AnimationCompat.ViewProxy; 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 @@ import java.util.TimerTask; 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 class ChangePhoneActivity extends BaseFragment { }); 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, 1); + views[2] = new LoginActivitySmsView(context, 2); + views[3] = new LoginActivitySmsView(context, 3); + views[4] = new LoginActivitySmsView(context, 4); - 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); + 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 class ChangePhoneActivity extends BaseFragment { 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 class ChangePhoneActivity extends BaseFragment { } 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 class ChangePhoneActivity extends BaseFragment { } } + 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 class ChangePhoneActivity extends BaseFragment { 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 class ChangePhoneActivity extends BaseFragment { 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 class ChangePhoneActivity extends BaseFragment { 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 class ChangePhoneActivity extends BaseFragment { @Override public void afterTextChanged(Editable editable) { if (ignoreOnTextChange) { - ignoreOnTextChange = false; return; } ignoreOnTextChange = true; @@ -414,6 +471,7 @@ public class ChangePhoneActivity extends BaseFragment { phoneField.setSelection(phoneField.length()); } } + ignoreOnTextChange = false; } }); codeField.setOnEditorActionListener(new TextView.OnEditorActionListener() { @@ -438,7 +496,7 @@ public class ChangePhoneActivity extends BaseFragment { 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 class ChangePhoneActivity extends BaseFragment { 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 class ChangePhoneActivity extends BaseFragment { 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 class ChangePhoneActivity extends BaseFragment { String hint = phoneFormatMap.get(code); phoneField.setHintText(hint != null ? hint.replace('X', '–') : null); countryState = 0; + ignoreOnTextChange = false; } } @@ -617,6 +676,7 @@ public class ChangePhoneActivity extends BaseFragment { ignoreOnTextChange = true; String str = countriesArray.get(i); codeField.setText(countriesMap.get(str)); + ignoreOnTextChange = false; } @Override @@ -626,9 +686,46 @@ public class ChangePhoneActivity extends BaseFragment { @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 class ChangePhoneActivity extends BaseFragment { 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 class ChangePhoneActivity extends BaseFragment { 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 class ChangePhoneActivity extends BaseFragment { } 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 class ChangePhoneActivity extends BaseFragment { 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 class ChangePhoneActivity extends BaseFragment { 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 class ChangePhoneActivity extends BaseFragment { if (ignoreOnTextChange) { return; } - if (codeField.length() == 5) { + if (length != 0 && codeField.length() == length) { onNextPressed(); } } @@ -797,55 +938,133 @@ public class ChangePhoneActivity extends BaseFragment { 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 class ChangePhoneActivity extends BaseFragment { 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 class ChangePhoneActivity extends BaseFragment { @Override public void run() { if (codeTime <= 1000) { + problemText.setVisibility(VISIBLE); destroyCodeTimer(); } } @@ -922,7 +1188,7 @@ public class ChangePhoneActivity extends BaseFragment { private void destroyCodeTimer() { try { - synchronized(timerSync) { + synchronized (timerSync) { if (codeTimer != null) { codeTimer.cancel(); codeTimer = null; @@ -941,7 +1207,10 @@ public class ChangePhoneActivity extends BaseFragment { 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 class ChangePhoneActivity extends BaseFragment { 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 class ChangePhoneActivity extends BaseFragment { private void destroyTimer() { try { - synchronized(timerSync) { + synchronized (timerSync) { if (timeTimer != null) { timeTimer.cancel(); timeTimer = null; @@ -998,9 +1285,14 @@ public class ChangePhoneActivity extends BaseFragment { 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 class ChangePhoneActivity extends BaseFragment { 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 class ChangePhoneActivity extends BaseFragment { 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 class ChangePhoneActivity extends BaseFragment { @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 32afbcb16..825fb18e1 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.MessagesStorage; 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 class ChannelEditActivity extends BaseFragment implements AvatarUpdater.A } } } - 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 class ChannelEditActivity extends BaseFragment implements AvatarUpdater.A 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 class ChannelEditActivity extends BaseFragment implements AvatarUpdater.A 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 class ChannelEditActivity extends BaseFragment implements AvatarUpdater.A 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 class ChannelEditActivity extends BaseFragment implements AvatarUpdater.A } }); - 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 class ChannelEditActivity extends BaseFragment implements AvatarUpdater.A 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 class ChannelEditActivity extends BaseFragment implements AvatarUpdater.A 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 class ChannelEditActivity extends BaseFragment implements AvatarUpdater.A } }); - 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)); + ShadowSectionCell sectionCell = new ShadowSectionCell(context); + sectionCell.setSize(20); + linearLayout.addView(sectionCell, 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)); - } - - /*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));*/ - - if (!currentChat.megagroup && currentChat.creator) { + if (currentChat.megagroup || !currentChat.megagroup) { 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); - } - }); + 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.setBackgroundResource(R.drawable.greydivider); - infoCell.setText(LocaleController.getString("ChannelSignMessagesInfo", R.string.ChannelSignMessagesInfo)); - 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)); + + frameLayout = new FrameLayoutFixed(context); + frameLayout.setBackgroundColor(0xffffffff); + linearLayout.addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + 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); + } + }); + + 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 class ChannelEditActivity extends BaseFragment implements AvatarUpdater.A } }); - 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 class ChannelEditActivity extends BaseFragment implements AvatarUpdater.A 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 class ChannelEditActivity extends BaseFragment implements AvatarUpdater.A 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 class ChannelEditActivity extends BaseFragment implements AvatarUpdater.A } } - 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("ChannelPublicLimitReached", R.string.ChannelPublicLimitReached)); - } else { - checkTextView.setText(LocaleController.getString("LinkInUse", R.string.LinkInUse)); - } - checkTextView.setTextColor(0xffcf3030); - lastNameAvailable = false; - } - } - } - }); - } - }, ConnectionsManager.RequestFlagFailOnServerErrors); - } - }; - AndroidUtilities.runOnUIThread(checkRunnable, 300); - } - return true; + public void setInfo(TLRPC.ChatFull chatFull) { + info = chatFull; } - private void showErrorAlert(String error) { - if (getParentActivity() == null) { + 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 { + typeCell.setTextAndValue(LocaleController.getString("ChannelType", R.string.ChannelType), type, false); + } + + if (currentChat.creator && (info == null || info.can_set_username)) { + typeCell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Bundle args = new Bundle(); + args.putInt("chat_id", chatId); + ChannelEditTypeActivity fragment = new ChannelEditTypeActivity(args); + fragment.setInfo(info); + presentFragment(fragment); + } + }); + typeCell.setTextColor(0xff212121); + typeCell.setTextValueColor(0xff2f8cc9); + } else { + typeCell.setOnClickListener(null); + typeCell.setTextColor(0xffa8a8a8); + typeCell.setTextValueColor(0xffa8a8a8); + } + } + + 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 000000000..a389ec5cf --- /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 736be7769..fe51b1ce5 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 class ChannelUsersActivity extends BaseFragment implements NotificationCe 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 9cd8bbc00..899037f59 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.MotionEvent; 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.Utilities; 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.ChatAudioCell; 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not cantDeleteMessagesCount = 0; chatActivityEnterView.setEditinigMessageObject(null, false); actionBar.hideActionMode(); + updatePinnedMessageView(true); updateVisibleRows(); } else { finishFragment(); @@ -724,8 +742,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } } 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } cantDeleteMessagesCount = 0; actionBar.hideActionMode(); + updatePinnedMessageView(true); updateVisibleRows(); } else if (id == chat_menu_attach) { if (getParentActivity() == null) { @@ -1133,7 +1127,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not searchItem.getSearchField().requestFocus(); AndroidUtilities.showKeyboard(searchItem.getSearchField()); } - }, 300); //TODO find a better way to open keyboard + }, 300); } @Override @@ -1158,6 +1152,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not }); 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not }); 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not @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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not firstLoading = true; loading = true; waitingForImportantLoad = false; + waitingForReplyMessageLoad = false; startLoadFromMessageId = 0; last_message_id = 0; needSelectFromMessageId = false; @@ -3328,21 +3399,24 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } 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 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } if (loadsCount <= 2) { - if (messages.size() >= 20 || !isCache) { + if (!isCache) { updateSpamView(); } } @@ -4438,13 +4546,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } if ((updateMask & MessagesController.UPDATE_MASK_USER_PHONE) != 0) { updateContactStatus(); - updateSpamView(); } } else if (id == NotificationCenter.didReceivedNewMessages) { long did = (Long) args[0]; @@ -4647,8 +4754,26 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } } + 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } cantDeleteMessagesCount = 0; actionBar.hideActionMode(); + updatePinnedMessageView(true); if (botButtons != null) { botButtons = null; @@ -5378,7 +5522,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not userBlocked = MessagesController.getInstance().blockedUsers.contains(currentUser.id); if (oldValue != userBlocked) { updateBottomOverlay(); - updateSpamView(); } } } else if (id == NotificationCenter.FileNewChunkAvailable) { @@ -5449,6 +5592,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not updateVisibleRows(); } } + } else if (id == NotificationCenter.peerSettingsDidLoaded) { + long did = (Long) args[0]; + if (did == dialog_id) { + updateSpamView(); + } } } @@ -5639,14 +5798,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not 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); - } - } - } - 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; - } + show = false; + break; } } } } - if (reportSpamUser != null) { - if (reportSpamView.getVisibility() != View.VISIBLE) { - reportSpamView.setVisibility(View.VISIBLE); + if (!show) { + if (reportSpamView.getTag() == null) { reportSpamView.setTag(1); - chatListView.setTopGlowOffset(AndroidUtilities.dp(48)); - chatListView.setPadding(0, AndroidUtilities.dp(52), 0, AndroidUtilities.dp(3)); + 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); + } } - } 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 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not || 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } paused = true; wasPaused = true; - NotificationsController.getInstance().setOpennedDialogId(0); + NotificationsController.getInstance().setOpenedDialogId(0); if (chatActivityEnterView != null) { chatActivityEnterView.onPause(); if (!chatActivityEnterView.isEditingMessage()) { @@ -6162,6 +6460,141 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } } + 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 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } 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 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } 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 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } actionBar.showActionMode(); + updatePinnedMessageView(true); if (Build.VERSION.SDK_INT >= 11) { AnimatorSetProxy animatorSet = new AnimatorSetProxy(); @@ -6399,26 +6858,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } 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 = MessagesController.getInstance().saveGif(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) { - - } - }); showGifHint(); - chatActivityEnterView.addRecentGif(searchImage); } else if (option == 12) { if (getParentActivity() == null) { @@ -6620,6 +7049,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } cantDeleteMessagesCount = 0; actionBar.hideActionMode(); + updatePinnedMessageView(true); } if (did != dialog_id) { @@ -6687,6 +7166,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not showReplyPanel(true, null, fmessages, null, false, AndroidUtilities.isTablet()); if (AndroidUtilities.isTablet()) { actionBar.hideActionMode(); + updatePinnedMessageView(true); } updateVisibleRows(); } @@ -6720,6 +7204,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } chatActivityEnterView.setEditinigMessageObject(null, false); actionBar.hideActionMode(); + updatePinnedMessageView(true); cantDeleteMessagesCount = 0; updateVisibleRows(); return false; @@ -6786,6 +7271,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } + 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } ((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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } @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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } @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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } @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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } } }); - 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 class ChatActivity extends BaseFragment implements NotificationCenter.Not } }); } - + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); return new Holder(view); } @@ -7526,7 +8022,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not 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 c77407c64..ed2c6c4cf 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.app.Dialog; 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.MessagesStorage; 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 class AlertsCreator { 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 7a8bda10b..299508c7c 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 @@ package org.telegram.ui.Components; 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 class ChatActivityEnterView extends FrameLayoutFixed implements Notificat 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 class ChatActivityEnterView extends FrameLayoutFixed implements Notificat 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 class ChatActivityEnterView extends FrameLayoutFixed implements Notificat 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 class ChatActivityEnterView extends FrameLayoutFixed implements Notificat 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 class ChatActivityEnterView extends FrameLayoutFixed implements Notificat 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 class ChatActivityEnterView extends FrameLayoutFixed implements Notificat } else { Toast.makeText(parentActivity, LocaleController.getString("ChannelNotifyMembersInfoOn", R.string.ChannelNotifyMembersInfoOn), Toast.LENGTH_SHORT).show(); } + updateFieldHint(); } }); } @@ -802,11 +825,9 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat 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 @@ public class ChatActivityEnterView extends FrameLayoutFixed implements Notificat 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 class ChatActivityEnterView extends FrameLayoutFixed implements Notificat 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 class ChatActivityEnterView extends FrameLayoutFixed implements Notificat @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 class ChatActivityEnterView extends FrameLayoutFixed implements Notificat 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 class ChatActivityEnterView extends FrameLayoutFixed implements Notificat 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 7877474c2..63c0773bf 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.LocaleController; 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 class EmojiView extends FrameLayout implements NotificationCenter.Notific 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 class EmojiView extends FrameLayout implements NotificationCenter.Notific 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 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific private GifsAdapter gifsAdapter; private AdapterView.OnItemClickListener stickersOnItemClickListener; - private EmojiColorPickerView pickerView; private EmojiPopupWindow pickerViewPopup; private int popupWidth; @@ -531,8 +534,6 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific private int gifTabBum = -2; private boolean switchToGifTab; - - private int oldWidth; private int lastNotifyWidth; @@ -667,7 +668,7 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific return; } TLRPC.Document document = recentImages.get(position).document; - listener.onStickerSelected(document); + listener.onGifSelected(document); } }); gifsGridView.setOnItemLongClickListener(new RecyclerListView.OnItemLongClickListener() { @@ -966,6 +967,15 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific 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 class EmojiView extends FrameLayout implements NotificationCenter.Notific 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 class EmojiView extends FrameLayout implements NotificationCenter.Notific } } if (string.length() > 0) { - emojiUseHistory.put(string, Integer.parseInt(args2[1])); + emojiUseHistory.put(string, Utilities.parseInt(args2[1])); } } } @@ -1291,22 +1301,25 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific 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 class EmojiView extends FrameLayout implements NotificationCenter.Notific 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 class EmojiView extends FrameLayout implements NotificationCenter.Notific 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 930ad1917..f558d9d2d 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.util.AttributeSet; 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 @@ public class FrameLayoutFixed extends FrameLayout { 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 3c12182b8..be8516efa 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 class PhotoCropView extends FrameLayout { 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 class PhotoCropView extends FrameLayout { } 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 @@ public class PhotoCropView extends FrameLayout { } 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 class PhotoCropView extends FrameLayout { 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 2eb4445a1..c7825462e 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 class PhotoFilterView extends FrameLayout { 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 505cbb901..ffbf63466 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 class PhotoViewerCaptionEnterView extends FrameLayoutFixed implements Not 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 e955b8547..4b135739f 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 @@ public class SimpleTextView extends View { } else { offsetX = 0; } + offsetX += getPaddingLeft(); } } catch (Exception e) { //ignore @@ -94,7 +95,7 @@ public class SimpleTextView extends View { @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 @@ public class SimpleTextView extends View { 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 2f93be636..217e6b368 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 class Switch extends CompoundButton { 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 2b7e44622..6785b376d 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.ApplicationLoader; 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 class ContactsActivity extends BaseFragment implements NotificationCenter 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 class ContactsActivity extends BaseFragment implements NotificationCenter 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 class ContactsActivity extends BaseFragment implements NotificationCenter } 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 000000000..3af4bb4ca --- /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 15cd7859a..a6b256bfe 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 class DialogsActivity extends BaseFragment implements NotificationCenter. 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 class DialogsActivity extends BaseFragment implements NotificationCenter. 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 class DialogsActivity extends BaseFragment implements NotificationCenter. } } 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 class DialogsActivity extends BaseFragment implements NotificationCenter. } } } 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 class DialogsActivity extends BaseFragment implements NotificationCenter. 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 5339c3d00..469f469ff 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 class GroupCreateActivity extends BaseFragment implements NotificationCen 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 c2c220b7b..4cde07fd7 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 class GroupCreateFinalActivity extends BaseFragment implements Notificati @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 213d5155a..e42fabcfe 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 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa 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 class LaunchActivity extends Activity implements ActionBarLayout.ActionBa 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 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa 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 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa 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 class LaunchActivity extends Activity implements ActionBarLayout.ActionBa 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 class LaunchActivity extends Activity implements ActionBarLayout.ActionBa 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 class LaunchActivity extends Activity implements ActionBarLayout.ActionBa 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 class LaunchActivity extends Activity implements ActionBarLayout.ActionBa 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 class LaunchActivity extends Activity implements ActionBarLayout.ActionBa 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 class LaunchActivity extends Activity implements ActionBarLayout.ActionBa } 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 class LaunchActivity extends Activity implements ActionBarLayout.ActionBa } 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 class LaunchActivity extends Activity implements ActionBarLayout.ActionBa 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 51e5088b2..5c8e98130 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 class LocationActivity extends BaseFragment implements NotificationCenter 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 358bc1127..bb6491688 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.DialogInterface; 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.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.text.method.PasswordTransformationMethod; @@ -40,6 +43,7 @@ import android.view.inputmethod.EditorInfo; 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.ActionBar.BaseFragment; 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 @@ import java.util.TimerTask; 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 class LoginActivity extends BaseFragment { }); 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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { } } + @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 @@ public class LoginActivity extends BaseFragment { } 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 @@ public class LoginActivity extends BaseFragment { } } + @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 class LoginActivity extends BaseFragment { } 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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { } } - 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 LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { } if (codeField.length() != 0) { - AndroidUtilities.showKeyboard(phoneField); phoneField.requestFocus(); phoneField.setSelection(phoneField.length()); } else { - AndroidUtilities.showKeyboard(codeField); codeField.requestFocus(); } } @@ -752,17 +828,6 @@ public class LoginActivity extends BaseFragment { } } - @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 class LoginActivity extends BaseFragment { @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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { codeField.requestFocus(); } } - //NotificationCenter.getInstance().addObserver(this, NotificationCenter.didReceiveCall); } @Override @@ -904,6 +1005,32 @@ public class LoginActivity extends BaseFragment { 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 LoginActivity extends BaseFragment { private TextView timeText; private TextView problemText; private Bundle currentParams; + private ProgressView progressView; private Timer timeTimer; private Timer codeTimer; @@ -921,14 +1049,20 @@ public class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { if (ignoreOnTextChange) { return; } - if (codeField.length() == 5) { + if (length != 0 && codeField.length() == length) { onNextPressed(); } } @@ -983,6 +1130,11 @@ public class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { 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); + 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)); + 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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { 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); - AndroidUtilities.showKeyboard(codeField); - codeField.requestFocus(); + if (currentType != 3) { + AndroidUtilities.showKeyboard(codeField); + codeField.requestFocus(); + } else { + AndroidUtilities.hideKeyboard(codeField); + } 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); + 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); - } else { - timeText.setText(LocaleController.formatString("CallText", R.string.CallText, 1, 0)); - lastCurrentTime = System.currentTimeMillis(); - problemText.setVisibility(time < 1000 ? VISIBLE : GONE); + createCodeTimer(); } - - createTimer(); } private void createCodeTimer() { @@ -1137,14 +1392,17 @@ public class LoginActivity extends BaseFragment { } 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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { @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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { @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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { 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 class LoginActivity extends BaseFragment { 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 e3d856b3f..76553ab3f 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 class MediaActivity extends BaseFragment implements NotificationCenter.No } 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 class MediaActivity extends BaseFragment implements NotificationCenter.No 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 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No 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 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No 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 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No 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 f51a2ebbf..95c30acc1 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.Paint; 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 class PhotoCropActivity extends BaseFragment { 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 50e819baf..9d4b170ab 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 class PhotoViewer implements NotificationCenter.NotificationCenterDelegat }; 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 class PhotoViewer implements NotificationCenter.NotificationCenterDelegat 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 class PhotoViewer implements NotificationCenter.NotificationCenterDelegat containerView.invalidate(); } } + + @Override + public Bitmap getBitmap() { + return centerImage.getBitmap(); + } }); } @@ -2579,7 +2587,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat 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 class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } }); } - - @Override - public void onAnimationCancel(Object animation) { - onAnimationEnd(animation); - } }); transitionAnimationStartTime = System.currentTimeMillis(); AndroidUtilities.runOnUIThread(new Runnable() { @@ -3178,11 +3181,6 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } }); } - - @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 3ff965bbe..2dd66ce53 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 class PrivacyUsersActivity extends BaseFragment implements NotificationCe 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 57d5e9e10..16ebe2f88 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 class ProfileActivity extends BaseFragment implements NotificationCenter. 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 class ProfileActivity extends BaseFragment implements NotificationCenter. 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 class ProfileActivity extends BaseFragment implements NotificationCenter. if (currentChat.megagroup) { getChannelParticipants(true); } else { - participants = null; participantsMap = null; } NotificationCenter.getInstance().addObserver(this, NotificationCenter.chatInfoDidLoaded); @@ -259,6 +258,10 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } }; avatarUpdater.parentFragment = this; + + if (ChatObject.isChannel(currentChat)) { + MessagesController.getInstance().loadFullChat(chat_id, classGuid, true); + } } else { return false; } @@ -294,6 +297,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. 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 class ProfileActivity extends BaseFragment implements NotificationCenter. 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 class ProfileActivity extends BaseFragment implements NotificationCenter. } 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 class ProfileActivity extends BaseFragment implements NotificationCenter. 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 class ProfileActivity extends BaseFragment implements NotificationCenter. 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 class ProfileActivity extends BaseFragment implements NotificationCenter. } 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 class ProfileActivity extends BaseFragment implements NotificationCenter. } 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 class ProfileActivity extends BaseFragment implements NotificationCenter. } } } - 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 class ProfileActivity extends BaseFragment implements NotificationCenter. } } } - 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 class ProfileActivity extends BaseFragment implements NotificationCenter. 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 class ProfileActivity extends BaseFragment implements NotificationCenter. 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 class ProfileActivity extends BaseFragment implements NotificationCenter. @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 class ProfileActivity extends BaseFragment implements NotificationCenter. } 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 class ProfileActivity extends BaseFragment implements NotificationCenter. 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 class ProfileActivity extends BaseFragment implements NotificationCenter. 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 class ProfileActivity extends BaseFragment implements NotificationCenter. 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 class ProfileActivity extends BaseFragment implements NotificationCenter. currentChat = newChat; createActionBarMenu(); } - if (currentChat.megagroup && !byChannelUsers) { + if (currentChat.megagroup && (loadChannelParticipants || !byChannelUsers)) { getChannelParticipants(true); } } @@ -1421,6 +1441,15 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. 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 class ProfileActivity extends BaseFragment implements NotificationCenter. } 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 class ProfileActivity extends BaseFragment implements NotificationCenter. 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++; - } - - 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 (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; - } else if (status2 < 0 && status1 > 0 || status2 == 0 && status1 != 0) { - return 1; - } - return 0; + 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++; } - }); - } catch (Exception e) { - FileLog.e("tmessages", e); //TODO find crash - } + sortedUsers.add(a); + } - if (listAdapter != null) { - listAdapter.notifyItemRangeChanged(emptyRowChat2 + 1, sortedUsers.size()); + 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 (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; + } else if (status2 < 0 && status1 > 0 || status2 == 0 && status1 != 0) { + return 1; + } + return 0; + } + }); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + + if (listAdapter != null) { + listAdapter.notifyItemRangeChanged(emptyRowChat2 + 1, sortedUsers.size()); + } } } @@ -1773,14 +1793,10 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } 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 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. 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 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } } if (changed) { + updateOnlineCount(); updateRowsIds(); listAdapter.notifyDataSetChanged(); } @@ -1834,16 +1851,35 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } 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 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. 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 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. 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 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. 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 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. 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 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. 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 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } 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 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. 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 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } @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 class ProfileActivity extends BaseFragment implements NotificationCenter. } 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 class ProfileActivity extends BaseFragment implements NotificationCenter. }; 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 class ProfileActivity extends BaseFragment implements NotificationCenter. }); break; } + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); return new Holder(view); } @@ -2389,7 +2436,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. 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 class ProfileActivity extends BaseFragment implements NotificationCenter. } 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 class ProfileActivity extends BaseFragment implements NotificationCenter. 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 class ProfileActivity extends BaseFragment implements NotificationCenter. 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 000000000..e16918642 --- /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 edcc97be7..b7dbcd7cd 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 class SecretPhotoViewer implements NotificationCenter.NotificationCenterD 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 8c9e4d7cf..614c5bab7 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 class SetAdminsActivity extends BaseFragment implements NotificationCente } } 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 class SetAdminsActivity extends BaseFragment implements NotificationCente @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 5f389d771..afdfe696a 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 class SettingsActivity extends BaseFragment implements NotificationCenter 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 class SettingsActivity extends BaseFragment implements NotificationCenter supportSectionRow2 = rowCount++; askQuestionRow = rowCount++; telegramFaqRow = rowCount++; + privacyPolicyRow = rowCount++; if (BuildVars.DEBUG_VERSION) { sendLogsRow = rowCount++; clearLogsRow = rowCount++; @@ -269,7 +277,7 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter //contactsReimportRow = rowCount++; //contactsSortRow = rowCount++; - MessagesController.getInstance().loadFullUser(UserConfig.getCurrentUser(), classGuid); + MessagesController.getInstance().loadFullUser(UserConfig.getCurrentUser(), classGuid, true); return true; } @@ -454,6 +462,16 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter 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 class SettingsActivity extends BaseFragment implements NotificationCenter 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 class SettingsActivity extends BaseFragment implements NotificationCenter @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 class SettingsActivity extends BaseFragment implements NotificationCenter 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 class SettingsActivity extends BaseFragment implements NotificationCenter 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 class SettingsActivity extends BaseFragment implements NotificationCenter 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 class SettingsActivity extends BaseFragment implements NotificationCenter 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 class SettingsActivity extends BaseFragment implements NotificationCenter } 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 e2bfaaa22..7abf9ddf9 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 class StickerPreviewViewer { 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 56bfeafd8..1c6b00917 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 class StickersActivity extends BaseFragment implements NotificationCenter 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 000000000..ee4081296 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 000000000..2f6db99ed 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 a819fd9f1..625167422 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 a8d1b924c..a1f0b063c 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 000000000..52b4de8d6 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 000000000..d05f969e9 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 000000000..d676d01e4 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 e245bb9c5..b74069ad0 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 9acf06791..3c3317fce 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 000000000..62dcc7aee 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 000000000..25423fabb --- /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 000000000..b57ee1935 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 000000000..370787e2f 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 1c307e8e1..b03cd17af 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 526e860d9..11d506d7d 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 41dcd5a94..7fcaef929 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 56a18f14a..74270aae7 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 000000000..f97ecc3bf 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 000000000..a1866ba45 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 000000000..8724c05b1 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 d48a051a8..0fff9d390 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 b8633f749..41511be20 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 64b6e2e83..3097e16f1 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 d7f1b935c..b468befd5 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 000000000..d422603db 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 e3bcba53c..e659827f8 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 8f1bb60c8..ecb46923a 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 7868b705f..080418ecc 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 74d816424..9e80947ee 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 bcae59fba..45334c650 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 12ce89add..121577771 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 b0b9bd761..39682c9b0 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 77a6fee48..7a98dad6f 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 afd42f469..a0870484a 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 ae8892580..8046380c1 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 26aaecfb0..2c397bc2a 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 01520498d..08f287cfb 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 89ecdcf96..b0b8557e8 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