Browse Source

Convert some more classes to kotlin

renovate/org.robolectric-robolectric-4.x
Ammar Githam 4 years ago
parent
commit
17fb608c34
  1. 5
      app/src/main/java/awais/instagrabber/InstaGrabberApplication.java
  2. 2
      app/src/main/java/awais/instagrabber/customviews/FormattedNumberTextView.java
  3. 5
      app/src/main/java/awais/instagrabber/customviews/Tooltip.java
  4. 228
      app/src/main/java/awais/instagrabber/utils/CombinedDrawable.kt
  5. 213
      app/src/main/java/awais/instagrabber/utils/CookieUtils.kt
  6. 33
      app/src/main/java/awais/instagrabber/utils/CubicInterpolation.java
  7. 43
      app/src/main/java/awais/instagrabber/utils/DataBox.java
  8. 74
      app/src/main/java/awais/instagrabber/utils/DeepLinkParser.kt
  9. 156
      app/src/main/java/awais/instagrabber/utils/LocaleUtils.kt
  10. 3
      app/src/main/java/awais/instagrabber/utils/MediaUploadHelper.kt
  11. 87
      app/src/main/java/awais/instagrabber/utils/NullSafePair.kt
  12. 202
      app/src/main/java/awais/instagrabber/utils/NumberUtils.kt
  13. 90
      app/src/main/java/awais/instagrabber/utils/RankedRecipientsCache.kt
  14. 25
      app/src/main/java/awais/instagrabber/utils/SerializablePair.kt
  15. 70
      app/src/main/java/awais/instagrabber/utils/UpdateCheckCommon.kt
  16. 155
      app/src/main/java/awais/instagrabber/utils/UserAgentUtils.kt
  17. 212
      app/src/main/java/awais/instagrabber/utils/ViewUtils.kt
  18. 12
      app/src/main/java/awais/instagrabber/utils/extensions/AnyExtensions.kt
  19. 4
      app/src/main/java/awais/instagrabber/viewmodels/UserSearchViewModel.java

5
app/src/main/java/awais/instagrabber/InstaGrabberApplication.java

@ -27,9 +27,6 @@ import static awais.instagrabber.utils.Utils.clipboardManager;
import static awais.instagrabber.utils.Utils.datetimeParser;
import static awais.instagrabber.utils.Utils.settingsHelper;
//import awaisomereport.LogCollector;
//import static awais.instagrabber.utils.Utils.logCollector;
public final class InstaGrabberApplication extends Application {
private static final String TAG = "InstaGrabberApplication";
@ -56,7 +53,7 @@ public final class InstaGrabberApplication extends Application {
Log.e(TAG, "Error", e);
}
}
// final Set<RequestListener> requestListeners = new HashSet<>();
// requestListeners.add(new RequestLoggingListener());
final ImagePipelineConfig imagePipelineConfig = ImagePipelineConfig

2
app/src/main/java/awais/instagrabber/customviews/FormattedNumberTextView.java

@ -153,7 +153,7 @@ public class FormattedNumberTextView extends AppCompatTextView {
return;
}
if (showAbbreviation) {
setText(NumberUtils.abbreviate(number));
setText(NumberUtils.abbreviate(number, null));
return;
}
setText(String.valueOf(number));

5
app/src/main/java/awais/instagrabber/customviews/Tooltip.java

