diff --git a/README.md b/README.md index b2d369ca..4b2c84db 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,7 @@ Force stop Instagram and **clear its cache** to apply the changes properly. - [silvzr](https://github.com/silvzr) - [oct888](https://github.com/oct888) - [HalfManBear](https://github.com/halfmanbear) +- [ar5to](https://github.com/ar5to) ## 🙌 Special Thanks - [xHookman](https://github.com/xHookman) diff --git a/app/build.gradle b/app/build.gradle index 57f91d72..d4a8171a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,9 +9,9 @@ android { defaultConfig { applicationId "ps.reso.instaeclipse" minSdkVersion 28 - targetSdk 34 - versionCode 10 - versionName '0.4.3' + targetSdk 36 + versionCode 11 + versionName '0.4.4' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/ps/reso/instaeclipse/Xposed/Module.java b/app/src/main/java/ps/reso/instaeclipse/Xposed/Module.java index 0acf2a4a..2d31f5c6 100644 --- a/app/src/main/java/ps/reso/instaeclipse/Xposed/Module.java +++ b/app/src/main/java/ps/reso/instaeclipse/Xposed/Module.java @@ -8,6 +8,9 @@ import org.luckypray.dexkit.DexKitBridge; +import java.util.Arrays; +import java.util.List; + import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.IXposedHookZygoteInit; import de.robv.android.xposed.XC_MethodHook; @@ -32,11 +35,13 @@ import ps.reso.instaeclipse.utils.core.SettingsManager; import ps.reso.instaeclipse.utils.feature.FeatureFlags; import ps.reso.instaeclipse.utils.feature.FeatureManager; -import ps.reso.instaeclipse.utils.toast.CustomToast; @SuppressLint("UnsafeDynamicallyLoadedCode") public class Module implements IXposedHookLoadPackage, IXposedHookZygoteInit { + // List of supported Instagram package names + private static final List SUPPORTED_PACKAGES = Arrays.asList(CommonUtils.IG_PACKAGE_NAME, // Original package name + "com.instagram.android", "com.instagold.android", "com.instaflux.app", "com.myinsta.android", "cc.honista.app", "com.instaprime.android", "com.instafel.android", "com.instadm.android", "com.dfistagram.android", "com.Instander.android", "com.aero.instagram", "com.instapro.android", "com.instaflow.android", "com.instagram1.android", "com.instagram2.android", "com.instagramclone.android", "com.instaclone.android"); public static DexKitBridge dexKitBridge; public static ClassLoader hostClassLoader; private static String moduleSourceDir; @@ -102,27 +107,27 @@ public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) { } } - // Hook into Instagram - if (lpparam.packageName.equals(CommonUtils.IG_PACKAGE_NAME)) { + // Hook into Instagram and its clones + if (SUPPORTED_PACKAGES.contains(lpparam.packageName)) { try { if (dexKitBridge == null) { // Load the .so file from your module (if not already loaded) System.load(moduleLibDir + "/libdexkit.so"); // XposedBridge.log("libdexkit.so loaded successfully."); - // Initialize DexKitBridge with Instagram's APK + // Initialize DexKitBridge with the target app's APK dexKitBridge = DexKitBridge.create(lpparam.appInfo.sourceDir); - // XposedBridge.log("DexKitBridge initialized with Instagram's APK: " + lpparam.appInfo.sourceDir); + // XposedBridge.log("DexKitBridge initialized with target APK: " + lpparam.appInfo.sourceDir); } - // Use Instagram's ClassLoader + // Use the target app's ClassLoader hostClassLoader = lpparam.classLoader; - // Call the method to hook Instagram + // Call the method to hook the target app hookInstagram(lpparam); } catch (Exception e) { - XposedBridge.log("(InstaEclipse): Failed to initialize DexKitBridge for Instagram: " + e.getMessage()); + XposedBridge.log("(InstaEclipse): Failed to initialize DexKitBridge for " + lpparam.packageName + ": " + e.getMessage()); } } } @@ -144,7 +149,7 @@ private void hookInstagram(XC_LoadPackage.LoadPackageParam lpparam) { @Override protected void afterHookedMethod(MethodHookParam param) { - XposedBridge.log("InstaEclipse: Settings loaded via Application.attach"); + XposedBridge.log("InstaEclipse: Settings loaded via Application.attach for " + lpparam.packageName); // Setup context, preferences Context context = (Context) param.args[0]; @@ -155,7 +160,7 @@ protected void afterHookedMethod(MethodHookParam param) { UIHookManager instagramUI = new UIHookManager(); instagramUI.mainActivity(hostClassLoader); - XposedBridge.log("(InstaEclipse): Instagram package detected. Starting feature hooks..."); + XposedBridge.log("(InstaEclipse): " + lpparam.packageName + " package detected. Starting feature hooks..."); Interceptor interceptor = new Interceptor(); @@ -228,27 +233,20 @@ protected void afterHookedMethod(MethodHookParam param) { try { FollowerIndicator followerIndicator = new FollowerIndicator(); - FollowerIndicator.FollowMethodResult result = - followerIndicator.findFollowerStatusMethod(Module.dexKitBridge); + FollowerIndicator.FollowMethodResult result = followerIndicator.findFollowerStatusMethod(Module.dexKitBridge); if (result != null && FeatureFlags.showFollowerToast) { - followerIndicator.checkFollow(hostClassLoader, result.methodName, result.userClassName); - } else { - XposedBridge.log("(InstaEclipse | FollowerToast): ❌ Method not found"); - } - } catch (Throwable ignored) { - XposedBridge.log("(InstaEclipse | FollowerToast): ❌ Failed to hook"); - } - // Custom Toast - if (FeatureFlags.showFeatureToasts) { - try { + String userIdClass = followerIndicator.findUserIdClassIfNeeded(Module.dexKitBridge, result.userClassName); - CustomToast.hookMainActivity(lpparam); + followerIndicator.checkFollow(hostClassLoader, result.methodName, result.userClassName, userIdClass); - } catch (Throwable ignored) { - XposedBridge.log("(InstaEclipse | CustomToast): ❌ Failed to hook"); + } else { + XposedBridge.log("(InstaEclipse | FollowerToast): ❌ Method not found"); } + } catch (Throwable e) { + + XposedBridge.log("(InstaEclipse | FollowerToast): ❌ Failed to hook + " + e); } // Network Interceptor @@ -263,7 +261,7 @@ protected void afterHookedMethod(MethodHookParam param) { }); } catch (Exception e) { - XposedBridge.log("(InstaEclipse): Failed to hook Instagram: " + e.getMessage()); + XposedBridge.log("(InstaEclipse): Failed to hook " + lpparam.packageName + ": " + e.getMessage()); } } -} \ No newline at end of file +} diff --git a/app/src/main/java/ps/reso/instaeclipse/fragments/HomeFragment.java b/app/src/main/java/ps/reso/instaeclipse/fragments/HomeFragment.java index ebcc7acc..922f0a4b 100644 --- a/app/src/main/java/ps/reso/instaeclipse/fragments/HomeFragment.java +++ b/app/src/main/java/ps/reso/instaeclipse/fragments/HomeFragment.java @@ -143,7 +143,8 @@ private void setupContributorsAndSpecialThanks(View rootView) { new Contributor("BrianML", "https://github.com/brianml31", null, "https://t.me/instamoon_channel"), new Contributor("silvzr", "https://github.com/silvzr", null, null), new Contributor("oct", "https://github.com/oct888", null, null), - new Contributor("HalfManBear", "https://github.com/halfmanbear", null, null) + new Contributor("HalfManBear", "https://github.com/halfmanbear", null, null), + new Contributor("ar5to", "https://github.com/ar5to", null, "https://t.me/ar5to") ); List specialThanks = Arrays.asList( diff --git a/app/src/main/java/ps/reso/instaeclipse/mods/misc/FollowerIndicator.java b/app/src/main/java/ps/reso/instaeclipse/mods/misc/FollowerIndicator.java index c1285006..9dd8648e 100644 --- a/app/src/main/java/ps/reso/instaeclipse/mods/misc/FollowerIndicator.java +++ b/app/src/main/java/ps/reso/instaeclipse/mods/misc/FollowerIndicator.java @@ -19,77 +19,72 @@ public class FollowerIndicator { - public static class FollowMethodResult { - public final String methodName; - public final String userClassName; - - public FollowMethodResult(String methodName, String userClassName) { - this.methodName = methodName; - this.userClassName = userClassName; - } - } + public String type; public FollowMethodResult findFollowerStatusMethod(DexKitBridge bridge) { try { - // 🔍 Step 1: Try new method detection first (obfuscated User class) - String obfUserClass = null; - List errMethods = bridge.findMethod( - FindMethod.create().matcher( - MethodMatcher.create().usingStrings("ERROR_INSERT_EXPIRED_URL") - ) - ); - if (!errMethods.isEmpty()) { - obfUserClass = errMethods.get(0).getClassName(); - // XposedBridge.log("🔍 Found obfuscated User class: " + obfUserClass); + + // Step 1: Get the second Boolean method in FriendshipStatus + try { + + // Find all methods declared inside com.instagram.user.model.FriendshipStatus + List friendshipMethods = bridge.findMethod(FindMethod.create().matcher(MethodMatcher.create().declaredClass("com.instagram.user.model.FriendshipStatus").returnType("java.lang.Boolean"))); + + if (friendshipMethods.size() >= 2) { + MethodData followedByMethod = friendshipMethods.get(1); // 2nd Boolean-returning method = followed_by (BeA) + type = "default"; + return new FollowMethodResult(followedByMethod.getName(), followedByMethod.getClassName()); + } + + } catch (Throwable ignore) { } - if (obfUserClass != null) { - List methods = bridge.findMethod( - FindMethod.create().matcher( - MethodMatcher.create() - .usingStrings("", "", "") - .paramTypes("com.instagram.common.session.UserSession", obfUserClass) - ) - ); - - for (MethodData method : methods) { - // XposedBridge.log("📌 Inspecting (new) method: " + method.getClassName() + "." + method.getName()); - - for (MethodData invoked : method.getInvokes()) { - String className = invoked.getClassName(); - String returnType = String.valueOf(invoked.getReturnType()); - - if (className.contains(obfUserClass) && returnType.contains("boolean")) { - XposedBridge.log("✅ Found follower status method (new): " + invoked.getName()); - return new FollowMethodResult(invoked.getName(), obfUserClass); + try { + // Step 2: Try method detection (obfuscated User class) + String obfUserClass = null; + List errMethods = bridge.findMethod(FindMethod.create().matcher(MethodMatcher.create().usingStrings("ERROR_INSERT_EXPIRED_URL"))); + if (!errMethods.isEmpty()) { + obfUserClass = errMethods.get(0).getClassName(); + } + + if (obfUserClass != null) { + List methods = bridge.findMethod(FindMethod.create().matcher(MethodMatcher.create().usingStrings("", "", "").paramTypes("com.instagram.common.session.UserSession", obfUserClass))); + + for (MethodData method : methods) { + + for (MethodData invoked : method.getInvokes()) { + String className = invoked.getClassName(); + String returnType = String.valueOf(invoked.getReturnType()); + + if (className.contains(obfUserClass) && returnType.contains("boolean")) { + type = "fallback - 1"; + return new FollowMethodResult(invoked.getName(), obfUserClass); + } } } } + } catch (Throwable ignore) { } - // 🔄 Step 2: Fallback to old detection - List methodsOld = bridge.findMethod( - FindMethod.create().matcher( - MethodMatcher.create().usingStrings("", "", "").paramCount(2) - ) - ); - - for (MethodData method : methodsOld) { - List paramTypes = new ArrayList<>(); - for (Object param : method.getParamTypes()) { - paramTypes.add(String.valueOf(param)); - } - if (paramTypes.size() == 2 && - paramTypes.get(0).contains("com.instagram.common.session.UserSession") && - paramTypes.get(1).contains("com.instagram.user.model.User")) { - for (MethodData invoked : method.getInvokes()) { - if (invoked.getClassName().contains("com.instagram.user.model.User") && - String.valueOf(invoked.getReturnType()).contains("boolean")) { - XposedBridge.log("✅ Found follower status method (old): " + invoked.getName()); - return new FollowMethodResult(invoked.getName(), "com.instagram.user.model.User"); + try { + // Step 3: Fallback to old detection + List methodsOld = bridge.findMethod(FindMethod.create().matcher(MethodMatcher.create().usingStrings("", "", "").paramCount(2))); + + for (MethodData method : methodsOld) { + List paramTypes = new ArrayList<>(); + for (Object param : method.getParamTypes()) { + paramTypes.add(String.valueOf(param)); + } + if (paramTypes.size() == 2 && paramTypes.get(0).contains("com.instagram.common.session.UserSession") && paramTypes.get(1).contains("com.instagram.user.model.User")) { + for (MethodData invoked : method.getInvokes()) { + if (invoked.getClassName().contains("com.instagram.user.model.User") && String.valueOf(invoked.getReturnType()).contains("boolean")) { + type = "fallback - 2"; + return new FollowMethodResult(invoked.getName(), "com.instagram.user.model.User"); + } } } } + } catch (Throwable ignore) { } } catch (Throwable e) { @@ -98,19 +93,97 @@ public FollowMethodResult findFollowerStatusMethod(DexKitBridge bridge) { return null; } - public void checkFollow(ClassLoader classLoader, String followerStatusMethod, String userClassName) { + public String findUserIdClassIfNeeded(DexKitBridge bridge, String userClassName) { + try { - if (followerStatusMethod == null || userClassName == null) { - XposedBridge.log("❌ method or class name not found. Skipping hook."); - return; + + if (!"com.instagram.user.model.FriendshipStatus".equals(userClassName)) { + // Step 2 / Step 3 found a usable class — no need to search again + return userClassName; + } else { + String userClass = null; + + try { + // Find method referencing "username_missing_during_update" + List methods = bridge.findMethod(FindMethod.create().matcher(MethodMatcher.create().usingStrings("username_missing_during_update"))); + + if (!methods.isEmpty()) { + MethodData m = methods.get(0); + userClass = m.getClassName(); + + // Verify toString exists + List toStringMethods = bridge.findMethod(FindMethod.create().matcher(MethodMatcher.create().declaredClass(userClass).name("toString").returnType("java.lang.String"))); + + if (!toStringMethods.isEmpty()) { + toStringMethods.get(0).getName(); + MethodData toStringMethodData = toStringMethods.get(0); // keep MethodData + + // Inspect what toString() calls internally + List invokedByToString = toStringMethodData.getInvokes(); + for (MethodData invoked : invokedByToString) { + + // return invoked class + return invoked.getClassName(); + + } + } + } + + } catch (Throwable e) { + XposedBridge.log("❌ Error finding user class via 'username_missing_during_update': " + e.getMessage()); + } + return userClass; } + } catch (Throwable ignore) { + } + + + return null; + } + + public void checkFollow(ClassLoader classLoader, String followerStatusMethod, String userClassName, String userIdClassName) { + try { + + final String[] userId = {null}; + + // If DexKit gave us the interface, switch to the implementation + if (userClassName.equals("com.instagram.user.model.FriendshipStatus")) { + //userClassName = userIdClassName; + userClassName = "com.instagram.user.model.FriendshipStatusImpl"; + try { + if (userIdClassName != null) { + final String methodName = "getId"; + + // Hook into that class’s toString() + XposedHelpers.findAndHookMethod(userIdClassName, classLoader, methodName, new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) { + userId[0] = (String) param.getResult(); + + } + }); + } + } catch (Throwable t) { + XposedBridge.log("❌ Failed to hook toString() fallback: " + t.getMessage()); + } + } + + String finalUserClassName = userClassName; XposedHelpers.findAndHookMethod(userClassName, classLoader, followerStatusMethod, new XC_MethodHook() { @Override - protected void afterHookedMethod(MethodHookParam param) throws Throwable { + protected void afterHookedMethod(MethodHookParam param) { Object user = param.thisObject; - String userId = (String) XposedHelpers.callMethod(user, "getId"); + if (!finalUserClassName.equals("com.instagram.user.model.FriendshipStatusImpl")) { + try { + // Try the usual User.getId() + userId[0] = (String) XposedHelpers.callMethod(param.thisObject, "getId"); + } catch (Throwable ignored) { + + } + } + String username = null; try { username = (String) XposedHelpers.callMethod(user, "getUsername"); @@ -120,28 +193,41 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable { Boolean followsMe = (Boolean) param.getResult(); String targetId = ps.reso.instaeclipse.utils.tracker.FollowIndicatorTracker.currentlyViewedUserId; - - if (userId != null && userId.equals(targetId)) { - Context context = AndroidAppHelper.currentApplication().getApplicationContext(); - String message; - if (username != null && !username.isEmpty()) { - message = "@" + username + " (" + userId + ") " + - (followsMe ? "follows you ✅" : "doesn’t follow you ❌"); - } else { - message = " (" + userId + ") " + - (followsMe ? "follows you ✅" : "doesn’t follow you ❌"); + try { + if (userId[0].equals(targetId)) { + Context context = AndroidAppHelper.currentApplication().getApplicationContext(); + String message; + if (username != null && !username.isEmpty()) { + message = "@" + username + " (" + userId[0] + ") " + (followsMe ? "follows you ✅" : "doesn’t follow you ❌"); + } else { + message = " (" + userId[0] + ") " + (followsMe ? "follows you ✅" : "doesn’t follow you ❌"); + } + CustomToast.showCustomToast(context, message); + ps.reso.instaeclipse.utils.tracker.FollowIndicatorTracker.currentlyViewedUserId = null; } - CustomToast.showCustomToast(context, message); - ps.reso.instaeclipse.utils.tracker.FollowIndicatorTracker.currentlyViewedUserId = null; + } catch (Throwable ignore) { + } + } }); - XposedBridge.log("✅ Hooked follower status method in: " + userClassName + "." + followerStatusMethod); + + XposedBridge.log("(InstaEclipse | FollowerStatus): ✅ Hooked (" + type + "): " + userClassName + "." + followerStatusMethod); FeatureStatusTracker.setHooked("ShowFollowerToast"); } catch (Exception e) { XposedBridge.log("❌ Error hooking follower status: " + e.getMessage()); } } + + public static class FollowMethodResult { + public final String methodName; + public final String userClassName; + + public FollowMethodResult(String methodName, String userClassName) { + this.methodName = methodName; + this.userClassName = userClassName; + } + } } diff --git a/app/src/main/java/ps/reso/instaeclipse/mods/network/Interceptor.java b/app/src/main/java/ps/reso/instaeclipse/mods/network/Interceptor.java index 453f4f09..a3032bae 100644 --- a/app/src/main/java/ps/reso/instaeclipse/mods/network/Interceptor.java +++ b/app/src/main/java/ps/reso/instaeclipse/mods/network/Interceptor.java @@ -141,9 +141,12 @@ protected void beforeHookedMethod(MethodHookParam param) { // XposedBridge.log("❌ [InstaEclipse] Failed to modify URI: " + e.getMessage()); } } + /* + DEV Purposes else { XposedBridge.log("Logging: " + uri.getHost() + uri.getPath()); } + */ if (FeatureFlags.showFollowerToast) { if (uri.getPath() != null && uri.getPath().startsWith("/api/v1/friendships/show/")) { diff --git a/app/src/main/java/ps/reso/instaeclipse/mods/ui/UIHookManager.java b/app/src/main/java/ps/reso/instaeclipse/mods/ui/UIHookManager.java index 081246a0..dbcee7be 100644 --- a/app/src/main/java/ps/reso/instaeclipse/mods/ui/UIHookManager.java +++ b/app/src/main/java/ps/reso/instaeclipse/mods/ui/UIHookManager.java @@ -11,6 +11,8 @@ import android.view.ViewGroup; import android.widget.Toast; +import java.util.Map; + import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; @@ -20,7 +22,9 @@ import ps.reso.instaeclipse.mods.ui.utils.VibrationUtil; import ps.reso.instaeclipse.utils.dialog.DialogUtils; import ps.reso.instaeclipse.utils.feature.FeatureFlags; +import ps.reso.instaeclipse.utils.feature.FeatureStatusTracker; import ps.reso.instaeclipse.utils.ghost.GhostModeUtils; +import ps.reso.instaeclipse.utils.toast.CustomToast; public class UIHookManager { @@ -130,6 +134,16 @@ protected void afterHookedMethod(MethodHookParam param) { try { setupHooks(activity); addGhostEmojiNextToInbox(activity, isAnyGhostOptionEnabled()); + if (!FeatureFlags.showFeatureToasts || CustomToast.toastShown) return; + CustomToast.toastShown = true; + + new Handler(Looper.getMainLooper()).postDelayed(() -> { + StringBuilder sb = new StringBuilder("InstaEclipse Loaded 🎯\n"); + for (Map.Entry entry : FeatureStatusTracker.getStatus().entrySet()) { + sb.append(entry.getValue() ? "✅ " : "❌ ").append(entry.getKey()).append("\n"); + } + CustomToast.showCustomToast(activity.getApplicationContext(), sb.toString().trim()); + }, 1000); } catch (Exception ignored) { } @@ -138,30 +152,26 @@ protected void afterHookedMethod(MethodHookParam param) { }); // Hook onResume - Instagram Main - XposedHelpers.findAndHookMethod( - "com.instagram.mainactivity.InstagramMainActivity", - classLoader, - "onResume", - new XC_MethodHook() { - @Override - protected void afterHookedMethod(MethodHookParam param) { - final Activity activity = (Activity) param.thisObject; - currentActivity = activity; - activity.runOnUiThread(() -> { - try { - setupHooks(activity); - addGhostEmojiNextToInbox(activity, isAnyGhostOptionEnabled()); - - if (FeatureFlags.isImportingConfig) { - // De-bounce: flip it off first so it won't re-trigger on next onResume - FeatureFlags.isImportingConfig = false; - ConfigManager.importConfigFromClipboard(activity); - } - } catch (Exception ignored) {} - }); + XposedHelpers.findAndHookMethod("com.instagram.mainactivity.InstagramMainActivity", classLoader, "onResume", new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) { + final Activity activity = (Activity) param.thisObject; + currentActivity = activity; + activity.runOnUiThread(() -> { + try { + setupHooks(activity); + addGhostEmojiNextToInbox(activity, isAnyGhostOptionEnabled()); + + if (FeatureFlags.isImportingConfig) { + // De-bounce: flip it off first so it won't re-trigger on next onResume + FeatureFlags.isImportingConfig = false; + ConfigManager.importConfigFromClipboard(activity); + } + } catch (Exception ignored) { } - } - ); + }); + } + }); // Hook getBottomSheetNavigator - Instagram Main diff --git a/app/src/main/java/ps/reso/instaeclipse/utils/core/SettingsManager.java b/app/src/main/java/ps/reso/instaeclipse/utils/core/SettingsManager.java index 97467576..15b455d2 100644 --- a/app/src/main/java/ps/reso/instaeclipse/utils/core/SettingsManager.java +++ b/app/src/main/java/ps/reso/instaeclipse/utils/core/SettingsManager.java @@ -39,6 +39,7 @@ public static void saveAllFlags() { editor.putBoolean("quickToggleLive", FeatureFlags.quickToggleLive); // Distraction Free + editor.putBoolean("isExtremeMode", FeatureFlags.isExtremeMode); editor.putBoolean("isDistractionFree", FeatureFlags.isDistractionFree); editor.putBoolean("disableStories", FeatureFlags.disableStories); editor.putBoolean("disableFeed", FeatureFlags.disableFeed); @@ -89,6 +90,7 @@ public static void loadAllFlags(Context context) { FeatureFlags.quickToggleLive = prefs.getBoolean("quickToggleLive", false); // Distraction Free + FeatureFlags.isExtremeMode = prefs.getBoolean("isExtremeMode", false); FeatureFlags.isDistractionFree = prefs.getBoolean("isDistractionFree", false); FeatureFlags.disableStories = prefs.getBoolean("disableStories", false); FeatureFlags.disableFeed = prefs.getBoolean("disableFeed", false); diff --git a/app/src/main/java/ps/reso/instaeclipse/utils/dialog/DialogUtils.java b/app/src/main/java/ps/reso/instaeclipse/utils/dialog/DialogUtils.java index 9fc4739f..55153f2f 100644 --- a/app/src/main/java/ps/reso/instaeclipse/utils/dialog/DialogUtils.java +++ b/app/src/main/java/ps/reso/instaeclipse/utils/dialog/DialogUtils.java @@ -50,11 +50,7 @@ public static void showEclipseOptionsDialog(Context context) { currentDialog.dismiss(); } - currentDialog = new AlertDialog.Builder(themedContext) - .setView(scrollView) - .setTitle(null) - .setCancelable(true) - .create(); + currentDialog = new AlertDialog.Builder(themedContext).setView(scrollView).setTitle(null).setCancelable(true).create(); Objects.requireNonNull(currentDialog.getWindow()).setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); @@ -63,11 +59,7 @@ public static void showEclipseOptionsDialog(Context context) { public static void showSimpleDialog(Context context, String title, String message) { try { - new AlertDialog.Builder(context) - .setTitle(title) - .setMessage(message) - .setPositiveButton("OK", null) - .show(); + new AlertDialog.Builder(context).setTitle(title).setMessage(message).setPositiveButton("OK", null).show(); } catch (Exception e) { // handle UI crash fallback } @@ -116,7 +108,7 @@ private static LinearLayout buildMainMenuLayout(Context context) { mainLayout.addView(createClickableSection(context, "ℹ️ About", () -> showAboutDialog(context))); // 6 - Restart Instagram => OPEN PAGE - mainLayout.addView(createClickableSection(context, "🔁 Restart Instagram", () -> showRestartSection(context))); + mainLayout.addView(createClickableSection(context, "🔁 Restart App", () -> showRestartSection(context))); mainLayout.addView(createDivider(context)); @@ -164,18 +156,10 @@ private static void showGhostQuickToggleOptions(Context context) { LinearLayout layout = createSwitchLayout(context); // Create switches for customizing what gets toggled - Switch[] toggleSwitches = new Switch[]{ - createSwitch(context, "Include Hide Seen", FeatureFlags.quickToggleSeen), - createSwitch(context, "Include Hide Typing", FeatureFlags.quickToggleTyping), - createSwitch(context, "Include Disable Screenshot Detection", FeatureFlags.quickToggleScreenshot), - createSwitch(context, "Include Hide View Once", FeatureFlags.quickToggleViewOnce), - createSwitch(context, "Include Hide Story Seen", FeatureFlags.quickToggleStory), - createSwitch(context, "Include Hide Live Seen", FeatureFlags.quickToggleLive) - }; + Switch[] toggleSwitches = new Switch[]{createSwitch(context, "Include Hide Seen", FeatureFlags.quickToggleSeen), createSwitch(context, "Include Hide Typing", FeatureFlags.quickToggleTyping), createSwitch(context, "Include Disable Screenshot Detection", FeatureFlags.quickToggleScreenshot), createSwitch(context, "Include Hide View Once", FeatureFlags.quickToggleViewOnce), createSwitch(context, "Include Hide Story Seen", FeatureFlags.quickToggleStory), createSwitch(context, "Include Hide Live Seen", FeatureFlags.quickToggleLive)}; // Create Enable/Disable All switch - @SuppressLint("UseSwitchCompatOrMaterialCode") - Switch enableAllSwitch = createSwitch(context, "Enable/Disable All", areAllEnabled(toggleSwitches)); + @SuppressLint("UseSwitchCompatOrMaterialCode") Switch enableAllSwitch = createSwitch(context, "Enable/Disable All", areAllEnabled(toggleSwitches)); // Master listener enableAllSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { @@ -248,47 +232,65 @@ private static void showGhostQuickToggleOptions(Context context) { private static View createDivider(Context context) { View divider = new View(context); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, 2); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 2); params.setMargins(0, 20, 0, 20); divider.setLayoutParams(params); divider.setBackgroundColor(Color.DKGRAY); return divider; } - private static void restartInstagram(Context context) { // Restart Instagram and Remove its cache + /** + * Clears the application's cache and restarts it. + * Works for any package name this module is running in. + * + * @param context The application context. + */ + private static void restartApp(Context context) { try { - Intent intent = context.getPackageManager().getLaunchIntentForPackage("com.instagram.android"); - clearInstagramCache(context); + String packageName = context.getPackageName(); + Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName); + if (intent != null) { + clearAppCache(context); // Clear cache first intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); + // Forcibly kill the current process to ensure a clean restart Runtime.getRuntime().exit(0); } else { - Toast.makeText(context, "Instagram not found", Toast.LENGTH_SHORT).show(); + Toast.makeText(context, "Could not find the app to restart.", Toast.LENGTH_SHORT).show(); } } catch (Exception e) { - XposedBridge.log("InstaEclipse: Restart failed - " + e.getMessage()); + String packageName = context.getPackageName(); + XposedBridge.log("InstaEclipse: Restart failed for " + packageName + " - " + e.getMessage()); Toast.makeText(context, "Restart failed: " + e.getMessage(), Toast.LENGTH_LONG).show(); } } - private static void clearInstagramCache(Context context) { // Clear Instagram Cache + /** + * Clears the cache directory for the current application. + * + * @param context The application context. + */ + private static void clearAppCache(Context context) { try { File cacheDir = context.getCacheDir(); - if (cacheDir != null && cacheDir.exists()) { - XposedBridge.log(""); + if (cacheDir != null && cacheDir.isDirectory()) { deleteRecursive(cacheDir); - XposedBridge.log("InstaEclipse: Cache cleared"); + XposedBridge.log("InstaEclipse: Cache cleared for " + context.getPackageName()); } else { - XposedBridge.log("InstaEclipse: Cache dir not found"); + XposedBridge.log("InstaEclipse: Cache directory not found for " + context.getPackageName()); } } catch (Exception e) { - XposedBridge.log("InstaEclipse: Failed to clear cache - " + e.getMessage()); + XposedBridge.log("InstaEclipse: Failed to clear cache for " + context.getPackageName() + " - " + e.getMessage()); } } - private static void deleteRecursive(File fileOrDirectory) { // Helper method + /** + * Recursively deletes a file or directory. + * + * @param fileOrDirectory The file or directory to delete. + */ + private static void deleteRecursive(File fileOrDirectory) { if (fileOrDirectory.isDirectory()) { File[] children = fileOrDirectory.listFiles(); if (children != null) { @@ -297,9 +299,11 @@ private static void deleteRecursive(File fileOrDirectory) { // Helper method } } } + // A direct result for a file or an empty directory fileOrDirectory.delete(); } + // ==== SECTIONS ==== @SuppressLint("SetTextI18n") @@ -307,8 +311,7 @@ private static void showDevOptions(Context context) { LinearLayout layout = createSwitchLayout(context); // Developer Mode Switch - @SuppressLint("UseSwitchCompatOrMaterialCode") - Switch devModeSwitch = createSwitch(context, "Enable Developer Mode", FeatureFlags.isDevEnabled); + @SuppressLint("UseSwitchCompatOrMaterialCode") Switch devModeSwitch = createSwitch(context, "Enable Developer Mode", FeatureFlags.isDevEnabled); devModeSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { FeatureFlags.isDevEnabled = isChecked; SettingsManager.saveAllFlags(); @@ -326,10 +329,7 @@ private static void showDevOptions(Context context) { FeatureFlags.isImportingConfig = true; Intent importIntent = new Intent(); - importIntent.setComponent(new ComponentName( - "ps.reso.instaeclipse", - "ps.reso.instaeclipse.mods.devops.config.JsonImportActivity" - )); + importIntent.setComponent(new ComponentName("ps.reso.instaeclipse", "ps.reso.instaeclipse.mods.devops.config.JsonImportActivity")); importIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { @@ -358,10 +358,7 @@ private static void showDevOptions(Context context) { // Launch InstaEclipse export screen Intent exportIntent = new Intent(); - exportIntent.setComponent(new ComponentName( - "ps.reso.instaeclipse", - "ps.reso.instaeclipse.mods.devops.config.JsonExportActivity" - )); + exportIntent.setComponent(new ComponentName("ps.reso.instaeclipse", "ps.reso.instaeclipse.mods.devops.config.JsonExportActivity")); exportIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { @@ -384,19 +381,11 @@ private static void showDevOptions(Context context) { private static void showGhostOptions(Context context) { LinearLayout layout = createSwitchLayout(context); - Switch[] switches = new Switch[]{ - createSwitch(context, "Hide Seen", FeatureFlags.isGhostSeen), - createSwitch(context, "Hide Typing", FeatureFlags.isGhostTyping), - createSwitch(context, "Disable Screenshot Detection", FeatureFlags.isGhostScreenshot), - createSwitch(context, "Hide View Once", FeatureFlags.isGhostViewOnce), - createSwitch(context, "Hide Story Seen", FeatureFlags.isGhostStory), - createSwitch(context, "Hide Live Seen", FeatureFlags.isGhostLive) - }; + Switch[] switches = new Switch[]{createSwitch(context, "Hide Seen", FeatureFlags.isGhostSeen), createSwitch(context, "Hide Typing", FeatureFlags.isGhostTyping), createSwitch(context, "Disable Screenshot Detection", FeatureFlags.isGhostScreenshot), createSwitch(context, "Hide View Once", FeatureFlags.isGhostViewOnce), createSwitch(context, "Hide Story Seen", FeatureFlags.isGhostStory), createSwitch(context, "Hide Live Seen", FeatureFlags.isGhostLive)}; layout.addView(createClickableSection(context, "🛠 Customize Quick Toggle", () -> showGhostQuickToggleOptions(context))); - @SuppressLint("UseSwitchCompatOrMaterialCode") - Switch enableAllSwitch = createSwitch(context, "Enable/Disable All", areAllEnabled(switches)); + @SuppressLint("UseSwitchCompatOrMaterialCode") Switch enableAllSwitch = createSwitch(context, "Enable/Disable All", areAllEnabled(switches)); enableAllSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { for (Switch s : switches) { @@ -466,20 +455,16 @@ private static void showAdOptions(Context context) { LinearLayout layout = createSwitchLayout(context); // Create switches - @SuppressLint("UseSwitchCompatOrMaterialCode") - Switch adBlock = createSwitch(context, "Block Ads", FeatureFlags.isAdBlockEnabled); + @SuppressLint("UseSwitchCompatOrMaterialCode") Switch adBlock = createSwitch(context, "Block Ads", FeatureFlags.isAdBlockEnabled); - @SuppressLint("UseSwitchCompatOrMaterialCode") - Switch analytics = createSwitch(context, "Block Analytics", FeatureFlags.isAnalyticsBlocked); + @SuppressLint("UseSwitchCompatOrMaterialCode") Switch analytics = createSwitch(context, "Block Analytics", FeatureFlags.isAnalyticsBlocked); - @SuppressLint("UseSwitchCompatOrMaterialCode") - Switch trackingLinks = createSwitch(context, "Disable Tracking Links", FeatureFlags.disableTrackingLinks); + @SuppressLint("UseSwitchCompatOrMaterialCode") Switch trackingLinks = createSwitch(context, "Disable Tracking Links", FeatureFlags.disableTrackingLinks); Switch[] switches = new Switch[]{adBlock, analytics, trackingLinks}; // Create Enable/Disable All switch - @SuppressLint("UseSwitchCompatOrMaterialCode") - Switch enableAllSwitch = createSwitch(context, "Enable/Disable All", areAllEnabled(switches)); + @SuppressLint("UseSwitchCompatOrMaterialCode") Switch enableAllSwitch = createSwitch(context, "Enable/Disable All", areAllEnabled(switches)); // Master listener enableAllSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { @@ -530,25 +515,53 @@ private static void showDistractionOptions(Context context) { LinearLayout layout = createSwitchLayout(context); // Child switches - Switch disableStoriesSwitch = createSwitch(context, "Disable Stories", FeatureFlags.disableStories); - Switch disableFeedSwitch = createSwitch(context, "Disable Feed", FeatureFlags.disableFeed); - Switch disableReelsSwitch = createSwitch(context, "Disable Reels", FeatureFlags.disableReels); - Switch onlyInDMSwitch = createSwitch(context, "Disable Reels Except in DMs", FeatureFlags.disableReelsExceptDM); - Switch disableExploreSwitch = createSwitch(context, "Disable Explore", FeatureFlags.disableExplore); - Switch disableCommentsSwitch = createSwitch(context, "Disable Comments", FeatureFlags.disableComments); - - Switch[] switches = new Switch[]{ - disableStoriesSwitch, - disableFeedSwitch, - disableReelsSwitch, - onlyInDMSwitch, - disableExploreSwitch, - disableCommentsSwitch - }; + @SuppressLint("UseSwitchCompatOrMaterialCode") Switch extremeModeSwitch = createSwitch(context, "Extreme Mode 🔒 (Irreversible until reinstall)", FeatureFlags.isExtremeMode); + @SuppressLint("UseSwitchCompatOrMaterialCode") Switch disableStoriesSwitch = createSwitch(context, "Disable Stories", FeatureFlags.disableStories); + @SuppressLint("UseSwitchCompatOrMaterialCode") Switch disableFeedSwitch = createSwitch(context, "Disable Feed", FeatureFlags.disableFeed); + @SuppressLint("UseSwitchCompatOrMaterialCode") Switch disableReelsSwitch = createSwitch(context, "Disable Reels", FeatureFlags.disableReels); + @SuppressLint("UseSwitchCompatOrMaterialCode") Switch onlyInDMSwitch = createSwitch(context, "Disable Reels Except in DMs", FeatureFlags.disableReelsExceptDM); + @SuppressLint("UseSwitchCompatOrMaterialCode") Switch disableExploreSwitch = createSwitch(context, "Disable Explore", FeatureFlags.disableExplore); + @SuppressLint("UseSwitchCompatOrMaterialCode") Switch disableCommentsSwitch = createSwitch(context, "Disable Comments", FeatureFlags.disableComments); + + Switch[] switches = new Switch[]{disableStoriesSwitch, disableFeedSwitch, disableReelsSwitch, onlyInDMSwitch, disableExploreSwitch, disableCommentsSwitch}; + // Enable/Disable All - @SuppressLint("UseSwitchCompatOrMaterialCode") - Switch enableAllSwitch = createSwitch(context, "Enable/Disable All", areAllEnabled(switches)); + @SuppressLint("UseSwitchCompatOrMaterialCode") Switch enableAllSwitch = createSwitch(context, "Enable/Disable All", areAllEnabled(switches)); + + if (FeatureFlags.isExtremeMode) { + disableAllSwitches(switches, enableAllSwitch, onlyInDMSwitch); + extremeModeSwitch.setChecked(true); + extremeModeSwitch.setEnabled(false); + } + + extremeModeSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (isChecked) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle("Activate Extreme Mode?"); + builder.setMessage("Once activated, you cannot disable Distraction-Free Mode until you reinstall the app. Continue?"); + builder.setPositiveButton("Yes", (dialog, which) -> { + FeatureFlags.isExtremeMode = true; + FeatureFlags.isDistractionFree = true; + + // Save user’s current selections before freezing them + FeatureFlags.disableStories = disableStoriesSwitch.isChecked(); + FeatureFlags.disableFeed = disableFeedSwitch.isChecked(); + FeatureFlags.disableReels = disableReelsSwitch.isChecked(); + FeatureFlags.disableReelsExceptDM = onlyInDMSwitch.isChecked(); + FeatureFlags.disableExplore = disableExploreSwitch.isChecked(); + FeatureFlags.disableComments = disableCommentsSwitch.isChecked(); + SettingsManager.saveAllFlags(); + + // Disable all UI switches to lock them + disableAllSwitches(switches, enableAllSwitch, onlyInDMSwitch); + extremeModeSwitch.setEnabled(false); + }); + builder.setNegativeButton("Cancel", (dialog, which) -> extremeModeSwitch.setChecked(false)); + builder.show(); + } + }); + // Master switch listener enableAllSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { @@ -595,6 +608,7 @@ private static void showDistractionOptions(Context context) { onlyInDMSwitch.setEnabled(disableReelsSwitch.isChecked()); // Layout building + layout.addView(extremeModeSwitch); layout.addView(createDivider(context)); layout.addView(createEnableAllSwitch(context, enableAllSwitch)); layout.addView(createDivider(context)); @@ -615,9 +629,24 @@ private static void showDistractionOptions(Context context) { SettingsManager.saveAllFlags(); } + private static void disableAllSwitches(Switch[] switches, @SuppressLint("UseSwitchCompatOrMaterialCode") Switch master, @SuppressLint("UseSwitchCompatOrMaterialCode") Switch onlyInDMSwitch) { + + for (Switch s : switches) { + if (s == onlyInDMSwitch) { + // Special rule for onlyInDM + s.setEnabled(s.isChecked()); // editable only if it was checked + } else { + // Normal switches: lock if checked, editable if unchecked + s.setEnabled(!s.isChecked()); + } + } + + // Master switch always frozen ON + master.setEnabled(false); + } + - private static void updateMasterSwitch(@SuppressLint("UseSwitchCompatOrMaterialCode") Switch enableAllSwitch, Switch[] switches, - @SuppressLint("UseSwitchCompatOrMaterialCode") Switch disableReelsSwitch, @SuppressLint("UseSwitchCompatOrMaterialCode") Switch onlyInDMSwitch) { + private static void updateMasterSwitch(@SuppressLint("UseSwitchCompatOrMaterialCode") Switch enableAllSwitch, Switch[] switches, @SuppressLint("UseSwitchCompatOrMaterialCode") Switch disableReelsSwitch, @SuppressLint("UseSwitchCompatOrMaterialCode") Switch onlyInDMSwitch) { enableAllSwitch.setOnCheckedChangeListener(null); enableAllSwitch.setChecked(areAllEnabled(switches)); enableAllSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { @@ -629,21 +658,14 @@ private static void updateMasterSwitch(@SuppressLint("UseSwitchCompatOrMaterialC } - private static void showMiscOptions(Context context) { LinearLayout layout = createSwitchLayout(context); // Create all child switches - Switch[] switches = new Switch[]{ - createSwitch(context, "Disable Story Auto-Swipe", FeatureFlags.disableStoryFlipping), - createSwitch(context, "Disable Video Autoplay", FeatureFlags.disableVideoAutoPlay), - createSwitch(context, "Show Follower Toast", FeatureFlags.showFollowerToast), - createSwitch(context, "Show Feature Toasts", FeatureFlags.showFeatureToasts) - }; + Switch[] switches = new Switch[]{createSwitch(context, "Disable Story Auto-Swipe", FeatureFlags.disableStoryFlipping), createSwitch(context, "Disable Video Autoplay", FeatureFlags.disableVideoAutoPlay), createSwitch(context, "Show Follower Toast", FeatureFlags.showFollowerToast), createSwitch(context, "Show Feature Toasts", FeatureFlags.showFeatureToasts)}; // Create Enable/Disable All switch - @SuppressLint("UseSwitchCompatOrMaterialCode") - Switch enableAllSwitch = createSwitch(context, "Enable/Disable All", areAllEnabled(switches)); + @SuppressLint("UseSwitchCompatOrMaterialCode") Switch enableAllSwitch = createSwitch(context, "Enable/Disable All", areAllEnabled(switches)); enableAllSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { for (Switch s : switches) { @@ -724,17 +746,13 @@ private static void showAboutDialog(Context context) { githubButton.setBackgroundTintList(ColorStateList.valueOf(Color.parseColor("#3F51B5"))); githubButton.setPadding(40, 20, 40, 20); - LinearLayout.LayoutParams githubParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ); + LinearLayout.LayoutParams githubParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); githubParams.gravity = Gravity.CENTER_HORIZONTAL; githubButton.setLayoutParams(githubParams); githubButton.setOnClickListener(v -> { - Intent browserIntent = new Intent(Intent.ACTION_VIEW, - android.net.Uri.parse("https://github.com/ReSo7200/InstaEclipse")); + Intent browserIntent = new Intent(Intent.ACTION_VIEW, android.net.Uri.parse("https://github.com/ReSo7200/InstaEclipse")); browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(browserIntent); }); @@ -755,7 +773,7 @@ private static void showRestartSection(Context context) { layout.setGravity(Gravity.CENTER_HORIZONTAL); TextView message = new TextView(context); - message.setText("⚠️ Restart Instagram and remove its cache?!"); + message.setText("⚠️ Clear app cache and restart?"); message.setTextColor(Color.WHITE); message.setTextSize(18f); message.setGravity(Gravity.CENTER); @@ -766,12 +784,12 @@ private static void showRestartSection(Context context) { restartButton.setTextColor(Color.WHITE); restartButton.setPadding(40, 20, 40, 20); - restartButton.setOnClickListener(v -> restartInstagram(context)); + restartButton.setOnClickListener(v -> restartApp(context)); layout.addView(message); layout.addView(restartButton); - showSectionDialog(context, "Restart Instagram", layout, () -> { + showSectionDialog(context, "Restart App", layout, () -> { }); } @@ -829,10 +847,7 @@ private static void showSectionDialog(Context context, String title, LinearLayou ScrollView scrollView = new ScrollView(context); scrollView.addView(container); - currentDialog = new AlertDialog.Builder(context) - .setView(scrollView) - .setCancelable(true) - .create(); + currentDialog = new AlertDialog.Builder(context).setView(scrollView).setCancelable(true).create(); Objects.requireNonNull(currentDialog.getWindow()).setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); @@ -864,33 +879,23 @@ private static Switch createSwitch(Context context, String label, boolean defaul } private static ColorStateList createThumbColor() { - return new ColorStateList( - new int[][]{ - new int[]{-android.R.attr.state_enabled}, // Disabled - new int[]{android.R.attr.state_checked}, // Checked - new int[]{-android.R.attr.state_checked} // Unchecked - }, - new int[]{ - Color.parseColor("#555555"), // Disabled - Color.parseColor("#448AFF"), // ON - Color.parseColor("#FFFFFF") // OFF - } - ); + return new ColorStateList(new int[][]{new int[]{-android.R.attr.state_enabled}, // Disabled + new int[]{android.R.attr.state_checked}, // Checked + new int[]{-android.R.attr.state_checked} // Unchecked + }, new int[]{Color.parseColor("#555555"), // Disabled + Color.parseColor("#448AFF"), // ON + Color.parseColor("#FFFFFF") // OFF + }); } private static ColorStateList createTrackColor() { - return new ColorStateList( - new int[][]{ - new int[]{-android.R.attr.state_enabled}, // Disabled - new int[]{android.R.attr.state_checked}, // Checked - new int[]{-android.R.attr.state_checked} // Unchecked - }, - new int[]{ - Color.parseColor("#777777"), // Disabled - Color.parseColor("#1C4C78"), // ON - Color.parseColor("#CFD8DC") // OFF - } - ); + return new ColorStateList(new int[][]{new int[]{-android.R.attr.state_enabled}, // Disabled + new int[]{android.R.attr.state_checked}, // Checked + new int[]{-android.R.attr.state_checked} // Unchecked + }, new int[]{Color.parseColor("#777777"), // Disabled + Color.parseColor("#1C4C78"), // ON + Color.parseColor("#CFD8DC") // OFF + }); } private static View createClickableSection(Context context, String label, Runnable onClick) { diff --git a/app/src/main/java/ps/reso/instaeclipse/utils/feature/FeatureFlags.java b/app/src/main/java/ps/reso/instaeclipse/utils/feature/FeatureFlags.java index 1345ac4a..446a9169 100644 --- a/app/src/main/java/ps/reso/instaeclipse/utils/feature/FeatureFlags.java +++ b/app/src/main/java/ps/reso/instaeclipse/utils/feature/FeatureFlags.java @@ -26,6 +26,7 @@ public class FeatureFlags { // Distraction Free + public static boolean isExtremeMode = false; // Extreme Mode public static boolean isDistractionFree = false; public static boolean disableStories = false; public static boolean disableFeed = false; diff --git a/app/src/main/java/ps/reso/instaeclipse/utils/toast/CustomToast.java b/app/src/main/java/ps/reso/instaeclipse/utils/toast/CustomToast.java index 664be0e5..3e351e5e 100644 --- a/app/src/main/java/ps/reso/instaeclipse/utils/toast/CustomToast.java +++ b/app/src/main/java/ps/reso/instaeclipse/utils/toast/CustomToast.java @@ -1,26 +1,18 @@ package ps.reso.instaeclipse.utils.toast; -import android.app.Activity; import android.content.Context; import android.graphics.Color; -import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.view.Gravity; import android.widget.TextView; -import java.util.Map; - -import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedBridge; -import de.robv.android.xposed.XposedHelpers; -import de.robv.android.xposed.callbacks.XC_LoadPackage; -import ps.reso.instaeclipse.utils.feature.FeatureStatusTracker; public class CustomToast { - private static boolean toastShown = false; + public static boolean toastShown = false; public static void showCustomToast(Context context, String message) { if (context == null) { @@ -30,7 +22,8 @@ public static void showCustomToast(Context context, String message) { new Handler(Looper.getMainLooper()).post(() -> { try { - TextView toastText = new TextView(context); + Context safeContext = new android.view.ContextThemeWrapper(context, android.R.style.Theme_Material_Light); + TextView toastText = new TextView(safeContext); toastText.setText(message); toastText.setTextColor(Color.WHITE); toastText.setBackgroundColor(Color.parseColor("#CC000000")); // semi-transparent black @@ -50,37 +43,4 @@ public static void showCustomToast(Context context, String message) { }); } - public static void hookMainActivity(XC_LoadPackage.LoadPackageParam lpparam) { - try { - XposedHelpers.findAndHookMethod( - "com.instagram.mainactivity.LauncherActivity", - lpparam.classLoader, - "onCreate", - Bundle.class, - new XC_MethodHook() { - @Override - protected void afterHookedMethod(MethodHookParam param) { - if (toastShown) return; - toastShown = true; - - Context context = ((Activity) param.thisObject).getApplicationContext(); - if (context == null || !FeatureStatusTracker.hasEnabledFeatures()) - return; - - new Handler(Looper.getMainLooper()).postDelayed(() -> { - StringBuilder sb = new StringBuilder("InstaEclipse Loaded 🎯\n"); - - for (Map.Entry entry : FeatureStatusTracker.getStatus().entrySet()) { - sb.append(entry.getValue() ? "✅ " : "❌ ").append(entry.getKey()).append("\n"); - } - - showCustomToast(context, sb.toString().trim()); - }, 1000); - } - } - ); - } catch (Throwable t) { - XposedBridge.log("❌ Failed to hook LauncherActivity for toast: " + Log.getStackTraceString(t)); - } - } } diff --git a/app/src/main/java/ps/reso/instaeclipse/utils/version/VersionCheckUtility.java b/app/src/main/java/ps/reso/instaeclipse/utils/version/VersionCheckUtility.java index 24736102..f47a8820 100644 --- a/app/src/main/java/ps/reso/instaeclipse/utils/version/VersionCheckUtility.java +++ b/app/src/main/java/ps/reso/instaeclipse/utils/version/VersionCheckUtility.java @@ -15,7 +15,7 @@ public class VersionCheckUtility { - private static final String CURRENT_VERSION = "0.4.3"; // Current version + private static final String CURRENT_VERSION = "0.4.4"; // Current version private static final String VERSION_CHECK_URL = "https://raw.githubusercontent.com/ReSo7200/InstaEclipse/refs/heads/main/version.json"; // JSON URL public static void checkForUpdates(Context context) { diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml new file mode 100644 index 00000000..47011473 --- /dev/null +++ b/app/src/main/res/values-el/strings.xml @@ -0,0 +1,120 @@ + + + + InstaEclipse + Αρχική + Λειτουργίες + Βοήθεια + Έκδοση + + + Το Module δεν είναι ενεργό. Παρακαλούμε ενεργοποιήστε το. + + Κατάσταση Module: + + Κατάσταση Module: Ανενεργό + Παρακαλούμε ενεργοποιήστε το Module στο Xposed Installer. + + Κατάσταση Module: Ενεργό (Χωρίς Πρόσβαση Root) + Η πρόσβαση Root είναι απαραίτητη για την αυτόματη επανεκκίνηση. Απαιτείται χειροκίνητη επανεκκίνηση. + + Κατάσταση Module: Ενεργό + Το module είναι ενεργό και λειτουργεί. + + Γίνεται έλεγχος του Instagram + Το Instagram είναι εγκατεστημένο + Το Instagram δεν είναι εγκατεστημένο + Σφάλμα ελέγχου κατάστασης του Instagram + + Άνοιγμα του Instagram + Λήψη APK + Οδηγίες χρήσης + Πατήστε παρατεταμένα το κουμπί αναζήτησης + Συνεισφέροντες + Θερμές ευχαριστίες + + + Instagram Logo + instagram_info + + + Βοήθεια & Υποστήριξη + Βρείτε ενημερώσεις και οδηγίες. + Μετάβαση στο GitHub + Βρείτε υποστήριξη. + Μετάβαση στο Telegram + Ομάδα Telegram του InstaEclipse + + + InstaEclipse GitHub + GitHub + Telegram + + + Συχνές ερωτήσεις + 1. Δεν λειτουργεί ή κλείνει απότομα η έκδοση από το Google Play Store;\n- Χρησιμοποιήστε APK από το APKMirror + 2. Το module δεν είναι ενεργό;\n- Απενεργοποιήστε και ενεργοποιήστε ξανά το module στο LSPosed. + 3. Προβλήματα με τις λειτουργίες του InstaEclipse;\n- Κλείστε αναγκαστικά το Instagram και ανοίξτε το ξανά. + 4. Οι επιλογές προγραμματιστών προκαλούν προβλήματα;\n- Ακολουθήστε τον οδηγό επιλογών προγραμματιστών. + 5. Οι επιλογές προγραμματιστών έχουν περίεργες ετικέτες ή αριθμούς;\n- Εγκαταστήστε τις εκδόσεις Beta ή Alpha, καθώς οι σταθερές εκδόσεις έχουν απόκρυψη ονομάτων/αριθμών. + 6. Η λειτουργία “Αφαίρεση Περισπασμών” είναι ενεργή αλλά εμφανίζεται ακόμα περιεχόμενο;\n- Κλείστε αναγκαστικά το Instagram και κάντε εκκαθάριση της cache του. + + + Το Module εξακολουθεί να μην λειτουργεί; + + + Επιλογές Προγραμματιστών + Αόρατη Λειτουργία + Αφαίρεση Περισπασμών + Αφαίρεση Διαφημίσεων + Αφαίρεση Αναλυτικών Στοιχείων + + Ενεργοποίηση/Απενεργοποίηση Όλων + + + Hint + + + Ρυθμίσεις Αόρατης Λειτουργίας + Πληκτρολόγηση + Ιστορία + Προβολή μία φορά + Ζωντανή Παρακολούθηση + Μηνύματα + Πληκτρολόγηση + Στιγμιότυπο Οθόνης + + + Οδηγός Επιλογών Προγραμματιστών + Χρησιμοποιήστε το σε εκδόσεις Beta/Alpha μόνο! + Βεβαιωθείτε πως έχετε συνδεθεί πρώτα! + + 1. Ανοίξτε το Instagram και πατήστε παρατεταμένα το κουμπί της αρχικής σελίδας. + 2. Μεταβείτε στο Developer Options > MetaConfig Settings & Overrides. + 3. Κάντε αναζήτηση για \'Employee\' και ενεργοποιήστε τις ακόλουθες επιλογές: + 4. Τέλος, απενεργοποιήστε τις Επιλογές Προγραμματιστών από το module για την αποφυγή προβλημάτων. + + + • is employee + • is employee or test user + • employee options + + + Ρυθμίσεις Αφαίρεσης Περισπασμών + Απενεργοποίηση Ιστοριών + Απενεργοποίηση Ροής Δημοσιεύσεων + Απενεργοποίηση των Reels + Απενεργοποίηση Εξερεύνησης + Απενεργοποίηση Σχολίων + + + Διάφορα + Διάφορες Ρυθμίσεις + Απενεργοποίηση Εναλλαγής Ιστοριών + Απενεργοποίηση Αυτόματης Αναπαραγωγής Βίντεο + Εμφάνιση μηνύματος ακόλουθου + Εμφάνιση μηνύματος ενεργοποιημένων λειτουργιών + + + Όνομα Συνεισφέροντα + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml new file mode 100644 index 00000000..057ac4e4 --- /dev/null +++ b/app/src/main/res/values-fr/strings.xml @@ -0,0 +1,122 @@ + + + InstaEclipse + v0.4.4 Beta + Menu + Fonctionnalités + Aide + Version + + + Le module n’est pas activé. Veuillez l’activer. + + Statut du module : + + Statut du module : Désactiver + Veuillez activer le module dans Xposed. + + Statut du module : Activé (Accès Non Rooté) + L\'accès Root est requis pour le redémarrage-auto. Redémarrage manuel requis. + + Statut du module : Activé + Le module est actif et fonctionnel. + + Vérification d\'Instagram + Instagram est installé + Instagram n\'est pas installé + Erreur lors de la vérification du statut d\'Instagram + + Lancer Instagram + Télécharger l\'APK + Comment utiliser + Maintenez le bouton recherche + Contributeurs + Remerciements + + + Instagram Logo + instagram_info + + + Aide & Dépannage + Trouver les mise à jours et la documentation. + Allez sur le GitHub + Trouver de l\'aide. + Aller sur Telegram + Groupe Telegram InstaEclipse + + + Github InstaEclipse + GitHub + Telegram + + + FAQ + 1. Ne fonctionne pas/plante lors de l'utilisation de la version Google Play ?
- Utilisez les APKs de APKMirror]]>
+ 2. Le module n'est pas activé ?
- Désactivez et réactivez le module dans LSPosed.]]>
+ 3. Les fonctionnalités ne fonctionnent pas ?
- Forcez l'arrêt et redémarrez Instagram.]]>
+ 4. Les options développeur provoquent des plantages ?
- Suivez le guide des options pour développeurs.]]>
+ 5. Les options développeur ont des étiquettes ou des numéros bizarres ?
- Utilisez les versions Bêta ou Alpha car les versions Stables ont des obfuscations.]]>
+ 6. Le mode Sans Distraction est activé mais le contenu apparaît toujours ?
- Forcez l'arrêt d'Instagram et videz son cache.]]>
+ + + Le module ne fonctionne toujours pas? + rooté essayez d\'utiliser JingMatrix\'s LSPosed

Si vous êtes non rooté installé JingMatrix\'s LSPatch puis suivez ce guide]]>
+ + + Options Développeurs + Mode Fantôme + Sans Distractions + Enlevé les pubs + Désactiver les Analyses + + Activé/Désactiver Tous + + + Hint + + + Options Mode Fantôme + Entrain d\'écrire + Story + Vue Unique + Live + Messages Direct + Entrain d\'écrire + Screenshot + + + Guide options développeurs + Utiliser le uniquement sur les versions Beta/Alpha seulement ! + Assurer vous d\'être d\'abord connecté ! + + 1. Ouvrez Instagram et maintenez le bouton Menu. + 2. Navigué au options développeurs > MetaConfig Settings & Overrides. + 3. Recherchez \'Employee\' et activer les options suivantes: + 4. Enfin, Désactiver les options pour développeurs a partir du module pour éviter les plantages d\'application. + + + • is employee + • is employee or test user + • employee options + + + Options Sans Distractions + Désactiver les Story + Désactiver le fil d\'actualité + Désactiver les Reels + Désactiver Explorer + Désactiver les Commentaires + + + Divers + Options Divers + Désactiver défilement auto des Story + Désactiver lecture auto des vidéos + Afficher Notification "Suivi en Retour" + Afficher la Notification des fonctions activées + + + Contributor Name + +
diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml new file mode 100644 index 00000000..48f89260 --- /dev/null +++ b/app/src/main/res/values-he/strings.xml @@ -0,0 +1,110 @@ + + + InstaEclipse + דף הבית + תכונות + עזרה + + + גרסה + המודול אינו מופעל. נא להפעיל אותו. + + סטטוס המודול: + + סטטוס המודול: אינו מופעל + נא להפעיל את המודול בXposed. + + סטטוס המודול: מופעל )בלי גישה לRoot) + גישה לRoot נדרש עבור תכונת הauto-restart. הפעלה מחדש ידנית נדרשת. + + סטטוס המודול: תקין + המודול תקין ועובד. + + בודק אינסטגרם + אינסטגרם מותקן + אינסטגרם אינו מותקן + שגיאה במהלך בדיקת סטטוס אינסטגרם + + הפעל אינסטגרם + הורדת APK + הוראות שימוש + נא ללחוץ ארוך על כפתור החיפוש באפליקציה + תורמים + תודה מיוחדת + + + + + עזרה & פתירת בעיות + מציאת עדכונים ותיעוד. + פתיחת GitHub + מציאת תמיכה. + פתיחת Telegram + + + + + שאלות נפוצות + 1. אינו עובד/קורס אצל שימוש בגרסת האפליקציה מהGoogle Play?\n- נא להשתמש בקובץ APK מאתר APKMirror. + 2. מודול אינו מופעל?\n- השבת את המודול ותפעילו אותו מחדש בLSPosed. + 3. התכונות אינם עובדות?\n- נא לעצור בכוח את אפליקציית אינסטגרם ותפעילו אותו מחדש. + 4. הגדרות מפתחים גורמים לקריסות?\n- נא לעקוב אחרי מדריך ההגרות מפתחים. + 5. להגרות מפתחים יש תגים מוזרים או מספרים?\n- נא להשתמש בגרסת Beta או Alpha במקום, כי גרסאות יציבות )Stable) של האפליקציה עוברות אובפוסקציה. + 6. "ללא הסחות דעת" מופעל אך התוכן עדיין מופיע?\n- לעצור בכוח את האפליקציה ולמחוק לו את הcache. + + + המודול עדיין לא עובד? + + + הגדרות מפתחים + מצב רפאים + ללא הסחות דעת + מחיקת פרסומות + מחיקת אנליטיקות + + הפעלה/השבתת הכל + + + + + הגדרות מצב רפאים + הקלדה + Story + מדיה חד-פעמית (View-Once) + Live + הודעות פרטיות + הקלדה + צילום מסך + + + מדריך הגדרות מפתחים + נא להשתמש רק על גרסאות Beta או Alpha בלבד! + נא לוודא שאתם כבר מחוברים לחשבון שלכם! + + 1. לפתוח אינסטגרם וללחוץ ארוך על כפתור עמוד הבית. + 2. לגשת להגדרת מפתחים > הגדרות MetaConfig & Overrides. + 3. לחפש \'Employee\' ולהפעיל את ההגדרות הבאות: + 4. לסוף, השבת את ההגדרות מפתחים מהמודול כדי למנות קריסות אפליקציה. + + + + + הגדרות מצב "ללא הסחות דעת" + השבתת Stories + השבתת תוכן עמוד הבית + השבתת ריילז + השבתת עמוד הExplore + השבתת עמוד התגובות + + + אחר + הגדרות נוספות + השבתת הלפת Stories + השבתת התחלת סרטונים אוטומתית + הראה עוקבים בהודעת סטטוס + הראה הודעת סטטוס עם כל התכונות המופעלות + + + שם התורם + + \ No newline at end of file diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml new file mode 100644 index 00000000..bb3e93c0 --- /dev/null +++ b/app/src/main/res/values-id/strings.xml @@ -0,0 +1,122 @@ + + + InstaEclipse + v0.4.2 Beta + Beranda + Fitur + Bantuan + Versi + + + Modul tidak aktif. Silakan aktifkan. + + Status Modul: + + Status Modul: Dinonaktifkan + Silakan aktifkan modul di Xposed Installer. + + Status Modul: Diaktifkan (Tanpa Akses Root) + Akses root diperlukan untuk memulai ulang otomatis. Diperlukan restart manual. + + Status Modul: Diaktifkan + Modul aktif dan berfungsi. + + Memeriksa Instagram + Instagram terpasang + Instagram tidak terpasang + Kesalahan saat memeriksa status Instagram + + Luncurkan Instagram + Unduh APK + Cara Penggunaan + Tahan ikon pencarian + Kontributor + Terima Kasih Khusus + + + Instagram Logo + instagram_info + + + Bantuan & Pemecahan Masalah + Temukan pembaruan dan dokumentasi. + Buka GitHub + Temukan dukungan. + Buka Telegram + Grup Telegram InstaEclipse + + + InstaEclipse GitHub + GitHub + Telegram + + + Pertanyaan yang Sering Diajukan + 1. Tidak berfungsi/crash saat menggunakan Versi Google Play?\n- Gunakan APK dari APKMirror + 2. Modul tidak aktif?\n- Nonaktifkan dan aktifkan kembali modul di LSPosed. + 3. Fitur tidak berfungsi?\n- Paksa berhenti dan mulai ulang Instagram. + 4. Opsi pengembang menyebabkan crash?\n- Ikuti panduan opsi pengembang. + 5. Opsi pengembang memiliki label atau angka yang aneh?\n- Gunakan versi Beta atau Alpha karena versi Stabil memiliki obfuskasi. + 6. Mode Bebas Gangguan diaktifkan tetapi konten masih muncul?\n- Paksa berhenti Instagram dan bersihkan cache-nya. + + + Modul masih tidak berfungsi? + root, coba gunakan LSPosed dari JingMatrix

