Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ac043da8a2 | |||
| a192fec127 | |||
| b0b09273cf | |||
| 01a943b7ea | |||
| 06a3738b59 |
+1
-1
Submodule AndroidLibXrayLite updated: df33063839...1f10bdfa7f
@@ -12,8 +12,8 @@ android {
|
||||
applicationId = "com.v2ray.ang"
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
versionCode = 705
|
||||
versionName = "2.0.5"
|
||||
versionCode = 706
|
||||
versionName = "2.0.6"
|
||||
multiDexEnabled = true
|
||||
|
||||
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
|
||||
|
||||
@@ -1,25 +1,47 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.FragmentGroupServerBinding
|
||||
import com.v2ray.ang.databinding.ItemQrcodeBinding
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.toastError
|
||||
import com.v2ray.ang.extension.toastSuccess
|
||||
import com.v2ray.ang.handler.AngConfigManager
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
|
||||
import com.v2ray.ang.viewmodel.MainViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class GroupServerFragment : BaseFragment<FragmentGroupServerBinding>() {
|
||||
private val ownerActivity: MainActivity
|
||||
get() = requireActivity() as MainActivity
|
||||
private val mainViewModel: MainViewModel by activityViewModels()
|
||||
private lateinit var adapter: MainRecyclerAdapter
|
||||
private var itemTouchHelper: ItemTouchHelper? = null
|
||||
private val subId: String by lazy { arguments?.getString(ARG_SUB_ID).orEmpty() }
|
||||
|
||||
private val share_method: Array<out String> by lazy {
|
||||
ownerActivity.resources.getStringArray(R.array.share_method)
|
||||
}
|
||||
private val share_method_more: Array<out String> by lazy {
|
||||
ownerActivity.resources.getStringArray(R.array.share_method_more)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ARG_SUB_ID = "subscriptionId"
|
||||
fun newInstance(subId: String) = GroupServerFragment().apply {
|
||||
@@ -32,7 +54,7 @@ class GroupServerFragment : BaseFragment<FragmentGroupServerBinding>() {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
||||
adapter = MainRecyclerAdapter(requireActivity() as MainActivity)
|
||||
adapter = MainRecyclerAdapter(mainViewModel, ActivityAdapterListener())
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_DOUBLE_COLUMN_DISPLAY, false)) {
|
||||
binding.recyclerView.layoutManager = GridLayoutManager(requireContext(), 2)
|
||||
@@ -52,9 +74,6 @@ class GroupServerFragment : BaseFragment<FragmentGroupServerBinding>() {
|
||||
// Log.d(TAG, "GroupServerFragment updateListAction subId=$subId")
|
||||
adapter.setData(mainViewModel.serversCache, index)
|
||||
}
|
||||
mainViewModel.isRunning.observe(viewLifecycleOwner) { isRunning ->
|
||||
adapter.isRunning = isRunning
|
||||
}
|
||||
|
||||
// Log.d(TAG, "GroupServerFragment onViewCreated: subId=$subId")
|
||||
}
|
||||
@@ -63,4 +82,191 @@ class GroupServerFragment : BaseFragment<FragmentGroupServerBinding>() {
|
||||
super.onResume()
|
||||
mainViewModel.subscriptionIdChanged(subId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shares server configuration
|
||||
* Displays a dialog with sharing options and executes the selected action
|
||||
* @param guid The server unique identifier
|
||||
* @param profile The server configuration
|
||||
* @param position The position in the list
|
||||
* @param shareOptions The list of share options
|
||||
* @param skip The number of options to skip
|
||||
*/
|
||||
private fun shareServer(guid: String, profile: ProfileItem, position: Int, shareOptions: List<String>, skip: Int) {
|
||||
AlertDialog.Builder(ownerActivity).setItems(shareOptions.toTypedArray()) { _, i ->
|
||||
try {
|
||||
when (i + skip) {
|
||||
0 -> showQRCode(guid)
|
||||
1 -> share2Clipboard(guid)
|
||||
2 -> shareFullContent(guid)
|
||||
3 -> editServer(guid, profile)
|
||||
4 -> removeServer(guid, position)
|
||||
else -> ownerActivity.toast("else")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.TAG, "Error when sharing server", e)
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays QR code for the server configuration
|
||||
* @param guid The server unique identifier
|
||||
*/
|
||||
private fun showQRCode(guid: String) {
|
||||
val ivBinding = ItemQrcodeBinding.inflate(LayoutInflater.from(ownerActivity))
|
||||
ivBinding.ivQcode.setImageBitmap(AngConfigManager.share2QRCode(guid))
|
||||
if (share_method.isNotEmpty()) {
|
||||
ivBinding.ivQcode.contentDescription = share_method[0]
|
||||
} else {
|
||||
ivBinding.ivQcode.contentDescription = "QR Code"
|
||||
}
|
||||
AlertDialog.Builder(ownerActivity).setView(ivBinding.root).show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Shares server configuration to clipboard
|
||||
* @param guid The server unique identifier
|
||||
*/
|
||||
private fun share2Clipboard(guid: String) {
|
||||
if (AngConfigManager.share2Clipboard(ownerActivity, guid) == 0) {
|
||||
ownerActivity.toastSuccess(R.string.toast_success)
|
||||
} else {
|
||||
ownerActivity.toastError(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shares full server configuration content to clipboard
|
||||
* @param guid The server unique identifier
|
||||
*/
|
||||
private fun shareFullContent(guid: String) {
|
||||
ownerActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||
val result = AngConfigManager.shareFullContent2Clipboard(ownerActivity, guid)
|
||||
launch(Dispatchers.Main) {
|
||||
if (result == 0) {
|
||||
ownerActivity.toastSuccess(R.string.toast_success)
|
||||
} else {
|
||||
ownerActivity.toastError(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits server configuration
|
||||
* Opens appropriate editing interface based on configuration type
|
||||
* @param guid The server unique identifier
|
||||
* @param profile The server configuration
|
||||
*/
|
||||
private fun editServer(guid: String, profile: ProfileItem) {
|
||||
val intent = Intent().putExtra("guid", guid)
|
||||
.putExtra("isRunning", mainViewModel.isRunning.value)
|
||||
.putExtra("createConfigType", profile.configType.value)
|
||||
when (profile.configType) {
|
||||
EConfigType.CUSTOM -> {
|
||||
ownerActivity.startActivity(intent.setClass(ownerActivity, ServerCustomConfigActivity::class.java))
|
||||
}
|
||||
|
||||
EConfigType.POLICYGROUP -> {
|
||||
ownerActivity.startActivity(intent.setClass(ownerActivity, ServerGroupActivity::class.java))
|
||||
}
|
||||
|
||||
else -> {
|
||||
ownerActivity.startActivity(intent.setClass(ownerActivity, ServerActivity::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes server configuration
|
||||
* Handles confirmation dialog and related checks
|
||||
* @param guid The server unique identifier
|
||||
* @param position The position in the list
|
||||
*/
|
||||
private fun removeServer(guid: String, position: Int) {
|
||||
if (guid == MmkvManager.getSelectServer()) {
|
||||
ownerActivity.toast(R.string.toast_action_not_allowed)
|
||||
return
|
||||
}
|
||||
|
||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_CONFIRM_REMOVE)) {
|
||||
AlertDialog.Builder(ownerActivity).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
removeServerSub(guid, position)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
//do noting
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
removeServerSub(guid, position)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the actual server removal process
|
||||
* @param guid The server unique identifier
|
||||
* @param position The position in the list
|
||||
*/
|
||||
private fun removeServerSub(guid: String, position: Int) {
|
||||
ownerActivity.mainViewModel.removeServer(guid)
|
||||
adapter.removeServerSub(guid, position)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selected server
|
||||
* Updates UI and restarts service if needed
|
||||
* @param guid The server unique identifier to select
|
||||
*/
|
||||
private fun setSelectServer(guid: String) {
|
||||
val selected = MmkvManager.getSelectServer()
|
||||
if (guid != selected) {
|
||||
MmkvManager.setSelectServer(guid)
|
||||
val fromPosition = mainViewModel.getPosition(selected.orEmpty())
|
||||
val toPosition = mainViewModel.getPosition(guid)
|
||||
adapter.setSelectServer(fromPosition, toPosition)
|
||||
|
||||
if (mainViewModel.isRunning.value == true) {
|
||||
ownerActivity.restartV2Ray()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ActivityAdapterListener : MainAdapterListener {
|
||||
override fun onEdit(guid: String, position: Int) {
|
||||
}
|
||||
|
||||
override fun onShare(url: String) {
|
||||
}
|
||||
|
||||
override fun onRefreshData() {
|
||||
}
|
||||
|
||||
override fun onRemove(guid: String, position: Int) {
|
||||
removeServer(guid, position)
|
||||
}
|
||||
|
||||
override fun onEdit(guid: String, position: Int, profile: ProfileItem) {
|
||||
editServer(guid, profile)
|
||||
}
|
||||
|
||||
override fun onSelectServer(guid: String) {
|
||||
setSelectServer(guid)
|
||||
}
|
||||
|
||||
override fun onShare(guid: String, profile: ProfileItem, position: Int, more: Boolean) {
|
||||
val isCustom = profile.configType == EConfigType.CUSTOM || profile.configType == EConfigType.POLICYGROUP
|
||||
|
||||
val (shareOptions, skip) = if (more) {
|
||||
val options = if (isCustom) share_method_more.asList().takeLast(3) else share_method_more.asList()
|
||||
options to if (isCustom) 2 else 0
|
||||
} else {
|
||||
val options = if (isCustom) share_method.asList().takeLast(1) else share_method.asList()
|
||||
options to if (isCustom) 2 else 0
|
||||
}
|
||||
|
||||
shareServer(guid, profile, position, shareOptions, skip)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
|
||||
interface MainAdapterListener :BaseAdapterListener {
|
||||
|
||||
fun onEdit(guid: String, position: Int, profile: ProfileItem)
|
||||
|
||||
fun onSelectServer(guid: String)
|
||||
|
||||
fun onShare(guid: String, profile: ProfileItem, position: Int, more: Boolean)
|
||||
|
||||
}
|
||||
@@ -1,60 +1,38 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.v2ray.ang.AngApplication.Companion.application
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ItemQrcodeBinding
|
||||
import com.v2ray.ang.databinding.ItemRecyclerFooterBinding
|
||||
import com.v2ray.ang.databinding.ItemRecyclerMainBinding
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.ServersCache
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.toastError
|
||||
import com.v2ray.ang.extension.toastSuccess
|
||||
import com.v2ray.ang.handler.AngConfigManager
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.helper.ItemTouchHelperAdapter
|
||||
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import com.v2ray.ang.viewmodel.MainViewModel
|
||||
import java.util.Collections
|
||||
|
||||
class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>(), ItemTouchHelperAdapter {
|
||||
class MainRecyclerAdapter(
|
||||
private val mainViewModel: MainViewModel,
|
||||
private val adapterListener: MainAdapterListener?
|
||||
) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>(), ItemTouchHelperAdapter {
|
||||
companion object {
|
||||
private const val VIEW_TYPE_ITEM = 1
|
||||
private const val VIEW_TYPE_FOOTER = 2
|
||||
}
|
||||
|
||||
private var mActivity: MainActivity = activity
|
||||
private val share_method: Array<out String> by lazy {
|
||||
mActivity.resources.getStringArray(R.array.share_method)
|
||||
}
|
||||
private val share_method_more: Array<out String> by lazy {
|
||||
mActivity.resources.getStringArray(R.array.share_method_more)
|
||||
}
|
||||
var isRunning = false
|
||||
private val doubleColumnDisplay = MmkvManager.decodeSettingsBool(AppConfig.PREF_DOUBLE_COLUMN_DISPLAY, false)
|
||||
private var data: MutableList<ServersCache> = mutableListOf()
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun setData(newData: MutableList<ServersCache>?, position: Int = -1) {
|
||||
if (android.os.Looper.myLooper() != android.os.Looper.getMainLooper()) {
|
||||
mActivity.runOnUiThread { setData(newData, position) }
|
||||
return
|
||||
}
|
||||
data = newData?.toMutableList() ?: mutableListOf()
|
||||
|
||||
if (position >= 0 && position in data.indices) {
|
||||
@@ -68,9 +46,9 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
|
||||
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
|
||||
if (holder is MainViewHolder) {
|
||||
val context = holder.itemMainBinding.root.context
|
||||
val guid = data[position].guid
|
||||
val profile = data[position].profile
|
||||
val isCustom = profile.configType == EConfigType.CUSTOM || profile.configType == EConfigType.POLICYGROUP
|
||||
|
||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||
|
||||
@@ -83,9 +61,9 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
val aff = MmkvManager.decodeServerAffiliationInfo(guid)
|
||||
holder.itemMainBinding.tvTestResult.text = aff?.getTestDelayString().orEmpty()
|
||||
if ((aff?.testDelayMillis ?: 0L) < 0L) {
|
||||
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPingRed))
|
||||
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(context, R.color.colorPingRed))
|
||||
} else {
|
||||
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPing))
|
||||
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(context, R.color.colorPing))
|
||||
}
|
||||
|
||||
//layoutIndicator
|
||||
@@ -107,11 +85,8 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
holder.itemMainBinding.layoutRemove.visibility = View.GONE
|
||||
holder.itemMainBinding.layoutMore.visibility = View.VISIBLE
|
||||
|
||||
//share method
|
||||
val shareOptions = if (isCustom) share_method_more.asList().takeLast(3) else share_method_more.asList()
|
||||
|
||||
holder.itemMainBinding.layoutMore.setOnClickListener {
|
||||
shareServer(guid, profile, position, shareOptions, if (isCustom) 2 else 0)
|
||||
adapterListener?.onShare(guid, profile, position, true)
|
||||
}
|
||||
} else {
|
||||
holder.itemMainBinding.layoutShare.visibility = View.VISIBLE
|
||||
@@ -119,34 +94,23 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
holder.itemMainBinding.layoutRemove.visibility = View.VISIBLE
|
||||
holder.itemMainBinding.layoutMore.visibility = View.GONE
|
||||
|
||||
//share method
|
||||
val shareOptions = if (isCustom) share_method.asList().takeLast(1) else share_method.asList()
|
||||
|
||||
holder.itemMainBinding.layoutShare.setOnClickListener {
|
||||
shareServer(guid, profile, position, shareOptions, if (isCustom) 2 else 0)
|
||||
adapterListener?.onShare(guid, profile, position, false)
|
||||
}
|
||||
|
||||
holder.itemMainBinding.layoutEdit.setOnClickListener {
|
||||
editServer(guid, profile)
|
||||
adapterListener?.onEdit(guid, position, profile)
|
||||
}
|
||||
holder.itemMainBinding.layoutRemove.setOnClickListener {
|
||||
removeServer(guid, position)
|
||||
adapterListener?.onRemove(guid, position)
|
||||
}
|
||||
}
|
||||
|
||||
holder.itemMainBinding.infoContainer.setOnClickListener {
|
||||
setSelectServer(guid)
|
||||
adapterListener?.onSelectServer(guid)
|
||||
}
|
||||
}
|
||||
// if (holder is FooterViewHolder) {
|
||||
// if (true) {
|
||||
// holder.itemFooterBinding.layoutEdit.visibility = View.INVISIBLE
|
||||
// } else {
|
||||
// holder.itemFooterBinding.layoutEdit.setOnClickListener {
|
||||
// Utils.openUri(mActivity, "${Utils.decode(AppConfig.PromotionUrl)}?t=${System.currentTimeMillis()}")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,135 +142,14 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
*/
|
||||
private fun getSubscriptionRemarks(profile: ProfileItem): String {
|
||||
val subRemarks =
|
||||
if (mActivity.mainViewModel.subscriptionId.isEmpty())
|
||||
if (mainViewModel.subscriptionId.isEmpty())
|
||||
MmkvManager.decodeSubscription(profile.subscriptionId)?.remarks?.firstOrNull()
|
||||
else
|
||||
null
|
||||
return subRemarks?.toString() ?: ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Shares server configuration
|
||||
* Displays a dialog with sharing options and executes the selected action
|
||||
* @param guid The server unique identifier
|
||||
* @param profile The server configuration
|
||||
* @param position The position in the list
|
||||
* @param shareOptions The list of share options
|
||||
* @param skip The number of options to skip
|
||||
*/
|
||||
private fun shareServer(guid: String, profile: ProfileItem, position: Int, shareOptions: List<String>, skip: Int) {
|
||||
AlertDialog.Builder(mActivity).setItems(shareOptions.toTypedArray()) { _, i ->
|
||||
try {
|
||||
when (i + skip) {
|
||||
0 -> showQRCode(guid)
|
||||
1 -> share2Clipboard(guid)
|
||||
2 -> shareFullContent(guid)
|
||||
3 -> editServer(guid, profile)
|
||||
4 -> removeServer(guid, position)
|
||||
else -> mActivity.toast("else")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.TAG, "Error when sharing server", e)
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays QR code for the server configuration
|
||||
* @param guid The server unique identifier
|
||||
*/
|
||||
private fun showQRCode(guid: String) {
|
||||
val ivBinding = ItemQrcodeBinding.inflate(LayoutInflater.from(mActivity))
|
||||
ivBinding.ivQcode.setImageBitmap(AngConfigManager.share2QRCode(guid))
|
||||
if (share_method.isNotEmpty()) {
|
||||
ivBinding.ivQcode.contentDescription = share_method[0]
|
||||
} else {
|
||||
ivBinding.ivQcode.contentDescription = "QR Code"
|
||||
}
|
||||
AlertDialog.Builder(mActivity).setView(ivBinding.root).show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Shares server configuration to clipboard
|
||||
* @param guid The server unique identifier
|
||||
*/
|
||||
private fun share2Clipboard(guid: String) {
|
||||
if (AngConfigManager.share2Clipboard(mActivity, guid) == 0) {
|
||||
mActivity.toastSuccess(R.string.toast_success)
|
||||
} else {
|
||||
mActivity.toastError(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shares full server configuration content to clipboard
|
||||
* @param guid The server unique identifier
|
||||
*/
|
||||
private fun shareFullContent(guid: String) {
|
||||
mActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||
val result = AngConfigManager.shareFullContent2Clipboard(mActivity, guid)
|
||||
launch(Dispatchers.Main) {
|
||||
if (result == 0) {
|
||||
mActivity.toastSuccess(R.string.toast_success)
|
||||
} else {
|
||||
mActivity.toastError(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits server configuration
|
||||
* Opens appropriate editing interface based on configuration type
|
||||
* @param guid The server unique identifier
|
||||
* @param profile The server configuration
|
||||
*/
|
||||
private fun editServer(guid: String, profile: ProfileItem) {
|
||||
val intent = Intent().putExtra("guid", guid)
|
||||
.putExtra("isRunning", isRunning)
|
||||
.putExtra("createConfigType", profile.configType.value)
|
||||
if (profile.configType == EConfigType.CUSTOM) {
|
||||
mActivity.startActivity(intent.setClass(mActivity, ServerCustomConfigActivity::class.java))
|
||||
} else if (profile.configType == EConfigType.POLICYGROUP) {
|
||||
mActivity.startActivity(intent.setClass(mActivity, ServerGroupActivity::class.java))
|
||||
} else {
|
||||
mActivity.startActivity(intent.setClass(mActivity, ServerActivity::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes server configuration
|
||||
* Handles confirmation dialog and related checks
|
||||
* @param guid The server unique identifier
|
||||
* @param position The position in the list
|
||||
*/
|
||||
private fun removeServer(guid: String, position: Int) {
|
||||
if (guid == MmkvManager.getSelectServer()) {
|
||||
application.toast(R.string.toast_action_not_allowed)
|
||||
return
|
||||
}
|
||||
|
||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_CONFIRM_REMOVE)) {
|
||||
AlertDialog.Builder(mActivity).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
removeServerSub(guid, position)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
//do noting
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
removeServerSub(guid, position)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the actual server removal process
|
||||
* @param guid The server unique identifier
|
||||
* @param position The position in the list
|
||||
*/
|
||||
private fun removeServerSub(guid: String, position: Int) {
|
||||
mActivity.mainViewModel.removeServer(guid)
|
||||
fun removeServerSub(guid: String, position: Int) {
|
||||
val idx = data.indexOfFirst { it.guid == guid }
|
||||
if (idx >= 0) {
|
||||
data.removeAt(idx)
|
||||
@@ -315,23 +158,9 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selected server
|
||||
* Updates UI and restarts service if needed
|
||||
* @param guid The server unique identifier to select
|
||||
*/
|
||||
private fun setSelectServer(guid: String) {
|
||||
val selected = MmkvManager.getSelectServer()
|
||||
if (guid != selected) {
|
||||
MmkvManager.setSelectServer(guid)
|
||||
if (!TextUtils.isEmpty(selected)) {
|
||||
notifyItemChanged(mActivity.mainViewModel.getPosition(selected.orEmpty()))
|
||||
}
|
||||
notifyItemChanged(mActivity.mainViewModel.getPosition(guid))
|
||||
if (isRunning) {
|
||||
mActivity.restartV2Ray()
|
||||
}
|
||||
}
|
||||
fun setSelectServer(fromPosition: Int, toPosition: Int) {
|
||||
notifyItemChanged(fromPosition)
|
||||
notifyItemChanged(toPosition)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
|
||||
@@ -369,7 +198,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
BaseViewHolder(itemFooterBinding.root)
|
||||
|
||||
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
|
||||
mActivity.mainViewModel.swapServer(fromPosition, toPosition)
|
||||
mainViewModel.swapServer(fromPosition, toPosition)
|
||||
if (fromPosition < data.size && toPosition < data.size) {
|
||||
Collections.swap(data, fromPosition, toPosition)
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
appsAll = apps
|
||||
adapter = PerAppProxyAdapter(this@PerAppProxyActivity, apps, viewModel)
|
||||
adapter = PerAppProxyAdapter(apps, viewModel)
|
||||
binding.recyclerView.adapter = adapter
|
||||
|
||||
} catch (e: Exception) {
|
||||
@@ -292,7 +292,7 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
adapter = PerAppProxyAdapter(this, apps, adapter?.viewModel ?: viewModel)
|
||||
adapter = PerAppProxyAdapter(apps, adapter?.viewModel ?: viewModel)
|
||||
binding.recyclerView.adapter = adapter
|
||||
refreshData()
|
||||
return true
|
||||
|
||||
@@ -8,8 +8,10 @@ import com.v2ray.ang.databinding.ItemRecyclerBypassListBinding
|
||||
import com.v2ray.ang.dto.AppInfo
|
||||
import com.v2ray.ang.viewmodel.PerAppProxyViewModel
|
||||
|
||||
class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, val viewModel: PerAppProxyViewModel) :
|
||||
RecyclerView.Adapter<PerAppProxyAdapter.BaseViewHolder>() {
|
||||
class PerAppProxyAdapter(
|
||||
val apps: List<AppInfo>,
|
||||
val viewModel: PerAppProxyViewModel
|
||||
) :RecyclerView.Adapter<PerAppProxyAdapter.BaseViewHolder>() {
|
||||
|
||||
companion object {
|
||||
private const val VIEW_TYPE_HEADER = 0
|
||||
@@ -37,6 +39,7 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, va
|
||||
)
|
||||
BaseViewHolder(view)
|
||||
}
|
||||
|
||||
else -> AppViewHolder(ItemRecyclerBypassListBinding.inflate(LayoutInflater.from(ctx), parent, false))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.util.QRCodeDecoder
|
||||
import io.github.g00fy2.quickie.QRResult
|
||||
import io.github.g00fy2.quickie.ScanCustomCode
|
||||
import io.github.g00fy2.quickie.config.BarcodeFormat
|
||||
import io.github.g00fy2.quickie.config.ScannerConfig
|
||||
|
||||
class ScannerActivity : BaseActivity() {
|
||||
@@ -73,6 +74,7 @@ class ScannerActivity : BaseActivity() {
|
||||
setHapticSuccessFeedback(true) // enable (default) or disable haptic feedback when a barcode was detected
|
||||
setShowTorchToggle(true) // show or hide (default) torch/flashlight toggle button
|
||||
setShowCloseButton(true) // show or hide (default) close button
|
||||
setBarcodeFormats(listOf(BarcodeFormat.QR_CODE))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -632,7 +632,7 @@ class ServerActivity : BaseActivity() {
|
||||
finish()
|
||||
}
|
||||
} else {
|
||||
application.toast(R.string.toast_action_not_allowed)
|
||||
toast(R.string.toast_action_not_allowed)
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -65,9 +65,9 @@ object QRCodeDecoder {
|
||||
val qrReader = QRCodeReader()
|
||||
|
||||
try {
|
||||
qrReader.decode(BinaryBitmap(GlobalHistogramBinarizer(source)), mapOf(DecodeHintType.TRY_HARDER to true)).text
|
||||
qrReader.decode(BinaryBitmap(GlobalHistogramBinarizer(source)), HINTS).text
|
||||
} catch (e: NotFoundException) {
|
||||
qrReader.decode(BinaryBitmap(GlobalHistogramBinarizer(source.invert())), mapOf(DecodeHintType.TRY_HARDER to true)).text
|
||||
qrReader.decode(BinaryBitmap(GlobalHistogramBinarizer(source.invert())), HINTS).text
|
||||
}
|
||||
}.getOrNull()
|
||||
}
|
||||
@@ -97,27 +97,9 @@ object QRCodeDecoder {
|
||||
}
|
||||
|
||||
init {
|
||||
val allFormats: List<BarcodeFormat> = arrayListOf(
|
||||
BarcodeFormat.AZTEC,
|
||||
BarcodeFormat.CODABAR,
|
||||
BarcodeFormat.CODE_39,
|
||||
BarcodeFormat.CODE_93,
|
||||
BarcodeFormat.CODE_128,
|
||||
BarcodeFormat.DATA_MATRIX,
|
||||
BarcodeFormat.EAN_8,
|
||||
BarcodeFormat.EAN_13,
|
||||
BarcodeFormat.ITF,
|
||||
BarcodeFormat.MAXICODE,
|
||||
BarcodeFormat.PDF_417,
|
||||
BarcodeFormat.QR_CODE,
|
||||
BarcodeFormat.RSS_14,
|
||||
BarcodeFormat.RSS_EXPANDED,
|
||||
BarcodeFormat.UPC_A,
|
||||
BarcodeFormat.UPC_E,
|
||||
BarcodeFormat.UPC_EAN_EXTENSION
|
||||
)
|
||||
HINTS[DecodeHintType.TRY_HARDER] = BarcodeFormat.QR_CODE
|
||||
HINTS[DecodeHintType.POSSIBLE_FORMATS] = allFormats
|
||||
HINTS[DecodeHintType.CHARACTER_SET] = Charsets.UTF_8
|
||||
// Keep decoding hints focused on QR codes and enable TRY_HARDER + UTF-8 charset for better success rate.
|
||||
HINTS[DecodeHintType.TRY_HARDER] = true
|
||||
HINTS[DecodeHintType.POSSIBLE_FORMATS] = listOf(BarcodeFormat.QR_CODE)
|
||||
HINTS[DecodeHintType.CHARACTER_SET] = Charsets.UTF_8.name()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user