Update to 2.8.1
Thanks to https://github.com/DrKLO/Telegram/issues/738 for smart notifications
|
@ -13,11 +13,10 @@ repositories {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:support-v4:22.0.+'
|
||||
compile 'com.android.support:support-v4:22.1.+'
|
||||
compile 'com.google.android.gms:play-services:3.2.+'
|
||||
compile 'net.hockeyapp.android:HockeySDK:3.5.+'
|
||||
compile 'com.googlecode.mp4parser:isoparser:1.0.+'
|
||||
compile 'com.android.support:recyclerview-v7:+'
|
||||
}
|
||||
|
||||
android {
|
||||
|
@ -82,7 +81,7 @@ android {
|
|||
defaultConfig {
|
||||
minSdkVersion 8
|
||||
targetSdkVersion 22
|
||||
versionCode 492
|
||||
versionName "2.7.0"
|
||||
versionCode 521
|
||||
versionName "2.8.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ include $(BUILD_STATIC_LIBRARY)
|
|||
include $(CLEAR_VARS)
|
||||
LOCAL_PRELINK_MODULE := false
|
||||
LOCAL_STATIC_LIBRARIES := webp sqlite
|
||||
LOCAL_MODULE := tmessages.7
|
||||
LOCAL_MODULE := tmessages.8
|
||||
LOCAL_CFLAGS := -w -std=gnu99 -O2 -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
|
||||
|
|
|
@ -288,7 +288,7 @@ METHODDEF(void) my_error_exit(j_common_ptr cinfo) {
|
|||
longjmp(myerr->setjmp_buffer, 1);
|
||||
}
|
||||
|
||||
JNIEXPORT void Java_org_telegram_messenger_Utilities_blurBitmap(JNIEnv *env, jclass class, jobject bitmap, int radius) {
|
||||
JNIEXPORT void Java_org_telegram_messenger_Utilities_blurBitmap(JNIEnv *env, jclass class, jobject bitmap, int radius, int unpin) {
|
||||
if (!bitmap) {
|
||||
return;
|
||||
}
|
||||
|
@ -312,7 +312,9 @@ JNIEXPORT void Java_org_telegram_messenger_Utilities_blurBitmap(JNIEnv *env, jcl
|
|||
} else {
|
||||
fastBlurMore(info.width, info.height, info.stride, pixels, radius);
|
||||
}
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
if (unpin) {
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void Java_org_telegram_messenger_Utilities_calcCDT(JNIEnv *env, jclass class, jobject hsvBuffer, int width, int height, jobject buffer) {
|
||||
|
|
Before Width: | Height: | Size: 135 KiB |
Before Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 151 KiB |
Before Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 136 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 237 KiB |
Before Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 270 KiB |
Before Width: | Height: | Size: 144 KiB |
Before Width: | Height: | Size: 249 KiB |
Before Width: | Height: | Size: 97 KiB |
Before Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 122 KiB |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 102 KiB |
After Width: | Height: | Size: 152 KiB |
After Width: | Height: | Size: 83 KiB |
After Width: | Height: | Size: 167 KiB |
After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 154 KiB |
After Width: | Height: | Size: 112 KiB |
After Width: | Height: | Size: 76 KiB |
After Width: | Height: | Size: 136 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 260 KiB |
After Width: | Height: | Size: 146 KiB |
After Width: | Height: | Size: 297 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 280 KiB |
After Width: | Height: | Size: 180 KiB |
After Width: | Height: | Size: 126 KiB |
After Width: | Height: | Size: 222 KiB |
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 182 KiB |
|
@ -45,10 +45,10 @@ import org.telegram.messenger.R;
|
|||
import org.telegram.messenger.TLRPC;
|
||||
import org.telegram.messenger.ApplicationLoader;
|
||||
import org.telegram.messenger.UserConfig;
|
||||
import org.telegram.ui.AnimationCompat.AnimatorListenerAdapterProxy;
|
||||
import org.telegram.ui.AnimationCompat.AnimatorSetProxy;
|
||||
import org.telegram.ui.AnimationCompat.ObjectAnimatorProxy;
|
||||
import org.telegram.ui.AnimationCompat.ViewProxy;
|
||||
import org.telegram.android.AnimationCompat.AnimatorListenerAdapterProxy;
|
||||
import org.telegram.android.AnimationCompat.AnimatorSetProxy;
|
||||
import org.telegram.android.AnimationCompat.ObjectAnimatorProxy;
|
||||
import org.telegram.android.AnimationCompat.ViewProxy;
|
||||
import org.telegram.ui.Components.ForegroundDetector;
|
||||
import org.telegram.ui.Components.NumberPicker;
|
||||
import org.telegram.ui.Components.TypefaceSpan;
|
||||
|
@ -72,6 +72,7 @@ public class AndroidUtilities {
|
|||
public static Integer photoSize = null;
|
||||
public static DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
public static int leftBaseline;
|
||||
public static boolean usingHardwareInput;
|
||||
private static Boolean isTablet = null;
|
||||
|
||||
static {
|
||||
|
@ -227,21 +228,38 @@ public class AndroidUtilities {
|
|||
}
|
||||
|
||||
public static int dp(float value) {
|
||||
if (value == 0) {
|
||||
return 0;
|
||||
}
|
||||
return (int)Math.ceil(density * value);
|
||||
}
|
||||
|
||||
public static int compare(int lhs, int rhs) {
|
||||
if (lhs == rhs) {
|
||||
return 0;
|
||||
} else if (lhs > rhs) {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static float dpf2(float value) {
|
||||
if (value == 0) {
|
||||
return 0;
|
||||
}
|
||||
return density * value;
|
||||
}
|
||||
|
||||
public static void checkDisplaySize() {
|
||||
try {
|
||||
WindowManager manager = (WindowManager)ApplicationLoader.applicationContext.getSystemService(Context.WINDOW_SERVICE);
|
||||
Configuration configuration = ApplicationLoader.applicationContext.getResources().getConfiguration();
|
||||
usingHardwareInput = configuration.keyboard != Configuration.KEYBOARD_NOKEYS && configuration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO;
|
||||
WindowManager manager = (WindowManager) ApplicationLoader.applicationContext.getSystemService(Context.WINDOW_SERVICE);
|
||||
if (manager != null) {
|
||||
Display display = manager.getDefaultDisplay();
|
||||
if (display != null) {
|
||||
display.getMetrics(displayMetrics);
|
||||
if(android.os.Build.VERSION.SDK_INT < 13) {
|
||||
if (android.os.Build.VERSION.SDK_INT < 13) {
|
||||
displaySize.set(display.getWidth(), display.getHeight());
|
||||
} else {
|
||||
display.getSize(displaySize);
|
||||
|
@ -252,6 +270,38 @@ public class AndroidUtilities {
|
|||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
|
||||
/*
|
||||
keyboardHidden
|
||||
public static final int KEYBOARDHIDDEN_NO = 1
|
||||
Constant for keyboardHidden, value corresponding to the keysexposed resource qualifier.
|
||||
|
||||
public static final int KEYBOARDHIDDEN_UNDEFINED = 0
|
||||
Constant for keyboardHidden: a value indicating that no value has been set.
|
||||
|
||||
public static final int KEYBOARDHIDDEN_YES = 2
|
||||
Constant for keyboardHidden, value corresponding to the keyshidden resource qualifier.
|
||||
|
||||
hardKeyboardHidden
|
||||
public static final int HARDKEYBOARDHIDDEN_NO = 1
|
||||
Constant for hardKeyboardHidden, value corresponding to the physical keyboard being exposed.
|
||||
|
||||
public static final int HARDKEYBOARDHIDDEN_UNDEFINED = 0
|
||||
Constant for hardKeyboardHidden: a value indicating that no value has been set.
|
||||
|
||||
public static final int HARDKEYBOARDHIDDEN_YES = 2
|
||||
Constant for hardKeyboardHidden, value corresponding to the physical keyboard being hidden.
|
||||
|
||||
keyboard
|
||||
public static final int KEYBOARD_12KEY = 3
|
||||
Constant for keyboard, value corresponding to the 12key resource qualifier.
|
||||
|
||||
public static final int KEYBOARD_NOKEYS = 1
|
||||
Constant for keyboard, value corresponding to the nokeys resource qualifier.
|
||||
|
||||
public static final int KEYBOARD_QWERTY = 2
|
||||
Constant for keyboard, value corresponding to the qwerty resource qualifier.
|
||||
*/
|
||||
}
|
||||
|
||||
public static float getPixelsInCM(float cm, boolean isX) {
|
||||
|
@ -562,6 +612,9 @@ public class AndroidUtilities {
|
|||
if (start != -1) {
|
||||
stringBuilder.replace(start, start + 3, "");
|
||||
end = stringBuilder.indexOf("</b>");
|
||||
if (end == -1) {
|
||||
end = stringBuilder.indexOf("<b>");
|
||||
}
|
||||
stringBuilder.replace(end, end + 4, "");
|
||||
bolds.add(start);
|
||||
bolds.add(end);
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
public abstract class AnimatorListenerAdapter10 implements Animator10.AnimatorListener, Animator10.AnimatorPauseListener {
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
public class FloatEvaluator implements TypeEvaluator<Number> {
|
||||
public Float evaluate(float fraction, Number startValue, Number endValue) {
|
|
@ -14,11 +14,11 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import org.telegram.ui.Animation.Keyframe.FloatKeyframe;
|
||||
import org.telegram.android.Animation.Keyframe.FloatKeyframe;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
public abstract class FloatProperty10<T> extends Property<T, Float> {
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
public class IntEvaluator implements TypeEvaluator<Integer> {
|
||||
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
|
|
@ -14,11 +14,11 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import org.telegram.ui.Animation.Keyframe.IntKeyframe;
|
||||
import org.telegram.android.Animation.Keyframe.IntKeyframe;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
public abstract class IntProperty<T> extends Property<T, Integer> {
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
|
@ -14,16 +14,16 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import android.util.Log;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import org.telegram.ui.Animation.Keyframe.IntKeyframe;
|
||||
import org.telegram.ui.Animation.Keyframe.FloatKeyframe;
|
||||
import org.telegram.ui.Animation.Keyframe.ObjectKeyframe;
|
||||
import org.telegram.android.Animation.Keyframe.IntKeyframe;
|
||||
import org.telegram.android.Animation.Keyframe.FloatKeyframe;
|
||||
import org.telegram.android.Animation.Keyframe.ObjectKeyframe;
|
||||
|
||||
class KeyframeSet {
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
public class NoSuchPropertyException extends RuntimeException {
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import android.view.View;
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
public abstract class Property<T, V> {
|
||||
|
|
@ -14,9 +14,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
public interface TypeEvaluator<T> {
|
||||
T evaluate(float fraction, T startValue, T endValue);
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import android.os.Looper;
|
||||
import android.util.AndroidRuntimeException;
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.ui.Animation;
|
||||
package org.telegram.android.Animation;
|
||||
|
||||
import android.graphics.Camera;
|
||||
import android.graphics.Matrix;
|
|
@ -6,14 +6,14 @@
|
|||
* Copyright Nikolai Kudashov, 2013-2014.
|
||||
*/
|
||||
|
||||
package org.telegram.ui.AnimationCompat;
|
||||
package org.telegram.android.AnimationCompat;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
|
||||
import org.telegram.ui.Animation.Animator10;
|
||||
import org.telegram.ui.Animation.AnimatorListenerAdapter10;
|
||||
import org.telegram.ui.Animation.View10;
|
||||
import org.telegram.android.Animation.Animator10;
|
||||
import org.telegram.android.Animation.AnimatorListenerAdapter10;
|
||||
import org.telegram.android.Animation.View10;
|
||||
|
||||
public class AnimatorListenerAdapterProxy {
|
||||
protected Object animatorListenerAdapter;
|
|
@ -6,17 +6,17 @@
|
|||
* Copyright Nikolai Kudashov, 2013-2014.
|
||||
*/
|
||||
|
||||
package org.telegram.ui.AnimationCompat;
|
||||
package org.telegram.android.AnimationCompat;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import org.telegram.ui.Animation.Animator10;
|
||||
import org.telegram.ui.Animation.AnimatorListenerAdapter10;
|
||||
import org.telegram.ui.Animation.AnimatorSet10;
|
||||
import org.telegram.ui.Animation.View10;
|
||||
import org.telegram.android.Animation.Animator10;
|
||||
import org.telegram.android.Animation.AnimatorListenerAdapter10;
|
||||
import org.telegram.android.Animation.AnimatorSet10;
|
||||
import org.telegram.android.Animation.View10;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
|
@ -6,15 +6,15 @@
|
|||
* Copyright Nikolai Kudashov, 2013-2014.
|
||||
*/
|
||||
|
||||
package org.telegram.ui.AnimationCompat;
|
||||
package org.telegram.android.AnimationCompat;
|
||||
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import org.telegram.ui.Animation.AnimatorListenerAdapter10;
|
||||
import org.telegram.ui.Animation.ObjectAnimator10;
|
||||
import org.telegram.ui.Animation.View10;
|
||||
import org.telegram.android.Animation.AnimatorListenerAdapter10;
|
||||
import org.telegram.android.Animation.ObjectAnimator10;
|
||||
import org.telegram.android.Animation.View10;
|
||||
|
||||
public class ObjectAnimatorProxy {
|
||||
|
|
@ -6,11 +6,11 @@
|
|||
* Copyright Nikolai Kudashov, 2013-2014.
|
||||
*/
|
||||
|
||||
package org.telegram.ui.AnimationCompat;
|
||||
package org.telegram.android.AnimationCompat;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import org.telegram.ui.Animation.View10;
|
||||
import org.telegram.android.Animation.View10;
|
||||
|
||||
public class ViewProxy {
|
||||
|
|
@ -14,6 +14,7 @@ import android.app.Activity;
|
|||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentProviderResult;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
@ -37,7 +38,6 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ContactsController {
|
||||
|
@ -168,7 +168,7 @@ public class ContactsController {
|
|||
if (!updatingInviteText && (inviteText == null || time + 86400 < (int)(System.currentTimeMillis() / 1000))) {
|
||||
updatingInviteText = true;
|
||||
TLRPC.TL_help_getInviteText req = new TLRPC.TL_help_getInviteText();
|
||||
req.lang_code = LocaleController.getLocaleString(Locale.getDefault());
|
||||
req.lang_code = LocaleController.getLocaleString(LocaleController.getInstance().getSystemDefaultLocale());
|
||||
if (req.lang_code == null || req.lang_code.length() == 0) {
|
||||
req.lang_code = "en";
|
||||
}
|
||||
|
@ -202,7 +202,19 @@ public class ContactsController {
|
|||
|
||||
public void checkAppAccount() {
|
||||
AccountManager am = AccountManager.get(ApplicationLoader.applicationContext);
|
||||
Account[] accounts = am.getAccountsByType("org.telegram.account");
|
||||
Account[] accounts;
|
||||
try {
|
||||
accounts = am.getAccountsByType("org.telegram.account");
|
||||
if (accounts != null && accounts.length > 0) {
|
||||
for (Account c : accounts) {
|
||||
am.removeAccount(c, null, null);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
|
||||
accounts = am.getAccountsByType("org.telegram.messenger");
|
||||
boolean recreateAccount = false;
|
||||
if (UserConfig.isClientActivated()) {
|
||||
if (accounts.length == 1) {
|
||||
|
@ -227,7 +239,7 @@ public class ContactsController {
|
|||
}
|
||||
if (UserConfig.isClientActivated()) {
|
||||
try {
|
||||
currentAccount = new Account(UserConfig.getCurrentUser().phone, "org.telegram.account");
|
||||
currentAccount = new Account(UserConfig.getCurrentUser().phone, "org.telegram.messenger");
|
||||
am.addAccountExplicitly(currentAccount, "", null);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
|
@ -239,7 +251,7 @@ public class ContactsController {
|
|||
public void deleteAllAppAccounts() {
|
||||
try {
|
||||
AccountManager am = AccountManager.get(ApplicationLoader.applicationContext);
|
||||
Account[] accounts = am.getAccountsByType("org.telegram.account");
|
||||
Account[] accounts = am.getAccountsByType("org.telegram.messenger");
|
||||
for (Account c : accounts) {
|
||||
am.removeAccount(c, null, null);
|
||||
}
|
||||
|
@ -1246,7 +1258,7 @@ public class ContactsController {
|
|||
private void performWriteContactsToPhoneBook() {
|
||||
final ArrayList<TLRPC.TL_contact> contactsArray = new ArrayList<>();
|
||||
contactsArray.addAll(contacts);
|
||||
Utilities.photoBookQueue.postRunnable(new Runnable() {
|
||||
Utilities.phoneBookQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
performWriteContactsToPhoneBookInternal(contactsArray);
|
||||
|
@ -1303,7 +1315,7 @@ public class ContactsController {
|
|||
}
|
||||
|
||||
for (final Integer uid : contactsTD) {
|
||||
Utilities.photoBookQueue.postRunnable(new Runnable() {
|
||||
Utilities.phoneBookQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
deleteContactFromPhoneBook(uid);
|
||||
|
@ -1463,7 +1475,7 @@ public class ContactsController {
|
|||
builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
|
||||
builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0);
|
||||
builder.withValue(ContactsContract.Data.MIMETYPE, "vnd.android.cursor.item/vnd.org.telegram.messenger.android.profile");
|
||||
builder.withValue(ContactsContract.Data.DATA1, "+" + user.phone);
|
||||
builder.withValue(ContactsContract.Data.DATA1, user.id);
|
||||
builder.withValue(ContactsContract.Data.DATA2, "Telegram Profile");
|
||||
builder.withValue(ContactsContract.Data.DATA3, "+" + user.phone);
|
||||
builder.withValue(ContactsContract.Data.DATA4, user.id);
|
||||
|
@ -1496,6 +1508,22 @@ public class ContactsController {
|
|||
}
|
||||
}
|
||||
|
||||
protected void markAsContacted(final String contactId) {
|
||||
if (contactId == null) {
|
||||
return;
|
||||
}
|
||||
Utilities.phoneBookQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Uri uri = Uri.parse(contactId);
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(ContactsContract.Contacts.LAST_TIME_CONTACTED, System.currentTimeMillis());
|
||||
ContentResolver cr = ApplicationLoader.applicationContext.getContentResolver();
|
||||
cr.update(uri, values, null, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void addContact(TLRPC.User user) {
|
||||
if (user == null || user.phone == null) {
|
||||
return;
|
||||
|
@ -1533,7 +1561,7 @@ public class ContactsController {
|
|||
// }
|
||||
|
||||
for (final TLRPC.User u : res.users) {
|
||||
Utilities.photoBookQueue.postRunnable(new Runnable() {
|
||||
Utilities.phoneBookQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
addContactToPhoneBook(u, true);
|
||||
|
@ -1546,7 +1574,7 @@ public class ContactsController {
|
|||
MessagesStorage.getInstance().putContacts(arrayList, false);
|
||||
|
||||
if (u.phone != null && u.phone.length() > 0) {
|
||||
String name = formatName(u.first_name, u.last_name);
|
||||
CharSequence name = formatName(u.first_name, u.last_name);
|
||||
MessagesStorage.getInstance().applyPhoneBookUpdates(u.phone, "");
|
||||
Contact contact = contactsBookSPhones.get(u.phone);
|
||||
if (contact != null) {
|
||||
|
@ -1599,7 +1627,7 @@ public class ContactsController {
|
|||
return;
|
||||
}
|
||||
MessagesStorage.getInstance().deleteContacts(uids);
|
||||
Utilities.photoBookQueue.postRunnable(new Runnable() {
|
||||
Utilities.phoneBookQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (TLRPC.User user : users) {
|
||||
|
@ -1610,7 +1638,7 @@ public class ContactsController {
|
|||
|
||||
for (TLRPC.User user : users) {
|
||||
if (user.phone != null && user.phone.length() > 0) {
|
||||
String name = ContactsController.formatName(user.first_name, user.last_name);
|
||||
CharSequence name = ContactsController.formatName(user.first_name, user.last_name);
|
||||
MessagesStorage.getInstance().applyPhoneBookUpdates(user.phone, "");
|
||||
Contact contact = contactsBookSPhones.get(user.phone);
|
||||
if (contact != null) {
|
||||
|
@ -1772,22 +1800,37 @@ public class ContactsController {
|
|||
}
|
||||
|
||||
public static String formatName(String firstName, String lastName) {
|
||||
String result = "";
|
||||
/*if ((firstName == null || firstName.length() == 0) && (lastName == null || lastName.length() == 0)) {
|
||||
return LocaleController.getString("HiddenName", R.string.HiddenName);
|
||||
}*/
|
||||
if (firstName != null) {
|
||||
firstName = firstName.trim();
|
||||
}
|
||||
if (lastName != null) {
|
||||
lastName = lastName.trim();
|
||||
}
|
||||
StringBuilder result = new StringBuilder((firstName != null ? firstName.length() : 0) + (lastName != null ? lastName.length() : 0) + 1);
|
||||
if (LocaleController.nameDisplayOrder == 1) {
|
||||
result = firstName;
|
||||
if (result == null || result.length() == 0) {
|
||||
result = lastName;
|
||||
} else if (result.length() != 0 && lastName != null && lastName.length() != 0) {
|
||||
result += " " + lastName;
|
||||
if (firstName != null && firstName.length() > 0) {
|
||||
result.append(firstName);
|
||||
if (lastName != null && lastName.length() > 0) {
|
||||
result.append(" ");
|
||||
result.append(lastName);
|
||||
}
|
||||
} else if (lastName != null && lastName.length() > 0) {
|
||||
result.append(lastName);
|
||||
}
|
||||
} else {
|
||||
result = lastName;
|
||||
if (result == null || result.length() == 0) {
|
||||
result = firstName;
|
||||
} else if (result.length() != 0 && firstName != null && firstName.length() != 0) {
|
||||
result += " " + firstName;
|
||||
if (lastName != null && lastName.length() > 0) {
|
||||
result.append(lastName);
|
||||
if (firstName != null && firstName.length() > 0) {
|
||||
result.append(" ");
|
||||
result.append(firstName);
|
||||
}
|
||||
} else if (firstName != null && firstName.length() > 0) {
|
||||
result.append(firstName);
|
||||
}
|
||||
}
|
||||
return result.trim();
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,8 @@ import org.telegram.messenger.ApplicationLoader;
|
|||
|
||||
public class Emoji {
|
||||
private static HashMap<Long, DrawableInfo> rects = new HashMap<>();
|
||||
private static int drawImgSize, bigImgSize;
|
||||
private static int drawImgSize;
|
||||
private static int bigImgSize;
|
||||
private static boolean inited = false;
|
||||
private static Paint placeholderPaint;
|
||||
private static Bitmap emojiBmp[] = new Bitmap[5];
|
||||
|
@ -193,19 +194,19 @@ public class Emoji {
|
|||
static {
|
||||
int emojiFullSize;
|
||||
if (AndroidUtilities.density <= 1.0f) {
|
||||
emojiFullSize = 30;
|
||||
emojiFullSize = 32;
|
||||
} else if (AndroidUtilities.density <= 1.5f) {
|
||||
emojiFullSize = 45;
|
||||
emojiFullSize = 48;
|
||||
} else if (AndroidUtilities.density <= 2.0f) {
|
||||
emojiFullSize = 60;
|
||||
emojiFullSize = 64;
|
||||
} else {
|
||||
emojiFullSize = 90;
|
||||
emojiFullSize = 96;
|
||||
}
|
||||
drawImgSize = AndroidUtilities.dp(20);
|
||||
if (AndroidUtilities.isTablet()) {
|
||||
bigImgSize = AndroidUtilities.dp(40);
|
||||
} else {
|
||||
bigImgSize = AndroidUtilities.dp(30);
|
||||
bigImgSize = AndroidUtilities.dp(32);
|
||||
}
|
||||
|
||||
for (int j = 1; j < data.length; j++) {
|
||||
|
@ -234,8 +235,26 @@ public class Emoji {
|
|||
scale = 3.0f;
|
||||
}
|
||||
|
||||
String imageName = String.format(Locale.US, "emoji%.01fx_%d.jpg", scale, page);
|
||||
File imageFile = ApplicationLoader.applicationContext.getFileStreamPath(imageName);
|
||||
String imageName;
|
||||
File imageFile;
|
||||
|
||||
try {
|
||||
imageName = String.format(Locale.US, "emoji%.01fx_%d.jpg", scale, page);
|
||||
imageFile = ApplicationLoader.applicationContext.getFileStreamPath(imageName);
|
||||
if (imageFile.exists()) {
|
||||
imageFile.delete();
|
||||
}
|
||||
imageName = String.format(Locale.US, "emoji%.01fx_a_%d.jpg", scale, page);
|
||||
imageFile = ApplicationLoader.applicationContext.getFileStreamPath(imageName);
|
||||
if (imageFile.exists()) {
|
||||
imageFile.delete();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
|
||||
imageName = String.format(Locale.US, "v4_emoji%.01fx_%d.jpg", scale, page);
|
||||
imageFile = ApplicationLoader.applicationContext.getFileStreamPath(imageName);
|
||||
if (!imageFile.exists()) {
|
||||
InputStream is = ApplicationLoader.applicationContext.getAssets().open("emoji/" + imageName);
|
||||
Utilities.copyFile(is, imageFile);
|
||||
|
@ -253,7 +272,7 @@ public class Emoji {
|
|||
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
Utilities.loadBitmap(imageFile.getAbsolutePath(), bitmap, imageResize, width, height, stride);
|
||||
|
||||
imageName = String.format(Locale.US, "emoji%.01fx_a_%d.jpg", scale, page);
|
||||
imageName = String.format(Locale.US, "v4_emoji%.01fx_a_%d.jpg", scale, page);
|
||||
imageFile = ApplicationLoader.applicationContext.getFileStreamPath(imageName);
|
||||
if (!imageFile.exists()) {
|
||||
InputStream is = ApplicationLoader.applicationContext.getAssets().open("emoji/" + imageName);
|
||||
|
|
|
@ -70,6 +70,8 @@ public class ImageLoader {
|
|||
private DispatchQueue recycleQueue = new DispatchQueue("recycleQueue");
|
||||
private ConcurrentHashMap<String, Float> fileProgresses = new ConcurrentHashMap<>();
|
||||
private HashMap<String, ThumbGenerateTask> thumbGenerateTasks = new HashMap<>();
|
||||
private static byte[] bytes;
|
||||
private static byte[] bytesThumb;
|
||||
private int currentHttpTasksCount = 0;
|
||||
|
||||
private LinkedList<HttpFileTask> httpFileLoadTasks = new LinkedList<>();
|
||||
|
@ -507,6 +509,7 @@ public class ImageLoader {
|
|||
}
|
||||
|
||||
Long mediaId = null;
|
||||
boolean mediaIsVideo = false;
|
||||
Bitmap image = null;
|
||||
File cacheFileFinal = cacheImage.finalFilePath;
|
||||
boolean canDeleteFile = true;
|
||||
|
@ -537,18 +540,35 @@ public class ImageLoader {
|
|||
}
|
||||
}
|
||||
|
||||
if (image == null) {
|
||||
if (isWebp) {
|
||||
RandomAccessFile file = new RandomAccessFile(cacheFileFinal, "r");
|
||||
ByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, cacheFileFinal.length());
|
||||
image = Utilities.loadWebpImage(buffer, buffer.limit(), null);
|
||||
file.close();
|
||||
BitmapFactory.Options opts = new BitmapFactory.Options();
|
||||
opts.inSampleSize = 1;
|
||||
|
||||
if (!isWebp && Build.VERSION.SDK_INT > 10 && Build.VERSION.SDK_INT < 21) {
|
||||
opts.inPurgeable = true;
|
||||
}
|
||||
|
||||
if (isWebp) {
|
||||
RandomAccessFile file = new RandomAccessFile(cacheFileFinal, "r");
|
||||
ByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, cacheFileFinal.length());
|
||||
image = Utilities.loadWebpImage(buffer, buffer.limit(), null);
|
||||
file.close();
|
||||
} else {
|
||||
if (opts.inPurgeable) {
|
||||
RandomAccessFile f = new RandomAccessFile(cacheFileFinal, "r");
|
||||
int len = (int) f.length();
|
||||
byte[] data = bytesThumb != null && bytesThumb.length >= len ? bytesThumb : null;
|
||||
if (data == null) {
|
||||
bytesThumb = data = new byte[len];
|
||||
}
|
||||
f.readFully(data, 0, len);
|
||||
image = BitmapFactory.decodeByteArray(data, 0, len, opts);
|
||||
} else {
|
||||
FileInputStream is = new FileInputStream(cacheFileFinal);
|
||||
image = BitmapFactory.decodeStream(is, null, null);
|
||||
image = BitmapFactory.decodeStream(is, null, opts);
|
||||
is.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (image == null) {
|
||||
if (canDeleteFile && (cacheFileFinal.length() == 0 || cacheImage.filter == null)) {
|
||||
cacheFileFinal.delete();
|
||||
|
@ -556,15 +576,18 @@ public class ImageLoader {
|
|||
} else {
|
||||
if (image != null) {
|
||||
if (blurType == 1) {
|
||||
Utilities.blurBitmap(image, 3);
|
||||
Utilities.blurBitmap(image, 3, opts.inPurgeable ? 0 : 1);
|
||||
} else if (blurType == 2) {
|
||||
Utilities.blurBitmap(image, 1);
|
||||
Utilities.blurBitmap(image, 1, opts.inPurgeable ? 0 : 1);
|
||||
} else if (blurType == 3) {
|
||||
Utilities.blurBitmap(image, 7);
|
||||
Utilities.blurBitmap(image, 7);
|
||||
Utilities.blurBitmap(image, 7);
|
||||
Utilities.blurBitmap(image, 7, opts.inPurgeable ? 0 : 1);
|
||||
Utilities.blurBitmap(image, 7, opts.inPurgeable ? 0 : 1);
|
||||
Utilities.blurBitmap(image, 7, opts.inPurgeable ? 0 : 1);
|
||||
}
|
||||
}
|
||||
if (blurType == 0 && opts.inPurgeable) {
|
||||
Utilities.pinBitmap(image);
|
||||
}
|
||||
if (runtimeHack != null) {
|
||||
runtimeHack.trackFree(image.getRowBytes() * image.getHeight());
|
||||
}
|
||||
|
@ -579,6 +602,14 @@ public class ImageLoader {
|
|||
int idx = cacheImage.httpUrl.indexOf(":", 8);
|
||||
if (idx >= 0) {
|
||||
mediaId = Long.parseLong(cacheImage.httpUrl.substring(8, idx));
|
||||
mediaIsVideo = false;
|
||||
}
|
||||
canDeleteFile = false;
|
||||
} else if (cacheImage.httpUrl.startsWith("vthumb://")) {
|
||||
int idx = cacheImage.httpUrl.indexOf(":", 9);
|
||||
if (idx >= 0) {
|
||||
mediaId = Long.parseLong(cacheImage.httpUrl.substring(9, idx));
|
||||
mediaIsVideo = true;
|
||||
}
|
||||
canDeleteFile = false;
|
||||
} else if (!cacheImage.httpUrl.startsWith("http")) {
|
||||
|
@ -604,35 +635,44 @@ public class ImageLoader {
|
|||
}
|
||||
|
||||
BitmapFactory.Options opts = new BitmapFactory.Options();
|
||||
opts.inSampleSize = 1;
|
||||
|
||||
float w_filter = 0;
|
||||
float h_filter = 0;
|
||||
boolean blur = false;
|
||||
if (cacheImage.filter != null) {
|
||||
String args[] = cacheImage.filter.split("_");
|
||||
w_filter = Float.parseFloat(args[0]) * AndroidUtilities.density;
|
||||
h_filter = Float.parseFloat(args[1]) * AndroidUtilities.density;
|
||||
if (args.length > 2) {
|
||||
if (args.length >= 2) {
|
||||
w_filter = Float.parseFloat(args[0]) * AndroidUtilities.density;
|
||||
h_filter = Float.parseFloat(args[1]) * AndroidUtilities.density;
|
||||
}
|
||||
if (cacheImage.filter.contains("b")) {
|
||||
blur = true;
|
||||
}
|
||||
opts.inJustDecodeBounds = true;
|
||||
if (w_filter != 0 && h_filter != 0) {
|
||||
opts.inJustDecodeBounds = true;
|
||||
|
||||
if (mediaId != null) {
|
||||
MediaStore.Images.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, opts);
|
||||
} else {
|
||||
FileInputStream is = new FileInputStream(cacheFileFinal);
|
||||
image = BitmapFactory.decodeStream(is, null, opts);
|
||||
is.close();
|
||||
}
|
||||
if (mediaId != null) {
|
||||
if (mediaIsVideo) {
|
||||
MediaStore.Video.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Video.Thumbnails.MINI_KIND, opts);
|
||||
} else {
|
||||
MediaStore.Images.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, opts);
|
||||
}
|
||||
} else {
|
||||
FileInputStream is = new FileInputStream(cacheFileFinal);
|
||||
image = BitmapFactory.decodeStream(is, null, opts);
|
||||
is.close();
|
||||
}
|
||||
|
||||
float photoW = opts.outWidth;
|
||||
float photoH = opts.outHeight;
|
||||
float scaleFactor = Math.max(photoW / w_filter, photoH / h_filter);
|
||||
if (scaleFactor < 1) {
|
||||
scaleFactor = 1;
|
||||
float photoW = opts.outWidth;
|
||||
float photoH = opts.outHeight;
|
||||
float scaleFactor = Math.max(photoW / w_filter, photoH / h_filter);
|
||||
if (scaleFactor < 1) {
|
||||
scaleFactor = 1;
|
||||
}
|
||||
opts.inJustDecodeBounds = false;
|
||||
opts.inSampleSize = (int) scaleFactor;
|
||||
}
|
||||
opts.inJustDecodeBounds = false;
|
||||
opts.inSampleSize = (int)scaleFactor;
|
||||
}
|
||||
synchronized (sync) {
|
||||
if (isCancelled) {
|
||||
|
@ -645,13 +685,17 @@ public class ImageLoader {
|
|||
} else {
|
||||
opts.inPreferredConfig = Bitmap.Config.RGB_565;
|
||||
}
|
||||
//if (Build.VERSION.SDK_INT < 21) {
|
||||
// opts.inPurgeable = true;
|
||||
//}
|
||||
if (!isWebp && Build.VERSION.SDK_INT > 10 && Build.VERSION.SDK_INT < 21) {
|
||||
opts.inPurgeable = true;
|
||||
}
|
||||
|
||||
opts.inDither = false;
|
||||
if (mediaId != null) {
|
||||
image = MediaStore.Images.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, opts);
|
||||
if (mediaIsVideo) {
|
||||
image = MediaStore.Video.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Video.Thumbnails.MINI_KIND, opts);
|
||||
} else {
|
||||
image = MediaStore.Images.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, opts);
|
||||
}
|
||||
}
|
||||
if (image == null) {
|
||||
if (isWebp) {
|
||||
|
@ -660,9 +704,20 @@ public class ImageLoader {
|
|||
image = Utilities.loadWebpImage(buffer, buffer.limit(), null);
|
||||
file.close();
|
||||
} else {
|
||||
FileInputStream is = new FileInputStream(cacheFileFinal);
|
||||
image = BitmapFactory.decodeStream(is, null, opts);
|
||||
is.close();
|
||||
if (opts.inPurgeable) {
|
||||
RandomAccessFile f = new RandomAccessFile(cacheFileFinal, "r");
|
||||
int len = (int) f.length();
|
||||
byte[] data = bytes != null && bytes.length >= len ? bytes : null;
|
||||
if (data == null) {
|
||||
bytes = data = new byte[len];
|
||||
}
|
||||
f.readFully(data, 0, len);
|
||||
image = BitmapFactory.decodeByteArray(data, 0, len, opts);
|
||||
} else {
|
||||
FileInputStream is = new FileInputStream(cacheFileFinal);
|
||||
image = BitmapFactory.decodeStream(is, null, opts);
|
||||
is.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (image == null) {
|
||||
|
@ -670,12 +725,13 @@ public class ImageLoader {
|
|||
cacheFileFinal.delete();
|
||||
}
|
||||
} else {
|
||||
boolean blured = false;
|
||||
if (cacheImage.filter != null) {
|
||||
float bitmapW = image.getWidth();
|
||||
float bitmapH = image.getHeight();
|
||||
if (bitmapW != w_filter && bitmapW > w_filter) {
|
||||
if (!opts.inPurgeable && w_filter != 0 && bitmapW != w_filter && bitmapW > w_filter + 20) {
|
||||
float scaleFactor = bitmapW / w_filter;
|
||||
Bitmap scaledBitmap = Bitmap.createScaledBitmap(image, (int)w_filter, (int)(bitmapH / scaleFactor), true);
|
||||
Bitmap scaledBitmap = Bitmap.createScaledBitmap(image, (int) w_filter, (int) (bitmapH / scaleFactor), true);
|
||||
if (image != scaledBitmap) {
|
||||
image.recycle();
|
||||
callGC();
|
||||
|
@ -683,9 +739,13 @@ public class ImageLoader {
|
|||
}
|
||||
}
|
||||
if (image != null && blur && bitmapH < 100 && bitmapW < 100) {
|
||||
Utilities.blurBitmap(image, 3);
|
||||
Utilities.blurBitmap(image, 3, opts.inPurgeable ? 0 : 1);
|
||||
blured = true;
|
||||
}
|
||||
}
|
||||
if (!blured && opts.inPurgeable) {
|
||||
Utilities.pinBitmap(image);
|
||||
}
|
||||
if (runtimeHack != null) {
|
||||
runtimeHack.trackFree(image.getRowBytes() * image.getHeight());
|
||||
}
|
||||
|
@ -753,7 +813,7 @@ public class ImageLoader {
|
|||
}
|
||||
try {
|
||||
Object res = trackAllocation.invoke(runtime, size);
|
||||
return (res instanceof Boolean) ? (Boolean)res : true;
|
||||
return (res instanceof Boolean) ? (Boolean) res : true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
@ -765,7 +825,7 @@ public class ImageLoader {
|
|||
}
|
||||
try {
|
||||
Object res = trackFree.invoke(runtime, size);
|
||||
return (res instanceof Boolean) ? (Boolean)res : true;
|
||||
return (res instanceof Boolean) ? (Boolean) res : true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
@ -778,8 +838,8 @@ public class ImageLoader {
|
|||
Method getRt = cl.getMethod("getRuntime", new Class[0]);
|
||||
Object[] objects = new Object[0];
|
||||
runtime = getRt.invoke(null, objects);
|
||||
trackAllocation = cl.getMethod("trackExternalAllocation", new Class[] {long.class});
|
||||
trackFree = cl.getMethod("trackExternalFree", new Class[] {long.class});
|
||||
trackAllocation = cl.getMethod("trackExternalAllocation", new Class[]{long.class});
|
||||
trackFree = cl.getMethod("trackExternalFree", new Class[]{long.class});
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
runtime = null;
|
||||
|
@ -872,7 +932,7 @@ public class ImageLoader {
|
|||
@Override
|
||||
public void run() {
|
||||
for (ImageReceiver imgView : finalImageReceiverArray) {
|
||||
imgView.setImageBitmapByKey(image, key, thumb);
|
||||
imgView.setImageBitmapByKey(image, key, thumb, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -891,6 +951,7 @@ public class ImageLoader {
|
|||
}
|
||||
|
||||
private static volatile ImageLoader Instance = null;
|
||||
|
||||
public static ImageLoader getInstance() {
|
||||
ImageLoader localInstance = Instance;
|
||||
if (localInstance == null) {
|
||||
|
@ -915,12 +976,13 @@ public class ImageLoader {
|
|||
@Override
|
||||
protected int sizeOf(String key, BitmapDrawable bitmap) {
|
||||
Bitmap b = bitmap.getBitmap();
|
||||
if(Build.VERSION.SDK_INT < 12) {
|
||||
if (Build.VERSION.SDK_INT < 12) {
|
||||
return b.getRowBytes() * b.getHeight();
|
||||
} else {
|
||||
return b.getByteCount();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entryRemoved(boolean evicted, String key, final BitmapDrawable oldBitmap, BitmapDrawable newBitmap) {
|
||||
if (ignoreRemoval != null && key != null && ignoreRemoval.equals(key)) {
|
||||
|
@ -1193,7 +1255,7 @@ public class ImageLoader {
|
|||
recycleQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.gc();
|
||||
//System.gc();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1296,17 +1358,21 @@ public class ImageLoader {
|
|||
return memCache.get(key);
|
||||
}
|
||||
|
||||
public void replaceImageInCache(final String oldKey, final String newKey) {
|
||||
public void replaceImageInCache(final String oldKey, final String newKey, final TLRPC.FileLocation newLocation) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ArrayList<String> arr = memCache.getFilterKeys(oldKey);
|
||||
if (arr != null) {
|
||||
for (String filter : arr) {
|
||||
performReplace(oldKey + "@" + filter, newKey + "@" + filter);
|
||||
String oldK = oldKey + "@" + filter;
|
||||
String newK = newKey + "@" + filter;
|
||||
performReplace(oldK, newK);
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.didReplacedPhotoInMemCache, oldK, newK, newLocation);
|
||||
}
|
||||
} else {
|
||||
performReplace(oldKey, newKey);
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.didReplacedPhotoInMemCache, oldKey, newKey, newLocation);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1384,6 +1450,11 @@ public class ImageLoader {
|
|||
if (idx >= 0) {
|
||||
cacheFile = new File(httpLocation.substring(idx + 1));
|
||||
}
|
||||
} else if (httpLocation.startsWith("vthumb://")) {
|
||||
int idx = httpLocation.indexOf(":", 9);
|
||||
if (idx >= 0) {
|
||||
cacheFile = new File(httpLocation.substring(idx + 1));
|
||||
}
|
||||
} else {
|
||||
cacheFile = new File(httpLocation);
|
||||
}
|
||||
|
@ -1487,7 +1558,7 @@ public class ImageLoader {
|
|||
if (bitmapDrawable != null) {
|
||||
cancelLoadingForImageReceiver(imageReceiver, 0);
|
||||
if (!imageReceiver.isForcePreview()) {
|
||||
imageReceiver.setImageBitmapByKey(bitmapDrawable, key, false);
|
||||
imageReceiver.setImageBitmapByKey(bitmapDrawable, key, false, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1497,7 +1568,7 @@ public class ImageLoader {
|
|||
if (thumbKey != null) {
|
||||
BitmapDrawable bitmapDrawable = memCache.get(thumbKey);
|
||||
if (bitmapDrawable != null) {
|
||||
imageReceiver.setImageBitmapByKey(bitmapDrawable, thumbKey, true);
|
||||
imageReceiver.setImageBitmapByKey(bitmapDrawable, thumbKey, true, true);
|
||||
cancelLoadingForImageReceiver(imageReceiver, 1);
|
||||
thumbSet = true;
|
||||
}
|
||||
|
@ -1784,7 +1855,7 @@ public class ImageLoader {
|
|||
scaleFactor = 1;
|
||||
}
|
||||
bmOptions.inJustDecodeBounds = false;
|
||||
bmOptions.inSampleSize = (int)scaleFactor;
|
||||
bmOptions.inSampleSize = (int) scaleFactor;
|
||||
|
||||
String exifPath = null;
|
||||
if (path != null) {
|
||||
|
@ -1900,7 +1971,7 @@ public class ImageLoader {
|
|||
size.size = size.bytes.length;
|
||||
stream2.close();
|
||||
} else {
|
||||
size.size = (int)stream.getChannel().size();
|
||||
size.size = (int) stream.getChannel().size();
|
||||
}
|
||||
stream.close();
|
||||
if (scaledBitmap != bitmap) {
|
||||
|
@ -1926,11 +1997,17 @@ public class ImageLoader {
|
|||
boolean scaleAnyway = false;
|
||||
float scaleFactor = Math.max(photoW / maxWidth, photoH / maxHeight);
|
||||
if (minWidth != 0 && minHeight != 0 && (photoW < minWidth || photoH < minHeight)) {
|
||||
scaleFactor = Math.max(photoW / minWidth, photoH / minHeight);
|
||||
if (photoW < minWidth && photoH > minHeight) {
|
||||
scaleFactor = photoW / minWidth;
|
||||
} else if (photoW > minWidth && photoH < minHeight) {
|
||||
scaleFactor = photoH / minHeight;
|
||||
} else {
|
||||
scaleFactor = Math.max(photoW / minWidth, photoH / minHeight);
|
||||
}
|
||||
scaleAnyway = true;
|
||||
}
|
||||
int w = (int)(photoW / scaleFactor);
|
||||
int h = (int)(photoH / scaleFactor);
|
||||
int w = (int) (photoW / scaleFactor);
|
||||
int h = (int) (photoH / scaleFactor);
|
||||
if (h == 0 || w == 0) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ package org.telegram.android;
|
|||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapShader;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
|
@ -33,12 +34,25 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
|||
void didSetImage(ImageReceiver imageReceiver, boolean set, boolean thumb);
|
||||
}
|
||||
|
||||
private class SetImageBackup {
|
||||
public TLObject fileLocation;
|
||||
public String httpUrl;
|
||||
public String filter;
|
||||
public Drawable thumb;
|
||||
public TLRPC.FileLocation thumbLocation;
|
||||
public String thumbFilter;
|
||||
public int size;
|
||||
public boolean cacheOnly;
|
||||
}
|
||||
|
||||
private View parentView;
|
||||
private Integer tag;
|
||||
private Integer thumbTag;
|
||||
private MessageObject parentMessageObject;
|
||||
private boolean canceledLoading;
|
||||
|
||||
private SetImageBackup setImageBackup;
|
||||
|
||||
private TLObject currentImageLocation;
|
||||
private String currentKey;
|
||||
private String currentThumbKey;
|
||||
|
@ -66,11 +80,16 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
|||
private RectF roundRect;
|
||||
private RectF bitmapRect;
|
||||
private Matrix shaderMatrix;
|
||||
private int alpha = 255;
|
||||
private float overrideAlpha = 1.0f;
|
||||
private boolean isPressed;
|
||||
private int orientation;
|
||||
private boolean centerRotation;
|
||||
private ImageReceiverDelegate delegate;
|
||||
private float currentAlpha;
|
||||
private long lastUpdateAlphaTime;
|
||||
private byte crossfadeAlpha = 1;
|
||||
private boolean crossfadeWithThumb;
|
||||
private ColorFilter colorFilter;
|
||||
|
||||
public ImageReceiver() {
|
||||
|
||||
|
@ -106,6 +125,13 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
|||
}
|
||||
|
||||
public void setImage(TLObject fileLocation, String httpUrl, String filter, Drawable thumb, TLRPC.FileLocation thumbLocation, String thumbFilter, int size, boolean cacheOnly) {
|
||||
if (setImageBackup != null) {
|
||||
setImageBackup.fileLocation = null;
|
||||
setImageBackup.httpUrl = null;
|
||||
setImageBackup.thumbLocation = null;
|
||||
setImageBackup.thumb = null;
|
||||
}
|
||||
|
||||
if ((fileLocation == null && httpUrl == null && thumbLocation == null)
|
||||
|| (fileLocation != null && !(fileLocation instanceof TLRPC.TL_fileLocation)
|
||||
&& !(fileLocation instanceof TLRPC.TL_fileEncryptedLocation)
|
||||
|
@ -120,13 +146,14 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
|||
currentFilter = null;
|
||||
currentCacheOnly = false;
|
||||
staticThumb = thumb;
|
||||
currentAlpha = 1;
|
||||
currentThumbLocation = null;
|
||||
currentSize = 0;
|
||||
currentImage = null;
|
||||
bitmapShader = null;
|
||||
ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0);
|
||||
if (parentView != null) {
|
||||
parentView.invalidate();
|
||||
parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
}
|
||||
if (delegate != null) {
|
||||
delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null);
|
||||
|
@ -134,6 +161,8 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!(thumbLocation instanceof TLRPC.TL_fileLocation)) {
|
||||
thumbLocation = null;
|
||||
}
|
||||
|
@ -187,6 +216,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
|||
currentThumbLocation = thumbLocation;
|
||||
staticThumb = thumb;
|
||||
bitmapShader = null;
|
||||
currentAlpha = 1.0f;
|
||||
|
||||
if (delegate != null) {
|
||||
delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null);
|
||||
|
@ -194,10 +224,14 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
|||
|
||||
ImageLoader.getInstance().loadImageForImageReceiver(this);
|
||||
if (parentView != null) {
|
||||
parentView.invalidate();
|
||||
parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
}
|
||||
}
|
||||
|
||||
public void setColorFilter(ColorFilter filter) {
|
||||
colorFilter = filter;
|
||||
}
|
||||
|
||||
public void setDelegate(ImageReceiverDelegate delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
@ -239,11 +273,18 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
|||
currentSize = 0;
|
||||
currentCacheOnly = false;
|
||||
bitmapShader = null;
|
||||
if (setImageBackup != null) {
|
||||
setImageBackup.fileLocation = null;
|
||||
setImageBackup.httpUrl = null;
|
||||
setImageBackup.thumbLocation = null;
|
||||
setImageBackup.thumb = null;
|
||||
}
|
||||
currentAlpha = 1;
|
||||
if (delegate != null) {
|
||||
delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null);
|
||||
}
|
||||
if (parentView != null) {
|
||||
parentView.invalidate();
|
||||
parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -256,6 +297,212 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
|||
}
|
||||
}
|
||||
|
||||
public void onDetachedFromWindow() {
|
||||
if (currentImageLocation != null || currentHttpUrl != null || currentThumbLocation != null || staticThumb != null) {
|
||||
if (setImageBackup == null) {
|
||||
setImageBackup = new SetImageBackup();
|
||||
}
|
||||
setImageBackup.fileLocation = currentImageLocation;
|
||||
setImageBackup.httpUrl = currentHttpUrl;
|
||||
setImageBackup.filter = currentFilter;
|
||||
setImageBackup.thumb = staticThumb;
|
||||
setImageBackup.thumbLocation = currentThumbLocation;
|
||||
setImageBackup.thumbFilter = currentThumbFilter;
|
||||
setImageBackup.size = currentSize;
|
||||
setImageBackup.cacheOnly = currentCacheOnly;
|
||||
}
|
||||
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReplacedPhotoInMemCache);
|
||||
clearImage();
|
||||
}
|
||||
|
||||
public boolean onAttachedToWindow() {
|
||||
NotificationCenter.getInstance().addObserver(this, NotificationCenter.didReplacedPhotoInMemCache);
|
||||
if (setImageBackup != null && (setImageBackup.fileLocation != null || setImageBackup.httpUrl != null || setImageBackup.thumbLocation != null || setImageBackup.thumb != null)) {
|
||||
setImage(setImageBackup.fileLocation, setImageBackup.httpUrl, setImageBackup.filter, setImageBackup.thumb, setImageBackup.thumbLocation, setImageBackup.thumbFilter, setImageBackup.size, setImageBackup.cacheOnly);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void drawDrawable(Canvas canvas, Drawable drawable, int alpha) {
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
|
||||
|
||||
Paint paint = bitmapDrawable.getPaint();
|
||||
boolean hasFilter = paint != null && paint.getColorFilter() != null;
|
||||
if (hasFilter && !isPressed) {
|
||||
bitmapDrawable.setColorFilter(null);
|
||||
hasFilter = false;
|
||||
} else if (!hasFilter && isPressed) {
|
||||
bitmapDrawable.setColorFilter(new PorterDuffColorFilter(0xffdddddd, PorterDuff.Mode.MULTIPLY));
|
||||
hasFilter = true;
|
||||
}
|
||||
if (colorFilter != null) {
|
||||
bitmapDrawable.setColorFilter(colorFilter);
|
||||
}
|
||||
if (bitmapShader != null) {
|
||||
drawRegion.set(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
if (isVisible) {
|
||||
roundRect.set(drawRegion);
|
||||
shaderMatrix.reset();
|
||||
shaderMatrix.setRectToRect(bitmapRect, roundRect, Matrix.ScaleToFit.FILL);
|
||||
bitmapShader.setLocalMatrix(shaderMatrix);
|
||||
roundPaint.setAlpha(alpha);
|
||||
canvas.drawRoundRect(roundRect, roundRadius, roundRadius, roundPaint);
|
||||
}
|
||||
} else {
|
||||
int bitmapW;
|
||||
int bitmapH;
|
||||
int originalW = bitmapDrawable.getIntrinsicWidth();
|
||||
int originalH = bitmapDrawable.getIntrinsicHeight();
|
||||
if (orientation == 90 || orientation == 270) {
|
||||
bitmapW = bitmapDrawable.getIntrinsicHeight();
|
||||
bitmapH = bitmapDrawable.getIntrinsicWidth();
|
||||
} else {
|
||||
bitmapW = bitmapDrawable.getIntrinsicWidth();
|
||||
bitmapH = bitmapDrawable.getIntrinsicHeight();
|
||||
}
|
||||
float scaleW = bitmapW / (float) imageW;
|
||||
float scaleH = bitmapH / (float) imageH;
|
||||
|
||||
if (isAspectFit) {
|
||||
float scale = Math.max(scaleW, scaleH);
|
||||
canvas.save();
|
||||
bitmapW /= scale;
|
||||
bitmapH /= scale;
|
||||
drawRegion.set(imageX + (imageW - bitmapW) / 2, imageY + (imageH - bitmapH) / 2, imageX + (imageW + bitmapW) / 2, imageY + (imageH + bitmapH) / 2);
|
||||
bitmapDrawable.setBounds(drawRegion);
|
||||
try {
|
||||
bitmapDrawable.setAlpha(alpha);
|
||||
bitmapDrawable.draw(canvas);
|
||||
} catch (Exception e) {
|
||||
if (bitmapDrawable == currentImage && currentKey != null) {
|
||||
ImageLoader.getInstance().removeImage(currentKey);
|
||||
currentKey = null;
|
||||
} else if (bitmapDrawable == currentThumb && currentThumbKey != null) {
|
||||
ImageLoader.getInstance().removeImage(currentThumbKey);
|
||||
currentThumbKey = null;
|
||||
}
|
||||
setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentCacheOnly);
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
canvas.restore();
|
||||
} else {
|
||||
if (Math.abs(scaleW - scaleH) > 0.00001f) {
|
||||
canvas.save();
|
||||
canvas.clipRect(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
|
||||
if (orientation != 0) {
|
||||
if (centerRotation) {
|
||||
canvas.rotate(orientation, imageW / 2, imageH / 2);
|
||||
} else {
|
||||
canvas.rotate(orientation, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (bitmapW / scaleH > imageW) {
|
||||
bitmapW /= scaleH;
|
||||
originalW /= scaleH;
|
||||
drawRegion.set(imageX - (bitmapW - imageW) / 2, imageY, imageX + (bitmapW + imageW) / 2, imageY + imageH);
|
||||
} else {
|
||||
bitmapH /= scaleW;
|
||||
originalH /= scaleW;
|
||||
drawRegion.set(imageX, imageY - (bitmapH - imageH) / 2, imageX + imageW, imageY + (bitmapH + imageH) / 2);
|
||||
}
|
||||
if (orientation == 90 || orientation == 270) {
|
||||
int width = (drawRegion.right - drawRegion.left) / 2;
|
||||
int height = (drawRegion.bottom - drawRegion.top) / 2;
|
||||
int centerX = (drawRegion.right + drawRegion.left) / 2;
|
||||
int centerY = (drawRegion.top + drawRegion.bottom) / 2;
|
||||
bitmapDrawable.setBounds(centerX - height, centerY - width, centerX + height, centerY + width);
|
||||
} else {
|
||||
bitmapDrawable.setBounds(drawRegion);
|
||||
}
|
||||
if (isVisible) {
|
||||
try {
|
||||
bitmapDrawable.setAlpha(alpha);
|
||||
bitmapDrawable.draw(canvas);
|
||||
} catch (Exception e) {
|
||||
if (bitmapDrawable == currentImage && currentKey != null) {
|
||||
ImageLoader.getInstance().removeImage(currentKey);
|
||||
currentKey = null;
|
||||
} else if (bitmapDrawable == currentThumb && currentThumbKey != null) {
|
||||
ImageLoader.getInstance().removeImage(currentThumbKey);
|
||||
currentThumbKey = null;
|
||||
}
|
||||
setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentCacheOnly);
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
|
||||
canvas.restore();
|
||||
} else {
|
||||
canvas.save();
|
||||
if (orientation != 0) {
|
||||
if (centerRotation) {
|
||||
canvas.rotate(orientation, imageW / 2, imageH / 2);
|
||||
} else {
|
||||
canvas.rotate(orientation, 0, 0);
|
||||
}
|
||||
}
|
||||
drawRegion.set(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
if (orientation == 90 || orientation == 270) {
|
||||
int width = (drawRegion.right - drawRegion.left) / 2;
|
||||
int height = (drawRegion.bottom - drawRegion.top) / 2;
|
||||
int centerX = (drawRegion.right + drawRegion.left) / 2;
|
||||
int centerY = (drawRegion.top + drawRegion.bottom) / 2;
|
||||
bitmapDrawable.setBounds(centerX - height, centerY - width, centerX + height, centerY + width);
|
||||
} else {
|
||||
bitmapDrawable.setBounds(drawRegion);
|
||||
}
|
||||
if (isVisible) {
|
||||
try {
|
||||
bitmapDrawable.setAlpha(alpha);
|
||||
bitmapDrawable.draw(canvas);
|
||||
} catch (Exception e) {
|
||||
if (bitmapDrawable == currentImage && currentKey != null) {
|
||||
ImageLoader.getInstance().removeImage(currentKey);
|
||||
currentKey = null;
|
||||
} else if (bitmapDrawable == currentThumb && currentThumbKey != null) {
|
||||
ImageLoader.getInstance().removeImage(currentThumbKey);
|
||||
currentThumbKey = null;
|
||||
}
|
||||
setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentCacheOnly);
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
drawRegion.set(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
drawable.setBounds(drawRegion);
|
||||
if (isVisible) {
|
||||
try {
|
||||
drawable.setAlpha(alpha);
|
||||
drawable.draw(canvas);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAlphaAnimation() {
|
||||
if (currentAlpha != 1) {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
currentAlpha += (currentTime - lastUpdateAlphaTime) / 150.0f;
|
||||
if (currentAlpha > 1) {
|
||||
currentAlpha = 1;
|
||||
}
|
||||
lastUpdateAlphaTime = System.currentTimeMillis();
|
||||
if (parentView != null) {
|
||||
parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean draw(Canvas canvas) {
|
||||
try {
|
||||
BitmapDrawable bitmapDrawable = null;
|
||||
|
@ -267,161 +514,34 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
|||
bitmapDrawable = currentThumb;
|
||||
}
|
||||
if (bitmapDrawable != null) {
|
||||
Paint paint = bitmapDrawable.getPaint();
|
||||
boolean hasFilter = paint != null && paint.getColorFilter() != null;
|
||||
if (hasFilter && !isPressed) {
|
||||
bitmapDrawable.setColorFilter(null);
|
||||
hasFilter = false;
|
||||
} else if (!hasFilter && isPressed) {
|
||||
bitmapDrawable.setColorFilter(new PorterDuffColorFilter(0xffdddddd, PorterDuff.Mode.MULTIPLY));
|
||||
hasFilter = true;
|
||||
}
|
||||
if (bitmapShader != null) {
|
||||
drawRegion.set(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
if (isVisible) {
|
||||
roundRect.set(drawRegion);
|
||||
shaderMatrix.reset();
|
||||
shaderMatrix.setRectToRect(bitmapRect, roundRect, Matrix.ScaleToFit.FILL);
|
||||
bitmapShader.setLocalMatrix(shaderMatrix);
|
||||
canvas.drawRoundRect(roundRect, roundRadius, roundRadius, roundPaint);
|
||||
if (crossfadeAlpha != 0) {
|
||||
if (crossfadeWithThumb && currentAlpha != 1.0f) {
|
||||
Drawable thumbDrawable = null;
|
||||
if (bitmapDrawable == currentImage) {
|
||||
if (staticThumb != null) {
|
||||
thumbDrawable = staticThumb;
|
||||
} else if (currentThumb != null) {
|
||||
thumbDrawable = currentThumb;
|
||||
}
|
||||
} else if (bitmapDrawable == currentThumb) {
|
||||
if (staticThumb != null) {
|
||||
thumbDrawable = staticThumb;
|
||||
}
|
||||
}
|
||||
if (thumbDrawable != null) {
|
||||
drawDrawable(canvas, thumbDrawable, (int) (overrideAlpha * 255));
|
||||
}
|
||||
}
|
||||
drawDrawable(canvas, bitmapDrawable, (int) (overrideAlpha * currentAlpha * 255));
|
||||
} else {
|
||||
int bitmapW;
|
||||
int bitmapH;
|
||||
int originalW = bitmapDrawable.getIntrinsicWidth();
|
||||
int originalH = bitmapDrawable.getIntrinsicHeight();
|
||||
if (orientation == 90 || orientation == 270) {
|
||||
bitmapW = bitmapDrawable.getIntrinsicHeight();
|
||||
bitmapH = bitmapDrawable.getIntrinsicWidth();
|
||||
} else {
|
||||
bitmapW = bitmapDrawable.getIntrinsicWidth();
|
||||
bitmapH = bitmapDrawable.getIntrinsicHeight();
|
||||
}
|
||||
float scaleW = bitmapW / (float) imageW;
|
||||
float scaleH = bitmapH / (float) imageH;
|
||||
|
||||
if (isAspectFit) {
|
||||
float scale = Math.max(scaleW, scaleH);
|
||||
canvas.save();
|
||||
bitmapW /= scale;
|
||||
bitmapH /= scale;
|
||||
drawRegion.set(imageX + (imageW - bitmapW) / 2, imageY + (imageH - bitmapH) / 2, imageX + (imageW + bitmapW) / 2, imageY + (imageH + bitmapH) / 2);
|
||||
bitmapDrawable.setBounds(drawRegion);
|
||||
try {
|
||||
bitmapDrawable.setAlpha(alpha);
|
||||
bitmapDrawable.draw(canvas);
|
||||
} catch (Exception e) {
|
||||
if (bitmapDrawable == currentImage && currentKey != null) {
|
||||
ImageLoader.getInstance().removeImage(currentKey);
|
||||
currentKey = null;
|
||||
} else if (bitmapDrawable == currentThumb && currentThumbKey != null) {
|
||||
ImageLoader.getInstance().removeImage(currentThumbKey);
|
||||
currentThumbKey = null;
|
||||
}
|
||||
setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentCacheOnly);
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
canvas.restore();
|
||||
} else {
|
||||
if (Math.abs(scaleW - scaleH) > 0.00001f) {
|
||||
canvas.save();
|
||||
canvas.clipRect(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
|
||||
if (orientation != 0) {
|
||||
if (centerRotation) {
|
||||
canvas.rotate(orientation, imageW / 2, imageH / 2);
|
||||
} else {
|
||||
canvas.rotate(orientation, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (bitmapW / scaleH > imageW) {
|
||||
bitmapW /= scaleH;
|
||||
originalW /= scaleH;
|
||||
drawRegion.set(imageX - (bitmapW - imageW) / 2, imageY, imageX + (bitmapW + imageW) / 2, imageY + imageH);
|
||||
} else {
|
||||
bitmapH /= scaleW;
|
||||
originalH /= scaleW;
|
||||
drawRegion.set(imageX, imageY - (bitmapH - imageH) / 2, imageX + imageW, imageY + (bitmapH + imageH) / 2);
|
||||
}
|
||||
if (orientation == 90 || orientation == 270) {
|
||||
int width = (drawRegion.right - drawRegion.left) / 2;
|
||||
int height = (drawRegion.bottom - drawRegion.top) / 2;
|
||||
int centerX = (drawRegion.right + drawRegion.left) / 2;
|
||||
int centerY = (drawRegion.top + drawRegion.bottom) / 2;
|
||||
bitmapDrawable.setBounds(centerX - height, centerY - width, centerX + height, centerY + width);
|
||||
} else {
|
||||
bitmapDrawable.setBounds(drawRegion);
|
||||
}
|
||||
if (isVisible) {
|
||||
try {
|
||||
bitmapDrawable.setAlpha(alpha);
|
||||
bitmapDrawable.draw(canvas);
|
||||
} catch (Exception e) {
|
||||
if (bitmapDrawable == currentImage && currentKey != null) {
|
||||
ImageLoader.getInstance().removeImage(currentKey);
|
||||
currentKey = null;
|
||||
} else if (bitmapDrawable == currentThumb && currentThumbKey != null) {
|
||||
ImageLoader.getInstance().removeImage(currentThumbKey);
|
||||
currentThumbKey = null;
|
||||
}
|
||||
setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentCacheOnly);
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
|
||||
canvas.restore();
|
||||
} else {
|
||||
canvas.save();
|
||||
if (orientation != 0) {
|
||||
if (centerRotation) {
|
||||
canvas.rotate(orientation, imageW / 2, imageH / 2);
|
||||
} else {
|
||||
canvas.rotate(orientation, 0, 0);
|
||||
}
|
||||
}
|
||||
drawRegion.set(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
if (orientation == 90 || orientation == 270) {
|
||||
int width = (drawRegion.right - drawRegion.left) / 2;
|
||||
int height = (drawRegion.bottom - drawRegion.top) / 2;
|
||||
int centerX = (drawRegion.right + drawRegion.left) / 2;
|
||||
int centerY = (drawRegion.top + drawRegion.bottom) / 2;
|
||||
bitmapDrawable.setBounds(centerX - height, centerY - width, centerX + height, centerY + width);
|
||||
} else {
|
||||
bitmapDrawable.setBounds(drawRegion);
|
||||
}
|
||||
if (isVisible) {
|
||||
try {
|
||||
bitmapDrawable.setAlpha(alpha);
|
||||
bitmapDrawable.draw(canvas);
|
||||
} catch (Exception e) {
|
||||
if (bitmapDrawable == currentImage && currentKey != null) {
|
||||
ImageLoader.getInstance().removeImage(currentKey);
|
||||
currentKey = null;
|
||||
} else if (bitmapDrawable == currentThumb && currentThumbKey != null) {
|
||||
ImageLoader.getInstance().removeImage(currentThumbKey);
|
||||
currentThumbKey = null;
|
||||
}
|
||||
setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentCacheOnly);
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
drawDrawable(canvas, bitmapDrawable, (int) (overrideAlpha * 255));
|
||||
}
|
||||
|
||||
checkAlphaAnimation();
|
||||
return true;
|
||||
} else if (staticThumb != null) {
|
||||
drawRegion.set(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
staticThumb.setBounds(drawRegion);
|
||||
if (isVisible) {
|
||||
try {
|
||||
staticThumb.setAlpha(alpha);
|
||||
staticThumb.draw(canvas);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
drawDrawable(canvas, staticThumb, 255);
|
||||
checkAlphaAnimation();
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -457,7 +577,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
|||
}
|
||||
isVisible = value;
|
||||
if (invalidate && parentView != null) {
|
||||
parentView.invalidate();
|
||||
parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -466,7 +586,11 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
|||
}
|
||||
|
||||
public void setAlpha(float value) {
|
||||
alpha = (int)(value * 255.0f);
|
||||
overrideAlpha = value;
|
||||
}
|
||||
|
||||
public void setCrossfadeAlpha(byte value) {
|
||||
crossfadeAlpha = value;
|
||||
}
|
||||
|
||||
public boolean hasImage() {
|
||||
|
@ -630,7 +754,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
|||
}
|
||||
}
|
||||
|
||||
protected void setImageBitmapByKey(BitmapDrawable bitmap, String key, boolean thumb) {
|
||||
protected void setImageBitmapByKey(BitmapDrawable bitmap, String key, boolean thumb, boolean memCache) {
|
||||
if (bitmap == null || key == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -646,17 +770,38 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
|||
roundPaint.setShader(bitmapShader);
|
||||
bitmapRect.set(0, 0, object.getWidth(), object.getHeight());
|
||||
}
|
||||
|
||||
if (!memCache && !forcePreview) {
|
||||
if (currentThumb == null && staticThumb == null || currentAlpha == 1.0f) {
|
||||
currentAlpha = 0.0f;
|
||||
lastUpdateAlphaTime = System.currentTimeMillis();
|
||||
crossfadeWithThumb = currentThumb != null || staticThumb != null;
|
||||
}
|
||||
} else {
|
||||
currentAlpha = 1.0f;
|
||||
}
|
||||
|
||||
if (parentView != null) {
|
||||
parentView.invalidate();
|
||||
parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
}
|
||||
} else if (currentThumb == null && (currentImage == null || forcePreview)) {
|
||||
if (currentThumbKey == null || !key.equals(currentThumbKey)) {
|
||||
return;
|
||||
}
|
||||
ImageLoader.getInstance().incrementUseCount(currentThumbKey);
|
||||
|
||||
currentThumb = bitmap;
|
||||
|
||||
if (!memCache && crossfadeAlpha != 2) {
|
||||
currentAlpha = 0.0f;
|
||||
lastUpdateAlphaTime = System.currentTimeMillis();
|
||||
crossfadeWithThumb = staticThumb != null && currentKey == null;
|
||||
} else {
|
||||
currentAlpha = 1.0f;
|
||||
}
|
||||
|
||||
if (!(staticThumb instanceof BitmapDrawable) && parentView != null) {
|
||||
parentView.invalidate();
|
||||
parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -721,7 +866,27 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
|||
staticThumb = null;
|
||||
}
|
||||
if (parentView != null) {
|
||||
parentView.invalidate();
|
||||
parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||
}
|
||||
}
|
||||
} else if (id == NotificationCenter.didReplacedPhotoInMemCache) {
|
||||
String oldKey = (String) args[0];
|
||||
if (currentKey != null && currentKey.equals(oldKey)) {
|
||||
currentKey = (String) args[1];
|
||||
currentImageLocation = (TLRPC.FileLocation) args[2];
|
||||
}
|
||||
if (currentThumbKey != null && currentThumbKey.equals(oldKey)) {
|
||||
currentThumbKey = (String) args[1];
|
||||
currentThumbLocation = (TLRPC.FileLocation) args[2];
|
||||
}
|
||||
if (setImageBackup != null) {
|
||||
if (currentKey != null && currentKey.equals(oldKey)) {
|
||||
currentKey = (String) args[1];
|
||||
currentImageLocation = (TLRPC.FileLocation) args[2];
|
||||
}
|
||||
if (currentThumbKey != null && currentThumbKey.equals(oldKey)) {
|
||||
currentThumbKey = (String) args[1];
|
||||
currentThumbLocation = (TLRPC.FileLocation) args[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -309,6 +309,10 @@ public class LocaleController {
|
|||
}
|
||||
}
|
||||
|
||||
public Locale getSystemDefaultLocale() {
|
||||
return systemDefaultLocale;
|
||||
}
|
||||
|
||||
public static String getLocaleString(Locale locale) {
|
||||
if (locale == null) {
|
||||
return "en";
|
||||
|
@ -607,11 +611,12 @@ public class LocaleController {
|
|||
}
|
||||
|
||||
public static String formatString(String key, int res, Object... args) {
|
||||
String value = getInstance().localeValues.get(key);
|
||||
if (value == null) {
|
||||
value = ApplicationLoader.applicationContext.getString(res);
|
||||
}
|
||||
try {
|
||||
String value = getInstance().localeValues.get(key);
|
||||
if (value == null) {
|
||||
value = ApplicationLoader.applicationContext.getString(res);
|
||||
}
|
||||
|
||||
if (getInstance().currentLocale != null) {
|
||||
return String.format(getInstance().currentLocale, value, args);
|
||||
} else {
|
||||
|
|
|
@ -117,17 +117,27 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
|
|||
MediaStore.Images.Media.ORIENTATION
|
||||
};
|
||||
|
||||
private static final String[] projectionVideo = {
|
||||
MediaStore.Video.Media._ID,
|
||||
MediaStore.Video.Media.BUCKET_ID,
|
||||
MediaStore.Video.Media.BUCKET_DISPLAY_NAME,
|
||||
MediaStore.Video.Media.DATA,
|
||||
MediaStore.Video.Media.DATE_TAKEN
|
||||
};
|
||||
|
||||
public static class AlbumEntry {
|
||||
public int bucketId;
|
||||
public String bucketName;
|
||||
public PhotoEntry coverPhoto;
|
||||
public ArrayList<PhotoEntry> photos = new ArrayList<>();
|
||||
public HashMap<Integer, PhotoEntry> photosByIds = new HashMap<>();
|
||||
public boolean isVideo;
|
||||
|
||||
public AlbumEntry(int bucketId, String bucketName, PhotoEntry coverPhoto) {
|
||||
public AlbumEntry(int bucketId, String bucketName, PhotoEntry coverPhoto, boolean isVideo) {
|
||||
this.bucketId = bucketId;
|
||||
this.bucketName = bucketName;
|
||||
this.coverPhoto = coverPhoto;
|
||||
this.isVideo = isVideo;
|
||||
}
|
||||
|
||||
public void addPhoto(PhotoEntry photoEntry) {
|
||||
|
@ -144,13 +154,16 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
|
|||
public int orientation;
|
||||
public String thumbPath;
|
||||
public String imagePath;
|
||||
public boolean isVideo;
|
||||
public CharSequence caption;
|
||||
|
||||
public PhotoEntry(int bucketId, int imageId, long dateTaken, String path, int orientation) {
|
||||
public PhotoEntry(int bucketId, int imageId, long dateTaken, String path, int orientation, boolean isVideo) {
|
||||
this.bucketId = bucketId;
|
||||
this.imageId = imageId;
|
||||
this.dateTaken = dateTaken;
|
||||
this.path = path;
|
||||
this.orientation = orientation;
|
||||
this.isVideo = isVideo;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,6 +180,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
|
|||
public int date;
|
||||
public String thumbPath;
|
||||
public String imagePath;
|
||||
public CharSequence caption;
|
||||
}
|
||||
|
||||
public final static String MIME_TYPE = "video/avc";
|
||||
|
@ -178,6 +192,8 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
|
|||
private final static int PROCESSOR_TYPE_TI = 5;
|
||||
private final Object videoConvertSync = new Object();
|
||||
|
||||
private HashMap<Long, Long> typingTimes = new HashMap<>();
|
||||
|
||||
private SensorManager sensorManager;
|
||||
private Sensor proximitySensor;
|
||||
private boolean ignoreProximity;
|
||||
|
@ -550,6 +566,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
|
|||
videoDownloadQueue.clear();
|
||||
downloadQueueKeys.clear();
|
||||
videoConvertQueue.clear();
|
||||
typingTimes.clear();
|
||||
cancelVideoConvert(null);
|
||||
}
|
||||
|
||||
|
@ -975,6 +992,29 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
|
|||
}
|
||||
listenerInProgress = false;
|
||||
processLaterArrays();
|
||||
try {
|
||||
ArrayList<SendMessagesHelper.DelayedMessage> delayedMessages = SendMessagesHelper.getInstance().getDelayedMessages(fileName);
|
||||
if (delayedMessages != null) {
|
||||
for (SendMessagesHelper.DelayedMessage delayedMessage : delayedMessages) {
|
||||
if (delayedMessage.encryptedChat == null) {
|
||||
long dialog_id = delayedMessage.obj.getDialogId();
|
||||
Long lastTime = typingTimes.get(dialog_id);
|
||||
if (lastTime == null || lastTime + 4000 < System.currentTimeMillis()) {
|
||||
if (delayedMessage.videoLocation != null) {
|
||||
MessagesController.getInstance().sendTyping(dialog_id, 5, 0);
|
||||
} else if (delayedMessage.documentLocation != null) {
|
||||
MessagesController.getInstance().sendTyping(dialog_id, 3, 0);
|
||||
} else if (delayedMessage.location != null) {
|
||||
MessagesController.getInstance().sendTyping(dialog_id, 4, 0);
|
||||
}
|
||||
typingTimes.put(dialog_id, System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
} else if (id == NotificationCenter.messagesDeleted) {
|
||||
if (playingMessageObject != null) {
|
||||
ArrayList<Integer> markAsDeletedMessages = (ArrayList<Integer>)args[0];
|
||||
|
@ -1970,10 +2010,12 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
|
|||
@Override
|
||||
public void run() {
|
||||
final ArrayList<AlbumEntry> albumsSorted = new ArrayList<>();
|
||||
final ArrayList<AlbumEntry> videoAlbumsSorted = new ArrayList<>();
|
||||
HashMap<Integer, AlbumEntry> albums = new HashMap<>();
|
||||
AlbumEntry allPhotosAlbum = null;
|
||||
String cameraFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath() + "/" + "Camera/";
|
||||
Integer cameraAlbumId = null;
|
||||
Integer cameraAlbumVideoId = null;
|
||||
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
|
@ -1998,10 +2040,10 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
|
|||
continue;
|
||||
}
|
||||
|
||||
PhotoEntry photoEntry = new PhotoEntry(bucketId, imageId, dateTaken, path, orientation);
|
||||
PhotoEntry photoEntry = new PhotoEntry(bucketId, imageId, dateTaken, path, orientation, false);
|
||||
|
||||
if (allPhotosAlbum == null) {
|
||||
allPhotosAlbum = new AlbumEntry(0, LocaleController.getString("AllPhotos", R.string.AllPhotos), photoEntry);
|
||||
allPhotosAlbum = new AlbumEntry(0, LocaleController.getString("AllPhotos", R.string.AllPhotos), photoEntry, false);
|
||||
albumsSorted.add(0, allPhotosAlbum);
|
||||
}
|
||||
if (allPhotosAlbum != null) {
|
||||
|
@ -2010,7 +2052,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
|
|||
|
||||
AlbumEntry albumEntry = albums.get(bucketId);
|
||||
if (albumEntry == null) {
|
||||
albumEntry = new AlbumEntry(bucketId, bucketName, photoEntry);
|
||||
albumEntry = new AlbumEntry(bucketId, bucketName, photoEntry, false);
|
||||
albums.put(bucketId, albumEntry);
|
||||
if (cameraAlbumId == null && cameraFolder != null && path != null && path.startsWith(cameraFolder)) {
|
||||
albumsSorted.add(0, albumEntry);
|
||||
|
@ -2034,11 +2076,72 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
albums.clear();
|
||||
allPhotosAlbum = null;
|
||||
cursor = MediaStore.Images.Media.query(ApplicationLoader.applicationContext.getContentResolver(), MediaStore.Video.Media.EXTERNAL_CONTENT_URI, projectionVideo, "", null, MediaStore.Video.Media.DATE_TAKEN + " DESC");
|
||||
if (cursor != null) {
|
||||
int imageIdColumn = cursor.getColumnIndex(MediaStore.Video.Media._ID);
|
||||
int bucketIdColumn = cursor.getColumnIndex(MediaStore.Video.Media.BUCKET_ID);
|
||||
int bucketNameColumn = cursor.getColumnIndex(MediaStore.Video.Media.BUCKET_DISPLAY_NAME);
|
||||
int dataColumn = cursor.getColumnIndex(MediaStore.Video.Media.DATA);
|
||||
int dateColumn = cursor.getColumnIndex(MediaStore.Video.Media.DATE_TAKEN);
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
int imageId = cursor.getInt(imageIdColumn);
|
||||
int bucketId = cursor.getInt(bucketIdColumn);
|
||||
String bucketName = cursor.getString(bucketNameColumn);
|
||||
String path = cursor.getString(dataColumn);
|
||||
long dateTaken = cursor.getLong(dateColumn);
|
||||
|
||||
if (path == null || path.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PhotoEntry photoEntry = new PhotoEntry(bucketId, imageId, dateTaken, path, 0, true);
|
||||
|
||||
if (allPhotosAlbum == null) {
|
||||
allPhotosAlbum = new AlbumEntry(0, LocaleController.getString("AllVideo", R.string.AllVideo), photoEntry, true);
|
||||
videoAlbumsSorted.add(0, allPhotosAlbum);
|
||||
}
|
||||
if (allPhotosAlbum != null) {
|
||||
allPhotosAlbum.addPhoto(photoEntry);
|
||||
}
|
||||
|
||||
AlbumEntry albumEntry = albums.get(bucketId);
|
||||
if (albumEntry == null) {
|
||||
albumEntry = new AlbumEntry(bucketId, bucketName, photoEntry, true);
|
||||
albums.put(bucketId, albumEntry);
|
||||
if (cameraAlbumVideoId == null && cameraFolder != null && path != null && path.startsWith(cameraFolder)) {
|
||||
videoAlbumsSorted.add(0, albumEntry);
|
||||
cameraAlbumVideoId = bucketId;
|
||||
} else {
|
||||
videoAlbumsSorted.add(albumEntry);
|
||||
}
|
||||
}
|
||||
|
||||
albumEntry.addPhoto(photoEntry);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
try {
|
||||
cursor.close();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Integer cameraAlbumIdFinal = cameraAlbumId;
|
||||
final Integer cameraAlbumVideoIdFinal = cameraAlbumVideoId;
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.albumsDidLoaded, guid, albumsSorted, cameraAlbumIdFinal);
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.albumsDidLoaded, guid, albumsSorted, cameraAlbumIdFinal, videoAlbumsSorted, cameraAlbumVideoIdFinal);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ public class MessageObject {
|
|||
public TLRPC.Message messageOwner;
|
||||
public CharSequence messageText;
|
||||
public CharSequence linkDescription;
|
||||
public CharSequence caption;
|
||||
public MessageObject replyMessageObject;
|
||||
public int type;
|
||||
public int contentType;
|
||||
|
@ -52,7 +53,7 @@ public class MessageObject {
|
|||
public int audioProgressSec;
|
||||
public ArrayList<TLRPC.PhotoSize> photoThumbs;
|
||||
|
||||
private static TextPaint textPaint;
|
||||
public static TextPaint textPaint;
|
||||
public int lastLineWidth;
|
||||
public int textWidth;
|
||||
public int textHeight;
|
||||
|
@ -144,17 +145,35 @@ public class MessageObject {
|
|||
whoUser = MessagesController.getInstance().getUser(message.action.user_id);
|
||||
}
|
||||
if (whoUser != null && fromUser != null) {
|
||||
if (isOut()) {
|
||||
messageText = replaceWithLink(LocaleController.getString("ActionYouAddUser", R.string.ActionYouAddUser), "un2", whoUser);
|
||||
} else if (message.action.user_id == UserConfig.getClientUserId()) {
|
||||
messageText = replaceWithLink(LocaleController.getString("ActionAddUserYou", R.string.ActionAddUserYou), "un1", fromUser);
|
||||
if (whoUser.id == fromUser.id) {
|
||||
if (isOut()) {
|
||||
messageText = LocaleController.getString("ActionAddUserSelf", R.string.ActionAddUserSelf).replace("un1", LocaleController.getString("FromYou", R.string.FromYou));
|
||||
} else {
|
||||
messageText = replaceWithLink(LocaleController.getString("ActionAddUserSelf", R.string.ActionAddUserSelf), "un1", fromUser);
|
||||
}
|
||||
} else {
|
||||
messageText = replaceWithLink(LocaleController.getString("ActionAddUser", R.string.ActionAddUser), "un2", whoUser);
|
||||
messageText = replaceWithLink(messageText, "un1", fromUser);
|
||||
if (isOut()) {
|
||||
messageText = replaceWithLink(LocaleController.getString("ActionYouAddUser", R.string.ActionYouAddUser), "un2", whoUser);
|
||||
} else if (message.action.user_id == 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);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
messageText = LocaleController.getString("ActionAddUser", R.string.ActionAddUser).replace("un2", "").replace("un1", "");
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
} else {
|
||||
messageText = LocaleController.getString("ActionInviteUser", R.string.ActionInviteUser).replace("un1", "");
|
||||
}
|
||||
} else if (message.action instanceof TLRPC.TL_messageActionChatEditPhoto) {
|
||||
if (isOut()) {
|
||||
messageText = LocaleController.getString("ActionYouChangedPhoto", R.string.ActionYouChangedPhoto);
|
||||
|
@ -279,7 +298,7 @@ public class MessageObject {
|
|||
messageText = LocaleController.getString("AttachPhoto", R.string.AttachPhoto);
|
||||
} else if (message.media instanceof TLRPC.TL_messageMediaVideo) {
|
||||
messageText = LocaleController.getString("AttachVideo", R.string.AttachVideo);
|
||||
} else if (message.media instanceof TLRPC.TL_messageMediaGeo) {
|
||||
} else if (message.media instanceof TLRPC.TL_messageMediaGeo || message.media instanceof TLRPC.TL_messageMediaVenue) {
|
||||
messageText = LocaleController.getString("AttachLocation", R.string.AttachLocation);
|
||||
} else if (message.media instanceof TLRPC.TL_messageMediaContact) {
|
||||
messageText = LocaleController.getString("AttachContact", R.string.AttachContact);
|
||||
|
@ -314,7 +333,7 @@ public class MessageObject {
|
|||
contentType = type = 0;
|
||||
} else if (message.media instanceof TLRPC.TL_messageMediaPhoto) {
|
||||
contentType = type = 1;
|
||||
} else if (message.media instanceof TLRPC.TL_messageMediaGeo) {
|
||||
} else if (message.media instanceof TLRPC.TL_messageMediaGeo || message.media instanceof TLRPC.TL_messageMediaVenue) {
|
||||
contentType = 1;
|
||||
type = 4;
|
||||
} else if (message.media instanceof TLRPC.TL_messageMediaVideo) {
|
||||
|
@ -374,6 +393,7 @@ public class MessageObject {
|
|||
monthKey = String.format("%d_%02d", dateYear, dateMonth);
|
||||
}
|
||||
|
||||
generateCaption();
|
||||
if (generateLayout) {
|
||||
generateLayout();
|
||||
}
|
||||
|
@ -569,6 +589,41 @@ public class MessageObject {
|
|||
}
|
||||
}
|
||||
|
||||
public void generateCaption() {
|
||||
if (caption != null) {
|
||||
return;
|
||||
}
|
||||
if (messageOwner.media != null && messageOwner.media.caption != null && messageOwner.media.caption.length() > 0) {
|
||||
caption = Emoji.replaceEmoji(messageOwner.media.caption, textPaint.getFontMetricsInt(), AndroidUtilities.dp(20));
|
||||
if (containsUrls(caption)) {
|
||||
try {
|
||||
Linkify.addLinks((Spannable) caption, Linkify.WEB_URLS);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
addUsernamesAndHashtags(caption);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addUsernamesAndHashtags(CharSequence charSequence) {
|
||||
try {
|
||||
Pattern pattern = Pattern.compile("(^|\\s)@[a-zA-Z\\d_]{5,32}|(^|\\s)#[\\w\\.]+");
|
||||
Matcher matcher = pattern.matcher(charSequence);
|
||||
while (matcher.find()) {
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
if (charSequence.charAt(start) != '@' && charSequence.charAt(start) != '#') {
|
||||
start++;
|
||||
}
|
||||
URLSpanNoUnderline url = new URLSpanNoUnderline(charSequence.subSequence(start, end).toString());
|
||||
((Spannable) charSequence).setSpan(url, start, end, 0);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateLayout() {
|
||||
if (type != 0 || messageOwner.to_id == null || messageText == null || messageText.length() == 0) {
|
||||
return;
|
||||
|
@ -579,26 +634,19 @@ public class MessageObject {
|
|||
|
||||
if (messageText instanceof Spannable && containsUrls(messageText)) {
|
||||
if (messageText.length() < 100) {
|
||||
Linkify.addLinks((Spannable) messageText, Linkify.WEB_URLS | Linkify.PHONE_NUMBERS);
|
||||
} else {
|
||||
Linkify.addLinks((Spannable) messageText, Linkify.WEB_URLS);
|
||||
}
|
||||
|
||||
try {
|
||||
Pattern pattern = Pattern.compile("(^|\\s)@[a-zA-Z\\d_]{5,32}|(^|\\s)#[\\w\\.]+");
|
||||
Matcher matcher = pattern.matcher(messageText);
|
||||
while (matcher.find()) {
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
if (messageText.charAt(start) != '@' && messageText.charAt(start) != '#') {
|
||||
start++;
|
||||
}
|
||||
URLSpanNoUnderline url = new URLSpanNoUnderline(messageText.subSequence(start, end).toString());
|
||||
((Spannable) messageText).setSpan(url, start, end, 0);
|
||||
try {
|
||||
Linkify.addLinks((Spannable) messageText, Linkify.WEB_URLS | Linkify.PHONE_NUMBERS);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
Linkify.addLinks((Spannable) messageText, Linkify.WEB_URLS);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
addUsernamesAndHashtags(messageText);
|
||||
}
|
||||
|
||||
int maxWidth;
|
||||
|
@ -763,10 +811,33 @@ public class MessageObject {
|
|||
return (messageOwner.flags & TLRPC.MESSAGE_FLAG_UNREAD) != 0;
|
||||
}
|
||||
|
||||
public boolean isContentUnread() {
|
||||
return (messageOwner.flags & TLRPC.MESSAGE_FLAG_CONTENT_UNREAD) != 0;
|
||||
}
|
||||
|
||||
public void setIsRead() {
|
||||
messageOwner.flags &= ~TLRPC.MESSAGE_FLAG_UNREAD;
|
||||
}
|
||||
|
||||
public int getUnradFlags() {
|
||||
return getUnreadFlags(messageOwner);
|
||||
}
|
||||
|
||||
public static int getUnreadFlags(TLRPC.Message message) {
|
||||
int flags = 0;
|
||||
if ((message.flags & TLRPC.MESSAGE_FLAG_UNREAD) == 0) {
|
||||
flags |= 1;
|
||||
}
|
||||
if ((message.flags & TLRPC.MESSAGE_FLAG_CONTENT_UNREAD) == 0) {
|
||||
flags |= 2;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
public void setContentIsRead() {
|
||||
messageOwner.flags &= ~TLRPC.MESSAGE_FLAG_CONTENT_UNREAD;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return messageOwner.id;
|
||||
}
|
||||
|
@ -782,18 +853,27 @@ public class MessageObject {
|
|||
messageOwner.media instanceof TLRPC.TL_messageMediaVideo);
|
||||
}
|
||||
|
||||
public static void setIsUnread(TLRPC.Message message, boolean unread) {
|
||||
if (unread) {
|
||||
public static void setUnreadFlags(TLRPC.Message message, int flag) {
|
||||
if ((flag & 1) == 0) {
|
||||
message.flags |= TLRPC.MESSAGE_FLAG_UNREAD;
|
||||
} else {
|
||||
message.flags &= ~TLRPC.MESSAGE_FLAG_UNREAD;
|
||||
}
|
||||
if ((flag & 2) == 0) {
|
||||
message.flags |= TLRPC.MESSAGE_FLAG_CONTENT_UNREAD;
|
||||
} else {
|
||||
message.flags &= ~TLRPC.MESSAGE_FLAG_CONTENT_UNREAD;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isUnread(TLRPC.Message message) {
|
||||
return (message.flags & TLRPC.MESSAGE_FLAG_UNREAD) != 0;
|
||||
}
|
||||
|
||||
public static boolean isContentUnread(TLRPC.Message message) {
|
||||
return (message.flags & TLRPC.MESSAGE_FLAG_CONTENT_UNREAD) != 0;
|
||||
}
|
||||
|
||||
public static boolean isOut(TLRPC.Message message) {
|
||||
return (message.flags & TLRPC.MESSAGE_FLAG_OUT) != 0;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import org.telegram.messenger.FileLog;
|
|||
import org.telegram.messenger.R;
|
||||
import org.telegram.messenger.RPCRequest;
|
||||
import org.telegram.messenger.SerializedData;
|
||||
import org.telegram.messenger.TLClassStore;
|
||||
import org.telegram.messenger.TLObject;
|
||||
import org.telegram.messenger.TLRPC;
|
||||
import org.telegram.messenger.UserConfig;
|
||||
|
@ -34,7 +33,6 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
|
@ -45,13 +43,16 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
private ConcurrentHashMap<Integer, TLRPC.User> users = new ConcurrentHashMap<>(100, 1.0f, 2);
|
||||
private ConcurrentHashMap<String, TLRPC.User> usersByUsernames = new ConcurrentHashMap<>(100, 1.0f, 2);
|
||||
|
||||
private HashMap<Integer, TLRPC.ExportedChatInvite> exportedChats = new HashMap<>();
|
||||
|
||||
public ArrayList<TLRPC.TL_dialog> dialogs = new ArrayList<>();
|
||||
public ArrayList<TLRPC.TL_dialog> dialogsServerOnly = new ArrayList<>();
|
||||
public ConcurrentHashMap<Long, TLRPC.TL_dialog> dialogs_dict = new ConcurrentHashMap<>(100, 1.0f, 2);
|
||||
public HashMap<Integer, MessageObject> dialogMessage = new HashMap<>();
|
||||
public ConcurrentHashMap<Long, ArrayList<PrintingUser>> printingUsers = new ConcurrentHashMap<>(20, 1.0f, 2);
|
||||
public HashMap<Long, CharSequence> printingStrings = new HashMap<>();
|
||||
public HashMap<Long, Boolean> sendingTypings = new HashMap<>();
|
||||
public HashMap<Long, Integer> printingStringsTypes = new HashMap<>();
|
||||
public HashMap<Integer, HashMap<Long, Boolean>> sendingTypings = new HashMap<>();
|
||||
public ConcurrentHashMap<Integer, Integer> onlinePrivacy = new ConcurrentHashMap<>(20, 1.0f, 2);
|
||||
private int lastPrintingStringCount = 0;
|
||||
|
||||
|
@ -124,6 +125,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
public static class PrintingUser {
|
||||
public long lastTime;
|
||||
public int userId;
|
||||
public TLRPC.SendMessageAction action;
|
||||
}
|
||||
|
||||
private static volatile MessagesController Instance = null;
|
||||
|
@ -163,9 +165,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
byte[] bytes = Base64.decode(disabledFeaturesString, Base64.DEFAULT);
|
||||
if (bytes != null) {
|
||||
SerializedData data = new SerializedData(bytes);
|
||||
int count = data.readInt32();
|
||||
int count = data.readInt32(false);
|
||||
for (int a = 0; a < count; a++) {
|
||||
TLRPC.TL_disabledFeature feature = (TLRPC.TL_disabledFeature) TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
TLRPC.TL_disabledFeature feature = TLRPC.TL_disabledFeature.TLdeserialize(data, data.readInt32(false), false);
|
||||
if (feature != null && feature.feature != null && feature.description != null) {
|
||||
disabledFeatures.add(feature);
|
||||
}
|
||||
|
@ -372,6 +374,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
SecretChatHelper.getInstance().cleanUp();
|
||||
|
||||
dialogs_dict.clear();
|
||||
exportedChats.clear();
|
||||
dialogs.clear();
|
||||
dialogsServerOnly.clear();
|
||||
users.clear();
|
||||
|
@ -380,6 +383,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
dialogMessage.clear();
|
||||
printingUsers.clear();
|
||||
printingStrings.clear();
|
||||
printingStringsTypes.clear();
|
||||
onlinePrivacy.clear();
|
||||
totalDialogsCount = 0;
|
||||
lastPrintingStringCount = 0;
|
||||
|
@ -463,6 +467,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
return chat;
|
||||
}
|
||||
|
||||
public TLRPC.ExportedChatInvite getExportedInvite(int chat_id) {
|
||||
return exportedChats.get(chat_id);
|
||||
}
|
||||
|
||||
public void putExportedInvite(int chat_id, TLRPC.TL_chatInviteExported invite) {
|
||||
exportedChats.put(chat_id, invite);
|
||||
}
|
||||
|
||||
public boolean putUser(TLRPC.User user, boolean fromCache) {
|
||||
if (user == null) {
|
||||
return false;
|
||||
|
@ -564,7 +576,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
}
|
||||
|
||||
public void loadFullChat(final int chat_id, final int classGuid) {
|
||||
if (loadingFullChats.contains(chat_id) || loadedFullChats.contains(chat_id)) {
|
||||
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;
|
||||
}
|
||||
loadingFullChats.add(chat_id);
|
||||
|
@ -580,12 +596,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
loadingFullChats.remove((Integer)chat_id);
|
||||
exportedChats.put(chat_id, res.full_chat.exported_invite);
|
||||
loadingFullChats.remove((Integer) chat_id);
|
||||
loadedFullChats.add(chat_id);
|
||||
|
||||
putUsers(res.users, false);
|
||||
putChats(res.chats, false);
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, chat_id, res.full_chat.participants);
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatInfoDidLoaded, chat_id, res.full_chat.participants, classGuid);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
@ -617,7 +634,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
loadingFullUsers.remove((Integer)user.id);
|
||||
loadingFullUsers.remove((Integer) user.id);
|
||||
loadedFullUsers.add(user.id);
|
||||
String names = user.first_name + user.last_name + user.username;
|
||||
TLRPC.TL_userFull userFull = (TLRPC.TL_userFull)response;
|
||||
|
@ -1275,15 +1292,72 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
}
|
||||
}
|
||||
|
||||
public void updatePrintingStrings() {
|
||||
private String getUserNameForTyping(TLRPC.User user) {
|
||||
if (user == null) {
|
||||
return "";
|
||||
}
|
||||
if (user.first_name != null && user.first_name.length() > 0) {
|
||||
return user.first_name;
|
||||
} else if (user.last_name != null && user.last_name.length() > 0) {
|
||||
return user.last_name;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private void updatePrintingStrings() {
|
||||
final HashMap<Long, CharSequence> newPrintingStrings = new HashMap<>();
|
||||
final HashMap<Long, Integer> newPrintingStringsTypes = new HashMap<>();
|
||||
|
||||
ArrayList<Long> keys = new ArrayList<>(printingUsers.keySet());
|
||||
for (Long key : keys) {
|
||||
if (key > 0 || key.intValue() == 0) {
|
||||
newPrintingStrings.put(key, LocaleController.getString("Typing", R.string.Typing));
|
||||
for (HashMap.Entry<Long, ArrayList<PrintingUser>> entry : printingUsers.entrySet()) {
|
||||
long key = entry.getKey();
|
||||
ArrayList<PrintingUser> arr = entry.getValue();
|
||||
|
||||
int lower_id = (int) key;
|
||||
|
||||
if (lower_id > 0 || lower_id == 0 || arr.size() == 1) {
|
||||
PrintingUser pu = arr.get(0);
|
||||
TLRPC.User user = getUser(pu.userId);
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
if (pu.action instanceof TLRPC.TL_sendMessageUploadAudioAction || pu.action instanceof TLRPC.TL_sendMessageRecordAudioAction) {
|
||||
if (lower_id < 0) {
|
||||
newPrintingStrings.put(key, LocaleController.formatString("IsRecordingAudio", R.string.IsRecordingAudio, getUserNameForTyping(user)));
|
||||
} else {
|
||||
newPrintingStrings.put(key, LocaleController.getString("RecordingAudio", R.string.RecordingAudio));
|
||||
}
|
||||
newPrintingStringsTypes.put(key, 1);
|
||||
} else if (pu.action instanceof TLRPC.TL_sendMessageUploadVideoAction || pu.action instanceof TLRPC.TL_sendMessageRecordVideoAction) {
|
||||
if (lower_id < 0) {
|
||||
newPrintingStrings.put(key, LocaleController.formatString("IsSendingVideo", R.string.IsSendingVideo, getUserNameForTyping(user)));
|
||||
} else {
|
||||
newPrintingStrings.put(key, LocaleController.getString("SendingVideoStatus", R.string.SendingVideoStatus));
|
||||
}
|
||||
newPrintingStringsTypes.put(key, 2);
|
||||
} else if (pu.action instanceof TLRPC.TL_sendMessageUploadDocumentAction) {
|
||||
if (lower_id < 0) {
|
||||
newPrintingStrings.put(key, LocaleController.formatString("IsSendingFile", R.string.IsSendingFile, getUserNameForTyping(user)));
|
||||
} else {
|
||||
newPrintingStrings.put(key, LocaleController.getString("SendingFile", R.string.SendingFile));
|
||||
}
|
||||
newPrintingStringsTypes.put(key, 2);
|
||||
} else if (pu.action instanceof TLRPC.TL_sendMessageUploadPhotoAction) {
|
||||
if (lower_id < 0) {
|
||||
newPrintingStrings.put(key, LocaleController.formatString("IsSendingPhoto", R.string.IsSendingPhoto, getUserNameForTyping(user)));
|
||||
} else {
|
||||
newPrintingStrings.put(key, LocaleController.getString("SendingPhoto", R.string.SendingPhoto));
|
||||
}
|
||||
newPrintingStringsTypes.put(key, 2);
|
||||
} else {
|
||||
if (lower_id < 0) {
|
||||
newPrintingStrings.put(key, String.format("%s %s", getUserNameForTyping(user), LocaleController.getString("IsTyping", R.string.IsTyping)));
|
||||
} else {
|
||||
newPrintingStrings.put(key, LocaleController.getString("Typing", R.string.Typing));
|
||||
}
|
||||
newPrintingStringsTypes.put(key, 0);
|
||||
}
|
||||
} else {
|
||||
ArrayList<PrintingUser> arr = printingUsers.get(key);
|
||||
int count = 0;
|
||||
String label = "";
|
||||
for (PrintingUser pu : arr) {
|
||||
|
@ -1292,11 +1366,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
if (label.length() != 0) {
|
||||
label += ", ";
|
||||
}
|
||||
if (user.first_name != null && user.first_name.length() > 0) {
|
||||
label += user.first_name;
|
||||
} else if (user.last_name != null && user.last_name.length() > 0) {
|
||||
label += user.last_name;
|
||||
}
|
||||
label += getUserNameForTyping(user);
|
||||
count++;
|
||||
}
|
||||
if (count == 2) {
|
||||
|
@ -1304,15 +1374,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
}
|
||||
}
|
||||
if (label.length() != 0) {
|
||||
if (count > 1) {
|
||||
if (arr.size() > 2) {
|
||||
newPrintingStrings.put(key, String.format("%s %s", label, LocaleController.formatPluralString("AndMoreTyping", arr.size() - 2)));
|
||||
} else {
|
||||
newPrintingStrings.put(key, String.format("%s %s", label, LocaleController.getString("AreTyping", R.string.AreTyping)));
|
||||
}
|
||||
if (arr.size() > 2) {
|
||||
newPrintingStrings.put(key, String.format("%s %s", label, LocaleController.formatPluralString("AndMoreTyping", arr.size() - 2)));
|
||||
} else {
|
||||
newPrintingStrings.put(key, String.format("%s %s", label, LocaleController.getString("IsTyping", R.string.IsTyping)));
|
||||
newPrintingStrings.put(key, String.format("%s %s", label, LocaleController.getString("AreTyping", R.string.AreTyping)));
|
||||
}
|
||||
newPrintingStringsTypes.put(key, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1323,21 +1390,30 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
@Override
|
||||
public void run() {
|
||||
printingStrings = newPrintingStrings;
|
||||
printingStringsTypes = newPrintingStringsTypes;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void cancelTyping(long dialog_id) {
|
||||
sendingTypings.remove(dialog_id);
|
||||
public void cancelTyping(int action, long dialog_id) {
|
||||
HashMap<Long, Boolean> typings = sendingTypings.get(action);
|
||||
if (typings != null) {
|
||||
typings.remove(dialog_id);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendTyping(final long dialog_id, int classGuid) {
|
||||
public void sendTyping(final long dialog_id, final int action, int classGuid) {
|
||||
if (dialog_id == 0) {
|
||||
return;
|
||||
}
|
||||
if (sendingTypings.get(dialog_id) != null) {
|
||||
HashMap<Long, Boolean> typings = sendingTypings.get(action);
|
||||
if (typings != null && typings.get(dialog_id) != null) {
|
||||
return;
|
||||
}
|
||||
if (typings == null) {
|
||||
typings = new HashMap<>();
|
||||
sendingTypings.put(action, typings);
|
||||
}
|
||||
int lower_part = (int)dialog_id;
|
||||
int high_id = (int)(dialog_id >> 32);
|
||||
if (lower_part != 0) {
|
||||
|
@ -1364,21 +1440,41 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
return;
|
||||
}
|
||||
}
|
||||
req.action = new TLRPC.TL_sendMessageTypingAction();
|
||||
sendingTypings.put(dialog_id, true);
|
||||
if (action == 0) {
|
||||
req.action = new TLRPC.TL_sendMessageTypingAction();
|
||||
} else if (action == 1) {
|
||||
req.action = new TLRPC.TL_sendMessageRecordAudioAction();
|
||||
} else if (action == 2) {
|
||||
req.action = new TLRPC.TL_sendMessageCancelAction();
|
||||
} else if (action == 3) {
|
||||
req.action = new TLRPC.TL_sendMessageUploadDocumentAction();
|
||||
} else if (action == 4) {
|
||||
req.action = new TLRPC.TL_sendMessageUploadPhotoAction();
|
||||
} else if (action == 5) {
|
||||
req.action = new TLRPC.TL_sendMessageUploadVideoAction();
|
||||
}
|
||||
typings.put(dialog_id, true);
|
||||
long reqId = ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
|
||||
@Override
|
||||
public void run(TLObject response, TLRPC.TL_error error) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sendingTypings.remove(dialog_id);
|
||||
HashMap<Long, Boolean> typings = sendingTypings.get(action);
|
||||
if (typings != null) {
|
||||
typings.remove(dialog_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, true, RPCRequest.RPCRequestClassGeneric | RPCRequest.RPCRequestClassFailOnServerErrors);
|
||||
ConnectionsManager.getInstance().bindRequestToGuid(reqId, classGuid);
|
||||
if (classGuid != 0) {
|
||||
ConnectionsManager.getInstance().bindRequestToGuid(reqId, classGuid);
|
||||
}
|
||||
} else {
|
||||
if (action != 0) {
|
||||
return;
|
||||
}
|
||||
TLRPC.EncryptedChat chat = getEncryptedChat(high_id);
|
||||
if (chat.auth_key != null && chat.auth_key.length > 1 && chat instanceof TLRPC.TL_encryptedChat) {
|
||||
TLRPC.TL_messages_setEncryptedTyping req = new TLRPC.TL_messages_setEncryptedTyping();
|
||||
|
@ -1386,14 +1482,24 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
req.peer.chat_id = chat.id;
|
||||
req.peer.access_hash = chat.access_hash;
|
||||
req.typing = true;
|
||||
sendingTypings.put(dialog_id, true);
|
||||
typings.put(dialog_id, true);
|
||||
long reqId = ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
|
||||
@Override
|
||||
public void run(TLObject response, TLRPC.TL_error error) {
|
||||
sendingTypings.remove(dialog_id);
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
HashMap<Long, Boolean> typings = sendingTypings.get(action);
|
||||
if (typings != null) {
|
||||
typings.remove(dialog_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, true, RPCRequest.RPCRequestClassGeneric | RPCRequest.RPCRequestClassFailOnServerErrors);
|
||||
ConnectionsManager.getInstance().bindRequestToGuid(reqId, classGuid);
|
||||
if (classGuid != 0) {
|
||||
ConnectionsManager.getInstance().bindRequestToGuid(reqId, classGuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1567,7 +1673,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
currentDialog.unread_count = entry.getValue();
|
||||
}
|
||||
}
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload);
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_READ_DIALOG_MESSAGE);
|
||||
NotificationsController.getInstance().processDialogsUpdateRead(dialogsToUpdate);
|
||||
}
|
||||
});
|
||||
|
@ -1803,12 +1909,29 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
|
||||
dialogsEndReached = (dialogsRes.dialogs.size() == 0 || dialogsRes.dialogs.size() != count) && !isCache;
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload);
|
||||
generateUpdateMessage();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void markMessageContentAsRead(int mid) {
|
||||
TLRPC.TL_messages_readMessageContents req = new TLRPC.TL_messages_readMessageContents();
|
||||
req.id.add(mid);
|
||||
MessagesStorage.getInstance().markMessagesContentAsRead(req.id);
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesReadContent, req.id);
|
||||
ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
|
||||
@Override
|
||||
public void run(TLObject response, TLRPC.TL_error error) {
|
||||
if (error == null) {
|
||||
TLRPC.TL_messages_affectedMessages res = (TLRPC.TL_messages_affectedMessages) response;
|
||||
processNewDifferenceParams(-1, res.pts, -1, res.pts_count);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void markMessageAsRead(final long dialog_id, final long random_id, int ttl) {
|
||||
if (random_id == 0 || dialog_id == 0 || ttl <= 0) {
|
||||
return;
|
||||
|
@ -2030,7 +2153,6 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
public void run() {
|
||||
putUsers(updates.users, false);
|
||||
putChats(updates.chats, false);
|
||||
TLRPC.Chat chat = null;
|
||||
if (updates.chats != null && !updates.chats.isEmpty()) {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatDidCreated, updates.chats.get(0).id);
|
||||
} else {
|
||||
|
@ -2213,6 +2335,35 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
});
|
||||
}
|
||||
|
||||
public void generateUpdateMessage() {
|
||||
Utilities.stageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
String build = LocaleController.getString("updateBuild", R.string.updateBuild);
|
||||
if (build != null) {
|
||||
int version = Utilities.parseInt(build);
|
||||
if (version <= UserConfig.lastUpdateVersion) {
|
||||
return;
|
||||
}
|
||||
UserConfig.lastUpdateVersion = version;
|
||||
UserConfig.saveConfig(false);
|
||||
}
|
||||
TLRPC.TL_updateServiceNotification update = new TLRPC.TL_updateServiceNotification();
|
||||
update.message = LocaleController.getString("updateText", R.string.updateText);
|
||||
update.media = new TLRPC.TL_messageMediaEmpty();
|
||||
update.type = "update";
|
||||
update.popup = false;
|
||||
ArrayList<TLRPC.Update> updates = new ArrayList<>();
|
||||
updates.add(update);
|
||||
processUpdateArray(updates, null, null);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void registerForPush(final String regid) {
|
||||
if (regid == null || regid.length() == 0 || registeringForPush || UserConfig.getClientUserId() == 0) {
|
||||
return;
|
||||
|
@ -2226,7 +2377,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
req.token = regid;
|
||||
req.app_sandbox = false;
|
||||
try {
|
||||
req.lang_code = LocaleController.getLocaleString(Locale.getDefault());
|
||||
req.lang_code = LocaleController.getLocaleString(LocaleController.getInstance().getSystemDefaultLocale());
|
||||
if (req.lang_code == null || req.lang_code.length() == 0) {
|
||||
req.lang_code = "en";
|
||||
}
|
||||
req.device_model = Build.MANUFACTURER + Build.MODEL;
|
||||
if (req.device_model == null) {
|
||||
req.device_model = "Android unknown";
|
||||
|
@ -2359,7 +2513,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
} else if (type == 2) {
|
||||
if (updates.qts <= MessagesStorage.lastQtsValue) {
|
||||
return 2;
|
||||
} else if (MessagesStorage.lastQtsValue + 1 == updates.qts) {
|
||||
} else if (MessagesStorage.lastQtsValue + updates.updates.size() == updates.qts) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
|
@ -2375,14 +2529,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
Collections.sort(updatesQueue, new Comparator<TLRPC.Updates>() {
|
||||
@Override
|
||||
public int compare(TLRPC.Updates updates, TLRPC.Updates updates2) {
|
||||
int seq1 = getUpdateSeq(updates);
|
||||
int seq2 = getUpdateSeq(updates2);
|
||||
if (seq1 == seq2) {
|
||||
return 0;
|
||||
} else if (seq1 > seq2) {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
return AndroidUtilities.compare(getUpdateSeq(updates), getUpdateSeq(updates2));
|
||||
}
|
||||
});
|
||||
} else if (type == 1) {
|
||||
|
@ -2390,12 +2537,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
Collections.sort(updatesQueue, new Comparator<TLRPC.Updates>() {
|
||||
@Override
|
||||
public int compare(TLRPC.Updates updates, TLRPC.Updates updates2) {
|
||||
if (updates.pts == updates2.pts) {
|
||||
return 0;
|
||||
} else if (updates.pts > updates2.pts) {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
return AndroidUtilities.compare(updates.pts, updates2.pts);
|
||||
}
|
||||
});
|
||||
} else if (type == 2) {
|
||||
|
@ -2403,12 +2545,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
Collections.sort(updatesQueue, new Comparator<TLRPC.Updates>() {
|
||||
@Override
|
||||
public int compare(TLRPC.Updates updates, TLRPC.Updates updates2) {
|
||||
if (updates.qts == updates2.qts) {
|
||||
return 0;
|
||||
} else if (updates.qts > updates2.qts) {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
return AndroidUtilities.compare(updates.qts, updates2.qts);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2710,6 +2847,17 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
});
|
||||
}
|
||||
|
||||
private int getUpdateType(TLRPC.Update update) {
|
||||
if (update instanceof TLRPC.TL_updateNewMessage || update instanceof TLRPC.TL_updateReadMessagesContents || update instanceof TLRPC.TL_updateReadHistoryInbox ||
|
||||
update instanceof TLRPC.TL_updateReadHistoryOutbox || update instanceof TLRPC.TL_updateDeleteMessages) {
|
||||
return 0;
|
||||
} else if (update instanceof TLRPC.TL_updateNewEncryptedMessage) {
|
||||
return 1;
|
||||
} else {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
public void processUpdates(final TLRPC.Updates updates, boolean fromQueue) {
|
||||
boolean needGetDiff = false;
|
||||
boolean needReceivedQueue = false;
|
||||
|
@ -2719,11 +2867,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
arr.add(updates.update);
|
||||
processUpdateArray(arr, null, null);
|
||||
} else if (updates instanceof TLRPC.TL_updateShortChatMessage || updates instanceof TLRPC.TL_updateShortMessage) {
|
||||
TLRPC.User user = getUser(updates.user_id);
|
||||
final int user_id = updates instanceof TLRPC.TL_updateShortChatMessage ? updates.from_id : updates.user_id;
|
||||
TLRPC.User user = getUser(user_id);
|
||||
TLRPC.User user2 = null;
|
||||
|
||||
if (user == null) {
|
||||
user = MessagesStorage.getInstance().getUserSync(updates.user_id);
|
||||
user = MessagesStorage.getInstance().getUserSync(user_id);
|
||||
putUser(user, true);
|
||||
}
|
||||
|
||||
|
@ -2763,13 +2912,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
if ((updates.flags & TLRPC.MESSAGE_FLAG_OUT) != 0) {
|
||||
message.from_id = UserConfig.getClientUserId();
|
||||
} else {
|
||||
message.from_id = updates.user_id;
|
||||
message.from_id = user_id;
|
||||
}
|
||||
message.to_id = new TLRPC.TL_peerUser();
|
||||
message.to_id.user_id = updates.user_id;
|
||||
message.dialog_id = updates.user_id;
|
||||
message.to_id.user_id = user_id;
|
||||
message.dialog_id = user_id;
|
||||
} else {
|
||||
message.from_id = updates.user_id;
|
||||
message.from_id = user_id;
|
||||
message.to_id = new TLRPC.TL_peerChat();
|
||||
message.to_id.chat_id = updates.chat_id;
|
||||
message.dialog_id = -updates.chat_id;
|
||||
|
@ -2798,7 +2947,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
if (printUpdate) {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_USER_PRINT);
|
||||
}
|
||||
updateInterfaceWithMessages(updates.user_id, objArr);
|
||||
updateInterfaceWithMessages(user_id, objArr);
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload);
|
||||
}
|
||||
});
|
||||
|
@ -2840,7 +2989,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
if (updatesStartWaitTimePts == 0) {
|
||||
updatesStartWaitTimePts = System.currentTimeMillis();
|
||||
}
|
||||
FileLog.e("tmessages", "add short message to queue");
|
||||
FileLog.e("tmessages", "add to queue");
|
||||
updatesQueuePts.add(updates);
|
||||
} else {
|
||||
needGetDiff = true;
|
||||
|
@ -2849,56 +2998,92 @@ 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);
|
||||
int lastQtsValue = MessagesStorage.lastQtsValue;
|
||||
Collections.sort(updates.updates, new Comparator<TLRPC.Update>() {
|
||||
@Override
|
||||
public int compare(TLRPC.Update lhs, TLRPC.Update rhs) {
|
||||
int ltype = getUpdateType(lhs);
|
||||
int rtype = getUpdateType(rhs);
|
||||
if (ltype != rtype) {
|
||||
return AndroidUtilities.compare(ltype, rtype);
|
||||
} else if (ltype == 0) {
|
||||
return AndroidUtilities.compare(lhs.pts, rhs.pts);
|
||||
} else if (ltype == 1) {
|
||||
return AndroidUtilities.compare(lhs.qts, rhs.qts);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
for (int a = 0; a < updates.updates.size(); a++) {
|
||||
TLRPC.Update update = updates.updates.get(a);
|
||||
if (update instanceof TLRPC.TL_updateNewMessage || update instanceof TLRPC.TL_updateReadMessages || update instanceof TLRPC.TL_updateReadHistoryInbox ||
|
||||
update instanceof TLRPC.TL_updateReadHistoryOutbox || update instanceof TLRPC.TL_updateDeleteMessages) {
|
||||
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;
|
||||
if (MessagesStorage.lastPtsValue + update.pts_count == update.pts) {
|
||||
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 = update.pts;
|
||||
MessagesStorage.lastPtsValue = updatesNew.pts;
|
||||
}
|
||||
} else if (MessagesStorage.lastPtsValue != update.pts) {
|
||||
FileLog.e("tmessages", update + " need get diff, pts: " + MessagesStorage.lastPtsValue + " " + update.pts + " count = " + update.pts_count);
|
||||
} 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 && updatesStartWaitTimePts + 1500 > System.currentTimeMillis()) {
|
||||
if (updatesStartWaitTimePts == 0) {
|
||||
updatesStartWaitTimePts = System.currentTimeMillis();
|
||||
}
|
||||
FileLog.e("tmessages", "add short message to queue");
|
||||
FileLog.e("tmessages", "add to queue");
|
||||
updatesQueuePts.add(updatesNew);
|
||||
} else {
|
||||
needGetDiff = true;
|
||||
}
|
||||
}
|
||||
} else if (update instanceof TLRPC.TL_updateNewEncryptedMessage) {
|
||||
} else if (getUpdateType(update) == 1) {
|
||||
TLRPC.TL_updates updatesNew = new TLRPC.TL_updates();
|
||||
updatesNew.updates.add(update);
|
||||
updatesNew.qts = update.qts;
|
||||
if (MessagesStorage.lastQtsValue == 0 || MessagesStorage.lastQtsValue + 1 == update.qts) {
|
||||
for (int b = a + 1; b < updates.updates.size(); b++) {
|
||||
TLRPC.Update update2 = updates.updates.get(b);
|
||||
if (getUpdateType(update2) == 1 && updatesNew.qts + 1 == update2.qts) {
|
||||
updatesNew.updates.add(update2);
|
||||
updatesNew.qts = update2.qts;
|
||||
updates.updates.remove(b);
|
||||
b--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (MessagesStorage.lastQtsValue == 0 || MessagesStorage.lastQtsValue + updatesNew.updates.size() == updatesNew.qts) {
|
||||
processUpdateArray(updatesNew.updates, updates.users, updates.chats);
|
||||
MessagesStorage.lastQtsValue = update.qts;
|
||||
MessagesStorage.lastQtsValue = updatesNew.qts;
|
||||
needReceivedQueue = true;
|
||||
} else if (MessagesStorage.lastPtsValue != update.qts) {
|
||||
FileLog.e("tmessages", update + " need get diff, qts: " + MessagesStorage.lastQtsValue + " " + update.qts);
|
||||
} else if (MessagesStorage.lastPtsValue != updatesNew.qts) {
|
||||
FileLog.e("tmessages", update + " need get diff, qts: " + MessagesStorage.lastQtsValue + " " + updatesNew.qts);
|
||||
if (gettingDifference || updatesStartWaitTimeQts == 0 || updatesStartWaitTimeQts != 0 && updatesStartWaitTimeQts + 1500 > System.currentTimeMillis()) {
|
||||
if (updatesStartWaitTimeQts == 0) {
|
||||
updatesStartWaitTimeQts = System.currentTimeMillis();
|
||||
}
|
||||
FileLog.e("tmessages", "add short message to queue");
|
||||
FileLog.e("tmessages", "add to queue");
|
||||
updatesQueueQts.add(updatesNew);
|
||||
} else {
|
||||
needGetDiff = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
updates.updates.remove(a);
|
||||
a--;
|
||||
|
@ -2996,6 +3181,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
final ArrayList<TLRPC.Message> messagesArr = new ArrayList<>();
|
||||
final HashMap<Integer, Integer> markAsReadMessagesInbox = new HashMap<>();
|
||||
final HashMap<Integer, Integer> markAsReadMessagesOutbox = new HashMap<>();
|
||||
final ArrayList<Integer> markAsReadMessages = new ArrayList<>();
|
||||
final HashMap<Integer, Integer> markAsReadEncrypted = new HashMap<>();
|
||||
final ArrayList<Integer> deletedMessages = new ArrayList<>();
|
||||
boolean printChanged = false;
|
||||
|
@ -3078,8 +3264,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
if (!obj.isOut() && obj.isUnread()) {
|
||||
pushMessages.add(obj);
|
||||
}
|
||||
} else if (update instanceof TLRPC.TL_updateReadMessages) {
|
||||
//markAsReadMessages.addAll(update.messages); disabled for now
|
||||
} else if (update instanceof TLRPC.TL_updateReadMessagesContents) {
|
||||
markAsReadMessages.addAll(update.messages);
|
||||
} else if (update instanceof TLRPC.TL_updateReadHistoryInbox) {
|
||||
TLRPC.Peer peer = ((TLRPC.TL_updateReadHistoryInbox) update).peer;
|
||||
if (peer.chat_id != 0) {
|
||||
|
@ -3097,30 +3283,48 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
} else if (update instanceof TLRPC.TL_updateDeleteMessages) {
|
||||
deletedMessages.addAll(update.messages);
|
||||
} else if (update instanceof TLRPC.TL_updateUserTyping || update instanceof TLRPC.TL_updateChatUserTyping) {
|
||||
if (update.action instanceof TLRPC.TL_sendMessageTypingAction && update.user_id != UserConfig.getClientUserId()) {
|
||||
if (update.user_id != UserConfig.getClientUserId()) {
|
||||
long uid = -update.chat_id;
|
||||
if (uid == 0) {
|
||||
uid = update.user_id;
|
||||
}
|
||||
ArrayList<PrintingUser> arr = printingUsers.get(uid);
|
||||
if (arr == null) {
|
||||
arr = new ArrayList<>();
|
||||
printingUsers.put(uid, arr);
|
||||
}
|
||||
boolean exist = false;
|
||||
for (PrintingUser u : arr) {
|
||||
if (u.userId == update.user_id) {
|
||||
exist = true;
|
||||
u.lastTime = currentTime;
|
||||
break;
|
||||
if (update.action instanceof TLRPC.TL_sendMessageCancelAction) {
|
||||
if (arr != null) {
|
||||
for (int a = 0; a < arr.size(); a++) {
|
||||
PrintingUser pu = arr.get(a);
|
||||
if (pu.userId == update.user_id) {
|
||||
arr.remove(a);
|
||||
printChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (arr.isEmpty()) {
|
||||
printingUsers.remove(uid);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (arr == null) {
|
||||
arr = new ArrayList<>();
|
||||
printingUsers.put(uid, arr);
|
||||
}
|
||||
boolean exist = false;
|
||||
for (PrintingUser u : arr) {
|
||||
if (u.userId == update.user_id) {
|
||||
exist = true;
|
||||
u.lastTime = currentTime;
|
||||
u.action = update.action;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exist) {
|
||||
PrintingUser newUser = new PrintingUser();
|
||||
newUser.userId = update.user_id;
|
||||
newUser.lastTime = currentTime;
|
||||
newUser.action = update.action;
|
||||
arr.add(newUser);
|
||||
printChanged = true;
|
||||
}
|
||||
}
|
||||
if (!exist) {
|
||||
PrintingUser newUser = new PrintingUser();
|
||||
newUser.userId = update.user_id;
|
||||
newUser.lastTime = currentTime;
|
||||
arr.add(newUser);
|
||||
printChanged = true;
|
||||
}
|
||||
onlinePrivacy.put(update.user_id, ConnectionsManager.getInstance().getCurrentTime());
|
||||
}
|
||||
|
@ -3181,8 +3385,6 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
contactsIds.add(-update.user_id);
|
||||
}
|
||||
}
|
||||
} else if (update instanceof TLRPC.TL_updateActivation) {
|
||||
//DEPRECATED
|
||||
} else if (update instanceof TLRPC.TL_updateNewAuthorization) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
|
@ -3247,6 +3449,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
if (u.userId == update.user_id) {
|
||||
exist = true;
|
||||
u.lastTime = currentTime;
|
||||
u.action = new TLRPC.TL_sendMessageTypingAction();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -3254,6 +3457,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
PrintingUser newUser = new PrintingUser();
|
||||
newUser.userId = update.user_id;
|
||||
newUser.lastTime = currentTime;
|
||||
newUser.action = new TLRPC.TL_sendMessageTypingAction();
|
||||
arr.add(newUser);
|
||||
printChanged = true;
|
||||
}
|
||||
|
@ -3304,7 +3508,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
newMessage.local_id = newMessage.id = UserConfig.getNewMessageId();
|
||||
UserConfig.saveConfig(false);
|
||||
newMessage.flags = TLRPC.MESSAGE_FLAG_UNREAD;
|
||||
newMessage.date = update.date;
|
||||
newMessage.date = ConnectionsManager.getInstance().getCurrentTime();
|
||||
newMessage.from_id = 777000;
|
||||
newMessage.to_id = new TLRPC.TL_peerUser();
|
||||
newMessage.to_id.user_id = UserConfig.getClientUserId();
|
||||
|
@ -3403,14 +3607,16 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
}
|
||||
} else if (update instanceof TLRPC.TL_updateUserName) {
|
||||
if (currentUser != null) {
|
||||
if (!(currentUser instanceof TLRPC.TL_userContact)) {
|
||||
currentUser.first_name = update.first_name;
|
||||
currentUser.last_name = update.last_name;
|
||||
}
|
||||
if (currentUser.username != null && currentUser.username.length() > 0) {
|
||||
usersByUsernames.remove(currentUser.username);
|
||||
}
|
||||
if (update.username != null && update.username.length() > 0) {
|
||||
usersByUsernames.put(update.username, currentUser);
|
||||
}
|
||||
currentUser.first_name = update.first_name;
|
||||
currentUser.last_name = update.last_name;
|
||||
currentUser.username = update.username;
|
||||
}
|
||||
toDbUser.first_name = update.first_name;
|
||||
|
@ -3427,7 +3633,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
} else if (update instanceof TLRPC.TL_updateUserPhone) {
|
||||
if (currentUser != null) {
|
||||
currentUser.phone = update.phone;
|
||||
Utilities.photoBookQueue.postRunnable(new Runnable() {
|
||||
Utilities.phoneBookQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ContactsController.getInstance().addContactToPhoneBook(currentUser, true);
|
||||
|
@ -3563,7 +3769,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
}
|
||||
if (!markAsReadEncrypted.isEmpty()) {
|
||||
for (HashMap.Entry<Integer, Integer> entry : markAsReadEncrypted.entrySet()) {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesReadedEncrypted, entry.getKey(), entry.getValue());
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesReadEncrypted, entry.getKey(), entry.getValue());
|
||||
long dialog_id = (long) (entry.getKey()) << 32;
|
||||
TLRPC.TL_dialog dialog = dialogs_dict.get(dialog_id);
|
||||
if (dialog != null) {
|
||||
|
@ -3575,6 +3781,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
}
|
||||
}
|
||||
}
|
||||
if (!markAsReadMessages.isEmpty()) {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesReadContent, markAsReadMessages);
|
||||
}
|
||||
if (!deletedMessages.isEmpty()) {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesDeleted, deletedMessages);
|
||||
for (Integer id : deletedMessages) {
|
||||
|
@ -3601,6 +3810,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
}
|
||||
MessagesStorage.getInstance().markMessagesAsRead(markAsReadMessagesInbox, markAsReadMessagesOutbox, markAsReadEncrypted, true);
|
||||
}
|
||||
if (!markAsReadMessages.isEmpty()) {
|
||||
MessagesStorage.getInstance().markMessagesContentAsRead(markAsReadMessages);
|
||||
}
|
||||
if (!deletedMessages.isEmpty()) {
|
||||
MessagesStorage.getInstance().markMessagesAsDeleted(deletedMessages, true);
|
||||
}
|
||||
|
@ -3684,7 +3896,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
|||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.didReceivedNewMessages, uid, messages);
|
||||
|
||||
for (MessageObject message : messages) {
|
||||
if (lastMessage == null || (!isEncryptedChat && message.getId() > lastMessage.getId() || isEncryptedChat && message.getId() < lastMessage.getId()) || message.messageOwner.date > lastMessage.messageOwner.date) {
|
||||
if (lastMessage == null || (!isEncryptedChat && message.getId() > lastMessage.getId() || (isEncryptedChat || message.getId() < 0 && lastMessage.getId() < 0) && message.getId() < lastMessage.getId()) || message.messageOwner.date > lastMessage.messageOwner.date) {
|
||||
lastMessage = message;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ public class MessagesStorage {
|
|||
database.executeFast("CREATE TABLE messages(mid INTEGER PRIMARY KEY, uid INTEGER, read_state INTEGER, send_state INTEGER, date INTEGER, data BLOB, out INTEGER, ttl INTEGER, media INTEGER, replydata 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 dialogs(did INTEGER PRIMARY KEY, date INTEGER, unread_count INTEGER, last_mid INTEGER)").stepThis().dispose();
|
||||
database.executeFast("CREATE TABLE dialogs(did INTEGER PRIMARY KEY, date INTEGER, unread_count INTEGER, last_mid INTEGER, inbox_max INTEGER, outbox_max INTEGER)").stepThis().dispose();
|
||||
database.executeFast("CREATE TABLE chat_settings(uid INTEGER PRIMARY KEY, participants BLOB)").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();
|
||||
|
@ -164,7 +164,7 @@ public class MessagesStorage {
|
|||
database.executeFast("CREATE TABLE keyvalue(id TEXT PRIMARY KEY, value TEXT)").stepThis().dispose();
|
||||
|
||||
//version
|
||||
database.executeFast("PRAGMA user_version = 16").stepThis().dispose();
|
||||
database.executeFast("PRAGMA user_version = 17").stepThis().dispose();
|
||||
} else {
|
||||
try {
|
||||
SQLiteCursor cursor = database.queryFinalized("SELECT seq, pts, date, qts, lsv, sg, pbytes FROM params WHERE id = 1");
|
||||
|
@ -195,7 +195,7 @@ public class MessagesStorage {
|
|||
}
|
||||
}
|
||||
int version = database.executeInt("PRAGMA user_version");
|
||||
if (version < 16) {
|
||||
if (version < 17) {
|
||||
updateDbToLastVersion(version);
|
||||
}
|
||||
}
|
||||
|
@ -295,7 +295,7 @@ public class MessagesStorage {
|
|||
if ((length = cursor.byteBufferValue(1, data.buffer)) != 0) {
|
||||
for (int a = 0; a < length / 4; a++) {
|
||||
state.requery();
|
||||
state.bindInteger(1, data.readInt32());
|
||||
state.bindInteger(1, data.readInt32(false));
|
||||
state.bindInteger(2, date);
|
||||
state.step();
|
||||
}
|
||||
|
@ -385,6 +385,12 @@ public class MessagesStorage {
|
|||
database.executeFast("PRAGMA user_version = 16").stepThis().dispose();
|
||||
version = 16;
|
||||
}
|
||||
if (version == 16 && version < 17) {
|
||||
database.executeFast("ALTER TABLE dialogs ADD COLUMN inbox_max INTEGER default 0").stepThis().dispose();
|
||||
database.executeFast("ALTER TABLE dialogs ADD COLUMN outbox_max INTEGER default 0").stepThis().dispose();
|
||||
database.executeFast("PRAGMA user_version = 17").stepThis().dispose();
|
||||
version = 17;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
|
@ -523,12 +529,12 @@ public class MessagesStorage {
|
|||
ArrayList<Integer> chatIds = new ArrayList<>();
|
||||
ArrayList<Integer> encryptedChatIds = new ArrayList<>();
|
||||
|
||||
cursor = database.queryFinalized("SELECT read_state, data, send_state, mid, date, uid FROM messages WHERE uid IN (" + ids.toString() + ") AND out = 0 AND read_state = 0 ORDER BY date DESC LIMIT 50");
|
||||
cursor = database.queryFinalized("SELECT read_state, data, send_state, mid, date, uid FROM messages WHERE uid IN (" + ids.toString() + ") AND out = 0 AND read_state IN(0,2) ORDER BY date DESC LIMIT 50");
|
||||
while (cursor.next()) {
|
||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(1));
|
||||
if (data != null && cursor.byteBufferValue(1, data.buffer) != 0) {
|
||||
TLRPC.Message message = (TLRPC.Message)TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
MessageObject.setIsUnread(message, cursor.intValue(0) != 1);
|
||||
TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false);
|
||||
MessageObject.setUnreadFlags(message, cursor.intValue(0));
|
||||
message.id = cursor.intValue(3);
|
||||
message.date = cursor.intValue(4);
|
||||
message.dialog_id = cursor.longValue(5);
|
||||
|
@ -762,7 +768,7 @@ public class MessagesStorage {
|
|||
while (cursor.next()) {
|
||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
|
||||
TLRPC.WallPaper wallPaper = (TLRPC.WallPaper) TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
TLRPC.WallPaper wallPaper = TLRPC.WallPaper.TLdeserialize(data, data.readInt32(false), false);
|
||||
wallPapers.add(wallPaper);
|
||||
}
|
||||
buffersStorage.reuseFreeBuffer(data);
|
||||
|
@ -876,7 +882,7 @@ public class MessagesStorage {
|
|||
while (cursor.next()) {
|
||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
|
||||
TLRPC.Message message = (TLRPC.Message) TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false);
|
||||
if (message == null || message.media == null) {
|
||||
continue;
|
||||
}
|
||||
|
@ -950,7 +956,7 @@ public class MessagesStorage {
|
|||
while (cursor.next()) {
|
||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
|
||||
TLRPC.Photo photo = (TLRPC.Photo)TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
TLRPC.Photo photo = TLRPC.Photo.TLdeserialize(data, data.readInt32(false), false);
|
||||
res.photos.add(photo);
|
||||
}
|
||||
buffersStorage.reuseFreeBuffer(data);
|
||||
|
@ -1065,7 +1071,7 @@ public class MessagesStorage {
|
|||
StringBuilder mids = new StringBuilder();
|
||||
SQLiteCursor cursor = null;
|
||||
if (random_ids == null) {
|
||||
cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid, ttl FROM messages WHERE uid = %d AND out = %d AND read_state = 1 AND ttl > 0 AND date <= %d AND send_state = 0 AND media != 1", ((long) chat_id) << 32, isOut, time));
|
||||
cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid, ttl FROM messages WHERE uid = %d AND out = %d AND read_state != 0 AND ttl > 0 AND date <= %d AND send_state = 0 AND media != 1", ((long) chat_id) << 32, isOut, time));
|
||||
} else {
|
||||
String ids = TextUtils.join(",", random_ids);
|
||||
cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.mid, m.ttl FROM messages as m INNER JOIN randoms as r ON m.mid = r.mid WHERE r.random_id IN (%s)", ids));
|
||||
|
@ -1147,7 +1153,7 @@ public class MessagesStorage {
|
|||
cursor.dispose();
|
||||
} else if (inbox != null && !inbox.isEmpty()) {
|
||||
for (HashMap.Entry<Integer, Integer> entry : inbox.entrySet()) {
|
||||
SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM messages WHERE uid = %d AND mid <= %d AND read_state = 0 AND out = 0", entry.getKey(), entry.getValue()));
|
||||
SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM messages WHERE uid = %d AND mid <= %d AND read_state IN(0,2) AND out = 0", entry.getKey(), entry.getValue()));
|
||||
if (cursor.next()) {
|
||||
int count = cursor.intValue(0);
|
||||
if (count == 0) {
|
||||
|
@ -1255,7 +1261,7 @@ public class MessagesStorage {
|
|||
if (cursor.next()) {
|
||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
|
||||
info = (TLRPC.ChatParticipants)TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
info = TLRPC.ChatParticipants.TLdeserialize(data, data.readInt32(false), false);
|
||||
}
|
||||
buffersStorage.reuseFreeBuffer(data);
|
||||
}
|
||||
|
@ -1318,7 +1324,7 @@ public class MessagesStorage {
|
|||
if (cursor.next()) {
|
||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
|
||||
info = (TLRPC.ChatParticipants)TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
info = TLRPC.ChatParticipants.TLdeserialize(data, data.readInt32(false), false);
|
||||
}
|
||||
buffersStorage.reuseFreeBuffer(data);
|
||||
}
|
||||
|
@ -1383,14 +1389,14 @@ public class MessagesStorage {
|
|||
int lower_id = (int)dialog_id;
|
||||
|
||||
if (lower_id != 0) {
|
||||
state = database.executeFast("UPDATE messages SET read_state = 1 WHERE uid = ? AND mid <= ? AND read_state = 0 AND out = 0");
|
||||
state = database.executeFast("UPDATE messages SET read_state = read_state | 1 WHERE uid = ? AND mid <= ? AND read_state IN(0,2) AND out = 0");
|
||||
state.requery();
|
||||
state.bindLong(1, dialog_id);
|
||||
state.bindInteger(2, max_id);
|
||||
state.step();
|
||||
state.dispose();
|
||||
} else {
|
||||
state = database.executeFast("UPDATE messages SET read_state = 1 WHERE uid = ? AND date <= ? AND read_state = 0 AND out = 0");
|
||||
state = database.executeFast("UPDATE messages SET read_state = read_state | 1 WHERE uid = ? AND date <= ? AND read_state IN(0,2) AND out = 0");
|
||||
state.requery();
|
||||
state.bindLong(1, dialog_id);
|
||||
state.bindInteger(2, max_date);
|
||||
|
@ -1613,9 +1619,9 @@ public class MessagesStorage {
|
|||
while (cursor.next()) {
|
||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(1));
|
||||
if (data != null && cursor.byteBufferValue(1, data.buffer) != 0) {
|
||||
TLRPC.Message message = (TLRPC.Message)TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false);
|
||||
if (!messageHashMap.containsKey(message.id)) {
|
||||
MessageObject.setIsUnread(message, cursor.intValue(0) != 1);
|
||||
MessageObject.setUnreadFlags(message, cursor.intValue(0));
|
||||
message.id = cursor.intValue(3);
|
||||
message.date = cursor.intValue(4);
|
||||
if (!cursor.isNull(5)) {
|
||||
|
@ -1798,14 +1804,14 @@ public class MessagesStorage {
|
|||
}
|
||||
cursor.dispose();
|
||||
|
||||
cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid), max(date) FROM messages WHERE uid = %d AND out = 0 AND read_state = 0 AND mid > 0", dialog_id));
|
||||
cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid), max(date) FROM messages WHERE uid = %d AND out = 0 AND read_state IN(0,2) AND mid > 0", dialog_id));
|
||||
if (cursor.next()) {
|
||||
min_unread_id = cursor.intValue(0);
|
||||
max_unread_date = cursor.intValue(1);
|
||||
}
|
||||
cursor.dispose();
|
||||
if (min_unread_id != 0) {
|
||||
cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages WHERE uid = %d AND mid >= %d AND out = 0 AND read_state = 0", dialog_id, min_unread_id));
|
||||
cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages WHERE uid = %d AND mid >= %d AND out = 0 AND read_state IN(0,2)", dialog_id, min_unread_id));
|
||||
if (cursor.next()) {
|
||||
count_unread = cursor.intValue(0);
|
||||
}
|
||||
|
@ -1843,14 +1849,14 @@ public class MessagesStorage {
|
|||
}
|
||||
cursor.dispose();
|
||||
|
||||
cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid), max(date) FROM messages WHERE uid = %d AND out = 0 AND read_state = 0 AND mid < 0", dialog_id));
|
||||
cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid), max(date) FROM messages WHERE uid = %d AND out = 0 AND read_state IN(0,2) AND mid < 0", dialog_id));
|
||||
if (cursor.next()) {
|
||||
min_unread_id = cursor.intValue(0);
|
||||
max_unread_date = cursor.intValue(1);
|
||||
}
|
||||
cursor.dispose();
|
||||
if (min_unread_id != 0) {
|
||||
cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages WHERE uid = %d AND mid <= %d AND out = 0 AND read_state = 0", dialog_id, min_unread_id));
|
||||
cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages WHERE uid = %d AND mid <= %d AND out = 0 AND read_state IN(0,2)", dialog_id, min_unread_id));
|
||||
if (cursor.next()) {
|
||||
count_unread = cursor.intValue(0);
|
||||
}
|
||||
|
@ -1876,8 +1882,8 @@ public class MessagesStorage {
|
|||
while (cursor.next()) {
|
||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(1));
|
||||
if (data != null && cursor.byteBufferValue(1, data.buffer) != 0) {
|
||||
TLRPC.Message message = (TLRPC.Message) TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
MessageObject.setIsUnread(message, cursor.intValue(0) != 1);
|
||||
TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false);
|
||||
MessageObject.setUnreadFlags(message, cursor.intValue(0));
|
||||
message.id = cursor.intValue(3);
|
||||
message.date = cursor.intValue(4);
|
||||
message.dialog_id = dialog_id;
|
||||
|
@ -1900,7 +1906,7 @@ public class MessagesStorage {
|
|||
if (!cursor.isNull(6)) {
|
||||
ByteBufferDesc data2 = buffersStorage.getFreeBuffer(cursor.byteArrayLength(6));
|
||||
if (data2 != null && cursor.byteBufferValue(6, data2.buffer) != 0) {
|
||||
message.replyMessage = (TLRPC.Message) TLClassStore.Instance().TLdeserialize(data2, data2.readInt32());
|
||||
message.replyMessage = TLRPC.Message.TLdeserialize(data2, data2.readInt32(false), false);
|
||||
if (message.replyMessage != null) {
|
||||
fromUser.add(message.replyMessage.from_id);
|
||||
if (message.replyMessage.action != null && message.replyMessage.action.user_id != 0) {
|
||||
|
@ -1987,7 +1993,7 @@ public class MessagesStorage {
|
|||
while (cursor.next()) {
|
||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
|
||||
TLRPC.Message message = (TLRPC.Message) TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
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;
|
||||
|
@ -2091,7 +2097,7 @@ public class MessagesStorage {
|
|||
if (cursor.next()) {
|
||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
|
||||
TLObject file = TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
TLObject file = TLClassStore.Instance().TLdeserialize(data, data.readInt32(false), false);
|
||||
if (file != null) {
|
||||
result.add(file);
|
||||
}
|
||||
|
@ -2374,11 +2380,13 @@ public class MessagesStorage {
|
|||
buffersStorage.reuseFreeBuffer(data5);
|
||||
|
||||
if (dialog != null) {
|
||||
state = database.executeFast("REPLACE INTO dialogs(did, date, unread_count, last_mid) VALUES(?, ?, ?, ?)");
|
||||
state = database.executeFast("REPLACE INTO dialogs(did, date, unread_count, last_mid, inbox_max, outbox_max) VALUES(?, ?, ?, ?, ?, ?)");
|
||||
state.bindLong(1, dialog.id);
|
||||
state.bindInteger(2, dialog.last_message_date);
|
||||
state.bindInteger(3, dialog.unread_count);
|
||||
state.bindInteger(4, dialog.top_message);
|
||||
state.bindInteger(5, dialog.read_inbox_max_id);
|
||||
state.bindInteger(6, 0);
|
||||
state.step();
|
||||
state.dispose();
|
||||
}
|
||||
|
@ -2469,7 +2477,7 @@ public class MessagesStorage {
|
|||
try {
|
||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
|
||||
TLRPC.User user = (TLRPC.User)TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
TLRPC.User user = TLRPC.User.TLdeserialize(data, data.readInt32(false), false);
|
||||
if (user != null) {
|
||||
if (user.status != null) {
|
||||
user.status.expires = cursor.intValue(1);
|
||||
|
@ -2494,7 +2502,7 @@ public class MessagesStorage {
|
|||
try {
|
||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
|
||||
TLRPC.Chat chat = (TLRPC.Chat)TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
TLRPC.Chat chat = TLRPC.Chat.TLdeserialize(data, data.readInt32(false), false);
|
||||
if (chat != null) {
|
||||
result.add(chat);
|
||||
}
|
||||
|
@ -2511,13 +2519,12 @@ public class MessagesStorage {
|
|||
if (chatsToLoad == null || chatsToLoad.length() == 0 || result == null) {
|
||||
return;
|
||||
}
|
||||
//use_count INTEGER, exchange_id INTEGER, key_date INTEGER, fprint INTEGER, fauthkey BLOB
|
||||
SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, user, g, authkey, ttl, layer, seq_in, seq_out, use_count, exchange_id, key_date, fprint, fauthkey, khash FROM enc_chats WHERE uid IN(%s)", chatsToLoad));
|
||||
while (cursor.next()) {
|
||||
try {
|
||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
|
||||
TLRPC.EncryptedChat chat = (TLRPC.EncryptedChat)TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
TLRPC.EncryptedChat chat = TLRPC.EncryptedChat.TLdeserialize(data, data.readInt32(false), false);
|
||||
if (chat != null) {
|
||||
chat.user_id = cursor.intValue(1);
|
||||
if (usersToLoad != null && !usersToLoad.contains(chat.user_id)) {
|
||||
|
@ -2634,7 +2641,7 @@ public class MessagesStorage {
|
|||
downloadObject.id = cursor.longValue(0);
|
||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(2));
|
||||
if (data != null && cursor.byteBufferValue(2, data.buffer) != 0) {
|
||||
downloadObject.object = TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
downloadObject.object = TLClassStore.Instance().TLdeserialize(data, data.readInt32(false), false);
|
||||
}
|
||||
buffersStorage.reuseFreeBuffer(data);
|
||||
objects.add(downloadObject);
|
||||
|
@ -2691,7 +2698,7 @@ public class MessagesStorage {
|
|||
int mid = cursor.intValue(0);
|
||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(1));
|
||||
if (data != null && cursor.byteBufferValue(1, data.buffer) != 0) {
|
||||
TLRPC.Message message = (TLRPC.Message)TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false);
|
||||
if (message.media instanceof TLRPC.TL_messageMediaWebPage) {
|
||||
message.id = mid;
|
||||
message.media.webpage = webPages.get(message.media.webpage.id);
|
||||
|
@ -2865,7 +2872,7 @@ public class MessagesStorage {
|
|||
|
||||
state.bindInteger(1, messageId);
|
||||
state.bindLong(2, dialog_id);
|
||||
state.bindInteger(3, (MessageObject.isUnread(message) ? 0 : 1));
|
||||
state.bindInteger(3, MessageObject.getUnreadFlags(message));
|
||||
state.bindInteger(4, message.send_state);
|
||||
state.bindInteger(5, message.date);
|
||||
state.bindByteBuffer(6, data.buffer);
|
||||
|
@ -2954,7 +2961,7 @@ public class MessagesStorage {
|
|||
state4.dispose();
|
||||
state5.dispose();
|
||||
|
||||
state = database.executeFast("REPLACE INTO dialogs(did, date, unread_count, last_mid) VALUES(?, ?, ?, ?)");
|
||||
state = database.executeFast("REPLACE INTO dialogs(did, date, unread_count, last_mid, inbox_max, outbox_max) VALUES(?, ?, ?, ?, ?, ?)");
|
||||
for (HashMap.Entry<Long, TLRPC.Message> pair : messagesMap.entrySet()) {
|
||||
Long key = pair.getKey();
|
||||
|
||||
|
@ -2987,6 +2994,8 @@ public class MessagesStorage {
|
|||
}
|
||||
state.bindInteger(3, old_unread_count + unread_count);
|
||||
state.bindInteger(4, messageId);
|
||||
state.bindInteger(5, 0);
|
||||
state.bindInteger(6, 0);
|
||||
state.step();
|
||||
}
|
||||
state.dispose();
|
||||
|
@ -3294,8 +3303,10 @@ public class MessagesStorage {
|
|||
TLRPC.User updateUser = usersDict.get(user.id);
|
||||
if (updateUser != null) {
|
||||
if (updateUser.first_name != null && updateUser.last_name != null) {
|
||||
user.first_name = updateUser.first_name;
|
||||
user.last_name = updateUser.last_name;
|
||||
if (!(user instanceof TLRPC.TL_userContact)) {
|
||||
user.first_name = updateUser.first_name;
|
||||
user.last_name = updateUser.last_name;
|
||||
}
|
||||
user.username = updateUser.username;
|
||||
} else if (updateUser.photo != null) {
|
||||
user.photo = updateUser.photo;
|
||||
|
@ -3337,25 +3348,22 @@ public class MessagesStorage {
|
|||
}
|
||||
|
||||
private void markMessagesAsReadInternal(HashMap<Integer, Integer> inbox, HashMap<Integer, Integer> outbox, HashMap<Integer, Integer> encryptedMessages) {
|
||||
if (Thread.currentThread().getId() != storageQueue.getId()) {
|
||||
throw new RuntimeException("wrong db thread");
|
||||
}
|
||||
try {
|
||||
if (inbox != null) {
|
||||
for (HashMap.Entry<Integer, Integer> entry : inbox.entrySet()) {
|
||||
database.executeFast(String.format(Locale.US, "UPDATE messages SET read_state = 1 WHERE uid = %d AND mid <= %d AND read_state = 0 AND out = 0", entry.getKey(), entry.getValue())).stepThis().dispose();
|
||||
database.executeFast(String.format(Locale.US, "UPDATE messages SET read_state = read_state | 1 WHERE uid = %d AND mid <= %d AND read_state IN(0,2) AND out = 0", entry.getKey(), entry.getValue())).stepThis().dispose();
|
||||
}
|
||||
}
|
||||
if (outbox != null) {
|
||||
for (HashMap.Entry<Integer, Integer> entry : outbox.entrySet()) {
|
||||
database.executeFast(String.format(Locale.US, "UPDATE messages SET read_state = 1 WHERE uid = %d AND mid <= %d AND read_state = 0 AND out = 1", entry.getKey(), entry.getValue())).stepThis().dispose();
|
||||
database.executeFast(String.format(Locale.US, "UPDATE messages SET read_state = read_state | 1 WHERE uid = %d AND mid <= %d AND read_state IN(0,2) AND out = 1", entry.getKey(), entry.getValue())).stepThis().dispose();
|
||||
}
|
||||
}
|
||||
if (encryptedMessages != null && !encryptedMessages.isEmpty()) {
|
||||
for (HashMap.Entry<Integer, Integer> entry : encryptedMessages.entrySet()) {
|
||||
long dialog_id = ((long)entry.getKey()) << 32;
|
||||
int max_date = entry.getValue();
|
||||
SQLitePreparedStatement state = database.executeFast("UPDATE messages SET read_state = 1 WHERE uid = ? AND date <= ? AND read_state = 0 AND out = 1");
|
||||
SQLitePreparedStatement state = database.executeFast("UPDATE messages SET read_state = read_state | 1 WHERE uid = ? AND date <= ? AND read_state IN(0,2) AND out = 1");
|
||||
state.requery();
|
||||
state.bindLong(1, dialog_id);
|
||||
state.bindInteger(2, max_date);
|
||||
|
@ -3368,6 +3376,22 @@ public class MessagesStorage {
|
|||
}
|
||||
}
|
||||
|
||||
public void markMessagesContentAsRead(final ArrayList<Integer> mids) {
|
||||
if (mids == null || mids.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
storageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
database.executeFast(String.format(Locale.US, "UPDATE messages SET read_state = read_state | 2 WHERE mid IN (%s)", TextUtils.join(",", mids))).stepThis().dispose();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void markMessagesAsRead(final HashMap<Integer, Integer> inbox, final HashMap<Integer, Integer> outbox, final HashMap<Integer, Integer> encryptedMessages, boolean useQueue) {
|
||||
if (useQueue) {
|
||||
storageQueue.postRunnable(new Runnable() {
|
||||
|
@ -3433,7 +3457,7 @@ public class MessagesStorage {
|
|||
}
|
||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(1));
|
||||
if (data != null && cursor.byteBufferValue(1, data.buffer) != 0) {
|
||||
TLRPC.Message message = (TLRPC.Message)TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false);
|
||||
if (message == null || message.media == null) {
|
||||
continue;
|
||||
}
|
||||
|
@ -3527,8 +3551,8 @@ public class MessagesStorage {
|
|||
|
||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(4));
|
||||
if (data != null && cursor.byteBufferValue(4, data.buffer) != 0) {
|
||||
TLRPC.Message message = (TLRPC.Message)TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
MessageObject.setIsUnread(message, cursor.intValue(5) != 1);
|
||||
TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false);
|
||||
MessageObject.setUnreadFlags(message, cursor.intValue(5));
|
||||
message.id = cursor.intValue(6);
|
||||
message.send_state = cursor.intValue(7);
|
||||
int date = cursor.intValue(8);
|
||||
|
@ -3667,7 +3691,7 @@ public class MessagesStorage {
|
|||
message.serializeToStream(data);
|
||||
state.bindInteger(1, message.id);
|
||||
state.bindLong(2, dialog_id);
|
||||
state.bindInteger(3, (MessageObject.isUnread(message) ? 0 : 1));
|
||||
state.bindInteger(3, MessageObject.getUnreadFlags(message));
|
||||
state.bindInteger(4, message.send_state);
|
||||
state.bindInteger(5, message.date);
|
||||
state.bindByteBuffer(6, data.buffer);
|
||||
|
@ -3732,9 +3756,9 @@ public class MessagesStorage {
|
|||
|
||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(4));
|
||||
if (data != null && cursor.byteBufferValue(4, data.buffer) != 0) {
|
||||
TLRPC.Message message = (TLRPC.Message)TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false);
|
||||
if (message != null) {
|
||||
MessageObject.setIsUnread(message, cursor.intValue(5) != 1);
|
||||
MessageObject.setUnreadFlags(message, cursor.intValue(5));
|
||||
message.id = cursor.intValue(6);
|
||||
int date = cursor.intValue(9);
|
||||
if (date != 0) {
|
||||
|
@ -3831,7 +3855,7 @@ public class MessagesStorage {
|
|||
|
||||
if (!dialogs.dialogs.isEmpty()) {
|
||||
SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)");
|
||||
SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO dialogs(did, date, unread_count, last_mid) VALUES(?, ?, ?, ?)");
|
||||
SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO dialogs(did, date, unread_count, last_mid, inbox_max, outbox_max) VALUES(?, ?, ?, ?, ?, ?)");
|
||||
SQLitePreparedStatement state3 = database.executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)");
|
||||
SQLitePreparedStatement state4 = database.executeFast("REPLACE INTO dialog_settings VALUES(?, ?)");
|
||||
|
||||
|
@ -3850,7 +3874,7 @@ public class MessagesStorage {
|
|||
|
||||
state.bindInteger(1, message.id);
|
||||
state.bindInteger(2, uid);
|
||||
state.bindInteger(3, (MessageObject.isUnread(message) ? 0 : 1));
|
||||
state.bindInteger(3, MessageObject.getUnreadFlags(message));
|
||||
state.bindInteger(4, message.send_state);
|
||||
state.bindInteger(5, message.date);
|
||||
state.bindByteBuffer(6, data.buffer);
|
||||
|
@ -3863,6 +3887,8 @@ public class MessagesStorage {
|
|||
state2.bindInteger(2, message.date);
|
||||
state2.bindInteger(3, dialog.unread_count);
|
||||
state2.bindInteger(4, dialog.top_message);
|
||||
state2.bindInteger(5, dialog.read_inbox_max_id);
|
||||
state2.bindInteger(6, 0);
|
||||
state2.step();
|
||||
|
||||
state4.bindLong(1, uid);
|
||||
|
|
|
@ -23,7 +23,7 @@ import java.util.zip.ZipFile;
|
|||
|
||||
public class NativeLoader {
|
||||
|
||||
private final static int LIB_VERSION = 7;
|
||||
private final static int LIB_VERSION = 8;
|
||||
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";
|
||||
|
|
|
@ -32,7 +32,7 @@ public class NotificationCenter {
|
|||
public static final int mediaDidLoaded = totalEvents++;
|
||||
public static final int mediaCountDidLoaded = totalEvents++;
|
||||
public static final int encryptedChatUpdated = totalEvents++;
|
||||
public static final int messagesReadedEncrypted = totalEvents++;
|
||||
public static final int messagesReadEncrypted = totalEvents++;
|
||||
public static final int encryptedChatCreated = totalEvents++;
|
||||
public static final int userPhotosLoaded = totalEvents++;
|
||||
public static final int removeAllMessagesFromDialog = totalEvents++;
|
||||
|
@ -56,6 +56,9 @@ public class NotificationCenter {
|
|||
public static final int newSessionReceived = totalEvents++;
|
||||
public static final int didReceivedWebpages = totalEvents++;
|
||||
public static final int didReceivedWebpagesInUpdates = totalEvents++;
|
||||
public static final int stickersDidLoaded = totalEvents++;
|
||||
public static final int didReplacedPhotoInMemCache = totalEvents++;
|
||||
public static final int messagesReadContent = totalEvents++;
|
||||
|
||||
public static final int httpFileDidLoaded = totalEvents++;
|
||||
public static final int httpFileDidFailedLoad = totalEvents++;
|
||||
|
|
|
@ -17,10 +17,10 @@ import android.content.Intent;
|
|||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.media.SoundPool;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
|
@ -54,7 +54,9 @@ public class NotificationsController {
|
|||
|
||||
private DispatchQueue notificationsQueue = new DispatchQueue("notificationsQueue");
|
||||
private ArrayList<MessageObject> pushMessages = new ArrayList<>();
|
||||
private ArrayList<MessageObject> delayedPushMessages = new ArrayList<>();
|
||||
private HashMap<Integer, MessageObject> pushMessagesDict = new HashMap<>();
|
||||
private HashMap<Long, Point> smartNotificationsDialogs = new HashMap<>();
|
||||
private NotificationManagerCompat notificationManager = null;
|
||||
private HashMap<Long, Integer> pushDialogs = new HashMap<>();
|
||||
private HashMap<Long, Integer> wearNoticationsIds = new HashMap<>();
|
||||
|
@ -67,10 +69,14 @@ public class NotificationsController {
|
|||
private boolean notifyCheck = false;
|
||||
private int lastOnlineFromOtherDevice = 0;
|
||||
private boolean inChatSoundEnabled = true;
|
||||
private int lastBadgeCount;
|
||||
|
||||
private long lastSoundPlay;
|
||||
private MediaPlayer mediaPlayerIn;
|
||||
private MediaPlayer mediaPlayerOut;
|
||||
//private MediaPlayer mediaPlayerIn;
|
||||
//private MediaPlayer mediaPlayerOut;
|
||||
private SoundPool soundPool;
|
||||
private int soundIn;
|
||||
private int soundOut;
|
||||
protected AudioManager audioManager;
|
||||
|
||||
private static volatile NotificationsController Instance = null;
|
||||
|
@ -110,6 +116,7 @@ public class NotificationsController {
|
|||
popupMessages.clear();
|
||||
wearNoticationsIds.clear();
|
||||
notifyCheck = false;
|
||||
lastBadgeCount = 0;
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.clear();
|
||||
|
@ -187,7 +194,7 @@ public class NotificationsController {
|
|||
msg = LocaleController.formatString("NotificationMessageVideo", R.string.NotificationMessageVideo, ContactsController.formatName(user.first_name, user.last_name));
|
||||
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) {
|
||||
msg = LocaleController.formatString("NotificationMessageContact", R.string.NotificationMessageContact, ContactsController.formatName(user.first_name, user.last_name));
|
||||
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo) {
|
||||
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaVenue) {
|
||||
msg = LocaleController.formatString("NotificationMessageMap", R.string.NotificationMessageMap, ContactsController.formatName(user.first_name, user.last_name));
|
||||
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) {
|
||||
if (messageObject.isSticker()) {
|
||||
|
@ -214,8 +221,14 @@ public class NotificationsController {
|
|||
if (u2 == null) {
|
||||
return null;
|
||||
}
|
||||
msg = LocaleController.formatString("NotificationGroupAddMember", R.string.NotificationGroupAddMember, ContactsController.formatName(user.first_name, user.last_name), chat.title, ContactsController.formatName(u2.first_name, u2.last_name));
|
||||
if (user.id == u2.id) {
|
||||
msg = LocaleController.formatString("NotificationGroupAddSelf", R.string.NotificationGroupAddSelf, ContactsController.formatName(user.first_name, user.last_name), chat.title);
|
||||
} else {
|
||||
msg = LocaleController.formatString("NotificationGroupAddMember", R.string.NotificationGroupAddMember, ContactsController.formatName(user.first_name, user.last_name), chat.title, ContactsController.formatName(u2.first_name, u2.last_name));
|
||||
}
|
||||
}
|
||||
} else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionChatJoinedByLink) {
|
||||
msg = LocaleController.formatString("NotificationInvitedToGroupByLink", R.string.NotificationInvitedToGroupByLink, ContactsController.formatName(user.first_name, user.last_name), chat.title);
|
||||
} else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionChatEditTitle) {
|
||||
msg = LocaleController.formatString("NotificationEditedGroupName", R.string.NotificationEditedGroupName, ContactsController.formatName(user.first_name, user.last_name), messageObject.messageOwner.action.title);
|
||||
} else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionChatEditPhoto || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionChatDeletePhoto) {
|
||||
|
@ -248,7 +261,7 @@ public class NotificationsController {
|
|||
msg = LocaleController.formatString("NotificationMessageGroupVideo", R.string.NotificationMessageGroupVideo, ContactsController.formatName(user.first_name, user.last_name), chat.title);
|
||||
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) {
|
||||
msg = LocaleController.formatString("NotificationMessageGroupContact", R.string.NotificationMessageGroupContact, ContactsController.formatName(user.first_name, user.last_name), chat.title);
|
||||
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo) {
|
||||
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaVenue) {
|
||||
msg = LocaleController.formatString("NotificationMessageGroupMap", R.string.NotificationMessageGroupMap, ContactsController.formatName(user.first_name, user.last_name), chat.title);
|
||||
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) {
|
||||
if (messageObject.isSticker()) {
|
||||
|
@ -286,14 +299,14 @@ public class NotificationsController {
|
|||
|
||||
private void scheduleNotificationDelay(boolean onlineReason) {
|
||||
try {
|
||||
FileLog.e("tmessages", "delay notification start");
|
||||
FileLog.e("tmessages", "delay notification start, onlineReason = " + onlineReason);
|
||||
AlarmManager alarm = (AlarmManager) ApplicationLoader.applicationContext.getSystemService(Context.ALARM_SERVICE);
|
||||
PendingIntent pintent = PendingIntent.getService(ApplicationLoader.applicationContext, 0, new Intent(ApplicationLoader.applicationContext, NotificationDelay.class), 0);
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE);
|
||||
if (onlineReason) {
|
||||
alarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 3 * 1000, pintent);
|
||||
} else {
|
||||
alarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 500, pintent);
|
||||
alarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 1000, pintent);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
|
@ -302,7 +315,10 @@ public class NotificationsController {
|
|||
|
||||
protected void notificationDelayReached() {
|
||||
FileLog.e("tmessages", "delay reached");
|
||||
showOrUpdateNotification(true);
|
||||
if (!delayedPushMessages.isEmpty()) {
|
||||
showOrUpdateNotification(true);
|
||||
delayedPushMessages.clear();
|
||||
}
|
||||
}
|
||||
|
||||
protected void repeatNotificationMaybe() {
|
||||
|
@ -319,6 +335,7 @@ public class NotificationsController {
|
|||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
FileLog.e("tmessages", "set last online from other device = " + time);
|
||||
lastOnlineFromOtherDevice = time;
|
||||
}
|
||||
});
|
||||
|
@ -364,29 +381,47 @@ public class NotificationsController {
|
|||
boolean inAppPreview = false;
|
||||
boolean inAppPriority = false;
|
||||
int priority = 0;
|
||||
int priority_override = 0;
|
||||
int vibrate_override = 0;
|
||||
int priorityOverride = 0;
|
||||
int vibrateOverride = 0;
|
||||
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE);
|
||||
int notify_override = preferences.getInt("notify2_" + override_dialog_id, 0);
|
||||
if (notify_override == 3) {
|
||||
int mute_until = preferences.getInt("notifyuntil_" + override_dialog_id, 0);
|
||||
if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) {
|
||||
notify_override = 2;
|
||||
}
|
||||
}
|
||||
if (!notifyAboutLast || notify_override == 2 || (!preferences.getBoolean("EnableAll", true) || chat_id != 0 && !preferences.getBoolean("EnableGroup", true)) && notify_override == 0) {
|
||||
int notifyOverride = getNotifyOverride(preferences, override_dialog_id);
|
||||
if (!notifyAboutLast || notifyOverride == 2 || (!preferences.getBoolean("EnableAll", true) || chat_id != 0 && !preferences.getBoolean("EnableGroup", true)) && notifyOverride == 0) {
|
||||
notifyDisabled = true;
|
||||
}
|
||||
|
||||
if (!notifyDisabled && dialog_id == override_dialog_id && chat != null) {
|
||||
int notifyMaxCount = preferences.getInt("smart_max_count_" + dialog_id, 2);
|
||||
int notifyDelay = preferences.getInt("smart_delay_" + dialog_id, 3 * 60);
|
||||
if (notifyMaxCount != 0) {
|
||||
Point dialogInfo = smartNotificationsDialogs.get(dialog_id);
|
||||
if (dialogInfo == null) {
|
||||
dialogInfo = new Point(1, (int) (System.currentTimeMillis() / 1000));
|
||||
smartNotificationsDialogs.put(dialog_id, dialogInfo);
|
||||
} else {
|
||||
int lastTime = dialogInfo.y;
|
||||
if (lastTime + notifyDelay < System.currentTimeMillis() / 1000) {
|
||||
dialogInfo.set(1, (int) (System.currentTimeMillis() / 1000));
|
||||
} else {
|
||||
int count = dialogInfo.x;
|
||||
if (count < notifyMaxCount) {
|
||||
dialogInfo.set(count + 1, (int) (System.currentTimeMillis() / 1000));
|
||||
} else {
|
||||
notifyDisabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String defaultPath = Settings.System.DEFAULT_NOTIFICATION_URI.getPath();
|
||||
if (!notifyDisabled) {
|
||||
inAppSounds = preferences.getBoolean("EnableInAppSounds", true);
|
||||
inAppVibrate = preferences.getBoolean("EnableInAppVibrate", true);
|
||||
inAppPreview = preferences.getBoolean("EnableInAppPreview", true);
|
||||
inAppPriority = preferences.getBoolean("EnableInAppPriority", false);
|
||||
vibrate_override = preferences.getInt("vibrate_" + dialog_id, 0);
|
||||
priority_override = preferences.getInt("priority_" + dialog_id, 3);
|
||||
vibrateOverride = preferences.getInt("vibrate_" + dialog_id, 0);
|
||||
priorityOverride = preferences.getInt("priority_" + dialog_id, 3);
|
||||
boolean vibrateOnlyIfSilent = false;
|
||||
|
||||
choosenSoundPath = preferences.getString("sound_path_" + dialog_id, null);
|
||||
|
@ -413,16 +448,16 @@ public class NotificationsController {
|
|||
ledColor = preferences.getInt("color_" + dialog_id, 0);
|
||||
}
|
||||
|
||||
if (priority_override != 3) {
|
||||
priority = priority_override;
|
||||
if (priorityOverride != 3) {
|
||||
priority = priorityOverride;
|
||||
}
|
||||
|
||||
if (needVibrate == 4) {
|
||||
vibrateOnlyIfSilent = true;
|
||||
needVibrate = 0;
|
||||
}
|
||||
if (needVibrate == 2 && (vibrate_override == 1 || vibrate_override == 3 || vibrate_override == 5) || needVibrate != 2 && vibrate_override == 2 || vibrate_override != 0) {
|
||||
needVibrate = vibrate_override;
|
||||
if (needVibrate == 2 && (vibrateOverride == 1 || vibrateOverride == 3 || vibrateOverride == 5) || needVibrate != 2 && vibrateOverride == 2 || vibrateOverride != 0) {
|
||||
needVibrate = vibrateOverride;
|
||||
}
|
||||
if (!ApplicationLoader.mainInterfacePaused) {
|
||||
if (!inAppSounds) {
|
||||
|
@ -524,9 +559,6 @@ public class NotificationsController {
|
|||
if (chat == null && user != null && user.phone != null && user.phone.length() > 0) {
|
||||
mBuilder.addPerson("tel:+" + user.phone);
|
||||
}
|
||||
/*Bundle bundle = new Bundle();
|
||||
bundle.putString(NotificationCompat.EXTRA_PEOPLE, );
|
||||
mBuilder.setExtras()*/
|
||||
|
||||
String lastMessage = null;
|
||||
String lastMessageFull = null;
|
||||
|
@ -558,7 +590,6 @@ public class NotificationsController {
|
|||
}
|
||||
if (i == 0) {
|
||||
lastMessageFull = message;
|
||||
//lastMessage = getStringForMessage(pushMessages.get(i), true);
|
||||
lastMessage = lastMessageFull;
|
||||
}
|
||||
if (pushDialogs.size() == 1) {
|
||||
|
@ -790,6 +821,7 @@ public class NotificationsController {
|
|||
}
|
||||
popupMessages.remove(messageObject);
|
||||
pushMessagesDict.remove(messageObject.getId());
|
||||
delayedPushMessages.remove(messageObject);
|
||||
pushMessages.remove(a);
|
||||
a--;
|
||||
}
|
||||
|
@ -821,6 +853,7 @@ public class NotificationsController {
|
|||
personal_count--;
|
||||
}
|
||||
pushMessages.remove(a);
|
||||
delayedPushMessages.remove(messageObject);
|
||||
popupMessages.remove(messageObject);
|
||||
pushMessagesDict.remove(messageObject.getId());
|
||||
a--;
|
||||
|
@ -847,14 +880,8 @@ public class NotificationsController {
|
|||
|
||||
try {
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE);
|
||||
int notify_override = preferences.getInt("notify2_" + openned_dialog_id, 0);
|
||||
if (notify_override == 3) {
|
||||
int mute_until = preferences.getInt("notifyuntil_" + openned_dialog_id, 0);
|
||||
if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) {
|
||||
notify_override = 2;
|
||||
}
|
||||
}
|
||||
if (notify_override == 2) {
|
||||
int notifyOverride = getNotifyOverride(preferences, openned_dialog_id);
|
||||
if (notifyOverride == 2) {
|
||||
return;
|
||||
}
|
||||
notificationsQueue.postRunnable(new Runnable() {
|
||||
|
@ -864,7 +891,14 @@ public class NotificationsController {
|
|||
return;
|
||||
}
|
||||
try {
|
||||
if (mediaPlayerIn == null) {
|
||||
if (soundPool == null) {
|
||||
soundPool = new SoundPool(4, AudioManager.STREAM_SYSTEM, 0);
|
||||
}
|
||||
if (soundIn == 0) {
|
||||
soundIn = soundPool.load(ApplicationLoader.applicationContext, R.raw.sound_in, 1);
|
||||
}
|
||||
soundPool.play(soundIn, 1.0f, 1.0f, 1, 0, 1.0f);
|
||||
/*if (mediaPlayerIn == null) {
|
||||
AssetFileDescriptor assetFileDescriptor = ApplicationLoader.applicationContext.getResources().openRawResourceFd(R.raw.sound_in);
|
||||
if (assetFileDescriptor != null) {
|
||||
mediaPlayerIn = new MediaPlayer();
|
||||
|
@ -875,45 +909,18 @@ public class NotificationsController {
|
|||
mediaPlayerIn.prepare();
|
||||
}
|
||||
}
|
||||
mediaPlayerIn.start();
|
||||
try {
|
||||
mediaPlayerIn.pause();
|
||||
mediaPlayerIn.seekTo(0);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
mediaPlayerIn.start();*/
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
/*String choosenSoundPath = null;
|
||||
String defaultPath = Settings.System.DEFAULT_NOTIFICATION_URI.getPath();
|
||||
|
||||
choosenSoundPath = preferences.getString("sound_path_" + openned_dialog_id, null);
|
||||
boolean isChat = (int)(openned_dialog_id) < 0;
|
||||
if (isChat) {
|
||||
if (choosenSoundPath != null && choosenSoundPath.equals(defaultPath)) {
|
||||
choosenSoundPath = null;
|
||||
} else if (choosenSoundPath == null) {
|
||||
choosenSoundPath = preferences.getString("GroupSoundPath", defaultPath);
|
||||
}
|
||||
} else {
|
||||
if (choosenSoundPath != null && choosenSoundPath.equals(defaultPath)) {
|
||||
choosenSoundPath = null;
|
||||
} else if (choosenSoundPath == null) {
|
||||
choosenSoundPath = preferences.getString("GlobalSoundPath", defaultPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (choosenSoundPath != null && !choosenSoundPath.equals("NoSound")) {
|
||||
if (lastMediaPlayerUri == null || !choosenSoundPath.equals(lastMediaPlayerUri)) {
|
||||
lastMediaPlayerUri = choosenSoundPath;
|
||||
mediaPlayer.reset();
|
||||
mediaPlayer.setAudioStreamType(AudioManager.STREAM_NOTIFICATION);
|
||||
if (choosenSoundPath.equals(defaultPath)) {
|
||||
mediaPlayer.setDataSource(ApplicationLoader.applicationContext, Settings.System.DEFAULT_NOTIFICATION_URI);
|
||||
} else {
|
||||
mediaPlayer.setDataSource(ApplicationLoader.applicationContext, Uri.parse(choosenSoundPath));
|
||||
}
|
||||
mediaPlayer.prepare();
|
||||
}
|
||||
mediaPlayer.start();
|
||||
}*/
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
|
@ -934,7 +941,14 @@ public class NotificationsController {
|
|||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (mediaPlayerOut == null) {
|
||||
if (soundPool == null) {
|
||||
soundPool = new SoundPool(4, AudioManager.STREAM_SYSTEM, 0);
|
||||
}
|
||||
if (soundOut == 0) {
|
||||
soundOut = soundPool.load(ApplicationLoader.applicationContext, R.raw.sound_out, 1);
|
||||
}
|
||||
soundPool.play(soundOut, 1.0f, 1.0f, 1, 0, 1.0f);
|
||||
/*if (mediaPlayerOut == null) {
|
||||
AssetFileDescriptor assetFileDescriptor = ApplicationLoader.applicationContext.getResources().openRawResourceFd(R.raw.sound_out);
|
||||
if (assetFileDescriptor != null) {
|
||||
mediaPlayerOut = new MediaPlayer();
|
||||
|
@ -945,7 +959,13 @@ public class NotificationsController {
|
|||
mediaPlayerOut.prepare();
|
||||
}
|
||||
}
|
||||
mediaPlayerOut.start();
|
||||
try {
|
||||
mediaPlayerOut.pause();
|
||||
mediaPlayerOut.seekTo(0);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
mediaPlayerOut.start();*/
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
|
@ -953,6 +973,17 @@ public class NotificationsController {
|
|||
});
|
||||
}
|
||||
|
||||
private int getNotifyOverride(SharedPreferences preferences, long dialog_id) {
|
||||
int notifyOverride = preferences.getInt("notify2_" + dialog_id, 0);
|
||||
if (notifyOverride == 3) {
|
||||
int muteUntil = preferences.getInt("notifyuntil_" + dialog_id, 0);
|
||||
if (muteUntil >= ConnectionsManager.getInstance().getCurrentTime()) {
|
||||
notifyOverride = 2;
|
||||
}
|
||||
}
|
||||
return notifyOverride;
|
||||
}
|
||||
|
||||
public void processNewMessages(ArrayList<MessageObject> messageObjects, boolean isLast) {
|
||||
if (messageObjects.isEmpty()) {
|
||||
return;
|
||||
|
@ -986,20 +1017,15 @@ public class NotificationsController {
|
|||
boolean isChat = (int)dialog_id < 0;
|
||||
popup = (int)dialog_id == 0 ? 0 : preferences.getInt(isChat ? "popupGroup" : "popupAll", 0);
|
||||
if (value == null) {
|
||||
int notify_override = preferences.getInt("notify2_" + dialog_id, 0);
|
||||
if (notify_override == 3) {
|
||||
int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0);
|
||||
if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) {
|
||||
notify_override = 2;
|
||||
}
|
||||
}
|
||||
value = !(notify_override == 2 || (!preferences.getBoolean("EnableAll", true) || isChat && !preferences.getBoolean("EnableGroup", true)) && notify_override == 0);
|
||||
int notifyOverride = getNotifyOverride(preferences, dialog_id);
|
||||
value = !(notifyOverride == 2 || (!preferences.getBoolean("EnableAll", true) || isChat && !preferences.getBoolean("EnableGroup", true)) && notifyOverride == 0);
|
||||
settingsCache.put(dialog_id, value);
|
||||
}
|
||||
if (value) {
|
||||
if (popup != 0) {
|
||||
popupMessages.add(0, messageObject);
|
||||
}
|
||||
delayedPushMessages.add(messageObject);
|
||||
pushMessages.add(0, messageObject);
|
||||
pushMessagesDict.put(messageObject.getId(), messageObject);
|
||||
if (original_dialog_id != dialog_id) {
|
||||
|
@ -1030,24 +1056,22 @@ public class NotificationsController {
|
|||
for (HashMap.Entry<Long, Integer> entry : dialogsToUpdate.entrySet()) {
|
||||
long dialog_id = entry.getKey();
|
||||
|
||||
int notify_override = preferences.getInt("notify2_" + dialog_id, 0);
|
||||
if (notify_override == 3) {
|
||||
int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0);
|
||||
if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) {
|
||||
notify_override = 2;
|
||||
}
|
||||
}
|
||||
int notifyOverride = getNotifyOverride(preferences, dialog_id);
|
||||
if (notifyCheck) {
|
||||
Integer override = pushDialogsOverrideMention.get(dialog_id);
|
||||
if (override != null && override == 1) {
|
||||
pushDialogsOverrideMention.put(dialog_id, 0);
|
||||
notify_override = 1;
|
||||
notifyOverride = 1;
|
||||
}
|
||||
}
|
||||
boolean canAddValue = !(notify_override == 2 || (!preferences.getBoolean("EnableAll", true) || ((int)dialog_id < 0) && !preferences.getBoolean("EnableGroup", true)) && notify_override == 0);
|
||||
boolean canAddValue = !(notifyOverride == 2 || (!preferences.getBoolean("EnableAll", true) || ((int)dialog_id < 0) && !preferences.getBoolean("EnableGroup", true)) && notifyOverride == 0);
|
||||
|
||||
Integer currentCount = pushDialogs.get(dialog_id);
|
||||
Integer newCount = entry.getValue();
|
||||
if (newCount == 0) {
|
||||
smartNotificationsDialogs.remove(dialog_id);
|
||||
}
|
||||
|
||||
if (newCount < 0) {
|
||||
if (currentCount == null) {
|
||||
continue;
|
||||
|
@ -1070,6 +1094,7 @@ public class NotificationsController {
|
|||
}
|
||||
pushMessages.remove(a);
|
||||
a--;
|
||||
delayedPushMessages.remove(messageObject);
|
||||
pushMessagesDict.remove(messageObject.getId());
|
||||
popupMessages.remove(messageObject);
|
||||
}
|
||||
|
@ -1079,17 +1104,18 @@ public class NotificationsController {
|
|||
pushDialogs.put(dialog_id, newCount);
|
||||
}
|
||||
}
|
||||
/*if (old_unread_count != total_unread_count) { TODO
|
||||
if (lastOnlineFromOtherDevice > ConnectionsManager.getInstance().getCurrentTime()) {
|
||||
showOrUpdateNotification(false);
|
||||
scheduleNotificationDelay(true);
|
||||
} else {
|
||||
showOrUpdateNotification(notifyCheck);
|
||||
}
|
||||
}*/
|
||||
if (old_unread_count != total_unread_count) {
|
||||
showOrUpdateNotification(notifyCheck);
|
||||
if (!notifyCheck) {
|
||||
delayedPushMessages.clear();
|
||||
showOrUpdateNotification(notifyCheck);
|
||||
} else {
|
||||
showOrUpdateNotification(false);
|
||||
scheduleNotificationDelay(lastOnlineFromOtherDevice > ConnectionsManager.getInstance().getCurrentTime());
|
||||
}
|
||||
}
|
||||
/*if (old_unread_count != total_unread_count) {
|
||||
showOrUpdateNotification(notifyCheck);
|
||||
}*/
|
||||
notifyCheck = false;
|
||||
if (preferences.getBoolean("badgeNumber", true)) {
|
||||
setBadge(ApplicationLoader.applicationContext, total_unread_count);
|
||||
|
@ -1125,14 +1151,8 @@ public class NotificationsController {
|
|||
}
|
||||
Boolean value = settingsCache.get(dialog_id);
|
||||
if (value == null) {
|
||||
int notify_override = preferences.getInt("notify2_" + dialog_id, 0);
|
||||
if (notify_override == 3) {
|
||||
int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0);
|
||||
if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) {
|
||||
notify_override = 2;
|
||||
}
|
||||
}
|
||||
value = !(notify_override == 2 || (!preferences.getBoolean("EnableAll", true) || ((int) dialog_id < 0) && !preferences.getBoolean("EnableGroup", true)) && notify_override == 0);
|
||||
int notifyOverride = getNotifyOverride(preferences, dialog_id);
|
||||
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) {
|
||||
|
@ -1149,19 +1169,13 @@ public class NotificationsController {
|
|||
long dialog_id = entry.getKey();
|
||||
Boolean value = settingsCache.get(dialog_id);
|
||||
if (value == null) {
|
||||
int notify_override = preferences.getInt("notify2_" + dialog_id, 0);
|
||||
if (notify_override == 3) {
|
||||
int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0);
|
||||
if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) {
|
||||
notify_override = 2;
|
||||
}
|
||||
}
|
||||
int notifyOverride = getNotifyOverride(preferences, dialog_id);
|
||||
Integer override = pushDialogsOverrideMention.get(dialog_id);
|
||||
if (override != null && override == 1) {
|
||||
pushDialogsOverrideMention.put(dialog_id, 0);
|
||||
notify_override = 1;
|
||||
notifyOverride = 1;
|
||||
}
|
||||
value = !(notify_override == 2 || (!preferences.getBoolean("EnableAll", true) || ((int) dialog_id < 0) && !preferences.getBoolean("EnableGroup", true)) && notify_override == 0);
|
||||
value = !(notifyOverride == 2 || (!preferences.getBoolean("EnableAll", true) || ((int) dialog_id < 0) && !preferences.getBoolean("EnableGroup", true)) && notifyOverride == 0);
|
||||
settingsCache.put(dialog_id, value);
|
||||
}
|
||||
if (!value) {
|
||||
|
@ -1190,6 +1204,10 @@ public class NotificationsController {
|
|||
notificationsQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (lastBadgeCount == count) {
|
||||
return;
|
||||
}
|
||||
lastBadgeCount = count;
|
||||
try {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put("tag", "org.telegram.messenger/org.telegram.ui.LaunchActivity");
|
||||
|
|
|
@ -542,7 +542,7 @@ public class SecretChatHelper {
|
|||
File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName + ".jpg");
|
||||
File cacheFile2 = FileLoader.getPathToAttach(size);
|
||||
cacheFile.renameTo(cacheFile2);
|
||||
ImageLoader.getInstance().replaceImageInCache(fileName, fileName2);
|
||||
ImageLoader.getInstance().replaceImageInCache(fileName, fileName2, size.location);
|
||||
ArrayList<TLRPC.Message> arr = new ArrayList<>();
|
||||
arr.add(newMsg);
|
||||
MessagesStorage.getInstance().putMessages(arr, false, true, false, 0);
|
||||
|
@ -557,7 +557,7 @@ public class SecretChatHelper {
|
|||
newMsg.media.video.w = video.w;
|
||||
newMsg.media.video.h = video.h;
|
||||
newMsg.media.video.date = video.date;
|
||||
newMsg.media.video.caption = "";
|
||||
newMsg.media.caption = video.caption != null ? video.caption : "";
|
||||
newMsg.media.video.user_id = video.user_id;
|
||||
newMsg.media.video.size = file.size;
|
||||
newMsg.media.video.id = file.id;
|
||||
|
@ -565,6 +565,7 @@ public class SecretChatHelper {
|
|||
newMsg.media.video.key = decryptedMessage.media.key;
|
||||
newMsg.media.video.iv = decryptedMessage.media.iv;
|
||||
newMsg.media.video.mime_type = video.mime_type;
|
||||
newMsg.media.video.caption = video.caption != null ? video.caption : "";
|
||||
|
||||
if (newMsg.attachPath != null && newMsg.attachPath.startsWith(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE).getAbsolutePath())) {
|
||||
File cacheFile = new File(newMsg.attachPath);
|
||||
|
@ -893,10 +894,10 @@ public class SecretChatHelper {
|
|||
return null;
|
||||
}
|
||||
newMessage.media = new TLRPC.TL_messageMediaPhoto();
|
||||
newMessage.media.caption = "";
|
||||
newMessage.media.photo = new TLRPC.TL_photo();
|
||||
newMessage.media.photo.user_id = newMessage.from_id;
|
||||
newMessage.media.photo.date = newMessage.date;
|
||||
newMessage.media.photo.caption = "";
|
||||
newMessage.media.photo.geo = new TLRPC.TL_geoPointEmpty();
|
||||
if (decryptedMessage.media.thumb.length != 0 && decryptedMessage.media.thumb.length <= 6000 && decryptedMessage.media.thumb_w <= 100 && decryptedMessage.media.thumb_h <= 100) {
|
||||
TLRPC.TL_photoCachedSize small = new TLRPC.TL_photoCachedSize();
|
||||
|
@ -926,6 +927,7 @@ public class SecretChatHelper {
|
|||
return null;
|
||||
}
|
||||
newMessage.media = new TLRPC.TL_messageMediaVideo();
|
||||
newMessage.media.caption = "";
|
||||
newMessage.media.video = new TLRPC.TL_videoEncrypted();
|
||||
if (decryptedMessage.media.thumb.length != 0 && decryptedMessage.media.thumb.length <= 6000 && decryptedMessage.media.thumb_w <= 100 && decryptedMessage.media.thumb_h <= 100) {
|
||||
newMessage.media.video.thumb = new TLRPC.TL_photoCachedSize();
|
||||
|
@ -943,7 +945,6 @@ public class SecretChatHelper {
|
|||
newMessage.media.video.w = decryptedMessage.media.w;
|
||||
newMessage.media.video.h = decryptedMessage.media.h;
|
||||
newMessage.media.video.date = date;
|
||||
newMessage.media.video.caption = "";
|
||||
newMessage.media.video.user_id = from_id;
|
||||
newMessage.media.video.size = file.size;
|
||||
newMessage.media.video.id = file.id;
|
||||
|
@ -951,6 +952,7 @@ public class SecretChatHelper {
|
|||
newMessage.media.video.key = decryptedMessage.media.key;
|
||||
newMessage.media.video.iv = decryptedMessage.media.iv;
|
||||
newMessage.media.video.mime_type = decryptedMessage.media.mime_type;
|
||||
newMessage.media.video.caption = "";
|
||||
if (newMessage.ttl != 0) {
|
||||
newMessage.ttl = Math.max(newMessage.media.video.duration + 1, newMessage.ttl);
|
||||
}
|
||||
|
@ -1300,7 +1302,7 @@ public class SecretChatHelper {
|
|||
ByteBufferDesc is = BuffersStorage.getInstance().getFreeBuffer(message.bytes.length);
|
||||
is.writeRaw(message.bytes);
|
||||
is.position(0);
|
||||
long fingerprint = is.readInt64();
|
||||
long fingerprint = is.readInt64(false);
|
||||
byte[] keyToDecrypt = null;
|
||||
boolean new_key_used = false;
|
||||
if (chat.key_fingerprint == fingerprint) {
|
||||
|
@ -1311,12 +1313,12 @@ public class SecretChatHelper {
|
|||
}
|
||||
|
||||
if (keyToDecrypt != null) {
|
||||
byte[] messageKey = is.readData(16);
|
||||
byte[] messageKey = is.readData(16, false);
|
||||
MessageKeyData keyData = Utilities.generateMessageKeyData(keyToDecrypt, messageKey, false);
|
||||
|
||||
Utilities.aesIgeEncryption(is.buffer, keyData.aesKey, keyData.aesIv, false, false, 24, is.limit() - 24);
|
||||
|
||||
int len = is.readInt32();
|
||||
int len = is.readInt32(false);
|
||||
if (len < 0 || len > is.limit() - 28) {
|
||||
return null;
|
||||
}
|
||||
|
@ -1325,7 +1327,13 @@ public class SecretChatHelper {
|
|||
return null;
|
||||
}
|
||||
|
||||
TLObject object = TLClassStore.Instance().TLdeserialize(is, is.readInt32());
|
||||
TLObject object = null;
|
||||
try {
|
||||
object = TLClassStore.Instance().TLdeserialize(is, is.readInt32(true), true);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
|
||||
BuffersStorage.getInstance().reuseFreeBuffer(is);
|
||||
if (!new_key_used && AndroidUtilities.getPeerLayerVersion(chat.layer) >= 20) {
|
||||
chat.key_use_count_in++;
|
||||
|
|
|
@ -44,7 +44,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
private HashMap<Integer, MessageObject> unsentMessages = new HashMap<>();
|
||||
private HashMap<Integer, TLRPC.Message> sendingMessages = new HashMap<>();
|
||||
|
||||
private class DelayedMessage {
|
||||
protected class DelayedMessage {
|
||||
public TLObject sendRequest;
|
||||
public TLRPC.TL_decryptedMessage sendEncryptedRequest;
|
||||
public int type;
|
||||
|
@ -458,8 +458,8 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
sendMessage(video, null, messageObject.messageOwner.attachPath, did, messageObject.replyMessageObject);
|
||||
} else if (messageObject.messageOwner.media.document instanceof TLRPC.TL_document) {
|
||||
sendMessage((TLRPC.TL_document) messageObject.messageOwner.media.document, null, messageObject.messageOwner.attachPath, did, messageObject.replyMessageObject);
|
||||
} else if (messageObject.messageOwner.media.geo instanceof TLRPC.TL_geoPoint) {
|
||||
sendMessage(messageObject.messageOwner.media.geo.lat, messageObject.messageOwner.media.geo._long, did, messageObject.replyMessageObject);
|
||||
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaVenue || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo) {
|
||||
sendMessage(messageObject.messageOwner.media, did, messageObject.replyMessageObject);
|
||||
} else if (messageObject.messageOwner.media.phone_number != null) {
|
||||
TLRPC.User user = new TLRPC.TL_userContact();
|
||||
user.phone = messageObject.messageOwner.media.phone_number;
|
||||
|
@ -481,8 +481,53 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
}
|
||||
}
|
||||
|
||||
public void sendSticker(TLRPC.Document document, long peer, MessageObject replyingMessageObject) {
|
||||
if (document == null) {
|
||||
return;
|
||||
}
|
||||
if (((int) peer) == 0 && document.thumb instanceof TLRPC.TL_photoSize) {
|
||||
File file = FileLoader.getPathToAttach(document.thumb, true);
|
||||
if (file.exists()) {
|
||||
try {
|
||||
int len = (int) file.length();
|
||||
byte[] arr = new byte[(int) file.length()];
|
||||
RandomAccessFile reader = new RandomAccessFile(file, "r");
|
||||
reader.readFully(arr);
|
||||
TLRPC.TL_document newDocument = new TLRPC.TL_document();
|
||||
newDocument.thumb = new TLRPC.TL_photoCachedSize();
|
||||
newDocument.thumb.location = document.thumb.location;
|
||||
newDocument.thumb.size = document.thumb.size;
|
||||
newDocument.thumb.w = document.thumb.w;
|
||||
newDocument.thumb.h = document.thumb.h;
|
||||
newDocument.thumb.type = document.thumb.type;
|
||||
newDocument.thumb.bytes = arr;
|
||||
|
||||
newDocument.id = document.id;
|
||||
newDocument.access_hash = document.access_hash;
|
||||
newDocument.date = document.date;
|
||||
newDocument.mime_type = document.mime_type;
|
||||
newDocument.size = document.size;
|
||||
newDocument.dc_id = document.dc_id;
|
||||
newDocument.attributes = document.attributes;
|
||||
document = newDocument;
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int a = 0; a < document.attributes.size(); a++) {
|
||||
TLRPC.DocumentAttribute attribute = document.attributes.get(a);
|
||||
if (attribute instanceof TLRPC.TL_documentAttributeSticker) {
|
||||
document.attributes.remove(a);
|
||||
document.attributes.add(new TLRPC.TL_documentAttributeSticker_old());
|
||||
break;
|
||||
}
|
||||
}
|
||||
SendMessagesHelper.getInstance().sendMessage((TLRPC.TL_document) document, null, null, peer, replyingMessageObject);
|
||||
}
|
||||
|
||||
public void sendMessage(TLRPC.User user, long peer, MessageObject reply_to_msg) {
|
||||
sendMessage(null, null, null, null, null, null, user, null, null, null, peer, false, null, reply_to_msg, null, true);
|
||||
sendMessage(null, null, null, null, null, user, null, null, null, peer, false, null, reply_to_msg, null, true);
|
||||
}
|
||||
|
||||
public void sendMessage(ArrayList<MessageObject> messages, long peer) {
|
||||
|
@ -550,6 +595,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
ids.add(newMsg.fwd_msg_id);
|
||||
newMsg.date = ConnectionsManager.getInstance().getCurrentTime();
|
||||
newMsg.flags |= TLRPC.MESSAGE_FLAG_UNREAD;
|
||||
if (newMsg.media instanceof TLRPC.TL_messageMediaAudio) {
|
||||
newMsg.flags |= TLRPC.MESSAGE_FLAG_CONTENT_UNREAD;
|
||||
}
|
||||
newMsg.dialog_id = peer;
|
||||
newMsg.to_id = to_id;
|
||||
MessageObject newMsgObj = new MessageObject(newMsg, null, true);
|
||||
|
@ -560,7 +608,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
putToSendingMessages(newMsg);
|
||||
|
||||
if (arr.size() == 100 || a == messages.size() - 1) {
|
||||
MessagesStorage.getInstance().putMessages(arr, false, true, false, 0);
|
||||
MessagesStorage.getInstance().putMessages(new ArrayList<>(arr), false, true, false, 0);
|
||||
MessagesController.getInstance().updateInterfaceWithMessages(peer, objArr);
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload);
|
||||
UserConfig.saveConfig(false);
|
||||
|
@ -655,38 +703,38 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
}
|
||||
|
||||
public void sendMessage(MessageObject message) {
|
||||
sendMessage(null, null, null, null, null, message, null, null, null, null, message.getDialogId(), true, message.messageOwner.attachPath, null, null, true);
|
||||
sendMessage(null, null, null, null, message, null, null, null, null, message.getDialogId(), true, message.messageOwner.attachPath, null, null, true);
|
||||
}
|
||||
|
||||
public void sendMessage(MessageObject message, long peer) {
|
||||
sendMessage(null, null, null, null, null, message, null, null, null, null, peer, false, message.messageOwner.attachPath, null, null, true);
|
||||
sendMessage(null, null, null, null, message, null, null, null, null, peer, false, message.messageOwner.attachPath, null, null, true);
|
||||
}
|
||||
|
||||
public void sendMessage(TLRPC.TL_document document, String originalPath, String path, long peer, MessageObject reply_to_msg) {
|
||||
sendMessage(null, null, null, null, null, null, null, document, null, originalPath, peer, false, path, reply_to_msg, null, true);
|
||||
sendMessage(null, null, null, null, null, null, document, null, originalPath, peer, false, path, reply_to_msg, null, true);
|
||||
}
|
||||
|
||||
public void sendMessage(String message, long peer, MessageObject reply_to_msg, TLRPC.WebPage webPage, boolean searchLinks) {
|
||||
sendMessage(message, null, null, null, null, null, null, null, null, null, peer, false, null, reply_to_msg, webPage, searchLinks);
|
||||
sendMessage(message, null, null, null, null, null, null, null, null, peer, false, null, reply_to_msg, webPage, searchLinks);
|
||||
}
|
||||
|
||||
public void sendMessage(double lat, double lon, long peer, MessageObject reply_to_msg) {
|
||||
sendMessage(null, lat, lon, null, null, null, null, null, null, null, peer, false, null, reply_to_msg, null, true);
|
||||
public void sendMessage(TLRPC.MessageMedia location, long peer, MessageObject reply_to_msg) {
|
||||
sendMessage(null, location, null, null, null, null, null, null, null, peer, false, null, reply_to_msg, null, true);
|
||||
}
|
||||
|
||||
public void sendMessage(TLRPC.TL_photo photo, String originalPath, String path, long peer, MessageObject reply_to_msg) {
|
||||
sendMessage(null, null, null, photo, null, null, null, null, null, originalPath, peer, false, path, reply_to_msg, null, true);
|
||||
sendMessage(null, null, photo, null, null, null, null, null, originalPath, peer, false, path, reply_to_msg, null, true);
|
||||
}
|
||||
|
||||
public void sendMessage(TLRPC.TL_video video, String originalPath, String path, long peer, MessageObject reply_to_msg) {
|
||||
sendMessage(null, null, null, null, video, null, null, null, null, originalPath, peer, false, path, reply_to_msg, null, true);
|
||||
sendMessage(null, null, null, video, null, null, null, null, originalPath, peer, false, path, reply_to_msg, null, true);
|
||||
}
|
||||
|
||||
public void sendMessage(TLRPC.TL_audio audio, String path, long peer, MessageObject reply_to_msg) {
|
||||
sendMessage(null, null, null, null, null, null, null, null, audio, null, peer, false, path, reply_to_msg, null, true);
|
||||
sendMessage(null, null, null, null, null, null, null, audio, null, peer, false, path, reply_to_msg, null, true);
|
||||
}
|
||||
|
||||
private void sendMessage(String message, Double lat, Double lon, TLRPC.TL_photo photo, TLRPC.TL_video video, MessageObject msgObj, TLRPC.User user, TLRPC.TL_document document, TLRPC.TL_audio audio, String originalPath, long peer, boolean retry, String path, MessageObject reply_to_msg, TLRPC.WebPage webPage, boolean searchLinks) {
|
||||
private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_photo photo, TLRPC.TL_video video, MessageObject msgObj, TLRPC.User user, TLRPC.TL_document document, TLRPC.TL_audio audio, String originalPath, long peer, boolean retry, String path, MessageObject reply_to_msg, TLRPC.WebPage webPage, boolean searchLinks) {
|
||||
if (peer == 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -713,8 +761,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
type = 0;
|
||||
}
|
||||
} else if (msgObj.type == 4) {
|
||||
lat = newMsg.media.geo.lat;
|
||||
lon = newMsg.media.geo._long;
|
||||
location = newMsg.media;
|
||||
type = 1;
|
||||
} else if (msgObj.type == 1) {
|
||||
if (msgObj.isForwarded()) {
|
||||
|
@ -760,16 +807,13 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
}
|
||||
type = 0;
|
||||
newMsg.message = message;
|
||||
} else if (lat != null && lon != null) {
|
||||
} else if (location != null) {
|
||||
if (encryptedChat != null && AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) {
|
||||
newMsg = new TLRPC.TL_message_secret();
|
||||
} else {
|
||||
newMsg = new TLRPC.TL_message();
|
||||
}
|
||||
newMsg.media = new TLRPC.TL_messageMediaGeo();
|
||||
newMsg.media.geo = new TLRPC.TL_geoPoint();
|
||||
newMsg.media.geo.lat = lat;
|
||||
newMsg.media.geo._long = lon;
|
||||
newMsg.media = location;
|
||||
newMsg.message = "";
|
||||
type = 1;
|
||||
} else if (photo != null) {
|
||||
|
@ -779,6 +823,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
newMsg = new TLRPC.TL_message();
|
||||
}
|
||||
newMsg.media = new TLRPC.TL_messageMediaPhoto();
|
||||
newMsg.media.caption = photo.caption != null ? photo.caption : "";
|
||||
newMsg.media.photo = photo;
|
||||
type = 2;
|
||||
newMsg.message = "-1";
|
||||
|
@ -795,6 +840,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
newMsg = new TLRPC.TL_message();
|
||||
}
|
||||
newMsg.media = new TLRPC.TL_messageMediaVideo();
|
||||
newMsg.media.caption = video.caption != null ? video.caption : "";
|
||||
newMsg.media.video = video;
|
||||
newMsg.videoEditedInfo = video.videoEditedInfo;
|
||||
type = 3;
|
||||
|
@ -868,6 +914,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
}
|
||||
newMsg.date = ConnectionsManager.getInstance().getCurrentTime();
|
||||
newMsg.flags |= TLRPC.MESSAGE_FLAG_UNREAD;
|
||||
if (encryptedChat == null && high_id != 1 && newMsg.media instanceof TLRPC.TL_messageMediaAudio) {
|
||||
newMsg.flags |= TLRPC.MESSAGE_FLAG_CONTENT_UNREAD;
|
||||
}
|
||||
newMsg.dialog_id = peer;
|
||||
if (reply_to_msg != null) {
|
||||
newMsg.flags |= TLRPC.MESSAGE_FLAG_REPLY;
|
||||
|
@ -993,13 +1042,22 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
TLRPC.InputMedia inputMedia = null;
|
||||
DelayedMessage delayedMessage = null;
|
||||
if (type == 1) {
|
||||
inputMedia = new TLRPC.TL_inputMediaGeoPoint();
|
||||
if (location instanceof TLRPC.TL_messageMediaVenue) {
|
||||
inputMedia = new TLRPC.TL_inputMediaVenue();
|
||||
inputMedia.address = location.address;
|
||||
inputMedia.title = location.title;
|
||||
inputMedia.provider = location.provider;
|
||||
inputMedia.venue_id = location.venue_id;
|
||||
} else {
|
||||
inputMedia = new TLRPC.TL_inputMediaGeoPoint();
|
||||
}
|
||||
inputMedia.geo_point = new TLRPC.TL_inputGeoPoint();
|
||||
inputMedia.geo_point.lat = lat;
|
||||
inputMedia.geo_point._long = lon;
|
||||
inputMedia.geo_point.lat = location.geo.lat;
|
||||
inputMedia.geo_point._long = location.geo._long;
|
||||
} else if (type == 2) {
|
||||
if (photo.access_hash == 0) {
|
||||
inputMedia = new TLRPC.TL_inputMediaUploadedPhoto();
|
||||
inputMedia.caption = photo.caption != null ? photo.caption : "";
|
||||
delayedMessage = new DelayedMessage();
|
||||
delayedMessage.originalPath = originalPath;
|
||||
delayedMessage.type = 0;
|
||||
|
@ -1012,6 +1070,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
} else {
|
||||
TLRPC.TL_inputMediaPhoto media = new TLRPC.TL_inputMediaPhoto();
|
||||
media.id = new TLRPC.TL_inputPhoto();
|
||||
media.caption = photo.caption != null ? photo.caption : "";
|
||||
media.id.id = photo.id;
|
||||
media.id.access_hash = photo.access_hash;
|
||||
inputMedia = media;
|
||||
|
@ -1023,6 +1082,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
} else {
|
||||
inputMedia = new TLRPC.TL_inputMediaUploadedVideo();
|
||||
}
|
||||
inputMedia.caption = video.caption != null ? video.caption : "";
|
||||
inputMedia.duration = video.duration;
|
||||
inputMedia.w = video.w;
|
||||
inputMedia.h = video.h;
|
||||
|
@ -1036,6 +1096,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
} else {
|
||||
TLRPC.TL_inputMediaVideo media = new TLRPC.TL_inputMediaVideo();
|
||||
media.id = new TLRPC.TL_inputVideo();
|
||||
media.caption = video.caption != null ? video.caption : "";
|
||||
media.id.id = video.id;
|
||||
media.id.access_hash = video.access_hash;
|
||||
inputMedia = media;
|
||||
|
@ -1161,8 +1222,8 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
reqSend.message = "";
|
||||
if (type == 1) {
|
||||
reqSend.media = new TLRPC.TL_decryptedMessageMediaGeoPoint();
|
||||
reqSend.media.lat = lat;
|
||||
reqSend.media._long = lon;
|
||||
reqSend.media.lat = location.geo.lat;
|
||||
reqSend.media._long = location.geo._long;
|
||||
SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null);
|
||||
} else if (type == 2) {
|
||||
TLRPC.PhotoSize small = photo.sizes.get(0);
|
||||
|
@ -1589,7 +1650,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
cacheFile2 = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName2 + ".jpg");
|
||||
}
|
||||
cacheFile.renameTo(cacheFile2);
|
||||
ImageLoader.getInstance().replaceImageInCache(fileName, fileName2);
|
||||
ImageLoader.getInstance().replaceImageInCache(fileName, fileName2, size.location);
|
||||
size2.location = size.location;
|
||||
break;
|
||||
}
|
||||
|
@ -1611,7 +1672,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName + ".jpg");
|
||||
File cacheFile2 = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName2 + ".jpg");
|
||||
cacheFile.renameTo(cacheFile2);
|
||||
ImageLoader.getInstance().replaceImageInCache(fileName, fileName2);
|
||||
ImageLoader.getInstance().replaceImageInCache(fileName, fileName2, size.location);
|
||||
size2.location = size.location;
|
||||
}
|
||||
}
|
||||
|
@ -1642,7 +1703,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName + ".jpg");
|
||||
File cacheFile2 = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName2 + ".jpg");
|
||||
cacheFile.renameTo(cacheFile2);
|
||||
ImageLoader.getInstance().replaceImageInCache(fileName, fileName2);
|
||||
ImageLoader.getInstance().replaceImageInCache(fileName, fileName2, size.location);
|
||||
size2.location = size.location;
|
||||
}
|
||||
} else if (MessageObject.isStickerMessage(sentMessage) && size2.location != null) {
|
||||
|
@ -1697,6 +1758,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
arrayList.add(message);
|
||||
}
|
||||
|
||||
protected ArrayList<DelayedMessage> getDelayedMessages(String location) {
|
||||
return delayedMessages.get(location);
|
||||
}
|
||||
|
||||
protected long getNextRandomId() {
|
||||
long val = 0;
|
||||
while (val == 0) {
|
||||
|
@ -1749,7 +1814,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
photo.user_id = UserConfig.getClientUserId();
|
||||
photo.date = ConnectionsManager.getInstance().getCurrentTime();
|
||||
photo.sizes = sizes;
|
||||
photo.caption = "";
|
||||
photo.geo = new TLRPC.TL_geoPointEmpty();
|
||||
return photo;
|
||||
}
|
||||
|
@ -1811,11 +1875,15 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
document.size = (int)f.length();
|
||||
document.dc_id = 0;
|
||||
if (ext.length() != 0) {
|
||||
String mimeType = myMime.getMimeTypeFromExtension(ext.toLowerCase());
|
||||
if (mimeType != null) {
|
||||
document.mime_type = mimeType;
|
||||
if (ext.toLowerCase().equals("webp")) {
|
||||
document.mime_type = "image/webp";
|
||||
} else {
|
||||
document.mime_type = "application/octet-stream";
|
||||
String mimeType = myMime.getMimeTypeFromExtension(ext.toLowerCase());
|
||||
if (mimeType != null) {
|
||||
document.mime_type = mimeType;
|
||||
} else {
|
||||
document.mime_type = "application/octet-stream";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
document.mime_type = "application/octet-stream";
|
||||
|
@ -1929,9 +1997,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
}).start();
|
||||
}
|
||||
|
||||
public static void prepareSendingPhoto(String imageFilePath, Uri imageUri, long dialog_id, MessageObject reply_to_msg) {
|
||||
public static void prepareSendingPhoto(String imageFilePath, Uri imageUri, long dialog_id, MessageObject reply_to_msg, CharSequence caption) {
|
||||
ArrayList<String> paths = null;
|
||||
ArrayList<Uri> uris = null;
|
||||
ArrayList<String> captions = null;
|
||||
if (imageFilePath != null && imageFilePath.length() != 0) {
|
||||
paths = new ArrayList<>();
|
||||
paths.add(imageFilePath);
|
||||
|
@ -1940,7 +2009,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
uris = new ArrayList<>();
|
||||
uris.add(imageUri);
|
||||
}
|
||||
prepareSendingPhotos(paths, uris, dialog_id, reply_to_msg);
|
||||
if (caption != null) {
|
||||
captions = new ArrayList<>();
|
||||
captions.add(caption.toString());
|
||||
}
|
||||
prepareSendingPhotos(paths, uris, dialog_id, reply_to_msg, captions);
|
||||
}
|
||||
|
||||
public static void prepareSendingPhotosSearch(final ArrayList<MediaController.SearchImage> photos, final long dialog_id, final MessageObject reply_to_msg) {
|
||||
|
@ -1951,7 +2024,8 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
@Override
|
||||
public void run() {
|
||||
boolean isEncrypted = (int)dialog_id == 0;
|
||||
for (final MediaController.SearchImage searchImage : photos) {
|
||||
for (int a = 0; a < photos.size(); a++) {
|
||||
final MediaController.SearchImage searchImage = photos.get(a);
|
||||
if (searchImage.type == 1) {
|
||||
TLRPC.TL_document document = null;
|
||||
if (!isEncrypted) {
|
||||
|
@ -2029,7 +2103,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
photo = new TLRPC.TL_photo();
|
||||
photo.user_id = UserConfig.getClientUserId();
|
||||
photo.date = ConnectionsManager.getInstance().getCurrentTime();
|
||||
photo.caption = "";
|
||||
photo.geo = new TLRPC.TL_geoPointEmpty();
|
||||
TLRPC.TL_photoSize photoSize = new TLRPC.TL_photoSize();
|
||||
photoSize.w = searchImage.width;
|
||||
|
@ -2042,6 +2115,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
}
|
||||
}
|
||||
if (photo != null) {
|
||||
if (searchImage.caption != null) {
|
||||
photo.caption = searchImage.caption.toString();
|
||||
}
|
||||
final String originalPathFinal = searchImage.imageUrl;
|
||||
final TLRPC.TL_photo photoFinal = photo;
|
||||
final boolean needDownloadHttpFinal = needDownloadHttp;
|
||||
|
@ -2058,7 +2134,32 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
}).start();
|
||||
}
|
||||
|
||||
public static void prepareSendingPhotos(ArrayList<String> paths, ArrayList<Uri> uris, final long dialog_id, final MessageObject reply_to_msg) {
|
||||
private static String getTrimmedString(String src) {
|
||||
String result = src.trim();
|
||||
if (result.length() == 0) {
|
||||
return result;
|
||||
}
|
||||
while (src.startsWith("\n")) {
|
||||
src = src.substring(1);
|
||||
}
|
||||
while (src.endsWith("\n")) {
|
||||
src = src.substring(0, src.length() - 1);
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
public static void prepareSendingText(String text, long dialog_id) {
|
||||
text = getTrimmedString(text);
|
||||
if (text.length() != 0) {
|
||||
int count = (int) Math.ceil(text.length() / 4096.0f);
|
||||
for (int a = 0; a < count; a++) {
|
||||
String mess = text.substring(a * 4096, Math.min((a + 1) * 4096, text.length()));
|
||||
SendMessagesHelper.getInstance().sendMessage(mess, dialog_id, null, null, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void prepareSendingPhotos(ArrayList<String> paths, ArrayList<Uri> uris, final long dialog_id, final MessageObject reply_to_msg, final ArrayList<String> captions) {
|
||||
if (paths == null && uris == null || paths != null && paths.isEmpty() || uris != null && uris.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -2080,6 +2181,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
int count = !pathsCopy.isEmpty() ? pathsCopy.size() : urisCopy.size();
|
||||
String path = null;
|
||||
Uri uri = null;
|
||||
String extension = null;
|
||||
for (int a = 0; a < count; a++) {
|
||||
if (!pathsCopy.isEmpty()) {
|
||||
path = pathsCopy.get(a);
|
||||
|
@ -2096,16 +2198,23 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
|
||||
boolean isDocument = false;
|
||||
if (tempPath != null && (tempPath.endsWith(".gif") || tempPath.endsWith(".webp"))) {
|
||||
if (tempPath.endsWith(".gif")) {
|
||||
extension = "gif";
|
||||
} else {
|
||||
extension = "webp";
|
||||
}
|
||||
isDocument = true;
|
||||
} else if (tempPath == null && uri != null) {
|
||||
if (MediaController.isGif(uri)) {
|
||||
isDocument = true;
|
||||
originalPath = uri.toString();
|
||||
tempPath = MediaController.copyDocumentToCache(uri, "gif");
|
||||
extension = "gif";
|
||||
} else if (MediaController.isWebp(uri)) {
|
||||
isDocument = true;
|
||||
originalPath = uri.toString();
|
||||
tempPath = MediaController.copyDocumentToCache(uri, "webp");
|
||||
extension = "webp";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2134,6 +2243,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
photo = SendMessagesHelper.getInstance().generatePhotoSizes(path, uri);
|
||||
}
|
||||
if (photo != null) {
|
||||
if (captions != null) {
|
||||
photo.caption = captions.get(a);
|
||||
}
|
||||
final String originalPathFinal = originalPath;
|
||||
final TLRPC.TL_photo photoFinal = photo;
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
|
@ -2147,7 +2259,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
}
|
||||
if (sendAsDocuments != null && !sendAsDocuments.isEmpty()) {
|
||||
for (int a = 0; a < sendAsDocuments.size(); a++) {
|
||||
prepareSendingDocumentInternal(sendAsDocuments.get(a), sendAsDocumentsOriginal.get(a), null, "gif", dialog_id, reply_to_msg);
|
||||
prepareSendingDocumentInternal(sendAsDocuments.get(a), sendAsDocumentsOriginal.get(a), null, extension, dialog_id, reply_to_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2190,7 +2302,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
|||
} else {
|
||||
video.thumb.type = "s";
|
||||
}
|
||||
video.caption = "";
|
||||
video.mime_type = "video/mp4";
|
||||
video.id = 0;
|
||||
UserConfig.saveConfig(false);
|
||||
|
|
|
@ -21,7 +21,6 @@ import org.telegram.messenger.ByteBufferDesc;
|
|||
import org.telegram.messenger.ConnectionsManager;
|
||||
import org.telegram.messenger.FileLog;
|
||||
import org.telegram.messenger.RPCRequest;
|
||||
import org.telegram.messenger.TLClassStore;
|
||||
import org.telegram.messenger.TLObject;
|
||||
import org.telegram.messenger.TLRPC;
|
||||
|
||||
|
@ -65,7 +64,7 @@ public class ReplyMessageQuery {
|
|||
while (cursor.next()) {
|
||||
ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0));
|
||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
|
||||
TLRPC.Message message = (TLRPC.Message) TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
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;
|
||||
|
|
|
@ -20,7 +20,6 @@ import org.telegram.messenger.ByteBufferDesc;
|
|||
import org.telegram.messenger.ConnectionsManager;
|
||||
import org.telegram.messenger.FileLog;
|
||||
import org.telegram.messenger.RPCRequest;
|
||||
import org.telegram.messenger.TLClassStore;
|
||||
import org.telegram.messenger.TLObject;
|
||||
import org.telegram.messenger.TLRPC;
|
||||
|
||||
|
@ -178,7 +177,7 @@ public class SharedMediaQuery {
|
|||
}
|
||||
final ArrayList<MessageObject> objects = new ArrayList<>();
|
||||
for (TLRPC.Message message : res.messages) {
|
||||
objects.add(new MessageObject(message, usersLocal, false));
|
||||
objects.add(new MessageObject(message, usersLocal, true));
|
||||
}
|
||||
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
|
@ -324,7 +323,7 @@ public class SharedMediaQuery {
|
|||
while (cursor.next()) {
|
||||
ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0));
|
||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
|
||||
TLRPC.Message message = (TLRPC.Message) TLClassStore.Instance().TLdeserialize(data, data.readInt32());
|
||||
TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false);
|
||||
message.id = cursor.intValue(1);
|
||||
message.dialog_id = uid;
|
||||
if ((int)uid == 0) {
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* This is the source code of Telegram for Android v. 2.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-2015.
|
||||
*/
|
||||
|
||||
package org.telegram.android.query;
|
||||
|
||||
import org.telegram.SQLite.SQLiteCursor;
|
||||
import org.telegram.SQLite.SQLitePreparedStatement;
|
||||
import org.telegram.android.AndroidUtilities;
|
||||
import org.telegram.android.MessagesStorage;
|
||||
import org.telegram.android.NotificationCenter;
|
||||
import org.telegram.messenger.ByteBufferDesc;
|
||||
import org.telegram.messenger.ConnectionsManager;
|
||||
import org.telegram.messenger.FileLog;
|
||||
import org.telegram.messenger.RPCRequest;
|
||||
import org.telegram.messenger.TLObject;
|
||||
import org.telegram.messenger.TLRPC;
|
||||
import org.telegram.messenger.Utilities;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class StickersQuery {
|
||||
|
||||
private static String hash;
|
||||
private static int loadDate;
|
||||
private static ArrayList<TLRPC.Document> stickers = new ArrayList<>();
|
||||
private static HashMap<String, ArrayList<TLRPC.Document>> allStickers = new HashMap<>();
|
||||
private static boolean loadingStickers;
|
||||
|
||||
public static void checkStickers() {
|
||||
if (!loadingStickers && (allStickers.isEmpty() || loadDate < (System.currentTimeMillis() / 1000 - 60 * 60))) {
|
||||
loadStickers(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static ArrayList<TLRPC.Document> getStickers() {
|
||||
return stickers;
|
||||
}
|
||||
|
||||
private static void loadStickers(boolean cache) {
|
||||
if (loadingStickers) {
|
||||
return;
|
||||
}
|
||||
loadingStickers = true;
|
||||
if (cache) {
|
||||
MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
TLRPC.messages_AllStickers result = null;
|
||||
int date = 0;
|
||||
try {
|
||||
SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized("SELECT data, date FROM stickers WHERE 1");
|
||||
ArrayList<TLRPC.User> loadedUsers = new ArrayList<>();
|
||||
if (cursor.next()) {
|
||||
ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0));
|
||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) {
|
||||
result = TLRPC.messages_AllStickers.TLdeserialize(data, data.readInt32(false), false);
|
||||
}
|
||||
date = cursor.intValue(1);
|
||||
MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data);
|
||||
}
|
||||
cursor.dispose();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
processLoadedStickers(result, true, date);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
TLRPC.TL_messages_getAllStickers req = new TLRPC.TL_messages_getAllStickers();
|
||||
req.hash = hash;
|
||||
if (req.hash == null) {
|
||||
req.hash = "";
|
||||
}
|
||||
ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
|
||||
@Override
|
||||
public void run(final TLObject response, final TLRPC.TL_error error) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
processLoadedStickers((TLRPC.messages_AllStickers) response, false, (int) (System.currentTimeMillis() / 1000));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void putStickersToCache(final TLRPC.TL_messages_allStickers stickers) {
|
||||
MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
SQLitePreparedStatement state = MessagesStorage.getInstance().getDatabase().executeFast("REPLACE INTO stickers VALUES(?, ?, ?)");
|
||||
state.requery();
|
||||
ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(stickers.getObjectSize());
|
||||
stickers.serializeToStream(data);
|
||||
state.bindInteger(1, 1);
|
||||
state.bindByteBuffer(2, data.buffer);
|
||||
state.bindInteger(3, (int) (System.currentTimeMillis() / 1000));
|
||||
state.step();
|
||||
MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data);
|
||||
state.dispose();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("tmessages", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void processLoadedStickers(final TLRPC.messages_AllStickers res, final boolean cache, final int date) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
loadingStickers = false;
|
||||
}
|
||||
});
|
||||
Utilities.stageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if ((res == null || date < (int) (System.currentTimeMillis() / 1000 - 60 * 60)) && cache) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
loadStickers(false);
|
||||
}
|
||||
});
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (res instanceof TLRPC.TL_messages_allStickers) {
|
||||
if (!cache) {
|
||||
putStickersToCache((TLRPC.TL_messages_allStickers) res);
|
||||
}
|
||||
HashMap<Long, TLRPC.Document> documents = new HashMap<>();
|
||||
for (TLRPC.Document document : res.documents) {
|
||||
if (document == null) {
|
||||
continue;
|
||||
}
|
||||
documents.put(document.id, document);
|
||||
if (document.thumb != null && document.thumb.location != null) {
|
||||
document.thumb.location.ext = "webp";
|
||||
}
|
||||
}
|
||||
final HashMap<String, ArrayList<TLRPC.Document>> result = new HashMap<>();
|
||||
for (TLRPC.TL_stickerPack stickerPack : res.packs) {
|
||||
if (stickerPack != null && stickerPack.emoticon != null) {
|
||||
stickerPack.emoticon = stickerPack.emoticon.replace("\uFE0F", "");
|
||||
ArrayList<TLRPC.Document> arrayList = result.get(stickerPack.emoticon);
|
||||
for (Long id : stickerPack.documents) {
|
||||
TLRPC.Document document = documents.get(id);
|
||||
if (document != null) {
|
||||
if (arrayList == null) {
|
||||
arrayList = new ArrayList<>();
|
||||
result.put(stickerPack.emoticon, arrayList);
|
||||
}
|
||||
arrayList.add(document);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
allStickers = result;
|
||||
stickers = res.documents;
|
||||
hash = res.hash;
|
||||
loadDate = date;
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.stickersDidLoaded);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,633 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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 android.support.v7.util;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
|
||||
/**
|
||||
* 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 android.support.v7.widget.RecyclerView.Adapter
|
||||
* RecyclerView.Adapter}.
|
||||
* <p>
|
||||
* It keeps items ordered using the {@link Callback#compare(Object, Object)} method and uses
|
||||
* binary search to retrieve items. If the sorting criteria of your items may change, make sure you
|
||||
* call appropriate methods while editing them to avoid data inconsistencies.
|
||||
* <p>
|
||||
* You can control the order of items and change notifications via the {@link Callback} parameter.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class SortedList<T> {
|
||||
|
||||
/**
|
||||
* Used by {@link #indexOf(Object)} when he item cannot be found in the list.
|
||||
*/
|
||||
public static final int INVALID_POSITION = -1;
|
||||
|
||||
private static final int MIN_CAPACITY = 10;
|
||||
private static final int CAPACITY_GROWTH = MIN_CAPACITY;
|
||||
private static final int INSERTION = 1;
|
||||
private static final int DELETION = 1 << 1;
|
||||
private static final int LOOKUP = 1 << 2;
|
||||
T[] mData;
|
||||
|
||||
/**
|
||||
* The callback instance that controls the behavior of the SortedList and get notified when
|
||||
* changes happen.
|
||||
*/
|
||||
private Callback mCallback;
|
||||
|
||||
private BatchedCallback mBatchedCallback;
|
||||
|
||||
private int mSize;
|
||||
private final Class<T> mTClass;
|
||||
|
||||
/**
|
||||
* Creates a new SortedList of type T.
|
||||
*
|
||||
* @param klass The class of the contents of the SortedList.
|
||||
* @param callback The callback that controls the behavior of SortedList.
|
||||
*/
|
||||
public SortedList(Class<T> klass, Callback<T> callback) {
|
||||
this(klass, callback, MIN_CAPACITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SortedList of type T.
|
||||
*
|
||||
* @param klass The class of the contents of the SortedList.
|
||||
* @param callback The callback that controls the behavior of SortedList.
|
||||
* @param initialCapacity The initial capacity to hold items.
|
||||
*/
|
||||
public SortedList(Class<T> klass, Callback<T> callback, int initialCapacity) {
|
||||
mTClass = klass;
|
||||
mData = (T[]) Array.newInstance(klass, initialCapacity);
|
||||
mCallback = callback;
|
||||
mSize = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of items in the list.
|
||||
*
|
||||
* @return The number of items in the list.
|
||||
*/
|
||||
public int size() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given item to the list. If this is a new item, SortedList calls
|
||||
* {@link Callback#onInserted(int, int)}.
|
||||
* <p>
|
||||
* If the item already exists in the list and its sorting criteria is not changed, it is
|
||||
* replaced with the existing Item. SortedList uses
|
||||
* {@link Callback#areItemsTheSame(Object, Object)} to check if two items are the same item
|
||||
* and uses {@link Callback#areContentsTheSame(Object, Object)} to decide whether it should
|
||||
* call {@link Callback#onChanged(int, int)} or not. In both cases, it always removes the
|
||||
* reference to the old item and puts the new item into the backing array even if
|
||||
* {@link Callback#areContentsTheSame(Object, Object)} returns false.
|
||||
* <p>
|
||||
* If the sorting criteria of the item is changed, SortedList won't be able to find
|
||||
* its duplicate in the list which will result in having a duplicate of the Item in the list.
|
||||
* If you need to update sorting criteria of an item that already exists in the list,
|
||||
* use {@link #updateItemAt(int, Object)}. You can find the index of the item using
|
||||
* {@link #indexOf(Object)} before you update the object.
|
||||
*
|
||||
* @param item The item to be added into the list.
|
||||
* @return The index of the newly added item.
|
||||
* @see {@link Callback#compare(Object, Object)}
|
||||
* @see {@link Callback#areItemsTheSame(Object, Object)}
|
||||
* @see {@link Callback#areContentsTheSame(Object, Object)}}
|
||||
*/
|
||||
public int add(T item) {
|
||||
return add(item, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batches adapter updates that happen between calling this method until calling
|
||||
* {@link #endBatchedUpdates()}. For example, if you add multiple items in a loop
|
||||
* and they are placed into consecutive indices, SortedList calls
|
||||
* {@link Callback#onInserted(int, int)} only once with the proper item count. If an event
|
||||
* cannot be merged with the previous event, the previous event is dispatched
|
||||
* to the callback instantly.
|
||||
* <p>
|
||||
* After running your data updates, you <b>must</b> call {@link #endBatchedUpdates()}
|
||||
* which will dispatch any deferred data change event to the current callback.
|
||||
* <p>
|
||||
* A sample implementation may look like this:
|
||||
* <pre>
|
||||
* mSortedList.beginBatchedUpdates();
|
||||
* try {
|
||||
* mSortedList.add(item1)
|
||||
* mSortedList.add(item2)
|
||||
* mSortedList.remove(item3)
|
||||
* ...
|
||||
* } finally {
|
||||
* mSortedList.endBatchedUpdates();
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* Instead of using this method to batch calls, you can use a Callback that extends
|
||||
* {@link BatchedCallback}. In that case, you must make sure that you are manually calling
|
||||
* {@link BatchedCallback#dispatchLastEvent()} right after you complete your data changes.
|
||||
* Failing to do so may create data inconsistencies with the Callback.
|
||||
* <p>
|
||||
* If the current Callback in an instance of {@link BatchedCallback}, calling this method
|
||||
* has no effect.
|
||||
*/
|
||||
public void beginBatchedUpdates() {
|
||||
if (mCallback instanceof BatchedCallback) {
|
||||
return;
|
||||
}
|
||||
if (mBatchedCallback == null) {
|
||||
mBatchedCallback = new BatchedCallback(mCallback);
|
||||
}
|
||||
mCallback = mBatchedCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the update transaction and dispatches any remaining event to the callback.
|
||||
*/
|
||||
public void endBatchedUpdates() {
|
||||
if (mCallback instanceof BatchedCallback) {
|
||||
((BatchedCallback) mCallback).dispatchLastEvent();
|
||||
}
|
||||
if (mCallback == mBatchedCallback) {
|
||||
mCallback = mBatchedCallback.mWrappedCallback;
|
||||
}
|
||||
}
|
||||
|
||||
private int add(T item, boolean notify) {
|
||||
int index = findIndexOf(item, INSERTION);
|
||||
if (index == INVALID_POSITION) {
|
||||
index = 0;
|
||||
} else if (index < mSize) {
|
||||
T existing = mData[index];
|
||||
if (mCallback.areItemsTheSame(existing, item)) {
|
||||
if (mCallback.areContentsTheSame(existing, item)) {
|
||||
//no change but still replace the item
|
||||
mData[index] = item;
|
||||
return index;
|
||||
} else {
|
||||
mData[index] = item;
|
||||
mCallback.onChanged(index, 1);
|
||||
return index;
|
||||
}
|
||||
}
|
||||
}
|
||||
addToData(index, item);
|
||||
if (notify) {
|
||||
mCallback.onInserted(index, 1);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the provided item from the list and calls {@link Callback#onRemoved(int, int)}.
|
||||
*
|
||||
* @param item The item to be removed from the list.
|
||||
* @return True if item is removed, false if item cannot be found in the list.
|
||||
*/
|
||||
public boolean remove(T item) {
|
||||
return remove(item, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the item at the given index and calls {@link Callback#onRemoved(int, int)}.
|
||||
*
|
||||
* @param index The index of the item to be removed.
|
||||
* @return The removed item.
|
||||
*/
|
||||
public T removeItemAt(int index) {
|
||||
T item = get(index);
|
||||
removeItemAtIndex(index, true);
|
||||
return item;
|
||||
}
|
||||
|
||||
private boolean remove(T item, boolean notify) {
|
||||
int index = findIndexOf(item, DELETION);
|
||||
if (index == INVALID_POSITION) {
|
||||
return false;
|
||||
}
|
||||
removeItemAtIndex(index, notify);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void removeItemAtIndex(int index, boolean notify) {
|
||||
System.arraycopy(mData, index + 1, mData, index, mSize - index - 1);
|
||||
mSize--;
|
||||
mData[mSize] = null;
|
||||
if (notify) {
|
||||
mCallback.onRemoved(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the item at the given index and calls {@link Callback#onChanged(int, int)} and/or
|
||||
* {@link Callback#onMoved(int, int)} if necessary.
|
||||
* <p>
|
||||
* You can use this method if you need to change an existing Item such that its position in the
|
||||
* list may change.
|
||||
* <p>
|
||||
* If the new object is a different object (<code>get(index) != item</code>) and
|
||||
* {@link Callback#areContentsTheSame(Object, Object)} returns <code>true</code>, SortedList
|
||||
* avoids calling {@link Callback#onChanged(int, int)} otherwise it calls
|
||||
* {@link Callback#onChanged(int, int)}.
|
||||
* <p>
|
||||
* If the new position of the item is different than the provided <code>index</code>,
|
||||
* SortedList
|
||||
* calls {@link Callback#onMoved(int, int)}.
|
||||
*
|
||||
* @param index The index of the item to replace
|
||||
* @param item The item to replace the item at the given Index.
|
||||
* @see #add(Object)
|
||||
*/
|
||||
public void updateItemAt(int index, T item) {
|
||||
final T existing = get(index);
|
||||
// assume changed if the same object is given back
|
||||
boolean contentsChanged = existing == item || !mCallback.areContentsTheSame(existing, item);
|
||||
if (existing != item) {
|
||||
// different items, we can use comparison and may avoid lookup
|
||||
final int cmp = mCallback.compare(existing, item);
|
||||
if (cmp == 0) {
|
||||
mData[index] = item;
|
||||
if (contentsChanged) {
|
||||
mCallback.onChanged(index, 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (contentsChanged) {
|
||||
mCallback.onChanged(index, 1);
|
||||
}
|
||||
// TODO this done in 1 pass to avoid shifting twice.
|
||||
removeItemAtIndex(index, false);
|
||||
int newIndex = add(item, false);
|
||||
if (index != newIndex) {
|
||||
mCallback.onMoved(index, newIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be used to recalculate the position of the item at the given index, without
|
||||
* triggering an {@link Callback#onChanged(int, int)} callback.
|
||||
* <p>
|
||||
* If you are editing objects in the list such that their position in the list may change but
|
||||
* you don't want to trigger an onChange animation, you can use this method to re-position it.
|
||||
* If the item changes position, SortedList will call {@link Callback#onMoved(int, int)}
|
||||
* without
|
||||
* calling {@link Callback#onChanged(int, int)}.
|
||||
* <p>
|
||||
* A sample usage may look like:
|
||||
*
|
||||
* <pre>
|
||||
* final int position = mSortedList.indexOf(item);
|
||||
* item.incrementPriority(); // assume items are sorted by priority
|
||||
* mSortedList.recalculatePositionOfItemAt(position);
|
||||
* </pre>
|
||||
* In the example above, because the sorting criteria of the item has been changed,
|
||||
* mSortedList.indexOf(item) will not be able to find the item. This is why the code above
|
||||
* first
|
||||
* gets the position before editing the item, edits it and informs the SortedList that item
|
||||
* should be repositioned.
|
||||
*
|
||||
* @param index The current index of the Item whose position should be re-calculated.
|
||||
* @see #updateItemAt(int, Object)
|
||||
* @see #add(Object)
|
||||
*/
|
||||
public void recalculatePositionOfItemAt(int index) {
|
||||
// TODO can be improved
|
||||
final T item = get(index);
|
||||
removeItemAtIndex(index, false);
|
||||
int newIndex = add(item, false);
|
||||
if (index != newIndex) {
|
||||
mCallback.onMoved(index, newIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item at the given index.
|
||||
*
|
||||
* @param index The index of the item to retrieve.
|
||||
* @return The item at the given index.
|
||||
* @throws java.lang.IndexOutOfBoundsException if provided index is negative or larger than the
|
||||
* size of the list.
|
||||
*/
|
||||
public T get(int index) throws IndexOutOfBoundsException {
|
||||
if (index >= mSize || index < 0) {
|
||||
throw new IndexOutOfBoundsException("Asked to get item at " + index + " but size is "
|
||||
+ mSize);
|
||||
}
|
||||
return mData[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position of the provided item.
|
||||
*
|
||||
* @param item The item to query for position.
|
||||
* @return The position of the provided item or {@link #INVALID_POSITION} if item is not in the
|
||||
* list.
|
||||
*/
|
||||
public int indexOf(T item) {
|
||||
return findIndexOf(item, LOOKUP);
|
||||
}
|
||||
|
||||
private int findIndexOf(T item, int reason) {
|
||||
int left = 0;
|
||||
int right = mSize;
|
||||
while (left < right) {
|
||||
final int middle = (left + right) / 2;
|
||||
T myItem = mData[middle];
|
||||
final int cmp = mCallback.compare(myItem, item);
|
||||
if (cmp < 0) {
|
||||
left = middle + 1;
|
||||
} else if (cmp == 0) {
|
||||
if (mCallback.areItemsTheSame(myItem, item)) {
|
||||
return middle;
|
||||
} else {
|
||||
int exact = linearEqualitySearch(item, middle, left, right);
|
||||
if (reason == INSERTION) {
|
||||
return exact == INVALID_POSITION ? middle : exact;
|
||||
} else {
|
||||
return exact;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
right = middle;
|
||||
}
|
||||
}
|
||||
return reason == INSERTION ? left : INVALID_POSITION;
|
||||
}
|
||||
|
||||
private int linearEqualitySearch(T item, int middle, int left, int right) {
|
||||
// go left
|
||||
for (int next = middle - 1; next >= left; next--) {
|
||||
T nextItem = mData[next];
|
||||
int cmp = mCallback.compare(nextItem, item);
|
||||
if (cmp != 0) {
|
||||
break;
|
||||
}
|
||||
if (mCallback.areItemsTheSame(nextItem, item)) {
|
||||
return next;
|
||||
}
|
||||
}
|
||||
for (int next = middle + 1; next < right; next++) {
|
||||
T nextItem = mData[next];
|
||||
int cmp = mCallback.compare(nextItem, item);
|
||||
if (cmp != 0) {
|
||||
break;
|
||||
}
|
||||
if (mCallback.areItemsTheSame(nextItem, item)) {
|
||||
return next;
|
||||
}
|
||||
}
|
||||
return INVALID_POSITION;
|
||||
}
|
||||
|
||||
private void addToData(int index, T item) {
|
||||
if (index > mSize) {
|
||||
throw new IndexOutOfBoundsException(
|
||||
"cannot add item to " + index + " because size is " + mSize);
|
||||
}
|
||||
if (mSize == mData.length) {
|
||||
// we are at the limit enlarge
|
||||
T[] newData = (T[]) Array.newInstance(mTClass, mData.length + CAPACITY_GROWTH);
|
||||
System.arraycopy(mData, 0, newData, 0, index);
|
||||
newData[index] = item;
|
||||
System.arraycopy(mData, index, newData, index + 1, mSize - index);
|
||||
mData = newData;
|
||||
} else {
|
||||
// just shift, we fit
|
||||
System.arraycopy(mData, index, mData, index + 1, mSize - index);
|
||||
mData[index] = item;
|
||||
}
|
||||
mSize++;
|
||||
}
|
||||
|
||||
/**
|
||||
* The class that controls the behavior of the {@link SortedList}.
|
||||
* <p>
|
||||
* It defines how items should be sorted and how duplicates should be handled.
|
||||
* <p>
|
||||
* SortedList calls the callback methods on this class to notify changes about the underlying
|
||||
* data.
|
||||
*/
|
||||
public static abstract class Callback<T2> {
|
||||
|
||||
/**
|
||||
* Similar to {@link java.util.Comparator#compare(Object, Object)}, should compare two and
|
||||
* return how they should be ordered.
|
||||
*
|
||||
* @param o1 The first object to compare.
|
||||
* @param o2 The second object to compare.
|
||||
* @return a negative integer, zero, or a positive integer as the
|
||||
* first argument is less than, equal to, or greater than the
|
||||
* second.
|
||||
*/
|
||||
abstract public int compare(T2 o1, T2 o2);
|
||||
|
||||
/**
|
||||
* Called by the SortedList when an item is inserted at the given position.
|
||||
*
|
||||
* @param position The position of the new item.
|
||||
* @param count The number of items that have been added.
|
||||
*/
|
||||
abstract public void onInserted(int position, int count);
|
||||
|
||||
/**
|
||||
* Called by the SortedList when an item is removed from the given position.
|
||||
*
|
||||
* @param position The position of the item which has been removed.
|
||||
* @param count The number of items which have been removed.
|
||||
*/
|
||||
abstract public void onRemoved(int position, int count);
|
||||
|
||||
/**
|
||||
* Called by the SortedList when an item changes its position in the list.
|
||||
*
|
||||
* @param fromPosition The previous position of the item before the move.
|
||||
* @param toPosition The new position of the item.
|
||||
*/
|
||||
abstract public void onMoved(int fromPosition, int toPosition);
|
||||
|
||||
/**
|
||||
* Called by the SortedList when the item at the given position is updated.
|
||||
*
|
||||
* @param position The position of the item which has been updated.
|
||||
* @param count The number of items which has changed.
|
||||
*/
|
||||
abstract public void onChanged(int position, int count);
|
||||
|
||||
/**
|
||||
* Called by the SortedList when it wants to check whether two items have the same data
|
||||
* or not. SortedList uses this information to decide whether it should call
|
||||
* {@link #onChanged(int, int)} or not.
|
||||
* <p>
|
||||
* SortedList uses this method to check equality instead of {@link Object#equals(Object)}
|
||||
* so
|
||||
* that you can change its behavior depending on your UI.
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* @param oldItem The previous representation of the object.
|
||||
* @param newItem The new object that replaces the previous one.
|
||||
* @return True if the contents of the items are the same or false if they are different.
|
||||
*/
|
||||
abstract public boolean areContentsTheSame(T2 oldItem, T2 newItem);
|
||||
|
||||
/**
|
||||
* Called by the SortedList to decide whether two object represent the same Item or not.
|
||||
* <p>
|
||||
* For example, if your items have unique ids, this method should check their equality.
|
||||
*
|
||||
* @param item1 The first item to check.
|
||||
* @param item2 The second item to check.
|
||||
* @return True if the two items represent the same object or false if they are different.
|
||||
*/
|
||||
abstract public boolean areItemsTheSame(T2 item1, T2 item2);
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback implementation that can batch notify events dispatched by the SortedList.
|
||||
* <p>
|
||||
* This class can be useful if you want to do multiple operations on a SortedList but don't
|
||||
* want to dispatch each event one by one, which may result in a performance issue.
|
||||
* <p>
|
||||
* For example, if you are going to add multiple items to a SortedList, BatchedCallback call
|
||||
* convert individual <code>onInserted(index, 1)</code> calls into one
|
||||
* <code>onInserted(index, N)</code> if items are added into consecutive indices. This change
|
||||
* can help RecyclerView resolve changes much more easily.
|
||||
* <p>
|
||||
* If consecutive changes in the SortedList are not suitable for batching, BatchingCallback
|
||||
* dispatches them as soon as such case is detected. After your edits on the SortedList is
|
||||
* complete, you <b>must</b> always call {@link BatchedCallback#dispatchLastEvent()} to flush
|
||||
* all changes to the Callback.
|
||||
*/
|
||||
public static class BatchedCallback<T2> extends Callback<T2> {
|
||||
|
||||
private final Callback<T2> mWrappedCallback;
|
||||
static final int TYPE_NONE = 0;
|
||||
static final int TYPE_ADD = 1;
|
||||
static final int TYPE_REMOVE = 2;
|
||||
static final int TYPE_CHANGE = 3;
|
||||
static final int TYPE_MOVE = 4;
|
||||
|
||||
int mLastEventType = TYPE_NONE;
|
||||
int mLastEventPosition = -1;
|
||||
int mLastEventCount = -1;
|
||||
|
||||
/**
|
||||
* Creates a new BatchedCallback that wraps the provided Callback.
|
||||
*
|
||||
* @param wrappedCallback The Callback which should received the data change callbacks.
|
||||
* Other method calls (e.g. {@link #compare(Object, Object)} from
|
||||
* the SortedList are directly forwarded to this Callback.
|
||||
*/
|
||||
public BatchedCallback(Callback<T2> wrappedCallback) {
|
||||
mWrappedCallback = wrappedCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(T2 o1, T2 o2) {
|
||||
return mWrappedCallback.compare(o1, o2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInserted(int position, int count) {
|
||||
if (mLastEventType == TYPE_ADD && position >= mLastEventPosition
|
||||
&& position <= mLastEventPosition + mLastEventCount) {
|
||||
mLastEventCount += count;
|
||||
mLastEventPosition = Math.min(position, mLastEventPosition);
|
||||
return;
|
||||
}
|
||||
dispatchLastEvent();
|
||||
mLastEventPosition = position;
|
||||
mLastEventCount = count;
|
||||
mLastEventType = TYPE_ADD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(int position, int count) {
|
||||
if (mLastEventType == TYPE_REMOVE && mLastEventPosition == position) {
|
||||
mLastEventCount += count;
|
||||
return;
|
||||
}
|
||||
dispatchLastEvent();
|
||||
mLastEventPosition = position;
|
||||
mLastEventCount = count;
|
||||
mLastEventType = TYPE_REMOVE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoved(int fromPosition, int toPosition) {
|
||||
dispatchLastEvent();//moves are not merged
|
||||
mWrappedCallback.onMoved(fromPosition, toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(int position, int count) {
|
||||
if (mLastEventType == TYPE_CHANGE &&
|
||||
!(position > mLastEventPosition + mLastEventCount
|
||||
|| position + count < mLastEventPosition)) {
|
||||
// take potential overlap into account
|
||||
int previousEnd = mLastEventPosition + mLastEventCount;
|
||||
mLastEventPosition = Math.min(position, mLastEventPosition);
|
||||
mLastEventCount = Math.max(previousEnd, position + count) - mLastEventPosition;
|
||||
return;
|
||||
}
|
||||
dispatchLastEvent();
|
||||
mLastEventPosition = position;
|
||||
mLastEventCount = count;
|
||||
mLastEventType = TYPE_CHANGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(T2 oldItem, T2 newItem) {
|
||||
return mWrappedCallback.areContentsTheSame(oldItem, newItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(T2 item1, T2 item2) {
|
||||
return mWrappedCallback.areItemsTheSame(item1, item2);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method dispatches any pending event notifications to the wrapped Callback.
|
||||
* You <b>must</b> always call this method after you are done with editing the SortedList.
|
||||
*/
|
||||
public void dispatchLastEvent() {
|
||||
if (mLastEventType == TYPE_NONE) {
|
||||
return;
|
||||
}
|
||||
switch (mLastEventType) {
|
||||
case TYPE_ADD:
|
||||
mWrappedCallback.onInserted(mLastEventPosition, mLastEventCount);
|
||||
break;
|
||||
case TYPE_REMOVE:
|
||||
mWrappedCallback.onRemoved(mLastEventPosition, mLastEventCount);
|
||||
break;
|
||||
case TYPE_CHANGE:
|
||||
mWrappedCallback.onChanged(mLastEventPosition, mLastEventCount);
|
||||
break;
|
||||
}
|
||||
mLastEventType = TYPE_NONE;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,733 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.android.support.widget;
|
||||
|
||||
import android.support.v4.util.Pools;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.telegram.android.support.widget.RecyclerView.*;
|
||||
|
||||
/**
|
||||
* Helper class that can enqueue and process adapter update operations.
|
||||
* <p>
|
||||
* To support animations, RecyclerView presents an older version the Adapter to best represent
|
||||
* previous state of the layout. Sometimes, this is not trivial when items are removed that were
|
||||
* not laid out, in which case, RecyclerView has no way of providing that item's view for
|
||||
* animations.
|
||||
* <p>
|
||||
* AdapterHelper creates an UpdateOp for each adapter data change then pre-processes them. During
|
||||
* pre processing, AdapterHelper finds out which UpdateOps can be deferred to second layout pass
|
||||
* and which cannot. For the UpdateOps that cannot be deferred, AdapterHelper will change them
|
||||
* according to previously deferred operation and dispatch them before the first layout pass. It
|
||||
* also takes care of updating deferred UpdateOps since order of operations is changed by this
|
||||
* process.
|
||||
* <p>
|
||||
* Although operations may be forwarded to LayoutManager in different orders, resulting data set
|
||||
* is guaranteed to be the consistent.
|
||||
*/
|
||||
class AdapterHelper implements OpReorderer.Callback {
|
||||
|
||||
final static int POSITION_TYPE_INVISIBLE = 0;
|
||||
|
||||
final static int POSITION_TYPE_NEW_OR_LAID_OUT = 1;
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final String TAG = "AHT";
|
||||
|
||||
private Pools.Pool<UpdateOp> mUpdateOpPool = new Pools.SimplePool<UpdateOp>(UpdateOp.POOL_SIZE);
|
||||
|
||||
final ArrayList<UpdateOp> mPendingUpdates = new ArrayList<UpdateOp>();
|
||||
|
||||
final ArrayList<UpdateOp> mPostponedList = new ArrayList<UpdateOp>();
|
||||
|
||||
final Callback mCallback;
|
||||
|
||||
Runnable mOnItemProcessedCallback;
|
||||
|
||||
final boolean mDisableRecycler;
|
||||
|
||||
final OpReorderer mOpReorderer;
|
||||
|
||||
AdapterHelper(Callback callback) {
|
||||
this(callback, false);
|
||||
}
|
||||
|
||||
AdapterHelper(Callback callback, boolean disableRecycler) {
|
||||
mCallback = callback;
|
||||
mDisableRecycler = disableRecycler;
|
||||
mOpReorderer = new OpReorderer(this);
|
||||
}
|
||||
|
||||
AdapterHelper addUpdateOp(UpdateOp... ops) {
|
||||
Collections.addAll(mPendingUpdates, ops);
|
||||
return this;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
recycleUpdateOpsAndClearList(mPendingUpdates);
|
||||
recycleUpdateOpsAndClearList(mPostponedList);
|
||||
}
|
||||
|
||||
void preProcess() {
|
||||
mOpReorderer.reorderOps(mPendingUpdates);
|
||||
final int count = mPendingUpdates.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
UpdateOp op = mPendingUpdates.get(i);
|
||||
switch (op.cmd) {
|
||||
case UpdateOp.ADD:
|
||||
applyAdd(op);
|
||||
break;
|
||||
case UpdateOp.REMOVE:
|
||||
applyRemove(op);
|
||||
break;
|
||||
case UpdateOp.UPDATE:
|
||||
applyUpdate(op);
|
||||
break;
|
||||
case UpdateOp.MOVE:
|
||||
applyMove(op);
|
||||
break;
|
||||
}
|
||||
if (mOnItemProcessedCallback != null) {
|
||||
mOnItemProcessedCallback.run();
|
||||
}
|
||||
}
|
||||
mPendingUpdates.clear();
|
||||
}
|
||||
|
||||
void consumePostponedUpdates() {
|
||||
final int count = mPostponedList.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
mCallback.onDispatchSecondPass(mPostponedList.get(i));
|
||||
}
|
||||
recycleUpdateOpsAndClearList(mPostponedList);
|
||||
}
|
||||
|
||||
private void applyMove(UpdateOp op) {
|
||||
// MOVE ops are pre-processed so at this point, we know that item is still in the adapter.
|
||||
// otherwise, it would be converted into a REMOVE operation
|
||||
postponeAndUpdateViewHolders(op);
|
||||
}
|
||||
|
||||
private void applyRemove(UpdateOp op) {
|
||||
int tmpStart = op.positionStart;
|
||||
int tmpCount = 0;
|
||||
int tmpEnd = op.positionStart + op.itemCount;
|
||||
int type = -1;
|
||||
for (int position = op.positionStart; position < tmpEnd; position++) {
|
||||
boolean typeChanged = false;
|
||||
ViewHolder vh = mCallback.findViewHolder(position);
|
||||
if (vh != null || canFindInPreLayout(position)) {
|
||||
// If a ViewHolder exists or this is a newly added item, we can defer this update
|
||||
// to post layout stage.
|
||||
// * For existing ViewHolders, we'll fake its existence in the pre-layout phase.
|
||||
// * For items that are added and removed in the same process cycle, they won't
|
||||
// have any effect in pre-layout since their add ops are already deferred to
|
||||
// post-layout pass.
|
||||
if (type == POSITION_TYPE_INVISIBLE) {
|
||||
// Looks like we have other updates that we cannot merge with this one.
|
||||
// Create an UpdateOp and dispatch it to LayoutManager.
|
||||
UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount);
|
||||
dispatchAndUpdateViewHolders(newOp);
|
||||
typeChanged = true;
|
||||
}
|
||||
type = POSITION_TYPE_NEW_OR_LAID_OUT;
|
||||
} else {
|
||||
// This update cannot be recovered because we don't have a ViewHolder representing
|
||||
// this position. Instead, post it to LayoutManager immediately
|
||||
if (type == POSITION_TYPE_NEW_OR_LAID_OUT) {
|
||||
// Looks like we have other updates that we cannot merge with this one.
|
||||
// Create UpdateOp op and dispatch it to LayoutManager.
|
||||
UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount);
|
||||
postponeAndUpdateViewHolders(newOp);
|
||||
typeChanged = true;
|
||||
}
|
||||
type = POSITION_TYPE_INVISIBLE;
|
||||
}
|
||||
if (typeChanged) {
|
||||
position -= tmpCount; // also equal to tmpStart
|
||||
tmpEnd -= tmpCount;
|
||||
tmpCount = 1;
|
||||
} else {
|
||||
tmpCount++;
|
||||
}
|
||||
}
|
||||
if (tmpCount != op.itemCount) { // all 1 effect
|
||||
recycleUpdateOp(op);
|
||||
op = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount);
|
||||
}
|
||||
if (type == POSITION_TYPE_INVISIBLE) {
|
||||
dispatchAndUpdateViewHolders(op);
|
||||
} else {
|
||||
postponeAndUpdateViewHolders(op);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyUpdate(UpdateOp op) {
|
||||
int tmpStart = op.positionStart;
|
||||
int tmpCount = 0;
|
||||
int tmpEnd = op.positionStart + op.itemCount;
|
||||
int type = -1;
|
||||
for (int position = op.positionStart; position < tmpEnd; position++) {
|
||||
ViewHolder vh = mCallback.findViewHolder(position);
|
||||
if (vh != null || canFindInPreLayout(position)) { // deferred
|
||||
if (type == POSITION_TYPE_INVISIBLE) {
|
||||
UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount);
|
||||
dispatchAndUpdateViewHolders(newOp);
|
||||
tmpCount = 0;
|
||||
tmpStart = position;
|
||||
}
|
||||
type = POSITION_TYPE_NEW_OR_LAID_OUT;
|
||||
} else { // applied
|
||||
if (type == POSITION_TYPE_NEW_OR_LAID_OUT) {
|
||||
UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount);
|
||||
postponeAndUpdateViewHolders(newOp);
|
||||
tmpCount = 0;
|
||||
tmpStart = position;
|
||||
}
|
||||
type = POSITION_TYPE_INVISIBLE;
|
||||
}
|
||||
tmpCount++;
|
||||
}
|
||||
if (tmpCount != op.itemCount) { // all 1 effect
|
||||
recycleUpdateOp(op);
|
||||
op = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount);
|
||||
}
|
||||
if (type == POSITION_TYPE_INVISIBLE) {
|
||||
dispatchAndUpdateViewHolders(op);
|
||||
} else {
|
||||
postponeAndUpdateViewHolders(op);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchAndUpdateViewHolders(UpdateOp op) {
|
||||
// tricky part.
|
||||
// traverse all postpones and revert their changes on this op if necessary, apply updated
|
||||
// dispatch to them since now they are after this op.
|
||||
if (op.cmd == UpdateOp.ADD || op.cmd == UpdateOp.MOVE) {
|
||||
throw new IllegalArgumentException("should not dispatch add or move for pre layout");
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "dispatch (pre)" + op);
|
||||
Log.d(TAG, "postponed state before:");
|
||||
for (UpdateOp updateOp : mPostponedList) {
|
||||
Log.d(TAG, updateOp.toString());
|
||||
}
|
||||
Log.d(TAG, "----");
|
||||
}
|
||||
|
||||
// handle each pos 1 by 1 to ensure continuity. If it breaks, dispatch partial
|
||||
// TODO Since move ops are pushed to end, we should not need this anymore
|
||||
int tmpStart = updatePositionWithPostponed(op.positionStart, op.cmd);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "pos:" + op.positionStart + ",updatedPos:" + tmpStart);
|
||||
}
|
||||
int tmpCnt = 1;
|
||||
int offsetPositionForPartial = op.positionStart;
|
||||
final int positionMultiplier;
|
||||
switch (op.cmd) {
|
||||
case UpdateOp.UPDATE:
|
||||
positionMultiplier = 1;
|
||||
break;
|
||||
case UpdateOp.REMOVE:
|
||||
positionMultiplier = 0;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("op should be remove or update." + op);
|
||||
}
|
||||
for (int p = 1; p < op.itemCount; p++) {
|
||||
final int pos = op.positionStart + (positionMultiplier * p);
|
||||
int updatedPos = updatePositionWithPostponed(pos, op.cmd);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "pos:" + pos + ",updatedPos:" + updatedPos);
|
||||
}
|
||||
boolean continuous = false;
|
||||
switch (op.cmd) {
|
||||
case UpdateOp.UPDATE:
|
||||
continuous = updatedPos == tmpStart + 1;
|
||||
break;
|
||||
case UpdateOp.REMOVE:
|
||||
continuous = updatedPos == tmpStart;
|
||||
break;
|
||||
}
|
||||
if (continuous) {
|
||||
tmpCnt++;
|
||||
} else {
|
||||
// need to dispatch this separately
|
||||
UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "need to dispatch separately " + tmp);
|
||||
}
|
||||
dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial);
|
||||
recycleUpdateOp(tmp);
|
||||
if (op.cmd == UpdateOp.UPDATE) {
|
||||
offsetPositionForPartial += tmpCnt;
|
||||
}
|
||||
tmpStart = updatedPos;// need to remove previously dispatched
|
||||
tmpCnt = 1;
|
||||
}
|
||||
}
|
||||
recycleUpdateOp(op);
|
||||
if (tmpCnt > 0) {
|
||||
UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "dispatching:" + tmp);
|
||||
}
|
||||
dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial);
|
||||
recycleUpdateOp(tmp);
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "post dispatch");
|
||||
Log.d(TAG, "postponed state after:");
|
||||
for (UpdateOp updateOp : mPostponedList) {
|
||||
Log.d(TAG, updateOp.toString());
|
||||
}
|
||||
Log.d(TAG, "----");
|
||||
}
|
||||
}
|
||||
|
||||
void dispatchFirstPassAndUpdateViewHolders(UpdateOp op, int offsetStart) {
|
||||
mCallback.onDispatchFirstPass(op);
|
||||
switch (op.cmd) {
|
||||
case UpdateOp.REMOVE:
|
||||
mCallback.offsetPositionsForRemovingInvisible(offsetStart, op.itemCount);
|
||||
break;
|
||||
case UpdateOp.UPDATE:
|
||||
mCallback.markViewHoldersUpdated(offsetStart, op.itemCount);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("only remove and update ops can be dispatched"
|
||||
+ " in first pass");
|
||||
}
|
||||
}
|
||||
|
||||
private int updatePositionWithPostponed(int pos, int cmd) {
|
||||
final int count = mPostponedList.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
UpdateOp postponed = mPostponedList.get(i);
|
||||
if (postponed.cmd == UpdateOp.MOVE) {
|
||||
int start, end;
|
||||
if (postponed.positionStart < postponed.itemCount) {
|
||||
start = postponed.positionStart;
|
||||
end = postponed.itemCount;
|
||||
} else {
|
||||
start = postponed.itemCount;
|
||||
end = postponed.positionStart;
|
||||
}
|
||||
if (pos >= start && pos <= end) {
|
||||
//i'm affected
|
||||
if (start == postponed.positionStart) {
|
||||
if (cmd == UpdateOp.ADD) {
|
||||
postponed.itemCount++;
|
||||
} else if (cmd == UpdateOp.REMOVE) {
|
||||
postponed.itemCount--;
|
||||
}
|
||||
// op moved to left, move it right to revert
|
||||
pos++;
|
||||
} else {
|
||||
if (cmd == UpdateOp.ADD) {
|
||||
postponed.positionStart++;
|
||||
} else if (cmd == UpdateOp.REMOVE) {
|
||||
postponed.positionStart--;
|
||||
}
|
||||
// op was moved right, move left to revert
|
||||
pos--;
|
||||
}
|
||||
} else if (pos < postponed.positionStart) {
|
||||
// postponed MV is outside the dispatched OP. if it is before, offset
|
||||
if (cmd == UpdateOp.ADD) {
|
||||
postponed.positionStart++;
|
||||
postponed.itemCount++;
|
||||
} else if (cmd == UpdateOp.REMOVE) {
|
||||
postponed.positionStart--;
|
||||
postponed.itemCount--;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (postponed.positionStart <= pos) {
|
||||
if (postponed.cmd == UpdateOp.ADD) {
|
||||
pos -= postponed.itemCount;
|
||||
} else if (postponed.cmd == UpdateOp.REMOVE) {
|
||||
pos += postponed.itemCount;
|
||||
}
|
||||
} else {
|
||||
if (cmd == UpdateOp.ADD) {
|
||||
postponed.positionStart++;
|
||||
} else if (cmd == UpdateOp.REMOVE) {
|
||||
postponed.positionStart--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "dispath (step" + i + ")");
|
||||
Log.d(TAG, "postponed state:" + i + ", pos:" + pos);
|
||||
for (UpdateOp updateOp : mPostponedList) {
|
||||
Log.d(TAG, updateOp.toString());
|
||||
}
|
||||
Log.d(TAG, "----");
|
||||
}
|
||||
}
|
||||
for (int i = mPostponedList.size() - 1; i >= 0; i--) {
|
||||
UpdateOp op = mPostponedList.get(i);
|
||||
if (op.cmd == UpdateOp.MOVE) {
|
||||
if (op.itemCount == op.positionStart || op.itemCount < 0) {
|
||||
mPostponedList.remove(i);
|
||||
recycleUpdateOp(op);
|
||||
}
|
||||
} else if (op.itemCount <= 0) {
|
||||
mPostponedList.remove(i);
|
||||
recycleUpdateOp(op);
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
private boolean canFindInPreLayout(int position) {
|
||||
final int count = mPostponedList.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
UpdateOp op = mPostponedList.get(i);
|
||||
if (op.cmd == UpdateOp.MOVE) {
|
||||
if (findPositionOffset(op.itemCount, i + 1) == position) {
|
||||
return true;
|
||||
}
|
||||
} else if (op.cmd == UpdateOp.ADD) {
|
||||
// TODO optimize.
|
||||
final int end = op.positionStart + op.itemCount;
|
||||
for (int pos = op.positionStart; pos < end; pos++) {
|
||||
if (findPositionOffset(pos, i + 1) == position) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void applyAdd(UpdateOp op) {
|
||||
postponeAndUpdateViewHolders(op);
|
||||
}
|
||||
|
||||
private void postponeAndUpdateViewHolders(UpdateOp op) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "postponing " + op);
|
||||
}
|
||||
mPostponedList.add(op);
|
||||
switch (op.cmd) {
|
||||
case UpdateOp.ADD:
|
||||
mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
|
||||
break;
|
||||
case UpdateOp.MOVE:
|
||||
mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
|
||||
break;
|
||||
case UpdateOp.REMOVE:
|
||||
mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart,
|
||||
op.itemCount);
|
||||
break;
|
||||
case UpdateOp.UPDATE:
|
||||
mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown update op type for " + op);
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasPendingUpdates() {
|
||||
return mPendingUpdates.size() > 0;
|
||||
}
|
||||
|
||||
int findPositionOffset(int position) {
|
||||
return findPositionOffset(position, 0);
|
||||
}
|
||||
|
||||
int findPositionOffset(int position, int firstPostponedItem) {
|
||||
int count = mPostponedList.size();
|
||||
for (int i = firstPostponedItem; i < count; ++i) {
|
||||
UpdateOp op = mPostponedList.get(i);
|
||||
if (op.cmd == UpdateOp.MOVE) {
|
||||
if (op.positionStart == position) {
|
||||
position = op.itemCount;
|
||||
} else {
|
||||
if (op.positionStart < position) {
|
||||
position--; // like a remove
|
||||
}
|
||||
if (op.itemCount <= position) {
|
||||
position++; // like an add
|
||||
}
|
||||
}
|
||||
} else if (op.positionStart <= position) {
|
||||
if (op.cmd == UpdateOp.REMOVE) {
|
||||
if (position < op.positionStart + op.itemCount) {
|
||||
return -1;
|
||||
}
|
||||
position -= op.itemCount;
|
||||
} else if (op.cmd == UpdateOp.ADD) {
|
||||
position += op.itemCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if updates should be processed.
|
||||
*/
|
||||
boolean onItemRangeChanged(int positionStart, int itemCount) {
|
||||
mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount));
|
||||
return mPendingUpdates.size() == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if updates should be processed.
|
||||
*/
|
||||
boolean onItemRangeInserted(int positionStart, int itemCount) {
|
||||
mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount));
|
||||
return mPendingUpdates.size() == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if updates should be processed.
|
||||
*/
|
||||
boolean onItemRangeRemoved(int positionStart, int itemCount) {
|
||||
mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount));
|
||||
return mPendingUpdates.size() == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if updates should be processed.
|
||||
*/
|
||||
boolean onItemRangeMoved(int from, int to, int itemCount) {
|
||||
if (from == to) {
|
||||
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));
|
||||
return mPendingUpdates.size() == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips pre-processing and applies all updates in one pass.
|
||||
*/
|
||||
void consumeUpdatesInOnePass() {
|
||||
// we still consume postponed updates (if there is) in case there was a pre-process call
|
||||
// w/o a matching consumePostponedUpdates.
|
||||
consumePostponedUpdates();
|
||||
final int count = mPendingUpdates.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
UpdateOp op = mPendingUpdates.get(i);
|
||||
switch (op.cmd) {
|
||||
case UpdateOp.ADD:
|
||||
mCallback.onDispatchSecondPass(op);
|
||||
mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
|
||||
break;
|
||||
case UpdateOp.REMOVE:
|
||||
mCallback.onDispatchSecondPass(op);
|
||||
mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount);
|
||||
break;
|
||||
case UpdateOp.UPDATE:
|
||||
mCallback.onDispatchSecondPass(op);
|
||||
mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount);
|
||||
break;
|
||||
case UpdateOp.MOVE:
|
||||
mCallback.onDispatchSecondPass(op);
|
||||
mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
|
||||
break;
|
||||
}
|
||||
if (mOnItemProcessedCallback != null) {
|
||||
mOnItemProcessedCallback.run();
|
||||
}
|
||||
}
|
||||
recycleUpdateOpsAndClearList(mPendingUpdates);
|
||||
}
|
||||
|
||||
public int applyPendingUpdatesToPosition(int position) {
|
||||
final int size = mPendingUpdates.size();
|
||||
for (int i = 0; i < size; i ++) {
|
||||
UpdateOp op = mPendingUpdates.get(i);
|
||||
switch (op.cmd) {
|
||||
case UpdateOp.ADD:
|
||||
if (op.positionStart <= position) {
|
||||
position += op.itemCount;
|
||||
}
|
||||
break;
|
||||
case UpdateOp.REMOVE:
|
||||
if (op.positionStart <= position) {
|
||||
final int end = op.positionStart + op.itemCount;
|
||||
if (end > position) {
|
||||
return RecyclerView.NO_POSITION;
|
||||
}
|
||||
position -= op.itemCount;
|
||||
}
|
||||
break;
|
||||
case UpdateOp.MOVE:
|
||||
if (op.positionStart == position) {
|
||||
position = op.itemCount;//position end
|
||||
} else {
|
||||
if (op.positionStart < position) {
|
||||
position -= 1;
|
||||
}
|
||||
if (op.itemCount <= position) {
|
||||
position += 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queued operation to happen when child views are updated.
|
||||
*/
|
||||
static class UpdateOp {
|
||||
|
||||
static final int ADD = 0;
|
||||
|
||||
static final int REMOVE = 1;
|
||||
|
||||
static final int UPDATE = 2;
|
||||
|
||||
static final int MOVE = 3;
|
||||
|
||||
static final int POOL_SIZE = 30;
|
||||
|
||||
int cmd;
|
||||
|
||||
int positionStart;
|
||||
|
||||
// holds the target position if this is a MOVE
|
||||
int itemCount;
|
||||
|
||||
UpdateOp(int cmd, int positionStart, int itemCount) {
|
||||
this.cmd = cmd;
|
||||
this.positionStart = positionStart;
|
||||
this.itemCount = itemCount;
|
||||
}
|
||||
|
||||
String cmdToString() {
|
||||
switch (cmd) {
|
||||
case ADD:
|
||||
return "add";
|
||||
case REMOVE:
|
||||
return "rm";
|
||||
case UPDATE:
|
||||
return "up";
|
||||
case MOVE:
|
||||
return "mv";
|
||||
}
|
||||
return "??";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + cmdToString() + ",s:" + positionStart + "c:" + itemCount + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateOp op = (UpdateOp) o;
|
||||
|
||||
if (cmd != op.cmd) {
|
||||
return false;
|
||||
}
|
||||
if (cmd == MOVE && Math.abs(itemCount - positionStart) == 1) {
|
||||
// reverse of this is also true
|
||||
if (itemCount == op.positionStart && positionStart == op.itemCount) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (itemCount != op.itemCount) {
|
||||
return false;
|
||||
}
|
||||
if (positionStart != op.positionStart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = cmd;
|
||||
result = 31 * result + positionStart;
|
||||
result = 31 * result + itemCount;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount) {
|
||||
UpdateOp op = mUpdateOpPool.acquire();
|
||||
if (op == null) {
|
||||
op = new UpdateOp(cmd, positionStart, itemCount);
|
||||
} else {
|
||||
op.cmd = cmd;
|
||||
op.positionStart = positionStart;
|
||||
op.itemCount = itemCount;
|
||||
}
|
||||
return op;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycleUpdateOp(UpdateOp op) {
|
||||
if (!mDisableRecycler) {
|
||||
mUpdateOpPool.release(op);
|
||||
}
|
||||
}
|
||||
|
||||
void recycleUpdateOpsAndClearList(List<UpdateOp> ops) {
|
||||
final int count = ops.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
recycleUpdateOp(ops.get(i));
|
||||
}
|
||||
ops.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Contract between AdapterHelper and RecyclerView.
|
||||
*/
|
||||
static interface Callback {
|
||||
|
||||
ViewHolder findViewHolder(int position);
|
||||
|
||||
void offsetPositionsForRemovingInvisible(int positionStart, int itemCount);
|
||||
|
||||
void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount);
|
||||
|
||||
void markViewHoldersUpdated(int positionStart, int itemCount);
|
||||
|
||||
void onDispatchFirstPass(UpdateOp updateOp);
|
||||
|
||||
void onDispatchSecondPass(UpdateOp updateOp);
|
||||
|
||||
void offsetPositionsForAdd(int positionStart, int itemCount);
|
||||
|
||||
void offsetPositionsForMove(int from, int to);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,487 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.android.support.widget;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Helper class to manage children.
|
||||
* <p>
|
||||
* It wraps a RecyclerView and adds ability to hide some children. There are two sets of methods
|
||||
* provided by this class. <b>Regular</b> methods are the ones that replicate ViewGroup methods
|
||||
* like getChildAt, getChildCount etc. These methods ignore hidden children.
|
||||
* <p>
|
||||
* When RecyclerView needs direct access to the view group children, it can call unfiltered
|
||||
* methods like get getUnfilteredChildCount or getUnfilteredChildAt.
|
||||
*/
|
||||
class ChildHelper {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final String TAG = "ChildrenHelper";
|
||||
|
||||
final Callback mCallback;
|
||||
|
||||
final Bucket mBucket;
|
||||
|
||||
final List<View> mHiddenViews;
|
||||
|
||||
ChildHelper(Callback callback) {
|
||||
mCallback = callback;
|
||||
mBucket = new Bucket();
|
||||
mHiddenViews = new ArrayList<View>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a view to the ViewGroup
|
||||
*
|
||||
* @param child View to add.
|
||||
* @param hidden If set to true, this item will be invisible from regular methods.
|
||||
*/
|
||||
void addView(View child, boolean hidden) {
|
||||
addView(child, -1, hidden);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a view to the ViewGroup at an index
|
||||
*
|
||||
* @param child View to add.
|
||||
* @param index Index of the child from the regular perspective (excluding hidden views).
|
||||
* ChildHelper offsets this index to actual ViewGroup index.
|
||||
* @param hidden If set to true, this item will be invisible from regular methods.
|
||||
*/
|
||||
void addView(View child, int index, boolean hidden) {
|
||||
final int offset;
|
||||
if (index < 0) {
|
||||
offset = mCallback.getChildCount();
|
||||
} else {
|
||||
offset = getOffset(index);
|
||||
}
|
||||
mBucket.insert(offset, hidden);
|
||||
if (hidden) {
|
||||
mHiddenViews.add(child);
|
||||
}
|
||||
mCallback.addView(child, offset);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "addViewAt " + index + ",h:" + hidden + ", " + this);
|
||||
}
|
||||
}
|
||||
|
||||
private int getOffset(int index) {
|
||||
if (index < 0) {
|
||||
return -1; //anything below 0 won't work as diff will be undefined.
|
||||
}
|
||||
final int limit = mCallback.getChildCount();
|
||||
int offset = index;
|
||||
while (offset < limit) {
|
||||
final int removedBefore = mBucket.countOnesBefore(offset);
|
||||
final int diff = index - (offset - removedBefore);
|
||||
if (diff == 0) {
|
||||
while (mBucket.get(offset)) { // ensure this offset is not hidden
|
||||
offset ++;
|
||||
}
|
||||
return offset;
|
||||
} else {
|
||||
offset += diff;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the provided View from underlying RecyclerView.
|
||||
*
|
||||
* @param view The view to remove.
|
||||
*/
|
||||
void removeView(View view) {
|
||||
int index = mCallback.indexOfChild(view);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
if (mBucket.remove(index)) {
|
||||
mHiddenViews.remove(view);
|
||||
}
|
||||
mCallback.removeViewAt(index);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "remove View off:" + index + "," + this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the view at the provided index from RecyclerView.
|
||||
*
|
||||
* @param index Index of the child from the regular perspective (excluding hidden views).
|
||||
* ChildHelper offsets this index to actual ViewGroup index.
|
||||
*/
|
||||
void removeViewAt(int index) {
|
||||
final int offset = getOffset(index);
|
||||
final View view = mCallback.getChildAt(offset);
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
if (mBucket.remove(offset)) {
|
||||
mHiddenViews.remove(view);
|
||||
}
|
||||
mCallback.removeViewAt(offset);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "removeViewAt " + index + ", off:" + offset + ", " + this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the child at provided index.
|
||||
*
|
||||
* @param index Index of the child to return in regular perspective.
|
||||
*/
|
||||
View getChildAt(int index) {
|
||||
final int offset = getOffset(index);
|
||||
return mCallback.getChildAt(offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all views from the ViewGroup including the hidden ones.
|
||||
*/
|
||||
void removeAllViewsUnfiltered() {
|
||||
mBucket.reset();
|
||||
mHiddenViews.clear();
|
||||
mCallback.removeAllViews();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "removeAllViewsUnfiltered");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This can be used to find a disappearing view by position.
|
||||
*
|
||||
* @param position The adapter position of the item.
|
||||
* @param type View type, can be {@link RecyclerView#INVALID_TYPE}.
|
||||
* @return A hidden view with a valid ViewHolder that matches the position and type.
|
||||
*/
|
||||
View findHiddenNonRemovedView(int position, int type) {
|
||||
final int count = mHiddenViews.size();
|
||||
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)) {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches the provided view to the underlying ViewGroup.
|
||||
*
|
||||
* @param child Child to attach.
|
||||
* @param index Index of the child to attach in regular perspective.
|
||||
* @param layoutParams LayoutParams for the child.
|
||||
* @param hidden If set to true, this item will be invisible to the regular methods.
|
||||
*/
|
||||
void attachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams,
|
||||
boolean hidden) {
|
||||
final int offset;
|
||||
if (index < 0) {
|
||||
offset = mCallback.getChildCount();
|
||||
} else {
|
||||
offset = getOffset(index);
|
||||
}
|
||||
mBucket.insert(offset, hidden);
|
||||
if (hidden) {
|
||||
mHiddenViews.add(child);
|
||||
}
|
||||
mCallback.attachViewToParent(child, offset, layoutParams);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "attach view to parent index:" + index + ",off:" + offset + "," +
|
||||
"h:" + hidden + ", " + this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of children that are not hidden.
|
||||
*
|
||||
* @return Number of children that are not hidden.
|
||||
* @see #getChildAt(int)
|
||||
*/
|
||||
int getChildCount() {
|
||||
return mCallback.getChildCount() - mHiddenViews.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of children.
|
||||
*
|
||||
* @return The total number of children including the hidden views.
|
||||
* @see #getUnfilteredChildAt(int)
|
||||
*/
|
||||
int getUnfilteredChildCount() {
|
||||
return mCallback.getChildCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a child by ViewGroup offset. ChildHelper won't offset this index.
|
||||
*
|
||||
* @param index ViewGroup index of the child to return.
|
||||
* @return The view in the provided index.
|
||||
*/
|
||||
View getUnfilteredChildAt(int index) {
|
||||
return mCallback.getChildAt(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches the view at the provided index.
|
||||
*
|
||||
* @param index Index of the child to return in regular perspective.
|
||||
*/
|
||||
void detachViewFromParent(int index) {
|
||||
final int offset = getOffset(index);
|
||||
mBucket.remove(offset);
|
||||
mCallback.detachViewFromParent(offset);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "detach view from parent " + index + ", off:" + offset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the child in regular perspective.
|
||||
*
|
||||
* @param child The child whose index will be returned.
|
||||
* @return The regular perspective index of the child or -1 if it does not exists.
|
||||
*/
|
||||
int indexOfChild(View child) {
|
||||
final int index = mCallback.indexOfChild(child);
|
||||
if (index == -1) {
|
||||
return -1;
|
||||
}
|
||||
if (mBucket.get(index)) {
|
||||
if (DEBUG) {
|
||||
throw new IllegalArgumentException("cannot get index of a hidden child");
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
// reverse the index
|
||||
return index - mBucket.countOnesBefore(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a View is visible to LayoutManager or not.
|
||||
*
|
||||
* @param view The child view to check. Should be a child of the Callback.
|
||||
* @return True if the View is not visible to LayoutManager
|
||||
*/
|
||||
boolean isHidden(View view) {
|
||||
return mHiddenViews.contains(view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a child view as hidden.
|
||||
*
|
||||
* @param view The view to hide.
|
||||
*/
|
||||
void hide(View view) {
|
||||
final int offset = mCallback.indexOfChild(view);
|
||||
if (offset < 0) {
|
||||
throw new IllegalArgumentException("view is not a child, cannot hide " + view);
|
||||
}
|
||||
if (DEBUG && mBucket.get(offset)) {
|
||||
throw new RuntimeException("trying to hide same view twice, how come ? " + view);
|
||||
}
|
||||
mBucket.set(offset);
|
||||
mHiddenViews.add(view);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "hiding child " + view + " at offset " + offset+ ", " + this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mBucket.toString() + ", hidden list:" + mHiddenViews.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a view from the ViewGroup if it is hidden.
|
||||
*
|
||||
* @param view The view to remove.
|
||||
* @return True if the View is found and it is hidden. False otherwise.
|
||||
*/
|
||||
boolean removeViewIfHidden(View view) {
|
||||
final int index = mCallback.indexOfChild(view);
|
||||
if (index == -1) {
|
||||
if (mHiddenViews.remove(view) && DEBUG) {
|
||||
throw new IllegalStateException("view is in hidden list but not in view group");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (mBucket.get(index)) {
|
||||
mBucket.remove(index);
|
||||
if (!mHiddenViews.remove(view) && DEBUG) {
|
||||
throw new IllegalStateException(
|
||||
"removed a hidden view but it is not in hidden views list");
|
||||
}
|
||||
mCallback.removeViewAt(index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitset implementation that provides methods to offset indices.
|
||||
*/
|
||||
static class Bucket {
|
||||
|
||||
final static int BITS_PER_WORD = Long.SIZE;
|
||||
|
||||
final static long LAST_BIT = 1L << (Long.SIZE - 1);
|
||||
|
||||
long mData = 0;
|
||||
|
||||
Bucket next;
|
||||
|
||||
void set(int index) {
|
||||
if (index >= BITS_PER_WORD) {
|
||||
ensureNext();
|
||||
next.set(index - BITS_PER_WORD);
|
||||
} else {
|
||||
mData |= 1L << index;
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureNext() {
|
||||
if (next == null) {
|
||||
next = new Bucket();
|
||||
}
|
||||
}
|
||||
|
||||
void clear(int index) {
|
||||
if (index >= BITS_PER_WORD) {
|
||||
if (next != null) {
|
||||
next.clear(index - BITS_PER_WORD);
|
||||
}
|
||||
} else {
|
||||
mData &= ~(1L << index);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
boolean get(int index) {
|
||||
if (index >= BITS_PER_WORD) {
|
||||
ensureNext();
|
||||
return next.get(index - BITS_PER_WORD);
|
||||
} else {
|
||||
return (mData & (1L << index)) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
mData = 0;
|
||||
if (next != null) {
|
||||
next.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void insert(int index, boolean value) {
|
||||
if (index >= BITS_PER_WORD) {
|
||||
ensureNext();
|
||||
next.insert(index - BITS_PER_WORD, value);
|
||||
} else {
|
||||
final boolean lastBit = (mData & LAST_BIT) != 0;
|
||||
long mask = (1L << index) - 1;
|
||||
final long before = mData & mask;
|
||||
final long after = ((mData & ~mask)) << 1;
|
||||
mData = before | after;
|
||||
if (value) {
|
||||
set(index);
|
||||
} else {
|
||||
clear(index);
|
||||
}
|
||||
if (lastBit || next != null) {
|
||||
ensureNext();
|
||||
next.insert(0, lastBit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean remove(int index) {
|
||||
if (index >= BITS_PER_WORD) {
|
||||
ensureNext();
|
||||
return next.remove(index - BITS_PER_WORD);
|
||||
} else {
|
||||
long mask = (1L << index);
|
||||
final boolean value = (mData & mask) != 0;
|
||||
mData &= ~mask;
|
||||
mask = mask - 1;
|
||||
final long before = mData & mask;
|
||||
// cannot use >> because it adds one.
|
||||
final long after = Long.rotateRight(mData & ~mask, 1);
|
||||
mData = before | after;
|
||||
if (next != null) {
|
||||
if (next.get(0)) {
|
||||
set(BITS_PER_WORD - 1);
|
||||
}
|
||||
next.remove(0);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
int countOnesBefore(int index) {
|
||||
if (next == null) {
|
||||
if (index >= BITS_PER_WORD) {
|
||||
return Long.bitCount(mData);
|
||||
}
|
||||
return Long.bitCount(mData & ((1L << index) - 1));
|
||||
}
|
||||
if (index < BITS_PER_WORD) {
|
||||
return Long.bitCount(mData & ((1L << index) - 1));
|
||||
} else {
|
||||
return next.countOnesBefore(index - BITS_PER_WORD) + Long.bitCount(mData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return next == null ? Long.toBinaryString(mData)
|
||||
: next.toString() + "xx" + Long.toBinaryString(mData);
|
||||
}
|
||||
}
|
||||
|
||||
static interface Callback {
|
||||
|
||||
int getChildCount();
|
||||
|
||||
void addView(View child, int index);
|
||||
|
||||
int indexOfChild(View view);
|
||||
|
||||
void removeViewAt(int index);
|
||||
|
||||
View getChildAt(int offset);
|
||||
|
||||
void removeAllViews();
|
||||
|
||||
RecyclerView.ViewHolder getChildViewHolder(View view);
|
||||
|
||||
void attachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams);
|
||||
|
||||
void detachViewFromParent(int offset);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,631 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.android.support.widget;
|
||||
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.view.ViewPropertyAnimatorCompat;
|
||||
import android.support.v4.view.ViewPropertyAnimatorListener;
|
||||
import org.telegram.android.support.widget.RecyclerView.ViewHolder;
|
||||
import android.view.View;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This implementation of {@link RecyclerView.ItemAnimator} provides basic
|
||||
* animations on remove, add, and move events that happen to the items in
|
||||
* a RecyclerView. RecyclerView uses a DefaultItemAnimator by default.
|
||||
*
|
||||
* @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator)
|
||||
*/
|
||||
public class DefaultItemAnimator extends RecyclerView.ItemAnimator {
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<ViewHolder>();
|
||||
private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<ViewHolder>();
|
||||
private ArrayList<MoveInfo> mPendingMoves = new ArrayList<MoveInfo>();
|
||||
private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<ChangeInfo>();
|
||||
|
||||
private ArrayList<ArrayList<ViewHolder>> mAdditionsList =
|
||||
new ArrayList<ArrayList<ViewHolder>>();
|
||||
private ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<ArrayList<MoveInfo>>();
|
||||
private ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<ArrayList<ChangeInfo>>();
|
||||
|
||||
private ArrayList<ViewHolder> mAddAnimations = new ArrayList<ViewHolder>();
|
||||
private ArrayList<ViewHolder> mMoveAnimations = new ArrayList<ViewHolder>();
|
||||
private ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<ViewHolder>();
|
||||
private ArrayList<ViewHolder> mChangeAnimations = new ArrayList<ViewHolder>();
|
||||
|
||||
private static class MoveInfo {
|
||||
public ViewHolder holder;
|
||||
public int fromX, fromY, toX, toY;
|
||||
|
||||
private MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) {
|
||||
this.holder = holder;
|
||||
this.fromX = fromX;
|
||||
this.fromY = fromY;
|
||||
this.toX = toX;
|
||||
this.toY = toY;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ChangeInfo {
|
||||
public ViewHolder oldHolder, newHolder;
|
||||
public int fromX, fromY, toX, toY;
|
||||
private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) {
|
||||
this.oldHolder = oldHolder;
|
||||
this.newHolder = newHolder;
|
||||
}
|
||||
|
||||
private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder,
|
||||
int fromX, int fromY, int toX, int toY) {
|
||||
this(oldHolder, newHolder);
|
||||
this.fromX = fromX;
|
||||
this.fromY = fromY;
|
||||
this.toX = toX;
|
||||
this.toY = toY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChangeInfo{" +
|
||||
"oldHolder=" + oldHolder +
|
||||
", newHolder=" + newHolder +
|
||||
", fromX=" + fromX +
|
||||
", fromY=" + fromY +
|
||||
", toX=" + toX +
|
||||
", toY=" + toY +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runPendingAnimations() {
|
||||
boolean removalsPending = !mPendingRemovals.isEmpty();
|
||||
boolean movesPending = !mPendingMoves.isEmpty();
|
||||
boolean changesPending = !mPendingChanges.isEmpty();
|
||||
boolean additionsPending = !mPendingAdditions.isEmpty();
|
||||
if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
|
||||
// nothing to animate
|
||||
return;
|
||||
}
|
||||
// First, remove stuff
|
||||
for (ViewHolder holder : mPendingRemovals) {
|
||||
animateRemoveImpl(holder);
|
||||
}
|
||||
mPendingRemovals.clear();
|
||||
// Next, move stuff
|
||||
if (movesPending) {
|
||||
final ArrayList<MoveInfo> moves = new ArrayList<MoveInfo>();
|
||||
moves.addAll(mPendingMoves);
|
||||
mMovesList.add(moves);
|
||||
mPendingMoves.clear();
|
||||
Runnable mover = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (MoveInfo moveInfo : moves) {
|
||||
animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
|
||||
moveInfo.toX, moveInfo.toY);
|
||||
}
|
||||
moves.clear();
|
||||
mMovesList.remove(moves);
|
||||
}
|
||||
};
|
||||
if (removalsPending) {
|
||||
View view = moves.get(0).holder.itemView;
|
||||
ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
|
||||
} else {
|
||||
mover.run();
|
||||
}
|
||||
}
|
||||
// Next, change stuff, to run in parallel with move animations
|
||||
if (changesPending) {
|
||||
final ArrayList<ChangeInfo> changes = new ArrayList<ChangeInfo>();
|
||||
changes.addAll(mPendingChanges);
|
||||
mChangesList.add(changes);
|
||||
mPendingChanges.clear();
|
||||
Runnable changer = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (ChangeInfo change : changes) {
|
||||
animateChangeImpl(change);
|
||||
}
|
||||
changes.clear();
|
||||
mChangesList.remove(changes);
|
||||
}
|
||||
};
|
||||
if (removalsPending) {
|
||||
ViewHolder holder = changes.get(0).oldHolder;
|
||||
ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
|
||||
} else {
|
||||
changer.run();
|
||||
}
|
||||
}
|
||||
// Next, add stuff
|
||||
if (additionsPending) {
|
||||
final ArrayList<ViewHolder> additions = new ArrayList<ViewHolder>();
|
||||
additions.addAll(mPendingAdditions);
|
||||
mAdditionsList.add(additions);
|
||||
mPendingAdditions.clear();
|
||||
Runnable adder = new Runnable() {
|
||||
public void run() {
|
||||
for (ViewHolder holder : additions) {
|
||||
animateAddImpl(holder);
|
||||
}
|
||||
additions.clear();
|
||||
mAdditionsList.remove(additions);
|
||||
}
|
||||
};
|
||||
if (removalsPending || movesPending || changesPending) {
|
||||
long removeDuration = removalsPending ? getRemoveDuration() : 0;
|
||||
long moveDuration = movesPending ? getMoveDuration() : 0;
|
||||
long changeDuration = changesPending ? getChangeDuration() : 0;
|
||||
long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
|
||||
View view = additions.get(0).itemView;
|
||||
ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
|
||||
} else {
|
||||
adder.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateRemove(final ViewHolder holder) {
|
||||
endAnimation(holder);
|
||||
mPendingRemovals.add(holder);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void animateRemoveImpl(final ViewHolder holder) {
|
||||
final View view = holder.itemView;
|
||||
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
|
||||
mRemoveAnimations.add(holder);
|
||||
animation.setDuration(getRemoveDuration())
|
||||
.alpha(0).setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchRemoveStarting(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
animation.setListener(null);
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchRemoveFinished(holder);
|
||||
mRemoveAnimations.remove(holder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateAdd(final ViewHolder holder) {
|
||||
endAnimation(holder);
|
||||
ViewCompat.setAlpha(holder.itemView, 0);
|
||||
mPendingAdditions.add(holder);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void animateAddImpl(final ViewHolder holder) {
|
||||
final View view = holder.itemView;
|
||||
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
|
||||
mAddAnimations.add(holder);
|
||||
animation.alpha(1).setDuration(getAddDuration()).
|
||||
setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchAddStarting(holder);
|
||||
}
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
animation.setListener(null);
|
||||
dispatchAddFinished(holder);
|
||||
mAddAnimations.remove(holder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateMove(final ViewHolder holder, int fromX, int fromY,
|
||||
int toX, int toY) {
|
||||
final View view = holder.itemView;
|
||||
fromX += ViewCompat.getTranslationX(holder.itemView);
|
||||
fromY += ViewCompat.getTranslationY(holder.itemView);
|
||||
endAnimation(holder);
|
||||
int deltaX = toX - fromX;
|
||||
int deltaY = toY - fromY;
|
||||
if (deltaX == 0 && deltaY == 0) {
|
||||
dispatchMoveFinished(holder);
|
||||
return false;
|
||||
}
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.setTranslationX(view, -deltaX);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.setTranslationY(view, -deltaY);
|
||||
}
|
||||
mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) {
|
||||
final View view = holder.itemView;
|
||||
final int deltaX = toX - fromX;
|
||||
final int deltaY = toY - fromY;
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.animate(view).translationX(0);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.animate(view).translationY(0);
|
||||
}
|
||||
// TODO: make EndActions end listeners instead, since end actions aren't called when
|
||||
// vpas are canceled (and can't end them. why?)
|
||||
// need listener functionality in VPACompat for this. Ick.
|
||||
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
|
||||
mMoveAnimations.add(holder);
|
||||
animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchMoveStarting(holder);
|
||||
}
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {
|
||||
if (deltaX != 0) {
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
animation.setListener(null);
|
||||
dispatchMoveFinished(holder);
|
||||
mMoveAnimations.remove(holder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
|
||||
int fromX, int fromY, int toX, int toY) {
|
||||
final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView);
|
||||
final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView);
|
||||
final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
|
||||
endAnimation(oldHolder);
|
||||
int deltaX = (int) (toX - fromX - prevTranslationX);
|
||||
int deltaY = (int) (toY - fromY - prevTranslationY);
|
||||
// recover prev translation state after ending animation
|
||||
ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX);
|
||||
ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY);
|
||||
ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
|
||||
if (newHolder != null && newHolder.itemView != null) {
|
||||
// carry over translation values
|
||||
endAnimation(newHolder);
|
||||
ViewCompat.setTranslationX(newHolder.itemView, -deltaX);
|
||||
ViewCompat.setTranslationY(newHolder.itemView, -deltaY);
|
||||
ViewCompat.setAlpha(newHolder.itemView, 0);
|
||||
}
|
||||
mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void animateChangeImpl(final ChangeInfo changeInfo) {
|
||||
final ViewHolder holder = changeInfo.oldHolder;
|
||||
final View view = holder == null ? null : holder.itemView;
|
||||
final ViewHolder newHolder = changeInfo.newHolder;
|
||||
final View newView = newHolder != null ? newHolder.itemView : null;
|
||||
if (view != null) {
|
||||
final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
|
||||
getChangeDuration());
|
||||
mChangeAnimations.add(changeInfo.oldHolder);
|
||||
oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
|
||||
oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
|
||||
oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchChangeStarting(changeInfo.oldHolder, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
oldViewAnim.setListener(null);
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
dispatchChangeFinished(changeInfo.oldHolder, true);
|
||||
mChangeAnimations.remove(changeInfo.oldHolder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
if (newView != null) {
|
||||
final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView);
|
||||
mChangeAnimations.add(changeInfo.newHolder);
|
||||
newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).
|
||||
alpha(1).setListener(new VpaListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
dispatchChangeStarting(changeInfo.newHolder, false);
|
||||
}
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
newViewAnimation.setListener(null);
|
||||
ViewCompat.setAlpha(newView, 1);
|
||||
ViewCompat.setTranslationX(newView, 0);
|
||||
ViewCompat.setTranslationY(newView, 0);
|
||||
dispatchChangeFinished(changeInfo.newHolder, false);
|
||||
mChangeAnimations.remove(changeInfo.newHolder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private void endChangeAnimation(List<ChangeInfo> infoList, ViewHolder item) {
|
||||
for (int i = infoList.size() - 1; i >= 0; i--) {
|
||||
ChangeInfo changeInfo = infoList.get(i);
|
||||
if (endChangeAnimationIfNecessary(changeInfo, item)) {
|
||||
if (changeInfo.oldHolder == null && changeInfo.newHolder == null) {
|
||||
infoList.remove(changeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) {
|
||||
if (changeInfo.oldHolder != null) {
|
||||
endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder);
|
||||
}
|
||||
if (changeInfo.newHolder != null) {
|
||||
endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder);
|
||||
}
|
||||
}
|
||||
private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, ViewHolder item) {
|
||||
boolean oldItem = false;
|
||||
if (changeInfo.newHolder == item) {
|
||||
changeInfo.newHolder = null;
|
||||
} else if (changeInfo.oldHolder == item) {
|
||||
changeInfo.oldHolder = null;
|
||||
oldItem = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
ViewCompat.setAlpha(item.itemView, 1);
|
||||
ViewCompat.setTranslationX(item.itemView, 0);
|
||||
ViewCompat.setTranslationY(item.itemView, 0);
|
||||
dispatchChangeFinished(item, oldItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAnimation(ViewHolder item) {
|
||||
final View view = item.itemView;
|
||||
// this will trigger end callback which should set properties to their target values.
|
||||
ViewCompat.animate(view).cancel();
|
||||
// TODO if some other animations are chained to end, how do we cancel them as well?
|
||||
for (int i = mPendingMoves.size() - 1; i >= 0; i--) {
|
||||
MoveInfo moveInfo = mPendingMoves.get(i);
|
||||
if (moveInfo.holder == item) {
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
dispatchMoveFinished(item);
|
||||
mPendingMoves.remove(i);
|
||||
}
|
||||
}
|
||||
endChangeAnimation(mPendingChanges, item);
|
||||
if (mPendingRemovals.remove(item)) {
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchRemoveFinished(item);
|
||||
}
|
||||
if (mPendingAdditions.remove(item)) {
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchAddFinished(item);
|
||||
}
|
||||
|
||||
for (int i = mChangesList.size() - 1; i >= 0; i--) {
|
||||
ArrayList<ChangeInfo> changes = mChangesList.get(i);
|
||||
endChangeAnimation(changes, item);
|
||||
if (changes.isEmpty()) {
|
||||
mChangesList.remove(i);
|
||||
}
|
||||
}
|
||||
for (int i = mMovesList.size() - 1; i >= 0; i--) {
|
||||
ArrayList<MoveInfo> moves = mMovesList.get(i);
|
||||
for (int j = moves.size() - 1; j >= 0; j--) {
|
||||
MoveInfo moveInfo = moves.get(j);
|
||||
if (moveInfo.holder == item) {
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
dispatchMoveFinished(item);
|
||||
moves.remove(j);
|
||||
if (moves.isEmpty()) {
|
||||
mMovesList.remove(i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
|
||||
ArrayList<ViewHolder> additions = mAdditionsList.get(i);
|
||||
if (additions.remove(item)) {
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchAddFinished(item);
|
||||
if (additions.isEmpty()) {
|
||||
mAdditionsList.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// animations should be ended by the cancel above.
|
||||
if (mRemoveAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mRemoveAnimations list");
|
||||
}
|
||||
|
||||
if (mAddAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mAddAnimations list");
|
||||
}
|
||||
|
||||
if (mChangeAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mChangeAnimations list");
|
||||
}
|
||||
|
||||
if (mMoveAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mMoveAnimations list");
|
||||
}
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return (!mPendingAdditions.isEmpty() ||
|
||||
!mPendingChanges.isEmpty() ||
|
||||
!mPendingMoves.isEmpty() ||
|
||||
!mPendingRemovals.isEmpty() ||
|
||||
!mMoveAnimations.isEmpty() ||
|
||||
!mRemoveAnimations.isEmpty() ||
|
||||
!mAddAnimations.isEmpty() ||
|
||||
!mChangeAnimations.isEmpty() ||
|
||||
!mMovesList.isEmpty() ||
|
||||
!mAdditionsList.isEmpty() ||
|
||||
!mChangesList.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the state of currently pending and running animations. If there are none
|
||||
* pending/running, call {@link #dispatchAnimationsFinished()} to notify any
|
||||
* listeners.
|
||||
*/
|
||||
private void dispatchFinishedWhenDone() {
|
||||
if (!isRunning()) {
|
||||
dispatchAnimationsFinished();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAnimations() {
|
||||
int count = mPendingMoves.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
MoveInfo item = mPendingMoves.get(i);
|
||||
View view = item.holder.itemView;
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
dispatchMoveFinished(item.holder);
|
||||
mPendingMoves.remove(i);
|
||||
}
|
||||
count = mPendingRemovals.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
ViewHolder item = mPendingRemovals.get(i);
|
||||
dispatchRemoveFinished(item);
|
||||
mPendingRemovals.remove(i);
|
||||
}
|
||||
count = mPendingAdditions.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
ViewHolder item = mPendingAdditions.get(i);
|
||||
View view = item.itemView;
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchAddFinished(item);
|
||||
mPendingAdditions.remove(i);
|
||||
}
|
||||
count = mPendingChanges.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
endChangeAnimationIfNecessary(mPendingChanges.get(i));
|
||||
}
|
||||
mPendingChanges.clear();
|
||||
if (!isRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int listCount = mMovesList.size();
|
||||
for (int i = listCount - 1; i >= 0; i--) {
|
||||
ArrayList<MoveInfo> moves = mMovesList.get(i);
|
||||
count = moves.size();
|
||||
for (int j = count - 1; j >= 0; j--) {
|
||||
MoveInfo moveInfo = moves.get(j);
|
||||
ViewHolder item = moveInfo.holder;
|
||||
View view = item.itemView;
|
||||
ViewCompat.setTranslationY(view, 0);
|
||||
ViewCompat.setTranslationX(view, 0);
|
||||
dispatchMoveFinished(moveInfo.holder);
|
||||
moves.remove(j);
|
||||
if (moves.isEmpty()) {
|
||||
mMovesList.remove(moves);
|
||||
}
|
||||
}
|
||||
}
|
||||
listCount = mAdditionsList.size();
|
||||
for (int i = listCount - 1; i >= 0; i--) {
|
||||
ArrayList<ViewHolder> additions = mAdditionsList.get(i);
|
||||
count = additions.size();
|
||||
for (int j = count - 1; j >= 0; j--) {
|
||||
ViewHolder item = additions.get(j);
|
||||
View view = item.itemView;
|
||||
ViewCompat.setAlpha(view, 1);
|
||||
dispatchAddFinished(item);
|
||||
additions.remove(j);
|
||||
if (additions.isEmpty()) {
|
||||
mAdditionsList.remove(additions);
|
||||
}
|
||||
}
|
||||
}
|
||||
listCount = mChangesList.size();
|
||||
for (int i = listCount - 1; i >= 0; i--) {
|
||||
ArrayList<ChangeInfo> changes = mChangesList.get(i);
|
||||
count = changes.size();
|
||||
for (int j = count - 1; j >= 0; j--) {
|
||||
endChangeAnimationIfNecessary(changes.get(j));
|
||||
if (changes.isEmpty()) {
|
||||
mChangesList.remove(changes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cancelAll(mRemoveAnimations);
|
||||
cancelAll(mMoveAnimations);
|
||||
cancelAll(mAddAnimations);
|
||||
cancelAll(mChangeAnimations);
|
||||
|
||||
dispatchAnimationsFinished();
|
||||
}
|
||||
|
||||
void cancelAll(List<ViewHolder> viewHolders) {
|
||||
for (int i = viewHolders.size() - 1; i >= 0; i--) {
|
||||
ViewCompat.animate(viewHolders.get(i).itemView).cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private static class VpaListenerAdapter implements ViewPropertyAnimatorListener {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,887 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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 languag`e governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.telegram.android.support.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A {@link RecyclerView.LayoutManager} implementations that lays out items in a grid.
|
||||
* <p>
|
||||
* By default, each item occupies 1 span. You can change it by providing a custom
|
||||
* {@link SpanSizeLookup} instance via {@link #setSpanSizeLookup(SpanSizeLookup)}.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
boolean mPendingSpanCountChange = false;
|
||||
int mSpanCount = DEFAULT_SPAN_COUNT;
|
||||
/**
|
||||
* Right borders for each span.
|
||||
* <p>For <b>i-th</b> item start is {@link #mCachedBorders}[i-1] + 1
|
||||
* and end is {@link #mCachedBorders}[i].
|
||||
*/
|
||||
int [] mCachedBorders;
|
||||
/**
|
||||
* Temporary array to keep views in layoutChunk method
|
||||
*/
|
||||
View[] mSet;
|
||||
final SparseIntArray mPreLayoutSpanSizeCache = new SparseIntArray();
|
||||
final SparseIntArray mPreLayoutSpanIndexCache = new SparseIntArray();
|
||||
SpanSizeLookup mSpanSizeLookup = new DefaultSpanSizeLookup();
|
||||
// re-used variable to acquire decor insets from RecyclerView
|
||||
final Rect mDecorInsets = new Rect();
|
||||
|
||||
/**
|
||||
* Creates a vertical GridLayoutManager
|
||||
*
|
||||
* @param context Current context, will be used to access resources.
|
||||
* @param spanCount The number of columns in the grid
|
||||
*/
|
||||
public GridLayoutManager(Context context, int spanCount) {
|
||||
super(context);
|
||||
setSpanCount(spanCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context Current context, will be used to access resources.
|
||||
* @param spanCount The number of columns or rows in the grid
|
||||
* @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link
|
||||
* #VERTICAL}.
|
||||
* @param reverseLayout When set to true, layouts from end to start.
|
||||
*/
|
||||
public GridLayoutManager(Context context, int spanCount, int orientation,
|
||||
boolean reverseLayout) {
|
||||
super(context, orientation, reverseLayout);
|
||||
setSpanCount(spanCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* stackFromEnd is not supported by GridLayoutManager. Consider using
|
||||
* {@link #setReverseLayout(boolean)}.
|
||||
*/
|
||||
@Override
|
||||
public void setStackFromEnd(boolean stackFromEnd) {
|
||||
if (stackFromEnd) {
|
||||
throw new UnsupportedOperationException(
|
||||
"GridLayoutManager does not support stack from end."
|
||||
+ " Consider using reverse layout");
|
||||
}
|
||||
super.setStackFromEnd(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
|
||||
RecyclerView.State state) {
|
||||
if (mOrientation == HORIZONTAL) {
|
||||
return mSpanCount;
|
||||
}
|
||||
if (state.getItemCount() < 1) {
|
||||
return 0;
|
||||
}
|
||||
return getSpanGroupIndex(recycler, state, state.getItemCount() - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
|
||||
RecyclerView.State state) {
|
||||
if (mOrientation == VERTICAL) {
|
||||
return mSpanCount;
|
||||
}
|
||||
if (state.getItemCount() < 1) {
|
||||
return 0;
|
||||
}
|
||||
return getSpanGroupIndex(recycler, state, state.getItemCount() - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
|
||||
RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
|
||||
ViewGroup.LayoutParams lp = host.getLayoutParams();
|
||||
if (!(lp instanceof LayoutParams)) {
|
||||
super.onInitializeAccessibilityNodeInfoForItem(host, info);
|
||||
return;
|
||||
}
|
||||
LayoutParams glp = (LayoutParams) lp;
|
||||
int spanGroupIndex = getSpanGroupIndex(recycler, state, glp.getViewLayoutPosition());
|
||||
if (mOrientation == HORIZONTAL) {
|
||||
info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
|
||||
glp.getSpanIndex(), glp.getSpanSize(),
|
||||
spanGroupIndex, 1,
|
||||
mSpanCount > 1 && glp.getSpanSize() == mSpanCount, false));
|
||||
} else { // VERTICAL
|
||||
info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
|
||||
spanGroupIndex , 1,
|
||||
glp.getSpanIndex(), glp.getSpanSize(),
|
||||
mSpanCount > 1 && glp.getSpanSize() == mSpanCount, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
|
||||
if (state.isPreLayout()) {
|
||||
cachePreLayoutSpanMapping();
|
||||
}
|
||||
super.onLayoutChildren(recycler, state);
|
||||
if (DEBUG) {
|
||||
validateChildOrder();
|
||||
}
|
||||
clearPreLayoutSpanMappingCache();
|
||||
if (!state.isPreLayout()) {
|
||||
mPendingSpanCountChange = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void clearPreLayoutSpanMappingCache() {
|
||||
mPreLayoutSpanSizeCache.clear();
|
||||
mPreLayoutSpanIndexCache.clear();
|
||||
}
|
||||
|
||||
private void cachePreLayoutSpanMapping() {
|
||||
final int childCount = getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
|
||||
final int viewPosition = lp.getViewLayoutPosition();
|
||||
mPreLayoutSpanSizeCache.put(viewPosition, lp.getSpanSize());
|
||||
mPreLayoutSpanIndexCache.put(viewPosition, lp.getSpanIndex());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
|
||||
mSpanSizeLookup.invalidateSpanIndexCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemsChanged(RecyclerView recyclerView) {
|
||||
mSpanSizeLookup.invalidateSpanIndexCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
|
||||
mSpanSizeLookup.invalidateSpanIndexCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
|
||||
mSpanSizeLookup.invalidateSpanIndexCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
|
||||
mSpanSizeLookup.invalidateSpanIndexCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
|
||||
return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
|
||||
return new LayoutParams(c, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
|
||||
if (lp instanceof ViewGroup.MarginLayoutParams) {
|
||||
return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
|
||||
} else {
|
||||
return new LayoutParams(lp);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
|
||||
return lp instanceof LayoutParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the source to get the number of spans occupied by each item in the adapter.
|
||||
*
|
||||
* @param spanSizeLookup {@link SpanSizeLookup} instance to be used to query number of spans
|
||||
* occupied by each item
|
||||
*/
|
||||
public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup) {
|
||||
mSpanSizeLookup = spanSizeLookup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current {@link SpanSizeLookup} used by the GridLayoutManager.
|
||||
*
|
||||
* @return The current {@link SpanSizeLookup} used by the GridLayoutManager.
|
||||
*/
|
||||
public SpanSizeLookup getSpanSizeLookup() {
|
||||
return mSpanSizeLookup;
|
||||
}
|
||||
|
||||
private void updateMeasurements() {
|
||||
int totalSpace;
|
||||
if (getOrientation() == VERTICAL) {
|
||||
totalSpace = getWidth() - getPaddingRight() - getPaddingLeft();
|
||||
} else {
|
||||
totalSpace = getHeight() - getPaddingBottom() - getPaddingTop();
|
||||
}
|
||||
calculateItemBorders(totalSpace);
|
||||
}
|
||||
|
||||
private void calculateItemBorders(int totalSpace) {
|
||||
if (mCachedBorders == null || mCachedBorders.length != mSpanCount + 1
|
||||
|| mCachedBorders[mCachedBorders.length - 1] != totalSpace) {
|
||||
mCachedBorders = new int[mSpanCount + 1];
|
||||
}
|
||||
mCachedBorders[0] = 0;
|
||||
int sizePerSpan = totalSpace / mSpanCount;
|
||||
int sizePerSpanRemainder = totalSpace % mSpanCount;
|
||||
int consumedPixels = 0;
|
||||
int additionalSize = 0;
|
||||
for (int i = 1; i <= mSpanCount; i++) {
|
||||
int itemSize = sizePerSpan;
|
||||
additionalSize += sizePerSpanRemainder;
|
||||
if (additionalSize > 0 && (mSpanCount - additionalSize) < sizePerSpanRemainder) {
|
||||
itemSize += 1;
|
||||
additionalSize -= mSpanCount;
|
||||
}
|
||||
consumedPixels += itemSize;
|
||||
mCachedBorders[i] = consumedPixels;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void onAnchorReady(RecyclerView.State state, AnchorInfo anchorInfo) {
|
||||
super.onAnchorReady(state, anchorInfo);
|
||||
updateMeasurements();
|
||||
if (state.getItemCount() > 0 && !state.isPreLayout()) {
|
||||
ensureAnchorIsInFirstSpan(anchorInfo);
|
||||
}
|
||||
if (mSet == null || mSet.length != mSpanCount) {
|
||||
mSet = new View[mSpanCount];
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureAnchorIsInFirstSpan(AnchorInfo anchorInfo) {
|
||||
int span = mSpanSizeLookup.getCachedSpanIndex(anchorInfo.mPosition, mSpanCount);
|
||||
while (span > 0 && anchorInfo.mPosition > 0) {
|
||||
anchorInfo.mPosition--;
|
||||
span = mSpanSizeLookup.getCachedSpanIndex(anchorInfo.mPosition, mSpanCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
View findReferenceChild(int start, int end, int itemCount) {
|
||||
ensureLayoutState();
|
||||
View invalidMatch = null;
|
||||
View outOfBoundsMatch = null;
|
||||
final int boundsStart = mOrientationHelper.getStartAfterPadding();
|
||||
final int boundsEnd = mOrientationHelper.getEndAfterPadding();
|
||||
final int diff = end > start ? 1 : -1;
|
||||
for (int i = start; i != end; i += diff) {
|
||||
final View view = getChildAt(i);
|
||||
final int position = getPosition(view);
|
||||
if (position >= 0 && position < itemCount) {
|
||||
final int span = mSpanSizeLookup.getCachedSpanIndex(position, mSpanCount);
|
||||
if (span != 0) {
|
||||
continue;
|
||||
}
|
||||
if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) {
|
||||
if (invalidMatch == null) {
|
||||
invalidMatch = view; // removed item, least preferred
|
||||
}
|
||||
} else if (mOrientationHelper.getDecoratedStart(view) >= boundsEnd ||
|
||||
mOrientationHelper.getDecoratedEnd(view) < boundsStart) {
|
||||
if (outOfBoundsMatch == null) {
|
||||
outOfBoundsMatch = view; // item is not visible, less preferred
|
||||
}
|
||||
} else {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
||||
return outOfBoundsMatch != null ? outOfBoundsMatch : invalidMatch;
|
||||
}
|
||||
|
||||
private int getSpanGroupIndex(RecyclerView.Recycler recycler, RecyclerView.State state,
|
||||
int viewPosition) {
|
||||
if (!state.isPreLayout()) {
|
||||
return mSpanSizeLookup.getSpanGroupIndex(viewPosition, mSpanCount);
|
||||
}
|
||||
final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(viewPosition);
|
||||
if (adapterPosition == -1) {
|
||||
if (DEBUG) {
|
||||
throw new RuntimeException("Cannot find span group index for position "
|
||||
+ viewPosition);
|
||||
}
|
||||
Log.w(TAG, "Cannot find span size for pre layout position. " + viewPosition);
|
||||
return 0;
|
||||
}
|
||||
return mSpanSizeLookup.getSpanGroupIndex(adapterPosition, mSpanCount);
|
||||
}
|
||||
|
||||
private int getSpanIndex(RecyclerView.Recycler recycler, RecyclerView.State state, int pos) {
|
||||
if (!state.isPreLayout()) {
|
||||
return mSpanSizeLookup.getCachedSpanIndex(pos, mSpanCount);
|
||||
}
|
||||
final int cached = mPreLayoutSpanIndexCache.get(pos, -1);
|
||||
if (cached != -1) {
|
||||
return cached;
|
||||
}
|
||||
final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(pos);
|
||||
if (adapterPosition == -1) {
|
||||
if (DEBUG) {
|
||||
throw new RuntimeException("Cannot find span index for pre layout position. It is"
|
||||
+ " not cached, not in the adapter. Pos:" + pos);
|
||||
}
|
||||
Log.w(TAG, "Cannot find span size for pre layout position. It is"
|
||||
+ " not cached, not in the adapter. Pos:" + pos);
|
||||
return 0;
|
||||
}
|
||||
return mSpanSizeLookup.getCachedSpanIndex(adapterPosition, mSpanCount);
|
||||
}
|
||||
|
||||
private int getSpanSize(RecyclerView.Recycler recycler, RecyclerView.State state, int pos) {
|
||||
if (!state.isPreLayout()) {
|
||||
return mSpanSizeLookup.getSpanSize(pos);
|
||||
}
|
||||
final int cached = mPreLayoutSpanSizeCache.get(pos, -1);
|
||||
if (cached != -1) {
|
||||
return cached;
|
||||
}
|
||||
final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(pos);
|
||||
if (adapterPosition == -1) {
|
||||
if (DEBUG) {
|
||||
throw new RuntimeException("Cannot find span size for pre layout position. It is"
|
||||
+ " not cached, not in the adapter. Pos:" + pos);
|
||||
}
|
||||
Log.w(TAG, "Cannot find span size for pre layout position. It is"
|
||||
+ " not cached, not in the adapter. Pos:" + pos);
|
||||
return 1;
|
||||
}
|
||||
return mSpanSizeLookup.getSpanSize(adapterPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
|
||||
LayoutState layoutState, LayoutChunkResult result) {
|
||||
final boolean layingOutInPrimaryDirection =
|
||||
layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_TAIL;
|
||||
int count = 0;
|
||||
int consumedSpanCount = 0;
|
||||
int remainingSpan = mSpanCount;
|
||||
if (!layingOutInPrimaryDirection) {
|
||||
int itemSpanIndex = getSpanIndex(recycler, state, layoutState.mCurrentPosition);
|
||||
int itemSpanSize = getSpanSize(recycler, state, layoutState.mCurrentPosition);
|
||||
remainingSpan = itemSpanIndex + itemSpanSize;
|
||||
}
|
||||
while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) {
|
||||
int pos = layoutState.mCurrentPosition;
|
||||
final int spanSize = getSpanSize(recycler, state, pos);
|
||||
if (spanSize > mSpanCount) {
|
||||
throw new IllegalArgumentException("Item at position " + pos + " requires " +
|
||||
spanSize + " spans but GridLayoutManager has only " + mSpanCount
|
||||
+ " spans.");
|
||||
}
|
||||
remainingSpan -= spanSize;
|
||||
if (remainingSpan < 0) {
|
||||
break; // item did not fit into this row or column
|
||||
}
|
||||
View view = layoutState.next(recycler);
|
||||
if (view == null) {
|
||||
break;
|
||||
}
|
||||
consumedSpanCount += spanSize;
|
||||
mSet[count] = view;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
result.mFinished = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int maxSize = 0;
|
||||
|
||||
// we should assign spans before item decor offsets are calculated
|
||||
assignSpans(recycler, state, count, consumedSpanCount, layingOutInPrimaryDirection);
|
||||
for (int i = 0; i < count; i++) {
|
||||
View view = mSet[i];
|
||||
if (layoutState.mScrapList == null) {
|
||||
if (layingOutInPrimaryDirection) {
|
||||
addView(view);
|
||||
} else {
|
||||
addView(view, 0);
|
||||
}
|
||||
} else {
|
||||
if (layingOutInPrimaryDirection) {
|
||||
addDisappearingView(view);
|
||||
} else {
|
||||
addDisappearingView(view, 0);
|
||||
}
|
||||
}
|
||||
|
||||
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
|
||||
final int spec = View.MeasureSpec.makeMeasureSpec(
|
||||
mCachedBorders[lp.mSpanIndex + lp.mSpanSize] -
|
||||
mCachedBorders[lp.mSpanIndex],
|
||||
View.MeasureSpec.EXACTLY);
|
||||
if (mOrientation == VERTICAL) {
|
||||
measureChildWithDecorationsAndMargin(view, spec, getMainDirSpec(lp.height));
|
||||
} else {
|
||||
measureChildWithDecorationsAndMargin(view, getMainDirSpec(lp.width), spec);
|
||||
}
|
||||
final int size = mOrientationHelper.getDecoratedMeasurement(view);
|
||||
if (size > maxSize) {
|
||||
maxSize = size;
|
||||
}
|
||||
}
|
||||
|
||||
// views that did not measure the maxSize has to be re-measured
|
||||
final int maxMeasureSpec = getMainDirSpec(maxSize);
|
||||
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);
|
||||
if (mOrientation == VERTICAL) {
|
||||
measureChildWithDecorationsAndMargin(view, spec, maxMeasureSpec);
|
||||
} else {
|
||||
measureChildWithDecorationsAndMargin(view, maxMeasureSpec, spec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.mConsumed = maxSize;
|
||||
|
||||
int left = 0, right = 0, top = 0, bottom = 0;
|
||||
if (mOrientation == VERTICAL) {
|
||||
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
|
||||
bottom = layoutState.mOffset;
|
||||
top = bottom - maxSize;
|
||||
} else {
|
||||
top = layoutState.mOffset;
|
||||
bottom = top + maxSize;
|
||||
}
|
||||
} else {
|
||||
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
|
||||
right = layoutState.mOffset;
|
||||
left = right - maxSize;
|
||||
} else {
|
||||
left = layoutState.mOffset;
|
||||
right = left + maxSize;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
View view = mSet[i];
|
||||
LayoutParams params = (LayoutParams) view.getLayoutParams();
|
||||
if (mOrientation == VERTICAL) {
|
||||
left = getPaddingLeft() + mCachedBorders[params.mSpanIndex];
|
||||
right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
|
||||
} else {
|
||||
top = getPaddingTop() + mCachedBorders[params.mSpanIndex];
|
||||
bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
|
||||
}
|
||||
// We calculate everything with View's bounding box (which includes decor and margins)
|
||||
// To calculate correct layout position, we subtract margins.
|
||||
layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
|
||||
right - params.rightMargin, bottom - params.bottomMargin);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
|
||||
+ (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
|
||||
+ (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin)
|
||||
+ ", span:" + params.mSpanIndex + ", spanSize:" + params.mSpanSize);
|
||||
}
|
||||
// Consume the available space if the view is not removed OR changed
|
||||
if (params.isItemRemoved() || params.isItemChanged()) {
|
||||
result.mIgnoreConsumed = true;
|
||||
}
|
||||
result.mFocusable |= view.isFocusable();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec) {
|
||||
calculateItemDecorationsForChild(child, mDecorInsets);
|
||||
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
|
||||
widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + mDecorInsets.left,
|
||||
lp.rightMargin + mDecorInsets.right);
|
||||
heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mDecorInsets.top,
|
||||
lp.bottomMargin + mDecorInsets.bottom);
|
||||
child.measure(widthSpec, heightSpec);
|
||||
}
|
||||
|
||||
private int updateSpecWithExtra(int spec, int startInset, int endInset) {
|
||||
if (startInset == 0 && endInset == 0) {
|
||||
return spec;
|
||||
}
|
||||
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);
|
||||
}
|
||||
return spec;
|
||||
}
|
||||
|
||||
private void assignSpans(RecyclerView.Recycler recycler, RecyclerView.State state, int count,
|
||||
int consumedSpanCount, boolean layingOutInPrimaryDirection) {
|
||||
int span, spanDiff, start, end, diff;
|
||||
// make sure we traverse from min position to max position
|
||||
if (layingOutInPrimaryDirection) {
|
||||
start = 0;
|
||||
end = count;
|
||||
diff = 1;
|
||||
} else {
|
||||
start = count - 1;
|
||||
end = -1;
|
||||
diff = -1;
|
||||
}
|
||||
if (mOrientation == VERTICAL && isLayoutRTL()) { // start from last span
|
||||
span = mSpanCount - 1;
|
||||
spanDiff = -1;
|
||||
} else {
|
||||
span = 0;
|
||||
spanDiff = 1;
|
||||
}
|
||||
for (int i = start; i != end; i += diff) {
|
||||
View view = mSet[i];
|
||||
LayoutParams params = (LayoutParams) view.getLayoutParams();
|
||||
params.mSpanSize = getSpanSize(recycler, state, getPosition(view));
|
||||
if (spanDiff == -1 && params.mSpanSize > 1) {
|
||||
params.mSpanIndex = span - (params.mSpanSize - 1);
|
||||
} else {
|
||||
params.mSpanIndex = span;
|
||||
}
|
||||
span += spanDiff * params.mSpanSize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of spans laid out by this grid.
|
||||
*
|
||||
* @return The number of spans
|
||||
* @see #setSpanCount(int)
|
||||
*/
|
||||
public int getSpanCount() {
|
||||
return mSpanCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of spans to be laid out.
|
||||
* <p>
|
||||
* If {@link #getOrientation()} is {@link #VERTICAL}, this is the number of columns.
|
||||
* If {@link #getOrientation()} is {@link #HORIZONTAL}, this is the number of rows.
|
||||
*
|
||||
* @param spanCount The total number of spans in the grid
|
||||
* @see #getSpanCount()
|
||||
*/
|
||||
public void setSpanCount(int spanCount) {
|
||||
if (spanCount == mSpanCount) {
|
||||
return;
|
||||
}
|
||||
mPendingSpanCountChange = true;
|
||||
if (spanCount < 1) {
|
||||
throw new IllegalArgumentException("Span count should be at least 1. Provided "
|
||||
+ spanCount);
|
||||
}
|
||||
mSpanCount = spanCount;
|
||||
mSpanSizeLookup.invalidateSpanIndexCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper class to provide the number of spans each item occupies.
|
||||
* <p>
|
||||
* Default implementation sets each item to occupy exactly 1 span.
|
||||
*
|
||||
* @see GridLayoutManager#setSpanSizeLookup(SpanSizeLookup)
|
||||
*/
|
||||
public static abstract class SpanSizeLookup {
|
||||
|
||||
final SparseIntArray mSpanIndexCache = new SparseIntArray();
|
||||
|
||||
private boolean mCacheSpanIndices = false;
|
||||
|
||||
/**
|
||||
* Returns the number of span occupied by the item at <code>position</code>.
|
||||
*
|
||||
* @param position The adapter position of the item
|
||||
* @return The number of spans occupied by the item at the provided position
|
||||
*/
|
||||
abstract public int getSpanSize(int position);
|
||||
|
||||
/**
|
||||
* Sets whether the results of {@link #getSpanIndex(int, int)} method should be cached or
|
||||
* not. By default these values are not cached. If you are not overriding
|
||||
* {@link #getSpanIndex(int, int)}, you should set this to true for better performance.
|
||||
*
|
||||
* @param cacheSpanIndices Whether results of getSpanIndex should be cached or not.
|
||||
*/
|
||||
public void setSpanIndexCacheEnabled(boolean cacheSpanIndices) {
|
||||
mCacheSpanIndices = cacheSpanIndices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the span index cache. GridLayoutManager automatically calls this method when
|
||||
* adapter changes occur.
|
||||
*/
|
||||
public void invalidateSpanIndexCache() {
|
||||
mSpanIndexCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether results of {@link #getSpanIndex(int, int)} method are cached or not.
|
||||
*
|
||||
* @return True if results of {@link #getSpanIndex(int, int)} are cached.
|
||||
*/
|
||||
public boolean isSpanIndexCacheEnabled() {
|
||||
return mCacheSpanIndices;
|
||||
}
|
||||
|
||||
int getCachedSpanIndex(int position, int spanCount) {
|
||||
if (!mCacheSpanIndices) {
|
||||
return getSpanIndex(position, spanCount);
|
||||
}
|
||||
final int existing = mSpanIndexCache.get(position, -1);
|
||||
if (existing != -1) {
|
||||
return existing;
|
||||
}
|
||||
final int value = getSpanIndex(position, spanCount);
|
||||
mSpanIndexCache.put(position, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the final span index of the provided position.
|
||||
* <p>
|
||||
* If you have a faster way to calculate span index for your items, you should override
|
||||
* this method. Otherwise, you should enable span index cache
|
||||
* ({@link #setSpanIndexCacheEnabled(boolean)}) for better performance. When caching is
|
||||
* disabled, default implementation traverses all items from 0 to
|
||||
* <code>position</code>. When caching is enabled, it calculates from the closest cached
|
||||
* value before the <code>position</code>.
|
||||
* <p>
|
||||
* If you override this method, you need to make sure it is consistent with
|
||||
* {@link #getSpanSize(int)}. GridLayoutManager does not call this method for
|
||||
* each item. It is called only for the reference item and rest of the items
|
||||
* are assigned to spans based on the reference item. For example, you cannot assign a
|
||||
* position to span 2 while span 1 is empty.
|
||||
* <p>
|
||||
* Note that span offsets always start with 0 and are not affected by RTL.
|
||||
*
|
||||
* @param position The position of the item
|
||||
* @param spanCount The total number of spans in the grid
|
||||
* @return The final span position of the item. Should be between 0 (inclusive) and
|
||||
* <code>spanCount</code>(exclusive)
|
||||
*/
|
||||
public int getSpanIndex(int position, int spanCount) {
|
||||
int positionSpanSize = getSpanSize(position);
|
||||
if (positionSpanSize == spanCount) {
|
||||
return 0; // quick return for full-span items
|
||||
}
|
||||
int span = 0;
|
||||
int startPos = 0;
|
||||
// If caching is enabled, try to jump
|
||||
if (mCacheSpanIndices && mSpanIndexCache.size() > 0) {
|
||||
int prevKey = findReferenceIndexFromCache(position);
|
||||
if (prevKey >= 0) {
|
||||
span = mSpanIndexCache.get(prevKey) + getSpanSize(prevKey);
|
||||
startPos = prevKey + 1;
|
||||
}
|
||||
}
|
||||
for (int i = startPos; i < position; i++) {
|
||||
int size = getSpanSize(i);
|
||||
span += size;
|
||||
if (span == spanCount) {
|
||||
span = 0;
|
||||
} else if (span > spanCount) {
|
||||
// did not fit, moving to next row / column
|
||||
span = size;
|
||||
}
|
||||
}
|
||||
if (span + positionSpanSize <= spanCount) {
|
||||
return span;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int findReferenceIndexFromCache(int position) {
|
||||
int lo = 0;
|
||||
int hi = mSpanIndexCache.size() - 1;
|
||||
|
||||
while (lo <= hi) {
|
||||
final int mid = (lo + hi) >>> 1;
|
||||
final int midVal = mSpanIndexCache.keyAt(mid);
|
||||
if (midVal < position) {
|
||||
lo = mid + 1;
|
||||
} else {
|
||||
hi = mid - 1;
|
||||
}
|
||||
}
|
||||
int index = lo - 1;
|
||||
if (index >= 0 && index < mSpanIndexCache.size()) {
|
||||
return mSpanIndexCache.keyAt(index);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the group this position belongs.
|
||||
* <p>
|
||||
* For example, if grid has 3 columns and each item occupies 1 span, span group index
|
||||
* for item 1 will be 0, item 5 will be 1.
|
||||
*
|
||||
* @param adapterPosition The position in adapter
|
||||
* @param spanCount The total number of spans in the grid
|
||||
* @return The index of the span group including the item at the given adapter position
|
||||
*/
|
||||
public int getSpanGroupIndex(int adapterPosition, int spanCount) {
|
||||
int span = 0;
|
||||
int group = 0;
|
||||
int positionSpanSize = getSpanSize(adapterPosition);
|
||||
for (int i = 0; i < adapterPosition; i++) {
|
||||
int size = getSpanSize(i);
|
||||
span += size;
|
||||
if (span == spanCount) {
|
||||
span = 0;
|
||||
group++;
|
||||
} else if (span > spanCount) {
|
||||
// did not fit, moving to next row / column
|
||||
span = size;
|
||||
group++;
|
||||
}
|
||||
}
|
||||
if (span + positionSpanSize > spanCount) {
|
||||
group++;
|
||||
}
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPredictiveItemAnimations() {
|
||||
return mPendingSavedState == null && !mPendingSpanCountChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation for {@link SpanSizeLookup}. Each item occupies 1 span.
|
||||
*/
|
||||
public static final class DefaultSpanSizeLookup extends SpanSizeLookup {
|
||||
|
||||
@Override
|
||||
public int getSpanSize(int position) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpanIndex(int position, int spanCount) {
|
||||
return position % spanCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LayoutParams used by GridLayoutManager.
|
||||
* <p>
|
||||
* Note that if the orientation is {@link #VERTICAL}, the width parameter is ignored and if the
|
||||
* orientation is {@link #HORIZONTAL} the height parameter is ignored because child view is
|
||||
* expected to fill all of the space given to it.
|
||||
*/
|
||||
public static class LayoutParams extends RecyclerView.LayoutParams {
|
||||
|
||||
/**
|
||||
* Span Id for Views that are not laid out yet.
|
||||
*/
|
||||
public static final int INVALID_SPAN_ID = -1;
|
||||
|
||||
private int mSpanIndex = INVALID_SPAN_ID;
|
||||
|
||||
private int mSpanSize = 0;
|
||||
|
||||
public LayoutParams(Context c, AttributeSet attrs) {
|
||||
super(c, attrs);
|
||||
}
|
||||
|
||||
public LayoutParams(int width, int height) {
|
||||
super(width, height);
|
||||
}
|
||||
|
||||
public LayoutParams(ViewGroup.MarginLayoutParams source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
public LayoutParams(ViewGroup.LayoutParams source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
public LayoutParams(RecyclerView.LayoutParams source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current span index of this View. If the View is not laid out yet, the return
|
||||
* value is <code>undefined</code>.
|
||||
* <p>
|
||||
* Note that span index may change by whether the RecyclerView is RTL or not. For
|
||||
* example, if the number of spans is 3 and layout is RTL, the rightmost item will have
|
||||
* span index of 2. If the layout changes back to LTR, span index for this view will be 0.
|
||||
* If the item was occupying 2 spans, span indices would be 1 and 0 respectively.
|
||||
* <p>
|
||||
* If the View occupies multiple spans, span with the minimum index is returned.
|
||||
*
|
||||
* @return The span index of the View.
|
||||
*/
|
||||
public int getSpanIndex() {
|
||||
return mSpanIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of spans occupied by this View. If the View not laid out yet, the
|
||||
* return value is <code>undefined</code>.
|
||||
*
|
||||
* @return The number of spans occupied by this View.
|
||||
*/
|
||||
public int getSpanSize() {
|
||||
return mSpanSize;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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 languag`e governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.telegram.android.support.widget;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Helper class that keeps temporary state while {LayoutManager} is filling out the empty
|
||||
* space.
|
||||
*/
|
||||
class LayoutState {
|
||||
|
||||
final static String TAG = "LayoutState";
|
||||
|
||||
final static int LAYOUT_START = -1;
|
||||
|
||||
final static int LAYOUT_END = 1;
|
||||
|
||||
final static int INVALID_LAYOUT = Integer.MIN_VALUE;
|
||||
|
||||
final static int ITEM_DIRECTION_HEAD = -1;
|
||||
|
||||
final static int ITEM_DIRECTION_TAIL = 1;
|
||||
|
||||
final static int SCOLLING_OFFSET_NaN = Integer.MIN_VALUE;
|
||||
|
||||
/**
|
||||
* Number of pixels that we should fill, in the layout direction.
|
||||
*/
|
||||
int mAvailable;
|
||||
|
||||
/**
|
||||
* Current position on the adapter to get the next item.
|
||||
*/
|
||||
int mCurrentPosition;
|
||||
|
||||
/**
|
||||
* Defines the direction in which the data adapter is traversed.
|
||||
* Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL}
|
||||
*/
|
||||
int mItemDirection;
|
||||
|
||||
/**
|
||||
* Defines the direction in which the layout is filled.
|
||||
* Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
|
||||
*/
|
||||
int mLayoutDirection;
|
||||
|
||||
/**
|
||||
* Used if you want to pre-layout items that are not yet visible.
|
||||
* The difference with {@link #mAvailable} is that, when recycling, distance rendered for
|
||||
* {@link #mExtra} is not considered not to recycle visible children.
|
||||
*/
|
||||
int mExtra = 0;
|
||||
|
||||
/**
|
||||
* @return true if there are more items in the data adapter
|
||||
*/
|
||||
boolean hasMore(RecyclerView.State state) {
|
||||
return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the view for the next element that we should render.
|
||||
* Also updates current item index to the next item, based on {@link #mItemDirection}
|
||||
*
|
||||
* @return The next element that we should render.
|
||||
*/
|
||||
View next(RecyclerView.Recycler recycler) {
|
||||
final View view = recycler.getViewForPosition(mCurrentPosition);
|
||||
mCurrentPosition += mItemDirection;
|
||||
return view;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,338 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.android.support.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PointF;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
/**
|
||||
* {@link RecyclerView.SmoothScroller} implementation which uses
|
||||
* {@link android.view.animation.LinearInterpolator} until the target position becames a child of
|
||||
* the RecyclerView and then uses
|
||||
* {@link android.view.animation.DecelerateInterpolator} to slowly approach to target position.
|
||||
*/
|
||||
abstract public class LinearSmoothScroller extends RecyclerView.SmoothScroller {
|
||||
|
||||
private static final String TAG = "LinearSmoothScroller";
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final float MILLISECONDS_PER_INCH = 25f;
|
||||
|
||||
private static final int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000;
|
||||
|
||||
/**
|
||||
* Align child view's left or top with parent view's left or top
|
||||
*
|
||||
* @see #calculateDtToFit(int, int, int, int, int)
|
||||
* @see #calculateDxToMakeVisible(android.view.View, int)
|
||||
* @see #calculateDyToMakeVisible(android.view.View, int)
|
||||
*/
|
||||
public static final int SNAP_TO_START = -1;
|
||||
|
||||
/**
|
||||
* Align child view's right or bottom with parent view's right or bottom
|
||||
*
|
||||
* @see #calculateDtToFit(int, int, int, int, int)
|
||||
* @see #calculateDxToMakeVisible(android.view.View, int)
|
||||
* @see #calculateDyToMakeVisible(android.view.View, int)
|
||||
*/
|
||||
public static final int SNAP_TO_END = 1;
|
||||
|
||||
/**
|
||||
* <p>Decides if the child should be snapped from start or end, depending on where it
|
||||
* currently is in relation to its parent.</p>
|
||||
* <p>For instance, if the view is virtually on the left of RecyclerView, using
|
||||
* {@code SNAP_TO_ANY} is the same as using {@code SNAP_TO_START}</p>
|
||||
*
|
||||
* @see #calculateDtToFit(int, int, int, int, int)
|
||||
* @see #calculateDxToMakeVisible(android.view.View, int)
|
||||
* @see #calculateDyToMakeVisible(android.view.View, int)
|
||||
*/
|
||||
public static final int SNAP_TO_ANY = 0;
|
||||
|
||||
// Trigger a scroll to a further distance than TARGET_SEEK_SCROLL_DISTANCE_PX so that if target
|
||||
// view is not laid out until interim target position is reached, we can detect the case before
|
||||
// scrolling slows down and reschedule another interim target scroll
|
||||
private static final float TARGET_SEEK_EXTRA_SCROLL_RATIO = 1.2f;
|
||||
|
||||
protected final LinearInterpolator mLinearInterpolator = new LinearInterpolator();
|
||||
|
||||
protected final DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
|
||||
|
||||
protected PointF mTargetVector;
|
||||
|
||||
private final float MILLISECONDS_PER_PX;
|
||||
|
||||
// Temporary variables to keep track of the interim scroll target. These values do not
|
||||
// point to a real item position, rather point to an estimated location pixels.
|
||||
protected int mInterimTargetDx = 0, mInterimTargetDy = 0;
|
||||
|
||||
public LinearSmoothScroller(Context context) {
|
||||
MILLISECONDS_PER_PX = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void onStart() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
|
||||
final int dx = calculateDxToMakeVisible(targetView, getHorizontalSnapPreference());
|
||||
final int dy = calculateDyToMakeVisible(targetView, getVerticalSnapPreference());
|
||||
final int distance = (int) Math.sqrt(dx * dx + dy * dy);
|
||||
final int time = calculateTimeForDeceleration(distance);
|
||||
if (time > 0) {
|
||||
action.update(-dx, -dy, time, mDecelerateInterpolator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void onSeekTargetStep(int dx, int dy, RecyclerView.State state, Action action) {
|
||||
if (getChildCount() == 0) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
if (DEBUG && mTargetVector != null
|
||||
&& ((mTargetVector.x * dx < 0 || mTargetVector.y * dy < 0))) {
|
||||
throw new IllegalStateException("Scroll happened in the opposite direction"
|
||||
+ " of the target. Some calculations are wrong");
|
||||
}
|
||||
mInterimTargetDx = clampApplyScroll(mInterimTargetDx, dx);
|
||||
mInterimTargetDy = clampApplyScroll(mInterimTargetDy, dy);
|
||||
|
||||
if (mInterimTargetDx == 0 && mInterimTargetDy == 0) {
|
||||
updateActionForInterimTarget(action);
|
||||
} // everything is valid, keep going
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void onStop() {
|
||||
mInterimTargetDx = mInterimTargetDy = 0;
|
||||
mTargetVector = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the scroll speed.
|
||||
*
|
||||
* @param displayMetrics DisplayMetrics to be used for real dimension calculations
|
||||
* @return The time (in ms) it should take for each pixel. For instance, if returned value is
|
||||
* 2 ms, it means scrolling 1000 pixels with LinearInterpolation should take 2 seconds.
|
||||
*/
|
||||
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
|
||||
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Calculates the time for deceleration so that transition from LinearInterpolator to
|
||||
* DecelerateInterpolator looks smooth.</p>
|
||||
*
|
||||
* @param dx Distance to scroll
|
||||
* @return Time for DecelerateInterpolator to smoothly traverse the distance when transitioning
|
||||
* from LinearInterpolation
|
||||
*/
|
||||
protected int calculateTimeForDeceleration(int dx) {
|
||||
// we want to cover same area with the linear interpolator for the first 10% of the
|
||||
// interpolation. After that, deceleration will take control.
|
||||
// area under curve (1-(1-x)^2) can be calculated as (1 - x/3) * x * x
|
||||
// which gives 0.100028 when x = .3356
|
||||
// this is why we divide linear scrolling time with .3356
|
||||
return (int) Math.ceil(calculateTimeForScrolling(dx) / .3356);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the time it should take to scroll the given distance (in pixels)
|
||||
*
|
||||
* @param dx Distance in pixels that we want to scroll
|
||||
* @return Time in milliseconds
|
||||
* @see #calculateSpeedPerPixel(android.util.DisplayMetrics)
|
||||
*/
|
||||
protected int calculateTimeForScrolling(int dx) {
|
||||
// In a case where dx is very small, rounding may return 0 although dx > 0.
|
||||
// To avoid that issue, ceil the result so that if dx > 0, we'll always return positive
|
||||
// time.
|
||||
return (int) Math.ceil(Math.abs(dx) * MILLISECONDS_PER_PX);
|
||||
}
|
||||
|
||||
/**
|
||||
* When scrolling towards a child view, this method defines whether we should align the left
|
||||
* or the right edge of the child with the parent RecyclerView.
|
||||
*
|
||||
* @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
|
||||
* @see #SNAP_TO_START
|
||||
* @see #SNAP_TO_END
|
||||
* @see #SNAP_TO_ANY
|
||||
*/
|
||||
protected int getHorizontalSnapPreference() {
|
||||
return mTargetVector == null || mTargetVector.x == 0 ? SNAP_TO_ANY :
|
||||
mTargetVector.x > 0 ? SNAP_TO_END : SNAP_TO_START;
|
||||
}
|
||||
|
||||
/**
|
||||
* When scrolling towards a child view, this method defines whether we should align the top
|
||||
* or the bottom edge of the child with the parent RecyclerView.
|
||||
*
|
||||
* @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
|
||||
* @see #SNAP_TO_START
|
||||
* @see #SNAP_TO_END
|
||||
* @see #SNAP_TO_ANY
|
||||
*/
|
||||
protected int getVerticalSnapPreference() {
|
||||
return mTargetVector == null || mTargetVector.y == 0 ? SNAP_TO_ANY :
|
||||
mTargetVector.y > 0 ? SNAP_TO_END : SNAP_TO_START;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the target scroll position is not a child of the RecyclerView, this method calculates
|
||||
* a direction vector towards that child and triggers a smooth scroll.
|
||||
*
|
||||
* @see #computeScrollVectorForPosition(int)
|
||||
*/
|
||||
protected void updateActionForInterimTarget(Action action) {
|
||||
// find an interim target position
|
||||
PointF scrollVector = computeScrollVectorForPosition(getTargetPosition());
|
||||
if (scrollVector == null || (scrollVector.x == 0 && scrollVector.y == 0)) {
|
||||
Log.e(TAG, "To support smooth scrolling, you should override \n"
|
||||
+ "LayoutManager#computeScrollVectorForPosition.\n"
|
||||
+ "Falling back to instant scroll");
|
||||
final int target = getTargetPosition();
|
||||
stop();
|
||||
instantScrollToPosition(target);
|
||||
return;
|
||||
}
|
||||
normalize(scrollVector);
|
||||
mTargetVector = scrollVector;
|
||||
|
||||
mInterimTargetDx = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.x);
|
||||
mInterimTargetDy = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.y);
|
||||
final int time = calculateTimeForScrolling(TARGET_SEEK_SCROLL_DISTANCE_PX);
|
||||
// To avoid UI hiccups, trigger a smooth scroll to a distance little further than the
|
||||
// interim target. Since we track the distance travelled in onSeekTargetStep callback, it
|
||||
// won't actually scroll more than what we need.
|
||||
action.update((int) (mInterimTargetDx * TARGET_SEEK_EXTRA_SCROLL_RATIO)
|
||||
, (int) (mInterimTargetDy * TARGET_SEEK_EXTRA_SCROLL_RATIO)
|
||||
, (int) (time * TARGET_SEEK_EXTRA_SCROLL_RATIO), mLinearInterpolator);
|
||||
}
|
||||
|
||||
private int clampApplyScroll(int tmpDt, int dt) {
|
||||
final int before = tmpDt;
|
||||
tmpDt -= dt;
|
||||
if (before * tmpDt <= 0) { // changed sign, reached 0 or was 0, reset
|
||||
return 0;
|
||||
}
|
||||
return tmpDt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for {@link #calculateDxToMakeVisible(android.view.View, int)} and
|
||||
* {@link #calculateDyToMakeVisible(android.view.View, int)}
|
||||
*/
|
||||
public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int
|
||||
snapPreference) {
|
||||
switch (snapPreference) {
|
||||
case SNAP_TO_START:
|
||||
return boxStart - viewStart;
|
||||
case SNAP_TO_END:
|
||||
return boxEnd - viewEnd;
|
||||
case SNAP_TO_ANY:
|
||||
final int dtStart = boxStart - viewStart;
|
||||
if (dtStart > 0) {
|
||||
return dtStart;
|
||||
}
|
||||
final int dtEnd = boxEnd - viewEnd;
|
||||
if (dtEnd < 0) {
|
||||
return dtEnd;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("snap preference should be one of the"
|
||||
+ " constants defined in SmoothScroller, starting with SNAP_");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the vertical scroll amount necessary to make the given view fully visible
|
||||
* inside the RecyclerView.
|
||||
*
|
||||
* @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}.
|
||||
* @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()) {
|
||||
return 0;
|
||||
}
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
final int top = layoutManager.getDecoratedTop(view) - params.topMargin;
|
||||
final int bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin;
|
||||
final int start = layoutManager.getPaddingTop();
|
||||
final int end = layoutManager.getHeight() - layoutManager.getPaddingBottom();
|
||||
return calculateDtToFit(top, bottom, start, end, snapPreference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the horizontal scroll amount necessary to make the given view fully visible
|
||||
* inside the RecyclerView.
|
||||
*
|
||||
* @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}
|
||||
* @return The vertical scroll amount necessary to make the view visible with the given
|
||||
* snap preference.
|
||||
*/
|
||||
public int calculateDxToMakeVisible(View view, int snapPreference) {
|
||||
final RecyclerView.LayoutManager layoutManager = getLayoutManager();
|
||||
if (!layoutManager.canScrollHorizontally()) {
|
||||
return 0;
|
||||
}
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
final int left = layoutManager.getDecoratedLeft(view) - params.leftMargin;
|
||||
final int right = layoutManager.getDecoratedRight(view) + params.rightMargin;
|
||||
final int start = layoutManager.getPaddingLeft();
|
||||
final int end = layoutManager.getWidth() - layoutManager.getPaddingRight();
|
||||
return calculateDtToFit(left, right, start, end, snapPreference);
|
||||
}
|
||||
|
||||
abstract public PointF computeScrollVectorForPosition(int targetPosition);
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.android.support.widget;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.telegram.android.support.widget.AdapterHelper.UpdateOp;
|
||||
import static org.telegram.android.support.widget.AdapterHelper.UpdateOp.ADD;
|
||||
import static org.telegram.android.support.widget.AdapterHelper.UpdateOp.MOVE;
|
||||
import static org.telegram.android.support.widget.AdapterHelper.UpdateOp.REMOVE;
|
||||
import static org.telegram.android.support.widget.AdapterHelper.UpdateOp.UPDATE;
|
||||
|
||||
class OpReorderer {
|
||||
|
||||
final Callback mCallback;
|
||||
|
||||
public OpReorderer(Callback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
void reorderOps(List<UpdateOp> ops) {
|
||||
// since move operations breaks continuity, their effects on ADD/RM are hard to handle.
|
||||
// we push them to the end of the list so that they can be handled easily.
|
||||
int badMove;
|
||||
while ((badMove = getLastMoveOutOfOrder(ops)) != -1) {
|
||||
swapMoveOp(ops, badMove, badMove + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void swapMoveOp(List<UpdateOp> list, int badMove, int next) {
|
||||
final UpdateOp moveOp = list.get(badMove);
|
||||
final UpdateOp nextOp = list.get(next);
|
||||
switch (nextOp.cmd) {
|
||||
case REMOVE:
|
||||
swapMoveRemove(list, badMove, moveOp, next, nextOp);
|
||||
break;
|
||||
case ADD:
|
||||
swapMoveAdd(list, badMove, moveOp, next, nextOp);
|
||||
break;
|
||||
case UPDATE:
|
||||
swapMoveUpdate(list, badMove, moveOp, next, nextOp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void swapMoveRemove(List<UpdateOp> list, int movePos, UpdateOp moveOp,
|
||||
int removePos, UpdateOp removeOp) {
|
||||
UpdateOp extraRm = null;
|
||||
// check if move is nulled out by remove
|
||||
boolean revertedMove = false;
|
||||
final boolean moveIsBackwards;
|
||||
|
||||
if (moveOp.positionStart < moveOp.itemCount) {
|
||||
moveIsBackwards = false;
|
||||
if (removeOp.positionStart == moveOp.positionStart
|
||||
&& removeOp.itemCount == moveOp.itemCount - moveOp.positionStart) {
|
||||
revertedMove = true;
|
||||
}
|
||||
} else {
|
||||
moveIsBackwards = true;
|
||||
if (removeOp.positionStart == moveOp.itemCount + 1 &&
|
||||
removeOp.itemCount == moveOp.positionStart - moveOp.itemCount) {
|
||||
revertedMove = true;
|
||||
}
|
||||
}
|
||||
|
||||
// going in reverse, first revert the effect of add
|
||||
if (moveOp.itemCount < removeOp.positionStart) {
|
||||
removeOp.positionStart--;
|
||||
} else if (moveOp.itemCount < removeOp.positionStart + removeOp.itemCount) {
|
||||
// move is removed.
|
||||
removeOp.itemCount --;
|
||||
moveOp.cmd = REMOVE;
|
||||
moveOp.itemCount = 1;
|
||||
if (removeOp.itemCount == 0) {
|
||||
list.remove(removePos);
|
||||
mCallback.recycleUpdateOp(removeOp);
|
||||
}
|
||||
// no need to swap, it is already a remove
|
||||
return;
|
||||
}
|
||||
|
||||
// now affect of add is consumed. now apply effect of first remove
|
||||
if (moveOp.positionStart <= removeOp.positionStart) {
|
||||
removeOp.positionStart++;
|
||||
} else if (moveOp.positionStart < removeOp.positionStart + removeOp.itemCount) {
|
||||
final int remaining = removeOp.positionStart + removeOp.itemCount
|
||||
- moveOp.positionStart;
|
||||
extraRm = mCallback.obtainUpdateOp(REMOVE, moveOp.positionStart + 1, remaining);
|
||||
removeOp.itemCount = moveOp.positionStart - removeOp.positionStart;
|
||||
}
|
||||
|
||||
// if effects of move is reverted by remove, we are done.
|
||||
if (revertedMove) {
|
||||
list.set(movePos, removeOp);
|
||||
list.remove(removePos);
|
||||
mCallback.recycleUpdateOp(moveOp);
|
||||
return;
|
||||
}
|
||||
|
||||
// now find out the new locations for move actions
|
||||
if (moveIsBackwards) {
|
||||
if (extraRm != null) {
|
||||
if (moveOp.positionStart > extraRm.positionStart) {
|
||||
moveOp.positionStart -= extraRm.itemCount;
|
||||
}
|
||||
if (moveOp.itemCount > extraRm.positionStart) {
|
||||
moveOp.itemCount -= extraRm.itemCount;
|
||||
}
|
||||
}
|
||||
if (moveOp.positionStart > removeOp.positionStart) {
|
||||
moveOp.positionStart -= removeOp.itemCount;
|
||||
}
|
||||
if (moveOp.itemCount > removeOp.positionStart) {
|
||||
moveOp.itemCount -= removeOp.itemCount;
|
||||
}
|
||||
} else {
|
||||
if (extraRm != null) {
|
||||
if (moveOp.positionStart >= extraRm.positionStart) {
|
||||
moveOp.positionStart -= extraRm.itemCount;
|
||||
}
|
||||
if (moveOp.itemCount >= extraRm.positionStart) {
|
||||
moveOp.itemCount -= extraRm.itemCount;
|
||||
}
|
||||
}
|
||||
if (moveOp.positionStart >= removeOp.positionStart) {
|
||||
moveOp.positionStart -= removeOp.itemCount;
|
||||
}
|
||||
if (moveOp.itemCount >= removeOp.positionStart) {
|
||||
moveOp.itemCount -= removeOp.itemCount;
|
||||
}
|
||||
}
|
||||
|
||||
list.set(movePos, removeOp);
|
||||
if (moveOp.positionStart != moveOp.itemCount) {
|
||||
list.set(removePos, moveOp);
|
||||
} else {
|
||||
list.remove(removePos);
|
||||
}
|
||||
if (extraRm != null) {
|
||||
list.add(movePos, extraRm);
|
||||
}
|
||||
}
|
||||
|
||||
private void swapMoveAdd(List<UpdateOp> list, int move, UpdateOp moveOp, int add,
|
||||
UpdateOp addOp) {
|
||||
int offset = 0;
|
||||
// going in reverse, first revert the effect of add
|
||||
if (moveOp.itemCount < addOp.positionStart) {
|
||||
offset--;
|
||||
}
|
||||
if (moveOp.positionStart < addOp.positionStart) {
|
||||
offset++;
|
||||
}
|
||||
if (addOp.positionStart <= moveOp.positionStart) {
|
||||
moveOp.positionStart += addOp.itemCount;
|
||||
}
|
||||
if (addOp.positionStart <= moveOp.itemCount) {
|
||||
moveOp.itemCount += addOp.itemCount;
|
||||
}
|
||||
addOp.positionStart += offset;
|
||||
list.set(move, addOp);
|
||||
list.set(add, moveOp);
|
||||
}
|
||||
|
||||
void swapMoveUpdate(List<UpdateOp> list, int move, UpdateOp moveOp, int update,
|
||||
UpdateOp updateOp) {
|
||||
UpdateOp extraUp1 = null;
|
||||
UpdateOp extraUp2 = null;
|
||||
// going in reverse, first revert the effect of add
|
||||
if (moveOp.itemCount < updateOp.positionStart) {
|
||||
updateOp.positionStart--;
|
||||
} else if (moveOp.itemCount < updateOp.positionStart + updateOp.itemCount) {
|
||||
// moved item is updated. add an update for it
|
||||
updateOp.itemCount--;
|
||||
extraUp1 = mCallback.obtainUpdateOp(UPDATE, moveOp.positionStart, 1);
|
||||
}
|
||||
// now affect of add is consumed. now apply effect of first remove
|
||||
if (moveOp.positionStart <= updateOp.positionStart) {
|
||||
updateOp.positionStart++;
|
||||
} else if (moveOp.positionStart < updateOp.positionStart + updateOp.itemCount) {
|
||||
final int remaining = updateOp.positionStart + updateOp.itemCount
|
||||
- moveOp.positionStart;
|
||||
extraUp2 = mCallback.obtainUpdateOp(UPDATE, moveOp.positionStart + 1, remaining);
|
||||
updateOp.itemCount -= remaining;
|
||||
}
|
||||
list.set(update, moveOp);
|
||||
if (updateOp.itemCount > 0) {
|
||||
list.set(move, updateOp);
|
||||
} else {
|
||||
list.remove(move);
|
||||
mCallback.recycleUpdateOp(updateOp);
|
||||
}
|
||||
if (extraUp1 != null) {
|
||||
list.add(move, extraUp1);
|
||||
}
|
||||
if (extraUp2 != null) {
|
||||
list.add(move, extraUp2);
|
||||
}
|
||||
}
|
||||
|
||||
private int getLastMoveOutOfOrder(List<UpdateOp> list) {
|
||||
boolean foundNonMove = false;
|
||||
for (int i = list.size() - 1; i >= 0; i--) {
|
||||
final UpdateOp op1 = list.get(i);
|
||||
if (op1.cmd == MOVE) {
|
||||
if (foundNonMove) {
|
||||
return i;
|
||||
}
|
||||
} else {
|
||||
foundNonMove = true;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static interface Callback {
|
||||
|
||||
UpdateOp obtainUpdateOp(int cmd, int startPosition, int itemCount);
|
||||
|
||||
void recycleUpdateOp(UpdateOp op);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,338 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.android.support.widget;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
/**
|
||||
* Helper class for LayoutManagers to abstract measurements depending on the View's orientation.
|
||||
* <p>
|
||||
* It is developed to easily support vertical and horizontal orientations in a LayoutManager but
|
||||
* can also be used to abstract calls around view bounds and child measurements with margins and
|
||||
* decorations.
|
||||
*
|
||||
* @see #createHorizontalHelper(RecyclerView.LayoutManager)
|
||||
* @see #createVerticalHelper(RecyclerView.LayoutManager)
|
||||
*/
|
||||
public abstract class OrientationHelper {
|
||||
|
||||
private static final int INVALID_SIZE = Integer.MIN_VALUE;
|
||||
|
||||
protected final RecyclerView.LayoutManager mLayoutManager;
|
||||
|
||||
public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
|
||||
|
||||
public static final int VERTICAL = LinearLayout.VERTICAL;
|
||||
|
||||
private int mLastTotalSpace = INVALID_SIZE;
|
||||
|
||||
private OrientationHelper(RecyclerView.LayoutManager layoutManager) {
|
||||
mLayoutManager = layoutManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method after onLayout method is complete if state is NOT pre-layout.
|
||||
* This method records information like layout bounds that might be useful in the next layout
|
||||
* calculations.
|
||||
*/
|
||||
public void onLayoutComplete() {
|
||||
mLastTotalSpace = getTotalSpace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the layout space change between the previous layout pass and current layout pass.
|
||||
* <p>
|
||||
* Make sure you call {@link #onLayoutComplete()} at the end of your LayoutManager's
|
||||
* {@link RecyclerView.LayoutManager#onLayoutChildren(RecyclerView.Recycler,
|
||||
* RecyclerView.State)} method.
|
||||
*
|
||||
* @return The difference between the current total space and previous layout's total space.
|
||||
* @see #onLayoutComplete()
|
||||
*/
|
||||
public int getTotalSpaceChange() {
|
||||
return INVALID_SIZE == mLastTotalSpace ? 0 : getTotalSpace() - mLastTotalSpace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start of the view including its decoration and margin.
|
||||
* <p>
|
||||
* For example, for the horizontal helper, if a View's left is at pixel 20, has 2px left
|
||||
* decoration and 3px left margin, returned value will be 15px.
|
||||
*
|
||||
* @param view The view element to check
|
||||
* @return The first pixel of the element
|
||||
* @see #getDecoratedEnd(android.view.View)
|
||||
*/
|
||||
public abstract int getDecoratedStart(View view);
|
||||
|
||||
/**
|
||||
* Returns the end of the view including its decoration and margin.
|
||||
* <p>
|
||||
* For example, for the horizontal helper, if a View's right is at pixel 200, has 2px right
|
||||
* decoration and 3px right margin, returned value will be 205.
|
||||
*
|
||||
* @param view The view element to check
|
||||
* @return The last pixel of the element
|
||||
* @see #getDecoratedStart(android.view.View)
|
||||
*/
|
||||
public abstract int getDecoratedEnd(View view);
|
||||
|
||||
/**
|
||||
* Returns the space occupied by this View in the current orientation including decorations and
|
||||
* margins.
|
||||
*
|
||||
* @param view The view element to check
|
||||
* @return Total space occupied by this view
|
||||
* @see #getDecoratedMeasurementInOther(View)
|
||||
*/
|
||||
public abstract int getDecoratedMeasurement(View view);
|
||||
|
||||
/**
|
||||
* Returns the space occupied by this View in the perpendicular orientation including
|
||||
* decorations and margins.
|
||||
*
|
||||
* @param view The view element to check
|
||||
* @return Total space occupied by this view in the perpendicular orientation to current one
|
||||
* @see #getDecoratedMeasurement(View)
|
||||
*/
|
||||
public abstract int getDecoratedMeasurementInOther(View view);
|
||||
|
||||
/**
|
||||
* Returns the start position of the layout after the start padding is added.
|
||||
*
|
||||
* @return The very first pixel we can draw.
|
||||
*/
|
||||
public abstract int getStartAfterPadding();
|
||||
|
||||
/**
|
||||
* Returns the end position of the layout after the end padding is removed.
|
||||
*
|
||||
* @return The end boundary for this layout.
|
||||
*/
|
||||
public abstract int getEndAfterPadding();
|
||||
|
||||
/**
|
||||
* Returns the end position of the layout without taking padding into account.
|
||||
*
|
||||
* @return The end boundary for this layout without considering padding.
|
||||
*/
|
||||
public abstract int getEnd();
|
||||
|
||||
/**
|
||||
* Offsets all children's positions by the given amount.
|
||||
*
|
||||
* @param amount Value to add to each child's layout parameters
|
||||
*/
|
||||
public abstract void offsetChildren(int amount);
|
||||
|
||||
/**
|
||||
* Returns the total space to layout. This number is the difference between
|
||||
* {@link #getEndAfterPadding()} and {@link #getStartAfterPadding()}.
|
||||
*
|
||||
* @return Total space to layout children
|
||||
*/
|
||||
public abstract int getTotalSpace();
|
||||
|
||||
/**
|
||||
* Offsets the child in this orientation.
|
||||
*
|
||||
* @param view View to offset
|
||||
* @param offset offset amount
|
||||
*/
|
||||
public abstract void offsetChild(View view, int offset);
|
||||
|
||||
/**
|
||||
* Returns the padding at the end of the layout. For horizontal helper, this is the right
|
||||
* padding and for vertical helper, this is the bottom padding. This method does not check
|
||||
* whether the layout is RTL or not.
|
||||
*
|
||||
* @return The padding at the end of the layout.
|
||||
*/
|
||||
public abstract int getEndPadding();
|
||||
|
||||
/**
|
||||
* Creates an OrientationHelper for the given LayoutManager and orientation.
|
||||
*
|
||||
* @param layoutManager LayoutManager to attach to
|
||||
* @param orientation Desired orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}
|
||||
* @return A new OrientationHelper
|
||||
*/
|
||||
public static OrientationHelper createOrientationHelper(
|
||||
RecyclerView.LayoutManager layoutManager, int orientation) {
|
||||
switch (orientation) {
|
||||
case HORIZONTAL:
|
||||
return createHorizontalHelper(layoutManager);
|
||||
case VERTICAL:
|
||||
return createVerticalHelper(layoutManager);
|
||||
}
|
||||
throw new IllegalArgumentException("invalid orientation");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a horizontal OrientationHelper for the given LayoutManager.
|
||||
*
|
||||
* @param layoutManager The LayoutManager to attach to.
|
||||
* @return A new OrientationHelper
|
||||
*/
|
||||
public static OrientationHelper createHorizontalHelper(
|
||||
RecyclerView.LayoutManager layoutManager) {
|
||||
return new OrientationHelper(layoutManager) {
|
||||
@Override
|
||||
public int getEndAfterPadding() {
|
||||
return mLayoutManager.getWidth() - mLayoutManager.getPaddingRight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEnd() {
|
||||
return mLayoutManager.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offsetChildren(int amount) {
|
||||
mLayoutManager.offsetChildrenHorizontal(amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartAfterPadding() {
|
||||
return mLayoutManager.getPaddingLeft();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecoratedMeasurement(View view) {
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
|
||||
+ params.rightMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecoratedMeasurementInOther(View view) {
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
|
||||
+ params.bottomMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecoratedEnd(View view) {
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
return mLayoutManager.getDecoratedRight(view) + params.rightMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecoratedStart(View view) {
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
return mLayoutManager.getDecoratedLeft(view) - params.leftMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalSpace() {
|
||||
return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft()
|
||||
- mLayoutManager.getPaddingRight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offsetChild(View view, int offset) {
|
||||
view.offsetLeftAndRight(offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndPadding() {
|
||||
return mLayoutManager.getPaddingRight();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a vertical OrientationHelper for the given LayoutManager.
|
||||
*
|
||||
* @param layoutManager The LayoutManager to attach to.
|
||||
* @return A new OrientationHelper
|
||||
*/
|
||||
public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) {
|
||||
return new OrientationHelper(layoutManager) {
|
||||
@Override
|
||||
public int getEndAfterPadding() {
|
||||
return mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEnd() {
|
||||
return mLayoutManager.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offsetChildren(int amount) {
|
||||
mLayoutManager.offsetChildrenVertical(amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartAfterPadding() {
|
||||
return mLayoutManager.getPaddingTop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecoratedMeasurement(View view) {
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
|
||||
+ params.bottomMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecoratedMeasurementInOther(View view) {
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
|
||||
+ params.rightMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecoratedEnd(View view) {
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecoratedStart(View view) {
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
|
||||
view.getLayoutParams();
|
||||
return mLayoutManager.getDecoratedTop(view) - params.topMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalSpace() {
|
||||
return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop()
|
||||
- mLayoutManager.getPaddingBottom();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offsetChild(View view, int offset) {
|
||||
view.offsetTopAndBottom(offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndPadding() {
|
||||
return mLayoutManager.getPaddingBottom();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,460 @@
|
|||
/*
|
||||
* Copyright (C) 2013 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.android.support.widget;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Like a SparseArray, but with the ability to offset key ranges for bulk insertions/deletions.
|
||||
*/
|
||||
class PositionMap<E> implements Cloneable {
|
||||
private static final Object DELETED = new Object();
|
||||
private boolean mGarbage = false;
|
||||
|
||||
private int[] mKeys;
|
||||
private Object[] mValues;
|
||||
private int mSize;
|
||||
|
||||
/**
|
||||
* Creates a new SparseArray containing no mappings.
|
||||
*/
|
||||
public PositionMap() {
|
||||
this(10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new PositionMap containing no mappings that will not
|
||||
* require any additional memory allocation to store the specified
|
||||
* number of mappings. If you supply an initial capacity of 0, the
|
||||
* sparse array will be initialized with a light-weight representation
|
||||
* not requiring any additional array allocations.
|
||||
*/
|
||||
public PositionMap(int initialCapacity) {
|
||||
if (initialCapacity == 0) {
|
||||
mKeys = ContainerHelpers.EMPTY_INTS;
|
||||
mValues = ContainerHelpers.EMPTY_OBJECTS;
|
||||
} else {
|
||||
initialCapacity = idealIntArraySize(initialCapacity);
|
||||
mKeys = new int[initialCapacity];
|
||||
mValues = new Object[initialCapacity];
|
||||
}
|
||||
mSize = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public PositionMap<E> clone() {
|
||||
PositionMap<E> clone = null;
|
||||
try {
|
||||
clone = (PositionMap<E>) super.clone();
|
||||
clone.mKeys = mKeys.clone();
|
||||
clone.mValues = mValues.clone();
|
||||
} catch (CloneNotSupportedException cnse) {
|
||||
/* ignore */
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Object mapped from the specified key, or <code>null</code>
|
||||
* if no such mapping has been made.
|
||||
*/
|
||||
public E get(int key) {
|
||||
return get(key, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Object mapped from the specified key, or the specified Object
|
||||
* if no such mapping has been made.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public E get(int key, E valueIfKeyNotFound) {
|
||||
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
|
||||
|
||||
if (i < 0 || mValues[i] == DELETED) {
|
||||
return valueIfKeyNotFound;
|
||||
} else {
|
||||
return (E) mValues[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the mapping from the specified key, if there was any.
|
||||
*/
|
||||
public void delete(int key) {
|
||||
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
|
||||
|
||||
if (i >= 0) {
|
||||
if (mValues[i] != DELETED) {
|
||||
mValues[i] = DELETED;
|
||||
mGarbage = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for {@link #delete(int)}.
|
||||
*/
|
||||
public void remove(int key) {
|
||||
delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the mapping at the specified index.
|
||||
*/
|
||||
public void removeAt(int index) {
|
||||
if (mValues[index] != DELETED) {
|
||||
mValues[index] = DELETED;
|
||||
mGarbage = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a range of mappings as a batch.
|
||||
*
|
||||
* @param index Index to begin at
|
||||
* @param size Number of mappings to remove
|
||||
*/
|
||||
public void removeAtRange(int index, int size) {
|
||||
final int end = Math.min(mSize, index + size);
|
||||
for (int i = index; i < end; i++) {
|
||||
removeAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void insertKeyRange(int keyStart, int count) {
|
||||
|
||||
}
|
||||
|
||||
public void removeKeyRange(ArrayList<E> removedItems, int keyStart, int count) {
|
||||
|
||||
}
|
||||
|
||||
private void gc() {
|
||||
// Log.e("SparseArray", "gc start with " + mSize);
|
||||
|
||||
int n = mSize;
|
||||
int o = 0;
|
||||
int[] keys = mKeys;
|
||||
Object[] values = mValues;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
Object val = values[i];
|
||||
|
||||
if (val != DELETED) {
|
||||
if (i != o) {
|
||||
keys[o] = keys[i];
|
||||
values[o] = val;
|
||||
values[i] = null;
|
||||
}
|
||||
|
||||
o++;
|
||||
}
|
||||
}
|
||||
|
||||
mGarbage = false;
|
||||
mSize = o;
|
||||
|
||||
// Log.e("SparseArray", "gc end with " + mSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mapping from the specified key to the specified value,
|
||||
* replacing the previous mapping from the specified key if there
|
||||
* was one.
|
||||
*/
|
||||
public void put(int key, E value) {
|
||||
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
|
||||
|
||||
if (i >= 0) {
|
||||
mValues[i] = value;
|
||||
} else {
|
||||
i = ~i;
|
||||
|
||||
if (i < mSize && mValues[i] == DELETED) {
|
||||
mKeys[i] = key;
|
||||
mValues[i] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mGarbage && mSize >= mKeys.length) {
|
||||
gc();
|
||||
|
||||
// Search again because indices may have changed.
|
||||
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
|
||||
}
|
||||
|
||||
if (mSize >= mKeys.length) {
|
||||
int n = idealIntArraySize(mSize + 1);
|
||||
|
||||
int[] nkeys = new int[n];
|
||||
Object[] nvalues = new Object[n];
|
||||
|
||||
// Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
|
||||
System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
|
||||
System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
|
||||
|
||||
mKeys = nkeys;
|
||||
mValues = nvalues;
|
||||
}
|
||||
|
||||
if (mSize - i != 0) {
|
||||
// Log.e("SparseArray", "move " + (mSize - i));
|
||||
System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
|
||||
System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
|
||||
}
|
||||
|
||||
mKeys[i] = key;
|
||||
mValues[i] = value;
|
||||
mSize++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of key-value mappings that this SparseArray
|
||||
* currently stores.
|
||||
*/
|
||||
public int size() {
|
||||
if (mGarbage) {
|
||||
gc();
|
||||
}
|
||||
|
||||
return mSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an index in the range <code>0...size()-1</code>, returns
|
||||
* the key from the <code>index</code>th key-value mapping that this
|
||||
* SparseArray stores.
|
||||
*/
|
||||
public int keyAt(int index) {
|
||||
if (mGarbage) {
|
||||
gc();
|
||||
}
|
||||
|
||||
return mKeys[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an index in the range <code>0...size()-1</code>, returns
|
||||
* the value from the <code>index</code>th key-value mapping that this
|
||||
* SparseArray stores.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public E valueAt(int index) {
|
||||
if (mGarbage) {
|
||||
gc();
|
||||
}
|
||||
|
||||
return (E) mValues[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an index in the range <code>0...size()-1</code>, sets a new
|
||||
* value for the <code>index</code>th key-value mapping that this
|
||||
* SparseArray stores.
|
||||
*/
|
||||
public void setValueAt(int index, E value) {
|
||||
if (mGarbage) {
|
||||
gc();
|
||||
}
|
||||
|
||||
mValues[index] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index for which {@link #keyAt} would return the
|
||||
* specified key, or a negative number if the specified
|
||||
* key is not mapped.
|
||||
*/
|
||||
public int indexOfKey(int key) {
|
||||
if (mGarbage) {
|
||||
gc();
|
||||
}
|
||||
|
||||
return ContainerHelpers.binarySearch(mKeys, mSize, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an index for which {@link #valueAt} would return the
|
||||
* specified key, or a negative number if no keys map to the
|
||||
* specified value.
|
||||
* <p>Beware that this is a linear search, unlike lookups by key,
|
||||
* and that multiple keys can map to the same value and this will
|
||||
* find only one of them.
|
||||
* <p>Note also that unlike most collections' {@code indexOf} methods,
|
||||
* this method compares values using {@code ==} rather than {@code equals}.
|
||||
*/
|
||||
public int indexOfValue(E value) {
|
||||
if (mGarbage) {
|
||||
gc();
|
||||
}
|
||||
|
||||
for (int i = 0; i < mSize; i++)
|
||||
if (mValues[i] == value)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all key-value mappings from this SparseArray.
|
||||
*/
|
||||
public void clear() {
|
||||
int n = mSize;
|
||||
Object[] values = mValues;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
values[i] = null;
|
||||
}
|
||||
|
||||
mSize = 0;
|
||||
mGarbage = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts a key/value pair into the array, optimizing for the case where
|
||||
* the key is greater than all existing keys in the array.
|
||||
*/
|
||||
public void append(int key, E value) {
|
||||
if (mSize != 0 && key <= mKeys[mSize - 1]) {
|
||||
put(key, value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mGarbage && mSize >= mKeys.length) {
|
||||
gc();
|
||||
}
|
||||
|
||||
int pos = mSize;
|
||||
if (pos >= mKeys.length) {
|
||||
int n = idealIntArraySize(pos + 1);
|
||||
|
||||
int[] nkeys = new int[n];
|
||||
Object[] nvalues = new Object[n];
|
||||
|
||||
// Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
|
||||
System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
|
||||
System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
|
||||
|
||||
mKeys = nkeys;
|
||||
mValues = nvalues;
|
||||
}
|
||||
|
||||
mKeys[pos] = key;
|
||||
mValues[pos] = value;
|
||||
mSize = pos + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>This implementation composes a string by iterating over its mappings. If
|
||||
* this map contains itself as a value, the string "(this Map)"
|
||||
* will appear in its place.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
if (size() <= 0) {
|
||||
return "{}";
|
||||
}
|
||||
|
||||
StringBuilder buffer = new StringBuilder(mSize * 28);
|
||||
buffer.append('{');
|
||||
for (int i=0; i<mSize; i++) {
|
||||
if (i > 0) {
|
||||
buffer.append(", ");
|
||||
}
|
||||
int key = keyAt(i);
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
Object value = valueAt(i);
|
||||
if (value != this) {
|
||||
buffer.append(value);
|
||||
} else {
|
||||
buffer.append("(this Map)");
|
||||
}
|
||||
}
|
||||
buffer.append('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
static int idealByteArraySize(int need) {
|
||||
for (int i = 4; i < 32; i++)
|
||||
if (need <= (1 << i) - 12)
|
||||
return (1 << i) - 12;
|
||||
|
||||
return need;
|
||||
}
|
||||
|
||||
static int idealBooleanArraySize(int need) {
|
||||
return idealByteArraySize(need);
|
||||
}
|
||||
|
||||
static int idealShortArraySize(int need) {
|
||||
return idealByteArraySize(need * 2) / 2;
|
||||
}
|
||||
|
||||
static int idealCharArraySize(int need) {
|
||||
return idealByteArraySize(need * 2) / 2;
|
||||
}
|
||||
|
||||
static int idealIntArraySize(int need) {
|
||||
return idealByteArraySize(need * 4) / 4;
|
||||
}
|
||||
|
||||
static int idealFloatArraySize(int need) {
|
||||
return idealByteArraySize(need * 4) / 4;
|
||||
}
|
||||
|
||||
static int idealObjectArraySize(int need) {
|
||||
return idealByteArraySize(need * 4) / 4;
|
||||
}
|
||||
|
||||
static int idealLongArraySize(int need) {
|
||||
return idealByteArraySize(need * 8) / 8;
|
||||
}
|
||||
|
||||
static class ContainerHelpers {
|
||||
static final boolean[] EMPTY_BOOLEANS = new boolean[0];
|
||||
static final int[] EMPTY_INTS = new int[0];
|
||||
static final long[] EMPTY_LONGS = new long[0];
|
||||
static final Object[] EMPTY_OBJECTS = new Object[0];
|
||||
|
||||
// This is Arrays.binarySearch(), but doesn't do any argument validation.
|
||||
static int binarySearch(int[] array, int size, int value) {
|
||||
int lo = 0;
|
||||
int hi = size - 1;
|
||||
|
||||
while (lo <= hi) {
|
||||
final int mid = (lo + hi) >>> 1;
|
||||
final int midVal = array[mid];
|
||||
|
||||
if (midVal < value) {
|
||||
lo = mid + 1;
|
||||
} else if (midVal > value) {
|
||||
hi = mid - 1;
|
||||
} else {
|
||||
return mid; // value found
|
||||
}
|
||||
}
|
||||
return ~lo; // value not present
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.android.support.widget;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.view.AccessibilityDelegateCompat;
|
||||
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
/**
|
||||
* The AccessibilityDelegate used by RecyclerView.
|
||||
* <p>
|
||||
* This class handles basic accessibility actions and delegates them to LayoutManager.
|
||||
*/
|
||||
public class RecyclerViewAccessibilityDelegate extends AccessibilityDelegateCompat {
|
||||
final RecyclerView mRecyclerView;
|
||||
|
||||
|
||||
public RecyclerViewAccessibilityDelegate(RecyclerView recyclerView) {
|
||||
mRecyclerView = recyclerView;
|
||||
}
|
||||
|
||||
private boolean shouldIgnore() {
|
||||
return mRecyclerView.hasPendingAdapterUpdates();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performAccessibilityAction(View host, int action, Bundle args) {
|
||||
if (super.performAccessibilityAction(host, action, args)) {
|
||||
return true;
|
||||
}
|
||||
if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
|
||||
return mRecyclerView.getLayoutManager().performAccessibilityAction(action, args);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
|
||||
super.onInitializeAccessibilityNodeInfo(host, info);
|
||||
info.setClassName(RecyclerView.class.getName());
|
||||
if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
|
||||
mRecyclerView.getLayoutManager().onInitializeAccessibilityNodeInfo(info);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
|
||||
super.onInitializeAccessibilityEvent(host, event);
|
||||
event.setClassName(RecyclerView.class.getName());
|
||||
if (host instanceof RecyclerView && !shouldIgnore()) {
|
||||
RecyclerView rv = (RecyclerView) host;
|
||||
if (rv.getLayoutManager() != null) {
|
||||
rv.getLayoutManager().onInitializeAccessibilityEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AccessibilityDelegateCompat getItemDelegate() {
|
||||
return mItemDelegate;
|
||||
}
|
||||
|
||||
final AccessibilityDelegateCompat mItemDelegate = new AccessibilityDelegateCompat() {
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
|
||||
super.onInitializeAccessibilityNodeInfo(host, info);
|
||||
if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
|
||||
mRecyclerView.getLayoutManager().
|
||||
onInitializeAccessibilityNodeInfoForItem(host, info);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performAccessibilityAction(View host, int action, Bundle args) {
|
||||
if (super.performAccessibilityAction(host, action, args)) {
|
||||
return true;
|
||||
}
|
||||
if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
|
||||
return mRecyclerView.getLayoutManager().
|
||||
performAccessibilityActionForItem(host, action, args);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|