关联用户

This commit is contained in:
2026-05-10 11:10:39 +08:00
parent 3df00ad493
commit 2c94ba767d
13 changed files with 584 additions and 9 deletions

View File

@@ -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<LinkedUserAdapter.LinkedUserViewHolder>() {
private val items = mutableListOf<LinkedUser>()
fun submitList(userList: List<LinkedUser>) {
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
}
}

View File

@@ -1,27 +1,199 @@
package com.example.defenseapplication.liveVideo package com.example.defenseapplication.liveVideo
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat import androidx.recyclerview.widget.ItemTouchHelper
import androidx.core.view.WindowInsetsCompat import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.defenseapplication.R 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.databinding.LinkedUserActivityBinding
import com.example.defenseapplication.view.CustomDialogFragmentText
import com.gyf.immersionbar.ImmersionBar import com.gyf.immersionbar.ImmersionBar
class LinkedUserActivity : AppCompatActivity() { class LinkedUserActivity : AppCompatActivity() {
private lateinit var binding: LinkedUserActivityBinding private lateinit var binding: LinkedUserActivityBinding
private lateinit var adapter: LinkedUserAdapter
private val userList = mutableListOf<LinkedUser>()
companion object {
const val REQUEST_CODE_SEARCH = 1001
const val EXTRA_SEARCH_RESULT = "search_result"
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = LinkedUserActivityBinding.inflate(layoutInflater) binding = LinkedUserActivityBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
ImmersionBar.with(this) ImmersionBar.with(this)
.statusBarDarkFont(true) .statusBarDarkFont(true)
.titleBar(binding.topBar) .titleBar(binding.topBar)
.init() .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<Int, Boolean>()
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 { binding.ivBack.setOnClickListener {
finish() 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)
}
}
} }
} }

View File

@@ -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)
}
}

View File

@@ -10,7 +10,7 @@ import com.example.defenseapplication.view.CustomDialogFragmentText
import com.example.defenseapplication.view.LanguageDialog import com.example.defenseapplication.view.LanguageDialog
import com.example.defenseapplication.view.RecognitionModeDialog import com.example.defenseapplication.view.RecognitionModeDialog
import com.gyf.immersionbar.ImmersionBar import com.gyf.immersionbar.ImmersionBar
//设置页面
class SettingsActivity : BaseActivity() { class SettingsActivity : BaseActivity() {
private lateinit var binding: SettingsActivityBinding private lateinit var binding: SettingsActivityBinding
@@ -74,6 +74,10 @@ class SettingsActivity : BaseActivity() {
val intent = Intent(this, DeviceInfoActivity::class.java) val intent = Intent(this, DeviceInfoActivity::class.java)
startActivity(intent) startActivity(intent)
} }
binding.itemRelatedUsers.setOnClickListener {
val intent = Intent(this, LinkedUserActivity::class.java)
startActivity( intent)
}
} }

View File

@@ -23,6 +23,7 @@ class CustomDialogFragmentText : DialogFragment() {
private var positiveText: String = "" private var positiveText: String = ""
private var showInput: Boolean = true private var showInput: Boolean = true
private var onConfirmListener: ((String) -> Unit)? = null private var onConfirmListener: ((String) -> Unit)? = null
private var onDismissListener: (() -> Unit)? = null
fun setTitle(title: String): CustomDialogFragmentText { fun setTitle(title: String): CustomDialogFragmentText {
this.title = title this.title = title
@@ -63,6 +64,10 @@ class CustomDialogFragmentText : DialogFragment() {
this.onConfirmListener = listener this.onConfirmListener = listener
} }
fun setOnDismissListener(listener: () -> Unit) {
this.onDismissListener = listener
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState) val dialog = super.onCreateDialog(savedInstanceState)
dialog.window?.let { dialog.window?.let {
@@ -87,7 +92,6 @@ class CustomDialogFragmentText : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// Set defaults if not explicitly configured
if (negativeText.isEmpty()) negativeText = getString(R.string.cancel) if (negativeText.isEmpty()) negativeText = getString(R.string.cancel)
if (positiveText.isEmpty()) positiveText = getString(R.string.confirm) if (positiveText.isEmpty()) positiveText = getString(R.string.confirm)
binding.tvTitle.text = title binding.tvTitle.text = title
@@ -112,6 +116,8 @@ class CustomDialogFragmentText : DialogFragment() {
override fun onDismiss(dialog: DialogInterface) { override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog) super.onDismiss(dialog)
onDismissListener?.invoke()
onConfirmListener = null onConfirmListener = null
onDismissListener = null
} }
} }

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#E9EBFD" /> <solid android:color="@color/setting_gray" />
<corners android:radius="30dp" /> <corners android:radius="30dp" />
</shape> </shape>

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- 背景删除按钮 -->
<LinearLayout
android:id="@+id/llDelete"
android:layout_width="100dp"
android:layout_height="120dp"
android:background="@color/red"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/unlink"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"/>
</LinearLayout>
<!-- 前景内容 -->
<LinearLayout
android:id="@+id/llContent"
android:layout_width="match_parent"
android:layout_height="120dp"
android:background="@color/white"
android:orientation="vertical"
android:padding="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tvUserName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="@color/black"
android:textSize="16sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/tvTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/gray"
android:textSize="12sp"/>
</LinearLayout>
<TextView
android:id="@+id/tvPhone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/gray"
android:textSize="14sp"
android:layout_marginTop="8dp"/>
<TextView
android:id="@+id/tvIdCard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/gray"
android:textSize="14sp"
android:layout_marginTop="4dp"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@color/white"
android:orientation="vertical"
android:padding="16dp"
android:layout_marginBottom="8dp">
<TextView
android:id="@+id/tvUserName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="16sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/tvPhone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/gray"
android:textSize="14sp"
android:layout_marginTop="8dp"/>
<TextView
android:id="@+id/tvIdCard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/gray"
android:textSize="14sp"
android:layout_marginTop="4dp"/>
</LinearLayout>
</LinearLayout>

View File

@@ -6,6 +6,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:background="@color/bg_gray"
tools:context=".liveVideo.LinkedUserActivity"> tools:context=".liveVideo.LinkedUserActivity">
<!-- 标题栏 --> <!-- 标题栏 -->
@@ -14,7 +15,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="56dp" android:layout_height="56dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:paddingHorizontal="16dp"> android:paddingHorizontal="16dp"
android:background="@color/white">
<ImageView <ImageView
android:id="@+id/ivBack" android:id="@+id/ivBack"
@@ -33,4 +35,63 @@
android:textStyle="bold" /> android:textStyle="bold" />
</LinearLayout> </LinearLayout>
<!-- 搜索区域 -->
<LinearLayout
android:id="@+id/searchLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp"
android:layout_marginTop="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/bg_user_manage"
android:gravity="center_vertical"
android:paddingHorizontal="16dp">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@drawable/search"
android:layout_marginRight="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/search_preset_text"
android:textColor="@color/gray"
android:textSize="14sp"/>
</LinearLayout>
</LinearLayout>
<!-- 列表区域 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvLinkedUser"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:paddingHorizontal="16dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"/>
<!-- 底部按钮 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingBottom="60dp">
<android.widget.Button
android:id="@+id/btnAddLinkedUser"
android:layout_width="match_parent"
android:layout_height="40dp"
android:text="@string/add_linked_user"
android:textColor="@color/white"
android:textSize="14dp"
android:background="@drawable/rounded_blue_bg" />
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/bg_gray"
tools:context=".liveVideo.LinkedUserSearchActivity">
<!-- 标题栏 -->
<LinearLayout
android:id="@+id/topBar"
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center_vertical"
android:paddingHorizontal="16dp"
android:background="@color/white">
<ImageView
android:id="@+id/ivBack"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/back" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="@string/search"
android:textColor="@color/black"
android:textSize="16dp"
android:textStyle="bold" />
<TextView
android:id="@+id/btnSearch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/search"
android:textColor="@color/green"
android:textSize="14sp"/>
</LinearLayout>
<!-- 搜索输入框 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp"
android:layout_marginTop="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/bg_user_manage"
android:gravity="center_vertical"
android:paddingHorizontal="16dp">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@drawable/search"
android:layout_marginRight="8dp"/>
<EditText
android:id="@+id/etSearch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/search_preset_text"
android:textColor="@color/black"
android:textColorHint="@color/gray"
android:textSize="14sp"
android:background="@null"/>
</LinearLayout>
</LinearLayout>
<!-- 搜索结果列表 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvSearchResult"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="16dp"
android:paddingTop="8dp"/>
</LinearLayout>

View File

@@ -174,6 +174,11 @@
<string name="chinese">中文</string> <string name="chinese">中文</string>
<string name="english">English</string> <string name="english">English</string>
<string name="linked_user">关联用户</string> <string name="linked_user">关联用户</string>
<string name="search">搜索</string>
<string name="search_preset_text">搜索预设文案</string>
<string name="add_linked_user">添加关联用户</string>
<string name="unlink">解除关联</string>
<string name="unlink_confirm_message">确定解除该用户对本设备的关联?</string>
<string name="wiFi_management">WiFi管理</string> <string name="wiFi_management">WiFi管理</string>
<string name="about">关于</string> <string name="about">关于</string>
<string name="version">版本</string> <string name="version">版本</string>

View File

@@ -163,7 +163,12 @@
<string name="language_settings">Language</string> <string name="language_settings">Language</string>
<string name="chinese">Chinese</string> <string name="chinese">Chinese</string>
<string name="english">English</string> <string name="english">English</string>
<string name="linked_user">Linked User</string> <string name="linked_user">Linked user</string>
<string name="search">search</string>
<string name="search_preset_text">Search preset text</string>
<string name="add_linked_user">Add linked user</string>
<string name="unlink">unlink</string>
<string name="unlink_confirm_message">Unlink confirm message?</string>
<string name="wiFi_management">WiFi Management</string> <string name="wiFi_management">WiFi Management</string>
<string name="about">About</string> <string name="about">About</string>
<string name="version">Version</string> <string name="version">Version</string>

View File

@@ -1,5 +1,6 @@
[versions] [versions]
agp = "9.1.1" agp = "9.1.1"
kotlin = "1.9.22"
coreKtx = "1.10.1" coreKtx = "1.10.1"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.1.5" junitVersion = "1.1.5"
@@ -27,4 +28,6 @@ androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview"
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } 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" }