Jika Anda tidak memiliki root, instal LSPatch dari JingMatrix lalu ikuti panduan ini]]>
+ + + Opsi Pengembang + Mode Hantu + Bebas Gangguan + Hapus Iklan + Hapus Analitik + + Aktifkan/Nonaktifkan Semua + + + Hint + + + Opsi Mode Hantu + Mengetik + Story + Sekali Lihat + Live + Pesan Langsung + Mengetik + Tangkapan Layar + + + Panduan Opsi Pengembang + Gunakan hanya pada versi Beta/Alpha! + Pastikan Anda sudah masuk terlebih dahulu! + + 1. Buka Instagram dan tahan tombol Beranda. + 2. Arahkan ke Opsi Pengembang > Pengaturan & Penggantian MetaConfig. + 3. Cari \'Employee\' dan aktifkan opsi berikut: + 4. Terakhir, nonaktifkan Opsi Pengembang dari modul untuk mencegah aplikasi crash. + + + • is employee + • is employee or test user + • employee options + + + Opsi Bebas Gangguan + Nonaktifkan Story + Nonaktifkan Beranda + Nonaktifkan Reels + Nonaktifkan Explore + Nonaktifkan Komentar + + + Lainnya + Opsi Lainnya + Nonaktifkan Story Flipping + Nonaktifkan Putar Otomatis Video + Tampilkan Toast Pengikut + Tampilkan Toast Fitur yang Diaktifkan + + + Nama Kontributor + +
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml new file mode 100644 index 00000000..2d1c38ef --- /dev/null +++ b/app/src/main/res/values-iw/strings.xml @@ -0,0 +1,110 @@ + + + InstaEclipse + דף הבית + תכונות + עזרה + + + גרסה + המודול אינו מופעל. נא להפעיל אותו. + + סטטוס המודול: + + סטטוס המודול: אינו מופעל + נא להפעיל את המודול בXposed. + + סטטוס המודול: מופעל )בלי גישה לRoot) + גישה לRoot נדרש עבור תכונת הauto-restart. הפעלה מחדש ידנית נדרשת. + + סטטוס המודול: תקין + המודול תקין ועובד. + + בודק אינסטגרם + אינסטגרם מותקן + אינסטגרם אינו מותקן + שגיאה במהלך בדיקת סטטוס אינסטגרם + + הפעל אינסטגרם + הורדת APK + הוראות שימוש + נא ללחוץ ארוך על כפתור החיפוש באפליקציה + תורמים + תודה מיוחדת + + + + + עזרה & פתירת בעיות + מציאת עדכונים ותיעוד. + פתיחת GitHub + מציאת תמיכה. + פתיחת Telegram + + + + + שאלות נפוצות + 1. אינו עובד/קורס אצל שימוש בגרסת האפליקציה מהGoogle Play?\n- נא להשתמש בקובץ APK מאתר APKMirror. + 2. מודול אינו מופעל?\n- השבת את המודול ותפעילו אותו מחדש בLSPosed. + 3. התכונות אינם עובדות?\n- נא לעצור בכוח את אפליקציית אינסטגרם ותפעילו אותו מחדש. + 4. הגדרות מפתחים גורמים לקריסות?\n- נא לעקוב אחרי מדריך ההגרות מפתחים. + 5. להגרות מפתחים יש תגים מוזרים או מספרים?\n- נא להשתמש בגרסת Beta או Alpha במקום, כי גרסאות יציבות )Stable) של האפליקציה עוברות אובפוסקציה. + 6. "ללא הסחות דעת" מופעל אך התוכן עדיין מופיע?\n- לעצור בכוח את האפליקציה ולמחוק לו את הcache. + + + המודול עדיין לא עובד? + + + הגדרות מפתחים + מצב רפאים + ללא הסחות דעת + מחיקת פרסומות + מחיקת אנליטיקות + + הפעלה/השבתת הכל + + + + + הגדרות מצב רפאים + הקלדה + Story + מדיה חד-פעמית (View-Once) + Live + הודעות פרטיות + הקלדה + צילום מסך + + + מדריך הגדרות מפתחים + נא להשתמש רק על גרסאות Beta או Alpha בלבד! + נא לוודא שאתם כבר מחוברים לחשבון שלכם! + + 1. לפתוח אינסטגרם וללחוץ ארוך על כפתור עמוד הבית. + 2. לגשת להגדרת מפתחים > הגדרות MetaConfig & Overrides. + 3. לחפש \'Employee\' ולהפעיל את ההגדרות הבאות: + 4. לסוף, השבת את ההגדרות מפתחים מהמודול כדי למנות קריסות אפליקציה. + + + + + הגדרות מצב "ללא הסחות דעת" + השבתת Stories + השבתת תוכן עמוד הבית + השבתת ריילז + השבתת עמוד הExplore + השבתת עמוד התגובות + + + אחר + הגדרות נוספות + השבתת הלפת Stories + השבתת התחלת סרטונים אוטומתית + הראה עוקבים בהודעת סטטוס + הראה הודעת סטטוס עם כל התכונות המופעלות + + + שם התורם + + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 7854d8fb..d3bc9963 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -2,5 +2,21 @@ com.instagram.android + com.instagold.android + com.instaflux.app + com.myinsta.android + cc.honista.app + com.instaprime.android + com.instafel.android + com.instadm.android + com.dfistagram.android + com.Instander.android + com.aero.instagram + com.instapro.android + com.instaflow.android + com.instagram1.android + com.instagram2.android + com.instagramclone.android + com.instaclone.android \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d48125d9..4dc6eeb2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,7 +1,7 @@ InstaEclipse - v0.4.3 Beta + v0.4.4 Beta Home Features Help diff --git a/version.json b/version.json index 69e92c8f..b42a4fe3 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "latest_version": "0.4.3", + "latest_version": "0.4.4", "update_url": "https://github.com/ReSo7200/InstaEclipse/releases/latest" }