From 3b03624f328d91501cbdd2f6096258cc56ccb678 Mon Sep 17 00:00:00 2001 From: Kr328 <39107975+Kr328@users.noreply.github.com> Date: Sat, 23 Nov 2019 14:06:10 +0800 Subject: [PATCH] use custom view --- app/src/main/AndroidManifest.xml | 2 + .../github/kr328/clash/ClashStatusActivity.kt | 7 ++ .../kr328/clash/CreateProfileActivity.kt | 13 ++- .../com/github/kr328/clash/MainActivity.kt | 5 +- .../kr328/clash/adapter/ProfileAdapter.kt | 26 +++-- .../com/github/kr328/clash/view/FatButton.kt | 18 ---- .../com/github/kr328/clash/view/FatItem.kt | 97 +++++++++++++++++++ .../github/kr328/clash/view/RadioFatItem.kt | 86 ++++++++++++++++ .../res/layout/activity_main_clash_status.xml | 2 +- app/src/main/res/layout/activity_profiles.xml | 32 ++---- .../main/res/layout/adapter_new_profile.xml | 44 --------- app/src/main/res/layout/adapter_profile.xml | 70 ------------- app/src/main/res/layout/dialog_text_edit.xml | 3 +- app/src/main/res/layout/view_fat_item.xml | 68 +++++++++++++ .../main/res/layout/view_radio_fat_item.xml | 71 ++++++++++++++ app/src/main/res/values/attrs.xml | 9 ++ app/src/main/res/values/colors.xml | 2 +- .../github/kr328/clash/service/TunService.kt | 4 +- .../service/net/DefaultNetworkObserver.kt | 20 +++- service/src/main/res/values/strings.xml | 4 +- 20 files changed, 396 insertions(+), 187 deletions(-) create mode 100644 app/src/main/java/com/github/kr328/clash/ClashStatusActivity.kt delete mode 100644 app/src/main/java/com/github/kr328/clash/view/FatButton.kt create mode 100644 app/src/main/java/com/github/kr328/clash/view/FatItem.kt create mode 100644 app/src/main/java/com/github/kr328/clash/view/RadioFatItem.kt delete mode 100644 app/src/main/res/layout/adapter_new_profile.xml delete mode 100644 app/src/main/res/layout/adapter_profile.xml create mode 100644 app/src/main/res/layout/view_fat_item.xml create mode 100644 app/src/main/res/layout/view_radio_fat_item.xml create mode 100644 app/src/main/res/values/attrs.xml 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