use custom view
This commit is contained in:
20 files changed
+396
-187
No files matched your search
@@ -30,5 +30,7 @@
|
||||
<activity android:name=".ImportFileActivity"
|
||||
android:label="@string/clash_import_from_file"
|
||||
android:exported="false"/>
|
||||
<activity android:name=".ClashStatusActivity"
|
||||
android:exported="false" />
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
class ClashStatusActivity : AppCompatActivity() {
|
||||
|
||||
}
|
||||
@@ -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<ImageView>(R.id.adapter_new_profile_icon).setImageResource(current.icon)
|
||||
findViewById<TextView>(R.id.adapter_new_profile_title).text = context.getString(current.title)
|
||||
findViewById<TextView>(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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<ProfileAdapter.ProfileViewHolder>() {
|
||||
var profiles: List<ClashProfileEntity> = 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@
|
||||
<TextView
|
||||
android:id="@+id/activity_main_clash_status_summary"
|
||||
android:text="@string/clash_status_click_to_start"
|
||||
android:textColor="@color/light_gray"
|
||||
android:textColor="@color/lightGray"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_alignStart="@id/activity_main_clash_status_title"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
@@ -29,32 +31,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<LinearLayout
|
||||
<com.github.kr328.clash.view.FatItem
|
||||
android:id="@+id/activity_profiles_new_profile"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="70dp"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:src="@drawable/ic_new_profile"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp" />
|
||||
|
||||
<TextView
|
||||
android:text="@string/clash_new_profile"
|
||||
android:textStyle="bold"
|
||||
android:textSize="16sp"
|
||||
android:gravity="center|start"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</LinearLayout>
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/ic_new_profile"
|
||||
app:title="@string/clash_new_profile" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
@@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/activity_main_clash_profiles"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="18dp"
|
||||
android:paddingEnd="18dp"
|
||||
android:paddingTop="15dp"
|
||||
android:paddingBottom="15dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardBackgroundColor="@android:color/white">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/adapter_new_profile_icon"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:src="@drawable/ic_profiles" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/adapter_new_profile_title"
|
||||
android:layout_toEndOf="@id/adapter_new_profile_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
android:text="@string/clash_profiles" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/adapter_new_profile_summary"
|
||||
android:text="@string/clash_profiles_summary_unselected"
|
||||
android:textColor="@android:color/darker_gray"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_alignStart="@id/adapter_new_profile_title"
|
||||
android:layout_below="@id/adapter_new_profile_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</RelativeLayout>
|
||||
@@ -1,70 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/adapter_profiles_clickable"
|
||||
android:orientation="horizontal"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatRadioButton
|
||||
android:id="@+id/adapter_profiles_checked"
|
||||
android:focusable="false"
|
||||
android:clickable="false"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="80dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent">
|
||||
<TextView
|
||||
android:id="@+id/adapter_profiles_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
android:textSize="16sp"
|
||||
android:text="@string/app_name"/>
|
||||
<TextView
|
||||
android:id="@+id/adapter_profiles_summary"
|
||||
android:textColor="@color/colorAccent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_name"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="end">
|
||||
|
||||
<View
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/light_gray"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/adapter_profiles_operation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/adapter_profiles_operation_image"
|
||||
android:layout_margin="25dp"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical" android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<EditText
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/view_fat_item_clickable"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<View
|
||||
android:id="@+id/view_fat_item_icon"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_marginStart="27.5dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/view_fat_item_text_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@id/view_fat_item_icon"
|
||||
android:layout_toStartOf="@id/view_fat_item_operation_clickable"
|
||||
android:layout_marginStart="27.5dp"
|
||||
android:paddingTop="15dp"
|
||||
android:paddingBottom="15dp"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:id="@+id/view_fat_item_title"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
<TextView
|
||||
android:layout_marginTop="5dp"
|
||||
android:id="@+id/view_fat_item_summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/view_fat_item_operation_clickable"
|
||||
android:layout_width="81dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignTop="@id/view_fat_item_text_container"
|
||||
android:layout_alignBottom="@id/view_fat_item_text_container">
|
||||
<View
|
||||
android:id="@+id/view_fat_item_divider"
|
||||
android:layout_width="1dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/lightGray"
|
||||
android:layout_marginEnd="25dp" />
|
||||
|
||||
<View
|
||||
android:id="@+id/view_fat_item_operation"
|
||||
android:layout_marginEnd="25dp"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/view_radio_fat_item_clickable"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<RadioButton
|
||||
android:gravity="center"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:id="@+id/view_radio_fat_item_radio"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginStart="24dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/view_radio_fat_item_text_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_toStartOf="@id/view_radio_fat_item_operation_clickable"
|
||||
android:layout_marginStart="80dp"
|
||||
android:paddingTop="15dp"
|
||||
android:paddingBottom="15dp"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:id="@+id/view_radio_fat_item_title"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
<TextView
|
||||
android:layout_marginTop="5dp"
|
||||
android:id="@+id/view_radio_fat_item_summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/view_radio_fat_item_operation_clickable"
|
||||
android:layout_width="81dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignTop="@id/view_radio_fat_item_text_container"
|
||||
android:layout_alignBottom="@id/view_radio_fat_item_text_container">
|
||||
<View
|
||||
android:id="@+id/view_radio_fat_item_divider"
|
||||
android:layout_width="1dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/lightGray"
|
||||
android:layout_marginEnd="25dp" />
|
||||
|
||||
<View
|
||||
android:id="@+id/view_radio_fat_item_operation"
|
||||
android:layout_marginEnd="25dp"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="FatItem">
|
||||
<attr name="icon" format="reference"/>
|
||||
<attr name="title" format="string" />
|
||||
<attr name="summary" format="string" />
|
||||
<attr name="operation" format="string" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
@@ -5,5 +5,5 @@
|
||||
<color name="colorAccent">#1E4376</color>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
|
||||
<color name="light_gray">#d3d3d3</color>
|
||||
<color name="lightGray">#d3d3d3</color>
|
||||
</resources>
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<resources>
|
||||
<string name="app_name">Service</string>
|
||||
<string name="app_name">Clash</string>
|
||||
|
||||
<string name="clash_service_status_channel">Clash Status</string>
|
||||
<string name="clash_service_running">Clash Running</string>
|
||||
<string name="clash_service_running">Running</string>
|
||||
<string name="clash_service_running_message">Profile %s loaded</string>
|
||||
<string name="clash_service_vpn_mode">VPN</string>
|
||||
</resources>
|
||||
Reference in new issue
Block a user