@ -16,6 +16,8 @@ import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.utils.ViewUtils;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
public class Tooltip extends AppCompatTextView {
private View anchor;
@ -40,8 +42,7 @@ public class Tooltip extends AppCompatTextView {
setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
setPadding(Utils.convertDpToPx(8), Utils.convertDpToPx(7), Utils.convertDpToPx(8), Utils.convertDpToPx(7));
setGravity(Gravity.CENTER_VERTICAL);
parentView.addView(this, ViewUtils.createFrame(
ViewUtils.WRAP_CONTENT, ViewUtils.WRAP_CONTENT, Gravity.START | Gravity.TOP, 5, 0, 5, 3));
parentView.addView(this, ViewUtils.createFrame(WRAP_CONTENT, WRAP_CONTENT, Gravity.START | Gravity.TOP, 5, 0, 5, 3));
setVisibility(GONE);
}

228
app/src/main/java/awais/instagrabber/utils/CombinedDrawable.kt

@ -1,185 +1,157 @@
package awais.instagrabber.utils;/*
/*
* This is the source code of Telegram for Android v. 5.x.x.
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* <p>
* Copyright Nikolai Kudashov, 2013-2018.
*/
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
public class CombinedDrawable extends Drawable implements Drawable.Callback {
private final Drawable background;
private final Drawable icon;
private int left;
private int top;
private int iconWidth;
private int iconHeight;
private int backWidth;
private int backHeight;
private int offsetX;
private int offsetY;
private boolean fullSize;
public CombinedDrawable(Drawable backgroundDrawable, Drawable iconDrawable, int leftOffset, int topOffset) {
background = backgroundDrawable;
icon = iconDrawable;
left = leftOffset;
top = topOffset;
package awais.instagrabber.utils
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.PixelFormat
import android.graphics.drawable.Drawable
class CombinedDrawable : Drawable, Drawable.Callback {
val background: Drawable
val icon: Drawable?
private var left = 0
private var top = 0
private var iconWidth = 0
private var iconHeight = 0
private var backWidth = 0
private var backHeight = 0
private var offsetX = 0
private var offsetY = 0
private var fullSize = false
constructor(backgroundDrawable: Drawable, iconDrawable: Drawable?, leftOffset: Int, topOffset: Int) {
background = backgroundDrawable
icon = iconDrawable
left = leftOffset
top = topOffset
if (iconDrawable != null) {
iconDrawable.setCallback(this);
iconDrawable.callback = this
}
}
public CombinedDrawable(Drawable backgroundDrawable, Drawable iconDrawable) {
background = backgroundDrawable;
icon = iconDrawable;
constructor(backgroundDrawable: Drawable, iconDrawable: Drawable?) {
background = backgroundDrawable
icon = iconDrawable
if (iconDrawable != null) {
iconDrawable.setCallback(this);
iconDrawable.callback = this
}
}
public void setIconSize(int width, int height) {
iconWidth = width;
iconHeight = height;
}
public void setCustomSize(int width, int height) {
backWidth = width;
backHeight = height;
fun setIconSize(width: Int, height: Int) {
iconWidth = width
iconHeight = height
}
public void setIconOffset(int x, int y) {
offsetX = x;
offsetY = y;
fun setCustomSize(width: Int, height: Int) {
backWidth = width
backHeight = height
}
public Drawable getIcon() {
return icon;
fun setIconOffset(x: Int, y: Int) {
offsetX = x
offsetY = y
}
public Drawable getBackground() {
return background;
fun setFullsize(value: Boolean) {
fullSize = value
}
public void setFullsize(boolean value) {
fullSize = value;
override fun setColorFilter(colorFilter: ColorFilter?) {
icon?.colorFilter = colorFilter
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
icon.setColorFilter(colorFilter);
override fun isStateful(): Boolean {
return icon?.isStateful ?: false
}
@Override
public boolean isStateful() {
return icon.isStateful();
override fun setState(stateSet: IntArray): Boolean {
icon?.state = stateSet
return true
}
@Override
public boolean setState(@NonNull int[] stateSet) {
icon.setState(stateSet);
return true;
override fun getState(): IntArray {
return icon?.state ?: super.getState()
}
@NonNull
@Override
public int[] getState() {
return icon.getState();
override fun onStateChange(state: IntArray): Boolean {
return true
}
@Override
protected boolean onStateChange(int[] state) {
return true;
override fun jumpToCurrentState() {
icon?.jumpToCurrentState()
}
@Override
public void jumpToCurrentState() {
icon.jumpToCurrentState();
override fun getConstantState(): ConstantState? {
return icon?.constantState
}
@Override
public ConstantState getConstantState() {
return icon.getConstantState();
}
@Override
public void draw(@NonNull Canvas canvas) {
background.setBounds(getBounds());
background.draw(canvas);
if (icon != null) {
if (fullSize) {
android.graphics.Rect bounds = getBounds();
if (left != 0) {
icon.setBounds(bounds.left + left, bounds.top + top, bounds.right - left, bounds.bottom - top);
} else {
icon.setBounds(bounds);
}
override fun draw(canvas: Canvas) {
background.bounds = bounds
background.draw(canvas)
if (icon == null) return
if (fullSize) {
val bounds = bounds
if (left != 0) {
icon.setBounds(bounds.left + left, bounds.top + top, bounds.right - left, bounds.bottom - top)
} else {
icon.bounds = bounds
}
} else {
val x: Int
val y: Int
if (iconWidth != 0) {
x = bounds.centerX() - iconWidth / 2 + left + offsetX
y = bounds.centerY() - iconHeight / 2 + top + offsetY
icon.setBounds(x, y, x + iconWidth, y + iconHeight)
} else {
int x;
int y;
if (iconWidth != 0) {
x = getBounds().centerX() - iconWidth / 2 + left + offsetX;
y = getBounds().centerY() - iconHeight / 2 + top + offsetY;
icon.setBounds(x, y, x + iconWidth, y + iconHeight);
} else {
x = getBounds().centerX() - icon.getIntrinsicWidth() / 2 + left;
y = getBounds().centerY() - icon.getIntrinsicHeight() / 2 + top;
icon.setBounds(x, y, x + icon.getIntrinsicWidth(), y + icon.getIntrinsicHeight());
}
x = bounds.centerX() - icon.intrinsicWidth / 2 + left
y = bounds.centerY() - icon.intrinsicHeight / 2 + top
icon.setBounds(x, y, x + icon.intrinsicWidth, y + icon.intrinsicHeight)
}
icon.draw(canvas);
}
icon.draw(canvas)
}
@Override
public void setAlpha(int alpha) {
icon.setAlpha(alpha);
background.setAlpha(alpha);
override fun setAlpha(alpha: Int) {
icon?.alpha = alpha
background.alpha = alpha
}
@Override
public int getIntrinsicWidth() {
return backWidth != 0 ? backWidth : background.getIntrinsicWidth();
override fun getIntrinsicWidth(): Int {
return if (backWidth != 0) backWidth else background.intrinsicWidth
}
@Override
public int getIntrinsicHeight() {
return backHeight != 0 ? backHeight : background.getIntrinsicHeight();
override fun getIntrinsicHeight(): Int {
return if (backHeight != 0) backHeight else background.intrinsicHeight
}
@Override
public int getMinimumWidth() {
return backWidth != 0 ? backWidth : background.getMinimumWidth();
override fun getMinimumWidth(): Int {
return if (backWidth != 0) backWidth else background.minimumWidth
}
@Override
public int getMinimumHeight() {
return backHeight != 0 ? backHeight : background.getMinimumHeight();
override fun getMinimumHeight(): Int {
return if (backHeight != 0) backHeight else background.minimumHeight
}
@Override
public int getOpacity() {
return icon.getOpacity();
override fun getOpacity(): Int {
return icon?.opacity ?: PixelFormat.UNKNOWN
}
@Override
public void invalidateDrawable(@NonNull Drawable who) {
invalidateSelf();
override fun invalidateDrawable(who: Drawable) {
invalidateSelf()
}
@Override
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
scheduleSelf(what, when);
override fun scheduleDrawable(who: Drawable, what: Runnable, `when`: Long) {
scheduleSelf(what, `when`)
}
@Override
public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
unscheduleSelf(what);
override fun unscheduleDrawable(who: Drawable, what: Runnable) {
unscheduleSelf(what)
}
}
}

213
app/src/main/java/awais/instagrabber/utils/CookieUtils.kt

@ -1,139 +1,114 @@
package awais.instagrabber.utils;
@file:JvmName("CookieUtils")
import android.content.Context;
import android.util.Log;
import android.webkit.CookieManager;
package awais.instagrabber.utils
import androidx.annotation.Nullable;
import android.content.Context
import android.util.Log
import android.webkit.CookieManager
import awais.instagrabber.db.datasources.AccountDataSource
import awais.instagrabber.db.repositories.AccountRepository
import awais.instagrabber.db.repositories.RepositoryCallback
import java.net.CookiePolicy
import java.net.HttpCookie
import java.net.URI
import java.net.URISyntaxException
import java.util.regex.Pattern
import java.net.CookiePolicy;
import java.net.CookieStore;
import java.net.HttpCookie;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
private const val TAG = "CookieUtils"
private val COOKIE_MANAGER = CookieManager.getInstance()
import awais.instagrabber.BuildConfig;
import awais.instagrabber.db.datasources.AccountDataSource;
import awais.instagrabber.db.repositories.AccountRepository;
import awais.instagrabber.db.repositories.RepositoryCallback;
//import awaisomereport.LogCollector;
@JvmField
val NET_COOKIE_MANAGER = java.net.CookieManager(null, CookiePolicy.ACCEPT_ALL)
public final class CookieUtils {
private static final String TAG = CookieUtils.class.getSimpleName();
public static final CookieManager COOKIE_MANAGER = CookieManager.getInstance();
public static final java.net.CookieManager NET_COOKIE_MANAGER = new java.net.CookieManager(null, CookiePolicy.ACCEPT_ALL);
public static void setupCookies(final String cookieRaw) {
final CookieStore cookieStore = NET_COOKIE_MANAGER.getCookieStore();
if (cookieStore == null || TextUtils.isEmpty(cookieRaw)) {
return;
}
if (cookieRaw.equals("LOGOUT")) {
cookieStore.removeAll();
return;
}
try {
final URI uri1 = new URI("https://instagram.com");
final URI uri2 = new URI("https://instagram.com/");
final URI uri3 = new URI("https://i.instagram.com/");
for (final String cookie : cookieRaw.split("; ")) {
final String[] strings = cookie.split("=", 2);
final HttpCookie httpCookie = new HttpCookie(strings[0].trim(), strings[1].trim());
httpCookie.setDomain(".instagram.com");
httpCookie.setPath("/");
httpCookie.setVersion(0);
cookieStore.add(uri1, httpCookie);
cookieStore.add(uri2, httpCookie);
cookieStore.add(uri3, httpCookie);
}
} catch (final URISyntaxException e) {
// if (Utils.logCollector != null)
// Utils.logCollector.appendException(e, LogCollector.LogFile.UTILS, "setupCookies");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
fun setupCookies(cookieRaw: String) {
val cookieStore = NET_COOKIE_MANAGER.cookieStore
if (cookieStore == null || TextUtils.isEmpty(cookieRaw)) {
return
}
public static void removeAllAccounts(final Context context, final RepositoryCallback<Void> callback) {
final CookieStore cookieStore = NET_COOKIE_MANAGER.getCookieStore();
if (cookieStore == null) return;
cookieStore.removeAll();
try {
AccountRepository.getInstance(AccountDataSource.getInstance(context))
.deleteAllAccounts(callback);
} catch (Exception e) {
Log.e(TAG, "setupCookies", e);
}
if (cookieRaw == "LOGOUT") {
cookieStore.removeAll()
return
}
public static long getUserIdFromCookie(final String cookies) {
final String dsUserId = getCookieValue(cookies, "ds_user_id");
if (dsUserId == null) {
return 0;
try {
val uri1 = URI("https://instagram.com")
val uri2 = URI("https://instagram.com/")
val uri3 = URI("https://i.instagram.com/")
for (cookie in cookieRaw.split("; ")) {
val strings = cookie.split("=", limit = 2)
val httpCookie = HttpCookie(strings[0].trim { it <= ' ' }, strings[1].trim { it <= ' ' })
httpCookie.domain = ".instagram.com"
httpCookie.path = "/"
httpCookie.version = 0
cookieStore.add(uri1, httpCookie)
cookieStore.add(uri2, httpCookie)
cookieStore.add(uri3, httpCookie)
}
try {
return Long.parseLong(dsUserId);
} catch (NumberFormatException e) {
Log.e(TAG, "getUserIdFromCookie: ", e);
}
return 0;
} catch (e: URISyntaxException) {
Log.e(TAG, "", e)
}
}
@Nullable
public static String getCsrfTokenFromCookie(final String cookies) {
return getCookieValue(cookies, "csrftoken");
fun removeAllAccounts(context: Context?, callback: RepositoryCallback<Void?>?) {
val cookieStore = NET_COOKIE_MANAGER.cookieStore ?: return
cookieStore.removeAll()
try {
AccountRepository.getInstance(AccountDataSource.getInstance(context!!))
.deleteAllAccounts(callback)
} catch (e: Exception) {
Log.e(TAG, "setupCookies", e)
}
}
@Nullable
private static String getCookieValue(final String cookies, final String name) {
if (cookies == null) return null;
final Pattern pattern = Pattern.compile(name + "=(.+?);");
final Matcher matcher = pattern.matcher(cookies);
if (matcher.find()) {
return matcher.group(1);
}
return null;
fun getUserIdFromCookie(cookies: String?): Long {
val dsUserId = getCookieValue(cookies, "ds_user_id") ?: return 0
try {
return dsUserId.toLong()
} catch (e: NumberFormatException) {
Log.e(TAG, "getUserIdFromCookie: ", e)
}
return 0
}
@Nullable
public static String getCookie(@Nullable final String webViewUrl) {
final List<String> domains = new ArrayList<>(Arrays.asList(
"https://instagram.com",
"https://instagram.com/",
"http://instagram.com",
"http://instagram.com",
"https://www.instagram.com",
"https://www.instagram.com/",
"http://www.instagram.com",
"http://www.instagram.com/"
));
if (!TextUtils.isEmpty(webViewUrl)) {
domains.add(0, webViewUrl);
}
fun getCsrfTokenFromCookie(cookies: String?): String? {
return getCookieValue(cookies, "csrftoken")
}
return getLongestCookie(domains);
}
private fun getCookieValue(cookies: String?, name: String): String? {
if (cookies == null) return null
val pattern = Pattern.compile("$name=(.+?);")
val matcher = pattern.matcher(cookies)
return if (matcher.find()) {
matcher.group(1)
} else null
}
@Nullable
private static String getLongestCookie(final List<String> domains) {
int longestLength = 0;
String longestCookie = null;
fun getCookie(webViewUrl: String?): String? {
val domains: List<String> = listOfNotNull(
if (!TextUtils.isEmpty(webViewUrl)) webViewUrl else null,
"https://instagram.com",
"https://instagram.com/",
"http://instagram.com",
"http://instagram.com",
"https://www.instagram.com",
"https://www.instagram.com/",
"http://www.instagram.com",
"http://www.instagram.com/",
)
return getLongestCookie(domains)
}
for (final String domain : domains) {
final String cookie = COOKIE_MANAGER.getCookie(domain);
if (cookie != null) {
final int cookieLength = cookie.length();
if (cookieLength > longestLength) {
longestCookie = cookie;
longestLength = cookieLength;
}
private fun getLongestCookie(domains: List<String>): String? {
var longestLength = 0
var longestCookie: String? = null
for (domain in domains) {
val cookie = COOKIE_MANAGER.getCookie(domain)
if (cookie != null) {
val cookieLength = cookie.length
if (cookieLength > longestLength) {
longestCookie = cookie
longestLength = cookieLength
}
}
return longestCookie;
}
}
return longestCookie
}

33
app/src/main/java/awais/instagrabber/utils/CubicInterpolation.java

@ -8,29 +8,6 @@ public class CubicInterpolation {
private final int tangentFactor;
private final int length;
// constructor: (array, config) ->
// @array = array.slice 0 #copy the array
// @length = @array.length #cache length
// class CubicInterpolator extends AbstractInterpolator
// constructor: (array, config)->
// #clamp cubic tension to [0,1] range
// @tangentFactor = 1 - Math.max 0, Math.min 1, config.cubicTension
// super
//
// # Cardinal spline with tension 0.5)
// getTangent: (k) -> @tangentFactor*(@getClippedInput(k + 1) - @getClippedInput(k - 1))/2
//
// interpolate: (t) ->
// k = Math.floor t
// m = [(@getTangent k), (@getTangent k+1)] #get tangents
// p = [(@getClippedInput k), (@getClippedInput k+1)] #get points
// #Translate t to interpolate between k and k+1
// t -= k
// t2 = t*t #t^2
// t3 = t*t2 #t^3
// #Apply cubic hermite spline formula
// return (2*t3 - 3*t2 + 1)*p[0] + (t3 - 2*t2 + t)*m[0] + (-2*t3 + 3*t2)*p[1] + (t3 - t2)*m[1]
public CubicInterpolation(final float[] array, final int cubicTension) {
this.array = Arrays.copyOf(array, array.length);
this.length = array.length;
@ -55,16 +32,6 @@ public class CubicInterpolation {
return (2 * t3 - 3 * t2 + 1) * p[0] + (t3 - 2 * t2 + t1) * m[0] + (-2 * t3 + 3 * t2) * p[1] + (t3 - t2) * m[1];
}
// getClippedInput: (i) ->
// #Normal behavior for indexes within bounds
// if 0 <= i < @length
// @array[i]
// else
// @clipHelper i
//
// clipHelperClamp: (i) -> @array[clipClamp i, @length]
// clipClamp = (i, n) -> Math.max 0, Math.min i, n - 1
private float getClippedInput(int i) {
if (i >= 0 && i < length) {
return array[i];

43
app/src/main/java/awais/instagrabber/utils/DataBox.java

@ -1,43 +0,0 @@
package awais.instagrabber.utils;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public final class DataBox extends SQLiteOpenHelper {
private static final String TAG = "DataBox";
private static DataBox sInstance;
private final static int VERSION = 3;
public static synchronized DataBox getInstance(final Context context) {
if (sInstance == null) sInstance = new DataBox(context.getApplicationContext());
return sInstance;
}
private DataBox(@Nullable final Context context) {
super(context, "cookiebox.db", null, VERSION);
}
@Override
public void onCreate(@NonNull final SQLiteDatabase db) {
Log.i(TAG, "Creating tables...");
Log.i(TAG, "Tables created!");
}
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
Log.i(TAG, String.format("Updating DB from v%d to v%d", oldVersion, newVersion));
// switch without break, so that all migrations from a previous version to new are run
switch (oldVersion) {
case 1:
case 2:
}
Log.i(TAG, String.format("DB update from v%d to v%d completed!", oldVersion, newVersion));
}
}

74
app/src/main/java/awais/instagrabber/utils/DeepLinkParser.kt

@ -1,67 +1,29 @@
package awais.instagrabber.utils;
package awais.instagrabber.utils
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.regex.Pattern
import com.google.common.collect.ImmutableMap;
object DeepLinkParser {
private val TYPE_PATTERN_MAP: Map<DeepLink.Type, DeepLinkPattern> = mapOf(
DeepLink.Type.USER to DeepLinkPattern("instagram://user?username="),
)
import java.util.Map;
import java.util.regex.Pattern;
public final class DeepLinkParser {
private static final Map<DeepLink.Type, DeepLinkPattern> TYPE_PATTERN_MAP = ImmutableMap
.<DeepLink.Type, DeepLinkPattern>builder()
.put(DeepLink.Type.USER, new DeepLinkPattern("instagram://user?username="))
.build();
@Nullable
public static DeepLink parse(@NonNull final String text) {
for (final Map.Entry<DeepLink.Type, DeepLinkPattern> entry : TYPE_PATTERN_MAP.entrySet()) {
if (text.startsWith(entry.getValue().getPatternText())) {
final String value = entry.getValue().getPattern().matcher(text).replaceAll("");
return new DeepLink(entry.getKey(), value);
@JvmStatic
fun parse(text: String): DeepLink? {
for ((key, value) in TYPE_PATTERN_MAP) {
if (text.startsWith(value.patternText)) {
return DeepLink(key, value.pattern.matcher(text).replaceAll(""))
}
}
return null;
return null
}
public static class DeepLinkPattern {
private final String patternText;
private final Pattern pattern;
public DeepLinkPattern(final String patternText) {
this.patternText = patternText;
pattern = Pattern.compile(patternText, Pattern.LITERAL);
}
public String getPatternText() {
return patternText;
}
public Pattern getPattern() {
return pattern;
}
data class DeepLinkPattern(val patternText: String) {
val pattern: Pattern = Pattern.compile(patternText, Pattern.LITERAL)
}
public static class DeepLink {
private final Type type;
private final String value;
public DeepLink(final Type type, final String value) {
this.type = type;
this.value = value;
}
public Type getType() {
return type;
}
public String getValue() {
return value;
}
public enum Type {
USER,
data class DeepLink(val type: Type, val value: String) {
enum class Type {
USER
}
}
}
}

156
app/src/main/java/awais/instagrabber/utils/LocaleUtils.kt

@ -1,91 +1,83 @@
package awais.instagrabber.utils;
package awais.instagrabber.utils
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.view.ContextThemeWrapper;
import androidx.annotation.Nullable;
import java.util.Locale;
import awais.instagrabber.fragments.settings.PreferenceKeys;
import android.content.Context
import android.content.res.Configuration
import android.view.ContextThemeWrapper
import awais.instagrabber.fragments.settings.PreferenceKeys
import java.util.*
// taken from my app TESV Console Codes
public final class LocaleUtils {
private static Locale defaultLocale, currentLocale;
public static void setLocale(Context baseContext) {
if (defaultLocale == null) defaultLocale = Locale.getDefault();
if (baseContext instanceof ContextThemeWrapper)
baseContext = ((ContextThemeWrapper) baseContext).getBaseContext();
if (Utils.settingsHelper == null)
Utils.settingsHelper = new SettingsHelper(baseContext);
final String appLanguageSettings = Utils.settingsHelper.getString(PreferenceKeys.APP_LANGUAGE);
final String lang = LocaleUtils.getCorrespondingLanguageCode(appLanguageSettings);
currentLocale = TextUtils.isEmpty(lang) ? defaultLocale :
(lang.contains("_") ? new Locale(lang.split("_")[0], lang.split("_")[1]) : new Locale(lang));
Locale.setDefault(currentLocale);
final Resources res = baseContext.getResources();
final Configuration config = res.getConfiguration();
config.locale = currentLocale;
config.setLocale(currentLocale);
config.setLayoutDirection(currentLocale);
res.updateConfiguration(config, res.getDisplayMetrics());
}
public static Locale getCurrentLocale() {
return currentLocale;
}
public static void updateConfig(final ContextThemeWrapper wrapper) {
if (currentLocale != null) {
final Configuration configuration = new Configuration();
configuration.locale = currentLocale;
configuration.setLocale(currentLocale);
wrapper.applyOverrideConfiguration(configuration);
object LocaleUtils {
private var defaultLocale: Locale? = null
@JvmStatic
var currentLocale: Locale? = null
private set
@JvmStatic
fun setLocale(baseContext: Context) {
var baseContext1 = baseContext
if (defaultLocale == null) defaultLocale = Locale.getDefault()
if (baseContext1 is ContextThemeWrapper) baseContext1 = baseContext1.baseContext
if (Utils.settingsHelper == null) Utils.settingsHelper = SettingsHelper(baseContext1)
val appLanguageSettings = Utils.settingsHelper.getString(PreferenceKeys.APP_LANGUAGE)
val lang = getCorrespondingLanguageCode(appLanguageSettings)
currentLocale = when {
TextUtils.isEmpty(lang) -> defaultLocale
lang!!.contains("_") -> {
val split = lang.split("_")
Locale(split[0], split[1])
}
else -> Locale(lang)
}
currentLocale?.let {
Locale.setDefault(it)
val res = baseContext1.resources
val config = res.configuration
// config.locale = currentLocale
config.setLocale(it)
config.setLayoutDirection(it)
res.updateConfiguration(config, res.displayMetrics)
}
}
@Nullable
public static String getCorrespondingLanguageCode(final String appLanguageSettings) {
if (TextUtils.isEmpty(appLanguageSettings)) return null;
@JvmStatic
fun updateConfig(wrapper: ContextThemeWrapper) {
if (currentLocale == null) return
val configuration = Configuration()
// configuration.locale = currentLocale
configuration.setLocale(currentLocale)
wrapper.applyOverrideConfiguration(configuration)
}
final int appLanguageIndex = Integer.parseInt(appLanguageSettings);
switch (appLanguageIndex) {
case 1: return "en";
case 2: return "fr";
case 3: return "es";
case 4: return "zh_CN";
case 5: return "in";
case 6: return "it";
case 7: return "de";
case 8: return "pl";
case 9: return "tr";
case 10: return "pt";
case 11: return "fa";
case 12: return "mk";
case 13: return "vi";
case 14: return "zh_TW";
case 15: return "ca";
case 16: return "ru";
case 17: return "hi";
case 18: return "nl";
case 19: return "sk";
case 20: return "ja";
case 21: return "el";
case 22: return "eu";
case 23: return "sv";
case 24: return "ko";
fun getCorrespondingLanguageCode(appLanguageSettings: String): String? {
if (TextUtils.isEmpty(appLanguageSettings)) return null
when (appLanguageSettings.toInt()) {
1 -> return "en"
2 -> return "fr"
3 -> return "es"
4 -> return "zh_CN"
5 -> return "in"
6 -> return "it"
7 -> return "de"
8 -> return "pl"
9 -> return "tr"
10 -> return "pt"
11 -> return "fa"
12 -> return "mk"
13 -> return "vi"
14 -> return "zh_TW"
15 -> return "ca"
16 -> return "ru"
17 -> return "hi"
18 -> return "nl"
19 -> return "sk"
20 -> return "ja"
21 -> return "el"
22 -> return "eu"
23 -> return "sv"
24 -> return "ko"
}
return null;
return null
}
}
}

3
app/src/main/java/awais/instagrabber/utils/MediaUploadHelper.kt

@ -7,6 +7,7 @@ import awais.instagrabber.models.UploadVideoOptions
import awais.instagrabber.models.enums.MediaItemType
import org.json.JSONObject
import java.util.*
import kotlin.random.Random
private const val LOWER = 1000000000L
@ -109,7 +110,7 @@ fun generateUploadId(): String {
}
fun generateName(uploadId: String): String {
val random = NumberUtils.random(LOWER, UPPER + 1)
val random = Random.nextLong(LOWER, UPPER + 1)
return "${uploadId}_0_$random"
}

87
app/src/main/java/awais/instagrabber/utils/NullSafePair.kt

@ -1,4 +1,4 @@
package awais.instagrabber.utils;
package awais.instagrabber.utils
/*
* Copyright (C) 2009 The Android Open Source Project
@ -15,77 +15,28 @@ package awais.instagrabber.utils;
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.ObjectsCompat;
/**
* Container to ease passing around a tuple of two objects. This object provides a sensible
* implementation of equals(), returning true if equals() is true on each of the contained
* objects.
*/
public class NullSafePair<F, S> {
public final @NonNull
F first;
public final @NonNull
S second;
/**
* Constructor for a Pair.
*
* @param first the first object in the Pair
* @param second the second object in the pair
*/
public NullSafePair(@NonNull F first, @NonNull S second) {
this.first = first;
this.second = second;
}
/**
* Checks the two objects for equality by delegating to their respective
* {@link Object#equals(Object)} methods.
*
* @param o the {@link androidx.core.util.Pair} to which this one is to be checked for equality
* @return true if the underlying objects of the Pair are both considered
* equal
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof androidx.core.util.Pair)) {
return false;
/**
* Constructor for a Pair.
*
* @param first the first object in the Pair
* @param second the second object in the pair
*/
data class NullSafePair<F, S>(@JvmField val first: F, @JvmField val second: S) {
companion object {
/**
* Convenience method for creating an appropriately typed pair.
*
* @param a the first object in the Pair
* @param b the second object in the pair
* @return a Pair that is templatized with the types of a and b
*/
fun <A, B> create(a: A, b: B): NullSafePair<A, B> {
return NullSafePair(a, b)
}
androidx.core.util.Pair<?, ?> p = (androidx.core.util.Pair<?, ?>) o;
return ObjectsCompat.equals(p.first, first) && ObjectsCompat.equals(p.second, second);
}
/**
* Compute a hash code using the hash codes of the underlying objects
*
* @return a hashcode of the Pair
*/
@Override
public int hashCode() {
return first.hashCode() ^ second.hashCode();
}
@NonNull
@Override
public String toString() {
return "Pair{" + first + " " + second + "}";
}
/**
* Convenience method for creating an appropriately typed pair.
*
* @param a the first object in the Pair
* @param b the second object in the pair
* @return a Pair that is templatized with the types of a and b
*/
@NonNull
public static <A, B> androidx.core.util.Pair<A, B> create(@Nullable A a, @Nullable B b) {
return new androidx.core.util.Pair<A, B>(a, b);
}
}
}

202
app/src/main/java/awais/instagrabber/utils/NumberUtils.kt

@ -1,149 +1,87 @@
package awais.instagrabber.utils;
@file:JvmName("NumberUtils")
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
package awais.instagrabber.utils
import java.util.Locale;
import java.util.Random;
import java.util.*
import kotlin.math.ln
import kotlin.math.pow
public final class NumberUtils {
// @NonNull
// public static String millisToString(final long timeMs) {
// final long totalSeconds = timeMs / 1000;
//
// final long seconds = totalSeconds % 60;
// final long minutes = totalSeconds / 60 % 60;
// final long hours = totalSeconds / 3600;
//
// final String strSec = Long.toString(seconds);
// final String strMin = Long.toString(minutes);
//
// final String strRetSec = strSec.length() > 1 ? strSec : "0" + seconds;
// final String strRetMin = strMin.length() > 1 ? strMin : "0" + minutes;
//
// final String retMinSec = strRetMin + ':' + strRetSec;
//
// if (hours > 0)
// return Long.toString(hours) + ':' + retMinSec;
// return retMinSec;
// }
public static int getResultingHeight(final int requiredWidth, final int height, final int width) {
return requiredWidth * height / width;
}
public static int getResultingWidth(final int requiredHeight, final int height, final int width) {
return requiredHeight * width / height;
}
fun getResultingHeight(requiredWidth: Int, height: Int, width: Int): Int {
return requiredWidth * height / width
}
public static long random(long origin, long bound) {
final Random random = new Random();
long r = random.nextLong();
long n = bound - origin, m = n - 1;
if ((n & m) == 0L) // power of two
r = (r & m) + origin;
else if (n > 0L) { // reject over-represented candidates
//noinspection StatementWithEmptyBody
for (long u = r >>> 1; // ensure non-negative
u + m - (r = u % n) < 0L; // rejection check
u = random.nextLong() >>> 1) // retry
;
r += origin;
} else { // range not representable as long
while (r < origin || r >= bound)
r = random.nextLong();
}
return r;
}
fun getResultingWidth(requiredHeight: Int, height: Int, width: Int): Int {
return requiredHeight * width / height
}
@NonNull
public static NullSafePair<Integer, Integer> calculateWidthHeight(final int height, final int width, final int maxHeight, final int maxWidth) {
if (width > maxWidth) {
int tempHeight = getResultingHeight(maxWidth, height, width);
int tempWidth = maxWidth;
if (tempHeight > maxHeight) {
tempWidth = getResultingWidth(maxHeight, tempHeight, tempWidth);
tempHeight = maxHeight;
// TODO Replace all usages with kotlin Random.nextLong() once converted to kotlin
fun random(origin: Long, bound: Long): Long {
val random = Random()
var r = random.nextLong()
val n = bound - origin
val m = n - 1
when {
n and m == 0L -> r = (r and m) + origin // power of two
n > 0L -> {
// reject over-represented candidates
var u = r ushr 1 // ensure non-negative
while (u + m - u % n.also { r = it } < 0L) { // rejection check
// retry
u = random.nextLong() ushr 1
}
return new NullSafePair<>(tempWidth, tempHeight);
r += origin
}
if ((height < maxHeight && width < maxWidth) || (height > maxHeight)) {
int tempWidth = getResultingWidth(maxHeight, height, width);
int tempHeight = maxHeight;
if (tempWidth > maxWidth) {
tempHeight = getResultingHeight(maxWidth, tempHeight, tempWidth);
tempWidth = maxWidth;
}
return new NullSafePair<>(tempWidth, tempHeight);
else -> {
// range not representable as long
while (r < origin || r >= bound) r = random.nextLong()
}
return new NullSafePair<>(width, height);
}
return r
}
public static float roundFloat2Decimals(final float value) {
return ((int) ((value + (value >= 0 ? 1 : -1) * 0.005f) * 100)) / 100f;
}
@NonNull
public static String abbreviate(final long number) {
return abbreviate(number, null);
}
@NonNull
public static String abbreviate(final long number, @Nullable final AbbreviateOptions options) {
// adapted from https://stackoverflow.com/a/9769590/1436766
int threshold = 1000;
boolean addSpace = false;
if (options != null) {
threshold = options.getThreshold();
addSpace = options.addSpaceBeforePrefix();
fun calculateWidthHeight(height: Int, width: Int, maxHeight: Int, maxWidth: Int): NullSafePair<Int, Int> {
if (width > maxWidth) {
var tempHeight = getResultingHeight(maxWidth, height, width)
var tempWidth = maxWidth
if (tempHeight > maxHeight) {
tempWidth = getResultingWidth(maxHeight, tempHeight, tempWidth)
tempHeight = maxHeight
}
if (number < threshold) return "" + number;
int exp = (int) (Math.log(number) / Math.log(threshold));
return String.format(Locale.US,
"%.1f%s%c",
number / Math.pow(threshold, exp),
addSpace ? " " : "",
"kMGTPE".charAt(exp - 1));
return NullSafePair(tempWidth, tempHeight)
}
public static final class AbbreviateOptions {
private final int threshold;
private final boolean addSpaceBeforePrefix;
public static final class Builder {
private int threshold = 1000;
private boolean addSpaceBeforePrefix = false;
public Builder setThreshold(final int threshold) {
this.threshold = threshold;
return this;
}
public Builder setAddSpaceBeforePrefix(final boolean addSpaceBeforePrefix) {
this.addSpaceBeforePrefix = addSpaceBeforePrefix;
return this;
}
@NonNull
public AbbreviateOptions build() {
return new AbbreviateOptions(threshold, addSpaceBeforePrefix);
}
}
private AbbreviateOptions(final int threshold, final boolean addSpaceBeforePrefix) {
this.threshold = threshold;
this.addSpaceBeforePrefix = addSpaceBeforePrefix;
if (height < maxHeight && width < maxWidth || height > maxHeight) {
var tempWidth = getResultingWidth(maxHeight, height, width)
var tempHeight = maxHeight
if (tempWidth > maxWidth) {
tempHeight = getResultingHeight(maxWidth, tempHeight, tempWidth)
tempWidth = maxWidth
}
return NullSafePair(tempWidth, tempHeight)
}
return NullSafePair(width, height)
}
public int getThreshold() {
return threshold;
}
fun roundFloat2Decimals(value: Float): Float {
return ((value + (if (value >= 0) 1 else -1) * 0.005f) * 100).toInt() / 100f
}
public boolean addSpaceBeforePrefix() {
return addSpaceBeforePrefix;
}
fun abbreviate(number: Long, options: AbbreviateOptions? = null): String {
// adapted from https://stackoverflow.com/a/9769590/1436766
var threshold = 1000
var addSpace = false
if (options != null) {
threshold = options.threshold
addSpace = options.addSpaceBeforePrefix
}
if (number < threshold) return "" + number
val exp = (ln(number.toDouble()) / ln(threshold.toDouble())).toInt()
return String.format(
Locale.US,
"%.1f%s%c",
number / threshold.toDouble().pow(exp.toDouble()),
if (addSpace) " " else "",
"kMGTPE"[exp - 1]
)
}
data class AbbreviateOptions(val threshold: Int = 1000, val addSpaceBeforePrefix: Boolean = false)

90
app/src/main/java/awais/instagrabber/utils/RankedRecipientsCache.kt

@ -1,69 +1,27 @@
package awais.instagrabber.utils;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.List;
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient;
import awais.instagrabber.repositories.responses.directmessages.RankedRecipientsResponse;
public class RankedRecipientsCache {
private static final Object LOCK = new Object();
private static RankedRecipientsCache instance;
public static RankedRecipientsCache getInstance() {
if (instance == null) {
synchronized (LOCK) {
if (instance == null) {
instance = new RankedRecipientsCache();
}
}
}
return instance;
}
private LocalDateTime lastUpdatedOn;
private RankedRecipientsResponse response;
private boolean updateInitiated = false;
private boolean failed = false;
private RankedRecipientsCache() {}
public List<RankedRecipient> getRankedRecipients() {
if (response != null) {
return response.getRankedRecipients();
package awais.instagrabber.utils
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
import awais.instagrabber.repositories.responses.directmessages.RankedRecipientsResponse
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
object RankedRecipientsCache {
private var lastUpdatedOn: LocalDateTime? = null
var isUpdateInitiated = false
var isFailed = false
val rankedRecipients: List<RankedRecipient>
get() = response?.rankedRecipients ?: emptyList()
var response: RankedRecipientsResponse? = null
set(value) {
field = value
lastUpdatedOn = LocalDateTime.now()
}
return Collections.emptyList();
}
public void setRankedRecipientsResponse(final RankedRecipientsResponse response) {
this.response = response;
lastUpdatedOn = LocalDateTime.now();
}
public boolean isExpired() {
if (lastUpdatedOn == null || response == null) {
return true;
val isExpired: Boolean
get() {
if (lastUpdatedOn == null || response == null) return true
val expiresInSecs = response!!.expires
return LocalDateTime.now().isAfter(lastUpdatedOn!!.plus(expiresInSecs, ChronoUnit.SECONDS))
}
final long expiresInSecs = response.getExpires();
return LocalDateTime.now().isAfter(lastUpdatedOn.plus(expiresInSecs, ChronoUnit.SECONDS));
}
public boolean isUpdateInitiated() {
return updateInitiated;
}
public void setUpdateInitiated(final boolean updateInitiated) {
this.updateInitiated = updateInitiated;
}
public boolean isFailed() {
return failed;
}
public void setFailed(final boolean failed) {
this.failed = failed;
}
}
}

25
app/src/main/java/awais/instagrabber/utils/SerializablePair.kt

@ -1,17 +1,12 @@
package awais.instagrabber.utils;
package awais.instagrabber.utils
import android.util.Pair;
import android.util.Pair
import java.io.Serializable
import java.io.Serializable;
public class SerializablePair<F, S> extends Pair<F, S> implements Serializable {
/**
* Constructor for a Pair.
*
* @param first the first object in the Pair
* @param second the second object in the pair
*/
public SerializablePair(final F first, final S second) {
super(first, second);
}
}
/**
* Constructor for a Pair.
*
* @param first the first object in the Pair
* @param second the second object in the pair
*/
data class SerializablePair<F, S>(@JvmField val first: F, @JvmField val second: S) : Pair<F, S>(first, second), Serializable

70
app/src/main/java/awais/instagrabber/utils/UpdateCheckCommon.kt

@ -1,38 +1,36 @@
package awais.instagrabber.utils;
import android.content.Context;
import android.content.DialogInterface;
import androidx.annotation.NonNull;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class UpdateCheckCommon {
public static boolean shouldShowUpdateDialog(final boolean force,
@NonNull final String version) {
final String skippedVersion = settingsHelper.getString(Constants.SKIPPED_VERSION);
return force || (!BuildConfig.DEBUG && !skippedVersion.equals(version));
}
@file:JvmName("UpdateCheckCommon")
package awais.instagrabber.utils
import android.content.Context
import android.content.DialogInterface
import awais.instagrabber.BuildConfig
import awais.instagrabber.R
import awais.instagrabber.utils.AppExecutors.mainThread
import com.google.android.material.dialog.MaterialAlertDialogBuilder
fun shouldShowUpdateDialog(
force: Boolean,
version: String
): Boolean {
val skippedVersion = Utils.settingsHelper.getString(Constants.SKIPPED_VERSION)
return force || !BuildConfig.DEBUG && skippedVersion != version
}
public static void showUpdateDialog(@NonNull final Context context,
@NonNull final String version,
@NonNull final DialogInterface.OnClickListener onDownloadClickListener) {
AppExecutors.INSTANCE.getMainThread().execute(() -> {
new MaterialAlertDialogBuilder(context)
.setTitle(context.getString(R.string.update_available, version))
.setNeutralButton(R.string.skip_update, (dialog, which) -> {
settingsHelper.putString(Constants.SKIPPED_VERSION, version);
dialog.dismiss();
})
.setPositiveButton(R.string.action_download, onDownloadClickListener)
.setNegativeButton(R.string.cancel, null)
.show();
});
fun showUpdateDialog(
context: Context,
version: String,
onDownloadClickListener: DialogInterface.OnClickListener
) {
mainThread.execute {
MaterialAlertDialogBuilder(context).apply {
setTitle(context.getString(R.string.update_available, version))
setNeutralButton(R.string.skip_update) { dialog: DialogInterface, which: Int ->
Utils.settingsHelper.putString(Constants.SKIPPED_VERSION, version)
dialog.dismiss()
}
setPositiveButton(R.string.action_download, onDownloadClickListener)
setNegativeButton(R.string.cancel, null)
}.show()
}
}
}

155
app/src/main/java/awais/instagrabber/utils/UserAgentUtils.kt

@ -1,85 +1,82 @@
package awais.instagrabber.utils;
@file:JvmName("UserAgentUtils")
import androidx.annotation.NonNull;
package awais.instagrabber.utils
public class UserAgentUtils {
/* GraphQL user agents (which are just standard browser UA"s).
* Go to https://www.whatismybrowser.com/guides/the-latest-user-agent/ to update it
* Windows first (Assume win64 not wow64): Chrome, Firefox, Edge
* Then macOS: Chrome, Firefox, Safari
*/
@JvmField
val browsers = arrayOf(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 11_3_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 11.3; rv:88.0) Gecko/20100101 Firefox/88.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 11_3_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Safari/605.1.15"
)
/* GraphQL user agents (which are just standard browser UA"s).
* Go to https://www.whatismybrowser.com/guides/the-latest-user-agent/ to update it
* Windows first (Assume win64 not wow64): Chrome, Firefox, Edge
* Then macOS: Chrome, Firefox, Safari
*/
public static final String[] browsers = {
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 11_3_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 11.3; rv:88.0) Gecko/20100101 Firefox/88.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 11_3_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Safari/605.1.15"
};
// use APKpure, assume arm64-v8a
private static final String igVersion = "188.0.0.35.124";
private static final String igVersionCode = "292080186";
// you can pick *any* device as long as you LEAVE OUT the resolution for maximum download quality
public static final String[] devices = {
// https://github.com/dilame/instagram-private-api/blob/master/src/samples/devices.json
"25/7.1.1; 440dpi; 2880x5884; Xiaomi; Mi Note 3; jason; qcom",
"23/6.0.1; 480dpi; 2880x5884; Xiaomi; Redmi Note 3; kenzo; qcom",
"23/6.0; 480dpi; 2880x5884; Xiaomi; Redmi Note 4; nikel; mt6797",
"24/7.0; 480dpi; 2880x5884; Xiaomi/xiaomi; Redmi Note 4; mido; qcom",
"23/6.0; 480dpi; 2880x5884; Xiaomi; Redmi Note 4X; nikel; mt6797",
"27/8.1.0; 440dpi; 2880x5884; Xiaomi/xiaomi; Redmi Note 5; whyred; qcom",
"23/6.0.1; 480dpi; 2880x5884; Xiaomi; Redmi 4; markw; qcom",
"27/8.1.0; 440dpi; 2880x5884; Xiaomi/xiaomi; Redmi 5 Plus; vince; qcom",
"25/7.1.2; 440dpi; 2880x5884; Xiaomi/xiaomi; Redmi 5 Plus; vince; qcom",
"26/8.0.0; 480dpi; 2880x5884; Xiaomi; MI 5; gemini; qcom",
"27/8.1.0; 480dpi; 2880x5884; Xiaomi/xiaomi; Mi A1; tissot_sprout; qcom",
"26/8.0.0; 480dpi; 2880x5884; Xiaomi; MI 6; sagit; qcom",
"25/7.1.1; 440dpi; 2880x5884; Xiaomi; MI MAX 2; oxygen; qcom",
"24/7.0; 480dpi; 2880x5884; Xiaomi; MI 5s; capricorn; qcom",
"26/8.0.0; 480dpi; 2880x5884; samsung; SM-A520F; a5y17lte; samsungexynos7880",
"26/8.0.0; 480dpi; 2880x5884; samsung; SM-G950F; dreamlte; samsungexynos8895",
"26/8.0.0; 640dpi; 2880x5884; samsung; SM-G950F; dreamlte; samsungexynos8895",
"26/8.0.0; 420dpi; 2880x5884; samsung; SM-G955F; dream2lte; samsungexynos8895",
"26/8.0.0; 560dpi; 2880x5884; samsung; SM-G955F; dream2lte; samsungexynos8895",
"24/7.0; 480dpi; 2880x5884; samsung; SM-A510F; a5xelte; samsungexynos7580",
"26/8.0.0; 480dpi; 2880x5884; samsung; SM-G930F; herolte; samsungexynos8890",
"26/8.0.0; 480dpi; 2880x5884; samsung; SM-G935F; hero2lte; samsungexynos8890",
"26/8.0.0; 420dpi; 2880x5884; samsung; SM-G965F; star2lte; samsungexynos9810",
"26/8.0.0; 480dpi; 2880x5884; samsung; SM-A530F; jackpotlte; samsungexynos7885",
"24/7.0; 640dpi; 2880x5884; samsung; SM-G925F; zerolte; samsungexynos7420",
"26/8.0.0; 420dpi; 2880x5884; samsung; SM-A720F; a7y17lte; samsungexynos7880",
"24/7.0; 640dpi; 2880x5884; samsung; SM-G920F; zeroflte; samsungexynos7420",
"24/7.0; 420dpi; 2880x5884; samsung; SM-J730FM; j7y17lte; samsungexynos7870",
"26/8.0.0; 480dpi; 2880x5884; samsung; SM-G960F; starlte; samsungexynos9810",
"26/8.0.0; 420dpi; 2880x5884; samsung; SM-N950F; greatlte; samsungexynos8895",
"26/8.0.0; 420dpi; 2880x5884; samsung; SM-A730F; jackpot2lte; samsungexynos7885",
"26/8.0.0; 420dpi; 2880x5884; samsung; SM-A605FN; a6plte; qcom",
"26/8.0.0; 480dpi; 2880x5884; HUAWEI/HONOR; STF-L09; HWSTF; hi3660",
"27/8.1.0; 480dpi; 2880x5884; HUAWEI/HONOR; COL-L29; HWCOL; kirin970",
"26/8.0.0; 480dpi; 2880x5884; HUAWEI/HONOR; LLD-L31; HWLLD-H; hi6250",
"26/8.0.0; 480dpi; 2880x5884; HUAWEI; ANE-LX1; HWANE; hi6250",
"26/8.0.0; 480dpi; 2880x5884; HUAWEI; FIG-LX1; HWFIG-H; hi6250",
"27/8.1.0; 480dpi; 2880x5884; HUAWEI/HONOR; COL-L29; HWCOL; kirin970",
"26/8.0.0; 480dpi; 2880x5884; HUAWEI/HONOR; BND-L21; HWBND-H; hi6250",
"23/6.0.1; 420dpi; 2880x5884; LeMobile/LeEco; Le X527; le_s2_ww; qcom",
// https://github.com/mimmi20/BrowserDetector/tree/master
"28/9; 560dpi; 2880x5884; samsung; SM-N960F; crownlte; samsungexynos9810",
// mgp25
"23/6.0.1; 640dpi; 2880x5884; LGE/lge; RS988; h1; h1",
"24/7.0; 640dpi; 2880x5884; HUAWEI; LON-L29; HWLON; hi3660",
"23/6.0.1; 640dpi; 2880x5884; ZTE; ZTE A2017U; ailsa_ii; qcom",
"23/6.0.1; 640dpi; 2880x5884; samsung; SM-G935F; hero2lte; samsungexynos8890",
"23/6.0.1; 640dpi; 2880x5884; samsung; SM-G930F; herolte; samsungexynos8890"
};
// use APKpure, assume arm64-v8a
private const val igVersion = "188.0.0.35.124"
private const val igVersionCode = "292080186"
@NonNull
public static String generateBrowserUA(final int code) {
return browsers[code];
}
// you can pick *any* device as long as you LEAVE OUT the resolution for maximum download quality
// https://github.com/dilame/instagram-private-api/blob/master/src/samples/devices.json
@JvmField
val devices = arrayOf(
"25/7.1.1; 440dpi; 2880x5884; Xiaomi; Mi Note 3; jason; qcom",
"23/6.0.1; 480dpi; 2880x5884; Xiaomi; Redmi Note 3; kenzo; qcom",
"23/6.0; 480dpi; 2880x5884; Xiaomi; Redmi Note 4; nikel; mt6797",
"24/7.0; 480dpi; 2880x5884; Xiaomi/xiaomi; Redmi Note 4; mido; qcom",
"23/6.0; 480dpi; 2880x5884; Xiaomi; Redmi Note 4X; nikel; mt6797",
"27/8.1.0; 440dpi; 2880x5884; Xiaomi/xiaomi; Redmi Note 5; whyred; qcom",
"23/6.0.1; 480dpi; 2880x5884; Xiaomi; Redmi 4; markw; qcom",
"27/8.1.0; 440dpi; 2880x5884; Xiaomi/xiaomi; Redmi 5 Plus; vince; qcom",
"25/7.1.2; 440dpi; 2880x5884; Xiaomi/xiaomi; Redmi 5 Plus; vince; qcom",
"26/8.0.0; 480dpi; 2880x5884; Xiaomi; MI 5; gemini; qcom",
"27/8.1.0; 480dpi; 2880x5884; Xiaomi/xiaomi; Mi A1; tissot_sprout; qcom",
"26/8.0.0; 480dpi; 2880x5884; Xiaomi; MI 6; sagit; qcom",
"25/7.1.1; 440dpi; 2880x5884; Xiaomi; MI MAX 2; oxygen; qcom",
"24/7.0; 480dpi; 2880x5884; Xiaomi; MI 5s; capricorn; qcom",
"26/8.0.0; 480dpi; 2880x5884; samsung; SM-A520F; a5y17lte; samsungexynos7880",
"26/8.0.0; 480dpi; 2880x5884; samsung; SM-G950F; dreamlte; samsungexynos8895",
"26/8.0.0; 640dpi; 2880x5884; samsung; SM-G950F; dreamlte; samsungexynos8895",
"26/8.0.0; 420dpi; 2880x5884; samsung; SM-G955F; dream2lte; samsungexynos8895",
"26/8.0.0; 560dpi; 2880x5884; samsung; SM-G955F; dream2lte; samsungexynos8895",
"24/7.0; 480dpi; 2880x5884; samsung; SM-A510F; a5xelte; samsungexynos7580",
"26/8.0.0; 480dpi; 2880x5884; samsung; SM-G930F; herolte; samsungexynos8890",
"26/8.0.0; 480dpi; 2880x5884; samsung; SM-G935F; hero2lte; samsungexynos8890",
"26/8.0.0; 420dpi; 2880x5884; samsung; SM-G965F; star2lte; samsungexynos9810",
"26/8.0.0; 480dpi; 2880x5884; samsung; SM-A530F; jackpotlte; samsungexynos7885",
"24/7.0; 640dpi; 2880x5884; samsung; SM-G925F; zerolte; samsungexynos7420",
"26/8.0.0; 420dpi; 2880x5884; samsung; SM-A720F; a7y17lte; samsungexynos7880",
"24/7.0; 640dpi; 2880x5884; samsung; SM-G920F; zeroflte; samsungexynos7420",
"24/7.0; 420dpi; 2880x5884; samsung; SM-J730FM; j7y17lte; samsungexynos7870",
"26/8.0.0; 480dpi; 2880x5884; samsung; SM-G960F; starlte; samsungexynos9810",
"26/8.0.0; 420dpi; 2880x5884; samsung; SM-N950F; greatlte; samsungexynos8895",
"26/8.0.0; 420dpi; 2880x5884; samsung; SM-A730F; jackpot2lte; samsungexynos7885",
"26/8.0.0; 420dpi; 2880x5884; samsung; SM-A605FN; a6plte; qcom",
"26/8.0.0; 480dpi; 2880x5884; HUAWEI/HONOR; STF-L09; HWSTF; hi3660",
"27/8.1.0; 480dpi; 2880x5884; HUAWEI/HONOR; COL-L29; HWCOL; kirin970",
"26/8.0.0; 480dpi; 2880x5884; HUAWEI/HONOR; LLD-L31; HWLLD-H; hi6250",
"26/8.0.0; 480dpi; 2880x5884; HUAWEI; ANE-LX1; HWANE; hi6250",
"26/8.0.0; 480dpi; 2880x5884; HUAWEI; FIG-LX1; HWFIG-H; hi6250",
"27/8.1.0; 480dpi; 2880x5884; HUAWEI/HONOR; COL-L29; HWCOL; kirin970",
"26/8.0.0; 480dpi; 2880x5884; HUAWEI/HONOR; BND-L21; HWBND-H; hi6250",
"23/6.0.1; 420dpi; 2880x5884; LeMobile/LeEco; Le X527; le_s2_ww; qcom", // https://github.com/mimmi20/BrowserDetector/tree/master
"28/9; 560dpi; 2880x5884; samsung; SM-N960F; crownlte; samsungexynos9810", // mgp25
"23/6.0.1; 640dpi; 2880x5884; LGE/lge; RS988; h1; h1",
"24/7.0; 640dpi; 2880x5884; HUAWEI; LON-L29; HWLON; hi3660",
"23/6.0.1; 640dpi; 2880x5884; ZTE; ZTE A2017U; ailsa_ii; qcom",
"23/6.0.1; 640dpi; 2880x5884; samsung; SM-G935F; hero2lte; samsungexynos8890",
"23/6.0.1; 640dpi; 2880x5884; samsung; SM-G930F; herolte; samsungexynos8890"
)
@NonNull
public static String generateAppUA(final int code, final String lang) {
return "Instagram " + igVersion + " Android (" + devices[code] + "; " + lang + "; " + igVersionCode + ")";
}
fun generateBrowserUA(code: Int): String {
return browsers[code]
}
fun generateAppUA(code: Int, lang: String): String {
return "Instagram " + igVersion + " Android (" + devices[code] + "; " + lang + "; " + igVersionCode + ")"
}

212
app/src/main/java/awais/instagrabber/utils/ViewUtils.kt

@ -1,121 +1,117 @@
package awais.instagrabber.utils;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.util.Pair;
import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import org.jetbrains.annotations.NotNull;
import kotlin.jvm.internal.Intrinsics;
public final class ViewUtils {
public static final int MATCH_PARENT = -1;
public static final int WRAP_CONTENT = -2;
public static Drawable createRoundRectDrawableWithIcon(final Context context, int rad, int iconRes) {
ShapeDrawable defaultDrawable = new ShapeDrawable(new RoundRectShape(new float[]{rad, rad, rad, rad, rad, rad, rad, rad}, null, null));
defaultDrawable.getPaint().setColor(0xffffffff);
final Drawable d = ResourcesCompat.getDrawable(context.getResources(), iconRes, null);
if (d == null) return null;
Drawable drawable = d.mutate();
return new CombinedDrawable(defaultDrawable, drawable);
}
@file:JvmName("ViewUtils")
package awais.instagrabber.utils
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.RoundRectShape
import android.os.Build
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.core.content.res.ResourcesCompat
import androidx.core.util.Pair
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringAnimation
import kotlin.jvm.internal.Intrinsics
fun createRoundRectDrawableWithIcon(context: Context, rad: Int, iconRes: Int): Drawable? {
val defaultDrawable = ShapeDrawable(RoundRectShape(FloatArray(8) { rad.toFloat() }, null, null))
defaultDrawable.paint.color = -0x1
val d = ResourcesCompat.getDrawable(context.resources, iconRes, null) ?: return null
val drawable = d.mutate()
return CombinedDrawable(defaultDrawable, drawable)
}
public static Drawable createRoundRectDrawable(int rad, int defaultColor) {
ShapeDrawable defaultDrawable = new ShapeDrawable(new RoundRectShape(new float[]{rad, rad, rad, rad, rad, rad, rad, rad}, null, null));
defaultDrawable.getPaint().setColor(defaultColor);
return defaultDrawable;
}
fun createRoundRectDrawable(rad: Int, defaultColor: Int): Drawable {
val defaultDrawable = ShapeDrawable(RoundRectShape(FloatArray(8) { rad.toFloat() }, null, null))
defaultDrawable.paint.color = defaultColor
return defaultDrawable
}
public static FrameLayout.LayoutParams createFrame(int width,
float height,
int gravity,
float leftMargin,
float topMargin,
float rightMargin,
float bottomMargin) {
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(getSize(width), getSize(height), gravity);
layoutParams.setMargins(Utils.convertDpToPx(leftMargin), Utils.convertDpToPx(topMargin), Utils.convertDpToPx(rightMargin),
Utils.convertDpToPx(bottomMargin));
return layoutParams;
}
fun createFrame(
width: Int,
height: Float,
gravity: Int,
leftMargin: Float,
topMargin: Float,
rightMargin: Float,
bottomMargin: Float
): FrameLayout.LayoutParams {
val layoutParams = FrameLayout.LayoutParams(getSize(width.toFloat()), getSize(height), gravity)
layoutParams.setMargins(
Utils.convertDpToPx(leftMargin), Utils.convertDpToPx(topMargin), Utils.convertDpToPx(rightMargin),
Utils.convertDpToPx(bottomMargin)
)
return layoutParams
}
public static GradientDrawable createGradientDrawable(final GradientDrawable.Orientation orientation,
@ColorInt final int[] colors) {
final GradientDrawable drawable = new GradientDrawable(orientation, colors);
drawable.setShape(GradientDrawable.RECTANGLE);
return drawable;
}
fun createGradientDrawable(
orientation: GradientDrawable.Orientation?,
@ColorInt colors: IntArray?
): GradientDrawable {
val drawable = GradientDrawable(orientation, colors)
drawable.shape = GradientDrawable.RECTANGLE
return drawable
}
private static int getSize(float size) {
return (int) (size < 0 ? size : Utils.convertDpToPx(size));
}
private fun getSize(size: Float): Int {
return if (size < 0) size.toInt() else Utils.convertDpToPx(size)
}
public static Pair<Integer, Integer> measure(@NonNull final View view, @NonNull final View parent) {
view.measure(
View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED)
);
return new Pair<>(view.getMeasuredHeight(), view.getMeasuredWidth());
}
fun measure(view: View, parent: View): Pair<Int, Int> {
view.measure(
View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.UNSPECIFIED)
)
return Pair(view.measuredHeight, view.measuredWidth)
}
public static float getTextViewValueWidth(final TextView textView, final String text) {
return textView.getPaint().measureText(text);
}
fun getTextViewValueWidth(textView: TextView, text: String?): Float {
return textView.paint.measureText(text)
}
/**
* Creates [SpringAnimation] for object.
* If finalPosition is not [Float.NaN] then create [SpringAnimation] with
* [SpringForce.mFinalPosition].
*
* @param object Object
* @param property object's property to be animated.
* @param finalPosition [SpringForce.mFinalPosition] Final position of spring.
* @return [SpringAnimation]
*/
@NonNull
public static SpringAnimation springAnimationOf(final Object object,
final FloatPropertyCompat<Object> property,
@Nullable final Float finalPosition) {
return finalPosition == null ? new SpringAnimation(object, property) : new SpringAnimation(object, property, finalPosition);
}
/**
* Creates [SpringAnimation] for object.
* If finalPosition is not [Float.NaN] then create [SpringAnimation] with
* [SpringForce.mFinalPosition].
*
* @param object Object
* @param property object's property to be animated.
* @param finalPosition [SpringForce.mFinalPosition] Final position of spring.
* @return [SpringAnimation]
*/
fun springAnimationOf(
`object`: Any?,
property: FloatPropertyCompat<Any?>?,
finalPosition: Float?
): SpringAnimation {
return finalPosition?.let { SpringAnimation(`object`, property, it) } ?: SpringAnimation(`object`, property)
}
public static void suppressLayoutCompat(@NotNull ViewGroup $this$suppressLayoutCompat, boolean suppress) {
Intrinsics.checkNotNullParameter($this$suppressLayoutCompat, "$this$suppressLayoutCompat");
if (Build.VERSION.SDK_INT >= 29) {
$this$suppressLayoutCompat.suppressLayout(suppress);
} else {
hiddenSuppressLayout($this$suppressLayoutCompat, suppress);
}
fun suppressLayoutCompat(`$this$suppressLayoutCompat`: ViewGroup, suppress: Boolean) {
Intrinsics.checkNotNullParameter(`$this$suppressLayoutCompat`, "\$this\$suppressLayoutCompat")
if (Build.VERSION.SDK_INT >= 29) {
`$this$suppressLayoutCompat`.suppressLayout(suppress)
} else {
hiddenSuppressLayout(`$this$suppressLayoutCompat`, suppress)
}
}
private static boolean tryHiddenSuppressLayout = true;
private var tryHiddenSuppressLayout = true
@SuppressLint({"NewApi"})
private static void hiddenSuppressLayout(ViewGroup group, boolean suppress) {
if (tryHiddenSuppressLayout) {
try {
group.suppressLayout(suppress);
} catch (NoSuchMethodError var3) {
tryHiddenSuppressLayout = false;
}
@SuppressLint("NewApi")
private fun hiddenSuppressLayout(group: ViewGroup, suppress: Boolean) {
if (tryHiddenSuppressLayout) {
try {
group.suppressLayout(suppress)
} catch (var3: NoSuchMethodError) {
tryHiddenSuppressLayout = false
}
}
}
}

12
app/src/main/java/awais/instagrabber/utils/extensions/AnyExtensions.kt

@ -0,0 +1,12 @@
package awais.instagrabber.utils.extensions
val Any.TAG: String
get() {
return if (!javaClass.isAnonymousClass) {
val name = javaClass.simpleName
if (name.length <= 23) name else name.substring(0, 23) // first 23 chars
} else {
val name = javaClass.name
if (name.length <= 23) name else name.substring(name.length - 23, name.length) // last 23 chars
}
}

4
app/src/main/java/awais/instagrabber/viewmodels/UserSearchViewModel.java

@ -73,7 +73,7 @@ public class UserSearchViewModel extends ViewModel {
}
userService = UserService.getInstance();
directMessagesService = DirectMessagesService.getInstance(csrfToken, viewerId, deviceUuid);
rankedRecipientsCache = RankedRecipientsCache.getInstance();
rankedRecipientsCache = RankedRecipientsCache.INSTANCE;
if ((rankedRecipientsCache.isFailed() || rankedRecipientsCache.isExpired()) && !rankedRecipientsCache.isUpdateInitiated()) {
updateRankedRecipientCache();
}
@ -113,7 +113,7 @@ public class UserSearchViewModel extends ViewModel {
continueSearchIfRequired();
return;
}
rankedRecipientsCache.setRankedRecipientsResponse(response.body());
rankedRecipientsCache.setResponse(response.body());
rankedRecipientsCache.setUpdateInitiated(false);
continueSearchIfRequired();
}

Loading…
Cancel
Save