diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2051a5d..e80cc69 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -25,6 +25,9 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.DefenseApplication"> + diff --git a/app/src/main/java/com/example/defenseapplication/adapter/LinkedUserAdapter.kt b/app/src/main/java/com/example/defenseapplication/adapter/LinkedUserAdapter.kt index 3820287..49d3e03 100644 --- a/app/src/main/java/com/example/defenseapplication/adapter/LinkedUserAdapter.kt +++ b/app/src/main/java/com/example/defenseapplication/adapter/LinkedUserAdapter.kt @@ -14,6 +14,7 @@ data class LinkedUser( ) class LinkedUserAdapter( + private val deleteText: String, private val onItemClick: (LinkedUser, Int) -> Unit, private val onDeleteClick: (LinkedUser, Int) -> Unit ) : RecyclerView.Adapter() { @@ -43,6 +44,7 @@ class LinkedUserAdapter( } override fun onBindViewHolder(holder: LinkedUserViewHolder, position: Int) { + holder.bind(items[position], position) } @@ -58,7 +60,8 @@ class LinkedUserAdapter( binding.tvPhone.text = "手机号码:${item.phone}" binding.tvIdCard.text = "身份证号:${item.idCard}" binding.tvTime.text = item.time - + //设置删除按钮的文字 + binding.tvDeleteAction.text = deleteText binding.llContent.setOnClickListener { onItemClick(item, position) } diff --git a/app/src/main/java/com/example/defenseapplication/home/PersonalFragment.kt b/app/src/main/java/com/example/defenseapplication/home/PersonalFragment.kt index a74256a..78a85d7 100644 --- a/app/src/main/java/com/example/defenseapplication/home/PersonalFragment.kt +++ b/app/src/main/java/com/example/defenseapplication/home/PersonalFragment.kt @@ -50,10 +50,13 @@ class PersonalFragment : Fragment() { binding.itemSwitchFamily.setOnClickListener { showFamilySwitchDialog() } + //用户管理点击事件跳转关联用户页面 binding.itemUserManage.setOnClickListener { - val intent= Intent(requireContext(), LinkedUserActivity::class.java) + val intent = Intent(requireContext(), LinkedUserActivity::class.java) + intent.putExtra(LinkedUserActivity.EXTRA_MODE, LinkedUserActivity.MODE_PERSONAL) startActivity(intent) } + // 密码管理点击事件 binding.itemPwdManage.setOnClickListener { val intent= Intent(requireContext(), PasswordManagementActivity::class.java) startActivity(intent) diff --git a/app/src/main/java/com/example/defenseapplication/liveVideo/LinkedUserActivity.kt b/app/src/main/java/com/example/defenseapplication/liveVideo/LinkedUserActivity.kt index 8d4387f..ae260ac 100644 --- a/app/src/main/java/com/example/defenseapplication/liveVideo/LinkedUserActivity.kt +++ b/app/src/main/java/com/example/defenseapplication/liveVideo/LinkedUserActivity.kt @@ -2,6 +2,7 @@ package com.example.defenseapplication.liveVideo import android.content.Intent import android.os.Bundle +import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager @@ -18,10 +19,14 @@ class LinkedUserActivity : AppCompatActivity() { private lateinit var binding: LinkedUserActivityBinding private lateinit var adapter: LinkedUserAdapter private val userList = mutableListOf() + private var mode: Int = MODE_SETTINGS companion object { const val REQUEST_CODE_SEARCH = 1001 const val EXTRA_SEARCH_RESULT = "search_result" + const val EXTRA_MODE = "extra_mode" + const val MODE_SETTINGS = 0 + const val MODE_PERSONAL = 1 } override fun onCreate(savedInstanceState: Bundle?) { @@ -29,6 +34,8 @@ class LinkedUserActivity : AppCompatActivity() { binding = LinkedUserActivityBinding.inflate(layoutInflater) setContentView(binding.root) + mode = intent.getIntExtra(EXTRA_MODE, MODE_SETTINGS) + ImmersionBar.with(this) .statusBarDarkFont(true) .titleBar(binding.topBar) @@ -37,10 +44,13 @@ class LinkedUserActivity : AppCompatActivity() { initView() initData() initListener() + setupModeUI() } private fun initView() { + val deleteText = if (mode == MODE_SETTINGS) getString(R.string.unlink) else getString(R.string.delete) adapter = LinkedUserAdapter( + deleteText = deleteText, onItemClick = { user, position -> val intent = Intent(this, UserDetailsActivity::class.java).apply { putExtra("name", user.name) @@ -131,11 +141,16 @@ class LinkedUserActivity : AppCompatActivity() { currentSwipeHeld = true val dialog = CustomDialogFragmentText() - dialog.setTitle("解除关联") - dialog.setMessage("确定要解除与 ${user.name} 的关联吗?") + if (mode == MODE_SETTINGS) { + dialog.setTitle(getString(R.string.tip)) + dialog.setMessage(getString(R.string.unlink_confirm_message)) + } else { + dialog.setTitle(getString(R.string.tip)) + dialog.setMessage(getString(R.string.confirm_delete_user)) + } dialog.setShowInput(false) - dialog.setPositiveText("确定") - dialog.setNegativeText("取消") + dialog.setPositiveText(getString(R.string.confirm)) + dialog.setNegativeText(getString(R.string.cancel)) dialog.setOnConfirmListener { userList.removeAt(position) adapter.removeItem(position) @@ -185,6 +200,21 @@ class LinkedUserActivity : AppCompatActivity() { val intent = Intent(this, AddLinkedUserActivity::class.java) startActivity(intent) } + + binding.fabAdd.setOnClickListener { + val intent = Intent(this, com.example.defenseapplication.personal.InvitationCodeActivity::class.java) + startActivity(intent) + } + } + + private fun setupModeUI() { + if (mode == MODE_SETTINGS) { + binding.btnAddLinkedUser.visibility = View.VISIBLE + binding.fabAdd.visibility = View.GONE + } else { + binding.btnAddLinkedUser.visibility = View.GONE + binding.fabAdd.visibility = View.VISIBLE + } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { diff --git a/app/src/main/java/com/example/defenseapplication/liveVideo/SettingsActivity.kt b/app/src/main/java/com/example/defenseapplication/liveVideo/SettingsActivity.kt index 00bd7b4..2d40e8f 100644 --- a/app/src/main/java/com/example/defenseapplication/liveVideo/SettingsActivity.kt +++ b/app/src/main/java/com/example/defenseapplication/liveVideo/SettingsActivity.kt @@ -74,9 +74,11 @@ class SettingsActivity : BaseActivity() { val intent = Intent(this, DeviceInfoActivity::class.java) startActivity(intent) } + // 关联用户 binding.itemRelatedUsers.setOnClickListener { val intent = Intent(this, LinkedUserActivity::class.java) - startActivity( intent) + intent.putExtra(LinkedUserActivity.EXTRA_MODE, LinkedUserActivity.MODE_SETTINGS) + startActivity( intent) } binding.itemWifiManage.setOnClickListener { val intent = Intent(this, WiFiManagementActivity::class.java) diff --git a/app/src/main/java/com/example/defenseapplication/personal/InvitationCodeActivity.kt b/app/src/main/java/com/example/defenseapplication/personal/InvitationCodeActivity.kt new file mode 100644 index 0000000..08201ff --- /dev/null +++ b/app/src/main/java/com/example/defenseapplication/personal/InvitationCodeActivity.kt @@ -0,0 +1,191 @@ +package com.example.defenseapplication.personal + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.os.Bundle + +import android.view.LayoutInflater +import android.view.View +import android.widget.RadioButton +import android.widget.RadioGroup +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.bottomsheet.BottomSheetDialog +import androidx.core.content.ContextCompat +import com.example.defenseapplication.R +import com.example.defenseapplication.databinding.DialogExpireTimeBinding +import com.example.defenseapplication.databinding.InvitationCodeActivityBinding +import com.google.zxing.BarcodeFormat +import com.google.zxing.EncodeHintType +import com.google.zxing.qrcode.QRCodeWriter +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel +import com.gyf.immersionbar.ImmersionBar +import java.util.Hashtable + +//邀请码页面 +class InvitationCodeActivity : AppCompatActivity() { + + private lateinit var binding: InvitationCodeActivityBinding + private var expireTimeInMillis: Long = 24 * 60 * 60 * 1000 // 默认24小时 + private var selectedExpireOption: Int = 0 // 0: 24h, 1: 7d,默认选中24h + + companion object { + const val EXPIRE_24H = 24 * 60 * 60 * 1000L + const val EXPIRE_7D = 7 * 24 * 60 * 60 * 1000L + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = InvitationCodeActivityBinding.inflate(layoutInflater) + setContentView(binding.root) + + ImmersionBar.with(this) + .statusBarDarkFont(true) + .titleBar(binding.topBar) + .init() + + initListener() + // 默认显示时效为24h + binding.tvExpireTime.text = getString(R.string.expire_24h) + // 默认显示灰色占位区域,二维码不可见 + hideQrCode() + } + + private fun initListener() { + binding.ivBack.setOnClickListener { + finish() + } + + binding.llExpireTime.setOnClickListener { + showExpireTimeDialog() + } + + binding.llWechatShare.setOnClickListener { + // 微信分享功能 + shareToWeChat() + } + } + + private fun showExpireTimeDialog() { + val dialogBinding = DialogExpireTimeBinding.inflate(LayoutInflater.from(this)) + + val dialog = BottomSheetDialog(this, R.style.BottomSheetDialog) + dialog.setContentView(dialogBinding.root) + + // 设置选中状态 + when (selectedExpireOption) { + 0 -> dialogBinding.rb24h.isChecked = true + 1 -> dialogBinding.rb7d.isChecked = true + } + + dialogBinding.radioGroup.setOnCheckedChangeListener { _, checkedId -> + when (checkedId) { + R.id.rb24h -> selectedExpireOption = 0 + R.id.rb7d -> selectedExpireOption = 1 + } + } + + dialogBinding.tvCancel.setOnClickListener { + dialog.dismiss() + } + + dialogBinding.tvConfirm.setOnClickListener { + when (selectedExpireOption) { + 0 -> { + expireTimeInMillis = EXPIRE_24H + binding.tvExpireTime.text = getString(R.string.expire_24h) + } + 1 -> { + expireTimeInMillis = EXPIRE_7D + binding.tvExpireTime.text = getString(R.string.expire_7d) + } + } + // 选择后显示二维码并生成 + showQrCode() + generateQRCode() + dialog.dismiss() + } + + dialog.show() + } + + private fun hideQrCode() { + // 隐藏二维码,显示灰色占位区域 + binding.ivQrCode.visibility = View.GONE + binding.ivQrLogo.visibility = View.GONE + binding.vQrPlaceholder.visibility = View.VISIBLE + } + + private fun showQrCode() { + // 显示二维码,隐藏灰色占位区域 + binding.vQrPlaceholder.visibility = View.GONE + binding.ivQrCode.visibility = View.VISIBLE + binding.ivQrLogo.visibility = View.VISIBLE + } + + private fun generateQRCode() { + val content = generateInviteCode() + val qrBitmap = createQRCodeWithLogo(content, 400, 400) + binding.ivQrCode.setImageBitmap(qrBitmap) + } + + private fun generateInviteCode(): String { + // 生成包含邀请信息的JSON字符串 + val timestamp = System.currentTimeMillis() + val expireTime = timestamp + expireTimeInMillis + return "{\"type\":\"invite\",\"code\":\"INV${timestamp}\",\"expire\":$expireTime}" + } + + private fun createQRCodeWithLogo(content: String, width: Int, height: Int): Bitmap? { + return try { + val hints = Hashtable() + hints[EncodeHintType.CHARACTER_SET] = "UTF-8" + hints[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.H + + val writer = QRCodeWriter() + val bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, width, height, hints) + + val qrBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(qrBitmap) + + // 绘制白色背景 + canvas.drawColor(Color.WHITE) + + // 绘制二维码 + val paint = Paint(Paint.ANTI_ALIAS_FLAG) + paint.color = Color.BLACK + + val cellWidth = width.toFloat() / bitMatrix.width + val cellHeight = height.toFloat() / bitMatrix.height + + for (x in 0 until bitMatrix.width) { + for (y in 0 until bitMatrix.height) { + if (bitMatrix.get(x, y)) { + canvas.drawRect( + x * cellWidth, + y * cellHeight, + (x + 1) * cellWidth, + (y + 1) * cellHeight, + paint + ) + } + } + } + + qrBitmap + } catch (e: Exception) { + e.printStackTrace() + null + } + } + + private fun shareToWeChat() { + // 微信分享实现 + // 这里需要集成微信SDK + } + + override fun onDestroy() { + super.onDestroy() + } +} diff --git a/app/src/main/java/com/example/defenseapplication/view/FamilySwitchDialog.kt b/app/src/main/java/com/example/defenseapplication/view/FamilySwitchDialog.kt index 0567437..ea92540 100644 --- a/app/src/main/java/com/example/defenseapplication/view/FamilySwitchDialog.kt +++ b/app/src/main/java/com/example/defenseapplication/view/FamilySwitchDialog.kt @@ -47,7 +47,7 @@ class FamilySwitchDialog( val familyList = listOf( FamilyItem("1", context.getString(R.string.my_family)), - FamilyItem("2", context.getString(R.string.zhangsan_family)) + FamilyItem("2", "张三的家庭") ) adapter = FamilySwitchAdapter(familyList) { familyItem -> diff --git a/app/src/main/res/drawable/add_img.png b/app/src/main/res/drawable/add_img.png new file mode 100644 index 0000000..47134ad Binary files /dev/null and b/app/src/main/res/drawable/add_img.png differ diff --git a/app/src/main/res/drawable/bg_radio_button_selector.xml b/app/src/main/res/drawable/bg_radio_button_selector.xml new file mode 100644 index 0000000..7695b5f --- /dev/null +++ b/app/src/main/res/drawable/bg_radio_button_selector.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_white_rounded.xml b/app/src/main/res/drawable/bg_white_rounded.xml new file mode 100644 index 0000000..9cf9a8d --- /dev/null +++ b/app/src/main/res/drawable/bg_white_rounded.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/drawable/logo.png b/app/src/main/res/drawable/logo.png new file mode 100644 index 0000000..c3977b7 Binary files /dev/null and b/app/src/main/res/drawable/logo.png differ diff --git a/app/src/main/res/drawable/text_color_selector.xml b/app/src/main/res/drawable/text_color_selector.xml new file mode 100644 index 0000000..f8eba85 --- /dev/null +++ b/app/src/main/res/drawable/text_color_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/vx.png b/app/src/main/res/drawable/vx.png new file mode 100644 index 0000000..ebeb065 Binary files /dev/null and b/app/src/main/res/drawable/vx.png differ diff --git a/app/src/main/res/layout/dialog_expire_time.xml b/app/src/main/res/layout/dialog_expire_time.xml new file mode 100644 index 0000000..29a84a9 --- /dev/null +++ b/app/src/main/res/layout/dialog_expire_time.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/invitation_code_activity.xml b/app/src/main/res/layout/invitation_code_activity.xml new file mode 100644 index 0000000..2ded2b2 --- /dev/null +++ b/app/src/main/res/layout/invitation_code_activity.xml @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_linked_user.xml b/app/src/main/res/layout/item_linked_user.xml index 1c23c42..75cbd8a 100644 --- a/app/src/main/res/layout/item_linked_user.xml +++ b/app/src/main/res/layout/item_linked_user.xml @@ -16,6 +16,7 @@ app:layout_constraintTop_toTopOf="parent"> - + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/strike_position_activity.xml b/app/src/main/res/layout/strike_position_activity.xml index 554d5be..3b42b14 100644 --- a/app/src/main/res/layout/strike_position_activity.xml +++ b/app/src/main/res/layout/strike_position_activity.xml @@ -85,7 +85,7 @@ android:layout_width="match_parent" android:layout_height="40dp" android:layout_margin="16dp" - android:layout_marginBottom="20dp" + android:layout_marginBottom="40dp" android:background="@drawable/rounded_blue_bg" android:text="@string/submit" android:textColor="@color/white" diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index d06a73f..bfa0d2c 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -398,4 +398,16 @@ 密码错误 未设置操作密码 确认重置 + 用户状态 + 联系人姓名 + 身份证号 + MAC地址 + 邀请码 + 邀请码有效期 + 二维码邀请 + 邀请二维码24小时内有效,仅可接受一次;手动撤销或重新生成将使原邀请二维码失效 + 其他邀请方式 + 微信好友 + 24小时 + 7天 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 67c3766..608cd6d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -103,8 +103,6 @@ Add User Admin Member - Ordinary Member - Zhang San Family Phone Number Enter nickname @@ -244,10 +242,6 @@ Please scan the QR code on the device or manual - 用户状态 - 联系人姓名 - 身份证号 - MAC地址 Workshop Parents Home Smart Hanging Defense @@ -408,4 +402,16 @@ Please enter new password Please re-enter new password Confirm Reset + User Status + Contact Name + ID Card Number + MAC Address + Invite Code + Invite Code Expiry + QR Code Scan Invite + Invite QR code is valid for 24 hours; can only be accepted once; manual revocation or regenerating will invalidate the previous invite QR code + Other Invite Methods + WeChat Friend + 24h + 7d \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 9b74115..7cebb25 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -19,4 +19,11 @@ @anim/slide_up @anim/slide_down + + \ No newline at end of file