左晓为主开发手持机充值管理机
zuoxiao
6 天以前 c3edd4a3122b711bc6a0bfef56b6ff6bf5ae03e2
feat(generallibrary): 优化用户开卡流程并添加支付方式功能

- 重构了 NewCard2Activity 中的支付方式逻辑,支持动态添加支付方式
- 新增了获取支付方式列表的接口和相关数据类
- 优化了 UI 样式,调整了部分控件的布局和样式- 添加了卡片初始化功能,包括修改扇区密码
- 修复了一些已知的 bug,提高了系统的稳定性
11个文件已修改
4个文件已添加
593 ■■■■ 已修改文件
README.md 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
baselibrary/src/main/java/com/dayu/baselibrary/tools/nfc/NativeNfcWriteHelper.java 137 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
baselibrary/src/main/java/com/dayu/baselibrary/tools/nfc/NfcWriteAdapter.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generallibrary/src/main/cpp/general-native-lib.cpp 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generallibrary/src/main/java/com/dayu/general/activity/NewCard2Activity.kt 183 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generallibrary/src/main/java/com/dayu/general/activity/SearchUserListActivity.kt 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
generallibrary/src/main/java/com/dayu/general/tool/GeBaseHelper.kt 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
generallibrary/src/main/java/com/dayu/general/tool/NfcWreatHelper.kt 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generallibrary/src/main/res/drawable/guashi.xml 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generallibrary/src/main/res/drawable/shape_click_tip_bg.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generallibrary/src/main/res/drawable/shape_status_bg.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generallibrary/src/main/res/drawable/xiaoka.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generallibrary/src/main/res/layout/activity_new_card_1_ge.xml 110 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generallibrary/src/main/res/layout/fragment_card.xml 47 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generallibrary/src/main/res/values/dimens.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md
@@ -366,6 +366,11 @@
   - 敏感数据需要加密存储
   - 注意用户数据的安全处理
4. 异常处理
   - 所有try catch块中必须使用`CrashReport.postCatchedException(e)`上报异常
   - 确保异常信息被正确记录和上报
   - 避免异常信息泄露敏感数据
