diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 36edc8c..1a947a6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -30,5 +30,7 @@
+
diff --git a/app/src/main/java/com/github/kr328/clash/ClashStatusActivity.kt b/app/src/main/java/com/github/kr328/clash/ClashStatusActivity.kt
new file mode 100644
index 0000000..9012223
--- /dev/null
+++ b/app/src/main/java/com/github/kr328/clash/ClashStatusActivity.kt
@@ -0,0 +1,7 @@
+package com.github.kr328.clash
+
+import androidx.appcompat.app.AppCompatActivity
+
+class ClashStatusActivity : AppCompatActivity() {
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/github/kr328/clash/CreateProfileActivity.kt b/app/src/main/java/com/github/kr328/clash/CreateProfileActivity.kt
index 451d2e2..9952e42 100644
--- a/app/src/main/java/com/github/kr328/clash/CreateProfileActivity.kt
+++ b/app/src/main/java/com/github/kr328/clash/CreateProfileActivity.kt
@@ -12,6 +12,7 @@ import android.widget.ListView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.commit
+import com.github.kr328.clash.view.FatItem
import kotlinx.android.synthetic.main.activity_new_profile.*
class CreateProfileActivity : AppCompatActivity() {
@@ -28,17 +29,19 @@ class CreateProfileActivity : AppCompatActivity() {
class Adapter(private val context: Context) : BaseAdapter() {
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
- return (convertView ?: LayoutInflater.from(context).inflate(R.layout.adapter_new_profile, parent, false)).apply {
+ return ((convertView ?: FatItem(context)) as FatItem).apply {
val current = NEW_PROFILE_SOURCE[position]
- findViewById(R.id.adapter_new_profile_icon).setImageResource(current.icon)
- findViewById(R.id.adapter_new_profile_title).text = context.getString(current.title)
- findViewById(R.id.adapter_new_profile_summary).text = context.getString(current.summary)
+ isClickable = false
+
+ icon = context.getDrawable(current.icon)
+ title = context.getString(current.title)
+ summary = context.getString(current.summary)
}
}
override fun getItem(position: Int): Any {
- return NEW_PROFILE_SOURCE
+ return NEW_PROFILE_SOURCE[position]
}
override fun getItemId(position: Int): Long {
diff --git a/app/src/main/java/com/github/kr328/clash/MainActivity.kt b/app/src/main/java/com/github/kr328/clash/MainActivity.kt
index ace1f3d..d2c3165 100644
--- a/app/src/main/java/com/github/kr328/clash/MainActivity.kt
+++ b/app/src/main/java/com/github/kr328/clash/MainActivity.kt
@@ -10,6 +10,7 @@ import android.net.VpnService
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
+import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.MutableLiveData
@@ -96,7 +97,9 @@ class MainActivity : AppCompatActivity() {
activity_main_clash_status.setOnClickListener {
when ( clashStatus.value ?: ClashProcessStatus.STATUS_STOPPED ) {
- ClashProcessStatus.STATUS_STARTED -> {}
+ ClashProcessStatus.STATUS_STARTED -> {
+ clash?.stop()
+ }
ClashProcessStatus.STATUS_STOPPED -> {
VpnService.prepare(this)?.apply {
startActivityForResult(this, VPN_REQUEST_CODE)
diff --git a/app/src/main/java/com/github/kr328/clash/adapter/ProfileAdapter.kt b/app/src/main/java/com/github/kr328/clash/adapter/ProfileAdapter.kt
index 2a4ae87..ec5bdd5 100644
--- a/app/src/main/java/com/github/kr328/clash/adapter/ProfileAdapter.kt
+++ b/app/src/main/java/com/github/kr328/clash/adapter/ProfileAdapter.kt
@@ -11,23 +11,19 @@ import androidx.appcompat.widget.AppCompatRadioButton
import androidx.recyclerview.widget.RecyclerView
import com.github.kr328.clash.R
import com.github.kr328.clash.service.data.ClashProfileEntity
+import com.github.kr328.clash.view.RadioFatItem
class ProfileAdapter(private val context: Context, private val listener: (Int) -> Unit) :
RecyclerView.Adapter() {
var profiles: List = emptyList()
- class ProfileViewHolder(view: View) : RecyclerView.ViewHolder(view) {
- val clickable: View = view.findViewById(R.id.adapter_profiles_clickable)
- val checked: AppCompatRadioButton = view.findViewById(R.id.adapter_profiles_checked)
- val title: TextView = view.findViewById(R.id.adapter_profiles_title)
- val summary: TextView = view.findViewById(R.id.adapter_profiles_summary)
- val operation: ImageView = view.findViewById(R.id.adapter_profiles_operation_image)
- }
+ class ProfileViewHolder(val view: RadioFatItem) : RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileViewHolder {
return ProfileViewHolder(
- LayoutInflater.from(context)
- .inflate(R.layout.adapter_profile, parent, false)
+ RadioFatItem(context).apply {
+ layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+ }
)
}
@@ -38,23 +34,23 @@ class ProfileAdapter(private val context: Context, private val listener: (Int) -
override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) {
val current = profiles[position]
- holder.title.text = current.name
- holder.summary.text = context.getString(
+ holder.view.title = current.name
+ holder.view.summary = context.getString(
R.string.clash_profile_item_summary,
current.proxies, current.proxyGroups, current.rules
)
- holder.checked.isChecked = current.selected
- holder.clickable.setOnClickListener {
+ holder.view.isChecked = current.selected
+ holder.view.setOnClickListener {
listener(current.id)
}
with(current.token) {
when {
startsWith("http") || startsWith("content") -> {
- holder.operation.setImageResource(R.drawable.ic_profile_refresh)
+ holder.view.operation = context.getDrawable(R.drawable.ic_profile_refresh)
}
startsWith("file") -> {
- holder.operation.setImageResource(R.drawable.ic_profile_edit)
+ holder.view.operation = context.getDrawable(R.drawable.ic_profile_edit)
}
}
}
diff --git a/app/src/main/java/com/github/kr328/clash/view/FatButton.kt b/app/src/main/java/com/github/kr328/clash/view/FatButton.kt
deleted file mode 100644
index 684d333..0000000
--- a/app/src/main/java/com/github/kr328/clash/view/FatButton.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.github.kr328.clash.view
-
-import android.content.Context
-import android.graphics.Canvas
-import android.util.AttributeSet
-import android.view.View
-
-class FatButton @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null, defaultStyleAttr: Int = 0, defaultStyleRes: Int = 0) :
- View(context, attributeSet, defaultStyleAttr, defaultStyleRes) {
-
- override fun onDraw(canvas: Canvas?) {
- super.onDraw(canvas)
-
- canvas?.apply {
-
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/github/kr328/clash/view/FatItem.kt b/app/src/main/java/com/github/kr328/clash/view/FatItem.kt
new file mode 100644
index 0000000..97cd5ec
--- /dev/null
+++ b/app/src/main/java/com/github/kr328/clash/view/FatItem.kt
@@ -0,0 +1,97 @@
+package com.github.kr328.clash.view
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.TextView
+import com.github.kr328.clash.R
+
+class FatItem @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0) :
+ FrameLayout(context, attributeSet, defStyleAttr, defStyleRes) {
+ private val titleView: TextView
+ private val summaryView: TextView
+ private val operationView: View
+ private val iconView: View
+ private val dividerView: View
+ private val clickable: View
+ private val operationClickable: View
+
+ var icon: Drawable?
+ get() = iconView.background
+ set(value) { iconView.background = value }
+ var title: CharSequence?
+ get() = titleView.text
+ set(value) { titleView.text = value }
+ var summary: CharSequence?
+ get() = summaryView.text
+ set(value) {
+ summaryView.text = value
+ if ( value?.isNotBlank() != true )
+ summaryView.visibility = View.GONE
+ else
+ summaryView.visibility = View.VISIBLE
+ }
+ var operation: Drawable?
+ get() = operationView.background
+ set(value) {
+ operationView.background = value
+ if ( value == null )
+ operationClickable.visibility = View.GONE
+ else
+ operationClickable.visibility = View.VISIBLE
+ }
+
+ override fun setOnClickListener(l: OnClickListener?) {
+ clickable.setOnClickListener(l)
+ }
+
+ override fun setOnLongClickListener(l: OnLongClickListener?) {
+ clickable.setOnLongClickListener(l)
+ }
+
+ fun setOperationOnClickListener(l: OnClickListener?) {
+ operationClickable.setOnClickListener(l)
+ }
+
+ fun setOperationOnLongClickListener(l: OnLongClickListener?) {
+ operationClickable.setOnLongClickListener(l)
+ }
+
+ override fun setClickable(clickable: Boolean) {
+ this.clickable.isFocusable = clickable
+ this.clickable.isClickable = clickable
+ this.operationClickable.isFocusable = clickable
+ this.operationClickable.isClickable = clickable
+ }
+
+ override fun isClickable(): Boolean {
+ return this.clickable.isClickable
+ }
+
+ init {
+ with ( LayoutInflater.from(context).inflate(R.layout.view_fat_item, this, true) ) {
+ titleView = findViewById(R.id.view_fat_item_title)
+ summaryView = findViewById(R.id.view_fat_item_summary)
+ operationView = findViewById(R.id.view_fat_item_operation)
+ dividerView = findViewById(R.id.view_fat_item_divider)
+ iconView = findViewById(R.id.view_fat_item_icon)
+ clickable = findViewById(R.id.view_fat_item_clickable)
+ operationClickable = findViewById(R.id.view_fat_item_operation_clickable)
+ }
+
+ context.theme.obtainStyledAttributes(attributeSet, R.styleable.FatItem, 0, 0).apply {
+ try {
+ icon = getDrawable(R.styleable.FatItem_icon)
+ title = getString(R.styleable.FatItem_title)
+ summary = getString(R.styleable.FatItem_summary)
+ operation = getDrawable(R.styleable.FatItem_operation)
+ }
+ finally {
+ recycle()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/github/kr328/clash/view/RadioFatItem.kt b/app/src/main/java/com/github/kr328/clash/view/RadioFatItem.kt
new file mode 100644
index 0000000..c1b956f
--- /dev/null
+++ b/app/src/main/java/com/github/kr328/clash/view/RadioFatItem.kt
@@ -0,0 +1,86 @@
+package com.github.kr328.clash.view
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.TextView
+import com.github.kr328.clash.R
+import com.google.android.material.radiobutton.MaterialRadioButton
+
+class RadioFatItem @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0) :
+ FrameLayout(context, attributeSet, defStyleAttr, defStyleRes) {
+ private val titleView: TextView
+ private val summaryView: TextView
+ private val operationView: View
+ private val radioView: MaterialRadioButton
+ private val dividerView: View
+ private val clickable: View
+ private val operationClickable: View
+
+ var isChecked: Boolean
+ get() = radioView.isChecked
+ set(value) { radioView.isChecked = value }
+ var title: CharSequence?
+ get() = titleView.text
+ set(value) { titleView.text = value }
+ var summary: CharSequence?
+ get() = summaryView.text
+ set(value) {
+ summaryView.text = value
+ if ( value?.isNotBlank() != true )
+ summaryView.visibility = View.GONE
+ else
+ summaryView.visibility = View.VISIBLE
+ }
+ var operation: Drawable?
+ get() = operationView.background
+ set(value) {
+ operationView.background = value
+ if ( value == null )
+ operationClickable.visibility = View.GONE
+ else
+ operationClickable.visibility = View.VISIBLE
+ }
+
+ override fun setOnClickListener(l: OnClickListener?) {
+ clickable.setOnClickListener(l)
+ }
+
+ override fun setOnLongClickListener(l: OnLongClickListener?) {
+ clickable.setOnLongClickListener(l)
+ }
+
+ fun setOperationOnClickListener(l: OnClickListener?) {
+ operationClickable.setOnClickListener(l)
+ }
+
+ fun setOperationOnLongClickListener(l: OnLongClickListener?) {
+ operationClickable.setOnLongClickListener(l)
+ }
+
+ init {
+ with ( LayoutInflater.from(context).inflate(R.layout.view_radio_fat_item, this, true) ) {
+ titleView = findViewById(R.id.view_radio_fat_item_title)
+ summaryView = findViewById(R.id.view_radio_fat_item_summary)
+ operationView = findViewById(R.id.view_radio_fat_item_operation)
+ dividerView = findViewById(R.id.view_radio_fat_item_divider)
+ radioView = findViewById(R.id.view_radio_fat_item_radio)
+ clickable = findViewById(R.id.view_radio_fat_item_clickable)
+ operationClickable = findViewById(R.id.view_radio_fat_item_operation_clickable)
+ }
+
+ context.theme.obtainStyledAttributes(attributeSet, R.styleable.FatItem, 0, 0).apply {
+ try {
+ title = getString(R.styleable.FatItem_title)
+ summary = getString(R.styleable.FatItem_summary)
+ operation = getDrawable(R.styleable.FatItem_operation)
+ }
+ finally {
+ recycle()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main_clash_status.xml b/app/src/main/res/layout/activity_main_clash_status.xml
index fff47dd..75ebac1 100644
--- a/app/src/main/res/layout/activity_main_clash_status.xml
+++ b/app/src/main/res/layout/activity_main_clash_status.xml
@@ -40,7 +40,7 @@
-
@@ -29,32 +31,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
-
-
-
-
-
-
+ android:layout_height="wrap_content"
+ app:icon="@drawable/ic_new_profile"
+ app:title="@string/clash_new_profile" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/adapter_new_profile.xml b/app/src/main/res/layout/adapter_new_profile.xml
deleted file mode 100644
index bffbd18..0000000
--- a/app/src/main/res/layout/adapter_new_profile.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/adapter_profile.xml b/app/src/main/res/layout/adapter_profile.xml
deleted file mode 100644
index 2566d98..0000000
--- a/app/src/main/res/layout/adapter_profile.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_text_edit.xml b/app/src/main/res/layout/dialog_text_edit.xml
index a418355..bc5d1f3 100644
--- a/app/src/main/res/layout/dialog_text_edit.xml
+++ b/app/src/main/res/layout/dialog_text_edit.xml
@@ -1,6 +1,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_radio_fat_item.xml b/app/src/main/res/layout/view_radio_fat_item.xml
new file mode 100644
index 0000000..cbba46e
--- /dev/null
+++ b/app/src/main/res/layout/view_radio_fat_item.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..e4adeb4
--- /dev/null
+++ b/app/src/main/res/values/attrs.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 247b017..e77297b 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -5,5 +5,5 @@
#1E4376
#FFFFFF
- #d3d3d3
+ #d3d3d3
diff --git a/service/src/main/java/com/github/kr328/clash/service/TunService.kt b/service/src/main/java/com/github/kr328/clash/service/TunService.kt
index 2bd025f..f7e43e5 100644
--- a/service/src/main/java/com/github/kr328/clash/service/TunService.kt
+++ b/service/src/main/java/com/github/kr328/clash/service/TunService.kt
@@ -105,6 +105,8 @@ class TunService : VpnService() {
}
override fun onDestroy() {
+ super.onDestroy()
+
fileDescriptor.close()
clash.stopTunDevice()
@@ -113,8 +115,6 @@ class TunService : VpnService() {
unbindService(connection)
defaultNetworkObserver.unregister()
-
- super.onDestroy()
}
private fun Builder.addDefaultDns(): Builder {
diff --git a/service/src/main/java/com/github/kr328/clash/service/net/DefaultNetworkObserver.kt b/service/src/main/java/com/github/kr328/clash/service/net/DefaultNetworkObserver.kt
index 4a04745..3ea1e71 100644
--- a/service/src/main/java/com/github/kr328/clash/service/net/DefaultNetworkObserver.kt
+++ b/service/src/main/java/com/github/kr328/clash/service/net/DefaultNetworkObserver.kt
@@ -6,20 +6,27 @@ import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
+import android.os.Handler
class DefaultNetworkObserver(val context: Context, val listener: (Network?) -> Unit) {
+ private val handler = Handler()
private val connectivity = context.getSystemService(ConnectivityManager::class.java)!!
- private val active: Network?
- get() = connectivity.activeNetwork
+ private var blocking: Boolean = true
private var current: Network? = null
private val callback = object: ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
+ if ( blocking )
+ return
+
current = network
listener(current)
}
override fun onLost(network: Network) {
+ if ( blocking )
+ return
+
if ( current == network )
current = null
@@ -30,6 +37,9 @@ class DefaultNetworkObserver(val context: Context, val listener: (Network?) -> U
network: Network,
networkCapabilities: NetworkCapabilities
) {
+ if ( blocking )
+ return
+
if ( network == current )
listener(current)
}
@@ -37,6 +47,12 @@ class DefaultNetworkObserver(val context: Context, val listener: (Network?) -> U
fun register() {
connectivity.registerNetworkCallback(NetworkRequest.Builder().build(), callback)
+
+ listener(connectivity.activeNetwork)
+
+ handler.postDelayed({
+ blocking = false
+ }, 1000)
}
fun unregister() {
diff --git a/service/src/main/res/values/strings.xml b/service/src/main/res/values/strings.xml
index 499ef39..4ad8fad 100644
--- a/service/src/main/res/values/strings.xml
+++ b/service/src/main/res/values/strings.xml
@@ -1,8 +1,8 @@
- Service
+ Clash
Clash Status
- Clash Running
+ Running
Profile %s loaded
VPN