Skip to content

Commit 7bce7c9

Browse files
add dot animation when vpn connect in selected profile and improvement display network type
1 parent ceb2efe commit 7bce7c9

5 files changed

Lines changed: 109 additions & 36 deletions

File tree

V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainRecyclerAdapter.kt

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ class MainRecyclerAdapter(
3535
}
3636

3737
private var data: MutableList<ServersCache> = mutableListOf()
38+
39+
private var isRunningObserver: androidx.lifecycle.Observer<Boolean>? = null
3840

3941
@SuppressLint("NotifyDataSetChanged")
4042
fun setData(newData: MutableList<ServersCache>?, position: Int = -1) {
@@ -47,6 +49,28 @@ class MainRecyclerAdapter(
4749
}
4850
}
4951

52+
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
53+
super.onAttachedToRecyclerView(recyclerView)
54+
val lifecycleOwner = recyclerView.context as? androidx.lifecycle.LifecycleOwner
55+
if (lifecycleOwner != null) {
56+
isRunningObserver = androidx.lifecycle.Observer { _ ->
57+
val selectedGuid = MmkvManager.getSelectServer()
58+
val position = data.indexOfFirst { it.guid == selectedGuid }
59+
if (position >= 0) {
60+
notifyItemChanged(position)
61+
}
62+
}
63+
mainViewModel.isRunning.observe(lifecycleOwner, isRunningObserver!!)
64+
}
65+
}
66+
67+
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
68+
super.onDetachedFromRecyclerView(recyclerView)
69+
isRunningObserver?.let {
70+
mainViewModel.isRunning.removeObserver(it)
71+
}
72+
}
73+
5074
override fun getItemCount() = data.size + 1
5175

