diff --git a/README.md b/README.md index a363fdb7..b2d369ca 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,7 @@ Force stop Instagram and **clear its cache** to apply the changes properly. - [BrianML](https://github.com/brianml31) - [silvzr](https://github.com/silvzr) - [oct888](https://github.com/oct888) +- [HalfManBear](https://github.com/halfmanbear) ## πŸ™Œ Special Thanks - [xHookman](https://github.com/xHookman) diff --git a/app/build.gradle b/app/build.gradle index 08d08a5a..57f91d72 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "ps.reso.instaeclipse" minSdkVersion 28 targetSdk 34 - versionCode 9 - versionName '0.4.2' + versionCode 10 + versionName '0.4.3' 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 d01efab5..0acf2a4a 100644 --- a/app/src/main/java/ps/reso/instaeclipse/Xposed/Module.java +++ b/app/src/main/java/ps/reso/instaeclipse/Xposed/Module.java @@ -227,10 +227,14 @@ protected void afterHookedMethod(MethodHookParam param) { } try { - FollowerIndicator followerIndicator = new FollowerIndicator(); // Follower Indicator - String bridge = followerIndicator.findFollowerStatusMethod(Module.dexKitBridge); - if (FeatureFlags.showFollowerToast) { - followerIndicator.checkFollow(hostClassLoader, bridge); + FollowerIndicator followerIndicator = new FollowerIndicator(); + 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"); 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 c2aa3dab..ebcc7acc 100644 --- a/app/src/main/java/ps/reso/instaeclipse/fragments/HomeFragment.java +++ b/app/src/main/java/ps/reso/instaeclipse/fragments/HomeFragment.java @@ -141,7 +141,9 @@ private void setupContributorsAndSpecialThanks(View rootView) { new Contributor("ReSo7200", "https://github.com/ReSo7200", "https://linkedin.com/in/abdalhaleem-altamimi", null), new Contributor("frknkrc44", "https://github.com/frknkrc44", null, null), new Contributor("BrianML", "https://github.com/brianml31", null, "https://t.me/instamoon_channel"), - new Contributor("silvzr", "https://github.com/silvzr", null, null) + 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) ); List specialThanks = Arrays.asList( diff --git a/app/src/main/java/ps/reso/instaeclipse/mods/ads/TrackingLinkDisable.java b/app/src/main/java/ps/reso/instaeclipse/mods/ads/TrackingLinkDisable.java index dc35ffb7..e843b6be 100644 --- a/app/src/main/java/ps/reso/instaeclipse/mods/ads/TrackingLinkDisable.java +++ b/app/src/main/java/ps/reso/instaeclipse/mods/ads/TrackingLinkDisable.java @@ -22,14 +22,19 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable { return; } String x = clipData.getItemAt(0).getText().toString(); - if (x.contains("https://www.instagram.com/") && x.contains("?igsh=")) { // Global - param.args[0] = ClipData.newPlainText("URL", x.replaceAll("\\?igsh=.*", "")); + if (x.contains("https://www.instagram.com/") && (x.contains("igsh=") || (x.contains("ig_rid=")))) { // Global + param.args[0] = ClipData.newPlainText("URL", x.replaceAll("\\?.*", "")); } else if (x.contains("https://www.instagram.com/") && x.contains("?utm_source=")) { // Stories param.args[0] = ClipData.newPlainText("URL", x.replaceAll("\\?utm_source=.*", "")); } else if (x.contains("https://www.instagram.com/") && x.contains("?story_media_id=")){ // Highlights param.args[0] = ClipData.newPlainText("URL", x.replaceAll("\\?story_media_id=.*", "")); } + // Saved-by rule: match saved-by or saved_by anywhere in query + else if (x.contains("https://www.instagram.com/") && + x.matches("(?i).*saved[-_]by.*")) { + param.args[0] = ClipData.newPlainText("URL", x.replaceAll("\\?.*", "")); + } } } diff --git a/app/src/main/java/ps/reso/instaeclipse/mods/devops/config/ConfigManager.java b/app/src/main/java/ps/reso/instaeclipse/mods/devops/config/ConfigManager.java index aed4f0c1..0242ff63 100644 --- a/app/src/main/java/ps/reso/instaeclipse/mods/devops/config/ConfigManager.java +++ b/app/src/main/java/ps/reso/instaeclipse/mods/devops/config/ConfigManager.java @@ -1,5 +1,7 @@ package ps.reso.instaeclipse.mods.devops.config; +import static ps.reso.instaeclipse.utils.feature.FeatureFlags.isImportingConfig; + import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; @@ -21,57 +23,60 @@ public class ConfigManager { // Import meta config from clipboard public static void importConfigFromClipboard(Context context) { - android.app.ProgressDialog progress = new android.app.ProgressDialog(context); - progress.setMessage("Importing config..."); - progress.setCancelable(false); - progress.show(); - - new Thread(() -> { - try { - ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); - if (clipboard == null || !clipboard.hasPrimaryClip()) { - return; - } - - ClipData clipData = clipboard.getPrimaryClip(); - if (clipData == null || clipData.getItemCount() == 0) { - return; - } - - CharSequence clipText = clipData.getItemAt(0).getText(); - if (clipText == null || clipText.length() == 0) { - return; - } - - String json = clipText.toString().trim(); - if (!json.startsWith("{") || !json.endsWith("}")) { - return; - } - - File dest = new File(context.getFilesDir(), "mobileconfig/mc_overrides.json"); - if (!Objects.requireNonNull(dest.getParentFile()).exists()) { - dest.getParentFile().mkdirs(); - } - - try (FileOutputStream fos = new FileOutputStream(dest, false)) { - fos.write(json.getBytes(StandardCharsets.UTF_8)); - fos.flush(); + if (isImportingConfig) { + android.app.ProgressDialog progress = new android.app.ProgressDialog(context); + progress.setMessage("Importing config..."); + progress.setCancelable(false); + progress.show(); + + new Thread(() -> { + try { + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + if (clipboard == null || !clipboard.hasPrimaryClip()) { + return; + } + + ClipData clipData = clipboard.getPrimaryClip(); + if (clipData == null || clipData.getItemCount() == 0) { + return; + } + + CharSequence clipText = clipData.getItemAt(0).getText(); + if (clipText == null || clipText.length() == 0) { + return; + } + + String json = clipText.toString().trim(); + if (!json.startsWith("{") || !json.endsWith("}")) { + return; + } + + File dest = new File(context.getFilesDir(), "mobileconfig/mc_overrides.json"); + if (!Objects.requireNonNull(dest.getParentFile()).exists()) { + dest.getParentFile().mkdirs(); + } + + try (FileOutputStream fos = new FileOutputStream(dest, false)) { + fos.write(json.getBytes(StandardCharsets.UTF_8)); + fos.flush(); + } + + new Handler(Looper.getMainLooper()).post(() -> { + progress.dismiss(); + Toast.makeText(context, "βœ… Imported into mc_overrides.json", Toast.LENGTH_LONG).show(); + XposedBridge.log("InstaEclipse | βœ… JSON imported from clipboard into mc_overrides.json"); + }); + + } catch (Exception e) { + XposedBridge.log("InstaEclipse | ❌ Clipboard import failed: " + e.getMessage()); + new Handler(Looper.getMainLooper()).post(() -> { + progress.dismiss(); + Toast.makeText(context, "❌ Failed to import config", Toast.LENGTH_LONG).show(); + }); } + }).start(); + } - new Handler(Looper.getMainLooper()).post(() -> { - progress.dismiss(); - Toast.makeText(context, "βœ… Imported into mc_overrides.json", Toast.LENGTH_LONG).show(); - XposedBridge.log("InstaEclipse | βœ… JSON imported from clipboard into mc_overrides.json"); - }); - - } catch (Exception e) { - XposedBridge.log("InstaEclipse | ❌ Clipboard import failed: " + e.getMessage()); - new Handler(Looper.getMainLooper()).post(() -> { - progress.dismiss(); - Toast.makeText(context, "❌ Failed to import config", Toast.LENGTH_LONG).show(); - }); - } - }).start(); } // Export meta config to Device diff --git a/app/src/main/java/ps/reso/instaeclipse/mods/ghost/ViewOnce.java b/app/src/main/java/ps/reso/instaeclipse/mods/ghost/ViewOnce.java index cb2596cf..bd0e4fd8 100644 --- a/app/src/main/java/ps/reso/instaeclipse/mods/ghost/ViewOnce.java +++ b/app/src/main/java/ps/reso/instaeclipse/mods/ghost/ViewOnce.java @@ -51,36 +51,40 @@ public void handleViewOnceBlock(DexKitBridge bridge) { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { if (!FeatureFlags.isGhostViewOnce) { - // If Ghost View Once is disabled, allow normal execution + return; // Feature disabled β†’ skip + } + + Object rw = param.args[2]; // Third argument (visual item object) + if (rw == null) { return; } - Object rw = param.args[2]; // Third argument (likely visual item object) - - if (rw != null) { - Method[] allMethods = rw.getClass().getDeclaredMethods(); - - for (Method m : allMethods) { - if (m.getParameterTypes().length == 0 && - m.getReturnType() == String.class) { - try { - m.setAccessible(true); - String value = (String) m.invoke(rw); - - if (value != null && value.contains("send_visual_item_seen_marker")) { - // If it matches visual seen marker, block it - param.setResult(null); - return; - } - } catch (Throwable ignored) { - // Ignore reflection exceptions - } + for (Method m : rw.getClass().getDeclaredMethods()) { + // Only check methods with no params returning String + if (m.getParameterTypes().length != 0 || m.getReturnType() != String.class) { + continue; + } + + try { + m.setAccessible(true); + String value = (String) m.invoke(rw); + if (value == null) { + continue; } + + if (value.contains("visual_item_seen") || + value.contains("send_visual_item_seen_marker")) { + // XposedBridge.log("Blocked ViewOnce send: " + value); + param.setResult(null); // Block this call + } + } catch (Throwable ignored) { + // Ignore reflection errors } } } }); + XposedBridge.log("(InstaEclipse | ViewOnce): βœ… Hooked (dynamic check): " + method.getClassName() + "." + method.getName()); FeatureStatusTracker.setHooked("GhostViewOnce"); 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 6a8bed9e..c1285006 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 @@ -18,60 +18,94 @@ import ps.reso.instaeclipse.utils.toast.CustomToast; public class FollowerIndicator { - public String findFollowerStatusMethod(DexKitBridge bridge) { - try { - List methods = bridge.findMethod(FindMethod.create().matcher(MethodMatcher.create().usingStrings("", "", "") // Look for 3+ empty strings - .paramCount(2))); - for (MethodData method : methods) { - List paramTypes = new ArrayList<>(); - for (Object param : method.getParamTypes()) { - paramTypes.add(String.valueOf(param)); - } + public static class FollowMethodResult { + public final String methodName; + public final String userClassName; - // Match (UserSession, User) - if (paramTypes.size() == 2 && paramTypes.get(0).contains("com.instagram.common.session.UserSession") && paramTypes.get(1).contains("com.instagram.user.model.User")) { + public FollowMethodResult(String methodName, String userClassName) { + this.methodName = methodName; + this.userClassName = userClassName; + } + } + 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); + } - XposedBridge.log("πŸ” Inspecting method: " + method.getClassName() + "." + method.getName()); + 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 methodName = invoked.getName(); String returnType = String.valueOf(invoked.getReturnType()); - for (Object param : invoked.getParamTypes()) { - paramTypes.add(String.valueOf(param)); + if (className.contains(obfUserClass) && returnType.contains("boolean")) { + XposedBridge.log("βœ… Found follower status method (new): " + invoked.getName()); + return new FollowMethodResult(invoked.getName(), obfUserClass); } + } + } + } - // βœ… Just find first method in User class with returnType boolean & no params - if (className.contains("com.instagram.user.model.User") && (returnType.contains("boolean"))) { + // πŸ”„ Step 2: Fallback to old detection + List methodsOld = bridge.findMethod( + FindMethod.create().matcher( + MethodMatcher.create().usingStrings("", "", "").paramCount(2) + ) + ); - // XposedBridge.log("🎯 Matched Method: " + className + "." + methodName); - return methodName; + 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"); } - FeatureStatusTracker.setHooked("ShowFollowerToast"); } - // inspectInvokedMethods(bridge, method); // your helper } } } catch (Throwable e) { - XposedBridge.log("❌ Error in method discovery: " + e.getMessage()); + XposedBridge.log("❌ Error in findFollowerStatusMethod: " + e.getMessage()); } return null; } - public void checkFollow(ClassLoader classLoader, String followerStatusMethod) { + public void checkFollow(ClassLoader classLoader, String followerStatusMethod, String userClassName) { try { - - if (followerStatusMethod == null) { - XposedBridge.log("❌ method name not found. Skipping hook."); + if (followerStatusMethod == null || userClassName == null) { + XposedBridge.log("❌ method or class name not found. Skipping hook."); return; } - XposedHelpers.findAndHookMethod("com.instagram.user.model.User", classLoader, followerStatusMethod, new XC_MethodHook() { + XposedHelpers.findAndHookMethod(userClassName, classLoader, followerStatusMethod, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Object user = param.thisObject; @@ -81,31 +115,33 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable { try { username = (String) XposedHelpers.callMethod(user, "getUsername"); } catch (Throwable ignored) { - // Obfuscated or missing method β€” skip username - } Boolean followsMe = (Boolean) param.getResult(); + // skip username for now in obfuscated versions + } + 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 ❌"); + message = "@" + username + " (" + userId + ") " + + (followsMe ? "follows you βœ…" : "doesn’t follow you ❌"); } else { - message = " (" + userId + ") " + (followsMe ? "follows you βœ…" : "doesn’t follow you ❌"); + message = " (" + userId + ") " + + (followsMe ? "follows you βœ…" : "doesn’t follow you ❌"); } CustomToast.showCustomToast(context, message); - ps.reso.instaeclipse.utils.tracker.FollowIndicatorTracker.currentlyViewedUserId = null; - - } } }); + XposedBridge.log("βœ… Hooked follower status method in: " + userClassName + "." + followerStatusMethod); + FeatureStatusTracker.setHooked("ShowFollowerToast"); + } catch (Exception e) { - XposedBridge.log("❌ Error hooking User class: " + e.getMessage()); + XposedBridge.log("❌ Error hooking follower status: " + e.getMessage()); } } -} \ No newline at end of file +} diff --git a/app/src/main/java/ps/reso/instaeclipse/mods/misc/StoryFlipping.java b/app/src/main/java/ps/reso/instaeclipse/mods/misc/StoryFlipping.java index 6acc75f4..83e69e29 100644 --- a/app/src/main/java/ps/reso/instaeclipse/mods/misc/StoryFlipping.java +++ b/app/src/main/java/ps/reso/instaeclipse/mods/misc/StoryFlipping.java @@ -25,11 +25,14 @@ public void handleStoryFlippingDisable(DexKitBridge bridge) { private void findAndHookMethod(DexKitBridge bridge) { try { - // Step 1: Find methods referencing the string "end_scene" - List methods = bridge.findMethod(FindMethod.create() - .matcher(MethodMatcher.create() - .declaredClass("instagram.features.stories.fragment.ReelViewerFragment") - .usingStrings("end_scene") + // Step 1: Find methods matching the targeted method structure + List methods = bridge.findMethod( + FindMethod.create().matcher( + MethodMatcher.create() + .declaredClass("instagram.features.stories.fragment.ReelViewerFragment") + .paramTypes("java.lang.Object") + .returnType("void") + .usingStrings("userSession") ) ); 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 0ada52f0..453f4f09 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 @@ -68,6 +68,7 @@ protected void beforeHookedMethod(MethodHookParam param) { } if (FeatureFlags.isGhostViewOnce) { shouldDrop |= uri.getPath().endsWith("/item_replayed/"); + shouldDrop |= (uri.getPath().contains("/direct") && uri.getPath().endsWith("/item_seen/")); } if (FeatureFlags.isGhostStory) { shouldDrop |= uri.getPath().contains("/api/v2/media/seen/"); @@ -88,13 +89,23 @@ protected void beforeHookedMethod(MethodHookParam param) { if (FeatureFlags.disableFeed) { shouldDrop |= uri.getPath().endsWith("/feed/timeline/"); } - if (FeatureFlags.disableReels) { + if (FeatureFlags.disableReels && !FeatureFlags.disableReelsExceptDM) { shouldDrop |= uri.getPath().endsWith("/qp/batch_fetch/") || uri.getPath().contains("api/v1/clips") || uri.getPath().contains("clips") || uri.getPath().contains("mixed_media") || uri.getPath().contains("mixed_media/discover/stream/"); } + if (FeatureFlags.disableReelsExceptDM) { + if (uri.getPath().startsWith("/api/v1/direct_v2/")) { + return; + } + shouldDrop |= (uri.getPath().startsWith("/api/v1/clips/") && uri.getQuery() != null + && (uri.getQuery().contains("next_media_ids=") + || uri.getQuery().contains("max_id="))) + || uri.getPath().contains("/clips/discover/") + || uri.getPath().contains("/mixed_media/discover/stream/"); + } if (FeatureFlags.disableExplore) { shouldDrop |= uri.getPath().contains("/discover/topical_explore") || uri.getPath().contains("/discover/topical_explore_stream") @@ -130,6 +141,9 @@ protected void beforeHookedMethod(MethodHookParam param) { // XposedBridge.log("❌ [InstaEclipse] Failed to modify URI: " + e.getMessage()); } } + 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/utils/core/SettingsManager.java b/app/src/main/java/ps/reso/instaeclipse/utils/core/SettingsManager.java index e81386da..97467576 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 @@ -43,6 +43,7 @@ public static void saveAllFlags() { editor.putBoolean("disableStories", FeatureFlags.disableStories); editor.putBoolean("disableFeed", FeatureFlags.disableFeed); editor.putBoolean("disableReels", FeatureFlags.disableReels); + editor.putBoolean("disableReelsExceptDM", FeatureFlags.disableReelsExceptDM); editor.putBoolean("disableExplore", FeatureFlags.disableExplore); editor.putBoolean("disableComments", FeatureFlags.disableComments); @@ -92,6 +93,7 @@ public static void loadAllFlags(Context context) { FeatureFlags.disableStories = prefs.getBoolean("disableStories", false); FeatureFlags.disableFeed = prefs.getBoolean("disableFeed", false); FeatureFlags.disableReels = prefs.getBoolean("disableReels", false); + FeatureFlags.disableReelsExceptDM = prefs.getBoolean("disableReelsExceptDM", false); FeatureFlags.disableExplore = prefs.getBoolean("disableExplore", false); FeatureFlags.disableComments = prefs.getBoolean("disableComments", 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 dbe6b380..9fc4739f 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 @@ -22,6 +22,7 @@ import android.widget.TextView; import android.widget.Toast; +import java.io.File; import java.util.Objects; import de.robv.android.xposed.XposedBridge; @@ -255,9 +256,10 @@ private static View createDivider(Context context) { return divider; } - private static void restartInstagram(Context context) { + private static void restartInstagram(Context context) { // Restart Instagram and Remove its cache try { Intent intent = context.getPackageManager().getLaunchIntentForPackage("com.instagram.android"); + clearInstagramCache(context); if (intent != null) { intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); @@ -271,6 +273,33 @@ private static void restartInstagram(Context context) { } } + private static void clearInstagramCache(Context context) { // Clear Instagram Cache + try { + File cacheDir = context.getCacheDir(); + if (cacheDir != null && cacheDir.exists()) { + XposedBridge.log(""); + deleteRecursive(cacheDir); + XposedBridge.log("InstaEclipse: Cache cleared"); + } else { + XposedBridge.log("InstaEclipse: Cache dir not found"); + } + } catch (Exception e) { + XposedBridge.log("InstaEclipse: Failed to clear cache - " + e.getMessage()); + } + } + + private static void deleteRecursive(File fileOrDirectory) { // Helper method + if (fileOrDirectory.isDirectory()) { + File[] children = fileOrDirectory.listFiles(); + if (children != null) { + for (File child : children) { + deleteRecursive(child); + } + } + } + fileOrDirectory.delete(); + } + // ==== SECTIONS ==== @SuppressLint("SetTextI18n") @@ -500,41 +529,72 @@ private static void showAdOptions(Context context) { private static void showDistractionOptions(Context context) { LinearLayout layout = createSwitchLayout(context); - // Create all child switches + // 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[]{ - createSwitch(context, "Disable Stories", FeatureFlags.disableStories), - createSwitch(context, "Disable Feed", FeatureFlags.disableFeed), - createSwitch(context, "Disable Reels", FeatureFlags.disableReels), - createSwitch(context, "Disable Explore", FeatureFlags.disableExplore), - createSwitch(context, "Disable Comments", FeatureFlags.disableComments) + disableStoriesSwitch, + disableFeedSwitch, + disableReelsSwitch, + onlyInDMSwitch, + disableExploreSwitch, + disableCommentsSwitch }; - // Create Enable/Disable All switch + // Enable/Disable All @SuppressLint("UseSwitchCompatOrMaterialCode") Switch enableAllSwitch = createSwitch(context, "Enable/Disable All", areAllEnabled(switches)); - // Master listener + // Master switch listener enableAllSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { for (Switch s : switches) { s.setChecked(isChecked); + s.setEnabled(true); + } + if (!isChecked) { + onlyInDMSwitch.setChecked(false); + onlyInDMSwitch.setEnabled(false); } }); - // Individual child listeners - for (Switch s : switches) { + // Parent-child logic for Reels + disableReelsSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { + onlyInDMSwitch.setEnabled(isChecked); + if (!isChecked) { + onlyInDMSwitch.setChecked(false); // turn off child immediately + onlyInDMSwitch.setEnabled(false); + } + updateMasterSwitch(enableAllSwitch, switches, disableReelsSwitch, onlyInDMSwitch); + SettingsManager.saveAllFlags(); + }); + + // Child logic for "Except in DMs" + onlyInDMSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (isChecked && !disableReelsSwitch.isChecked()) { + // Auto-enable parent if user enables child + disableReelsSwitch.setChecked(true); + } + updateMasterSwitch(enableAllSwitch, switches, disableReelsSwitch, onlyInDMSwitch); + SettingsManager.saveAllFlags(); + }); + + // All other switches + for (Switch s : new Switch[]{disableStoriesSwitch, disableFeedSwitch, disableExploreSwitch, disableCommentsSwitch}) { s.setOnCheckedChangeListener((buttonView, isChecked) -> { - enableAllSwitch.setOnCheckedChangeListener(null); - enableAllSwitch.setChecked(areAllEnabled(switches)); - enableAllSwitch.setOnCheckedChangeListener((buttonView2, isChecked2) -> { - for (Switch s2 : switches) { - s2.setChecked(isChecked2); - } - }); + updateMasterSwitch(enableAllSwitch, switches, disableReelsSwitch, onlyInDMSwitch); SettingsManager.saveAllFlags(); }); } - // Add to layout + // Init "Except in DMs" state + onlyInDMSwitch.setEnabled(disableReelsSwitch.isChecked()); + + // Layout building layout.addView(createDivider(context)); layout.addView(createEnableAllSwitch(context, enableAllSwitch)); layout.addView(createDivider(context)); @@ -543,19 +603,33 @@ private static void showDistractionOptions(Context context) { layout.addView(s); } - // Show the dialog showSectionDialog(context, "Distraction-Free Instagram 🧘", layout, () -> { - FeatureFlags.disableStories = switches[0].isChecked(); - FeatureFlags.disableFeed = switches[1].isChecked(); - FeatureFlags.disableReels = switches[2].isChecked(); - FeatureFlags.disableExplore = switches[3].isChecked(); - FeatureFlags.disableComments = switches[4].isChecked(); + 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(); } + 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) -> { + for (Switch s : switches) { + s.setChecked(isChecked); + } + onlyInDMSwitch.setEnabled(disableReelsSwitch.isChecked()); + }); + } + + + private static void showMiscOptions(Context context) { LinearLayout layout = createSwitchLayout(context); @@ -681,7 +755,7 @@ private static void showRestartSection(Context context) { layout.setGravity(Gravity.CENTER_HORIZONTAL); TextView message = new TextView(context); - message.setText("Restart Instagram to apply changes?"); + message.setText("⚠️ Restart Instagram and remove its cache?!"); message.setTextColor(Color.WHITE); message.setTextSize(18f); message.setGravity(Gravity.CENTER); @@ -792,11 +866,13 @@ 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_checked}, // Checked - new int[]{-android.R.attr.state_checked} // Unchecked + 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("#448AFF"), // ON + Color.parseColor("#555555"), // Disabled + Color.parseColor("#448AFF"), // ON Color.parseColor("#FFFFFF") // OFF } ); @@ -805,12 +881,14 @@ private static ColorStateList createThumbColor() { private static ColorStateList createTrackColor() { return new ColorStateList( new int[][]{ - new int[]{android.R.attr.state_checked}, - new int[]{-android.R.attr.state_checked} + 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("#1C4C78"), // ON - Color.parseColor("#CFD8DC") // OFF + Color.parseColor("#777777"), // Disabled + Color.parseColor("#1C4C78"), // ON + Color.parseColor("#CFD8DC") // OFF } ); } 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 df81b6e2..1345ac4a 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 @@ -30,6 +30,7 @@ public class FeatureFlags { public static boolean disableStories = false; public static boolean disableFeed = false; public static boolean disableReels = false; + public static boolean disableReelsExceptDM = false; public static boolean disableExplore = false; public static boolean disableComments = false; 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 8e83ec28..24736102 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.2"; // Current version + private static final String CURRENT_VERSION = "0.4.3"; // 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/strings.xml b/app/src/main/res/values/strings.xml index a5682709..d48125d9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,7 +1,7 @@ InstaEclipse - v0.4.2 Beta + v0.4.3 Beta Home Features Help diff --git a/version.json b/version.json index 1f99d2aa..69e92c8f 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "latest_version": "0.4.2", + "latest_version": "0.4.3", "update_url": "https://github.com/ReSo7200/InstaEclipse/releases/latest" }