mirror of
https://github.com/xtclovver/RKNHardering.git
synced 2026-07-09 17:19:25 +00:00
feat: Выбор между классической и новой иконкой приложения
This commit is contained in:
parent
e16a78fd5e
commit
e7d76f9b4b
20 changed files with 339 additions and 9 deletions
|
|
@ -144,6 +144,21 @@
|
|||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".LauncherClassic"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:enabled="false"
|
||||
android:exported="true"
|
||||
android:icon="@mipmap/ic_launcher_classic"
|
||||
android:roundIcon="@mipmap/ic_launcher_classic_round"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:exported="false" />
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ internal enum class LauncherIconVariant(
|
|||
val prefValue: String,
|
||||
) {
|
||||
ORIGINAL("com.notcvnt.rknhardering.LauncherOriginal", "original"),
|
||||
CLASSIC("com.notcvnt.rknhardering.LauncherClassic", "classic"),
|
||||
PROTANOPIA("com.notcvnt.rknhardering.LauncherProtanopia", "protanopia"),
|
||||
DEUTERANOPIA("com.notcvnt.rknhardering.LauncherDeuteranopia", "deuteranopia"),
|
||||
TRITANOPIA("com.notcvnt.rknhardering.LauncherTritanopia", "tritanopia"),
|
||||
|
|
@ -23,5 +24,14 @@ internal enum class LauncherIconVariant(
|
|||
ColorVisionMode.ACHROMATOPSIA -> MONOCHROME
|
||||
}
|
||||
}
|
||||
|
||||
fun resolve(
|
||||
iconStyleClassic: Boolean,
|
||||
mode: ColorVisionMode,
|
||||
redGreenSub: LauncherIconVariant?,
|
||||
): LauncherIconVariant {
|
||||
if (iconStyleClassic) return CLASSIC
|
||||
return fromCvd(mode, redGreenSub)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,7 +146,8 @@ internal class SettingsAccessibilityFragment : Fragment(R.layout.fragment_settin
|
|||
}
|
||||
|
||||
private fun applyLauncherIcon(root: View, mode: ColorVisionMode) {
|
||||
val target = LauncherIconVariant.fromCvd(mode, redGreenSubVariantFromPrefs())
|
||||
val iconStyleClassic = prefs.getString(SettingsPrefs.PREF_ICON_STYLE, "new") == "classic"
|
||||
val target = LauncherIconVariant.resolve(iconStyleClassic, mode, redGreenSubVariantFromPrefs())
|
||||
val message = if (LauncherIconManager.apply(requireContext(), target)) {
|
||||
R.string.settings_color_vision_icon_changed_warning
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -6,20 +6,23 @@ import android.view.View
|
|||
import androidx.core.content.edit
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
|
||||
internal class SettingsAppearanceFragment : Fragment(R.layout.fragment_settings_appearance) {
|
||||
|
||||
private lateinit var prefs: SharedPreferences
|
||||
private lateinit var chipGroupTheme: ChipGroup
|
||||
private lateinit var chipGroupIconStyle: ChipGroup
|
||||
private lateinit var chipGroupLanguage: ChipGroup
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
prefs = AppUiSettings.prefs(requireContext())
|
||||
chipGroupTheme = view.findViewById(R.id.chipGroupTheme)
|
||||
chipGroupIconStyle = view.findViewById(R.id.chipGroupIconStyle)
|
||||
chipGroupLanguage = view.findViewById(R.id.chipGroupLanguage)
|
||||
loadSettings()
|
||||
setupListeners()
|
||||
setupListeners(view)
|
||||
}
|
||||
|
||||
private fun loadSettings() {
|
||||
|
|
@ -32,6 +35,11 @@ internal class SettingsAppearanceFragment : Fragment(R.layout.fragment_settings_
|
|||
},
|
||||
)
|
||||
|
||||
val iconStyle = prefs.getString(SettingsPrefs.PREF_ICON_STYLE, "new") ?: "new"
|
||||
chipGroupIconStyle.check(
|
||||
if (iconStyle == "classic") R.id.chipIconStyleClassic else R.id.chipIconStyleNew,
|
||||
)
|
||||
|
||||
val language = prefs.getString(SettingsPrefs.PREF_LANGUAGE, "").orEmpty()
|
||||
chipGroupLanguage.check(
|
||||
when (language) {
|
||||
|
|
@ -44,7 +52,7 @@ internal class SettingsAppearanceFragment : Fragment(R.layout.fragment_settings_
|
|||
)
|
||||
}
|
||||
|
||||
private fun setupListeners() {
|
||||
private fun setupListeners(view: View) {
|
||||
chipGroupTheme.setOnCheckedStateChangeListener { _, checkedIds ->
|
||||
if (checkedIds.isEmpty()) return@setOnCheckedStateChangeListener
|
||||
val value = when (checkedIds.first()) {
|
||||
|
|
@ -56,6 +64,14 @@ internal class SettingsAppearanceFragment : Fragment(R.layout.fragment_settings_
|
|||
SettingsActivity.applyTheme(value)
|
||||
}
|
||||
|
||||
chipGroupIconStyle.setOnCheckedStateChangeListener { _, checkedIds ->
|
||||
if (checkedIds.isEmpty()) return@setOnCheckedStateChangeListener
|
||||
val isClassic = checkedIds.first() == R.id.chipIconStyleClassic
|
||||
val value = if (isClassic) "classic" else "new"
|
||||
prefs.edit { putString(SettingsPrefs.PREF_ICON_STYLE, value) }
|
||||
applyIconStyle(view, isClassic)
|
||||
}
|
||||
|
||||
chipGroupLanguage.setOnCheckedStateChangeListener { _, checkedIds ->
|
||||
if (checkedIds.isEmpty()) return@setOnCheckedStateChangeListener
|
||||
val value = when (checkedIds.first()) {
|
||||
|
|
@ -69,4 +85,26 @@ internal class SettingsAppearanceFragment : Fragment(R.layout.fragment_settings_
|
|||
AppUiSettings.applyLanguage(value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyIconStyle(view: View, isClassic: Boolean) {
|
||||
val rawMode = prefs.getString(SettingsPrefs.PREF_COLOR_VISION_MODE, null)
|
||||
val mode = ColorVisionMode.fromPref(rawMode)
|
||||
val redGreenSub = redGreenSubVariantFromPrefs()
|
||||
val target = LauncherIconVariant.resolve(isClassic, mode, redGreenSub)
|
||||
val message = if (LauncherIconManager.apply(requireContext(), target)) {
|
||||
R.string.settings_color_vision_icon_changed_warning
|
||||
} else {
|
||||
R.string.settings_color_vision_icon_change_failed
|
||||
}
|
||||
Snackbar.make(view, message, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
private fun redGreenSubVariantFromPrefs(): LauncherIconVariant? {
|
||||
val unlocked = prefs.getBoolean(SettingsPrefs.PREF_EASTER_EGG_PROTANOPIA_UNLOCKED, false)
|
||||
if (!unlocked) return null
|
||||
return when (prefs.getString(SettingsPrefs.PREF_RED_GREEN_ICON_VARIANT, LauncherIconVariant.DEUTERANOPIA.prefValue)) {
|
||||
LauncherIconVariant.PROTANOPIA.prefValue -> LauncherIconVariant.PROTANOPIA
|
||||
else -> LauncherIconVariant.DEUTERANOPIA
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,4 +28,5 @@ internal object SettingsPrefs {
|
|||
const val PREF_EASTER_EGG_PROTANOPIA_UNLOCKED = "pref_easter_egg_protanopia_unlocked"
|
||||
const val PREF_RED_GREEN_ICON_VARIANT = "pref_red_green_icon_variant"
|
||||
const val PREF_ICON_MIGRATION_DONE = "pref_icon_migration_done"
|
||||
const val PREF_ICON_STYLE = "pref_icon_style"
|
||||
}
|
||||
|
|
|
|||
10
app/src/main/res/drawable/ic_launcher_classic_background.xml
Normal file
10
app/src/main/res/drawable/ic_launcher_classic_background.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#1A1A26"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
</vector>
|
||||
66
app/src/main/res/drawable/ic_launcher_classic_foreground.xml
Normal file
66
app/src/main/res/drawable/ic_launcher_classic_foreground.xml
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
|
||||
<path
|
||||
android:fillColor="#20203A"
|
||||
android:pathData="M54,54m-28,0a28,28 0,1 1,56 0a28,28 0,1 1,-56 0" />
|
||||
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M54,54m-28,0a28,28 0,1 1,56 0a28,28 0,1 1,-56 0"
|
||||
android:strokeColor="#C8C5D0"
|
||||
android:strokeWidth="3" />
|
||||
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M54,54m-21,0a21,21 0,1 1,42 0a21,21 0,1 1,-42 0"
|
||||
android:strokeColor="#B3261E"
|
||||
android:strokeWidth="1.2"
|
||||
android:strokeAlpha="0.25" />
|
||||
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M54,38 A16,16 0 0,1 70,54"
|
||||
android:strokeColor="#B3261E"
|
||||
android:strokeWidth="2"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M70,54 A16,16 0 0,1 54,70"
|
||||
android:strokeColor="#B3261E"
|
||||
android:strokeWidth="2"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeAlpha="0.4" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M54,70 A16,16 0 0,1 38,54"
|
||||
android:strokeColor="#B3261E"
|
||||
android:strokeWidth="2"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M38,54 A16,16 0 0,1 54,38"
|
||||
android:strokeColor="#B3261E"
|
||||
android:strokeWidth="2"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeAlpha="0.4" />
|
||||
|
||||
<path
|
||||
android:fillColor="#B3261E"
|
||||
android:pathData="M54,54m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0" />
|
||||
|
||||
<path
|
||||
android:fillColor="#FFDAD6"
|
||||
android:pathData="M54,54m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0" />
|
||||
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M73.8,73.8 L86,86"
|
||||
android:strokeColor="#C8C5D0"
|
||||
android:strokeWidth="5.5"
|
||||
android:strokeLineCap="round" />
|
||||
</vector>
|
||||
|
|
@ -65,6 +65,54 @@
|
|||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="?attr/colorSurfaceContainer"
|
||||
app:strokeColor="?attr/colorOutlineVariant"
|
||||
app:strokeWidth="1dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_icon_style"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/chipGroupIconStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
app:selectionRequired="true"
|
||||
app:singleSelection="true">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/chipIconStyleNew"
|
||||
style="@style/Widget.Material3.Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_icon_style_new" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/chipIconStyleClassic"
|
||||
style="@style/Widget.Material3.Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_icon_style_classic" />
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_classic_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_classic_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_classic_foreground" />
|
||||
</adaptive-icon>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_classic_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_classic_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_classic_foreground" />
|
||||
</adaptive-icon>
|
||||
|
|
@ -84,6 +84,9 @@
|
|||
<string name="settings_dns_bootstrap_hint">8.8.8.8, 8.8.4.4</string>
|
||||
<string name="settings_privacy_mode">Приватный режим</string>
|
||||
<string name="settings_privacy_mode_desc">IP отображается как 185.22.*.*</string>
|
||||
<string name="settings_icon_style">Иконка приложения</string>
|
||||
<string name="settings_icon_style_classic">Классическая</string>
|
||||
<string name="settings_icon_style_new">Новая</string>
|
||||
<string name="settings_theme">Тема</string>
|
||||
<string name="settings_theme_light">Светлая</string>
|
||||
<string name="settings_theme_dark">Тёмная</string>
|
||||
|
|
@ -307,7 +310,7 @@
|
|||
<string name="narrative_value_call_transport_proxy_assisted_udp_stun">UDP STUN через proxy</string>
|
||||
|
||||
<!-- VerdictNarrative: reason rows -->
|
||||
<string name="narrative_reason_xray_api">На устройстве найден локальный Xray API, из которого читаются outbound-адреса.</string>
|
||||
<string name="narrative_reason_xray_api">На устройстве найден локальный Xray API.</string>
|
||||
<string name="narrative_reason_split_tunnel">Прямой IP и IP через локальный proxy различаются.</string>
|
||||
<string name="narrative_reason_vpn_gateway_leak">При активном VPN приложение смогло выйти через non-VPN сеть.</string>
|
||||
<string name="narrative_reason_vpn_network_binding">Приложение смогло использовать объект VPN Network отдельно от обычной сети.</string>
|
||||
|
|
@ -480,6 +483,9 @@
|
|||
<string name="checker_bypass_open_proxy_review_suffix"> (требует подтверждения обхода)</string>
|
||||
<string name="checker_bypass_no_xray">Xray gRPC API: не обнаружен</string>
|
||||
<string name="checker_bypass_xray_api">Xray gRPC API: %1$s</string>
|
||||
<string name="checker_bypass_xray_api_stats_only">Xray gRPC StatsService: %1$s</string>
|
||||
<string name="checker_bypass_xray_handler_unavailable">HandlerService/ListOutbounds недоступен; outbound-адреса не раскрыты.</string>
|
||||
<string name="checker_bypass_xray_stats_sample">Счётчики StatsService: %1$s</string>
|
||||
<plurals name="checker_bypass_extra_outbounds">
|
||||
<item quantity="one">  …ещё %1$d аутбаунд</item>
|
||||
<item quantity="few">  …ещё %1$d аутбаунда</item>
|
||||
|
|
|
|||
|
|
@ -83,6 +83,9 @@
|
|||
<string name="settings_dns_bootstrap_hint">8.8.8.8, 8.8.4.4</string>
|
||||
<string name="settings_privacy_mode">Privacy mode</string>
|
||||
<string name="settings_privacy_mode_desc">IP is shown as 185.22.*.*</string>
|
||||
<string name="settings_icon_style">App icon</string>
|
||||
<string name="settings_icon_style_classic">Classic</string>
|
||||
<string name="settings_icon_style_new">New</string>
|
||||
<string name="settings_theme">Theme</string>
|
||||
<string name="settings_theme_light">Light</string>
|
||||
<string name="settings_theme_dark">Dark</string>
|
||||
|
|
@ -304,7 +307,7 @@
|
|||
<string name="narrative_value_call_transport_proxy_assisted_udp_stun">proxy-assisted UDP STUN</string>
|
||||
|
||||
<!-- VerdictNarrative: reason rows -->
|
||||
<string name="narrative_reason_xray_api">A local Xray API was found on the device and exposes outbound addresses.</string>
|
||||
<string name="narrative_reason_xray_api">A local Xray API was found on the device.</string>
|
||||
<string name="narrative_reason_split_tunnel">The direct IP and the IP via the local proxy differ.</string>
|
||||
<string name="narrative_reason_vpn_gateway_leak">With VPN active, the app was able to access the non-VPN network.</string>
|
||||
<string name="narrative_reason_vpn_network_binding">The app was able to use the VPN Network object separately from the regular network.</string>
|
||||
|
|
@ -477,6 +480,9 @@
|
|||
<string name="checker_bypass_open_proxy_review_suffix"> (requires bypass confirmation)</string>
|
||||
<string name="checker_bypass_no_xray">Xray gRPC API: not found</string>
|
||||
<string name="checker_bypass_xray_api">Xray gRPC API: %1$s</string>
|
||||
<string name="checker_bypass_xray_api_stats_only">Xray gRPC StatsService: %1$s</string>
|
||||
<string name="checker_bypass_xray_handler_unavailable">HandlerService/ListOutbounds is unavailable; outbound addresses were not exposed.</string>
|
||||
<string name="checker_bypass_xray_stats_sample">StatsService counters: %1$s</string>
|
||||
<plurals name="checker_bypass_extra_outbounds">
|
||||
<item quantity="one">  …%1$d more outbound</item>
|
||||
<item quantity="other">  …%1$d more outbounds</item>
|
||||
|
|
|
|||
|
|
@ -79,6 +79,20 @@ class LauncherIconManagerTest {
|
|||
assertEquals(LauncherIconVariant.ORIGINAL, LauncherIconManager.current(context))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apply enables CLASSIC and disables all other variants`() {
|
||||
assertTrue(LauncherIconManager.apply(context, LauncherIconVariant.CLASSIC))
|
||||
|
||||
LauncherIconVariant.entries.forEach { variant ->
|
||||
val expected = if (variant == LauncherIconVariant.CLASSIC) {
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||
} else {
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
||||
}
|
||||
assertEquals(expected, componentState(variant))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apply returns false when package manager rejects alias toggle`() {
|
||||
LauncherIconManager.setComponentEnabledSettingForTests = { _, _, _ ->
|
||||
|
|
|
|||
|
|
@ -36,4 +36,60 @@ class LauncherIconVariantTest {
|
|||
LauncherIconVariant.fromCvd(ColorVisionMode.ACHROMATOPSIA, LauncherIconVariant.PROTANOPIA),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolve returns CLASSIC when icon style is classic regardless of CVD`() {
|
||||
assertEquals(
|
||||
LauncherIconVariant.CLASSIC,
|
||||
LauncherIconVariant.resolve(
|
||||
iconStyleClassic = true,
|
||||
mode = ColorVisionMode.OFF,
|
||||
redGreenSub = null,
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
LauncherIconVariant.CLASSIC,
|
||||
LauncherIconVariant.resolve(
|
||||
iconStyleClassic = true,
|
||||
mode = ColorVisionMode.RED_GREEN,
|
||||
redGreenSub = LauncherIconVariant.DEUTERANOPIA,
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
LauncherIconVariant.CLASSIC,
|
||||
LauncherIconVariant.resolve(
|
||||
iconStyleClassic = true,
|
||||
mode = ColorVisionMode.ACHROMATOPSIA,
|
||||
redGreenSub = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolve delegates to fromCvd when icon style is new`() {
|
||||
assertEquals(
|
||||
LauncherIconVariant.ORIGINAL,
|
||||
LauncherIconVariant.resolve(
|
||||
iconStyleClassic = false,
|
||||
mode = ColorVisionMode.OFF,
|
||||
redGreenSub = null,
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
LauncherIconVariant.DEUTERANOPIA,
|
||||
LauncherIconVariant.resolve(
|
||||
iconStyleClassic = false,
|
||||
mode = ColorVisionMode.RED_GREEN,
|
||||
redGreenSub = null,
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
LauncherIconVariant.MONOCHROME,
|
||||
LauncherIconVariant.resolve(
|
||||
iconStyleClassic = false,
|
||||
mode = ColorVisionMode.ACHROMATOPSIA,
|
||||
redGreenSub = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,6 +113,20 @@ class SettingsAccessibilityFragmentTest {
|
|||
assertEquals(LauncherIconVariant.MONOCHROME, LauncherIconManager.current(context))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `changing CVD mode when classic icon style is set does not change launcher icon`() {
|
||||
AppUiSettings.prefs(context).edit {
|
||||
putString(SettingsPrefs.PREF_ICON_STYLE, "classic")
|
||||
}
|
||||
LauncherIconManager.apply(context, LauncherIconVariant.CLASSIC)
|
||||
|
||||
val scenario = accessibilityFragment()
|
||||
scenario.root.findViewById<Chip>(R.id.chipColorVisionRedGreen).performClick()
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
|
||||
assertEquals(LauncherIconVariant.CLASSIC, LauncherIconManager.current(context))
|
||||
}
|
||||
|
||||
private fun accessibilityFragment(): AccessibilityScenario {
|
||||
val activity = Robolectric.buildActivity(SettingsActivity::class.java).setup().get()
|
||||
activity.supportFragmentManager.beginTransaction()
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
Launcher icon now follows the selected color-vision accessibility mode.
|
||||
Added SNITCH check (β): detects IP and DNS leak signals via a redirect chain.
|
||||
Redesigned app icon; icon now follows the selected color-vision accessibility mode.
|
||||
Added classic icon option in Appearance settings.
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
نماد برنامه اکنون از حالت دید رنگ انتخابشده پیروی میکند.
|
||||
بررسی SNITCH (β) اضافه شد: نشت IP و DNS را از طریق زنجیره ریدایرکت شناسایی میکند.
|
||||
طراحی مجدد نماد برنامه؛ نماد اکنون از حالت دید رنگ انتخابشده پیروی میکند.
|
||||
گزینه نماد کلاسیک در تنظیمات ظاهر اضافه شد.
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
Иконка приложения теперь следует выбранному режиму цветового зрения.
|
||||
Добавлена SNITCH-проверка (β): обнаруживает утечки IP и DNS через цепочку редиректов.
|
||||
Редизайн иконки приложения; иконка теперь следует выбранному режиму цветового зрения.
|
||||
В настройках оформления добавлен выбор классической иконки.
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
应用图标现在会跟随所选的色觉辅助模式。
|
||||
新增 SNITCH 检测(β):通过重定向链检测 IP 和 DNS 泄漏。
|
||||
重新设计应用图标;图标现在会跟随所选的色觉辅助模式。
|
||||
在外观设置中新增经典图标选项。
|
||||
|
|
|
|||
25
xray-protos/src/main/proto/app/stats/command/command.proto
Normal file
25
xray-protos/src/main/proto/app/stats/command/command.proto
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package xray.app.stats.command;
|
||||
option csharp_namespace = "Xray.App.Stats.Command";
|
||||
option go_package = "github.com/xtls/xray-core/app/stats/command";
|
||||
option java_package = "com.xray.app.stats.command";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message Stat {
|
||||
string name = 1;
|
||||
int64 value = 2;
|
||||
}
|
||||
|
||||
message QueryStatsRequest {
|
||||
string pattern = 1;
|
||||
bool reset = 2;
|
||||
}
|
||||
|
||||
message QueryStatsResponse {
|
||||
repeated Stat stat = 1;
|
||||
}
|
||||
|
||||
service StatsService {
|
||||
rpc QueryStats(QueryStatsRequest) returns (QueryStatsResponse) {}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue