From 5876b7da100a1467db75c32888cf8b6bf9b07136 Mon Sep 17 00:00:00 2001 From: kmmiio99o <164557248+kmmiio99o@users.noreply.github.com> Date: Thu, 11 Dec 2025 16:30:57 +0100 Subject: [PATCH] Add options menu in LogBox module and bump to 1.2.3 Add options menu in xposed settings to disabling injecting bundle, change Xposed menu flavor, and choose theme from dark to light mode and system detection --- app/build.gradle.kts | 4 +- .../xposed/xposed/modules/LogBoxModule.kt | 262 +++++++++++++++++- 2 files changed, 261 insertions(+), 5 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a2cc3d0..ab5e7cc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -12,8 +12,8 @@ android { applicationId = "ShiggyXposed.xposed" minSdk = 24 targetSdk = 36 - versionCode = 1220 - versionName = "1.2.2" + versionCode = 1230 + versionName = "1.2.3" } buildTypes { diff --git a/app/src/main/kotlin/ShiggyXposed/xposed/xposed/modules/LogBoxModule.kt b/app/src/main/kotlin/ShiggyXposed/xposed/xposed/modules/LogBoxModule.kt index be94ff0..80023fb 100644 --- a/app/src/main/kotlin/ShiggyXposed/xposed/xposed/modules/LogBoxModule.kt +++ b/app/src/main/kotlin/ShiggyXposed/xposed/xposed/modules/LogBoxModule.kt @@ -230,6 +230,21 @@ object LogBoxModule : Module() { } private fun isDarkMode(context: Context): Boolean { + // Read LogBox-specific appearance override first (logbox/LOGBOX_SETTINGS). + // Supported values for appearanceMode: "system" | "light" | "dark" + try { + val settingsFile = File(context.filesDir, "logbox/LOGBOX_SETTINGS") + if (settingsFile.exists()) { + val json = JSONObject(settingsFile.readText()) + val mode = json.optString("appearanceMode", "") + if (mode == "light") return false + if (mode == "dark") return true + // if mode == "system" or empty, fall back to system below + } + } catch (e: Exception) { + Log.e("Error reading logbox appearance settings: ${e.message}") + } + return (context.resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK) == android.content.res.Configuration.UI_MODE_NIGHT_YES @@ -237,7 +252,9 @@ object LogBoxModule : Module() { private fun getM3Colors(context: Context): M3Colors { val isDark = isDarkMode(context) - return if (isDark) { + + // Base palettes + val base = if (isDark) { M3Colors( surface = Color.parseColor("#1C1B1F"), surfaceVariant = Color.parseColor("#49454F"), @@ -268,6 +285,45 @@ object LogBoxModule : Module() { onError = Color.parseColor("#FFFFFF") ) } + + // Apply LogBox menu flavor override if present (logbox/LOGBOX_THEMES) + try { + val themeFile = File(context.filesDir, "logbox/LOGBOX_THEMES") + if (themeFile.exists()) { + val json = JSONObject(themeFile.readText()) + // Support multiple keys that might be written by different UIs/tools: + val flavor = json.optString("menuFlavor", json.optString("flavor", json.optString("id", ""))).lowercase() + if (flavor.isNotEmpty()) { + val flavorMap = mapOf( + "blue" to Pair("#0D47A1", "#82B1FF"), + "green" to Pair("#1B5E20", "#A5D6A7"), + "mocha" to Pair("#3E2723", "#BCAAA4"), + "vanilla" to Pair("#F9A825", "#FFF59D"), + "purple" to Pair("#6A1B9A", "#E1BEE7"), + "amber" to Pair("#FF6F00", "#FFE0B2"), + "teal" to Pair("#004D40", "#80CBC4") + ) + val pair = flavorMap[flavor] + if (pair != null) { + val primary = Color.parseColor(pair.first) + val primaryContainer = Color.parseColor(pair.second) + // Keep contrast choices simple: white on primary for most flavors + val onPrimary = if (isDark) Color.WHITE else Color.WHITE + val onPrimaryContainer = if (isDark) Color.BLACK else Color.BLACK + return base.copy( + primary = primary, + primaryContainer = primaryContainer, + onPrimary = onPrimary, + onPrimaryContainer = onPrimaryContainer + ) + } + } + } + } catch (e: Exception) { + Log.e("Error applying logbox flavor: ${e.message}") + } + + return base } private data class M3Colors( @@ -393,6 +449,12 @@ object LogBoxModule : Module() { handleMenuSelection(context, 2) }) + // Options Button (opens submenu for theme, injection, flavor) + container.addView(createM3Button(context, "Options", colors) { + dialog.dismiss() + handleMenuSelection(context, 5) + }) + // Reload App Button container.addView(createM3Button(context, "Reload App", colors) { dialog.dismiss() @@ -435,6 +497,9 @@ object LogBoxModule : Module() { context, "Clear Cache & Reset", "This will clear all cached bundles and reset to default settings." ) { clearCacheAndReset(context) } + + // Options submenu + 5 -> showOptionsMenu(context) } } @@ -526,6 +591,197 @@ object LogBoxModule : Module() { dialog.show() } + // Options Submenu: Theme mode, disable bundle injection, flavor selection + private fun showOptionsMenu(context: Context) { + val colors = getM3Colors(context) + lateinit var dialog: AlertDialog + + val container = LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + setPadding( + dpToPx(context, 24), + dpToPx(context, 24), + dpToPx(context, 24), + dpToPx(context, 24) + ) + background = createM3Background(context, colors.surface, 28f) + } + + val titleView = TextView(context).apply { + text = "Options" + textSize = 20f + setTextColor(colors.onSurface) + typeface = android.graphics.Typeface.create("sans-serif", android.graphics.Typeface.BOLD) + setPadding(0, 0, 0, dpToPx(context, 12)) + } + container.addView(titleView) + + // Theme Mode selection (affects LogBox menu only) + container.addView(createM3Button(context, "Follow System Theme", colors) { + dialog.dismiss() + setAppearanceMode(context, "system") + }) + container.addView(createM3Button(context, "Force Light Mode", colors) { + dialog.dismiss() + setAppearanceMode(context, "light") + }) + container.addView(createM3Button(context, "Force Dark Mode", colors) { + dialog.dismiss() + setAppearanceMode(context, "dark") + }) + + // Disable/Enable bundle injection + val injectionDisabled = isBundleInjectionDisabled(context) + val injectionText = if (injectionDisabled) "Enable Bundle Injection" else "Disable Bundle Injection" + container.addView(createM3Button(context, injectionText, colors) { + dialog.dismiss() + toggleBundleInjection(context) + }) + + // Flavor selection + container.addView(createM3Button(context, "Menu Color Flavor", colors) { + dialog.dismiss() + showFlavorSelection(context) + }) + + // Back / Close button + container.addView(createM3Button(context, "Close", colors) { + dialog.dismiss() + }) + + dialog = AlertDialog.Builder(context) + .setView(container) + .create() + + dialog.window?.setBackgroundDrawable( + createM3Background(context, Color.TRANSPARENT, 28f) + ) + + dialog.show() + } + + private fun setAppearanceMode(context: Context, mode: String) { + try { + val settingsFile = File(context.filesDir, "logbox/LOGBOX_SETTINGS") + settingsFile.parentFile?.mkdirs() + val settings = if (settingsFile.exists()) JSONObject(settingsFile.readText()) else JSONObject() + settings.put("appearanceMode", mode) + settingsFile.writeText(settings.toString()) + showM3Toast(context, "Appearance set: ${mode.replaceFirstChar { it.uppercase() }}") + reloadApp() + } catch (e: Exception) { + Log.e("Error setting appearance mode: ${e.message}") + showError(context, "Failed to set appearance", e.message) + } + } + + private fun isBundleInjectionDisabled(context: Context): Boolean { + return try { + val settingsFile = File(context.filesDir, "logbox/LOGBOX_SETTINGS") + if (!settingsFile.exists()) return false + val json = JSONObject(settingsFile.readText()) + json.optBoolean("bundleInjectionDisabled", false) + } catch (e: Exception) { + Log.e("Error reading injection setting: ${e.message}") + false + } + } + + private fun toggleBundleInjection(context: Context) { + try { + val pyonDir = getPyoncordDirectory(context) + val bundleFile = File(pyonDir, "bundle.js") + val disabledFile = File(pyonDir, "bundle.js.disabled") + + val settingsFile = File(context.filesDir, "logbox/LOGBOX_SETTINGS") + settingsFile.parentFile?.mkdirs() + val settings = if (settingsFile.exists()) JSONObject(settingsFile.readText()) else JSONObject() + + val currentlyDisabled = settings.optBoolean("bundleInjectionDisabled", false) + + if (!currentlyDisabled) { + // disable: rename bundle if exists + if (bundleFile.exists()) { + bundleFile.renameTo(disabledFile) + } + settings.put("bundleInjectionDisabled", true) + settingsFile.writeText(settings.toString()) + showM3Toast(context, "Bundle injection disabled") + } else { + // enable: restore bundle if previously renamed + if (disabledFile.exists()) { + disabledFile.renameTo(bundleFile) + } + settings.put("bundleInjectionDisabled", false) + settingsFile.writeText(settings.toString()) + showM3Toast(context, "Bundle injection enabled") + } + + reloadApp() + } catch (e: Exception) { + Log.e("Error toggling bundle injection: ${e.message}") + showError(context, "Failed to toggle bundle injection", e.message) + } + } + + private fun showFlavorSelection(context: Context) { + val colors = getM3Colors(context) + val flavors = listOf("blue", "green", "mocha", "vanilla", "purple", "amber", "teal") + val container = LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + setPadding( + dpToPx(context, 24), + dpToPx(context, 24), + dpToPx(context, 24), + dpToPx(context, 24) + ) + background = createM3Background(context, colors.surface, 28f) + } + + val titleView = TextView(context).apply { + text = "Select Menu Flavor" + textSize = 18f + setTextColor(colors.onSurface) + typeface = android.graphics.Typeface.create("sans-serif-medium", android.graphics.Typeface.BOLD) + setPadding(0, 0, 0, dpToPx(context, 12)) + } + container.addView(titleView) + + flavors.forEach { flavor -> + container.addView(createM3Button(context, flavor.replaceFirstChar { it.uppercase() }, colors) { + saveMenuFlavor(context, flavor) + }) + } + + val dialog = AlertDialog.Builder(context) + .setView(container) + .create() + + dialog.window?.setBackgroundDrawable( + createM3Background(context, Color.TRANSPARENT, 28f) + ) + + dialog.show() + } + + private fun saveMenuFlavor(context: Context, flavor: String) { + try { + val themeFile = File(context.filesDir, "logbox/LOGBOX_THEMES") + themeFile.parentFile?.mkdirs() + + val themeJson = JSONObject().apply { + put("menuFlavor", flavor) + } + + themeFile.writeText(themeJson.toString()) + showM3Toast(context, "Menu flavor set to ${flavor.replaceFirstChar { it.uppercase() }}") + reloadApp() + } catch (e: Exception) { + Log.e("Error saving menu flavor: ${e.message}") + showError(context, "Failed to save flavor", e.message) + } + } + private fun isSafeModeEnabled(context: Context): Boolean { return try { val settingsFile = File(context.filesDir, "vd_mmkv/VENDETTA_SETTINGS") @@ -541,8 +797,8 @@ object LogBoxModule : Module() { private fun toggleSafeMode(context: Context) { try { - val settingsFile = File(context.filesDir, "vd_mmkv/VENDETTA_SETTINGS") - val themeFile = File(context.filesDir, "vd_mmkv/VENDETTA_THEMES") + val settingsFile = File(context.filesDir, "logbox/LOGBOX_SETTINGS") + val themeFile = File(context.filesDir, "logbox/LOGBOX_THEMES") settingsFile.parentFile?.mkdirs()