feat(generallibrary): 优化用户开卡流程并添加支付方式功能
- 重构了 NewCard2Activity 中的支付方式逻辑,支持动态添加支付方式
- 新增了获取支付方式列表的接口和相关数据类
- 优化了 UI 样式,调整了部分控件的布局和样式- 添加了卡片初始化功能,包括修改扇区密码
- 修复了一些已知的 bug,提高了系统的稳定性
| | |
| | | - 敏感数据需要加密存储 |
| | | - 注意用户数据的安全处理 |
| | | |
| | | 4. 异常处理 |
| | | - 所有try catch块中必须使用`CrashReport.postCatchedException(e)`上报异常 |
| | | - 确保异常信息被正确记录和上报 |
| | | - 避免异常信息泄露敏感数据 |
| | | |
| | | ## 贡献指南 |
| | | |
| | | 1. Fork 项目 |
| | |
| | | import com.tencent.bugly.crashreport.CrashReport; |
| | | |
| | | import java.io.IOException; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * author: zuo |
| | |
| | | /** |
| | | * 修改密码 |
| | | * |
| | | * @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 { |
| | |
| | | int bCount = mfc.getBlockCountInSector(a); |
| | | //写到扇区的最后一个块 |
| | | mfc.writeBlock(bIndex + bCount - 1, data); |
| | | |
| | | return true; |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | CrashReport.postCatchedException(e); |
| | | |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | |
| | | public boolean changePasword(int a,byte[] passWord, MifareClassic mfc) { |
| | | try { |
| | | |
| | | |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | CrashReport.postCatchedException(e); |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * 初始化卡 |
| | |
| | | import com.dayu.baselibrary.bean.BaseUserCardCard; |
| | | import com.dayu.baselibrary.utils.ModelUtils; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * author: zuo |
| | | * Date: 2024-09-26 |
| | |
| | | } |
| | | 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; |
| | | } |
| | | } |
| | |
| | | 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 |
| | | }; |
| | |
| | | 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; |
| | |
| | | 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()); |
| | | } |
| | | |
| | |
| | | |
| | | // 解密并转换每个扇区密钥 |
| | | 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)) { |
| | |
| | | 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; |
| | | } |
| | | |
| | |
| | | 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 |
| | |
| | | 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: 用户开卡界面(同步修改白卡密码) |
| | |
| | | |
| | | // 支付方式 |
| | | private var paymentMethod: String = "现金" |
| | | |
| | | // 支付方式ID |
| | | private var paymentId: Long = 0 |
| | | |
| | | // 支付方式列表 |
| | | private var paymentMethodList: List<PaymentMethod> = listOf() |
| | | |
| | | // 卡物理ID |
| | | private var cardPhysicalId: String = "" |
| | |
| | | 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() |
| | | } |
| | | |
| | |
| | | // 设置金额输入限制为两位小数 |
| | | 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 |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | 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 |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | 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() |
| | | |
| | | // 构建请求参数 |
| | |
| | | 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 |
| | | |
| | |
| | | farmerCode = binding.newCardFarmerCode.text.toString(), |
| | | cardFee = cardFee, |
| | | remark = binding.newCardRemark.text.toString(), |
| | | paymentMethod = paymentId, |
| | | paymentMethod = paymentId.toInt(), |
| | | isReported = true, |
| | | isCardWritten = true |
| | | ) |
| | | |
| | | |
| | | // 使用协程在后台线程中保存数据 |
| | | 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 { |
| | |
| | | } |
| | | |
| | | override fun onNfcBack(intent: Intent) { |
| | | var cardNumber = NfcReadHelper.getInstance(intent, this).getCardNumber() |
| | | var cardNumber = NfcReadHelper.getInstance(intent, this).getCardNumberNoClose() |
| | | if (!cardNumber.isEmpty()) { |
| | | // 保存卡物理ID |
| | | cardPhysicalId = cardNumber |
| | | // 更新UI |
| | | binding.newCardArerNumber.text = cardNumber |
| | | // 隐藏NFC读卡界面 |
| | | binding.nfcContainer.visibility = View.GONE |
| | | binding.centerScroll.visibility = View.VISIBLE |
| | | binding.newCardRegistBtn.visibility = View.VISIBLE |
| | | 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 |
| | | binding.newCardArerNumber.text = cardNumber |
| | | // 隐藏NFC读卡界面 |
| | | 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() |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | } |
| | |
| | | // 使用正确的类型参数 |
| | | ApiManager.getInstance().requestGetLoading( |
| | | this, |
| | | "sell/client/get", |
| | | "terminal/client/getTermClients", |
| | | SearchUserResult::class.java, |
| | | map, |
| | | object : SubscriberListener<BaseResponse<SearchUserResult>>() { |
| | |
| | | private external fun getM1SectorKeySecure(context: Context, sectorIndex: Int): String |
| | | |
| | | init { |
| | | try { |
| | | // 获取所有扇区密钥 |
| | | val allKeys: String = getM1SectorKeySecure(context, 0) |
| | | if (allKeys.isNotEmpty()) { |
| | | val keys: Array<String> = allKeys.split(",").dropLastWhile { it.isEmpty() }.toTypedArray() |
| | | for (i in keys.indices) { |
| | | val key = keys[i] |
| | | if (key.isNotEmpty()) { |
| | | listA_PS.add(HexUtil.hexToByteArray(key)) |
| | | if(listA_PS.isEmpty()){ |
| | | try { |
| | | // 获取所有扇区密钥 |
| | | val allKeys: String = getM1SectorKeySecure(context, 0) |
| | | if (allKeys.isNotEmpty()) { |
| | | val keys: Array<String> = allKeys.split(",").dropLastWhile { it.isEmpty() }.toTypedArray() |
| | | for (i in keys.indices) { |
| | | val key = keys[i] |
| | | if (key.isNotEmpty()) { |
| | | listA_PS.add(HexUtil.hexToByteArray(key)) |
| | | } |
| | | } |
| | | } |
| | | defauleKey = HexUtil.hexToByteArray("FFFFFFFFFFFF") |
| | | } catch (e: Exception) { |
| | | e.printStackTrace() |
| | | // 设置默认密钥 |
| | | defauleKey = HexUtil.hexToByteArray("FFFFFFFFFFFF") |
| | | } |
| | | defauleKey = HexUtil.hexToByteArray("FFFFFFFFFFFF") |
| | | } catch (e: Exception) { |
| | | e.printStackTrace() |
| | | // 设置默认密钥 |
| | | defauleKey = HexUtil.hexToByteArray("FFFFFFFFFFFF") |
| | | } |
| | | } |
| | | |
| | | fun getKeyList(): List<ByteArray> { |
| | | return listKeyA |
| | | return listA_PS |
| | | } |
| | | } |
| | |
| | | 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) |
| | | |
| | |
| | | * @param b 书写的块(从0开始数) |
| | | * @param |
| | | */ |
| | | fun writeData(str: ByteArray?, a: Int, b: Int,callBack: NFCCallBack): Boolean { |
| | | fun writeData(str: ByteArray?, a: Int, b: Int, callBack: NFCCallBack): Boolean { |
| | | try { |
| | | return adapter.writeData(str, a, b,false,callBack) |
| | | return adapter.writeData(str, a, b, false, callBack) |
| | | } catch (e: Exception) { |
| | | e.printStackTrace() |
| | | } |
| | |
| | | */ |
| | | fun writeUserData(userCard: UserCard): Boolean { |
| | | try { |
| | | return adapter.writeUserData(userCard,7) |
| | | return adapter.writeUserData(userCard, 7) |
| | | } catch (e: java.lang.Exception) { |
| | | e.printStackTrace() |
| | | } |
| | | 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() |
| | | } |
New file |
| | |
| | | <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> |
New file |
| | |
| | | <?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> |
New file |
| | |
| | | <?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> |
New file |
| | |
| | | <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> |
| | |
| | | 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" |
| | |
| | | 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 |
| | |
| | | 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"> |
| | | |
| | |
| | | <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> |
| | |
| | | <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" |
| | |
| | | <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" |
| | |
| | | android:background="@null" |
| | | android:hint="请输入备注(选填)" |
| | | android:textColor="#333333" |
| | | android:textColorHint="#BBBBBB" |
| | | android:textSize="@dimen/new_card_size" /> |
| | | </LinearLayout> |
| | | </LinearLayout> |
| | |
| | | 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> |
| | |
| | | <ImageView |
| | | android:layout_width="55dp" |
| | | android:layout_height="55dp" |
| | | android:src="@drawable/home_report" /> |
| | | android:src="@drawable/guashi" /> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | |
| | | <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> |
| | |
| | | </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" |
| | |
| | | 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"> |
| | | |
| | |
| | | <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> |