Modernize settings UI with Material Components, DayNight theme, and app icon

The screen was built entirely from raw android.widget.* views with no
theme applied at all, so it never had a chance to follow system dark/light
mode and looked dated regardless. Adds AppCompat + Material Components,
switches SettingsActivity to AppCompatActivity with a Theme.Material3.
DayNight theme, and rebuilds the screen with Material widgets (cards,
chips for the hidden-packages list, proper text fields).

Also adds a launcher icon (previously none): a vector adaptive icon, a
moon-like disc partially eclipsed by a diagonal veil in the background
color — no image tool involved, just XML.

Also renames the per-app "Remove" button to "Apply defaults (hide
everything)" — "Remove" read as removing the app from the list, not
resetting it to defaults, which is all it actually does.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Claude Sonnet 5
2026-08-06 21:46:16 +06:00
parent 22d7ec9d12
commit aea37e31d6
8 changed files with 160 additions and 99 deletions
+2
View File
@@ -33,4 +33,6 @@ android {
dependencies { dependencies {
compileOnly("de.robv.android.xposed:api:82") compileOnly("de.robv.android.xposed:api:82")
implementation("androidx.appcompat:appcompat:1.7.0")
implementation("com.google.android.material:material:1.12.0")
} }
+5 -1
View File
@@ -2,6 +2,9 @@
<application <application
android:label="@string/app_name" android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.Veil"
android:allowBackup="false" android:allowBackup="false"
android:hasCode="true"> android:hasCode="true">
@@ -18,7 +21,8 @@
<activity <activity
android:name=".SettingsActivity" android:name=".SettingsActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name"> android:label="@string/app_name"
android:theme="@style/Theme.Veil">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
@@ -1,6 +1,5 @@
package space.bdeshi.veil package space.bdeshi.veil
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.graphics.Typeface import android.graphics.Typeface
@@ -8,12 +7,17 @@ import android.os.Bundle
import android.text.InputType import android.text.InputType
import android.view.Gravity import android.view.Gravity
import android.view.View import android.view.View
import android.widget.Button
import android.widget.CheckBox
import android.widget.EditText
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.ScrollView import android.widget.ScrollView
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.google.android.material.checkbox.MaterialCheckBox
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import java.io.File import java.io.File
private val HELP_TIPS: List<Pair<String, String>> = listOf( private val HELP_TIPS: List<Pair<String, String>> = listOf(
@@ -35,19 +39,19 @@ private val HELP_TIPS: List<Pair<String, String>> = listOf(
"the target app. No log lines from Veil there usually means it never got injected " + "the target app. No log lines from Veil there usually means it never got injected " +
"(see the DenyList tip above), not a bug in this module.", "(see the DenyList tip above), not a bug in this module.",
"Forgot what an unconfigured app does?" to "Forgot what an unconfigured app does?" to
"Any app not in the list above gets every feature applied by default — check here " + "Any app not in the list below gets every feature applied by default — check here " +
"first if something's being hidden that you didn't expect.", "first if something's being hidden that you didn't expect.",
) )
class SettingsActivity : Activity() { class SettingsActivity : AppCompatActivity() {
private lateinit var prefs: SharedPreferences private lateinit var prefs: SharedPreferences
private lateinit var appListContainer: LinearLayout private lateinit var appListContainer: LinearLayout
private lateinit var hiddenPackagesContainer: LinearLayout private lateinit var hiddenPackagesChips: ChipGroup
private lateinit var helpContainer: LinearLayout private lateinit var helpContainer: View
private lateinit var helpToggle: Button private lateinit var helpToggle: MaterialButton
private lateinit var packageInput: EditText private lateinit var packageInput: TextInputEditText
private lateinit var hiddenPackageInput: EditText private lateinit var hiddenPackageInput: TextInputEditText
private var helpVisible = false private var helpVisible = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -62,11 +66,17 @@ class SettingsActivity : Activity() {
root.addView(TextView(this).apply { root.addView(TextView(this).apply {
text = "Veil" text = "Veil"
textSize = 20f textSize = 26f
setTypeface(typeface, Typeface.BOLD) setTypeface(typeface, Typeface.BOLD)
}) })
root.addView(TextView(this).apply {
text = "Hide device state from specific apps, per app."
textSize = 14f
alpha = 0.7f
setPadding(0, dp(2), 0, dp(16))
})
helpToggle = Button(this).apply { helpToggle = MaterialButton(this).apply {
text = "Help & troubleshooting ▾" text = "Help & troubleshooting ▾"
setOnClickListener { setOnClickListener {
helpVisible = !helpVisible helpVisible = !helpVisible
@@ -76,36 +86,34 @@ class SettingsActivity : Activity() {
} }
root.addView(helpToggle) root.addView(helpToggle)
helpContainer = LinearLayout(this).apply { val (helpCard, helpContent) = card()
orientation = LinearLayout.VERTICAL helpCard.visibility = View.GONE
setPadding(0, dp(8), 0, dp(8)) helpContainer = helpCard
visibility = View.GONE for ((title, body) in HELP_TIPS) {
for ((title, body) in HELP_TIPS) { helpContent.addView(TextView(this).apply {
addView(TextView(this@SettingsActivity).apply { text = title
text = title setTypeface(typeface, Typeface.BOLD)
setTypeface(typeface, Typeface.BOLD) setPadding(0, dp(8), 0, dp(2))
setPadding(0, dp(8), 0, dp(2)) })
}) helpContent.addView(TextView(this).apply {
addView(TextView(this@SettingsActivity).apply { text = body
text = body textSize = 13f
textSize = 13f alpha = 0.85f
}) })
}
} }
root.addView(helpContainer) root.addView(helpCard, marginTop(dp(8)))
root.addView(sectionTitle("Apps"))
root.addView(TextView(this).apply { root.addView(TextView(this).apply {
text = "Add the package name of an app you've scoped this module to in LSPosed, " + text = "Add the package name of an app you've scoped this module to in LSPosed, " +
"then choose which checks to hide from it. Apps you haven't added here get " + "or just scope it in LSPosed and open the target app once — Veil picks it up " +
"everything hidden by default (matches this module's original behavior)." "automatically. Apps not listed here get everything hidden by default."
setPadding(0, dp(8), 0, dp(16)) textSize = 13f
alpha = 0.8f
setPadding(0, dp(4), 0, dp(8))
}) })
packageInput = EditText(this).apply { packageInput = textInput("e.g. com.example.targetapp")
hint = "e.g. com.example.targetapp"
inputType = InputType.TYPE_CLASS_TEXT
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
}
root.addView(addRow(packageInput) { root.addView(addRow(packageInput) {
val pkg = packageInput.text.toString().trim() val pkg = packageInput.text.toString().trim()
if (pkg.isNotEmpty() && pkg.contains(".")) { if (pkg.isNotEmpty() && pkg.contains(".")) {
@@ -118,28 +126,20 @@ class SettingsActivity : Activity() {
appListContainer = LinearLayout(this).apply { appListContainer = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL orientation = LinearLayout.VERTICAL
setPadding(0, dp(16), 0, 0) setPadding(0, dp(8), 0, 0)
} }
root.addView(appListContainer) root.addView(appListContainer)
root.addView(sectionTitle("Packages hidden from PackageManager"), marginTop(dp(24)))
root.addView(TextView(this).apply { root.addView(TextView(this).apply {
text = "Packages hidden from PackageManager" text = "Used by the \"Hide root / manager apps\" checkbox above — apps here become " +
textSize = 18f "invisible to PackageManager lookups for any target app with that box checked."
setTypeface(typeface, Typeface.BOLD) textSize = 13f
setPadding(0, dp(24), 0, dp(4)) alpha = 0.8f
}) setPadding(0, dp(4), 0, dp(8))
root.addView(TextView(this).apply {
text = "Used by the \"Hide root / manager apps\" checkbox above — apps in this list " +
"become invisible to PackageManager lookups for any target app with that box " +
"checked."
setPadding(0, 0, 0, dp(8))
}) })
hiddenPackageInput = EditText(this).apply { hiddenPackageInput = textInput("e.g. me.weishu.kernelsu")
hint = "e.g. me.weishu.kernelsu"
inputType = InputType.TYPE_CLASS_TEXT
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
}
root.addView(addRow(hiddenPackageInput) { root.addView(addRow(hiddenPackageInput) {
val pkg = hiddenPackageInput.text.toString().trim() val pkg = hiddenPackageInput.text.toString().trim()
if (pkg.isNotEmpty() && pkg.contains(".")) { if (pkg.isNotEmpty() && pkg.contains(".")) {
@@ -152,29 +152,69 @@ class SettingsActivity : Activity() {
} }
}) })
hiddenPackagesContainer = LinearLayout(this).apply { hiddenPackagesChips = ChipGroup(this).apply {
orientation = LinearLayout.VERTICAL
setPadding(0, dp(8), 0, 0) setPadding(0, dp(8), 0, 0)
} }
root.addView(hiddenPackagesContainer) root.addView(hiddenPackagesChips)
setContentView(ScrollView(this).apply { addView(root) }) setContentView(ScrollView(this).apply { addView(root) })
refreshAppList() refreshAppList()
refreshHiddenPackages() refreshHiddenPackages()
} }
private fun addRow(input: EditText, onAdd: () -> Unit): View { private fun sectionTitle(text: String): TextView = TextView(this).apply {
this.text = text
textSize = 18f
setTypeface(typeface, Typeface.BOLD)
}
private fun marginTop(px: Int): LinearLayout.LayoutParams =
LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
topMargin = px
}
private fun textInput(hintText: String): TextInputEditText {
val editText = TextInputEditText(this).apply {
hint = hintText
inputType = InputType.TYPE_CLASS_TEXT
}
return editText
}
private fun addRow(input: TextInputEditText, onAdd: () -> Unit): View {
val layout = TextInputLayout(this).apply {
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
addView(input)
}
return LinearLayout(this).apply { return LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER_VERTICAL gravity = Gravity.CENTER_VERTICAL
addView(input) addView(layout)
addView(Button(this@SettingsActivity).apply { addView(MaterialButton(this@SettingsActivity).apply {
text = "Add" text = "Add"
setOnClickListener { onAdd() } setOnClickListener { onAdd() }
}) })
} }
} }
/** A MaterialCardView with an inner padded vertical container, added to root with bottom margin. */
private fun card(): Pair<MaterialCardView, LinearLayout> {
val content = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setPadding(dp(16), dp(16), dp(16), dp(16))
}
val cardView = MaterialCardView(this).apply {
radius = dp(12).toFloat()
cardElevation = dp(1).toFloat()
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT,
).apply { bottomMargin = dp(12) }
addView(content)
}
return cardView to content
}
private fun hiddenPackages(): Set<String> = private fun hiddenPackages(): Set<String> =
prefs.getStringSet(Features.HIDDEN_PACKAGES_KEY, Features.DEFAULT_HIDDEN_PACKAGES) ?: Features.DEFAULT_HIDDEN_PACKAGES prefs.getStringSet(Features.HIDDEN_PACKAGES_KEY, Features.DEFAULT_HIDDEN_PACKAGES) ?: Features.DEFAULT_HIDDEN_PACKAGES
@@ -193,6 +233,8 @@ class SettingsActivity : Activity() {
appListContainer.addView(TextView(this).apply { appListContainer.addView(TextView(this).apply {
text = "No apps yet — scope Veil to a target app in LSPosed Manager and open " + text = "No apps yet — scope Veil to a target app in LSPosed Manager and open " +
"it once, or add a package name above." "it once, or add a package name above."
textSize = 13f
alpha = 0.8f
}) })
return return
} }
@@ -204,11 +246,8 @@ class SettingsActivity : Activity() {
private fun buildPackageCard(pkg: String, isDiscoveredOnly: Boolean): View { private fun buildPackageCard(pkg: String, isDiscoveredOnly: Boolean): View {
val enabled = prefs.getStringSet(Features.keyFor(pkg), Features.ALL) ?: Features.ALL val enabled = prefs.getStringSet(Features.keyFor(pkg), Features.ALL) ?: Features.ALL
val (cardView, content) = card()
val card = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setPadding(0, dp(12), 0, dp(12))
}
val titleRow = LinearLayout(this).apply { val titleRow = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER_VERTICAL gravity = Gravity.CENTER_VERTICAL
@@ -217,22 +256,27 @@ class SettingsActivity : Activity() {
text = pkg text = pkg
textSize = 16f textSize = 16f
setTypeface(typeface, Typeface.BOLD) setTypeface(typeface, Typeface.BOLD)
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
}) })
if (isDiscoveredOnly) { if (isDiscoveredOnly) {
titleRow.addView(TextView(this).apply { titleRow.addView(Chip(this).apply {
text = " (auto-detected)" text = "Auto-detected"
textSize = 13f isClickable = false
isCheckable = false
}) })
} }
card.addView(titleRow) content.addView(titleRow)
card.addView(TextView(this).apply {
text = "Checking a box below hides that thing from this app specifically — it stays " + content.addView(TextView(this).apply {
"on for every other app." text = "Checking a box below hides that thing from this app specifically — it " +
setPadding(0, 0, 0, dp(8)) "stays on for every other app."
textSize = 12f
alpha = 0.7f
setPadding(0, dp(2), 0, dp(8))
}) })
for ((id, info) in Features.INFO) { for ((id, info) in Features.INFO) {
card.addView(CheckBox(this).apply { content.addView(MaterialCheckBox(this).apply {
text = info.label text = info.label
isChecked = id in enabled isChecked = id in enabled
setOnCheckedChangeListener { _, isChecked -> setOnCheckedChangeListener { _, isChecked ->
@@ -242,15 +286,16 @@ class SettingsActivity : Activity() {
fixPermissions() fixPermissions()
} }
}) })
card.addView(TextView(this).apply { content.addView(TextView(this).apply {
text = info.description text = info.description
textSize = 13f textSize = 13f
alpha = 0.7f
setPadding(dp(32), 0, 0, dp(8)) setPadding(dp(32), 0, 0, dp(8))
}) })
} }
card.addView(Button(this).apply { content.addView(MaterialButton(this).apply {
text = "Remove (revert to default: hide everything)" text = "Apply defaults (hide everything)"
setOnClickListener { setOnClickListener {
prefs.edit().remove(Features.keyFor(pkg)).apply() prefs.edit().remove(Features.keyFor(pkg)).apply()
fixPermissions() fixPermissions()
@@ -258,32 +303,16 @@ class SettingsActivity : Activity() {
} }
}) })
return card return cardView
} }
private fun refreshHiddenPackages() { private fun refreshHiddenPackages() {
hiddenPackagesContainer.removeAllViews() hiddenPackagesChips.removeAllViews()
val packages = hiddenPackages().sorted() for (pkg in hiddenPackages().sorted()) {
hiddenPackagesChips.addView(Chip(this).apply {
if (packages.isEmpty()) {
hiddenPackagesContainer.addView(TextView(this).apply {
text = "None configured."
})
return
}
for (pkg in packages) {
val row = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
}
row.addView(TextView(this).apply {
text = pkg text = pkg
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f) isCloseIconVisible = true
}) setOnCloseIconClickListener {
row.addView(Button(this).apply {
text = "Remove"
setOnClickListener {
val current = hiddenPackages().toMutableSet() val current = hiddenPackages().toMutableSet()
current.remove(pkg) current.remove(pkg)
prefs.edit().putStringSet(Features.HIDDEN_PACKAGES_KEY, current).apply() prefs.edit().putStringSet(Features.HIDDEN_PACKAGES_KEY, current).apply()
@@ -291,7 +320,6 @@ class SettingsActivity : Activity() {
refreshHiddenPackages() refreshHiddenPackages()
} }
}) })
hiddenPackagesContainer.addView(row)
} }
} }
@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<!-- A moon-like disc, partially eclipsed by a diagonal veil (cut in the background color) -->
<path
android:fillColor="#F2EDE4"
android:pathData="M28,54 A26,26 0 1,1 80,54 A26,26 0 1,1 28,54 Z" />
<path
android:fillColor="#1B1B3A"
android:pathData="M12,68 L36,92 L96,32 L72,8 Z" />
</vector>
@@ -0,0 +1,4 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
@@ -0,0 +1,4 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
+3
View File
@@ -0,0 +1,3 @@
<resources>
<color name="ic_launcher_background">#1B1B3A</color>
</resources>
+3
View File
@@ -0,0 +1,3 @@
<resources>
<style name="Theme.Veil" parent="Theme.Material3.DayNight.NoActionBar" />
</resources>