5276
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
@@ -62,7 +86,7 @@ class MainRecyclerAdapter(
6286
holder.itemMainBinding.tvStatistics.text = getAddress(profile)
6387
holder.itemMainBinding.tvType.text = getProtocolName(profile)
6488

65-
// Network & security icon+text di bawah alamat
89+
// Network & security icon+text (TCP Fix)
6690
val isNetSecEnabled = MmkvManager.decodeSettingsBool(AppConfig.PREF_NETWORK_SECURITY_ENABLED) == true
6791
bindNetworkSecurity(holder, profile, isNetSecEnabled)
6892

@@ -85,8 +109,32 @@ class MainRecyclerAdapter(
85109
holder.itemMainBinding.tvTraffic.visibility = View.GONE
86110
}
87111

88-
//layoutIndicator & Card Background (Transparent when selected)
89-
if (guid == MmkvManager.getSelectServer()) {
112+
val isSelectedServer = (guid == MmkvManager.getSelectServer())
113+
val isVpnConnected = mainViewModel.isRunning.value == true
114+
115+
if (isSelectedServer && isVpnConnected) {
116+
holder.itemMainBinding.vStatusDot.setBackgroundResource(R.drawable.blink_color)
117+
val blinkAnimDrawable = holder.itemMainBinding.vStatusDot.background
118+
119+
if (blinkAnimDrawable is android.graphics.drawable.AnimationDrawable) {
120+
holder.itemMainBinding.vStatusDot.visibility = View.VISIBLE
121+
holder.itemMainBinding.vStatusDot.post {
122+
if (!blinkAnimDrawable.isRunning) {
123+
blinkAnimDrawable.start()
124+
}
125+
}
126+
}
127+
} else {
128+
val blinkAnimDrawable = holder.itemMainBinding.vStatusDot.background
129+
if (blinkAnimDrawable is android.graphics.drawable.AnimationDrawable) {
130+
blinkAnimDrawable.stop()
131+
}
132+
holder.itemMainBinding.vStatusDot.visibility = View.GONE
133+
holder.itemMainBinding.vStatusDot.background = null
134+
}
135+
136+
//layoutIndicator & Card Background
137+
if (isSelectedServer) {
90138
val styleName = MmkvManager.decodeSettingsString(
91139
AppConfig.PREF_INDICATOR_STYLE,
92140
IndicatorStyle.STYLE_0.name
@@ -158,11 +206,10 @@ class MainRecyclerAdapter(
158206
enabled: Boolean
159207
) {
160208
val context = holder.itemView.context
161-
162209
val iconSize = (14 * context.resources.displayMetrics.density).toInt()
163210

164211
fun makeIcon(drawableRes: Int): android.graphics.drawable.Drawable? {
165-
val d = androidx.core.content.ContextCompat.getDrawable(context, drawableRes) ?: return null
212+
val d = ContextCompat.getDrawable(context, drawableRes) ?: return null
166213
val wrapped = androidx.core.graphics.drawable.DrawableCompat.wrap(d.mutate())
167214
androidx.core.graphics.drawable.DrawableCompat.setTint(
168215
wrapped,
@@ -176,7 +223,7 @@ class MainRecyclerAdapter(
176223
}
177224

178225
val isComplex = profile.configType.isComplexType()
179-
val network = profile.network?.takeIf { it.isNotBlank() && !it.equals("tcp", ignoreCase = true) }
226+
val network = profile.network?.takeIf { it.isNotBlank() }
180227

181228
val security = profile.security?.takeIf { it.isNotBlank() }?.let { sec ->
182229
if (profile.insecure == true && sec.equals("tls", ignoreCase = true)) {
@@ -190,7 +237,6 @@ class MainRecyclerAdapter(
190237
holder.itemMainBinding.layoutNetworkSecurity.visibility =
191238
if (showAny) View.VISIBLE else View.GONE
192239

193-
// tv_network
194240
if (enabled && !isComplex && network != null) {
195241
holder.itemMainBinding.tvNetwork.text = network
196242
holder.itemMainBinding.tvNetwork.setCompoundDrawables(makeIcon(R.drawable.ic_thumb_up_outline), null, null, null)
@@ -199,16 +245,9 @@ class MainRecyclerAdapter(
199245
holder.itemMainBinding.tvNetwork.visibility = View.GONE
200246
}
201247

202-
// tv_security
203248
if (enabled && !isComplex && security != null) {
204249
holder.itemMainBinding.tvSecurity.text = security
205-
206-
val iconRes = if (security == "none") {
207-
R.drawable.ic_unlock_24dp
208-
} else {
209-
R.drawable.ic_lock_24dp
210-
}
211-
250+
val iconRes = if (security == "none") R.drawable.ic_unlock_24dp else R.drawable.ic_lock_24dp
212251
holder.itemMainBinding.tvSecurity.setCompoundDrawables(makeIcon(iconRes), null, null, null)
213252
holder.itemMainBinding.tvSecurity.visibility = View.VISIBLE
214253
} else {
@@ -234,18 +273,13 @@ class MainRecyclerAdapter(
234273
return when (viewType) {
235274
VIEW_TYPE_ITEM ->
236275
MainViewHolder(ItemRecyclerMainBinding.inflate(LayoutInflater.from(parent.context), parent, false))
237-
238276
else ->
239277
FooterViewHolder(ItemRecyclerFooterBinding.inflate(LayoutInflater.from(parent.context), parent, false))
240278
}
241279
}
242280

243281
override fun getItemViewType(position: Int): Int {
244-
return if (position == data.size) {
245-
VIEW_TYPE_FOOTER
246-
} else {
247-
VIEW_TYPE_ITEM
248-
}
282+
return if (position == data.size) VIEW_TYPE_FOOTER else VIEW_TYPE_ITEM
249283
}
250284

251285
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), ItemTouchHelperViewHolder {
@@ -255,14 +289,12 @@ class MainRecyclerAdapter(
255289

256290
class MainViewHolder(val itemMainBinding: ItemRecyclerMainBinding) :
257291
BaseViewHolder(itemMainBinding.root) {
258-
259292
override fun onItemSelected() {
260293
val context = itemView.context
261294
val typedValue = TypedValue()
262295
context.theme.resolveAttribute(com.google.android.material.R.attr.colorSurfaceVariant, typedValue, true)
263296
itemMainBinding.layoutCard.setCardBackgroundColor(typedValue.data)
264297
}
265-
266298
override fun onItemClear() {
267299
val context = itemView.context
268300
val typedValue = TypedValue()
@@ -283,10 +315,6 @@ class MainRecyclerAdapter(
283315
return true
284316
}
285317

286-
override fun onItemMoveCompleted() {
287-
// do nothing
288-
}
289-
290-
override fun onItemDismiss(position: Int) {
291-
}
318+
override fun onItemMoveCompleted() {}
319+
override fun onItemDismiss(position: Int) {}
292320
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:oneshot="false">
4+
<item
5+
android:drawable="@drawable/dot_primary"
6+
android:duration="500" />
7+
<item
8+
android:drawable="@drawable/dot_quaternary"
9+
android:duration="500" />
10+
</animation-list>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:shape="oval">
4+
<solid android:color="@color/qsb_icon_tint_primary" />
5+
<size
6+
android:width="10dp"
7+
android:height="10dp" />
8+
</shape>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:shape="oval">
4+
<solid android:color="@color/qsb_icon_tint_quaternary" />
5+
<size
6+
android:width="10dp"
7+
android:height="10dp" />
8+
</shape>

V2rayNG/app/src/main/res/layout/item_recycler_main.xml

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,33 @@
4242
android:layout_weight="1.0"
4343
android:paddingStart="8.0dip"
4444
android:paddingEnd="8.0dip">
45-
<TextView
46-
android:textAppearance="?attr/textAppearanceTitleMedium"
47-
android:textSize="17.0sp"
48-
android:textColor="?colorOnSurface"
49-
android:id="@+id/tv_name"
50-
android:layout_width="wrap_content"
45+
46+
<LinearLayout
47+
android:layout_width="match_parent"
5148
android:layout_height="wrap_content"
52-
android:maxLines="1" />
49+
android:orientation="horizontal"
50+
android:gravity="center_vertical">
51+
52+
<TextView
53+
android:textAppearance="?attr/textAppearanceTitleMedium"
54+
android:textSize="17.0sp"
55+
android:textColor="?colorOnSurface"
56+
android:id="@+id/tv_name"
57+
android:layout_width="0dip"
58+
android:layout_height="wrap_content"
59+
android:layout_weight="1.0"
60+
android:ellipsize="end"
61+
android:maxLines="1" />
62+
63+
<View
64+
android:id="@+id/v_status_dot"
65+
android:layout_width="10dp"
66+
android:layout_height="10dp"
67+
android:layout_marginStart="8dp"
68+
android:layout_marginEnd="0dp"
69+
android:visibility="gone" />
70+
</LinearLayout>
71+
5372
<LinearLayout
5473
android:gravity="center_vertical"
5574
android:orientation="horizontal"

0 commit comments

Comments
 (0)