@@ -16,21 +16,42 @@ import com.v2ray.ang.R
1616import com.v2ray.ang.handler.MmkvManager
1717import com.v2ray.ang.util.getColorAttr
1818
19+ /* *
20+ * Shape-aware ImageView used both for the app's icon-shape background badges
21+ * and, via [R.styleable.DynamicShapeImageView_shapeTarget], for the small
22+ * "arrow" background badges shown at the end of preference/menu cards.
23+ *
24+ * Both targets share the exact same set of SVG shapes (see [resolveShapeId]),
25+ * but each target has its own MMKV pref key / broadcast action so the icon
26+ * shape and arrow shape can be customized independently from UI Settings.
27+ */
1928class DynamicShapeImageView @JvmOverloads constructor(
2029 context : Context ,
2130 attrs : AttributeSet ? = null ,
2231 defStyleAttr : Int = 0
2332) : ShaderImageView(context, attrs, defStyleAttr) {
2433
25- private var currentShapeKey: String? = AppConfig .PREF_ICON_SHAPE_DEFAULT
34+ private enum class ShapeTarget { ICON , ARROW }
35+
36+ private var shapeTarget: ShapeTarget = ShapeTarget .ICON
37+
38+ private val prefKey: String
39+ get() = if (shapeTarget == ShapeTarget .ARROW ) AppConfig .PREF_ARROW_SHAPE else AppConfig .PREF_ICON_SHAPE
40+
41+ private val defaultShapeKey: String
42+ get() = if (shapeTarget == ShapeTarget .ARROW ) AppConfig .PREF_ARROW_SHAPE_DEFAULT else AppConfig .PREF_ICON_SHAPE_DEFAULT
43+
44+ private val broadcastAction: String
45+ get() = if (shapeTarget == ShapeTarget .ARROW ) AppConfig .BROADCAST_ACTION_ARROW_SHAPE_CHANGED else AppConfig .BROADCAST_ACTION_ICON_SHAPE_CHANGED
46+
47+ private var currentShapeKey: String? = null
2648
2749 private var customBgColor: Int? = null
2850
2951 private val shapeChangeReceiver = object : BroadcastReceiver () {
3052 override fun onReceive (ctx : Context ? , intent : Intent ? ) {
31- if (intent?.action == AppConfig .BROADCAST_ACTION_ICON_SHAPE_CHANGED ) {
32- val newKey = MmkvManager .decodeSettingsString(AppConfig .PREF_ICON_SHAPE )
33- ? : AppConfig .PREF_ICON_SHAPE_DEFAULT
53+ if (intent?.action == broadcastAction) {
54+ val newKey = MmkvManager .decodeSettingsString(prefKey) ? : defaultShapeKey
3455 applyShape(newKey)
3556 }
3657 }
@@ -56,10 +77,28 @@ class DynamicShapeImageView @JvmOverloads constructor(
5677 )
5778 }
5879
80+ shapeTarget = if (typedArray.getInt(R .styleable.DynamicShapeImageView_shapeTarget , 0 ) == 1 ) {
81+ ShapeTarget .ARROW
82+ } else {
83+ ShapeTarget .ICON
84+ }
85+
5986 typedArray.recycle()
6087 }
6188
89+ currentShapeKey = defaultShapeKey
90+
6291 scaleType = ScaleType .CENTER_CROP
92+
93+ // ShaderImageView's own init block already called createImageViewHelper()
94+ // during the super() constructor call above, i.e. before shapeTarget and
95+ // customBgColor (parsed just above) even existed yet — so that first
96+ // helper was always built with the ICON defaults. Force a rebuild now
97+ // that this view's real target/color are known, so arrow badges (and any
98+ // non-default color) render correctly from the very first frame instead
99+ // of only after the user actively changes the shape preference.
100+ reloadShape()
101+
63102 loadColorBitmap()
64103 }
65104
@@ -80,11 +119,10 @@ class DynamicShapeImageView @JvmOverloads constructor(
80119 override fun onAttachedToWindow () {
81120 super .onAttachedToWindow()
82121 if (! isInEditMode) {
83- val savedKey = MmkvManager .decodeSettingsString(AppConfig .PREF_ICON_SHAPE )
84- ? : AppConfig .PREF_ICON_SHAPE_DEFAULT
122+ val savedKey = MmkvManager .decodeSettingsString(prefKey) ? : defaultShapeKey
85123 applyShape(savedKey)
86124
87- val filter = IntentFilter (AppConfig . BROADCAST_ACTION_ICON_SHAPE_CHANGED )
125+ val filter = IntentFilter (broadcastAction )
88126 ContextCompat .registerReceiver(
89127 context, shapeChangeReceiver, filter,
90128 ContextCompat .RECEIVER_NOT_EXPORTED
@@ -103,8 +141,7 @@ class DynamicShapeImageView @JvmOverloads constructor(
103141 super .onWindowFocusChanged(hasWindowFocus)
104142
105143 if (hasWindowFocus && ! isInEditMode) {
106- val savedKey = MmkvManager .decodeSettingsString(AppConfig .PREF_ICON_SHAPE )
107- ? : AppConfig .PREF_ICON_SHAPE_DEFAULT
144+ val savedKey = MmkvManager .decodeSettingsString(prefKey) ? : defaultShapeKey
108145 applyShape(savedKey)
109146 }
110147 }
@@ -117,7 +154,8 @@ class DynamicShapeImageView @JvmOverloads constructor(
117154 }
118155 }
119156
120- private fun resolveShapeId (): Int = when (currentShapeKey ? : AppConfig .PREF_ICON_SHAPE_DEFAULT ) {
157+ private fun resolveShapeId (): Int = when (currentShapeKey ? : defaultShapeKey) {
158+ " uwu_shape_cookie" -> R .raw.uwu_shape_cookie
121159 " uwu_shape_clover" -> R .raw.uwu_shape_clover
122160 " uwu_shape_circle" -> R .raw.uwu_shape_circle
123161 " uwu_shape_diamond" -> R .raw.uwu_shape_diamond
@@ -127,9 +165,9 @@ class DynamicShapeImageView @JvmOverloads constructor(
127165 " uwu_shape_rounded_square" -> R .raw.uwu_shape_rounded_square
128166 " uwu_shape_squircle" -> R .raw.uwu_shape_squircle
129167 " uwu_shape_heart" -> R .raw.uwu_shape_heart
130- " uwu_shape_hive" -> R .raw.uwu_shape_hive
131- " uwu_shape_pill" -> R .raw.uwu_shape_pill
132- " uwu_shape_scallop" -> R .raw.uwu_shape_scallop
133- else -> R .raw.uwu_shape_cookie
168+ " uwu_shape_hive" -> R .raw.uwu_shape_hive
169+ " uwu_shape_pill" -> R .raw.uwu_shape_pill
170+ " uwu_shape_scallop" -> R .raw.uwu_shape_scallop
171+ else -> if (shapeTarget == ShapeTarget . ARROW ) R .raw.uwu_shape_circle else R .raw.uwu_shape_cookie
134172 }
135173}
0 commit comments