diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..7061a0d
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/juhabi/birds/custom/DeviceActionPopupWindow.kt b/app/src/main/java/com/juhabi/birds/custom/DeviceActionPopupWindow.kt
new file mode 100644
index 0000000..63eb3aa
--- /dev/null
+++ b/app/src/main/java/com/juhabi/birds/custom/DeviceActionPopupWindow.kt
@@ -0,0 +1,56 @@
+package com.juhabi.birds.custom
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.GradientDrawable
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.PopupWindow
+import com.juhabi.birds.R
+
+/** 设备操作菜单,提供重命名和解绑设备两个操作回调。 */
+class DeviceActionPopupWindow(context: Context) {
+ private val popupWindow: PopupWindow
+
+ init {
+ val content = LayoutInflater.from(context).inflate(R.layout.popup_device_action, null)
+ content.background = GradientDrawable().apply {
+ setColor(Color.rgb(82, 82, 82))
+ cornerRadius = 12f
+ }
+ popupWindow = PopupWindow(
+ content,
+ dp(context, 142),
+ dp(context, 96),
+ true
+ ).apply {
+ isOutsideTouchable = true
+ elevation = dp(context, 6).toFloat()
+ }
+ content.findViewById(R.id.action_rename).setOnClickListener {
+ popupWindow.dismiss()
+ renameAction?.invoke()
+ }
+ content.findViewById(R.id.action_unbind).setOnClickListener {
+ popupWindow.dismiss()
+ unbindAction?.invoke()
+ }
+ }
+
+ private var renameAction: (() -> Unit)? = null
+ private var unbindAction: (() -> Unit)? = null
+
+ /** 在指定锚点下方显示菜单,并设置两个操作的回调。 */
+ fun show(
+ anchor: View,
+ onRename: () -> Unit,
+ onUnbind: () -> Unit
+ ) {
+ renameAction = onRename
+ unbindAction = onUnbind
+ popupWindow.showAsDropDown(anchor, -dp(anchor.context, 112), -dp(anchor.context, 4))
+ }
+
+ private fun dp(context: Context, value: Int): Int =
+ (value * context.resources.displayMetrics.density).toInt()
+}
diff --git a/app/src/main/res/drawable/shape_login_button_bg.xml b/app/src/main/res/drawable/shape_login_button_bg.xml
new file mode 100644
index 0000000..536a512
--- /dev/null
+++ b/app/src/main/res/drawable/shape_login_button_bg.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/shape_login_field_bg.xml b/app/src/main/res/drawable/shape_login_field_bg.xml
new file mode 100644
index 0000000..bcce9b4
--- /dev/null
+++ b/app/src/main/res/drawable/shape_login_field_bg.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/shape_register_button_bg.xml b/app/src/main/res/drawable/shape_register_button_bg.xml
new file mode 100644
index 0000000..2d1cad2
--- /dev/null
+++ b/app/src/main/res/drawable/shape_register_button_bg.xml
@@ -0,0 +1 @@
+
diff --git a/app/src/main/res/drawable/shape_register_code_bg.xml b/app/src/main/res/drawable/shape_register_code_bg.xml
new file mode 100644
index 0000000..d200f51
--- /dev/null
+++ b/app/src/main/res/drawable/shape_register_code_bg.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/shape_register_code_disabled_bg.xml b/app/src/main/res/drawable/shape_register_code_disabled_bg.xml
new file mode 100644
index 0000000..8baa8b7
--- /dev/null
+++ b/app/src/main/res/drawable/shape_register_code_disabled_bg.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/shape_register_field_bg.xml b/app/src/main/res/drawable/shape_register_field_bg.xml
new file mode 100644
index 0000000..10c1592
--- /dev/null
+++ b/app/src/main/res/drawable/shape_register_field_bg.xml
@@ -0,0 +1 @@
+
diff --git a/app/src/main/res/drawable/shape_register_rule_bg.xml b/app/src/main/res/drawable/shape_register_rule_bg.xml
new file mode 100644
index 0000000..2b47f66
--- /dev/null
+++ b/app/src/main/res/drawable/shape_register_rule_bg.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file