## 贡献指南
1. Fork 项目
baselibrary/src/main/java/com/dayu/baselibrary/tools/nfc/NativeNfcWriteHelper.java
@@ -12,6 +12,7 @@
import com.tencent.bugly.crashreport.CrashReport;
import java.io.IOException;
import java.util.List;
/**
 * author: zuo
@@ -249,12 +250,120 @@
    /**
     * 修改密码
     *
     * @param a 书写的扇区
     *          //     * @param callback 返回监听
     * @param         书写的扇区
     * @param passWord 密码
     * @return
     */
    public boolean changePasword( List<byte[]> passWord, boolean isConnect, boolean isClose, NFCCallBack callBack) {
        if (passWord == null || passWord.size()!=16) {
            if (callBack != null) {
                callBack.isSusses(false, "密码数组为空或长度不为16");
            }
            return false;
        }
        try {
            MifareClassic mfc = MifareClassic.get(tag);
            if (mfc == null) {
                if (callBack != null) {
                    callBack.isSusses(false, "获取MifareClassic实例失败");
                }
                return false;
            }
            try {
                // 连接NFC
                if (isConnect) {
                    mfc.connect();
                }
                for (int i = 0; i < passWord.size(); i++) {
                    byte [] passWordItem = passWord.get(i);
                    // 验证扇区密码
                    boolean isAuthenticated = false;
                    // 尝试使用默认密钥验证
                    if (mfc.authenticateSectorWithKeyA(i, defauleKey)) {
                        isAuthenticated = true;
                    } else if (mfc.authenticateSectorWithKeyA(i, listA_PS.get(i))) {
                        continue;
                    }
                    if (!isAuthenticated) {
                        if (callBack != null) {
                            callBack.isSusses(false, "扇区" + i + "密码验证失败");
                        }
                        return false;
                    }
                    // 准备数据
                    byte[] data = new byte[16];
                    if (passWordItem.length == 16) {
                        // 如果是完整的16字节数据,直接使用
                        System.arraycopy(passWordItem, 0, data, 0, 16);
                    } else if (passWordItem.length == 6) {
                        // 如果只是6字节密钥,构建完整的控制块
                        // 将密码转换为keyA
                        System.arraycopy(passWordItem, 0, data, 0, 6);
                        // 输入控制位
                        data[6] = (byte) 0xFF;
                        data[7] = (byte) 0x07;
                        data[8] = (byte) 0x80;
                        data[9] = (byte) 0x69;
                        // 将密码作为KeyB
                        System.arraycopy(passWordItem, 0, data, 10, 6);
                    } else {
                        if (callBack != null) {
                            callBack.isSusses(false, "扇区" + i + "密码长度不正确,应为6字节或16字节");
                        }
                        return false;
                    }
                    // 获取写入位置
                    int bIndex = mfc.sectorToBlock(i);
                    int bCount = mfc.getBlockCountInSector(i);
                    try {
                        // 写到扇区的最后一个块(控制块)
                        mfc.writeBlock(bIndex + bCount - 1, data);
                    } catch (IOException e) {
                        if (callBack != null) {
                            callBack.isSusses(false, "扇区" + i + "写入控制块失败: " + e.getMessage());
                        }
                        return false;
                    }
                }
                if (callBack != null) {
                    callBack.isSusses(true, "所有扇区密码修改成功");
                }
                return true;
            } finally {
                try {
                    if (isClose) {
                        mfc.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            CrashReport.postCatchedException(e);
            if (callBack != null) {
                callBack.isSusses(false, "修改密码时发生异常: " + e.getMessage());
            }
        }
        return false;
    }
    /**
     * 修改密码
     *
     * @param a  扇区
     * @param mfc MifareClassic实例
     * @return
     */
    @Override
    public boolean changePasword(int a, MifareClassic mfc) {
        byte[] data = new byte[16];
        if (null != mfc) {
            try {
@@ -277,26 +386,20 @@
                int bCount = mfc.getBlockCountInSector(a);
                //写到扇区的最后一个块
                mfc.writeBlock(bIndex + bCount - 1, data);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        return false;
    }
    public boolean changePasword(int a,byte[] passWord, MifareClassic mfc) {
        try {
        } catch (Exception e) {
            e.printStackTrace();
            CrashReport.postCatchedException(e);
        }
        return false;
    }
        }
        return false;
    }
    /**
     * 初始化卡
baselibrary/src/main/java/com/dayu/baselibrary/tools/nfc/NfcWriteAdapter.java
@@ -8,6 +8,8 @@
import com.dayu.baselibrary.bean.BaseUserCardCard;
import com.dayu.baselibrary.utils.ModelUtils;
import java.util.List;
/**
 * author: zuo
 * Date: 2024-09-26
@@ -79,4 +81,12 @@
        }
        return false;
    }
    public boolean changePasword( List<byte[]> passWord, boolean isConnect, boolean isClose, NFCCallBack callBack) {
        switch (BaseNfcActivity.adapterType) {
            case ModelUtils.defaultType:
                return nativeNfcWriteHelper.changePasword(passWord,isConnect, isClose,callBack);
        }
        return false;
    }
}
generallibrary/src/main/cpp/general-native-lib.cpp
@@ -39,7 +39,8 @@
    0x8E, 0x93, 0xB5, 0x44, 0x8D, 0x42, 0x22, 0x84, 0x95, 0x33, 0x22, 0x93,
    0x42, 0x82, 0xA3, 0x35, 0x91, 0x33, 0x13, 0x93, 0x71, 0x21, 0x01, 0x71,
    0x37, 0x9F, 0xA6, 0x68, 0x92, 0x86, 0x46, 0x72, 0x43, 0x62, 0x12, 0x52,
    0x5D, 0x85, 0x93, 0x86, 0x82, 0x46, 0x31, 0x86, 0x57, 0x48, 0x16, 0x88,
    // 扇区14密码 (基于扇区13的XOR密钥重新计算为27562CCFE9F7)
    0x5D, 0xE5, 0x93, 0x86, 0x82, 0x46, 0x31, 0x86, 0x57, 0x48, 0x16, 0x88,
    0x97, 0x73, 0xB5, 0x47, 0x95, 0x55, 0x36, 0x69, 0x49, 0x58, 0x18, 0x6A,
    0xEA, 0x46, 0x84, 0x93, 0x82, 0x19, 0x29, 0x91, 0x31, 0x1C, 0x0C, 0x7D
};
@@ -95,19 +96,19 @@
        return nullptr;
    }
//    __android_log_print(ANDROID_LOG_INFO, "GeBaseHelper", "Decrypting sector %d", sector);
//    __android_log_print(ANDROID_LOG_INFO, "GeBaseHelper", "Encrypted data: %02X %02X %02X %02X %02X %02X",
//                       encrypted_data[0], encrypted_data[1], encrypted_data[2],
//                       encrypted_data[3], encrypted_data[4], encrypted_data[5]);
//    __android_log_print(ANDROID_LOG_INFO, "GeBaseHelper", "Using XOR key: %02X %02X %02X %02X %02X %02X",
//                       XOR_KEYS[sector][0], XOR_KEYS[sector][1], XOR_KEYS[sector][2],
//                       XOR_KEYS[sector][3], XOR_KEYS[sector][4], XOR_KEYS[sector][5]);
    __android_log_print(ANDROID_LOG_INFO, "GeBaseHelper", "Decrypting sector %d", sector);
    __android_log_print(ANDROID_LOG_INFO, "GeBaseHelper", "Encrypted data: %02X %02X %02X %02X %02X %02X",
                       encrypted_data[0], encrypted_data[1], encrypted_data[2],
                       encrypted_data[3], encrypted_data[4], encrypted_data[5]);
    __android_log_print(ANDROID_LOG_INFO, "GeBaseHelper", "Using XOR key: %02X %02X %02X %02X %02X %02X",
                       XOR_KEYS[sector][0], XOR_KEYS[sector][1], XOR_KEYS[sector][2],
                       XOR_KEYS[sector][3], XOR_KEYS[sector][4], XOR_KEYS[sector][5]);
    unsigned char decrypted[6];
    for (int i = 0; i < 6; i++) {
        decrypted[i] = encrypted_data[i] ^ XOR_KEYS[sector][i];
//        __android_log_print(ANDROID_LOG_INFO, "GeBaseHelper", "Byte %d: %02X ^ %02X = %02X",
//                          i, encrypted_data[i], XOR_KEYS[sector][i], decrypted[i]);
        __android_log_print(ANDROID_LOG_INFO, "GeBaseHelper", "Byte %d: %02X ^ %02X = %02X",
                          i, encrypted_data[i], XOR_KEYS[sector][i], decrypted[i]);
    }
    std::string result;
@@ -118,7 +119,7 @@
        result += hex;
    }
//    __android_log_print(ANDROID_LOG_INFO, "GeBaseHelper", "Decrypted result: %s", result.c_str());
    __android_log_print(ANDROID_LOG_INFO, "GeBaseHelper", "Decrypted result: %s", result.c_str());
    return env->NewStringUTF(result.c_str());
}
@@ -239,7 +240,7 @@
        // 解密并转换每个扇区密钥
        for (size_t i = 0; i < numKeys; i++) {
//            __android_log_print(ANDROID_LOG_DEBUG, "M1Card", "处理扇区 %zu 的密钥", i);
           __android_log_print(ANDROID_LOG_DEBUG, "M1Card", "处理扇区 %zu 的密钥", i);
            
            // 确保不会越界访问
            if (i * 12 + 6 > sizeof(ENCRYPTED_SECTOR_KEYS)) {
@@ -251,7 +252,7 @@
            jstring decrypted_str = decrypt_key(env, ENCRYPTED_SECTOR_KEYS + (i * 12), 6);
            
            if (!decrypted_str) {
//                __android_log_print(ANDROID_LOG_ERROR, "M1Card", "Failed to decrypt key for sector %zu", i);
               __android_log_print(ANDROID_LOG_ERROR, "M1Card", "Failed to decrypt key for sector %zu", i);
                continue;
            }
            
generallibrary/src/main/java/com/dayu/general/activity/NewCard2Activity.kt
@@ -5,9 +5,11 @@
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import android.widget.RadioButton
import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import com.dayu.baselibrary.net.subscribers.SubscriberListener
import com.dayu.baselibrary.tools.nfc.NFCCallBack
import com.dayu.baselibrary.view.TitleBar.ClickType_LEFT_IMAGE
import com.dayu.general.BaseApplication
import com.dayu.general.R
@@ -17,7 +19,10 @@
import com.dayu.general.net.ApiManager
import com.dayu.general.net.BaseResponse
import com.dayu.general.tool.NfcReadHelper
import com.dayu.general.tool.NfcWreatHelper
import com.tencent.bugly.crashreport.CrashReport
import kotlinx.coroutines.launch
import android.util.TypedValue
/**
 * Description: 用户开卡界面(同步修改白卡密码)
@@ -31,6 +36,12 @@
    // 支付方式
    private var paymentMethod: String = "现金"
    // 支付方式ID
    private var paymentId: Long = 0
    // 支付方式列表
    private var paymentMethodList: List<PaymentMethod> = listOf()
    // 卡物理ID
    private var cardPhysicalId: String = ""
@@ -41,12 +52,31 @@
        private const val TAG = "NewCard2Activity"
    }
    // 支付方式数据类
    data class PaymentMethod(
        val id: Long,
        val name: String,
        val remarks: String,
        val deleted: Int
    )
    // 支付方式接口返回数据类
    data class PaymentMethodResponse(
        val itemTotal: Any?,
        val obj: List<PaymentMethod>,
        val pageCurr: Any?,
        val pageSize: Any?,
        val pageTotal: Any?
    )
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityNewCard1GeBinding.inflate(layoutInflater)
        setContentView(binding.root)
        initView()
        // 获取支付方式
        getPaymentMethods()
        initListener()
    }
@@ -75,6 +105,88 @@
        // 设置金额输入限制为两位小数
        binding.newCardRechargeAmount.addTextChangedListener(createDecimalTextWatcher())
        binding.newCardCardFee.addTextChangedListener(createDecimalTextWatcher())
    }
    /**
     * 获取支付方式列表
     */
    private fun getPaymentMethods() {
        ApiManager.getInstance().requestGetLoading(
            this,
            "sell/paymentmethod/get",
            PaymentMethodResponse::class.java,
            null,
            object : SubscriberListener<BaseResponse<PaymentMethodResponse>>() {
                override fun onNext(response: BaseResponse<PaymentMethodResponse>) {
                    if (response.success) {
                        // 获取支付方式列表
                        val paymentMethods = response.content?.obj ?: listOf()
                        if (paymentMethods.isNotEmpty()) {
                            paymentMethodList = paymentMethods
                            // 更新支付方式显示
                            updatePaymentMethodRadioGroup()
                        }
                    } else {
                        Toast.makeText(
                            this@NewCard2Activity,
                            "获取支付方式失败: ${response.msg}",
                            Toast.LENGTH_SHORT
                        ).show()
                    }
                }
                override fun onError(e: Throwable?) {
                    super.onError(e)
                    Toast.makeText(
                        this@NewCard2Activity,
                        "获取支付方式失败: ${e?.message ?: "网络异常"}",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
        )
    }
    /**
     * 更新支付方式RadioGroup
     */
    private fun updatePaymentMethodRadioGroup() {
        // 清空原有RadioButton
        binding.newCardPaymentMethod.removeAllViews()
        // 动态添加RadioButton
        paymentMethodList.forEachIndexed { index, method ->
            val radioButton = RadioButton(this)
            radioButton.id = View.generateViewId() // 生成唯一ID
            radioButton.layoutParams = android.widget.LinearLayout.LayoutParams(
                0,
                resources.getDimensionPixelSize(R.dimen.dimen_40),
                1.0f
            )
            // 如果不是最后一个按钮,添加右边距
            if (index < paymentMethodList.size - 1) {
                (radioButton.layoutParams as android.widget.LinearLayout.LayoutParams).rightMargin =
                    resources.getDimensionPixelSize(R.dimen.dimen_15)
            }
            radioButton.text = method.name
            radioButton.background = resources.getDrawable(R.drawable.radio_selector)
            radioButton.buttonDrawable = null
            radioButton.gravity = android.view.Gravity.CENTER
            radioButton.setTextColor(resources.getColorStateList(R.color.radio_button_text_color))
            radioButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f)
            // 添加到RadioGroup
            binding.newCardPaymentMethod.addView(radioButton)
            // 默认选中第一个
            if (index == 0) {
                radioButton.isChecked = true
                paymentMethod = method.name
                paymentId = method.id
            }
        }
    }
    /**
@@ -109,11 +221,14 @@
    private fun initListener() {
        // 设置支付方式选择监听
        binding.newCardPaymentMethod.setOnCheckedChangeListener { group, checkedId ->
            paymentMethod = when (checkedId) {
                R.id.newCard_cashPayment -> "现金"
                R.id.newCard_posPayment -> "POS机"
                R.id.newCard_bankTransfer -> "银行转账"
                else -> "现金"
            // 根据选中的ID获取支付方式
            for (i in 0 until group.childCount) {
                val radioButton = group.getChildAt(i) as RadioButton
                if (radioButton.id == checkedId) {
                    paymentMethod = radioButton.text.toString()
                    paymentId = paymentMethodList[i].id
                    break
                }
            }
        }
@@ -142,14 +257,6 @@
        val formattedRechargeAmount = String.format("%.2f", rechargeAmount)
        val formattedCardFee = String.format("%.2f", cardFee)
        // 获取支付方式ID
        val paymentId = when (paymentMethod) {
            "现金" -> 1
            "POS机" -> 2
            "银行转账" -> 3
            else -> 1
        }
        val remark = binding.newCardRemark.text.toString()
        // 构建请求参数
@@ -158,7 +265,7 @@
        params["clientNum"] = binding.newCardFarmerCode.text.toString() // 农户编号
        params["cardCost"] = (cardFee * 100).toInt() // 购卡金额(工本费)转为分
        params["amount"] = (rechargeAmount * 100).toInt() // 充值金额转为分
        params["paymentId"] = paymentId // 支付方式
        params["paymentId"] = paymentId // 支付方式ID
        params["remarks"] = remark // 备注
        params["operator"] = BaseApplication.userId // 操作人ID
@@ -179,7 +286,7 @@
                            farmerCode = binding.newCardFarmerCode.text.toString(),
                            cardFee = cardFee,
                            remark = binding.newCardRemark.text.toString(),
                            paymentMethod = paymentId,
                            paymentMethod = paymentId.toInt(),
                            isReported = true,
                            isCardWritten = true
                        )
@@ -187,12 +294,22 @@
                        // 使用协程在后台线程中保存数据
                        lifecycleScope.launch {
                            try {
                                BaseDaoSingleton.getInstance(this@NewCard2Activity).cardRegistrationDao().insert(cardRegistration)
                                Toast.makeText(this@NewCard2Activity, "开卡成功", Toast.LENGTH_SHORT).show()
                                BaseDaoSingleton.getInstance(this@NewCard2Activity)
                                    .cardRegistrationDao().insert(cardRegistration)
                                Toast.makeText(
                                    this@NewCard2Activity,
                                    "开卡成功",
                                    Toast.LENGTH_SHORT
                                ).show()
                                setResult(RESULT_OK)
                                finish()
                            } catch (e: Exception) {
                                Toast.makeText(this@NewCard2Activity, "保存开卡信息失败: ${e.message}", Toast.LENGTH_SHORT).show()
                                CrashReport.postCatchedException(e)
                                Toast.makeText(
                                    this@NewCard2Activity,
                                    "保存开卡信息失败: ${e.message}",
                                    Toast.LENGTH_SHORT
                                ).show()
                            }
                        }
                    } else {
@@ -231,8 +348,17 @@
    }
    override fun onNfcBack(intent: Intent) {
        var cardNumber = NfcReadHelper.getInstance(intent, this).getCardNumber()
        var cardNumber = NfcReadHelper.getInstance(intent, this).getCardNumberNoClose()
        if (!cardNumber.isEmpty()) {
            try {
                // 创建密钥列表的副本,避免ConcurrentModificationException
                val keyList = ArrayList(NfcReadHelper.getInstance(intent, this).getKeyList())
                NfcWreatHelper.getInstance(intent, this).changePS(keyList, false, true, object :
                    NFCCallBack {
                    override fun isSusses(flag: Boolean, msg: String?) {
                        if (flag) {
            // 保存卡物理ID
            cardPhysicalId = cardNumber
            // 更新UI
@@ -241,13 +367,26 @@
            binding.nfcContainer.visibility = View.GONE
            binding.centerScroll.visibility = View.VISIBLE
            binding.newCardRegistBtn.visibility = View.VISIBLE
                        } else {
                            // 密码修改失败,处理错误情况
                            Toast.makeText(
                                this@NewCard2Activity,
                                "卡片初始化失败:" + msg,
                                Toast.LENGTH_LONG
                            )
                                .show()
        }
    }
                })
            } catch (e: Exception) {
                // 处理异常情况
                CrashReport.postCatchedException(e)
                e.printStackTrace()
            }
        }
    }
}
generallibrary/src/main/java/com/dayu/general/activity/SearchUserListActivity.kt
@@ -160,7 +160,7 @@
        // 使用正确的类型参数
        ApiManager.getInstance().requestGetLoading(
            this,
            "sell/client/get",
            "terminal/client/getTermClients",
            SearchUserResult::class.java,
            map,
            object : SubscriberListener<BaseResponse<SearchUserResult>>() {
generallibrary/src/main/java/com/dayu/general/tool/GeBaseHelper.kt
@@ -19,6 +19,7 @@
    private external fun getM1SectorKeySecure(context: Context, sectorIndex: Int): String
    init {
        if(listA_PS.isEmpty()){
        try {
            // 获取所有扇区密钥
            val allKeys: String = getM1SectorKeySecure(context, 0)
@@ -38,8 +39,9 @@
            defauleKey = HexUtil.hexToByteArray("FFFFFFFFFFFF")
        }
    }
    }
    fun getKeyList(): List<ByteArray> {
        return listKeyA
        return listA_PS
    }
}
generallibrary/src/main/java/com/dayu/general/tool/NfcWreatHelper.kt
@@ -6,7 +6,8 @@
import com.dayu.baselibrary.tools.nfc.NfcWriteAdapter
import com.dayu.general.bean.card.UserCard
class NfcWreatHelper private constructor(intent: Intent, activity: Activity) : GeBaseHelper(activity) {
class NfcWreatHelper private constructor(intent: Intent, activity: Activity) :
    GeBaseHelper(activity) {
    private val adapter: NfcWriteAdapter = NfcWriteAdapter(intent, activity)
@@ -59,5 +60,14 @@
        return false
    }
    fun changePS( ps: List<ByteArray>, isConnect: Boolean, isClose: Boolean,nfcCallBack: NFCCallBack): Boolean {
        try {
            return adapter.changePasword( ps, isConnect, isClose,nfcCallBack)
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
        return false
    }
}
generallibrary/src/main/res/drawable/guashi.xml
New file
@@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportWidth="1024"
    android:viewportHeight="1024">
  <path
      android:pathData="M343.9,215m0,29.4l0,6.4q0,29.4 -29.4,29.4l-71.7,0q-29.4,0 -29.4,-29.4l0,-6.4q0,-29.4 29.4,-29.4l71.7,0q29.4,0 29.4,29.4Z"
      android:fillColor="#009ad6"/>
  <path
      android:pathData="M834.1,759.5m0,32.4l0,0.4q0,32.4 -32.4,32.4l-46.1,0q-32.4,0 -32.4,-32.4l0,-0.4q0,-32.4 32.4,-32.4l46.1,0q32.4,0 32.4,32.4Z"
      android:fillColor="#009ad6"/>
  <path
      android:pathData="M113.5,377.2m0,-30.7l0,-3.8q0,-30.7 30.7,-30.7l726.2,0q30.7,0 30.7,30.7l0,3.8q0,30.7 -30.7,30.7l-726.2,0q-30.7,0 -30.7,-30.7Z"
      android:fillColor="#009ad6"/>
  <path
      android:pathData="M505.6,867.8H175.4A93,93 0,0 1,85.3 771.8V203.1a93,93 0,0 1,89.6 -96h660.5a93,93 0,0 1,89.6 96v272.2a32.4,32.4 0,0 1,-64.9 0V203.1a28.6,28.6 0,0 0,-24.7 -32.4H175.4a28.6,28.6 0,0 0,-24.3 30.7v570.5a28.6,28.6 0,0 0,24.3 30.7h330.2a32.9,32.9 0,0 1,0 65.3z"
      android:fillColor="#009ad6"/>
  <path
      android:pathData="M887.5,922.9h-218a51.2,51.2 0,0 1,-50.8 -50.8v-160a50.8,50.8 0,0 1,50.8 -50.8h218a50.8,50.8 0,0 1,51.2 50.8v160a51.2,51.2 0,0 1,-51.2 50.8zM682.7,857.6h189V725.3H682.7z"
      android:fillColor="#009ad6"/>
  <path
      android:pathData="M845.7,689.5a32.9,32.9 0,0 1,-32.9 -32.9c0,-58 -19.2,-58 -34.1,-58s-34.6,0 -34.6,58a32.9,32.9 0,1 1,-65.3 0c0,-77.2 37.1,-123.3 99.8,-123.3 37.1,0 99.4,15.8 99.4,123.3a32.9,32.9 0,0 1,-32.4 32.9z"
      android:fillColor="#009ad6"/>
</vector>
generallibrary/src/main/res/drawable/shape_click_tip_bg.xml
New file
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="10dp" />
    <stroke
        android:width="1dp"
        android:color="@color/colorPrimary" />
    <solid android:color="#EEFFFFFF" />
</shape>
generallibrary/src/main/res/drawable/shape_status_bg.xml
New file
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="12dp" />
    <solid android:color="@color/colorPrimary" />
</shape>
generallibrary/src/main/res/drawable/xiaoka.xml
New file
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportWidth="1024"
    android:viewportHeight="1024">
  <path
      android:pathData="M532.5,726L196.6,726c-2,-2.6 -3.6,-6.7 -3.1,-10.8L193.5,466.4h599L792.6,409.6h-599L193.5,280.6c-0.5,-4.1 1,-8.2 3.1,-10.8h612.9c2,2.6 3.6,6.7 3.1,10.8v218.1c0.5,15.4 13.3,28.2 29.2,27.6 16.4,0.5 29.2,-12.3 29.2,-27.6L870.9,280.6c0,-36.9 -25.6,-69.1 -61.4,-69.1L196.6,211.5c-35.8,0 -61.4,32.3 -61.4,69.1v434.7c0,36.9 25.6,69.1 61.4,69.1L532.5,784.4c15.9,0 28.7,-12.8 29.2,-29.2 -0.5,-15.4 -13.3,-28.7 -29.2,-29.2zM890.4,575.5l-34.8,-34.8 -104.4,104.4 -104.4,-104.4 -34.8,34.8 104.4,104.4 -104.4,104.4 34.8,34.8 104.4,-104.4 104.4,104.4 34.8,-34.8 -104.4,-104.4 104.4,-104.4z"
      android:fillColor="#009ad6"/>
</vector>
generallibrary/src/main/res/layout/activity_new_card_1_ge.xml
@@ -24,7 +24,7 @@
        android:layout_marginTop="24dp"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="24dp"
        android:visibility="gone"
        android:visibility="visible"
        app:cardBackgroundColor="@android:color/white"
        app:cardCornerRadius="12dp"
        app:cardElevation="4dp"
@@ -54,9 +54,10 @@
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:text="(将获取物理卡卡号)"
                android:textColor="#999999"
                android:text="将进行初始化卡片\n耗时较长请不要移动卡片"
                android:textColor="@color/base_blue_bg"
                android:textSize="18sp"
                android:layout_marginTop="20dp"
                android:textStyle="bold" />
            <ImageView
@@ -84,7 +85,7 @@
        android:layout_height="0dp"
        android:fillViewport="true"
        android:scrollbars="none"
        android:visibility="visible"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/titleBar">
@@ -246,24 +247,38 @@
                        <TextView
                            android:layout_width="105dp"
                            android:layout_height="wrap_content"
                            android:text="工本费:"
                            android:text="充值金额:"
                            android:textColor="#666666"
                            android:textSize="@dimen/new_card_size" />
                        <LinearLayout
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:background="#FFFFFF"
                            android:orientation="vertical">
                            android:background="@drawable/edit_text_bg_selector"
                            android:orientation="horizontal"
                            android:gravity="center_vertical"
                            android:paddingStart="10dp"
                            android:paddingEnd="10dp"
                            android:paddingTop="8dp"
                            android:paddingBottom="8dp">
                            <EditText
                                android:id="@+id/newCard_rechargeAmount"
                                android:layout_width="match_parent"
                                android:layout_width="0dp"
                                android:layout_weight="1"
                                android:layout_height="wrap_content"
                                android:background="@null"
                                android:hint="请输入工本费(选填)"
                                android:hint="请输入充值金额(选填)"
                                android:inputType="numberDecimal"
                                android:textColor="#333333"
                                android:textColorHint="#BBBBBB"
                                android:textSize="@dimen/new_card_size" />
                            <TextView
                                android:layout_width="wrap_content"
                                android:layout_height="wrap_content"
                                android:text="元"
                                android:textColor="#666666"
                                android:textSize="@dimen/new_card_size" />
                        </LinearLayout>
                    </LinearLayout>
@@ -278,28 +293,41 @@
                        <TextView
                            android:layout_width="105dp"
                            android:layout_height="wrap_content"
                            android:text="充值金额:"
                            android:text="工本费:"
                            android:textColor="#666666"
                            android:textSize="@dimen/new_card_size" />
                        <LinearLayout
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:background="#FFFFFF"
                            android:orientation="vertical">
                            android:background="@drawable/edit_text_bg_selector"
                            android:orientation="horizontal"
                            android:gravity="center_vertical"
                            android:paddingStart="10dp"
                            android:paddingEnd="10dp"
                            android:paddingTop="8dp"
                            android:paddingBottom="8dp">
                            <EditText
                                android:id="@+id/newCard_cardFee"
                                android:layout_width="match_parent"
                                android:layout_width="0dp"
                                android:layout_weight="1"
                                android:layout_height="wrap_content"
                                android:background="@null"
                                android:hint="请输入充值金额(选填)"
                                android:hint="请输入工本费(选填)"
                                android:inputType="numberDecimal"
                                android:textColor="#333333"
                                android:textColorHint="#BBBBBB"
                                android:textSize="@dimen/new_card_size" />
                            <TextView
                                android:layout_width="wrap_content"
                                android:layout_height="wrap_content"
                                android:text="元"
                                android:textColor="#666666"
                                android:textSize="@dimen/new_card_size" />
                        </LinearLayout>
                    </LinearLayout>
                    <LinearLayout
                        android:layout_width="match_parent"
@@ -318,8 +346,12 @@
                        <LinearLayout
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:background="#FFFFFF"
                            android:orientation="vertical">
                            android:background="@drawable/edit_text_bg_selector"
                            android:orientation="vertical"
                            android:paddingStart="10dp"
                            android:paddingEnd="10dp"
                            android:paddingTop="8dp"
                            android:paddingBottom="8dp">
                            <EditText
                                android:id="@+id/newCard_remark"
@@ -328,6 +360,7 @@
                                android:background="@null"
                                android:hint="请输入备注(选填)"
                                android:textColor="#333333"
                                android:textColorHint="#BBBBBB"
                                android:textSize="@dimen/new_card_size" />
                        </LinearLayout>
                    </LinearLayout>
@@ -360,48 +393,7 @@
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:orientation="horizontal">
                        <RadioButton
                            android:id="@+id/newCard_cashPayment"
                            android:layout_width="0dp"
                            android:layout_height="40dp"
                            android:layout_marginRight="15dp"
                            android:layout_weight="1"
                            android:background="@drawable/radio_selector"
                            android:button="@null"
                            android:checked="true"
                            android:gravity="center"
                            android:text="现金"
                            android:textColor="@color/radio_button_text_color"
                            android:textSize="@dimen/new_card_size" />
                        <RadioButton
                            android:id="@+id/newCard_posPayment"
                            android:layout_width="0dp"
                            android:layout_height="40dp"
                            android:layout_marginRight="15dp"
                            android:layout_weight="1"
                            android:background="@drawable/radio_selector"
                            android:button="@null"
                            android:gravity="center"
                            android:text="POS机"
                            android:textColor="@color/radio_button_text_color"
                            android:textSize="@dimen/new_card_size" />
                        <RadioButton
                            android:id="@+id/newCard_bankTransfer"
                            android:layout_width="0dp"
                            android:layout_height="40dp"
                            android:layout_weight="1"
                            android:background="@drawable/radio_selector"
                            android:button="@null"
                            android:gravity="center"
                            android:text="银行转账"
                            android:textColor="@color/radio_button_text_color"
                            android:textSize="@dimen/new_card_size" />
                        <!-- 动态添加RadioButton,移除固定的RadioButton -->
                    </RadioGroup>
                </LinearLayout>
            </androidx.cardview.widget.CardView>
generallibrary/src/main/res/layout/fragment_card.xml
@@ -173,7 +173,7 @@
                    <ImageView
                        android:layout_width="55dp"
                        android:layout_height="55dp"
                        android:src="@drawable/home_report" />
                        android:src="@drawable/guashi" />
                    <TextView
                        android:layout_width="wrap_content"
@@ -264,14 +264,14 @@
                    <ImageView
                        android:layout_width="55dp"
                        android:layout_height="55dp"
                        android:src="@drawable/chongzheng" />
                        android:src="@drawable/xiaoka" />
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="6dp"
                        android:gravity="center"
                        android:text="冲正"
                        android:text="销卡"
                        android:textColor="@color/text_selecter_color"
                        android:textSize="14sp" />
                </LinearLayout>
@@ -316,48 +316,11 @@
                </LinearLayout>
            </androidx.cardview.widget.CardView>
            <androidx.cardview.widget.CardView
                android:id="@+id/home_rewrite"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="6dp"
                android:layout_marginTop="16dp"
                android:layout_marginEnd="6dp"
                android:clickable="true"
                android:focusable="true"
                android:foreground="?android:attr/selectableItemBackground"
                app:cardCornerRadius="10dp"
                app:cardElevation="3dp"
                app:layout_constraintEnd_toStartOf="@+id/home_manage"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/home_reverse">
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:gravity="center"
                    android:orientation="vertical"
                    android:padding="12dp">
                    <ImageView
                        android:layout_width="55dp"
                        android:layout_height="55dp"
                        android:src="@drawable/fanxie" />
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="6dp"
                        android:gravity="center"
                        android:text="反写"
                        android:textColor="@color/text_selecter_color"
                        android:textSize="14sp" />
                </LinearLayout>
            </androidx.cardview.widget.CardView>
            <androidx.cardview.widget.CardView
                android:id="@+id/home_manage"
                android:layout_width="0dp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginStart="6dp"
                android:layout_marginTop="16dp"
@@ -370,7 +333,7 @@
                app:cardElevation="3dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toEndOf="@+id/home_rewrite"
                app:layout_constraintTop_toBottomOf="@+id/home_deduction"
                app:layout_constraintVertical_bias="0.0">
generallibrary/src/main/res/values/dimens.xml
@@ -2,5 +2,13 @@
<resources>
    <dimen name="title_text_size">20sp</dimen>
    <dimen name="dimen_40">40dp</dimen>
    <dimen name="dimen_15">15dp</dimen>
    <dimen name="new_card_size">14sp</dimen>
    <!-- 输入框相关 -->
    <dimen name="input_padding_horizontal">12dp</dimen>
    <dimen name="input_padding_vertical">10dp</dimen>
    <dimen name="input_corner_radius">8dp</dimen>
</resources>