Update to 2.8.1
Thanks to https://github.com/DrKLO/Telegram/issues/738 for smart notifications
|
@ -13,11 +13,10 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
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 'com.google.android.gms:play-services:3.2.+'
|
||||||
compile 'net.hockeyapp.android:HockeySDK:3.5.+'
|
compile 'net.hockeyapp.android:HockeySDK:3.5.+'
|
||||||
compile 'com.googlecode.mp4parser:isoparser:1.0.+'
|
compile 'com.googlecode.mp4parser:isoparser:1.0.+'
|
||||||
compile 'com.android.support:recyclerview-v7:+'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
@ -82,7 +81,7 @@ android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 8
|
minSdkVersion 8
|
||||||
targetSdkVersion 22
|
targetSdkVersion 22
|
||||||
versionCode 492
|
versionCode 521
|
||||||
versionName "2.7.0"
|
versionName "2.8.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ include $(BUILD_STATIC_LIBRARY)
|
||||||
include $(CLEAR_VARS)
|
include $(CLEAR_VARS)
|
||||||
LOCAL_PRELINK_MODULE := false
|
LOCAL_PRELINK_MODULE := false
|
||||||
LOCAL_STATIC_LIBRARIES := webp sqlite
|
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 := -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 += -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
|
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);
|
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) {
|
if (!bitmap) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -312,7 +312,9 @@ JNIEXPORT void Java_org_telegram_messenger_Utilities_blurBitmap(JNIEnv *env, jcl
|
||||||
} else {
|
} else {
|
||||||
fastBlurMore(info.width, info.height, info.stride, pixels, radius);
|
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) {
|
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.TLRPC;
|
||||||
import org.telegram.messenger.ApplicationLoader;
|
import org.telegram.messenger.ApplicationLoader;
|
||||||
import org.telegram.messenger.UserConfig;
|
import org.telegram.messenger.UserConfig;
|
||||||
import org.telegram.ui.AnimationCompat.AnimatorListenerAdapterProxy;
|
import org.telegram.android.AnimationCompat.AnimatorListenerAdapterProxy;
|
||||||
import org.telegram.ui.AnimationCompat.AnimatorSetProxy;
|
import org.telegram.android.AnimationCompat.AnimatorSetProxy;
|
||||||
import org.telegram.ui.AnimationCompat.ObjectAnimatorProxy;
|
import org.telegram.android.AnimationCompat.ObjectAnimatorProxy;
|
||||||
import org.telegram.ui.AnimationCompat.ViewProxy;
|
import org.telegram.android.AnimationCompat.ViewProxy;
|
||||||
import org.telegram.ui.Components.ForegroundDetector;
|
import org.telegram.ui.Components.ForegroundDetector;
|
||||||
import org.telegram.ui.Components.NumberPicker;
|
import org.telegram.ui.Components.NumberPicker;
|
||||||
import org.telegram.ui.Components.TypefaceSpan;
|
import org.telegram.ui.Components.TypefaceSpan;
|
||||||
|
@ -72,6 +72,7 @@ public class AndroidUtilities {
|
||||||
public static Integer photoSize = null;
|
public static Integer photoSize = null;
|
||||||
public static DisplayMetrics displayMetrics = new DisplayMetrics();
|
public static DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||||
public static int leftBaseline;
|
public static int leftBaseline;
|
||||||
|
public static boolean usingHardwareInput;
|
||||||
private static Boolean isTablet = null;
|
private static Boolean isTablet = null;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
@ -227,21 +228,38 @@ public class AndroidUtilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int dp(float value) {
|
public static int dp(float value) {
|
||||||
|
if (value == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return (int)Math.ceil(density * value);
|
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) {
|
public static float dpf2(float value) {
|
||||||
|
if (value == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return density * value;
|
return density * value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void checkDisplaySize() {
|
public static void checkDisplaySize() {
|
||||||
try {
|
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) {
|
if (manager != null) {
|
||||||
Display display = manager.getDefaultDisplay();
|
Display display = manager.getDefaultDisplay();
|
||||||
if (display != null) {
|
if (display != null) {
|
||||||
display.getMetrics(displayMetrics);
|
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());
|
displaySize.set(display.getWidth(), display.getHeight());
|
||||||
} else {
|
} else {
|
||||||
display.getSize(displaySize);
|
display.getSize(displaySize);
|
||||||
|
@ -252,6 +270,38 @@ public class AndroidUtilities {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
FileLog.e("tmessages", 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) {
|
public static float getPixelsInCM(float cm, boolean isX) {
|
||||||
|
@ -562,6 +612,9 @@ public class AndroidUtilities {
|
||||||
if (start != -1) {
|
if (start != -1) {
|
||||||
stringBuilder.replace(start, start + 3, "");
|
stringBuilder.replace(start, start + 3, "");
|
||||||
end = stringBuilder.indexOf("</b>");
|
end = stringBuilder.indexOf("</b>");
|
||||||
|
if (end == -1) {
|
||||||
|
end = stringBuilder.indexOf("<b>");
|
||||||
|
}
|
||||||
stringBuilder.replace(end, end + 4, "");
|
stringBuilder.replace(end, end + 4, "");
|
||||||
bolds.add(start);
|
bolds.add(start);
|
||||||
bolds.add(end);
|
bolds.add(end);
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.telegram.ui.Animation;
|
package org.telegram.android.Animation;
|
||||||
|
|
||||||
import android.view.animation.Interpolator;
|
import android.view.animation.Interpolator;
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.telegram.ui.Animation;
|
package org.telegram.android.Animation;
|
||||||
|
|
||||||
public abstract class AnimatorListenerAdapter10 implements Animator10.AnimatorListener, Animator10.AnimatorPauseListener {
|
public abstract class AnimatorListenerAdapter10 implements Animator10.AnimatorListener, Animator10.AnimatorPauseListener {
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.telegram.ui.Animation;
|
package org.telegram.android.Animation;
|
||||||
|
|
||||||
import android.view.animation.Interpolator;
|
import android.view.animation.Interpolator;
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.telegram.ui.Animation;
|
package org.telegram.android.Animation;
|
||||||
|
|
||||||
public class FloatEvaluator implements TypeEvaluator<Number> {
|
public class FloatEvaluator implements TypeEvaluator<Number> {
|
||||||
public Float evaluate(float fraction, Number startValue, Number endValue) {
|
public Float evaluate(float fraction, Number startValue, Number endValue) {
|
|
@ -14,11 +14,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.telegram.ui.Animation;
|
package org.telegram.android.Animation;
|
||||||
|
|
||||||
import android.view.animation.Interpolator;
|
import android.view.animation.Interpolator;
|
||||||
|
|
||||||
import org.telegram.ui.Animation.Keyframe.FloatKeyframe;
|
import org.telegram.android.Animation.Keyframe.FloatKeyframe;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.telegram.ui.Animation;
|
package org.telegram.android.Animation;
|
||||||
|
|
||||||
public abstract class FloatProperty10<T> extends Property<T, Float> {
|
public abstract class FloatProperty10<T> extends Property<T, Float> {
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.telegram.ui.Animation;
|
package org.telegram.android.Animation;
|
||||||
|
|
||||||
public class IntEvaluator implements TypeEvaluator<Integer> {
|
public class IntEvaluator implements TypeEvaluator<Integer> {
|
||||||
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
|
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
|
|
@ -14,11 +14,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.telegram.ui.Animation;
|
package org.telegram.android.Animation;
|
||||||
|
|
||||||
import android.view.animation.Interpolator;
|
import android.view.animation.Interpolator;
|
||||||
|
|
||||||
import org.telegram.ui.Animation.Keyframe.IntKeyframe;
|
import org.telegram.android.Animation.Keyframe.IntKeyframe;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.telegram.ui.Animation;
|
package org.telegram.android.Animation;
|
||||||
|
|
||||||
public abstract class IntProperty<T> extends Property<T, Integer> {
|
public abstract class IntProperty<T> extends Property<T, Integer> {
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.telegram.ui.Animation;
|
package org.telegram.android.Animation;
|
||||||
|
|
||||||
import android.view.animation.Interpolator;
|
import android.view.animation.Interpolator;
|
||||||
|
|
|
@ -14,16 +14,16 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.telegram.ui.Animation;
|
package org.telegram.android.Animation;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.animation.Interpolator;
|
import android.view.animation.Interpolator;
|
||||||
|
|
||||||
import org.telegram.ui.Animation.Keyframe.IntKeyframe;
|
import org.telegram.android.Animation.Keyframe.IntKeyframe;
|
||||||
import org.telegram.ui.Animation.Keyframe.FloatKeyframe;
|
import org.telegram.android.Animation.Keyframe.FloatKeyframe;
|
||||||
import org.telegram.ui.Animation.Keyframe.ObjectKeyframe;
|
import org.telegram.android.Animation.Keyframe.ObjectKeyframe;
|
||||||
|
|
||||||
class KeyframeSet {
|
class KeyframeSet {
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.telegram.ui.Animation;
|
package org.telegram.android.Animation;
|
||||||
|
|
||||||
public class NoSuchPropertyException extends RuntimeException {
|
public class NoSuchPropertyException extends RuntimeException {
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.telegram.ui.Animation;
|
package org.telegram.android.Animation;
|
||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.telegram.ui.Animation;
|
package org.telegram.android.Animation;
|
||||||
|
|
||||||
public abstract class Property<T, V> {
|
public abstract class Property<T, V> {
|
||||||
|
|
|
@ -14,9 +14,8 @@
|
||||||
* limitations under the License.
|
* 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.lang.reflect.Method;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.telegram.ui.Animation;
|
package org.telegram.android.Animation;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.telegram.ui.Animation;
|
package org.telegram.android.Animation;
|
||||||
|
|
||||||
public interface TypeEvaluator<T> {
|
public interface TypeEvaluator<T> {
|
||||||
T evaluate(float fraction, T startValue, T endValue);
|
T evaluate(float fraction, T startValue, T endValue);
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.telegram.ui.Animation;
|
package org.telegram.android.Animation;
|
||||||
|
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.util.AndroidRuntimeException;
|
import android.util.AndroidRuntimeException;
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.telegram.ui.Animation;
|
package org.telegram.android.Animation;
|
||||||
|
|
||||||
import android.graphics.Camera;
|
import android.graphics.Camera;
|
||||||
import android.graphics.Matrix;
|
import android.graphics.Matrix;
|
|
@ -6,14 +6,14 @@
|
||||||
* Copyright Nikolai Kudashov, 2013-2014.
|
* Copyright Nikolai Kudashov, 2013-2014.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.telegram.ui.AnimationCompat;
|
package org.telegram.android.AnimationCompat;
|
||||||
|
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
|
||||||
import org.telegram.ui.Animation.Animator10;
|
import org.telegram.android.Animation.Animator10;
|
||||||
import org.telegram.ui.Animation.AnimatorListenerAdapter10;
|
import org.telegram.android.Animation.AnimatorListenerAdapter10;
|
||||||
import org.telegram.ui.Animation.View10;
|
import org.telegram.android.Animation.View10;
|
||||||
|
|
||||||
public class AnimatorListenerAdapterProxy {
|
public class AnimatorListenerAdapterProxy {
|
||||||
protected Object animatorListenerAdapter;
|
protected Object animatorListenerAdapter;
|
|
@ -6,17 +6,17 @@
|
||||||
* Copyright Nikolai Kudashov, 2013-2014.
|
* Copyright Nikolai Kudashov, 2013-2014.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.telegram.ui.AnimationCompat;
|
package org.telegram.android.AnimationCompat;
|
||||||
|
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
import android.animation.AnimatorSet;
|
import android.animation.AnimatorSet;
|
||||||
import android.view.animation.Interpolator;
|
import android.view.animation.Interpolator;
|
||||||
|
|
||||||
import org.telegram.ui.Animation.Animator10;
|
import org.telegram.android.Animation.Animator10;
|
||||||
import org.telegram.ui.Animation.AnimatorListenerAdapter10;
|
import org.telegram.android.Animation.AnimatorListenerAdapter10;
|
||||||
import org.telegram.ui.Animation.AnimatorSet10;
|
import org.telegram.android.Animation.AnimatorSet10;
|
||||||
import org.telegram.ui.Animation.View10;
|
import org.telegram.android.Animation.View10;
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
|
@ -6,15 +6,15 @@
|
||||||
* Copyright Nikolai Kudashov, 2013-2014.
|
* Copyright Nikolai Kudashov, 2013-2014.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.telegram.ui.AnimationCompat;
|
package org.telegram.android.AnimationCompat;
|
||||||
|
|
||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
import android.animation.ObjectAnimator;
|
import android.animation.ObjectAnimator;
|
||||||
import android.view.animation.Interpolator;
|
import android.view.animation.Interpolator;
|
||||||
|
|
||||||
import org.telegram.ui.Animation.AnimatorListenerAdapter10;
|
import org.telegram.android.Animation.AnimatorListenerAdapter10;
|
||||||
import org.telegram.ui.Animation.ObjectAnimator10;
|
import org.telegram.android.Animation.ObjectAnimator10;
|
||||||
import org.telegram.ui.Animation.View10;
|
import org.telegram.android.Animation.View10;
|
||||||
|
|
||||||
public class ObjectAnimatorProxy {
|
public class ObjectAnimatorProxy {
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
* Copyright Nikolai Kudashov, 2013-2014.
|
* Copyright Nikolai Kudashov, 2013-2014.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.telegram.ui.AnimationCompat;
|
package org.telegram.android.AnimationCompat;
|
||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import org.telegram.ui.Animation.View10;
|
import org.telegram.android.Animation.View10;
|
||||||
|
|
||||||
public class ViewProxy {
|
public class ViewProxy {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import android.app.Activity;
|
||||||
import android.content.ContentProviderOperation;
|
import android.content.ContentProviderOperation;
|
||||||
import android.content.ContentProviderResult;
|
import android.content.ContentProviderResult;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
|
import android.content.ContentValues;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
@ -37,7 +38,6 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public class ContactsController {
|
public class ContactsController {
|
||||||
|
@ -168,7 +168,7 @@ public class ContactsController {
|
||||||
if (!updatingInviteText && (inviteText == null || time + 86400 < (int)(System.currentTimeMillis() / 1000))) {
|
if (!updatingInviteText && (inviteText == null || time + 86400 < (int)(System.currentTimeMillis() / 1000))) {
|
||||||
updatingInviteText = true;
|
updatingInviteText = true;
|
||||||
TLRPC.TL_help_getInviteText req = new TLRPC.TL_help_getInviteText();
|
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) {
|
if (req.lang_code == null || req.lang_code.length() == 0) {
|
||||||
req.lang_code = "en";
|
req.lang_code = "en";
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,19 @@ public class ContactsController {
|
||||||
|
|
||||||
public void checkAppAccount() {
|
public void checkAppAccount() {
|
||||||
AccountManager am = AccountManager.get(ApplicationLoader.applicationContext);
|
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;
|
boolean recreateAccount = false;
|
||||||
if (UserConfig.isClientActivated()) {
|
if (UserConfig.isClientActivated()) {
|
||||||
if (accounts.length == 1) {
|
if (accounts.length == 1) {
|
||||||
|
@ -227,7 +239,7 @@ public class ContactsController {
|
||||||
}
|
}
|
||||||
if (UserConfig.isClientActivated()) {
|
if (UserConfig.isClientActivated()) {
|
||||||
try {
|
try {
|
||||||
currentAccount = new Account(UserConfig.getCurrentUser().phone, "org.telegram.account");
|
currentAccount = new Account(UserConfig.getCurrentUser().phone, "org.telegram.messenger");
|
||||||
am.addAccountExplicitly(currentAccount, "", null);
|
am.addAccountExplicitly(currentAccount, "", null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
FileLog.e("tmessages", e);
|
FileLog.e("tmessages", e);
|
||||||
|
@ -239,7 +251,7 @@ public class ContactsController {
|
||||||
public void deleteAllAppAccounts() {
|
public void deleteAllAppAccounts() {
|
||||||
try {
|
try {
|
||||||
AccountManager am = AccountManager.get(ApplicationLoader.applicationContext);
|
AccountManager am = AccountManager.get(ApplicationLoader.applicationContext);
|
||||||
Account[] accounts = am.getAccountsByType("org.telegram.account");
|
Account[] accounts = am.getAccountsByType("org.telegram.messenger");
|
||||||
for (Account c : accounts) {
|
for (Account c : accounts) {
|
||||||
am.removeAccount(c, null, null);
|
am.removeAccount(c, null, null);
|
||||||
}
|
}
|
||||||
|
@ -1246,7 +1258,7 @@ public class ContactsController {
|
||||||
private void performWriteContactsToPhoneBook() {
|
private void performWriteContactsToPhoneBook() {
|
||||||
final ArrayList<TLRPC.TL_contact> contactsArray = new ArrayList<>();
|
final ArrayList<TLRPC.TL_contact> contactsArray = new ArrayList<>();
|
||||||
contactsArray.addAll(contacts);
|
contactsArray.addAll(contacts);
|
||||||
Utilities.photoBookQueue.postRunnable(new Runnable() {
|
Utilities.phoneBookQueue.postRunnable(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
performWriteContactsToPhoneBookInternal(contactsArray);
|
performWriteContactsToPhoneBookInternal(contactsArray);
|
||||||
|
@ -1303,7 +1315,7 @@ public class ContactsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final Integer uid : contactsTD) {
|
for (final Integer uid : contactsTD) {
|
||||||
Utilities.photoBookQueue.postRunnable(new Runnable() {
|
Utilities.phoneBookQueue.postRunnable(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
deleteContactFromPhoneBook(uid);
|
deleteContactFromPhoneBook(uid);
|
||||||
|
@ -1463,7 +1475,7 @@ public class ContactsController {
|
||||||
builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
|
builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
|
||||||
builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0);
|
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.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.DATA2, "Telegram Profile");
|
||||||
builder.withValue(ContactsContract.Data.DATA3, "+" + user.phone);
|
builder.withValue(ContactsContract.Data.DATA3, "+" + user.phone);
|
||||||
builder.withValue(ContactsContract.Data.DATA4, user.id);
|
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) {
|
public void addContact(TLRPC.User user) {
|
||||||
if (user == null || user.phone == null) {
|
if (user == null || user.phone == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -1533,7 +1561,7 @@ public class ContactsController {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
for (final TLRPC.User u : res.users) {
|
for (final TLRPC.User u : res.users) {
|
||||||
Utilities.photoBookQueue.postRunnable(new Runnable() {
|
Utilities.phoneBookQueue.postRunnable(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
addContactToPhoneBook(u, true);
|
addContactToPhoneBook(u, true);
|
||||||
|
@ -1546,7 +1574,7 @@ public class ContactsController {
|
||||||
MessagesStorage.getInstance().putContacts(arrayList, false);
|
MessagesStorage.getInstance().putContacts(arrayList, false);
|
||||||
|
|
||||||
if (u.phone != null && u.phone.length() > 0) {
|
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, "");
|
MessagesStorage.getInstance().applyPhoneBookUpdates(u.phone, "");
|
||||||
Contact contact = contactsBookSPhones.get(u.phone);
|
Contact contact = contactsBookSPhones.get(u.phone);
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
|
@ -1599,7 +1627,7 @@ public class ContactsController {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MessagesStorage.getInstance().deleteContacts(uids);
|
MessagesStorage.getInstance().deleteContacts(uids);
|
||||||
Utilities.photoBookQueue.postRunnable(new Runnable() {
|
Utilities.phoneBookQueue.postRunnable(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
for (TLRPC.User user : users) {
|
for (TLRPC.User user : users) {
|
||||||
|
@ -1610,7 +1638,7 @@ public class ContactsController {
|
||||||
|
|
||||||
for (TLRPC.User user : users) {
|
for (TLRPC.User user : users) {
|
||||||
if (user.phone != null && user.phone.length() > 0) {
|
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, "");
|
MessagesStorage.getInstance().applyPhoneBookUpdates(user.phone, "");
|
||||||
Contact contact = contactsBookSPhones.get(user.phone);
|
Contact contact = contactsBookSPhones.get(user.phone);
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
|
@ -1772,22 +1800,37 @@ public class ContactsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatName(String firstName, String lastName) {
|
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) {
|
if (LocaleController.nameDisplayOrder == 1) {
|
||||||
result = firstName;
|
if (firstName != null && firstName.length() > 0) {
|
||||||
if (result == null || result.length() == 0) {
|
result.append(firstName);
|
||||||
result = lastName;
|
if (lastName != null && lastName.length() > 0) {
|
||||||
} else if (result.length() != 0 && lastName != null && lastName.length() != 0) {
|
result.append(" ");
|
||||||
result += " " + lastName;
|
result.append(lastName);
|
||||||
|
}
|
||||||
|
} else if (lastName != null && lastName.length() > 0) {
|
||||||
|
result.append(lastName);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result = lastName;
|
if (lastName != null && lastName.length() > 0) {
|
||||||
if (result == null || result.length() == 0) {
|
result.append(lastName);
|
||||||
result = firstName;
|
if (firstName != null && firstName.length() > 0) {
|
||||||
} else if (result.length() != 0 && firstName != null && firstName.length() != 0) {
|
result.append(" ");
|
||||||
result += " " + firstName;
|
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 {
|
public class Emoji {
|
||||||
private static HashMap<Long, DrawableInfo> rects = new HashMap<>();
|
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 boolean inited = false;
|
||||||
private static Paint placeholderPaint;
|
private static Paint placeholderPaint;
|
||||||
private static Bitmap emojiBmp[] = new Bitmap[5];
|
private static Bitmap emojiBmp[] = new Bitmap[5];
|
||||||
|
@ -193,19 +194,19 @@ public class Emoji {
|
||||||
static {
|
static {
|
||||||
int emojiFullSize;
|
int emojiFullSize;
|
||||||
if (AndroidUtilities.density <= 1.0f) {
|
if (AndroidUtilities.density <= 1.0f) {
|
||||||
emojiFullSize = 30;
|
emojiFullSize = 32;
|
||||||
} else if (AndroidUtilities.density <= 1.5f) {
|
} else if (AndroidUtilities.density <= 1.5f) {
|
||||||
emojiFullSize = 45;
|
emojiFullSize = 48;
|
||||||
} else if (AndroidUtilities.density <= 2.0f) {
|
} else if (AndroidUtilities.density <= 2.0f) {
|
||||||
emojiFullSize = 60;
|
emojiFullSize = 64;
|
||||||
} else {
|
} else {
|
||||||
emojiFullSize = 90;
|
emojiFullSize = 96;
|
||||||
}
|
}
|
||||||
drawImgSize = AndroidUtilities.dp(20);
|
drawImgSize = AndroidUtilities.dp(20);
|
||||||
if (AndroidUtilities.isTablet()) {
|
if (AndroidUtilities.isTablet()) {
|
||||||
bigImgSize = AndroidUtilities.dp(40);
|
bigImgSize = AndroidUtilities.dp(40);
|
||||||
} else {
|
} else {
|
||||||
bigImgSize = AndroidUtilities.dp(30);
|
bigImgSize = AndroidUtilities.dp(32);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int j = 1; j < data.length; j++) {
|
for (int j = 1; j < data.length; j++) {
|
||||||
|
@ -234,8 +235,26 @@ public class Emoji {
|
||||||
scale = 3.0f;
|
scale = 3.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
String imageName = String.format(Locale.US, "emoji%.01fx_%d.jpg", scale, page);
|
String imageName;
|
||||||
File imageFile = ApplicationLoader.applicationContext.getFileStreamPath(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()) {
|
if (!imageFile.exists()) {
|
||||||
InputStream is = ApplicationLoader.applicationContext.getAssets().open("emoji/" + imageName);
|
InputStream is = ApplicationLoader.applicationContext.getAssets().open("emoji/" + imageName);
|
||||||
Utilities.copyFile(is, imageFile);
|
Utilities.copyFile(is, imageFile);
|
||||||
|
@ -253,7 +272,7 @@ public class Emoji {
|
||||||
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||||
Utilities.loadBitmap(imageFile.getAbsolutePath(), bitmap, imageResize, width, height, stride);
|
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);
|
imageFile = ApplicationLoader.applicationContext.getFileStreamPath(imageName);
|
||||||
if (!imageFile.exists()) {
|
if (!imageFile.exists()) {
|
||||||
InputStream is = ApplicationLoader.applicationContext.getAssets().open("emoji/" + imageName);
|
InputStream is = ApplicationLoader.applicationContext.getAssets().open("emoji/" + imageName);
|
||||||
|
|
|
@ -70,6 +70,8 @@ public class ImageLoader {
|
||||||
private DispatchQueue recycleQueue = new DispatchQueue("recycleQueue");
|
private DispatchQueue recycleQueue = new DispatchQueue("recycleQueue");
|
||||||
private ConcurrentHashMap<String, Float> fileProgresses = new ConcurrentHashMap<>();
|
private ConcurrentHashMap<String, Float> fileProgresses = new ConcurrentHashMap<>();
|
||||||
private HashMap<String, ThumbGenerateTask> thumbGenerateTasks = new HashMap<>();
|
private HashMap<String, ThumbGenerateTask> thumbGenerateTasks = new HashMap<>();
|
||||||
|
private static byte[] bytes;
|
||||||
|
private static byte[] bytesThumb;
|
||||||
private int currentHttpTasksCount = 0;
|
private int currentHttpTasksCount = 0;
|
||||||
|
|
||||||
private LinkedList<HttpFileTask> httpFileLoadTasks = new LinkedList<>();
|
private LinkedList<HttpFileTask> httpFileLoadTasks = new LinkedList<>();
|
||||||
|
@ -507,6 +509,7 @@ public class ImageLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
Long mediaId = null;
|
Long mediaId = null;
|
||||||
|
boolean mediaIsVideo = false;
|
||||||
Bitmap image = null;
|
Bitmap image = null;
|
||||||
File cacheFileFinal = cacheImage.finalFilePath;
|
File cacheFileFinal = cacheImage.finalFilePath;
|
||||||
boolean canDeleteFile = true;
|
boolean canDeleteFile = true;
|
||||||
|
@ -537,18 +540,35 @@ public class ImageLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (image == null) {
|
BitmapFactory.Options opts = new BitmapFactory.Options();
|
||||||
if (isWebp) {
|
opts.inSampleSize = 1;
|
||||||
RandomAccessFile file = new RandomAccessFile(cacheFileFinal, "r");
|
|
||||||
ByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, cacheFileFinal.length());
|
if (!isWebp && Build.VERSION.SDK_INT > 10 && Build.VERSION.SDK_INT < 21) {
|
||||||
image = Utilities.loadWebpImage(buffer, buffer.limit(), null);
|
opts.inPurgeable = true;
|
||||||
file.close();
|
}
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
FileInputStream is = new FileInputStream(cacheFileFinal);
|
FileInputStream is = new FileInputStream(cacheFileFinal);
|
||||||
image = BitmapFactory.decodeStream(is, null, null);
|
image = BitmapFactory.decodeStream(is, null, opts);
|
||||||
is.close();
|
is.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
if (canDeleteFile && (cacheFileFinal.length() == 0 || cacheImage.filter == null)) {
|
if (canDeleteFile && (cacheFileFinal.length() == 0 || cacheImage.filter == null)) {
|
||||||
cacheFileFinal.delete();
|
cacheFileFinal.delete();
|
||||||
|
@ -556,15 +576,18 @@ public class ImageLoader {
|
||||||
} else {
|
} else {
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
if (blurType == 1) {
|
if (blurType == 1) {
|
||||||
Utilities.blurBitmap(image, 3);
|
Utilities.blurBitmap(image, 3, opts.inPurgeable ? 0 : 1);
|
||||||
} else if (blurType == 2) {
|
} else if (blurType == 2) {
|
||||||
Utilities.blurBitmap(image, 1);
|
Utilities.blurBitmap(image, 1, opts.inPurgeable ? 0 : 1);
|
||||||
} else if (blurType == 3) {
|
} else if (blurType == 3) {
|
||||||
Utilities.blurBitmap(image, 7);
|
Utilities.blurBitmap(image, 7, opts.inPurgeable ? 0 : 1);
|
||||||
Utilities.blurBitmap(image, 7);
|
Utilities.blurBitmap(image, 7, opts.inPurgeable ? 0 : 1);
|
||||||
Utilities.blurBitmap(image, 7);
|
Utilities.blurBitmap(image, 7, opts.inPurgeable ? 0 : 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (blurType == 0 && opts.inPurgeable) {
|
||||||
|
Utilities.pinBitmap(image);
|
||||||
|
}
|
||||||
if (runtimeHack != null) {
|
if (runtimeHack != null) {
|
||||||
runtimeHack.trackFree(image.getRowBytes() * image.getHeight());
|
runtimeHack.trackFree(image.getRowBytes() * image.getHeight());
|
||||||
}
|
}
|
||||||
|
@ -579,6 +602,14 @@ public class ImageLoader {
|
||||||
int idx = cacheImage.httpUrl.indexOf(":", 8);
|
int idx = cacheImage.httpUrl.indexOf(":", 8);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
mediaId = Long.parseLong(cacheImage.httpUrl.substring(8, idx));
|
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;
|
canDeleteFile = false;
|
||||||
} else if (!cacheImage.httpUrl.startsWith("http")) {
|
} else if (!cacheImage.httpUrl.startsWith("http")) {
|
||||||
|
@ -604,35 +635,44 @@ public class ImageLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
BitmapFactory.Options opts = new BitmapFactory.Options();
|
BitmapFactory.Options opts = new BitmapFactory.Options();
|
||||||
|
opts.inSampleSize = 1;
|
||||||
|
|
||||||
float w_filter = 0;
|
float w_filter = 0;
|
||||||
float h_filter = 0;
|
float h_filter = 0;
|
||||||
boolean blur = false;
|
boolean blur = false;
|
||||||
if (cacheImage.filter != null) {
|
if (cacheImage.filter != null) {
|
||||||
String args[] = cacheImage.filter.split("_");
|
String args[] = cacheImage.filter.split("_");
|
||||||
w_filter = Float.parseFloat(args[0]) * AndroidUtilities.density;
|
if (args.length >= 2) {
|
||||||
h_filter = Float.parseFloat(args[1]) * AndroidUtilities.density;
|
w_filter = Float.parseFloat(args[0]) * AndroidUtilities.density;
|
||||||
if (args.length > 2) {
|
h_filter = Float.parseFloat(args[1]) * AndroidUtilities.density;
|
||||||
|
}
|
||||||
|
if (cacheImage.filter.contains("b")) {
|
||||||
blur = true;
|
blur = true;
|
||||||
}
|
}
|
||||||
opts.inJustDecodeBounds = true;
|
if (w_filter != 0 && h_filter != 0) {
|
||||||
|
opts.inJustDecodeBounds = true;
|
||||||
|
|
||||||
if (mediaId != null) {
|
if (mediaId != null) {
|
||||||
MediaStore.Images.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, opts);
|
if (mediaIsVideo) {
|
||||||
} else {
|
MediaStore.Video.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Video.Thumbnails.MINI_KIND, opts);
|
||||||
FileInputStream is = new FileInputStream(cacheFileFinal);
|
} else {
|
||||||
image = BitmapFactory.decodeStream(is, null, opts);
|
MediaStore.Images.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, opts);
|
||||||
is.close();
|
}
|
||||||
}
|
} else {
|
||||||
|
FileInputStream is = new FileInputStream(cacheFileFinal);
|
||||||
|
image = BitmapFactory.decodeStream(is, null, opts);
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
|
||||||
float photoW = opts.outWidth;
|
float photoW = opts.outWidth;
|
||||||
float photoH = opts.outHeight;
|
float photoH = opts.outHeight;
|
||||||
float scaleFactor = Math.max(photoW / w_filter, photoH / h_filter);
|
float scaleFactor = Math.max(photoW / w_filter, photoH / h_filter);
|
||||||
if (scaleFactor < 1) {
|
if (scaleFactor < 1) {
|
||||||
scaleFactor = 1;
|
scaleFactor = 1;
|
||||||
|
}
|
||||||
|
opts.inJustDecodeBounds = false;
|
||||||
|
opts.inSampleSize = (int) scaleFactor;
|
||||||
}
|
}
|
||||||
opts.inJustDecodeBounds = false;
|
|
||||||
opts.inSampleSize = (int)scaleFactor;
|
|
||||||
}
|
}
|
||||||
synchronized (sync) {
|
synchronized (sync) {
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
|
@ -645,13 +685,17 @@ public class ImageLoader {
|
||||||
} else {
|
} else {
|
||||||
opts.inPreferredConfig = Bitmap.Config.RGB_565;
|
opts.inPreferredConfig = Bitmap.Config.RGB_565;
|
||||||
}
|
}
|
||||||
//if (Build.VERSION.SDK_INT < 21) {
|
if (!isWebp && Build.VERSION.SDK_INT > 10 && Build.VERSION.SDK_INT < 21) {
|
||||||
// opts.inPurgeable = true;
|
opts.inPurgeable = true;
|
||||||
//}
|
}
|
||||||
|
|
||||||
opts.inDither = false;
|
opts.inDither = false;
|
||||||
if (mediaId != null) {
|
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 (image == null) {
|
||||||
if (isWebp) {
|
if (isWebp) {
|
||||||
|
@ -660,9 +704,20 @@ public class ImageLoader {
|
||||||
image = Utilities.loadWebpImage(buffer, buffer.limit(), null);
|
image = Utilities.loadWebpImage(buffer, buffer.limit(), null);
|
||||||
file.close();
|
file.close();
|
||||||
} else {
|
} else {
|
||||||
FileInputStream is = new FileInputStream(cacheFileFinal);
|
if (opts.inPurgeable) {
|
||||||
image = BitmapFactory.decodeStream(is, null, opts);
|
RandomAccessFile f = new RandomAccessFile(cacheFileFinal, "r");
|
||||||
is.close();
|
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) {
|
if (image == null) {
|
||||||
|
@ -670,12 +725,13 @@ public class ImageLoader {
|
||||||
cacheFileFinal.delete();
|
cacheFileFinal.delete();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
boolean blured = false;
|
||||||
if (cacheImage.filter != null) {
|
if (cacheImage.filter != null) {
|
||||||
float bitmapW = image.getWidth();
|
float bitmapW = image.getWidth();
|
||||||
float bitmapH = image.getHeight();
|
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;
|
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) {
|
if (image != scaledBitmap) {
|
||||||
image.recycle();
|
image.recycle();
|
||||||
callGC();
|
callGC();
|
||||||
|
@ -683,9 +739,13 @@ public class ImageLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (image != null && blur && bitmapH < 100 && bitmapW < 100) {
|
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) {
|
if (runtimeHack != null) {
|
||||||
runtimeHack.trackFree(image.getRowBytes() * image.getHeight());
|
runtimeHack.trackFree(image.getRowBytes() * image.getHeight());
|
||||||
}
|
}
|
||||||
|
@ -753,7 +813,7 @@ public class ImageLoader {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Object res = trackAllocation.invoke(runtime, size);
|
Object res = trackAllocation.invoke(runtime, size);
|
||||||
return (res instanceof Boolean) ? (Boolean)res : true;
|
return (res instanceof Boolean) ? (Boolean) res : true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -765,7 +825,7 @@ public class ImageLoader {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Object res = trackFree.invoke(runtime, size);
|
Object res = trackFree.invoke(runtime, size);
|
||||||
return (res instanceof Boolean) ? (Boolean)res : true;
|
return (res instanceof Boolean) ? (Boolean) res : true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -778,8 +838,8 @@ public class ImageLoader {
|
||||||
Method getRt = cl.getMethod("getRuntime", new Class[0]);
|
Method getRt = cl.getMethod("getRuntime", new Class[0]);
|
||||||
Object[] objects = new Object[0];
|
Object[] objects = new Object[0];
|
||||||
runtime = getRt.invoke(null, objects);
|
runtime = getRt.invoke(null, objects);
|
||||||
trackAllocation = cl.getMethod("trackExternalAllocation", new Class[] {long.class});
|
trackAllocation = cl.getMethod("trackExternalAllocation", new Class[]{long.class});
|
||||||
trackFree = cl.getMethod("trackExternalFree", new Class[] {long.class});
|
trackFree = cl.getMethod("trackExternalFree", new Class[]{long.class});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
FileLog.e("tmessages", e);
|
FileLog.e("tmessages", e);
|
||||||
runtime = null;
|
runtime = null;
|
||||||
|
@ -872,7 +932,7 @@ public class ImageLoader {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
for (ImageReceiver imgView : finalImageReceiverArray) {
|
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;
|
private static volatile ImageLoader Instance = null;
|
||||||
|
|
||||||
public static ImageLoader getInstance() {
|
public static ImageLoader getInstance() {
|
||||||
ImageLoader localInstance = Instance;
|
ImageLoader localInstance = Instance;
|
||||||
if (localInstance == null) {
|
if (localInstance == null) {
|
||||||
|
@ -915,12 +976,13 @@ public class ImageLoader {
|
||||||
@Override
|
@Override
|
||||||
protected int sizeOf(String key, BitmapDrawable bitmap) {
|
protected int sizeOf(String key, BitmapDrawable bitmap) {
|
||||||
Bitmap b = bitmap.getBitmap();
|
Bitmap b = bitmap.getBitmap();
|
||||||
if(Build.VERSION.SDK_INT < 12) {
|
if (Build.VERSION.SDK_INT < 12) {
|
||||||
return b.getRowBytes() * b.getHeight();
|
return b.getRowBytes() * b.getHeight();
|
||||||
} else {
|
} else {
|
||||||
return b.getByteCount();
|
return b.getByteCount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void entryRemoved(boolean evicted, String key, final BitmapDrawable oldBitmap, BitmapDrawable newBitmap) {
|
protected void entryRemoved(boolean evicted, String key, final BitmapDrawable oldBitmap, BitmapDrawable newBitmap) {
|
||||||
if (ignoreRemoval != null && key != null && ignoreRemoval.equals(key)) {
|
if (ignoreRemoval != null && key != null && ignoreRemoval.equals(key)) {
|
||||||
|
@ -1193,7 +1255,7 @@ public class ImageLoader {
|
||||||
recycleQueue.postRunnable(new Runnable() {
|
recycleQueue.postRunnable(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
System.gc();
|
//System.gc();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1296,17 +1358,21 @@ public class ImageLoader {
|
||||||
return memCache.get(key);
|
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() {
|
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
ArrayList<String> arr = memCache.getFilterKeys(oldKey);
|
ArrayList<String> arr = memCache.getFilterKeys(oldKey);
|
||||||
if (arr != null) {
|
if (arr != null) {
|
||||||
for (String filter : arr) {
|
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 {
|
} else {
|
||||||
performReplace(oldKey, newKey);
|
performReplace(oldKey, newKey);
|
||||||
|
NotificationCenter.getInstance().postNotificationName(NotificationCenter.didReplacedPhotoInMemCache, oldKey, newKey, newLocation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1384,6 +1450,11 @@ public class ImageLoader {
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
cacheFile = new File(httpLocation.substring(idx + 1));
|
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 {
|
} else {
|
||||||
cacheFile = new File(httpLocation);
|
cacheFile = new File(httpLocation);
|
||||||
}
|
}
|
||||||
|
@ -1487,7 +1558,7 @@ public class ImageLoader {
|
||||||
if (bitmapDrawable != null) {
|
if (bitmapDrawable != null) {
|
||||||
cancelLoadingForImageReceiver(imageReceiver, 0);
|
cancelLoadingForImageReceiver(imageReceiver, 0);
|
||||||
if (!imageReceiver.isForcePreview()) {
|
if (!imageReceiver.isForcePreview()) {
|
||||||
imageReceiver.setImageBitmapByKey(bitmapDrawable, key, false);
|
imageReceiver.setImageBitmapByKey(bitmapDrawable, key, false, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1497,7 +1568,7 @@ public class ImageLoader {
|
||||||
if (thumbKey != null) {
|
if (thumbKey != null) {
|
||||||
BitmapDrawable bitmapDrawable = memCache.get(thumbKey);
|
BitmapDrawable bitmapDrawable = memCache.get(thumbKey);
|
||||||
if (bitmapDrawable != null) {
|
if (bitmapDrawable != null) {
|
||||||
imageReceiver.setImageBitmapByKey(bitmapDrawable, thumbKey, true);
|
imageReceiver.setImageBitmapByKey(bitmapDrawable, thumbKey, true, true);
|
||||||
cancelLoadingForImageReceiver(imageReceiver, 1);
|
cancelLoadingForImageReceiver(imageReceiver, 1);
|
||||||
thumbSet = true;
|
thumbSet = true;
|
||||||
}
|
}
|
||||||
|
@ -1784,7 +1855,7 @@ public class ImageLoader {
|
||||||
scaleFactor = 1;
|
scaleFactor = 1;
|
||||||
}
|
}
|
||||||
bmOptions.inJustDecodeBounds = false;
|
bmOptions.inJustDecodeBounds = false;
|
||||||
bmOptions.inSampleSize = (int)scaleFactor;
|
bmOptions.inSampleSize = (int) scaleFactor;
|
||||||
|
|
||||||
String exifPath = null;
|
String exifPath = null;
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
|
@ -1900,7 +1971,7 @@ public class ImageLoader {
|
||||||
size.size = size.bytes.length;
|
size.size = size.bytes.length;
|
||||||
stream2.close();
|
stream2.close();
|
||||||
} else {
|
} else {
|
||||||
size.size = (int)stream.getChannel().size();
|
size.size = (int) stream.getChannel().size();
|
||||||
}
|
}
|
||||||
stream.close();
|
stream.close();
|
||||||
if (scaledBitmap != bitmap) {
|
if (scaledBitmap != bitmap) {
|
||||||
|
@ -1926,11 +1997,17 @@ public class ImageLoader {
|
||||||
boolean scaleAnyway = false;
|
boolean scaleAnyway = false;
|
||||||
float scaleFactor = Math.max(photoW / maxWidth, photoH / maxHeight);
|
float scaleFactor = Math.max(photoW / maxWidth, photoH / maxHeight);
|
||||||
if (minWidth != 0 && minHeight != 0 && (photoW < minWidth || photoH < minHeight)) {
|
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;
|
scaleAnyway = true;
|
||||||
}
|
}
|
||||||
int w = (int)(photoW / scaleFactor);
|
int w = (int) (photoW / scaleFactor);
|
||||||
int h = (int)(photoH / scaleFactor);
|
int h = (int) (photoH / scaleFactor);
|
||||||
if (h == 0 || w == 0) {
|
if (h == 0 || w == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ package org.telegram.android;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapShader;
|
import android.graphics.BitmapShader;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.ColorFilter;
|
||||||
import android.graphics.Matrix;
|
import android.graphics.Matrix;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
|
@ -33,12 +34,25 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
||||||
void didSetImage(ImageReceiver imageReceiver, boolean set, boolean thumb);
|
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 View parentView;
|
||||||
private Integer tag;
|
private Integer tag;
|
||||||
private Integer thumbTag;
|
private Integer thumbTag;
|
||||||
private MessageObject parentMessageObject;
|
private MessageObject parentMessageObject;
|
||||||
private boolean canceledLoading;
|
private boolean canceledLoading;
|
||||||
|
|
||||||
|
private SetImageBackup setImageBackup;
|
||||||
|
|
||||||
private TLObject currentImageLocation;
|
private TLObject currentImageLocation;
|
||||||
private String currentKey;
|
private String currentKey;
|
||||||
private String currentThumbKey;
|
private String currentThumbKey;
|
||||||
|
@ -66,11 +80,16 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
||||||
private RectF roundRect;
|
private RectF roundRect;
|
||||||
private RectF bitmapRect;
|
private RectF bitmapRect;
|
||||||
private Matrix shaderMatrix;
|
private Matrix shaderMatrix;
|
||||||
private int alpha = 255;
|
private float overrideAlpha = 1.0f;
|
||||||
private boolean isPressed;
|
private boolean isPressed;
|
||||||
private int orientation;
|
private int orientation;
|
||||||
private boolean centerRotation;
|
private boolean centerRotation;
|
||||||
private ImageReceiverDelegate delegate;
|
private ImageReceiverDelegate delegate;
|
||||||
|
private float currentAlpha;
|
||||||
|
private long lastUpdateAlphaTime;
|
||||||
|
private byte crossfadeAlpha = 1;
|
||||||
|
private boolean crossfadeWithThumb;
|
||||||
|
private ColorFilter colorFilter;
|
||||||
|
|
||||||
public ImageReceiver() {
|
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) {
|
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)
|
if ((fileLocation == null && httpUrl == null && thumbLocation == null)
|
||||||
|| (fileLocation != null && !(fileLocation instanceof TLRPC.TL_fileLocation)
|
|| (fileLocation != null && !(fileLocation instanceof TLRPC.TL_fileLocation)
|
||||||
&& !(fileLocation instanceof TLRPC.TL_fileEncryptedLocation)
|
&& !(fileLocation instanceof TLRPC.TL_fileEncryptedLocation)
|
||||||
|
@ -120,13 +146,14 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
||||||
currentFilter = null;
|
currentFilter = null;
|
||||||
currentCacheOnly = false;
|
currentCacheOnly = false;
|
||||||
staticThumb = thumb;
|
staticThumb = thumb;
|
||||||
|
currentAlpha = 1;
|
||||||
currentThumbLocation = null;
|
currentThumbLocation = null;
|
||||||
currentSize = 0;
|
currentSize = 0;
|
||||||
currentImage = null;
|
currentImage = null;
|
||||||
bitmapShader = null;
|
bitmapShader = null;
|
||||||
ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0);
|
ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0);
|
||||||
if (parentView != null) {
|
if (parentView != null) {
|
||||||
parentView.invalidate();
|
parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||||
}
|
}
|
||||||
if (delegate != null) {
|
if (delegate != null) {
|
||||||
delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null);
|
delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null);
|
||||||
|
@ -134,6 +161,8 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (!(thumbLocation instanceof TLRPC.TL_fileLocation)) {
|
if (!(thumbLocation instanceof TLRPC.TL_fileLocation)) {
|
||||||
thumbLocation = null;
|
thumbLocation = null;
|
||||||
}
|
}
|
||||||
|
@ -187,6 +216,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
||||||
currentThumbLocation = thumbLocation;
|
currentThumbLocation = thumbLocation;
|
||||||
staticThumb = thumb;
|
staticThumb = thumb;
|
||||||
bitmapShader = null;
|
bitmapShader = null;
|
||||||
|
currentAlpha = 1.0f;
|
||||||
|
|
||||||
if (delegate != null) {
|
if (delegate != null) {
|
||||||
delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == 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);
|
ImageLoader.getInstance().loadImageForImageReceiver(this);
|
||||||
if (parentView != null) {
|
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) {
|
public void setDelegate(ImageReceiverDelegate delegate) {
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
}
|
}
|
||||||
|
@ -239,11 +273,18 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
||||||
currentSize = 0;
|
currentSize = 0;
|
||||||
currentCacheOnly = false;
|
currentCacheOnly = false;
|
||||||
bitmapShader = null;
|
bitmapShader = null;
|
||||||
|
if (setImageBackup != null) {
|
||||||
|
setImageBackup.fileLocation = null;
|
||||||
|
setImageBackup.httpUrl = null;
|
||||||
|
setImageBackup.thumbLocation = null;
|
||||||
|
setImageBackup.thumb = null;
|
||||||
|
}
|
||||||
|
currentAlpha = 1;
|
||||||
if (delegate != null) {
|
if (delegate != null) {
|
||||||
delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null);
|
delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null);
|
||||||
}
|
}
|
||||||
if (parentView != 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) {
|
public boolean draw(Canvas canvas) {
|
||||||
try {
|
try {
|
||||||
BitmapDrawable bitmapDrawable = null;
|
BitmapDrawable bitmapDrawable = null;
|
||||||
|
@ -267,161 +514,34 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
||||||
bitmapDrawable = currentThumb;
|
bitmapDrawable = currentThumb;
|
||||||
}
|
}
|
||||||
if (bitmapDrawable != null) {
|
if (bitmapDrawable != null) {
|
||||||
Paint paint = bitmapDrawable.getPaint();
|
if (crossfadeAlpha != 0) {
|
||||||
boolean hasFilter = paint != null && paint.getColorFilter() != null;
|
if (crossfadeWithThumb && currentAlpha != 1.0f) {
|
||||||
if (hasFilter && !isPressed) {
|
Drawable thumbDrawable = null;
|
||||||
bitmapDrawable.setColorFilter(null);
|
if (bitmapDrawable == currentImage) {
|
||||||
hasFilter = false;
|
if (staticThumb != null) {
|
||||||
} else if (!hasFilter && isPressed) {
|
thumbDrawable = staticThumb;
|
||||||
bitmapDrawable.setColorFilter(new PorterDuffColorFilter(0xffdddddd, PorterDuff.Mode.MULTIPLY));
|
} else if (currentThumb != null) {
|
||||||
hasFilter = true;
|
thumbDrawable = currentThumb;
|
||||||
}
|
}
|
||||||
if (bitmapShader != null) {
|
} else if (bitmapDrawable == currentThumb) {
|
||||||
drawRegion.set(imageX, imageY, imageX + imageW, imageY + imageH);
|
if (staticThumb != null) {
|
||||||
if (isVisible) {
|
thumbDrawable = staticThumb;
|
||||||
roundRect.set(drawRegion);
|
}
|
||||||
shaderMatrix.reset();
|
}
|
||||||
shaderMatrix.setRectToRect(bitmapRect, roundRect, Matrix.ScaleToFit.FILL);
|
if (thumbDrawable != null) {
|
||||||
bitmapShader.setLocalMatrix(shaderMatrix);
|
drawDrawable(canvas, thumbDrawable, (int) (overrideAlpha * 255));
|
||||||
canvas.drawRoundRect(roundRect, roundRadius, roundRadius, roundPaint);
|
}
|
||||||
}
|
}
|
||||||
|
drawDrawable(canvas, bitmapDrawable, (int) (overrideAlpha * currentAlpha * 255));
|
||||||
} else {
|
} else {
|
||||||
int bitmapW;
|
drawDrawable(canvas, bitmapDrawable, (int) (overrideAlpha * 255));
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkAlphaAnimation();
|
||||||
return true;
|
return true;
|
||||||
} else if (staticThumb != null) {
|
} else if (staticThumb != null) {
|
||||||
drawRegion.set(imageX, imageY, imageX + imageW, imageY + imageH);
|
drawDrawable(canvas, staticThumb, 255);
|
||||||
staticThumb.setBounds(drawRegion);
|
checkAlphaAnimation();
|
||||||
if (isVisible) {
|
|
||||||
try {
|
|
||||||
staticThumb.setAlpha(alpha);
|
|
||||||
staticThumb.draw(canvas);
|
|
||||||
} catch (Exception e) {
|
|
||||||
FileLog.e("tmessages", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -457,7 +577,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
||||||
}
|
}
|
||||||
isVisible = value;
|
isVisible = value;
|
||||||
if (invalidate && parentView != null) {
|
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) {
|
public void setAlpha(float value) {
|
||||||
alpha = (int)(value * 255.0f);
|
overrideAlpha = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCrossfadeAlpha(byte value) {
|
||||||
|
crossfadeAlpha = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasImage() {
|
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) {
|
if (bitmap == null || key == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -646,17 +770,38 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
||||||
roundPaint.setShader(bitmapShader);
|
roundPaint.setShader(bitmapShader);
|
||||||
bitmapRect.set(0, 0, object.getWidth(), object.getHeight());
|
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) {
|
if (parentView != null) {
|
||||||
parentView.invalidate();
|
parentView.invalidate(imageX, imageY, imageX + imageW, imageY + imageH);
|
||||||
}
|
}
|
||||||
} else if (currentThumb == null && (currentImage == null || forcePreview)) {
|
} else if (currentThumb == null && (currentImage == null || forcePreview)) {
|
||||||
if (currentThumbKey == null || !key.equals(currentThumbKey)) {
|
if (currentThumbKey == null || !key.equals(currentThumbKey)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ImageLoader.getInstance().incrementUseCount(currentThumbKey);
|
ImageLoader.getInstance().incrementUseCount(currentThumbKey);
|
||||||
|
|
||||||
currentThumb = bitmap;
|
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) {
|
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;
|
staticThumb = null;
|
||||||
}
|
}
|
||||||
if (parentView != 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) {
|
public static String getLocaleString(Locale locale) {
|
||||||
if (locale == null) {
|
if (locale == null) {
|
||||||
return "en";
|
return "en";
|
||||||
|
@ -607,11 +611,12 @@ public class LocaleController {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatString(String key, int res, Object... args) {
|
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 {
|
try {
|
||||||
|
String value = getInstance().localeValues.get(key);
|
||||||
|
if (value == null) {
|
||||||
|
value = ApplicationLoader.applicationContext.getString(res);
|
||||||
|
}
|
||||||
|
|
||||||
if (getInstance().currentLocale != null) {
|
if (getInstance().currentLocale != null) {
|
||||||
return String.format(getInstance().currentLocale, value, args);
|
return String.format(getInstance().currentLocale, value, args);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -117,17 +117,27 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
|
||||||
MediaStore.Images.Media.ORIENTATION
|
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 static class AlbumEntry {
|
||||||
public int bucketId;
|
public int bucketId;
|
||||||
public String bucketName;
|
public String bucketName;
|
||||||
public PhotoEntry coverPhoto;
|
public PhotoEntry coverPhoto;
|
||||||
public ArrayList<PhotoEntry> photos = new ArrayList<>();
|
public ArrayList<PhotoEntry> photos = new ArrayList<>();
|
||||||
public HashMap<Integer, PhotoEntry> photosByIds = new HashMap<>();
|
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.bucketId = bucketId;
|
||||||
this.bucketName = bucketName;
|
this.bucketName = bucketName;
|
||||||
this.coverPhoto = coverPhoto;
|
this.coverPhoto = coverPhoto;
|
||||||
|
this.isVideo = isVideo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPhoto(PhotoEntry photoEntry) {
|
public void addPhoto(PhotoEntry photoEntry) {
|
||||||
|
@ -144,13 +154,16 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
|
||||||
public int orientation;
|
public int orientation;
|
||||||
public String thumbPath;
|
public String thumbPath;
|
||||||
public String imagePath;
|
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.bucketId = bucketId;
|
||||||
this.imageId = imageId;
|
this.imageId = imageId;
|
||||||
this.dateTaken = dateTaken;
|
this.dateTaken = dateTaken;
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.orientation = orientation;
|
this.orientation = orientation;
|
||||||
|
this.isVideo = isVideo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,6 +180,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
|
||||||
public int date;
|
public int date;
|
||||||
public String thumbPath;
|
public String thumbPath;
|
||||||
public String imagePath;
|
public String imagePath;
|
||||||
|
public CharSequence caption;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final static String MIME_TYPE = "video/avc";
|
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 static int PROCESSOR_TYPE_TI = 5;
|
||||||
private final Object videoConvertSync = new Object();
|
private final Object videoConvertSync = new Object();
|
||||||
|
|
||||||
|
private HashMap<Long, Long> typingTimes = new HashMap<>();
|
||||||
|
|
||||||
private SensorManager sensorManager;
|
private SensorManager sensorManager;
|
||||||
private Sensor proximitySensor;
|
private Sensor proximitySensor;
|
||||||
private boolean ignoreProximity;
|
private boolean ignoreProximity;
|
||||||
|
@ -550,6 +566,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
|
||||||
videoDownloadQueue.clear();
|
videoDownloadQueue.clear();
|
||||||
downloadQueueKeys.clear();
|
downloadQueueKeys.clear();
|
||||||
videoConvertQueue.clear();
|
videoConvertQueue.clear();
|
||||||
|
typingTimes.clear();
|
||||||
cancelVideoConvert(null);
|
cancelVideoConvert(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -975,6 +992,29 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
|
||||||
}
|
}
|
||||||
listenerInProgress = false;
|
listenerInProgress = false;
|
||||||
processLaterArrays();
|
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) {
|
} else if (id == NotificationCenter.messagesDeleted) {
|
||||||
if (playingMessageObject != null) {
|
if (playingMessageObject != null) {
|
||||||
ArrayList<Integer> markAsDeletedMessages = (ArrayList<Integer>)args[0];
|
ArrayList<Integer> markAsDeletedMessages = (ArrayList<Integer>)args[0];
|
||||||
|
@ -1970,10 +2010,12 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
final ArrayList<AlbumEntry> albumsSorted = new ArrayList<>();
|
final ArrayList<AlbumEntry> albumsSorted = new ArrayList<>();
|
||||||
|
final ArrayList<AlbumEntry> videoAlbumsSorted = new ArrayList<>();
|
||||||
HashMap<Integer, AlbumEntry> albums = new HashMap<>();
|
HashMap<Integer, AlbumEntry> albums = new HashMap<>();
|
||||||
AlbumEntry allPhotosAlbum = null;
|
AlbumEntry allPhotosAlbum = null;
|
||||||
String cameraFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath() + "/" + "Camera/";
|
String cameraFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath() + "/" + "Camera/";
|
||||||
Integer cameraAlbumId = null;
|
Integer cameraAlbumId = null;
|
||||||
|
Integer cameraAlbumVideoId = null;
|
||||||
|
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
|
@ -1998,10 +2040,10 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
PhotoEntry photoEntry = new PhotoEntry(bucketId, imageId, dateTaken, path, orientation);
|
PhotoEntry photoEntry = new PhotoEntry(bucketId, imageId, dateTaken, path, orientation, false);
|
||||||
|
|
||||||
if (allPhotosAlbum == null) {
|
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);
|
albumsSorted.add(0, allPhotosAlbum);
|
||||||
}
|
}
|
||||||
if (allPhotosAlbum != null) {
|
if (allPhotosAlbum != null) {
|
||||||
|
@ -2010,7 +2052,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel
|
||||||
|
|
||||||
AlbumEntry albumEntry = albums.get(bucketId);
|
AlbumEntry albumEntry = albums.get(bucketId);
|
||||||
if (albumEntry == null) {
|
if (albumEntry == null) {
|
||||||
albumEntry = new AlbumEntry(bucketId, bucketName, photoEntry);
|
albumEntry = new AlbumEntry(bucketId, bucketName, photoEntry, false);
|
||||||
albums.put(bucketId, albumEntry);
|
albums.put(bucketId, albumEntry);
|
||||||
if (cameraAlbumId == null && cameraFolder != null && path != null && path.startsWith(cameraFolder)) {
|
if (cameraAlbumId == null && cameraFolder != null && path != null && path.startsWith(cameraFolder)) {
|
||||||
albumsSorted.add(0, albumEntry);
|
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 cameraAlbumIdFinal = cameraAlbumId;
|
||||||
|
final Integer cameraAlbumVideoIdFinal = cameraAlbumVideoId;
|
||||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
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 TLRPC.Message messageOwner;
|
||||||
public CharSequence messageText;
|
public CharSequence messageText;
|
||||||
public CharSequence linkDescription;
|
public CharSequence linkDescription;
|
||||||
|
public CharSequence caption;
|
||||||
public MessageObject replyMessageObject;
|
public MessageObject replyMessageObject;
|
||||||
public int type;
|
public int type;
|
||||||
public int contentType;
|
public int contentType;
|
||||||
|
@ -52,7 +53,7 @@ public class MessageObject {
|
||||||
public int audioProgressSec;
|
public int audioProgressSec;
|
||||||
public ArrayList<TLRPC.PhotoSize> photoThumbs;
|
public ArrayList<TLRPC.PhotoSize> photoThumbs;
|
||||||
|
|
||||||
private static TextPaint textPaint;
|
public static TextPaint textPaint;
|
||||||
public int lastLineWidth;
|
public int lastLineWidth;
|
||||||
public int textWidth;
|
public int textWidth;
|
||||||
public int textHeight;
|
public int textHeight;
|
||||||
|
@ -144,17 +145,35 @@ public class MessageObject {
|
||||||
whoUser = MessagesController.getInstance().getUser(message.action.user_id);
|
whoUser = MessagesController.getInstance().getUser(message.action.user_id);
|
||||||
}
|
}
|
||||||
if (whoUser != null && fromUser != null) {
|
if (whoUser != null && fromUser != null) {
|
||||||
if (isOut()) {
|
if (whoUser.id == fromUser.id) {
|
||||||
messageText = replaceWithLink(LocaleController.getString("ActionYouAddUser", R.string.ActionYouAddUser), "un2", whoUser);
|
if (isOut()) {
|
||||||
} else if (message.action.user_id == UserConfig.getClientUserId()) {
|
messageText = LocaleController.getString("ActionAddUserSelf", R.string.ActionAddUserSelf).replace("un1", LocaleController.getString("FromYou", R.string.FromYou));
|
||||||
messageText = replaceWithLink(LocaleController.getString("ActionAddUserYou", R.string.ActionAddUserYou), "un1", fromUser);
|
} else {
|
||||||
|
messageText = replaceWithLink(LocaleController.getString("ActionAddUserSelf", R.string.ActionAddUserSelf), "un1", fromUser);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
messageText = replaceWithLink(LocaleController.getString("ActionAddUser", R.string.ActionAddUser), "un2", whoUser);
|
if (isOut()) {
|
||||||
messageText = replaceWithLink(messageText, "un1", fromUser);
|
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 {
|
} else {
|
||||||
messageText = LocaleController.getString("ActionAddUser", R.string.ActionAddUser).replace("un2", "").replace("un1", "");
|
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) {
|
} else if (message.action instanceof TLRPC.TL_messageActionChatEditPhoto) {
|
||||||
if (isOut()) {
|
if (isOut()) {
|
||||||
messageText = LocaleController.getString("ActionYouChangedPhoto", R.string.ActionYouChangedPhoto);
|
messageText = LocaleController.getString("ActionYouChangedPhoto", R.string.ActionYouChangedPhoto);
|
||||||
|
@ -279,7 +298,7 @@ public class MessageObject {
|
||||||
messageText = LocaleController.getString("AttachPhoto", R.string.AttachPhoto);
|
messageText = LocaleController.getString("AttachPhoto", R.string.AttachPhoto);
|
||||||
} else if (message.media instanceof TLRPC.TL_messageMediaVideo) {
|
} else if (message.media instanceof TLRPC.TL_messageMediaVideo) {
|
||||||
messageText = LocaleController.getString("AttachVideo", R.string.AttachVideo);
|
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);
|
messageText = LocaleController.getString("AttachLocation", R.string.AttachLocation);
|
||||||
} else if (message.media instanceof TLRPC.TL_messageMediaContact) {
|
} else if (message.media instanceof TLRPC.TL_messageMediaContact) {
|
||||||
messageText = LocaleController.getString("AttachContact", R.string.AttachContact);
|
messageText = LocaleController.getString("AttachContact", R.string.AttachContact);
|
||||||
|
@ -314,7 +333,7 @@ public class MessageObject {
|
||||||
contentType = type = 0;
|
contentType = type = 0;
|
||||||
} else if (message.media instanceof TLRPC.TL_messageMediaPhoto) {
|
} else if (message.media instanceof TLRPC.TL_messageMediaPhoto) {
|
||||||
contentType = type = 1;
|
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;
|
contentType = 1;
|
||||||
type = 4;
|
type = 4;
|
||||||
} else if (message.media instanceof TLRPC.TL_messageMediaVideo) {
|
} else if (message.media instanceof TLRPC.TL_messageMediaVideo) {
|
||||||
|
@ -374,6 +393,7 @@ public class MessageObject {
|
||||||
monthKey = String.format("%d_%02d", dateYear, dateMonth);
|
monthKey = String.format("%d_%02d", dateYear, dateMonth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generateCaption();
|
||||||
if (generateLayout) {
|
if (generateLayout) {
|
||||||
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() {
|
private void generateLayout() {
|
||||||
if (type != 0 || messageOwner.to_id == null || messageText == null || messageText.length() == 0) {
|
if (type != 0 || messageOwner.to_id == null || messageText == null || messageText.length() == 0) {
|
||||||
return;
|
return;
|
||||||
|
@ -579,26 +634,19 @@ public class MessageObject {
|
||||||
|
|
||||||
if (messageText instanceof Spannable && containsUrls(messageText)) {
|
if (messageText instanceof Spannable && containsUrls(messageText)) {
|
||||||
if (messageText.length() < 100) {
|
if (messageText.length() < 100) {
|
||||||
Linkify.addLinks((Spannable) messageText, Linkify.WEB_URLS | Linkify.PHONE_NUMBERS);
|
try {
|
||||||
} else {
|
Linkify.addLinks((Spannable) messageText, Linkify.WEB_URLS | Linkify.PHONE_NUMBERS);
|
||||||
Linkify.addLinks((Spannable) messageText, Linkify.WEB_URLS);
|
} catch (Exception e) {
|
||||||
}
|
FileLog.e("tmessages", e);
|
||||||
|
}
|
||||||
try {
|
} else {
|
||||||
Pattern pattern = Pattern.compile("(^|\\s)@[a-zA-Z\\d_]{5,32}|(^|\\s)#[\\w\\.]+");
|
try {
|
||||||
Matcher matcher = pattern.matcher(messageText);
|
Linkify.addLinks((Spannable) messageText, Linkify.WEB_URLS);
|
||||||
while (matcher.find()) {
|
} catch (Exception e) {
|
||||||
int start = matcher.start();
|
FileLog.e("tmessages", e);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
FileLog.e("tmessages", e);
|
|
||||||
}
|
}
|
||||||
|
addUsernamesAndHashtags(messageText);
|
||||||
}
|
}
|
||||||
|
|
||||||
int maxWidth;
|
int maxWidth;
|
||||||
|
@ -763,10 +811,33 @@ public class MessageObject {
|
||||||
return (messageOwner.flags & TLRPC.MESSAGE_FLAG_UNREAD) != 0;
|
return (messageOwner.flags & TLRPC.MESSAGE_FLAG_UNREAD) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isContentUnread() {
|
||||||
|
return (messageOwner.flags & TLRPC.MESSAGE_FLAG_CONTENT_UNREAD) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
public void setIsRead() {
|
public void setIsRead() {
|
||||||
messageOwner.flags &= ~TLRPC.MESSAGE_FLAG_UNREAD;
|
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() {
|
public int getId() {
|
||||||
return messageOwner.id;
|
return messageOwner.id;
|
||||||
}
|
}
|
||||||
|
@ -782,18 +853,27 @@ public class MessageObject {
|
||||||
messageOwner.media instanceof TLRPC.TL_messageMediaVideo);
|
messageOwner.media instanceof TLRPC.TL_messageMediaVideo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setIsUnread(TLRPC.Message message, boolean unread) {
|
public static void setUnreadFlags(TLRPC.Message message, int flag) {
|
||||||
if (unread) {
|
if ((flag & 1) == 0) {
|
||||||
message.flags |= TLRPC.MESSAGE_FLAG_UNREAD;
|
message.flags |= TLRPC.MESSAGE_FLAG_UNREAD;
|
||||||
} else {
|
} else {
|
||||||
message.flags &= ~TLRPC.MESSAGE_FLAG_UNREAD;
|
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) {
|
public static boolean isUnread(TLRPC.Message message) {
|
||||||
return (message.flags & TLRPC.MESSAGE_FLAG_UNREAD) != 0;
|
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) {
|
public static boolean isOut(TLRPC.Message message) {
|
||||||
return (message.flags & TLRPC.MESSAGE_FLAG_OUT) != 0;
|
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.R;
|
||||||
import org.telegram.messenger.RPCRequest;
|
import org.telegram.messenger.RPCRequest;
|
||||||
import org.telegram.messenger.SerializedData;
|
import org.telegram.messenger.SerializedData;
|
||||||
import org.telegram.messenger.TLClassStore;
|
|
||||||
import org.telegram.messenger.TLObject;
|
import org.telegram.messenger.TLObject;
|
||||||
import org.telegram.messenger.TLRPC;
|
import org.telegram.messenger.TLRPC;
|
||||||
import org.telegram.messenger.UserConfig;
|
import org.telegram.messenger.UserConfig;
|
||||||
|
@ -34,7 +33,6 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Semaphore;
|
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<Integer, TLRPC.User> users = new ConcurrentHashMap<>(100, 1.0f, 2);
|
||||||
private ConcurrentHashMap<String, TLRPC.User> usersByUsernames = 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> dialogs = new ArrayList<>();
|
||||||
public ArrayList<TLRPC.TL_dialog> dialogsServerOnly = 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 ConcurrentHashMap<Long, TLRPC.TL_dialog> dialogs_dict = new ConcurrentHashMap<>(100, 1.0f, 2);
|
||||||
public HashMap<Integer, MessageObject> dialogMessage = new HashMap<>();
|
public HashMap<Integer, MessageObject> dialogMessage = new HashMap<>();
|
||||||
public ConcurrentHashMap<Long, ArrayList<PrintingUser>> printingUsers = new ConcurrentHashMap<>(20, 1.0f, 2);
|
public ConcurrentHashMap<Long, ArrayList<PrintingUser>> printingUsers = new ConcurrentHashMap<>(20, 1.0f, 2);
|
||||||
public HashMap<Long, CharSequence> printingStrings = new HashMap<>();
|
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);
|
public ConcurrentHashMap<Integer, Integer> onlinePrivacy = new ConcurrentHashMap<>(20, 1.0f, 2);
|
||||||
private int lastPrintingStringCount = 0;
|
private int lastPrintingStringCount = 0;
|
||||||
|
|
||||||
|
@ -124,6 +125,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
public static class PrintingUser {
|
public static class PrintingUser {
|
||||||
public long lastTime;
|
public long lastTime;
|
||||||
public int userId;
|
public int userId;
|
||||||
|
public TLRPC.SendMessageAction action;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static volatile MessagesController Instance = null;
|
private static volatile MessagesController Instance = null;
|
||||||
|
@ -163,9 +165,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
byte[] bytes = Base64.decode(disabledFeaturesString, Base64.DEFAULT);
|
byte[] bytes = Base64.decode(disabledFeaturesString, Base64.DEFAULT);
|
||||||
if (bytes != null) {
|
if (bytes != null) {
|
||||||
SerializedData data = new SerializedData(bytes);
|
SerializedData data = new SerializedData(bytes);
|
||||||
int count = data.readInt32();
|
int count = data.readInt32(false);
|
||||||
for (int a = 0; a < count; a++) {
|
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) {
|
if (feature != null && feature.feature != null && feature.description != null) {
|
||||||
disabledFeatures.add(feature);
|
disabledFeatures.add(feature);
|
||||||
}
|
}
|
||||||
|
@ -372,6 +374,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
SecretChatHelper.getInstance().cleanUp();
|
SecretChatHelper.getInstance().cleanUp();
|
||||||
|
|
||||||
dialogs_dict.clear();
|
dialogs_dict.clear();
|
||||||
|
exportedChats.clear();
|
||||||
dialogs.clear();
|
dialogs.clear();
|
||||||
dialogsServerOnly.clear();
|
dialogsServerOnly.clear();
|
||||||
users.clear();
|
users.clear();
|
||||||
|
@ -380,6 +383,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
dialogMessage.clear();
|
dialogMessage.clear();
|
||||||
printingUsers.clear();
|
printingUsers.clear();
|
||||||
printingStrings.clear();
|
printingStrings.clear();
|
||||||
|
printingStringsTypes.clear();
|
||||||
onlinePrivacy.clear();
|
onlinePrivacy.clear();
|
||||||
totalDialogsCount = 0;
|
totalDialogsCount = 0;
|
||||||
lastPrintingStringCount = 0;
|
lastPrintingStringCount = 0;
|
||||||
|
@ -463,6 +467,14 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
return chat;
|
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) {
|
public boolean putUser(TLRPC.User user, boolean fromCache) {
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -564,7 +576,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadFullChat(final int chat_id, final int classGuid) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
loadingFullChats.add(chat_id);
|
loadingFullChats.add(chat_id);
|
||||||
|
@ -580,12 +596,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
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);
|
loadedFullChats.add(chat_id);
|
||||||
|
|
||||||
putUsers(res.users, false);
|
putUsers(res.users, false);
|
||||||
putChats(res.chats, 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 {
|
} else {
|
||||||
|
@ -617,7 +634,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
loadingFullUsers.remove((Integer)user.id);
|
loadingFullUsers.remove((Integer) user.id);
|
||||||
loadedFullUsers.add(user.id);
|
loadedFullUsers.add(user.id);
|
||||||
String names = user.first_name + user.last_name + user.username;
|
String names = user.first_name + user.last_name + user.username;
|
||||||
TLRPC.TL_userFull userFull = (TLRPC.TL_userFull)response;
|
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, CharSequence> newPrintingStrings = new HashMap<>();
|
||||||
|
final HashMap<Long, Integer> newPrintingStringsTypes = new HashMap<>();
|
||||||
|
|
||||||
ArrayList<Long> keys = new ArrayList<>(printingUsers.keySet());
|
ArrayList<Long> keys = new ArrayList<>(printingUsers.keySet());
|
||||||
for (Long key : keys) {
|
for (HashMap.Entry<Long, ArrayList<PrintingUser>> entry : printingUsers.entrySet()) {
|
||||||
if (key > 0 || key.intValue() == 0) {
|
long key = entry.getKey();
|
||||||
newPrintingStrings.put(key, LocaleController.getString("Typing", R.string.Typing));
|
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 {
|
} else {
|
||||||
ArrayList<PrintingUser> arr = printingUsers.get(key);
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
String label = "";
|
String label = "";
|
||||||
for (PrintingUser pu : arr) {
|
for (PrintingUser pu : arr) {
|
||||||
|
@ -1292,11 +1366,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
if (label.length() != 0) {
|
if (label.length() != 0) {
|
||||||
label += ", ";
|
label += ", ";
|
||||||
}
|
}
|
||||||
if (user.first_name != null && user.first_name.length() > 0) {
|
label += getUserNameForTyping(user);
|
||||||
label += user.first_name;
|
|
||||||
} else if (user.last_name != null && user.last_name.length() > 0) {
|
|
||||||
label += user.last_name;
|
|
||||||
}
|
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
if (count == 2) {
|
if (count == 2) {
|
||||||
|
@ -1304,15 +1374,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (label.length() != 0) {
|
if (label.length() != 0) {
|
||||||
if (count > 1) {
|
if (arr.size() > 2) {
|
||||||
if (arr.size() > 2) {
|
newPrintingStrings.put(key, String.format("%s %s", label, LocaleController.formatPluralString("AndMoreTyping", 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)));
|
|
||||||
}
|
|
||||||
} else {
|
} 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
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
printingStrings = newPrintingStrings;
|
printingStrings = newPrintingStrings;
|
||||||
|
printingStringsTypes = newPrintingStringsTypes;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancelTyping(long dialog_id) {
|
public void cancelTyping(int action, long dialog_id) {
|
||||||
sendingTypings.remove(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) {
|
if (dialog_id == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (sendingTypings.get(dialog_id) != null) {
|
HashMap<Long, Boolean> typings = sendingTypings.get(action);
|
||||||
|
if (typings != null && typings.get(dialog_id) != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (typings == null) {
|
||||||
|
typings = new HashMap<>();
|
||||||
|
sendingTypings.put(action, typings);
|
||||||
|
}
|
||||||
int lower_part = (int)dialog_id;
|
int lower_part = (int)dialog_id;
|
||||||
int high_id = (int)(dialog_id >> 32);
|
int high_id = (int)(dialog_id >> 32);
|
||||||
if (lower_part != 0) {
|
if (lower_part != 0) {
|
||||||
|
@ -1364,21 +1440,41 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
req.action = new TLRPC.TL_sendMessageTypingAction();
|
if (action == 0) {
|
||||||
sendingTypings.put(dialog_id, true);
|
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() {
|
long reqId = ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
|
||||||
@Override
|
@Override
|
||||||
public void run(TLObject response, TLRPC.TL_error error) {
|
public void run(TLObject response, TLRPC.TL_error error) {
|
||||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
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);
|
}, true, RPCRequest.RPCRequestClassGeneric | RPCRequest.RPCRequestClassFailOnServerErrors);
|
||||||
ConnectionsManager.getInstance().bindRequestToGuid(reqId, classGuid);
|
if (classGuid != 0) {
|
||||||
|
ConnectionsManager.getInstance().bindRequestToGuid(reqId, classGuid);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (action != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
TLRPC.EncryptedChat chat = getEncryptedChat(high_id);
|
TLRPC.EncryptedChat chat = getEncryptedChat(high_id);
|
||||||
if (chat.auth_key != null && chat.auth_key.length > 1 && chat instanceof TLRPC.TL_encryptedChat) {
|
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();
|
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.chat_id = chat.id;
|
||||||
req.peer.access_hash = chat.access_hash;
|
req.peer.access_hash = chat.access_hash;
|
||||||
req.typing = true;
|
req.typing = true;
|
||||||
sendingTypings.put(dialog_id, true);
|
typings.put(dialog_id, true);
|
||||||
long reqId = ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
|
long reqId = ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() {
|
||||||
@Override
|
@Override
|
||||||
public void run(TLObject response, TLRPC.TL_error error) {
|
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);
|
}, 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();
|
currentDialog.unread_count = entry.getValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload);
|
NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_READ_DIALOG_MESSAGE);
|
||||||
NotificationsController.getInstance().processDialogsUpdateRead(dialogsToUpdate);
|
NotificationsController.getInstance().processDialogsUpdateRead(dialogsToUpdate);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1803,12 +1909,29 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
|
|
||||||
dialogsEndReached = (dialogsRes.dialogs.size() == 0 || dialogsRes.dialogs.size() != count) && !isCache;
|
dialogsEndReached = (dialogsRes.dialogs.size() == 0 || dialogsRes.dialogs.size() != count) && !isCache;
|
||||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload);
|
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) {
|
public void markMessageAsRead(final long dialog_id, final long random_id, int ttl) {
|
||||||
if (random_id == 0 || dialog_id == 0 || ttl <= 0) {
|
if (random_id == 0 || dialog_id == 0 || ttl <= 0) {
|
||||||
return;
|
return;
|
||||||
|
@ -2030,7 +2153,6 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
public void run() {
|
public void run() {
|
||||||
putUsers(updates.users, false);
|
putUsers(updates.users, false);
|
||||||
putChats(updates.chats, false);
|
putChats(updates.chats, false);
|
||||||
TLRPC.Chat chat = null;
|
|
||||||
if (updates.chats != null && !updates.chats.isEmpty()) {
|
if (updates.chats != null && !updates.chats.isEmpty()) {
|
||||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatDidCreated, updates.chats.get(0).id);
|
NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatDidCreated, updates.chats.get(0).id);
|
||||||
} else {
|
} 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) {
|
public void registerForPush(final String regid) {
|
||||||
if (regid == null || regid.length() == 0 || registeringForPush || UserConfig.getClientUserId() == 0) {
|
if (regid == null || regid.length() == 0 || registeringForPush || UserConfig.getClientUserId() == 0) {
|
||||||
return;
|
return;
|
||||||
|
@ -2226,7 +2377,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
req.token = regid;
|
req.token = regid;
|
||||||
req.app_sandbox = false;
|
req.app_sandbox = false;
|
||||||
try {
|
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;
|
req.device_model = Build.MANUFACTURER + Build.MODEL;
|
||||||
if (req.device_model == null) {
|
if (req.device_model == null) {
|
||||||
req.device_model = "Android unknown";
|
req.device_model = "Android unknown";
|
||||||
|
@ -2359,7 +2513,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
} else if (type == 2) {
|
} else if (type == 2) {
|
||||||
if (updates.qts <= MessagesStorage.lastQtsValue) {
|
if (updates.qts <= MessagesStorage.lastQtsValue) {
|
||||||
return 2;
|
return 2;
|
||||||
} else if (MessagesStorage.lastQtsValue + 1 == updates.qts) {
|
} else if (MessagesStorage.lastQtsValue + updates.updates.size() == updates.qts) {
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -2375,14 +2529,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
Collections.sort(updatesQueue, new Comparator<TLRPC.Updates>() {
|
Collections.sort(updatesQueue, new Comparator<TLRPC.Updates>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(TLRPC.Updates updates, TLRPC.Updates updates2) {
|
public int compare(TLRPC.Updates updates, TLRPC.Updates updates2) {
|
||||||
int seq1 = getUpdateSeq(updates);
|
return AndroidUtilities.compare(getUpdateSeq(updates), getUpdateSeq(updates2));
|
||||||
int seq2 = getUpdateSeq(updates2);
|
|
||||||
if (seq1 == seq2) {
|
|
||||||
return 0;
|
|
||||||
} else if (seq1 > seq2) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (type == 1) {
|
} else if (type == 1) {
|
||||||
|
@ -2390,12 +2537,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
Collections.sort(updatesQueue, new Comparator<TLRPC.Updates>() {
|
Collections.sort(updatesQueue, new Comparator<TLRPC.Updates>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(TLRPC.Updates updates, TLRPC.Updates updates2) {
|
public int compare(TLRPC.Updates updates, TLRPC.Updates updates2) {
|
||||||
if (updates.pts == updates2.pts) {
|
return AndroidUtilities.compare(updates.pts, updates2.pts);
|
||||||
return 0;
|
|
||||||
} else if (updates.pts > updates2.pts) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (type == 2) {
|
} else if (type == 2) {
|
||||||
|
@ -2403,12 +2545,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
Collections.sort(updatesQueue, new Comparator<TLRPC.Updates>() {
|
Collections.sort(updatesQueue, new Comparator<TLRPC.Updates>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(TLRPC.Updates updates, TLRPC.Updates updates2) {
|
public int compare(TLRPC.Updates updates, TLRPC.Updates updates2) {
|
||||||
if (updates.qts == updates2.qts) {
|
return AndroidUtilities.compare(updates.qts, updates2.qts);
|
||||||
return 0;
|
|
||||||
} else if (updates.qts > updates2.qts) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
public void processUpdates(final TLRPC.Updates updates, boolean fromQueue) {
|
||||||
boolean needGetDiff = false;
|
boolean needGetDiff = false;
|
||||||
boolean needReceivedQueue = false;
|
boolean needReceivedQueue = false;
|
||||||
|
@ -2719,11 +2867,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
arr.add(updates.update);
|
arr.add(updates.update);
|
||||||
processUpdateArray(arr, null, null);
|
processUpdateArray(arr, null, null);
|
||||||
} else if (updates instanceof TLRPC.TL_updateShortChatMessage || updates instanceof TLRPC.TL_updateShortMessage) {
|
} 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;
|
TLRPC.User user2 = null;
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
user = MessagesStorage.getInstance().getUserSync(updates.user_id);
|
user = MessagesStorage.getInstance().getUserSync(user_id);
|
||||||
putUser(user, true);
|
putUser(user, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2763,13 +2912,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
if ((updates.flags & TLRPC.MESSAGE_FLAG_OUT) != 0) {
|
if ((updates.flags & TLRPC.MESSAGE_FLAG_OUT) != 0) {
|
||||||
message.from_id = UserConfig.getClientUserId();
|
message.from_id = UserConfig.getClientUserId();
|
||||||
} else {
|
} else {
|
||||||
message.from_id = updates.user_id;
|
message.from_id = user_id;
|
||||||
}
|
}
|
||||||
message.to_id = new TLRPC.TL_peerUser();
|
message.to_id = new TLRPC.TL_peerUser();
|
||||||
message.to_id.user_id = updates.user_id;
|
message.to_id.user_id = user_id;
|
||||||
message.dialog_id = updates.user_id;
|
message.dialog_id = user_id;
|
||||||
} else {
|
} else {
|
||||||
message.from_id = updates.user_id;
|
message.from_id = user_id;
|
||||||
message.to_id = new TLRPC.TL_peerChat();
|
message.to_id = new TLRPC.TL_peerChat();
|
||||||
message.to_id.chat_id = updates.chat_id;
|
message.to_id.chat_id = updates.chat_id;
|
||||||
message.dialog_id = -updates.chat_id;
|
message.dialog_id = -updates.chat_id;
|
||||||
|
@ -2798,7 +2947,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
if (printUpdate) {
|
if (printUpdate) {
|
||||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_USER_PRINT);
|
NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_USER_PRINT);
|
||||||
}
|
}
|
||||||
updateInterfaceWithMessages(updates.user_id, objArr);
|
updateInterfaceWithMessages(user_id, objArr);
|
||||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload);
|
NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -2840,7 +2989,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
if (updatesStartWaitTimePts == 0) {
|
if (updatesStartWaitTimePts == 0) {
|
||||||
updatesStartWaitTimePts = System.currentTimeMillis();
|
updatesStartWaitTimePts = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
FileLog.e("tmessages", "add short message to queue");
|
FileLog.e("tmessages", "add to queue");
|
||||||
updatesQueuePts.add(updates);
|
updatesQueuePts.add(updates);
|
||||||
} else {
|
} else {
|
||||||
needGetDiff = true;
|
needGetDiff = true;
|
||||||
|
@ -2849,56 +2998,92 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
}
|
}
|
||||||
} else if (updates instanceof TLRPC.TL_updatesCombined || updates instanceof TLRPC.TL_updates) {
|
} else if (updates instanceof TLRPC.TL_updatesCombined || updates instanceof TLRPC.TL_updates) {
|
||||||
MessagesStorage.getInstance().putUsersAndChats(updates.users, updates.chats, true, true);
|
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++) {
|
for (int a = 0; a < updates.updates.size(); a++) {
|
||||||
TLRPC.Update update = updates.updates.get(a);
|
TLRPC.Update update = updates.updates.get(a);
|
||||||
if (update instanceof TLRPC.TL_updateNewMessage || update instanceof TLRPC.TL_updateReadMessages || update instanceof TLRPC.TL_updateReadHistoryInbox ||
|
if (getUpdateType(update) == 0) {
|
||||||
update instanceof TLRPC.TL_updateReadHistoryOutbox || update instanceof TLRPC.TL_updateDeleteMessages) {
|
|
||||||
TLRPC.TL_updates updatesNew = new TLRPC.TL_updates();
|
TLRPC.TL_updates updatesNew = new TLRPC.TL_updates();
|
||||||
updatesNew.updates.add(update);
|
updatesNew.updates.add(update);
|
||||||
updatesNew.pts = update.pts;
|
updatesNew.pts = update.pts;
|
||||||
updatesNew.pts_count = update.pts_count;
|
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)) {
|
if (!processUpdateArray(updatesNew.updates, updates.users, updates.chats)) {
|
||||||
FileLog.e("tmessages", "need get diff inner TL_updates, seq: " + MessagesStorage.lastSeqValue + " " + updates.seq);
|
FileLog.e("tmessages", "need get diff inner TL_updates, seq: " + MessagesStorage.lastSeqValue + " " + updates.seq);
|
||||||
needGetDiff = true;
|
needGetDiff = true;
|
||||||
} else {
|
} else {
|
||||||
MessagesStorage.lastPtsValue = update.pts;
|
MessagesStorage.lastPtsValue = updatesNew.pts;
|
||||||
}
|
}
|
||||||
} else if (MessagesStorage.lastPtsValue != update.pts) {
|
} else if (MessagesStorage.lastPtsValue != updatesNew.pts) {
|
||||||
FileLog.e("tmessages", update + " need get diff, pts: " + MessagesStorage.lastPtsValue + " " + update.pts + " count = " + update.pts_count);
|
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 (gettingDifference || updatesStartWaitTimePts == 0 || updatesStartWaitTimePts != 0 && updatesStartWaitTimePts + 1500 > System.currentTimeMillis()) {
|
||||||
if (updatesStartWaitTimePts == 0) {
|
if (updatesStartWaitTimePts == 0) {
|
||||||
updatesStartWaitTimePts = System.currentTimeMillis();
|
updatesStartWaitTimePts = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
FileLog.e("tmessages", "add short message to queue");
|
FileLog.e("tmessages", "add to queue");
|
||||||
updatesQueuePts.add(updatesNew);
|
updatesQueuePts.add(updatesNew);
|
||||||
} else {
|
} else {
|
||||||
needGetDiff = true;
|
needGetDiff = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (update instanceof TLRPC.TL_updateNewEncryptedMessage) {
|
} else if (getUpdateType(update) == 1) {
|
||||||
TLRPC.TL_updates updatesNew = new TLRPC.TL_updates();
|
TLRPC.TL_updates updatesNew = new TLRPC.TL_updates();
|
||||||
updatesNew.updates.add(update);
|
updatesNew.updates.add(update);
|
||||||
updatesNew.qts = update.qts;
|
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);
|
processUpdateArray(updatesNew.updates, updates.users, updates.chats);
|
||||||
MessagesStorage.lastQtsValue = update.qts;
|
MessagesStorage.lastQtsValue = updatesNew.qts;
|
||||||
needReceivedQueue = true;
|
needReceivedQueue = true;
|
||||||
} else if (MessagesStorage.lastPtsValue != update.qts) {
|
} else if (MessagesStorage.lastPtsValue != updatesNew.qts) {
|
||||||
FileLog.e("tmessages", update + " need get diff, qts: " + MessagesStorage.lastQtsValue + " " + update.qts);
|
FileLog.e("tmessages", update + " need get diff, qts: " + MessagesStorage.lastQtsValue + " " + updatesNew.qts);
|
||||||
if (gettingDifference || updatesStartWaitTimeQts == 0 || updatesStartWaitTimeQts != 0 && updatesStartWaitTimeQts + 1500 > System.currentTimeMillis()) {
|
if (gettingDifference || updatesStartWaitTimeQts == 0 || updatesStartWaitTimeQts != 0 && updatesStartWaitTimeQts + 1500 > System.currentTimeMillis()) {
|
||||||
if (updatesStartWaitTimeQts == 0) {
|
if (updatesStartWaitTimeQts == 0) {
|
||||||
updatesStartWaitTimeQts = System.currentTimeMillis();
|
updatesStartWaitTimeQts = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
FileLog.e("tmessages", "add short message to queue");
|
FileLog.e("tmessages", "add to queue");
|
||||||
updatesQueueQts.add(updatesNew);
|
updatesQueueQts.add(updatesNew);
|
||||||
} else {
|
} else {
|
||||||
needGetDiff = true;
|
needGetDiff = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
continue;
|
break;
|
||||||
}
|
}
|
||||||
updates.updates.remove(a);
|
updates.updates.remove(a);
|
||||||
a--;
|
a--;
|
||||||
|
@ -2996,6 +3181,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
final ArrayList<TLRPC.Message> messagesArr = new ArrayList<>();
|
final ArrayList<TLRPC.Message> messagesArr = new ArrayList<>();
|
||||||
final HashMap<Integer, Integer> markAsReadMessagesInbox = new HashMap<>();
|
final HashMap<Integer, Integer> markAsReadMessagesInbox = new HashMap<>();
|
||||||
final HashMap<Integer, Integer> markAsReadMessagesOutbox = new HashMap<>();
|
final HashMap<Integer, Integer> markAsReadMessagesOutbox = new HashMap<>();
|
||||||
|
final ArrayList<Integer> markAsReadMessages = new ArrayList<>();
|
||||||
final HashMap<Integer, Integer> markAsReadEncrypted = new HashMap<>();
|
final HashMap<Integer, Integer> markAsReadEncrypted = new HashMap<>();
|
||||||
final ArrayList<Integer> deletedMessages = new ArrayList<>();
|
final ArrayList<Integer> deletedMessages = new ArrayList<>();
|
||||||
boolean printChanged = false;
|
boolean printChanged = false;
|
||||||
|
@ -3078,8 +3264,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
if (!obj.isOut() && obj.isUnread()) {
|
if (!obj.isOut() && obj.isUnread()) {
|
||||||
pushMessages.add(obj);
|
pushMessages.add(obj);
|
||||||
}
|
}
|
||||||
} else if (update instanceof TLRPC.TL_updateReadMessages) {
|
} else if (update instanceof TLRPC.TL_updateReadMessagesContents) {
|
||||||
//markAsReadMessages.addAll(update.messages); disabled for now
|
markAsReadMessages.addAll(update.messages);
|
||||||
} else if (update instanceof TLRPC.TL_updateReadHistoryInbox) {
|
} else if (update instanceof TLRPC.TL_updateReadHistoryInbox) {
|
||||||
TLRPC.Peer peer = ((TLRPC.TL_updateReadHistoryInbox) update).peer;
|
TLRPC.Peer peer = ((TLRPC.TL_updateReadHistoryInbox) update).peer;
|
||||||
if (peer.chat_id != 0) {
|
if (peer.chat_id != 0) {
|
||||||
|
@ -3097,30 +3283,48 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
} else if (update instanceof TLRPC.TL_updateDeleteMessages) {
|
} else if (update instanceof TLRPC.TL_updateDeleteMessages) {
|
||||||
deletedMessages.addAll(update.messages);
|
deletedMessages.addAll(update.messages);
|
||||||
} else if (update instanceof TLRPC.TL_updateUserTyping || update instanceof TLRPC.TL_updateChatUserTyping) {
|
} 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;
|
long uid = -update.chat_id;
|
||||||
if (uid == 0) {
|
if (uid == 0) {
|
||||||
uid = update.user_id;
|
uid = update.user_id;
|
||||||
}
|
}
|
||||||
ArrayList<PrintingUser> arr = printingUsers.get(uid);
|
ArrayList<PrintingUser> arr = printingUsers.get(uid);
|
||||||
if (arr == null) {
|
if (update.action instanceof TLRPC.TL_sendMessageCancelAction) {
|
||||||
arr = new ArrayList<>();
|
if (arr != null) {
|
||||||
printingUsers.put(uid, arr);
|
for (int a = 0; a < arr.size(); a++) {
|
||||||
}
|
PrintingUser pu = arr.get(a);
|
||||||
boolean exist = false;
|
if (pu.userId == update.user_id) {
|
||||||
for (PrintingUser u : arr) {
|
arr.remove(a);
|
||||||
if (u.userId == update.user_id) {
|
printChanged = true;
|
||||||
exist = true;
|
break;
|
||||||
u.lastTime = currentTime;
|
}
|
||||||
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());
|
onlinePrivacy.put(update.user_id, ConnectionsManager.getInstance().getCurrentTime());
|
||||||
}
|
}
|
||||||
|
@ -3181,8 +3385,6 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
contactsIds.add(-update.user_id);
|
contactsIds.add(-update.user_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (update instanceof TLRPC.TL_updateActivation) {
|
|
||||||
//DEPRECATED
|
|
||||||
} else if (update instanceof TLRPC.TL_updateNewAuthorization) {
|
} else if (update instanceof TLRPC.TL_updateNewAuthorization) {
|
||||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -3247,6 +3449,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
if (u.userId == update.user_id) {
|
if (u.userId == update.user_id) {
|
||||||
exist = true;
|
exist = true;
|
||||||
u.lastTime = currentTime;
|
u.lastTime = currentTime;
|
||||||
|
u.action = new TLRPC.TL_sendMessageTypingAction();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3254,6 +3457,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
PrintingUser newUser = new PrintingUser();
|
PrintingUser newUser = new PrintingUser();
|
||||||
newUser.userId = update.user_id;
|
newUser.userId = update.user_id;
|
||||||
newUser.lastTime = currentTime;
|
newUser.lastTime = currentTime;
|
||||||
|
newUser.action = new TLRPC.TL_sendMessageTypingAction();
|
||||||
arr.add(newUser);
|
arr.add(newUser);
|
||||||
printChanged = true;
|
printChanged = true;
|
||||||
}
|
}
|
||||||
|
@ -3304,7 +3508,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
newMessage.local_id = newMessage.id = UserConfig.getNewMessageId();
|
newMessage.local_id = newMessage.id = UserConfig.getNewMessageId();
|
||||||
UserConfig.saveConfig(false);
|
UserConfig.saveConfig(false);
|
||||||
newMessage.flags = TLRPC.MESSAGE_FLAG_UNREAD;
|
newMessage.flags = TLRPC.MESSAGE_FLAG_UNREAD;
|
||||||
newMessage.date = update.date;
|
newMessage.date = ConnectionsManager.getInstance().getCurrentTime();
|
||||||
newMessage.from_id = 777000;
|
newMessage.from_id = 777000;
|
||||||
newMessage.to_id = new TLRPC.TL_peerUser();
|
newMessage.to_id = new TLRPC.TL_peerUser();
|
||||||
newMessage.to_id.user_id = UserConfig.getClientUserId();
|
newMessage.to_id.user_id = UserConfig.getClientUserId();
|
||||||
|
@ -3403,14 +3607,16 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
}
|
}
|
||||||
} else if (update instanceof TLRPC.TL_updateUserName) {
|
} else if (update instanceof TLRPC.TL_updateUserName) {
|
||||||
if (currentUser != null) {
|
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) {
|
if (currentUser.username != null && currentUser.username.length() > 0) {
|
||||||
usersByUsernames.remove(currentUser.username);
|
usersByUsernames.remove(currentUser.username);
|
||||||
}
|
}
|
||||||
if (update.username != null && update.username.length() > 0) {
|
if (update.username != null && update.username.length() > 0) {
|
||||||
usersByUsernames.put(update.username, currentUser);
|
usersByUsernames.put(update.username, currentUser);
|
||||||
}
|
}
|
||||||
currentUser.first_name = update.first_name;
|
|
||||||
currentUser.last_name = update.last_name;
|
|
||||||
currentUser.username = update.username;
|
currentUser.username = update.username;
|
||||||
}
|
}
|
||||||
toDbUser.first_name = update.first_name;
|
toDbUser.first_name = update.first_name;
|
||||||
|
@ -3427,7 +3633,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
} else if (update instanceof TLRPC.TL_updateUserPhone) {
|
} else if (update instanceof TLRPC.TL_updateUserPhone) {
|
||||||
if (currentUser != null) {
|
if (currentUser != null) {
|
||||||
currentUser.phone = update.phone;
|
currentUser.phone = update.phone;
|
||||||
Utilities.photoBookQueue.postRunnable(new Runnable() {
|
Utilities.phoneBookQueue.postRunnable(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
ContactsController.getInstance().addContactToPhoneBook(currentUser, true);
|
ContactsController.getInstance().addContactToPhoneBook(currentUser, true);
|
||||||
|
@ -3563,7 +3769,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
}
|
}
|
||||||
if (!markAsReadEncrypted.isEmpty()) {
|
if (!markAsReadEncrypted.isEmpty()) {
|
||||||
for (HashMap.Entry<Integer, Integer> entry : markAsReadEncrypted.entrySet()) {
|
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;
|
long dialog_id = (long) (entry.getKey()) << 32;
|
||||||
TLRPC.TL_dialog dialog = dialogs_dict.get(dialog_id);
|
TLRPC.TL_dialog dialog = dialogs_dict.get(dialog_id);
|
||||||
if (dialog != null) {
|
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()) {
|
if (!deletedMessages.isEmpty()) {
|
||||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesDeleted, deletedMessages);
|
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesDeleted, deletedMessages);
|
||||||
for (Integer id : deletedMessages) {
|
for (Integer id : deletedMessages) {
|
||||||
|
@ -3601,6 +3810,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
}
|
}
|
||||||
MessagesStorage.getInstance().markMessagesAsRead(markAsReadMessagesInbox, markAsReadMessagesOutbox, markAsReadEncrypted, true);
|
MessagesStorage.getInstance().markMessagesAsRead(markAsReadMessagesInbox, markAsReadMessagesOutbox, markAsReadEncrypted, true);
|
||||||
}
|
}
|
||||||
|
if (!markAsReadMessages.isEmpty()) {
|
||||||
|
MessagesStorage.getInstance().markMessagesContentAsRead(markAsReadMessages);
|
||||||
|
}
|
||||||
if (!deletedMessages.isEmpty()) {
|
if (!deletedMessages.isEmpty()) {
|
||||||
MessagesStorage.getInstance().markMessagesAsDeleted(deletedMessages, true);
|
MessagesStorage.getInstance().markMessagesAsDeleted(deletedMessages, true);
|
||||||
}
|
}
|
||||||
|
@ -3684,7 +3896,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
|
||||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.didReceivedNewMessages, uid, messages);
|
NotificationCenter.getInstance().postNotificationName(NotificationCenter.didReceivedNewMessages, uid, messages);
|
||||||
|
|
||||||
for (MessageObject message : 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;
|
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 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 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 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 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 contacts(uid INTEGER PRIMARY KEY, mutual INTEGER)").stepThis().dispose();
|
||||||
database.executeFast("CREATE TABLE pending_read(uid INTEGER PRIMARY KEY, max_id 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();
|
database.executeFast("CREATE TABLE keyvalue(id TEXT PRIMARY KEY, value TEXT)").stepThis().dispose();
|
||||||
|
|
||||||
//version
|
//version
|
||||||
database.executeFast("PRAGMA user_version = 16").stepThis().dispose();
|
database.executeFast("PRAGMA user_version = 17").stepThis().dispose();
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
SQLiteCursor cursor = database.queryFinalized("SELECT seq, pts, date, qts, lsv, sg, pbytes FROM params WHERE id = 1");
|
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");
|
int version = database.executeInt("PRAGMA user_version");
|
||||||
if (version < 16) {
|
if (version < 17) {
|
||||||
updateDbToLastVersion(version);
|
updateDbToLastVersion(version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -295,7 +295,7 @@ public class MessagesStorage {
|
||||||
if ((length = cursor.byteBufferValue(1, data.buffer)) != 0) {
|
if ((length = cursor.byteBufferValue(1, data.buffer)) != 0) {
|
||||||
for (int a = 0; a < length / 4; a++) {
|
for (int a = 0; a < length / 4; a++) {
|
||||||
state.requery();
|
state.requery();
|
||||||
state.bindInteger(1, data.readInt32());
|
state.bindInteger(1, data.readInt32(false));
|
||||||
state.bindInteger(2, date);
|
state.bindInteger(2, date);
|
||||||
state.step();
|
state.step();
|
||||||
}
|
}
|
||||||
|
@ -385,6 +385,12 @@ public class MessagesStorage {
|
||||||
database.executeFast("PRAGMA user_version = 16").stepThis().dispose();
|
database.executeFast("PRAGMA user_version = 16").stepThis().dispose();
|
||||||
version = 16;
|
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) {
|
} catch (Exception e) {
|
||||||
FileLog.e("tmessages", e);
|
FileLog.e("tmessages", e);
|
||||||
}
|
}
|
||||||
|
@ -523,12 +529,12 @@ public class MessagesStorage {
|
||||||
ArrayList<Integer> chatIds = new ArrayList<>();
|
ArrayList<Integer> chatIds = new ArrayList<>();
|
||||||
ArrayList<Integer> encryptedChatIds = 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()) {
|
while (cursor.next()) {
|
||||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(1));
|
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(1));
|
||||||
if (data != null && cursor.byteBufferValue(1, data.buffer) != 0) {
|
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);
|
||||||
MessageObject.setIsUnread(message, cursor.intValue(0) != 1);
|
MessageObject.setUnreadFlags(message, cursor.intValue(0));
|
||||||
message.id = cursor.intValue(3);
|
message.id = cursor.intValue(3);
|
||||||
message.date = cursor.intValue(4);
|
message.date = cursor.intValue(4);
|
||||||
message.dialog_id = cursor.longValue(5);
|
message.dialog_id = cursor.longValue(5);
|
||||||
|
@ -762,7 +768,7 @@ public class MessagesStorage {
|
||||||
while (cursor.next()) {
|
while (cursor.next()) {
|
||||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 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);
|
wallPapers.add(wallPaper);
|
||||||
}
|
}
|
||||||
buffersStorage.reuseFreeBuffer(data);
|
buffersStorage.reuseFreeBuffer(data);
|
||||||
|
@ -876,7 +882,7 @@ public class MessagesStorage {
|
||||||
while (cursor.next()) {
|
while (cursor.next()) {
|
||||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 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) {
|
if (message == null || message.media == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -950,7 +956,7 @@ public class MessagesStorage {
|
||||||
while (cursor.next()) {
|
while (cursor.next()) {
|
||||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 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);
|
res.photos.add(photo);
|
||||||
}
|
}
|
||||||
buffersStorage.reuseFreeBuffer(data);
|
buffersStorage.reuseFreeBuffer(data);
|
||||||
|
@ -1065,7 +1071,7 @@ public class MessagesStorage {
|
||||||
StringBuilder mids = new StringBuilder();
|
StringBuilder mids = new StringBuilder();
|
||||||
SQLiteCursor cursor = null;
|
SQLiteCursor cursor = null;
|
||||||
if (random_ids == 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 {
|
} else {
|
||||||
String ids = TextUtils.join(",", random_ids);
|
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));
|
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();
|
cursor.dispose();
|
||||||
} else if (inbox != null && !inbox.isEmpty()) {
|
} else if (inbox != null && !inbox.isEmpty()) {
|
||||||
for (HashMap.Entry<Integer, Integer> entry : inbox.entrySet()) {
|
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()) {
|
if (cursor.next()) {
|
||||||
int count = cursor.intValue(0);
|
int count = cursor.intValue(0);
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
|
@ -1255,7 +1261,7 @@ public class MessagesStorage {
|
||||||
if (cursor.next()) {
|
if (cursor.next()) {
|
||||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 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);
|
buffersStorage.reuseFreeBuffer(data);
|
||||||
}
|
}
|
||||||
|
@ -1318,7 +1324,7 @@ public class MessagesStorage {
|
||||||
if (cursor.next()) {
|
if (cursor.next()) {
|
||||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 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);
|
buffersStorage.reuseFreeBuffer(data);
|
||||||
}
|
}
|
||||||
|
@ -1383,14 +1389,14 @@ public class MessagesStorage {
|
||||||
int lower_id = (int)dialog_id;
|
int lower_id = (int)dialog_id;
|
||||||
|
|
||||||
if (lower_id != 0) {
|
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.requery();
|
||||||
state.bindLong(1, dialog_id);
|
state.bindLong(1, dialog_id);
|
||||||
state.bindInteger(2, max_id);
|
state.bindInteger(2, max_id);
|
||||||
state.step();
|
state.step();
|
||||||
state.dispose();
|
state.dispose();
|
||||||
} else {
|
} 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.requery();
|
||||||
state.bindLong(1, dialog_id);
|
state.bindLong(1, dialog_id);
|
||||||
state.bindInteger(2, max_date);
|
state.bindInteger(2, max_date);
|
||||||
|
@ -1613,9 +1619,9 @@ public class MessagesStorage {
|
||||||
while (cursor.next()) {
|
while (cursor.next()) {
|
||||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(1));
|
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(1));
|
||||||
if (data != null && cursor.byteBufferValue(1, data.buffer) != 0) {
|
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)) {
|
if (!messageHashMap.containsKey(message.id)) {
|
||||||
MessageObject.setIsUnread(message, cursor.intValue(0) != 1);
|
MessageObject.setUnreadFlags(message, cursor.intValue(0));
|
||||||
message.id = cursor.intValue(3);
|
message.id = cursor.intValue(3);
|
||||||
message.date = cursor.intValue(4);
|
message.date = cursor.intValue(4);
|
||||||
if (!cursor.isNull(5)) {
|
if (!cursor.isNull(5)) {
|
||||||
|
@ -1798,14 +1804,14 @@ public class MessagesStorage {
|
||||||
}
|
}
|
||||||
cursor.dispose();
|
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()) {
|
if (cursor.next()) {
|
||||||
min_unread_id = cursor.intValue(0);
|
min_unread_id = cursor.intValue(0);
|
||||||
max_unread_date = cursor.intValue(1);
|
max_unread_date = cursor.intValue(1);
|
||||||
}
|
}
|
||||||
cursor.dispose();
|
cursor.dispose();
|
||||||
if (min_unread_id != 0) {
|
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()) {
|
if (cursor.next()) {
|
||||||
count_unread = cursor.intValue(0);
|
count_unread = cursor.intValue(0);
|
||||||
}
|
}
|
||||||
|
@ -1843,14 +1849,14 @@ public class MessagesStorage {
|
||||||
}
|
}
|
||||||
cursor.dispose();
|
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()) {
|
if (cursor.next()) {
|
||||||
min_unread_id = cursor.intValue(0);
|
min_unread_id = cursor.intValue(0);
|
||||||
max_unread_date = cursor.intValue(1);
|
max_unread_date = cursor.intValue(1);
|
||||||
}
|
}
|
||||||
cursor.dispose();
|
cursor.dispose();
|
||||||
if (min_unread_id != 0) {
|
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()) {
|
if (cursor.next()) {
|
||||||
count_unread = cursor.intValue(0);
|
count_unread = cursor.intValue(0);
|
||||||
}
|
}
|
||||||
|
@ -1876,8 +1882,8 @@ public class MessagesStorage {
|
||||||
while (cursor.next()) {
|
while (cursor.next()) {
|
||||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(1));
|
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(1));
|
||||||
if (data != null && cursor.byteBufferValue(1, data.buffer) != 0) {
|
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);
|
||||||
MessageObject.setIsUnread(message, cursor.intValue(0) != 1);
|
MessageObject.setUnreadFlags(message, cursor.intValue(0));
|
||||||
message.id = cursor.intValue(3);
|
message.id = cursor.intValue(3);
|
||||||
message.date = cursor.intValue(4);
|
message.date = cursor.intValue(4);
|
||||||
message.dialog_id = dialog_id;
|
message.dialog_id = dialog_id;
|
||||||
|
@ -1900,7 +1906,7 @@ public class MessagesStorage {
|
||||||
if (!cursor.isNull(6)) {
|
if (!cursor.isNull(6)) {
|
||||||
ByteBufferDesc data2 = buffersStorage.getFreeBuffer(cursor.byteArrayLength(6));
|
ByteBufferDesc data2 = buffersStorage.getFreeBuffer(cursor.byteArrayLength(6));
|
||||||
if (data2 != null && cursor.byteBufferValue(6, data2.buffer) != 0) {
|
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) {
|
if (message.replyMessage != null) {
|
||||||
fromUser.add(message.replyMessage.from_id);
|
fromUser.add(message.replyMessage.from_id);
|
||||||
if (message.replyMessage.action != null && message.replyMessage.action.user_id != 0) {
|
if (message.replyMessage.action != null && message.replyMessage.action.user_id != 0) {
|
||||||
|
@ -1987,7 +1993,7 @@ public class MessagesStorage {
|
||||||
while (cursor.next()) {
|
while (cursor.next()) {
|
||||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 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.id = cursor.intValue(1);
|
||||||
message.date = cursor.intValue(2);
|
message.date = cursor.intValue(2);
|
||||||
message.dialog_id = dialog_id;
|
message.dialog_id = dialog_id;
|
||||||
|
@ -2091,7 +2097,7 @@ public class MessagesStorage {
|
||||||
if (cursor.next()) {
|
if (cursor.next()) {
|
||||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 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) {
|
if (file != null) {
|
||||||
result.add(file);
|
result.add(file);
|
||||||
}
|
}
|
||||||
|
@ -2374,11 +2380,13 @@ public class MessagesStorage {
|
||||||
buffersStorage.reuseFreeBuffer(data5);
|
buffersStorage.reuseFreeBuffer(data5);
|
||||||
|
|
||||||
if (dialog != null) {
|
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.bindLong(1, dialog.id);
|
||||||
state.bindInteger(2, dialog.last_message_date);
|
state.bindInteger(2, dialog.last_message_date);
|
||||||
state.bindInteger(3, dialog.unread_count);
|
state.bindInteger(3, dialog.unread_count);
|
||||||
state.bindInteger(4, dialog.top_message);
|
state.bindInteger(4, dialog.top_message);
|
||||||
|
state.bindInteger(5, dialog.read_inbox_max_id);
|
||||||
|
state.bindInteger(6, 0);
|
||||||
state.step();
|
state.step();
|
||||||
state.dispose();
|
state.dispose();
|
||||||
}
|
}
|
||||||
|
@ -2469,7 +2477,7 @@ public class MessagesStorage {
|
||||||
try {
|
try {
|
||||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 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 != null) {
|
||||||
if (user.status != null) {
|
if (user.status != null) {
|
||||||
user.status.expires = cursor.intValue(1);
|
user.status.expires = cursor.intValue(1);
|
||||||
|
@ -2494,7 +2502,7 @@ public class MessagesStorage {
|
||||||
try {
|
try {
|
||||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 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) {
|
if (chat != null) {
|
||||||
result.add(chat);
|
result.add(chat);
|
||||||
}
|
}
|
||||||
|
@ -2511,13 +2519,12 @@ public class MessagesStorage {
|
||||||
if (chatsToLoad == null || chatsToLoad.length() == 0 || result == null) {
|
if (chatsToLoad == null || chatsToLoad.length() == 0 || result == null) {
|
||||||
return;
|
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));
|
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()) {
|
while (cursor.next()) {
|
||||||
try {
|
try {
|
||||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0));
|
||||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 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) {
|
if (chat != null) {
|
||||||
chat.user_id = cursor.intValue(1);
|
chat.user_id = cursor.intValue(1);
|
||||||
if (usersToLoad != null && !usersToLoad.contains(chat.user_id)) {
|
if (usersToLoad != null && !usersToLoad.contains(chat.user_id)) {
|
||||||
|
@ -2634,7 +2641,7 @@ public class MessagesStorage {
|
||||||
downloadObject.id = cursor.longValue(0);
|
downloadObject.id = cursor.longValue(0);
|
||||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(2));
|
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(2));
|
||||||
if (data != null && cursor.byteBufferValue(2, data.buffer) != 0) {
|
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);
|
buffersStorage.reuseFreeBuffer(data);
|
||||||
objects.add(downloadObject);
|
objects.add(downloadObject);
|
||||||
|
@ -2691,7 +2698,7 @@ public class MessagesStorage {
|
||||||
int mid = cursor.intValue(0);
|
int mid = cursor.intValue(0);
|
||||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(1));
|
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(1));
|
||||||
if (data != null && cursor.byteBufferValue(1, data.buffer) != 0) {
|
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) {
|
if (message.media instanceof TLRPC.TL_messageMediaWebPage) {
|
||||||
message.id = mid;
|
message.id = mid;
|
||||||
message.media.webpage = webPages.get(message.media.webpage.id);
|
message.media.webpage = webPages.get(message.media.webpage.id);
|
||||||
|
@ -2865,7 +2872,7 @@ public class MessagesStorage {
|
||||||
|
|
||||||
state.bindInteger(1, messageId);
|
state.bindInteger(1, messageId);
|
||||||
state.bindLong(2, dialog_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(4, message.send_state);
|
||||||
state.bindInteger(5, message.date);
|
state.bindInteger(5, message.date);
|
||||||
state.bindByteBuffer(6, data.buffer);
|
state.bindByteBuffer(6, data.buffer);
|
||||||
|
@ -2954,7 +2961,7 @@ public class MessagesStorage {
|
||||||
state4.dispose();
|
state4.dispose();
|
||||||
state5.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()) {
|
for (HashMap.Entry<Long, TLRPC.Message> pair : messagesMap.entrySet()) {
|
||||||
Long key = pair.getKey();
|
Long key = pair.getKey();
|
||||||
|
|
||||||
|
@ -2987,6 +2994,8 @@ public class MessagesStorage {
|
||||||
}
|
}
|
||||||
state.bindInteger(3, old_unread_count + unread_count);
|
state.bindInteger(3, old_unread_count + unread_count);
|
||||||
state.bindInteger(4, messageId);
|
state.bindInteger(4, messageId);
|
||||||
|
state.bindInteger(5, 0);
|
||||||
|
state.bindInteger(6, 0);
|
||||||
state.step();
|
state.step();
|
||||||
}
|
}
|
||||||
state.dispose();
|
state.dispose();
|
||||||
|
@ -3294,8 +3303,10 @@ public class MessagesStorage {
|
||||||
TLRPC.User updateUser = usersDict.get(user.id);
|
TLRPC.User updateUser = usersDict.get(user.id);
|
||||||
if (updateUser != null) {
|
if (updateUser != null) {
|
||||||
if (updateUser.first_name != null && updateUser.last_name != null) {
|
if (updateUser.first_name != null && updateUser.last_name != null) {
|
||||||
user.first_name = updateUser.first_name;
|
if (!(user instanceof TLRPC.TL_userContact)) {
|
||||||
user.last_name = updateUser.last_name;
|
user.first_name = updateUser.first_name;
|
||||||
|
user.last_name = updateUser.last_name;
|
||||||
|
}
|
||||||
user.username = updateUser.username;
|
user.username = updateUser.username;
|
||||||
} else if (updateUser.photo != null) {
|
} else if (updateUser.photo != null) {
|
||||||
user.photo = updateUser.photo;
|
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) {
|
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 {
|
try {
|
||||||
if (inbox != null) {
|
if (inbox != null) {
|
||||||
for (HashMap.Entry<Integer, Integer> entry : inbox.entrySet()) {
|
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) {
|
if (outbox != null) {
|
||||||
for (HashMap.Entry<Integer, Integer> entry : outbox.entrySet()) {
|
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()) {
|
if (encryptedMessages != null && !encryptedMessages.isEmpty()) {
|
||||||
for (HashMap.Entry<Integer, Integer> entry : encryptedMessages.entrySet()) {
|
for (HashMap.Entry<Integer, Integer> entry : encryptedMessages.entrySet()) {
|
||||||
long dialog_id = ((long)entry.getKey()) << 32;
|
long dialog_id = ((long)entry.getKey()) << 32;
|
||||||
int max_date = entry.getValue();
|
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.requery();
|
||||||
state.bindLong(1, dialog_id);
|
state.bindLong(1, dialog_id);
|
||||||
state.bindInteger(2, max_date);
|
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) {
|
public void markMessagesAsRead(final HashMap<Integer, Integer> inbox, final HashMap<Integer, Integer> outbox, final HashMap<Integer, Integer> encryptedMessages, boolean useQueue) {
|
||||||
if (useQueue) {
|
if (useQueue) {
|
||||||
storageQueue.postRunnable(new Runnable() {
|
storageQueue.postRunnable(new Runnable() {
|
||||||
|
@ -3433,7 +3457,7 @@ public class MessagesStorage {
|
||||||
}
|
}
|
||||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(1));
|
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(1));
|
||||||
if (data != null && cursor.byteBufferValue(1, data.buffer) != 0) {
|
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) {
|
if (message == null || message.media == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -3527,8 +3551,8 @@ public class MessagesStorage {
|
||||||
|
|
||||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(4));
|
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(4));
|
||||||
if (data != null && cursor.byteBufferValue(4, data.buffer) != 0) {
|
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);
|
||||||
MessageObject.setIsUnread(message, cursor.intValue(5) != 1);
|
MessageObject.setUnreadFlags(message, cursor.intValue(5));
|
||||||
message.id = cursor.intValue(6);
|
message.id = cursor.intValue(6);
|
||||||
message.send_state = cursor.intValue(7);
|
message.send_state = cursor.intValue(7);
|
||||||
int date = cursor.intValue(8);
|
int date = cursor.intValue(8);
|
||||||
|
@ -3667,7 +3691,7 @@ public class MessagesStorage {
|
||||||
message.serializeToStream(data);
|
message.serializeToStream(data);
|
||||||
state.bindInteger(1, message.id);
|
state.bindInteger(1, message.id);
|
||||||
state.bindLong(2, dialog_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(4, message.send_state);
|
||||||
state.bindInteger(5, message.date);
|
state.bindInteger(5, message.date);
|
||||||
state.bindByteBuffer(6, data.buffer);
|
state.bindByteBuffer(6, data.buffer);
|
||||||
|
@ -3732,9 +3756,9 @@ public class MessagesStorage {
|
||||||
|
|
||||||
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(4));
|
ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(4));
|
||||||
if (data != null && cursor.byteBufferValue(4, data.buffer) != 0) {
|
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) {
|
if (message != null) {
|
||||||
MessageObject.setIsUnread(message, cursor.intValue(5) != 1);
|
MessageObject.setUnreadFlags(message, cursor.intValue(5));
|
||||||
message.id = cursor.intValue(6);
|
message.id = cursor.intValue(6);
|
||||||
int date = cursor.intValue(9);
|
int date = cursor.intValue(9);
|
||||||
if (date != 0) {
|
if (date != 0) {
|
||||||
|
@ -3831,7 +3855,7 @@ public class MessagesStorage {
|
||||||
|
|
||||||
if (!dialogs.dialogs.isEmpty()) {
|
if (!dialogs.dialogs.isEmpty()) {
|
||||||
SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)");
|
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 state3 = database.executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)");
|
||||||
SQLitePreparedStatement state4 = database.executeFast("REPLACE INTO dialog_settings VALUES(?, ?)");
|
SQLitePreparedStatement state4 = database.executeFast("REPLACE INTO dialog_settings VALUES(?, ?)");
|
||||||
|
|
||||||
|
@ -3850,7 +3874,7 @@ public class MessagesStorage {
|
||||||
|
|
||||||
state.bindInteger(1, message.id);
|
state.bindInteger(1, message.id);
|
||||||
state.bindInteger(2, uid);
|
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(4, message.send_state);
|
||||||
state.bindInteger(5, message.date);
|
state.bindInteger(5, message.date);
|
||||||
state.bindByteBuffer(6, data.buffer);
|
state.bindByteBuffer(6, data.buffer);
|
||||||
|
@ -3863,6 +3887,8 @@ public class MessagesStorage {
|
||||||
state2.bindInteger(2, message.date);
|
state2.bindInteger(2, message.date);
|
||||||
state2.bindInteger(3, dialog.unread_count);
|
state2.bindInteger(3, dialog.unread_count);
|
||||||
state2.bindInteger(4, dialog.top_message);
|
state2.bindInteger(4, dialog.top_message);
|
||||||
|
state2.bindInteger(5, dialog.read_inbox_max_id);
|
||||||
|
state2.bindInteger(6, 0);
|
||||||
state2.step();
|
state2.step();
|
||||||
|
|
||||||
state4.bindLong(1, uid);
|
state4.bindLong(1, uid);
|
||||||
|
|
|
@ -23,7 +23,7 @@ import java.util.zip.ZipFile;
|
||||||
|
|
||||||
public class NativeLoader {
|
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_NAME = "tmessages." + LIB_VERSION;
|
||||||
private final static String LIB_SO_NAME = "lib" + LIB_NAME + ".so";
|
private final static String LIB_SO_NAME = "lib" + LIB_NAME + ".so";
|
||||||
private final static String LOCALE_LIB_SO_NAME = "lib" + LIB_NAME + "loc.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 mediaDidLoaded = totalEvents++;
|
||||||
public static final int mediaCountDidLoaded = totalEvents++;
|
public static final int mediaCountDidLoaded = totalEvents++;
|
||||||
public static final int encryptedChatUpdated = 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 encryptedChatCreated = totalEvents++;
|
||||||
public static final int userPhotosLoaded = totalEvents++;
|
public static final int userPhotosLoaded = totalEvents++;
|
||||||
public static final int removeAllMessagesFromDialog = totalEvents++;
|
public static final int removeAllMessagesFromDialog = totalEvents++;
|
||||||
|
@ -56,6 +56,9 @@ public class NotificationCenter {
|
||||||
public static final int newSessionReceived = totalEvents++;
|
public static final int newSessionReceived = totalEvents++;
|
||||||
public static final int didReceivedWebpages = totalEvents++;
|
public static final int didReceivedWebpages = totalEvents++;
|
||||||
public static final int didReceivedWebpagesInUpdates = 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 httpFileDidLoaded = totalEvents++;
|
||||||
public static final int httpFileDidFailedLoad = totalEvents++;
|
public static final int httpFileDidFailedLoad = totalEvents++;
|
||||||
|
|
|
@ -17,10 +17,10 @@ import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
import android.content.res.AssetFileDescriptor;
|
import android.graphics.Point;
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.media.MediaPlayer;
|
import android.media.SoundPool;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
@ -54,7 +54,9 @@ public class NotificationsController {
|
||||||
|
|
||||||
private DispatchQueue notificationsQueue = new DispatchQueue("notificationsQueue");
|
private DispatchQueue notificationsQueue = new DispatchQueue("notificationsQueue");
|
||||||
private ArrayList<MessageObject> pushMessages = new ArrayList<>();
|
private ArrayList<MessageObject> pushMessages = new ArrayList<>();
|
||||||
|
private ArrayList<MessageObject> delayedPushMessages = new ArrayList<>();
|
||||||
private HashMap<Integer, MessageObject> pushMessagesDict = new HashMap<>();
|
private HashMap<Integer, MessageObject> pushMessagesDict = new HashMap<>();
|
||||||
|
private HashMap<Long, Point> smartNotificationsDialogs = new HashMap<>();
|
||||||
private NotificationManagerCompat notificationManager = null;
|
private NotificationManagerCompat notificationManager = null;
|
||||||
private HashMap<Long, Integer> pushDialogs = new HashMap<>();
|
private HashMap<Long, Integer> pushDialogs = new HashMap<>();
|
||||||
private HashMap<Long, Integer> wearNoticationsIds = new HashMap<>();
|
private HashMap<Long, Integer> wearNoticationsIds = new HashMap<>();
|
||||||
|
@ -67,10 +69,14 @@ public class NotificationsController {
|
||||||
private boolean notifyCheck = false;
|
private boolean notifyCheck = false;
|
||||||
private int lastOnlineFromOtherDevice = 0;
|
private int lastOnlineFromOtherDevice = 0;
|
||||||
private boolean inChatSoundEnabled = true;
|
private boolean inChatSoundEnabled = true;
|
||||||
|
private int lastBadgeCount;
|
||||||
|
|
||||||
private long lastSoundPlay;
|
private long lastSoundPlay;
|
||||||
private MediaPlayer mediaPlayerIn;
|
//private MediaPlayer mediaPlayerIn;
|
||||||
private MediaPlayer mediaPlayerOut;
|
//private MediaPlayer mediaPlayerOut;
|
||||||
|
private SoundPool soundPool;
|
||||||
|
private int soundIn;
|
||||||
|
private int soundOut;
|
||||||
protected AudioManager audioManager;
|
protected AudioManager audioManager;
|
||||||
|
|
||||||
private static volatile NotificationsController Instance = null;
|
private static volatile NotificationsController Instance = null;
|
||||||
|
@ -110,6 +116,7 @@ public class NotificationsController {
|
||||||
popupMessages.clear();
|
popupMessages.clear();
|
||||||
wearNoticationsIds.clear();
|
wearNoticationsIds.clear();
|
||||||
notifyCheck = false;
|
notifyCheck = false;
|
||||||
|
lastBadgeCount = 0;
|
||||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE);
|
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE);
|
||||||
SharedPreferences.Editor editor = preferences.edit();
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
editor.clear();
|
editor.clear();
|
||||||
|
@ -187,7 +194,7 @@ public class NotificationsController {
|
||||||
msg = LocaleController.formatString("NotificationMessageVideo", R.string.NotificationMessageVideo, ContactsController.formatName(user.first_name, user.last_name));
|
msg = LocaleController.formatString("NotificationMessageVideo", R.string.NotificationMessageVideo, ContactsController.formatName(user.first_name, user.last_name));
|
||||||
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) {
|
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) {
|
||||||
msg = LocaleController.formatString("NotificationMessageContact", R.string.NotificationMessageContact, ContactsController.formatName(user.first_name, user.last_name));
|
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));
|
msg = LocaleController.formatString("NotificationMessageMap", R.string.NotificationMessageMap, ContactsController.formatName(user.first_name, user.last_name));
|
||||||
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) {
|
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) {
|
||||||
if (messageObject.isSticker()) {
|
if (messageObject.isSticker()) {
|
||||||
|
@ -214,8 +221,14 @@ public class NotificationsController {
|
||||||
if (u2 == null) {
|
if (u2 == null) {
|
||||||
return 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) {
|
} 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);
|
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) {
|
} 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);
|
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) {
|
} 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);
|
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);
|
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) {
|
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) {
|
||||||
if (messageObject.isSticker()) {
|
if (messageObject.isSticker()) {
|
||||||
|
@ -286,14 +299,14 @@ public class NotificationsController {
|
||||||
|
|
||||||
private void scheduleNotificationDelay(boolean onlineReason) {
|
private void scheduleNotificationDelay(boolean onlineReason) {
|
||||||
try {
|
try {
|
||||||
FileLog.e("tmessages", "delay notification start");
|
FileLog.e("tmessages", "delay notification start, onlineReason = " + onlineReason);
|
||||||
AlarmManager alarm = (AlarmManager) ApplicationLoader.applicationContext.getSystemService(Context.ALARM_SERVICE);
|
AlarmManager alarm = (AlarmManager) ApplicationLoader.applicationContext.getSystemService(Context.ALARM_SERVICE);
|
||||||
PendingIntent pintent = PendingIntent.getService(ApplicationLoader.applicationContext, 0, new Intent(ApplicationLoader.applicationContext, NotificationDelay.class), 0);
|
PendingIntent pintent = PendingIntent.getService(ApplicationLoader.applicationContext, 0, new Intent(ApplicationLoader.applicationContext, NotificationDelay.class), 0);
|
||||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE);
|
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE);
|
||||||
if (onlineReason) {
|
if (onlineReason) {
|
||||||
alarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 3 * 1000, pintent);
|
alarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 3 * 1000, pintent);
|
||||||
} else {
|
} else {
|
||||||
alarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 500, pintent);
|
alarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 1000, pintent);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
FileLog.e("tmessages", e);
|
FileLog.e("tmessages", e);
|
||||||
|
@ -302,7 +315,10 @@ public class NotificationsController {
|
||||||
|
|
||||||
protected void notificationDelayReached() {
|
protected void notificationDelayReached() {
|
||||||
FileLog.e("tmessages", "delay reached");
|
FileLog.e("tmessages", "delay reached");
|
||||||
showOrUpdateNotification(true);
|
if (!delayedPushMessages.isEmpty()) {
|
||||||
|
showOrUpdateNotification(true);
|
||||||
|
delayedPushMessages.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void repeatNotificationMaybe() {
|
protected void repeatNotificationMaybe() {
|
||||||
|
@ -319,6 +335,7 @@ public class NotificationsController {
|
||||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
FileLog.e("tmessages", "set last online from other device = " + time);
|
||||||
lastOnlineFromOtherDevice = time;
|
lastOnlineFromOtherDevice = time;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -364,29 +381,47 @@ public class NotificationsController {
|
||||||
boolean inAppPreview = false;
|
boolean inAppPreview = false;
|
||||||
boolean inAppPriority = false;
|
boolean inAppPriority = false;
|
||||||
int priority = 0;
|
int priority = 0;
|
||||||
int priority_override = 0;
|
int priorityOverride = 0;
|
||||||
int vibrate_override = 0;
|
int vibrateOverride = 0;
|
||||||
|
|
||||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE);
|
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE);
|
||||||
int notify_override = preferences.getInt("notify2_" + override_dialog_id, 0);
|
int notifyOverride = getNotifyOverride(preferences, override_dialog_id);
|
||||||
if (notify_override == 3) {
|
if (!notifyAboutLast || notifyOverride == 2 || (!preferences.getBoolean("EnableAll", true) || chat_id != 0 && !preferences.getBoolean("EnableGroup", true)) && notifyOverride == 0) {
|
||||||
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) {
|
|
||||||
notifyDisabled = true;
|
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();
|
String defaultPath = Settings.System.DEFAULT_NOTIFICATION_URI.getPath();
|
||||||
if (!notifyDisabled) {
|
if (!notifyDisabled) {
|
||||||
inAppSounds = preferences.getBoolean("EnableInAppSounds", true);
|
inAppSounds = preferences.getBoolean("EnableInAppSounds", true);
|
||||||
inAppVibrate = preferences.getBoolean("EnableInAppVibrate", true);
|
inAppVibrate = preferences.getBoolean("EnableInAppVibrate", true);
|
||||||
inAppPreview = preferences.getBoolean("EnableInAppPreview", true);
|
inAppPreview = preferences.getBoolean("EnableInAppPreview", true);
|
||||||
inAppPriority = preferences.getBoolean("EnableInAppPriority", false);
|
inAppPriority = preferences.getBoolean("EnableInAppPriority", false);
|
||||||
vibrate_override = preferences.getInt("vibrate_" + dialog_id, 0);
|
vibrateOverride = preferences.getInt("vibrate_" + dialog_id, 0);
|
||||||
priority_override = preferences.getInt("priority_" + dialog_id, 3);
|
priorityOverride = preferences.getInt("priority_" + dialog_id, 3);
|
||||||
boolean vibrateOnlyIfSilent = false;
|
boolean vibrateOnlyIfSilent = false;
|
||||||
|
|
||||||
choosenSoundPath = preferences.getString("sound_path_" + dialog_id, null);
|
choosenSoundPath = preferences.getString("sound_path_" + dialog_id, null);
|
||||||
|
@ -413,16 +448,16 @@ public class NotificationsController {
|
||||||
ledColor = preferences.getInt("color_" + dialog_id, 0);
|
ledColor = preferences.getInt("color_" + dialog_id, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (priority_override != 3) {
|
if (priorityOverride != 3) {
|
||||||
priority = priority_override;
|
priority = priorityOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needVibrate == 4) {
|
if (needVibrate == 4) {
|
||||||
vibrateOnlyIfSilent = true;
|
vibrateOnlyIfSilent = true;
|
||||||
needVibrate = 0;
|
needVibrate = 0;
|
||||||
}
|
}
|
||||||
if (needVibrate == 2 && (vibrate_override == 1 || vibrate_override == 3 || vibrate_override == 5) || needVibrate != 2 && vibrate_override == 2 || vibrate_override != 0) {
|
if (needVibrate == 2 && (vibrateOverride == 1 || vibrateOverride == 3 || vibrateOverride == 5) || needVibrate != 2 && vibrateOverride == 2 || vibrateOverride != 0) {
|
||||||
needVibrate = vibrate_override;
|
needVibrate = vibrateOverride;
|
||||||
}
|
}
|
||||||
if (!ApplicationLoader.mainInterfacePaused) {
|
if (!ApplicationLoader.mainInterfacePaused) {
|
||||||
if (!inAppSounds) {
|
if (!inAppSounds) {
|
||||||
|
@ -524,9 +559,6 @@ public class NotificationsController {
|
||||||
if (chat == null && user != null && user.phone != null && user.phone.length() > 0) {
|
if (chat == null && user != null && user.phone != null && user.phone.length() > 0) {
|
||||||
mBuilder.addPerson("tel:+" + user.phone);
|
mBuilder.addPerson("tel:+" + user.phone);
|
||||||
}
|
}
|
||||||
/*Bundle bundle = new Bundle();
|
|
||||||
bundle.putString(NotificationCompat.EXTRA_PEOPLE, );
|
|
||||||
mBuilder.setExtras()*/
|
|
||||||
|
|
||||||
String lastMessage = null;
|
String lastMessage = null;
|
||||||
String lastMessageFull = null;
|
String lastMessageFull = null;
|
||||||
|
@ -558,7 +590,6 @@ public class NotificationsController {
|
||||||
}
|
}
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
lastMessageFull = message;
|
lastMessageFull = message;
|
||||||
//lastMessage = getStringForMessage(pushMessages.get(i), true);
|
|
||||||
lastMessage = lastMessageFull;
|
lastMessage = lastMessageFull;
|
||||||
}
|
}
|
||||||
if (pushDialogs.size() == 1) {
|
if (pushDialogs.size() == 1) {
|
||||||
|
@ -790,6 +821,7 @@ public class NotificationsController {
|
||||||
}
|
}
|
||||||
popupMessages.remove(messageObject);
|
popupMessages.remove(messageObject);
|
||||||
pushMessagesDict.remove(messageObject.getId());
|
pushMessagesDict.remove(messageObject.getId());
|
||||||
|
delayedPushMessages.remove(messageObject);
|
||||||
pushMessages.remove(a);
|
pushMessages.remove(a);
|
||||||
a--;
|
a--;
|
||||||
}
|
}
|
||||||
|
@ -821,6 +853,7 @@ public class NotificationsController {
|
||||||
personal_count--;
|
personal_count--;
|
||||||
}
|
}
|
||||||
pushMessages.remove(a);
|
pushMessages.remove(a);
|
||||||
|
delayedPushMessages.remove(messageObject);
|
||||||
popupMessages.remove(messageObject);
|
popupMessages.remove(messageObject);
|
||||||
pushMessagesDict.remove(messageObject.getId());
|
pushMessagesDict.remove(messageObject.getId());
|
||||||
a--;
|
a--;
|
||||||
|
@ -847,14 +880,8 @@ public class NotificationsController {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE);
|
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE);
|
||||||
int notify_override = preferences.getInt("notify2_" + openned_dialog_id, 0);
|
int notifyOverride = getNotifyOverride(preferences, openned_dialog_id);
|
||||||
if (notify_override == 3) {
|
if (notifyOverride == 2) {
|
||||||
int mute_until = preferences.getInt("notifyuntil_" + openned_dialog_id, 0);
|
|
||||||
if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) {
|
|
||||||
notify_override = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (notify_override == 2) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
notificationsQueue.postRunnable(new Runnable() {
|
notificationsQueue.postRunnable(new Runnable() {
|
||||||
|
@ -864,7 +891,14 @@ public class NotificationsController {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
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);
|
AssetFileDescriptor assetFileDescriptor = ApplicationLoader.applicationContext.getResources().openRawResourceFd(R.raw.sound_in);
|
||||||
if (assetFileDescriptor != null) {
|
if (assetFileDescriptor != null) {
|
||||||
mediaPlayerIn = new MediaPlayer();
|
mediaPlayerIn = new MediaPlayer();
|
||||||
|
@ -875,45 +909,18 @@ public class NotificationsController {
|
||||||
mediaPlayerIn.prepare();
|
mediaPlayerIn.prepare();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mediaPlayerIn.start();
|
try {
|
||||||
|
mediaPlayerIn.pause();
|
||||||
|
mediaPlayerIn.seekTo(0);
|
||||||
|
} catch (Exception e) {
|
||||||
|
FileLog.e("tmessages", e);
|
||||||
|
}
|
||||||
|
mediaPlayerIn.start();*/
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
FileLog.e("tmessages", 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) {
|
} catch (Exception e) {
|
||||||
FileLog.e("tmessages", e);
|
FileLog.e("tmessages", e);
|
||||||
}
|
}
|
||||||
|
@ -934,7 +941,14 @@ public class NotificationsController {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
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);
|
AssetFileDescriptor assetFileDescriptor = ApplicationLoader.applicationContext.getResources().openRawResourceFd(R.raw.sound_out);
|
||||||
if (assetFileDescriptor != null) {
|
if (assetFileDescriptor != null) {
|
||||||
mediaPlayerOut = new MediaPlayer();
|
mediaPlayerOut = new MediaPlayer();
|
||||||
|
@ -945,7 +959,13 @@ public class NotificationsController {
|
||||||
mediaPlayerOut.prepare();
|
mediaPlayerOut.prepare();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mediaPlayerOut.start();
|
try {
|
||||||
|
mediaPlayerOut.pause();
|
||||||
|
mediaPlayerOut.seekTo(0);
|
||||||
|
} catch (Exception e) {
|
||||||
|
FileLog.e("tmessages", e);
|
||||||
|
}
|
||||||
|
mediaPlayerOut.start();*/
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
FileLog.e("tmessages", 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) {
|
public void processNewMessages(ArrayList<MessageObject> messageObjects, boolean isLast) {
|
||||||
if (messageObjects.isEmpty()) {
|
if (messageObjects.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
@ -986,20 +1017,15 @@ public class NotificationsController {
|
||||||
boolean isChat = (int)dialog_id < 0;
|
boolean isChat = (int)dialog_id < 0;
|
||||||
popup = (int)dialog_id == 0 ? 0 : preferences.getInt(isChat ? "popupGroup" : "popupAll", 0);
|
popup = (int)dialog_id == 0 ? 0 : preferences.getInt(isChat ? "popupGroup" : "popupAll", 0);
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
int notify_override = preferences.getInt("notify2_" + dialog_id, 0);
|
int notifyOverride = getNotifyOverride(preferences, dialog_id);
|
||||||
if (notify_override == 3) {
|
value = !(notifyOverride == 2 || (!preferences.getBoolean("EnableAll", true) || isChat && !preferences.getBoolean("EnableGroup", true)) && notifyOverride == 0);
|
||||||
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);
|
|
||||||
settingsCache.put(dialog_id, value);
|
settingsCache.put(dialog_id, value);
|
||||||
}
|
}
|
||||||
if (value) {
|
if (value) {
|
||||||
if (popup != 0) {
|
if (popup != 0) {
|
||||||
popupMessages.add(0, messageObject);
|
popupMessages.add(0, messageObject);
|
||||||
}
|
}
|
||||||
|
delayedPushMessages.add(messageObject);
|
||||||
pushMessages.add(0, messageObject);
|
pushMessages.add(0, messageObject);
|
||||||
pushMessagesDict.put(messageObject.getId(), messageObject);
|
pushMessagesDict.put(messageObject.getId(), messageObject);
|
||||||
if (original_dialog_id != dialog_id) {
|
if (original_dialog_id != dialog_id) {
|
||||||
|
@ -1030,24 +1056,22 @@ public class NotificationsController {
|
||||||
for (HashMap.Entry<Long, Integer> entry : dialogsToUpdate.entrySet()) {
|
for (HashMap.Entry<Long, Integer> entry : dialogsToUpdate.entrySet()) {
|
||||||
long dialog_id = entry.getKey();
|
long dialog_id = entry.getKey();
|
||||||
|
|
||||||
int notify_override = preferences.getInt("notify2_" + dialog_id, 0);
|
int notifyOverride = getNotifyOverride(preferences, dialog_id);
|
||||||
if (notify_override == 3) {
|
|
||||||
int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0);
|
|
||||||
if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) {
|
|
||||||
notify_override = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (notifyCheck) {
|
if (notifyCheck) {
|
||||||
Integer override = pushDialogsOverrideMention.get(dialog_id);
|
Integer override = pushDialogsOverrideMention.get(dialog_id);
|
||||||
if (override != null && override == 1) {
|
if (override != null && override == 1) {
|
||||||
pushDialogsOverrideMention.put(dialog_id, 0);
|
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 currentCount = pushDialogs.get(dialog_id);
|
||||||
Integer newCount = entry.getValue();
|
Integer newCount = entry.getValue();
|
||||||
|
if (newCount == 0) {
|
||||||
|
smartNotificationsDialogs.remove(dialog_id);
|
||||||
|
}
|
||||||
|
|
||||||
if (newCount < 0) {
|
if (newCount < 0) {
|
||||||
if (currentCount == null) {
|
if (currentCount == null) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -1070,6 +1094,7 @@ public class NotificationsController {
|
||||||
}
|
}
|
||||||
pushMessages.remove(a);
|
pushMessages.remove(a);
|
||||||
a--;
|
a--;
|
||||||
|
delayedPushMessages.remove(messageObject);
|
||||||
pushMessagesDict.remove(messageObject.getId());
|
pushMessagesDict.remove(messageObject.getId());
|
||||||
popupMessages.remove(messageObject);
|
popupMessages.remove(messageObject);
|
||||||
}
|
}
|
||||||
|
@ -1079,17 +1104,18 @@ public class NotificationsController {
|
||||||
pushDialogs.put(dialog_id, newCount);
|
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) {
|
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;
|
notifyCheck = false;
|
||||||
if (preferences.getBoolean("badgeNumber", true)) {
|
if (preferences.getBoolean("badgeNumber", true)) {
|
||||||
setBadge(ApplicationLoader.applicationContext, total_unread_count);
|
setBadge(ApplicationLoader.applicationContext, total_unread_count);
|
||||||
|
@ -1125,14 +1151,8 @@ public class NotificationsController {
|
||||||
}
|
}
|
||||||
Boolean value = settingsCache.get(dialog_id);
|
Boolean value = settingsCache.get(dialog_id);
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
int notify_override = preferences.getInt("notify2_" + dialog_id, 0);
|
int notifyOverride = getNotifyOverride(preferences, dialog_id);
|
||||||
if (notify_override == 3) {
|
value = !(notifyOverride == 2 || (!preferences.getBoolean("EnableAll", true) || ((int) dialog_id < 0) && !preferences.getBoolean("EnableGroup", true)) && notifyOverride == 0);
|
||||||
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);
|
|
||||||
settingsCache.put(dialog_id, value);
|
settingsCache.put(dialog_id, value);
|
||||||
}
|
}
|
||||||
if (!value || dialog_id == openned_dialog_id && ApplicationLoader.isScreenOn) {
|
if (!value || dialog_id == openned_dialog_id && ApplicationLoader.isScreenOn) {
|
||||||
|
@ -1149,19 +1169,13 @@ public class NotificationsController {
|
||||||
long dialog_id = entry.getKey();
|
long dialog_id = entry.getKey();
|
||||||
Boolean value = settingsCache.get(dialog_id);
|
Boolean value = settingsCache.get(dialog_id);
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
int notify_override = preferences.getInt("notify2_" + dialog_id, 0);
|
int notifyOverride = getNotifyOverride(preferences, dialog_id);
|
||||||
if (notify_override == 3) {
|
|
||||||
int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0);
|
|
||||||
if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) {
|
|
||||||
notify_override = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Integer override = pushDialogsOverrideMention.get(dialog_id);
|
Integer override = pushDialogsOverrideMention.get(dialog_id);
|
||||||
if (override != null && override == 1) {
|
if (override != null && override == 1) {
|
||||||
pushDialogsOverrideMention.put(dialog_id, 0);
|
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);
|
settingsCache.put(dialog_id, value);
|
||||||
}
|
}
|
||||||
if (!value) {
|
if (!value) {
|
||||||
|
@ -1190,6 +1204,10 @@ public class NotificationsController {
|
||||||
notificationsQueue.postRunnable(new Runnable() {
|
notificationsQueue.postRunnable(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
if (lastBadgeCount == count) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastBadgeCount = count;
|
||||||
try {
|
try {
|
||||||
ContentValues cv = new ContentValues();
|
ContentValues cv = new ContentValues();
|
||||||
cv.put("tag", "org.telegram.messenger/org.telegram.ui.LaunchActivity");
|
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 cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName + ".jpg");
|
||||||
File cacheFile2 = FileLoader.getPathToAttach(size);
|
File cacheFile2 = FileLoader.getPathToAttach(size);
|
||||||
cacheFile.renameTo(cacheFile2);
|
cacheFile.renameTo(cacheFile2);
|
||||||
ImageLoader.getInstance().replaceImageInCache(fileName, fileName2);
|
ImageLoader.getInstance().replaceImageInCache(fileName, fileName2, size.location);
|
||||||
ArrayList<TLRPC.Message> arr = new ArrayList<>();
|
ArrayList<TLRPC.Message> arr = new ArrayList<>();
|
||||||
arr.add(newMsg);
|
arr.add(newMsg);
|
||||||
MessagesStorage.getInstance().putMessages(arr, false, true, false, 0);
|
MessagesStorage.getInstance().putMessages(arr, false, true, false, 0);
|
||||||
|
@ -557,7 +557,7 @@ public class SecretChatHelper {
|
||||||
newMsg.media.video.w = video.w;
|
newMsg.media.video.w = video.w;
|
||||||
newMsg.media.video.h = video.h;
|
newMsg.media.video.h = video.h;
|
||||||
newMsg.media.video.date = video.date;
|
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.user_id = video.user_id;
|
||||||
newMsg.media.video.size = file.size;
|
newMsg.media.video.size = file.size;
|
||||||
newMsg.media.video.id = file.id;
|
newMsg.media.video.id = file.id;
|
||||||
|
@ -565,6 +565,7 @@ public class SecretChatHelper {
|
||||||
newMsg.media.video.key = decryptedMessage.media.key;
|
newMsg.media.video.key = decryptedMessage.media.key;
|
||||||
newMsg.media.video.iv = decryptedMessage.media.iv;
|
newMsg.media.video.iv = decryptedMessage.media.iv;
|
||||||
newMsg.media.video.mime_type = video.mime_type;
|
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())) {
|
if (newMsg.attachPath != null && newMsg.attachPath.startsWith(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE).getAbsolutePath())) {
|
||||||
File cacheFile = new File(newMsg.attachPath);
|
File cacheFile = new File(newMsg.attachPath);
|
||||||
|
@ -893,10 +894,10 @@ public class SecretChatHelper {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
newMessage.media = new TLRPC.TL_messageMediaPhoto();
|
newMessage.media = new TLRPC.TL_messageMediaPhoto();
|
||||||
|
newMessage.media.caption = "";
|
||||||
newMessage.media.photo = new TLRPC.TL_photo();
|
newMessage.media.photo = new TLRPC.TL_photo();
|
||||||
newMessage.media.photo.user_id = newMessage.from_id;
|
newMessage.media.photo.user_id = newMessage.from_id;
|
||||||
newMessage.media.photo.date = newMessage.date;
|
newMessage.media.photo.date = newMessage.date;
|
||||||
newMessage.media.photo.caption = "";
|
|
||||||
newMessage.media.photo.geo = new TLRPC.TL_geoPointEmpty();
|
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) {
|
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();
|
TLRPC.TL_photoCachedSize small = new TLRPC.TL_photoCachedSize();
|
||||||
|
@ -926,6 +927,7 @@ public class SecretChatHelper {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
newMessage.media = new TLRPC.TL_messageMediaVideo();
|
newMessage.media = new TLRPC.TL_messageMediaVideo();
|
||||||
|
newMessage.media.caption = "";
|
||||||
newMessage.media.video = new TLRPC.TL_videoEncrypted();
|
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) {
|
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();
|
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.w = decryptedMessage.media.w;
|
||||||
newMessage.media.video.h = decryptedMessage.media.h;
|
newMessage.media.video.h = decryptedMessage.media.h;
|
||||||
newMessage.media.video.date = date;
|
newMessage.media.video.date = date;
|
||||||
newMessage.media.video.caption = "";
|
|
||||||
newMessage.media.video.user_id = from_id;
|
newMessage.media.video.user_id = from_id;
|
||||||
newMessage.media.video.size = file.size;
|
newMessage.media.video.size = file.size;
|
||||||
newMessage.media.video.id = file.id;
|
newMessage.media.video.id = file.id;
|
||||||
|
@ -951,6 +952,7 @@ public class SecretChatHelper {
|
||||||
newMessage.media.video.key = decryptedMessage.media.key;
|
newMessage.media.video.key = decryptedMessage.media.key;
|
||||||
newMessage.media.video.iv = decryptedMessage.media.iv;
|
newMessage.media.video.iv = decryptedMessage.media.iv;
|
||||||
newMessage.media.video.mime_type = decryptedMessage.media.mime_type;
|
newMessage.media.video.mime_type = decryptedMessage.media.mime_type;
|
||||||
|
newMessage.media.video.caption = "";
|
||||||
if (newMessage.ttl != 0) {
|
if (newMessage.ttl != 0) {
|
||||||
newMessage.ttl = Math.max(newMessage.media.video.duration + 1, newMessage.ttl);
|
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);
|
ByteBufferDesc is = BuffersStorage.getInstance().getFreeBuffer(message.bytes.length);
|
||||||
is.writeRaw(message.bytes);
|
is.writeRaw(message.bytes);
|
||||||
is.position(0);
|
is.position(0);
|
||||||
long fingerprint = is.readInt64();
|
long fingerprint = is.readInt64(false);
|
||||||
byte[] keyToDecrypt = null;
|
byte[] keyToDecrypt = null;
|
||||||
boolean new_key_used = false;
|
boolean new_key_used = false;
|
||||||
if (chat.key_fingerprint == fingerprint) {
|
if (chat.key_fingerprint == fingerprint) {
|
||||||
|
@ -1311,12 +1313,12 @@ public class SecretChatHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyToDecrypt != null) {
|
if (keyToDecrypt != null) {
|
||||||
byte[] messageKey = is.readData(16);
|
byte[] messageKey = is.readData(16, false);
|
||||||
MessageKeyData keyData = Utilities.generateMessageKeyData(keyToDecrypt, messageKey, false);
|
MessageKeyData keyData = Utilities.generateMessageKeyData(keyToDecrypt, messageKey, false);
|
||||||
|
|
||||||
Utilities.aesIgeEncryption(is.buffer, keyData.aesKey, keyData.aesIv, false, false, 24, is.limit() - 24);
|
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) {
|
if (len < 0 || len > is.limit() - 28) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1325,7 +1327,13 @@ public class SecretChatHelper {
|
||||||
return null;
|
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);
|
BuffersStorage.getInstance().reuseFreeBuffer(is);
|
||||||
if (!new_key_used && AndroidUtilities.getPeerLayerVersion(chat.layer) >= 20) {
|
if (!new_key_used && AndroidUtilities.getPeerLayerVersion(chat.layer) >= 20) {
|
||||||
chat.key_use_count_in++;
|
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, MessageObject> unsentMessages = new HashMap<>();
|
||||||
private HashMap<Integer, TLRPC.Message> sendingMessages = new HashMap<>();
|
private HashMap<Integer, TLRPC.Message> sendingMessages = new HashMap<>();
|
||||||
|
|
||||||
private class DelayedMessage {
|
protected class DelayedMessage {
|
||||||
public TLObject sendRequest;
|
public TLObject sendRequest;
|
||||||
public TLRPC.TL_decryptedMessage sendEncryptedRequest;
|
public TLRPC.TL_decryptedMessage sendEncryptedRequest;
|
||||||
public int type;
|
public int type;
|
||||||
|
@ -458,8 +458,8 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
sendMessage(video, null, messageObject.messageOwner.attachPath, did, messageObject.replyMessageObject);
|
sendMessage(video, null, messageObject.messageOwner.attachPath, did, messageObject.replyMessageObject);
|
||||||
} else if (messageObject.messageOwner.media.document instanceof TLRPC.TL_document) {
|
} 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);
|
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) {
|
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaVenue || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo) {
|
||||||
sendMessage(messageObject.messageOwner.media.geo.lat, messageObject.messageOwner.media.geo._long, did, messageObject.replyMessageObject);
|
sendMessage(messageObject.messageOwner.media, did, messageObject.replyMessageObject);
|
||||||
} else if (messageObject.messageOwner.media.phone_number != null) {
|
} else if (messageObject.messageOwner.media.phone_number != null) {
|
||||||
TLRPC.User user = new TLRPC.TL_userContact();
|
TLRPC.User user = new TLRPC.TL_userContact();
|
||||||
user.phone = messageObject.messageOwner.media.phone_number;
|
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) {
|
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) {
|
public void sendMessage(ArrayList<MessageObject> messages, long peer) {
|
||||||
|
@ -550,6 +595,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
ids.add(newMsg.fwd_msg_id);
|
ids.add(newMsg.fwd_msg_id);
|
||||||
newMsg.date = ConnectionsManager.getInstance().getCurrentTime();
|
newMsg.date = ConnectionsManager.getInstance().getCurrentTime();
|
||||||
newMsg.flags |= TLRPC.MESSAGE_FLAG_UNREAD;
|
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.dialog_id = peer;
|
||||||
newMsg.to_id = to_id;
|
newMsg.to_id = to_id;
|
||||||
MessageObject newMsgObj = new MessageObject(newMsg, null, true);
|
MessageObject newMsgObj = new MessageObject(newMsg, null, true);
|
||||||
|
@ -560,7 +608,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
putToSendingMessages(newMsg);
|
putToSendingMessages(newMsg);
|
||||||
|
|
||||||
if (arr.size() == 100 || a == messages.size() - 1) {
|
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);
|
MessagesController.getInstance().updateInterfaceWithMessages(peer, objArr);
|
||||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload);
|
NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload);
|
||||||
UserConfig.saveConfig(false);
|
UserConfig.saveConfig(false);
|
||||||
|
@ -655,38 +703,38 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendMessage(MessageObject message) {
|
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) {
|
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) {
|
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) {
|
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) {
|
public void sendMessage(TLRPC.MessageMedia location, 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);
|
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) {
|
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) {
|
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) {
|
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) {
|
if (peer == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -713,8 +761,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
type = 0;
|
type = 0;
|
||||||
}
|
}
|
||||||
} else if (msgObj.type == 4) {
|
} else if (msgObj.type == 4) {
|
||||||
lat = newMsg.media.geo.lat;
|
location = newMsg.media;
|
||||||
lon = newMsg.media.geo._long;
|
|
||||||
type = 1;
|
type = 1;
|
||||||
} else if (msgObj.type == 1) {
|
} else if (msgObj.type == 1) {
|
||||||
if (msgObj.isForwarded()) {
|
if (msgObj.isForwarded()) {
|
||||||
|
@ -760,16 +807,13 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
}
|
}
|
||||||
type = 0;
|
type = 0;
|
||||||
newMsg.message = message;
|
newMsg.message = message;
|
||||||
} else if (lat != null && lon != null) {
|
} else if (location != null) {
|
||||||
if (encryptedChat != null && AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) {
|
if (encryptedChat != null && AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) {
|
||||||
newMsg = new TLRPC.TL_message_secret();
|
newMsg = new TLRPC.TL_message_secret();
|
||||||
} else {
|
} else {
|
||||||
newMsg = new TLRPC.TL_message();
|
newMsg = new TLRPC.TL_message();
|
||||||
}
|
}
|
||||||
newMsg.media = new TLRPC.TL_messageMediaGeo();
|
newMsg.media = location;
|
||||||
newMsg.media.geo = new TLRPC.TL_geoPoint();
|
|
||||||
newMsg.media.geo.lat = lat;
|
|
||||||
newMsg.media.geo._long = lon;
|
|
||||||
newMsg.message = "";
|
newMsg.message = "";
|
||||||
type = 1;
|
type = 1;
|
||||||
} else if (photo != null) {
|
} else if (photo != null) {
|
||||||
|
@ -779,6 +823,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
newMsg = new TLRPC.TL_message();
|
newMsg = new TLRPC.TL_message();
|
||||||
}
|
}
|
||||||
newMsg.media = new TLRPC.TL_messageMediaPhoto();
|
newMsg.media = new TLRPC.TL_messageMediaPhoto();
|
||||||
|
newMsg.media.caption = photo.caption != null ? photo.caption : "";
|
||||||
newMsg.media.photo = photo;
|
newMsg.media.photo = photo;
|
||||||
type = 2;
|
type = 2;
|
||||||
newMsg.message = "-1";
|
newMsg.message = "-1";
|
||||||
|
@ -795,6 +840,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
newMsg = new TLRPC.TL_message();
|
newMsg = new TLRPC.TL_message();
|
||||||
}
|
}
|
||||||
newMsg.media = new TLRPC.TL_messageMediaVideo();
|
newMsg.media = new TLRPC.TL_messageMediaVideo();
|
||||||
|
newMsg.media.caption = video.caption != null ? video.caption : "";
|
||||||
newMsg.media.video = video;
|
newMsg.media.video = video;
|
||||||
newMsg.videoEditedInfo = video.videoEditedInfo;
|
newMsg.videoEditedInfo = video.videoEditedInfo;
|
||||||
type = 3;
|
type = 3;
|
||||||
|
@ -868,6 +914,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
}
|
}
|
||||||
newMsg.date = ConnectionsManager.getInstance().getCurrentTime();
|
newMsg.date = ConnectionsManager.getInstance().getCurrentTime();
|
||||||
newMsg.flags |= TLRPC.MESSAGE_FLAG_UNREAD;
|
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;
|
newMsg.dialog_id = peer;
|
||||||
if (reply_to_msg != null) {
|
if (reply_to_msg != null) {
|
||||||
newMsg.flags |= TLRPC.MESSAGE_FLAG_REPLY;
|
newMsg.flags |= TLRPC.MESSAGE_FLAG_REPLY;
|
||||||
|
@ -993,13 +1042,22 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
TLRPC.InputMedia inputMedia = null;
|
TLRPC.InputMedia inputMedia = null;
|
||||||
DelayedMessage delayedMessage = null;
|
DelayedMessage delayedMessage = null;
|
||||||
if (type == 1) {
|
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 = new TLRPC.TL_inputGeoPoint();
|
||||||
inputMedia.geo_point.lat = lat;
|
inputMedia.geo_point.lat = location.geo.lat;
|
||||||
inputMedia.geo_point._long = lon;
|
inputMedia.geo_point._long = location.geo._long;
|
||||||
} else if (type == 2) {
|
} else if (type == 2) {
|
||||||
if (photo.access_hash == 0) {
|
if (photo.access_hash == 0) {
|
||||||
inputMedia = new TLRPC.TL_inputMediaUploadedPhoto();
|
inputMedia = new TLRPC.TL_inputMediaUploadedPhoto();
|
||||||
|
inputMedia.caption = photo.caption != null ? photo.caption : "";
|
||||||
delayedMessage = new DelayedMessage();
|
delayedMessage = new DelayedMessage();
|
||||||
delayedMessage.originalPath = originalPath;
|
delayedMessage.originalPath = originalPath;
|
||||||
delayedMessage.type = 0;
|
delayedMessage.type = 0;
|
||||||
|
@ -1012,6 +1070,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
} else {
|
} else {
|
||||||
TLRPC.TL_inputMediaPhoto media = new TLRPC.TL_inputMediaPhoto();
|
TLRPC.TL_inputMediaPhoto media = new TLRPC.TL_inputMediaPhoto();
|
||||||
media.id = new TLRPC.TL_inputPhoto();
|
media.id = new TLRPC.TL_inputPhoto();
|
||||||
|
media.caption = photo.caption != null ? photo.caption : "";
|
||||||
media.id.id = photo.id;
|
media.id.id = photo.id;
|
||||||
media.id.access_hash = photo.access_hash;
|
media.id.access_hash = photo.access_hash;
|
||||||
inputMedia = media;
|
inputMedia = media;
|
||||||
|
@ -1023,6 +1082,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
} else {
|
} else {
|
||||||
inputMedia = new TLRPC.TL_inputMediaUploadedVideo();
|
inputMedia = new TLRPC.TL_inputMediaUploadedVideo();
|
||||||
}
|
}
|
||||||
|
inputMedia.caption = video.caption != null ? video.caption : "";
|
||||||
inputMedia.duration = video.duration;
|
inputMedia.duration = video.duration;
|
||||||
inputMedia.w = video.w;
|
inputMedia.w = video.w;
|
||||||
inputMedia.h = video.h;
|
inputMedia.h = video.h;
|
||||||
|
@ -1036,6 +1096,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
} else {
|
} else {
|
||||||
TLRPC.TL_inputMediaVideo media = new TLRPC.TL_inputMediaVideo();
|
TLRPC.TL_inputMediaVideo media = new TLRPC.TL_inputMediaVideo();
|
||||||
media.id = new TLRPC.TL_inputVideo();
|
media.id = new TLRPC.TL_inputVideo();
|
||||||
|
media.caption = video.caption != null ? video.caption : "";
|
||||||
media.id.id = video.id;
|
media.id.id = video.id;
|
||||||
media.id.access_hash = video.access_hash;
|
media.id.access_hash = video.access_hash;
|
||||||
inputMedia = media;
|
inputMedia = media;
|
||||||
|
@ -1161,8 +1222,8 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
reqSend.message = "";
|
reqSend.message = "";
|
||||||
if (type == 1) {
|
if (type == 1) {
|
||||||
reqSend.media = new TLRPC.TL_decryptedMessageMediaGeoPoint();
|
reqSend.media = new TLRPC.TL_decryptedMessageMediaGeoPoint();
|
||||||
reqSend.media.lat = lat;
|
reqSend.media.lat = location.geo.lat;
|
||||||
reqSend.media._long = lon;
|
reqSend.media._long = location.geo._long;
|
||||||
SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null);
|
SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null);
|
||||||
} else if (type == 2) {
|
} else if (type == 2) {
|
||||||
TLRPC.PhotoSize small = photo.sizes.get(0);
|
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");
|
cacheFile2 = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName2 + ".jpg");
|
||||||
}
|
}
|
||||||
cacheFile.renameTo(cacheFile2);
|
cacheFile.renameTo(cacheFile2);
|
||||||
ImageLoader.getInstance().replaceImageInCache(fileName, fileName2);
|
ImageLoader.getInstance().replaceImageInCache(fileName, fileName2, size.location);
|
||||||
size2.location = size.location;
|
size2.location = size.location;
|
||||||
break;
|
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 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");
|
File cacheFile2 = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName2 + ".jpg");
|
||||||
cacheFile.renameTo(cacheFile2);
|
cacheFile.renameTo(cacheFile2);
|
||||||
ImageLoader.getInstance().replaceImageInCache(fileName, fileName2);
|
ImageLoader.getInstance().replaceImageInCache(fileName, fileName2, size.location);
|
||||||
size2.location = 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 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");
|
File cacheFile2 = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName2 + ".jpg");
|
||||||
cacheFile.renameTo(cacheFile2);
|
cacheFile.renameTo(cacheFile2);
|
||||||
ImageLoader.getInstance().replaceImageInCache(fileName, fileName2);
|
ImageLoader.getInstance().replaceImageInCache(fileName, fileName2, size.location);
|
||||||
size2.location = size.location;
|
size2.location = size.location;
|
||||||
}
|
}
|
||||||
} else if (MessageObject.isStickerMessage(sentMessage) && size2.location != null) {
|
} else if (MessageObject.isStickerMessage(sentMessage) && size2.location != null) {
|
||||||
|
@ -1697,6 +1758,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
arrayList.add(message);
|
arrayList.add(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ArrayList<DelayedMessage> getDelayedMessages(String location) {
|
||||||
|
return delayedMessages.get(location);
|
||||||
|
}
|
||||||
|
|
||||||
protected long getNextRandomId() {
|
protected long getNextRandomId() {
|
||||||
long val = 0;
|
long val = 0;
|
||||||
while (val == 0) {
|
while (val == 0) {
|
||||||
|
@ -1749,7 +1814,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
photo.user_id = UserConfig.getClientUserId();
|
photo.user_id = UserConfig.getClientUserId();
|
||||||
photo.date = ConnectionsManager.getInstance().getCurrentTime();
|
photo.date = ConnectionsManager.getInstance().getCurrentTime();
|
||||||
photo.sizes = sizes;
|
photo.sizes = sizes;
|
||||||
photo.caption = "";
|
|
||||||
photo.geo = new TLRPC.TL_geoPointEmpty();
|
photo.geo = new TLRPC.TL_geoPointEmpty();
|
||||||
return photo;
|
return photo;
|
||||||
}
|
}
|
||||||
|
@ -1811,11 +1875,15 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
document.size = (int)f.length();
|
document.size = (int)f.length();
|
||||||
document.dc_id = 0;
|
document.dc_id = 0;
|
||||||
if (ext.length() != 0) {
|
if (ext.length() != 0) {
|
||||||
String mimeType = myMime.getMimeTypeFromExtension(ext.toLowerCase());
|
if (ext.toLowerCase().equals("webp")) {
|
||||||
if (mimeType != null) {
|
document.mime_type = "image/webp";
|
||||||
document.mime_type = mimeType;
|
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
document.mime_type = "application/octet-stream";
|
document.mime_type = "application/octet-stream";
|
||||||
|
@ -1929,9 +1997,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
}).start();
|
}).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<String> paths = null;
|
||||||
ArrayList<Uri> uris = null;
|
ArrayList<Uri> uris = null;
|
||||||
|
ArrayList<String> captions = null;
|
||||||
if (imageFilePath != null && imageFilePath.length() != 0) {
|
if (imageFilePath != null && imageFilePath.length() != 0) {
|
||||||
paths = new ArrayList<>();
|
paths = new ArrayList<>();
|
||||||
paths.add(imageFilePath);
|
paths.add(imageFilePath);
|
||||||
|
@ -1940,7 +2009,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
uris = new ArrayList<>();
|
uris = new ArrayList<>();
|
||||||
uris.add(imageUri);
|
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) {
|
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
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
boolean isEncrypted = (int)dialog_id == 0;
|
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) {
|
if (searchImage.type == 1) {
|
||||||
TLRPC.TL_document document = null;
|
TLRPC.TL_document document = null;
|
||||||
if (!isEncrypted) {
|
if (!isEncrypted) {
|
||||||
|
@ -2029,7 +2103,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
photo = new TLRPC.TL_photo();
|
photo = new TLRPC.TL_photo();
|
||||||
photo.user_id = UserConfig.getClientUserId();
|
photo.user_id = UserConfig.getClientUserId();
|
||||||
photo.date = ConnectionsManager.getInstance().getCurrentTime();
|
photo.date = ConnectionsManager.getInstance().getCurrentTime();
|
||||||
photo.caption = "";
|
|
||||||
photo.geo = new TLRPC.TL_geoPointEmpty();
|
photo.geo = new TLRPC.TL_geoPointEmpty();
|
||||||
TLRPC.TL_photoSize photoSize = new TLRPC.TL_photoSize();
|
TLRPC.TL_photoSize photoSize = new TLRPC.TL_photoSize();
|
||||||
photoSize.w = searchImage.width;
|
photoSize.w = searchImage.width;
|
||||||
|
@ -2042,6 +2115,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (photo != null) {
|
if (photo != null) {
|
||||||
|
if (searchImage.caption != null) {
|
||||||
|
photo.caption = searchImage.caption.toString();
|
||||||
|
}
|
||||||
final String originalPathFinal = searchImage.imageUrl;
|
final String originalPathFinal = searchImage.imageUrl;
|
||||||
final TLRPC.TL_photo photoFinal = photo;
|
final TLRPC.TL_photo photoFinal = photo;
|
||||||
final boolean needDownloadHttpFinal = needDownloadHttp;
|
final boolean needDownloadHttpFinal = needDownloadHttp;
|
||||||
|
@ -2058,7 +2134,32 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
}).start();
|
}).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()) {
|
if (paths == null && uris == null || paths != null && paths.isEmpty() || uris != null && uris.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2080,6 +2181,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
int count = !pathsCopy.isEmpty() ? pathsCopy.size() : urisCopy.size();
|
int count = !pathsCopy.isEmpty() ? pathsCopy.size() : urisCopy.size();
|
||||||
String path = null;
|
String path = null;
|
||||||
Uri uri = null;
|
Uri uri = null;
|
||||||
|
String extension = null;
|
||||||
for (int a = 0; a < count; a++) {
|
for (int a = 0; a < count; a++) {
|
||||||
if (!pathsCopy.isEmpty()) {
|
if (!pathsCopy.isEmpty()) {
|
||||||
path = pathsCopy.get(a);
|
path = pathsCopy.get(a);
|
||||||
|
@ -2096,16 +2198,23 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
|
|
||||||
boolean isDocument = false;
|
boolean isDocument = false;
|
||||||
if (tempPath != null && (tempPath.endsWith(".gif") || tempPath.endsWith(".webp"))) {
|
if (tempPath != null && (tempPath.endsWith(".gif") || tempPath.endsWith(".webp"))) {
|
||||||
|
if (tempPath.endsWith(".gif")) {
|
||||||
|
extension = "gif";
|
||||||
|
} else {
|
||||||
|
extension = "webp";
|
||||||
|
}
|
||||||
isDocument = true;
|
isDocument = true;
|
||||||
} else if (tempPath == null && uri != null) {
|
} else if (tempPath == null && uri != null) {
|
||||||
if (MediaController.isGif(uri)) {
|
if (MediaController.isGif(uri)) {
|
||||||
isDocument = true;
|
isDocument = true;
|
||||||
originalPath = uri.toString();
|
originalPath = uri.toString();
|
||||||
tempPath = MediaController.copyDocumentToCache(uri, "gif");
|
tempPath = MediaController.copyDocumentToCache(uri, "gif");
|
||||||
|
extension = "gif";
|
||||||
} else if (MediaController.isWebp(uri)) {
|
} else if (MediaController.isWebp(uri)) {
|
||||||
isDocument = true;
|
isDocument = true;
|
||||||
originalPath = uri.toString();
|
originalPath = uri.toString();
|
||||||
tempPath = MediaController.copyDocumentToCache(uri, "webp");
|
tempPath = MediaController.copyDocumentToCache(uri, "webp");
|
||||||
|
extension = "webp";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2134,6 +2243,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
photo = SendMessagesHelper.getInstance().generatePhotoSizes(path, uri);
|
photo = SendMessagesHelper.getInstance().generatePhotoSizes(path, uri);
|
||||||
}
|
}
|
||||||
if (photo != null) {
|
if (photo != null) {
|
||||||
|
if (captions != null) {
|
||||||
|
photo.caption = captions.get(a);
|
||||||
|
}
|
||||||
final String originalPathFinal = originalPath;
|
final String originalPathFinal = originalPath;
|
||||||
final TLRPC.TL_photo photoFinal = photo;
|
final TLRPC.TL_photo photoFinal = photo;
|
||||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||||
|
@ -2147,7 +2259,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||||
}
|
}
|
||||||
if (sendAsDocuments != null && !sendAsDocuments.isEmpty()) {
|
if (sendAsDocuments != null && !sendAsDocuments.isEmpty()) {
|
||||||
for (int a = 0; a < sendAsDocuments.size(); a++) {
|
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 {
|
} else {
|
||||||
video.thumb.type = "s";
|
video.thumb.type = "s";
|
||||||
}
|
}
|
||||||
video.caption = "";
|
|
||||||
video.mime_type = "video/mp4";
|
video.mime_type = "video/mp4";
|
||||||
video.id = 0;
|
video.id = 0;
|
||||||
UserConfig.saveConfig(false);
|
UserConfig.saveConfig(false);
|
||||||
|
|
|
@ -21,7 +21,6 @@ import org.telegram.messenger.ByteBufferDesc;
|
||||||
import org.telegram.messenger.ConnectionsManager;
|
import org.telegram.messenger.ConnectionsManager;
|
||||||
import org.telegram.messenger.FileLog;
|
import org.telegram.messenger.FileLog;
|
||||||
import org.telegram.messenger.RPCRequest;
|
import org.telegram.messenger.RPCRequest;
|
||||||
import org.telegram.messenger.TLClassStore;
|
|
||||||
import org.telegram.messenger.TLObject;
|
import org.telegram.messenger.TLObject;
|
||||||
import org.telegram.messenger.TLRPC;
|
import org.telegram.messenger.TLRPC;
|
||||||
|
|
||||||
|
@ -65,7 +64,7 @@ public class ReplyMessageQuery {
|
||||||
while (cursor.next()) {
|
while (cursor.next()) {
|
||||||
ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0));
|
ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0));
|
||||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 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.id = cursor.intValue(1);
|
||||||
message.date = cursor.intValue(2);
|
message.date = cursor.intValue(2);
|
||||||
message.dialog_id = dialog_id;
|
message.dialog_id = dialog_id;
|
||||||
|
|
|
@ -20,7 +20,6 @@ import org.telegram.messenger.ByteBufferDesc;
|
||||||
import org.telegram.messenger.ConnectionsManager;
|
import org.telegram.messenger.ConnectionsManager;
|
||||||
import org.telegram.messenger.FileLog;
|
import org.telegram.messenger.FileLog;
|
||||||
import org.telegram.messenger.RPCRequest;
|
import org.telegram.messenger.RPCRequest;
|
||||||
import org.telegram.messenger.TLClassStore;
|
|
||||||
import org.telegram.messenger.TLObject;
|
import org.telegram.messenger.TLObject;
|
||||||
import org.telegram.messenger.TLRPC;
|
import org.telegram.messenger.TLRPC;
|
||||||
|
|
||||||
|
@ -178,7 +177,7 @@ public class SharedMediaQuery {
|
||||||
}
|
}
|
||||||
final ArrayList<MessageObject> objects = new ArrayList<>();
|
final ArrayList<MessageObject> objects = new ArrayList<>();
|
||||||
for (TLRPC.Message message : res.messages) {
|
for (TLRPC.Message message : res.messages) {
|
||||||
objects.add(new MessageObject(message, usersLocal, false));
|
objects.add(new MessageObject(message, usersLocal, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||||
|
@ -324,7 +323,7 @@ public class SharedMediaQuery {
|
||||||
while (cursor.next()) {
|
while (cursor.next()) {
|
||||||
ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0));
|
ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0));
|
||||||
if (data != null && cursor.byteBufferValue(0, data.buffer) != 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.id = cursor.intValue(1);
|
||||||
message.dialog_id = uid;
|
message.dialog_id = uid;
|
||||||
if ((int)uid == 0) {
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|