Compare commits

..

5 Commits

Author SHA1 Message Date
2dust ac043da8a2 up 2.0.6 2026-01-24 10:20:18 +08:00
2dust a192fec127 Update AndroidLibXrayLite 2026-01-24 10:19:46 +08:00
2dust b0b09273cf Restrict scanner and decoder to QR codes only 2026-01-23 19:41:21 +08:00
2dust 01a943b7ea Refactor main list adapter with listener interface 2026-01-23 17:09:35 +08:00
2dust 06a3738b59 Remove activity parameter from PerAppProxyAdapter 2026-01-22 18:34:05 +08:00
10 changed files with 263 additions and 228 deletions
+2 -2
View File
@@ -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()
}
}