From 2c94ba767da8097ec2f5eb11e78e347a13e05a87 Mon Sep 17 00:00:00 2001 From: maxinyu Date: Sun, 10 May 2026 11:10:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=B3=E8=81=94=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/LinkedUserAdapter.kt | 73 +++++++ .../liveVideo/LinkedUserActivity.kt | 180 +++++++++++++++++- .../liveVideo/LinkedUserSearchActivity.kt | 44 +++++ .../liveVideo/SettingsActivity.kt | 6 +- .../view/CustomDialogFragment.kt | 8 +- app/src/main/res/drawable/bg_user_manage.xml | 2 +- app/src/main/res/layout/item_linked_user.xml | 77 ++++++++ .../res/layout/item_linked_user_search.xml | 39 ++++ .../main/res/layout/linked_user_activity.xml | 63 +++++- .../layout/linked_user_search_activity.xml | 86 +++++++++ app/src/main/res/values-zh/strings.xml | 5 + app/src/main/res/values/strings.xml | 7 +- gradle/libs.versions.toml | 3 + 13 files changed, 584 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/com/example/defenseapplication/adapter/LinkedUserAdapter.kt create mode 100644 app/src/main/java/com/example/defenseapplication/liveVideo/LinkedUserSearchActivity.kt create mode 100644 app/src/main/res/layout/item_linked_user.xml create mode 100644 app/src/main/res/layout/item_linked_user_search.xml create mode 100644 app/src/main/res/layout/linked_user_search_activity.xml diff --git a/app/src/main/java/com/example/defenseapplication/adapter/LinkedUserAdapter.kt b/app/src/main/java/com/example/defenseapplication/adapter/LinkedUserAdapter.kt new file mode 100644 index 0000000..3820287 --- /dev/null +++ b/app/src/main/java/com/example/defenseapplication/adapter/LinkedUserAdapter.kt @@ -0,0 +1,73 @@ +package com.example.defenseapplication.adapter + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.example.defenseapplication.databinding.ItemLinkedUserBinding + +data class LinkedUser( + val name: String, + val phone: String, + val idCard: String, + val time: String +) + +class LinkedUserAdapter( + private val onItemClick: (LinkedUser, Int) -> Unit, + private val onDeleteClick: (LinkedUser, Int) -> Unit +) : RecyclerView.Adapter() { + + private val items = mutableListOf() + + fun submitList(userList: List) { + items.clear() + items.addAll(userList) + notifyDataSetChanged() + } + + fun removeItem(position: Int) { + if (position in items.indices) { + items.removeAt(position) + notifyItemRemoved(position) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LinkedUserViewHolder { + val binding = ItemLinkedUserBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return LinkedUserViewHolder(binding) + } + + override fun onBindViewHolder(holder: LinkedUserViewHolder, position: Int) { + holder.bind(items[position], position) + } + + override fun getItemCount(): Int = items.size + + inner class LinkedUserViewHolder( + private val binding: ItemLinkedUserBinding + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: LinkedUser, position: Int) { + binding.llContent.translationX = 0f + binding.tvUserName.text = item.name + binding.tvPhone.text = "手机号码:${item.phone}" + binding.tvIdCard.text = "身份证号:${item.idCard}" + binding.tvTime.text = item.time + + binding.llContent.setOnClickListener { + onItemClick(item, position) + } + + binding.llDelete.setOnClickListener { + onDeleteClick(item, position) + } + } + + fun getContentLayout(): View = binding.llContent + } +} 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 37cf24f..c388afb 100644 --- a/app/src/main/java/com/example/defenseapplication/liveVideo/LinkedUserActivity.kt +++ b/app/src/main/java/com/example/defenseapplication/liveVideo/LinkedUserActivity.kt @@ -1,27 +1,199 @@ package com.example.defenseapplication.liveVideo +import android.content.Intent import android.os.Bundle -import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.example.defenseapplication.R -import com.example.defenseapplication.databinding.DeviceInfoActivityBinding +import com.example.defenseapplication.adapter.LinkedUser +import com.example.defenseapplication.adapter.LinkedUserAdapter import com.example.defenseapplication.databinding.LinkedUserActivityBinding +import com.example.defenseapplication.view.CustomDialogFragmentText import com.gyf.immersionbar.ImmersionBar class LinkedUserActivity : AppCompatActivity() { + private lateinit var binding: LinkedUserActivityBinding + private lateinit var adapter: LinkedUserAdapter + private val userList = mutableListOf() + + companion object { + const val REQUEST_CODE_SEARCH = 1001 + const val EXTRA_SEARCH_RESULT = "search_result" + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = LinkedUserActivityBinding.inflate(layoutInflater) setContentView(binding.root) + ImmersionBar.with(this) .statusBarDarkFont(true) .titleBar(binding.topBar) .init() + + initView() + initData() + initListener() + } + + private fun initView() { + adapter = LinkedUserAdapter( + onItemClick = { user, position -> + + }, + onDeleteClick = { user, position -> + showUnlinkDialog(user, position) + } + ) + binding.rvLinkedUser.layoutManager = LinearLayoutManager(this) + binding.rvLinkedUser.adapter = adapter + + val swipeThreshold = -300f + val heldPositions = mutableMapOf() + + val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean = false + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + } + + override fun onChildDraw( + c: android.graphics.Canvas, + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + dX: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean + ) { + if (viewHolder is LinkedUserAdapter.LinkedUserViewHolder) { + val contentLayout = viewHolder.getContentLayout() + val position = viewHolder.adapterPosition + + if (isCurrentlyActive) { + val swipeDistance = if (dX < swipeThreshold) swipeThreshold else dX + contentLayout.translationX = swipeDistance + } else { + val isHeld = heldPositions[position] + if (isHeld == true) { + contentLayout.translationX = swipeThreshold + } + } + } + } + + /* + - 滑动超过一半后松手:列表项 保持在滑动位置 ,显示"解除关联"按钮 + - 滑动未超过一半松手:列表项 恢复原位 + * */ + override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { + super.clearView(recyclerView, viewHolder) + if (viewHolder is LinkedUserAdapter.LinkedUserViewHolder) { + val position = viewHolder.adapterPosition + val contentLayout = viewHolder.getContentLayout() + + if (contentLayout.translationX < swipeThreshold / 2) { + heldPositions[position] = true + contentLayout.translationX = swipeThreshold + } else { + heldPositions.remove(position) + contentLayout.animate() + .translationX(0f) + .setDuration(300) + .start() + } + } + } + + override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float = 0.05f + }) + itemTouchHelper.attachToRecyclerView(binding.rvLinkedUser) + } + + private var currentSwipePosition: Int = -1 + private var currentSwipeHeld: Boolean = false + + private fun showUnlinkDialog(user: LinkedUser, position: Int) { + currentSwipePosition = position + currentSwipeHeld = true + + val dialog = CustomDialogFragmentText() + dialog.setTitle("解除关联") + dialog.setMessage("确定要解除与 ${user.name} 的关联吗?") + dialog.setShowInput(false) + dialog.setPositiveText("确定") + dialog.setNegativeText("取消") + dialog.setOnConfirmListener { + userList.removeAt(position) + adapter.removeItem(position) + resetSwipeState() + } + dialog.setOnDismissListener { + resetSwipeState() + } + dialog.show(supportFragmentManager, "UnlinkDialog") + } + + private fun resetSwipeState() { + if (currentSwipePosition >= 0 && currentSwipeHeld) { + binding.rvLinkedUser.post { + val viewHolder = binding.rvLinkedUser.findViewHolderForAdapterPosition(currentSwipePosition) + if (viewHolder is LinkedUserAdapter.LinkedUserViewHolder) { + viewHolder.getContentLayout().animate() + .translationX(0f) + .setDuration(300) + .start() + } + currentSwipePosition = -1 + currentSwipeHeld = false + } + } + } + + private fun initData() { + userList.clear() + userList.add(LinkedUser("张三", "12388888888", "123456789012345678", "2026-05-06 16:03:26")) + userList.add(LinkedUser("李四", "13899999999", "987654321098765432", "2026-05-05 14:20:15")) + userList.add(LinkedUser("王五", "15977777777", "456123789012345678", "2026-05-04 10:15:30")) + adapter.submitList(userList) + } + + private fun initListener() { binding.ivBack.setOnClickListener { finish() } + + binding.searchLayout.setOnClickListener { + val intent = Intent(this, LinkedUserSearchActivity::class.java) + startActivityForResult(intent, REQUEST_CODE_SEARCH) + } + + binding.btnAddLinkedUser.setOnClickListener { + val intent = Intent(this, LinkedUserSearchActivity::class.java) + startActivityForResult(intent, REQUEST_CODE_SEARCH) + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == REQUEST_CODE_SEARCH && resultCode == RESULT_OK) { + data?.getStringExtra(EXTRA_SEARCH_RESULT)?.let { result -> + val newUser = LinkedUser( + name = result, + phone = "13800000000", + idCard = "110101199001011234", + time = "2026-05-10 ${android.text.format.DateFormat.format("HH:mm:ss", System.currentTimeMillis())}" + ) + userList.add(0, newUser) + adapter.submitList(userList) + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/defenseapplication/liveVideo/LinkedUserSearchActivity.kt b/app/src/main/java/com/example/defenseapplication/liveVideo/LinkedUserSearchActivity.kt new file mode 100644 index 0000000..933789f --- /dev/null +++ b/app/src/main/java/com/example/defenseapplication/liveVideo/LinkedUserSearchActivity.kt @@ -0,0 +1,44 @@ +package com.example.defenseapplication.liveVideo + + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager +import com.example.defenseapplication.databinding.LinkedUserSearchActivityBinding +import com.gyf.immersionbar.ImmersionBar + +class LinkedUserSearchActivity : AppCompatActivity() { + + private lateinit var binding: LinkedUserSearchActivityBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = LinkedUserSearchActivityBinding.inflate(layoutInflater) + setContentView(binding.root) + + ImmersionBar.with(this) + .statusBarDarkFont(true) + .titleBar(binding.topBar) + .init() + + initListener() + initData() + } + + private fun initListener() { + binding.ivBack.setOnClickListener { + finish() + } + + binding.btnSearch.setOnClickListener { + initData() + } + } + + private fun initData() { + val searchText = binding.etSearch.text.toString().trim() + + binding.rvSearchResult.layoutManager = LinearLayoutManager(this) + + } +} \ No newline at end of file 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 b0cfaaf..5d77568 100644 --- a/app/src/main/java/com/example/defenseapplication/liveVideo/SettingsActivity.kt +++ b/app/src/main/java/com/example/defenseapplication/liveVideo/SettingsActivity.kt @@ -10,7 +10,7 @@ import com.example.defenseapplication.view.CustomDialogFragmentText import com.example.defenseapplication.view.LanguageDialog import com.example.defenseapplication.view.RecognitionModeDialog import com.gyf.immersionbar.ImmersionBar - +//设置页面 class SettingsActivity : BaseActivity() { private lateinit var binding: SettingsActivityBinding @@ -74,6 +74,10 @@ class SettingsActivity : BaseActivity() { val intent = Intent(this, DeviceInfoActivity::class.java) startActivity(intent) } + binding.itemRelatedUsers.setOnClickListener { + val intent = Intent(this, LinkedUserActivity::class.java) + startActivity( intent) + } } diff --git a/app/src/main/java/com/example/defenseapplication/view/CustomDialogFragment.kt b/app/src/main/java/com/example/defenseapplication/view/CustomDialogFragment.kt index 710a041..9f6dfef 100644 --- a/app/src/main/java/com/example/defenseapplication/view/CustomDialogFragment.kt +++ b/app/src/main/java/com/example/defenseapplication/view/CustomDialogFragment.kt @@ -23,6 +23,7 @@ class CustomDialogFragmentText : DialogFragment() { private var positiveText: String = "" private var showInput: Boolean = true private var onConfirmListener: ((String) -> Unit)? = null + private var onDismissListener: (() -> Unit)? = null fun setTitle(title: String): CustomDialogFragmentText { this.title = title @@ -63,6 +64,10 @@ class CustomDialogFragmentText : DialogFragment() { this.onConfirmListener = listener } + fun setOnDismissListener(listener: () -> Unit) { + this.onDismissListener = listener + } + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val dialog = super.onCreateDialog(savedInstanceState) dialog.window?.let { @@ -87,7 +92,6 @@ class CustomDialogFragmentText : DialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - // Set defaults if not explicitly configured if (negativeText.isEmpty()) negativeText = getString(R.string.cancel) if (positiveText.isEmpty()) positiveText = getString(R.string.confirm) binding.tvTitle.text = title @@ -112,6 +116,8 @@ class CustomDialogFragmentText : DialogFragment() { override fun onDismiss(dialog: DialogInterface) { super.onDismiss(dialog) + onDismissListener?.invoke() onConfirmListener = null + onDismissListener = null } } \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_user_manage.xml b/app/src/main/res/drawable/bg_user_manage.xml index 4c092f5..45e88fe 100644 --- a/app/src/main/res/drawable/bg_user_manage.xml +++ b/app/src/main/res/drawable/bg_user_manage.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/layout/item_linked_user.xml b/app/src/main/res/layout/item_linked_user.xml new file mode 100644 index 0000000..1c23c42 --- /dev/null +++ b/app/src/main/res/layout/item_linked_user.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_linked_user_search.xml b/app/src/main/res/layout/item_linked_user_search.xml new file mode 100644 index 0000000..05e8bbb --- /dev/null +++ b/app/src/main/res/layout/item_linked_user_search.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/linked_user_activity.xml b/app/src/main/res/layout/linked_user_activity.xml index 5036ec7..c9e982b 100644 --- a/app/src/main/res/layout/linked_user_activity.xml +++ b/app/src/main/res/layout/linked_user_activity.xml @@ -6,6 +6,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" + android:background="@color/bg_gray" tools:context=".liveVideo.LinkedUserActivity"> @@ -14,7 +15,8 @@ android:layout_width="match_parent" android:layout_height="56dp" android:gravity="center_vertical" - android:paddingHorizontal="16dp"> + android:paddingHorizontal="16dp" + android:background="@color/white"> + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/linked_user_search_activity.xml b/app/src/main/res/layout/linked_user_search_activity.xml new file mode 100644 index 0000000..c852a9d --- /dev/null +++ b/app/src/main/res/layout/linked_user_search_activity.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index d0c57d2..cce4d72 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -174,6 +174,11 @@ 中文 English 关联用户 + 搜索 + 搜索预设文案 + 添加关联用户 + 解除关联 + 确定解除该用户对本设备的关联? WiFi管理 关于 版本 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fe3a3f2..39c6d1c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -163,7 +163,12 @@ Language Chinese English - Linked User + Linked user + search + Search preset text + Add linked user + unlink + Unlink confirm message? WiFi Management About Version diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cfe9662..02718ff 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] agp = "9.1.1" +kotlin = "1.9.22" coreKtx = "1.10.1" junit = "4.13.2" junitVersion = "1.1.5" @@ -27,4 +28,6 @@ androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview" [plugins] android-application = { id = "com.android.application", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }