This commit is contained in:
2026-04-28 11:01:10 +08:00
parent 1b62e4d264
commit 8a5f599266
15 changed files with 419 additions and 32 deletions

View File

@@ -45,17 +45,20 @@ dependencies {
implementation(libs.material)
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
implementation("androidx.recyclerview:recyclerview:1.3.2")
implementation(libs.firebase.crashlytics.buildtools)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
// RecyclerView
implementation("androidx.recyclerview:recyclerview:1.3.2")
// OkHttp
// Retrofit
implementation("com.squareup.retrofit2:retrofit:2.9.0")
// Gson 转换器
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
// OkHttpRetrofit 会自动依赖 OkHttp但显式指定版本也可以
implementation("com.squareup.okhttp3:okhttp:4.12.0")
// Gson (用于解析 JSON)
implementation("com.google.code.gson:gson:2.10.1")
// OkHttp 日志拦截器
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
implementation("com.blankj:utilcodex:1.31.1")
// Kotlin 协程
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")

View File

@@ -9,6 +9,7 @@
android:required="true" />
<application
android:name=".MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"

View File

@@ -0,0 +1,10 @@
package com.example.defenseapplication
import android.app.Application
import com.blankj.utilcode.util.Utils;
class MyApplication : Application(){
override fun onCreate() {
super.onCreate()
Utils.init(this) // 初始化 AndroidUtilCode
}
}

View File

@@ -0,0 +1,44 @@
package com.example.defenseapplication.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.defenseapplication.R
import com.example.defenseapplication.databinding.ItemFamilyBinding
class FamilyAdapter(
private val onItemClick: (String) -> Unit
) : RecyclerView.Adapter<FamilyAdapter.FamilyViewHolder>() {
private val items = mutableListOf<String>()
private var selectedFamily: String? = null
fun submitList(familyList: List<String>, currentFamily: String) {
items.clear()
items.addAll(familyList)
selectedFamily = currentFamily
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FamilyViewHolder {
val binding = ItemFamilyBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return FamilyViewHolder(binding)
}
override fun onBindViewHolder(holder: FamilyViewHolder, position: Int) {
holder.bind(items[position])
}
override fun getItemCount(): Int = items.size
inner class FamilyViewHolder(private val binding: ItemFamilyBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: String) {
binding.tvFamilyName.text = item
val textColor = if (item == selectedFamily) R.color.green else R.color.black
binding.tvFamilyName.setTextColor(binding.root.context.getColor(textColor))
binding.root.setOnClickListener { onItemClick(item) }
}
}
}

View File

@@ -0,0 +1,7 @@
package com.example.defenseapplication.bean
data class GetCodeBean<T> (
val code:Int,
val msg:String,
val data:T
)

View File

@@ -0,0 +1,17 @@
package bean
//登录-bean
data class Login<T>(
val message: String,
val result: T,
val status: String
)
data class LoginBean(
val headPic: String,
val nickName: String,
val sex: Int,
val signature: String,
val token: String,
val userId: Int
)

View File

@@ -5,13 +5,39 @@ import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.example.defenseapplication.R
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.defenseapplication.adapter.DeviceAdapter
import com.example.defenseapplication.adapter.DeviceUi
import com.example.defenseapplication.adapter.FamilyAdapter
import com.example.defenseapplication.databinding.HomeMenuFragmentBinding
class HomeMenuFragment : Fragment() {
// ViewBinding
private var _binding: HomeMenuFragmentBinding? = null
private val binding get() = _binding!!
private val familyAdapter by lazy { FamilyAdapter(::onFamilySelected) }
private val deviceAdapter by lazy { DeviceAdapter() }
private var isFamilyExpanded = false
private var currentFamily = "我的家庭"
private val familyList = listOf("我的家庭", "工作室", "父母家")
private val familyDeviceMap = mapOf(
"我的家庭" to listOf(
DeviceUi("智能挂式防御机", true),
DeviceUi("智能防御机", false),
DeviceUi("智能轨道防御机", false)
),
"工作室" to listOf(
DeviceUi("走廊防御机", true),
DeviceUi("门禁防御机", true)
),
"父母家" to listOf(
DeviceUi("客厅防御机", false),
DeviceUi("阳台防御机", true)
)
)
override fun onCreateView(
inflater: LayoutInflater,
@@ -19,11 +45,59 @@ class HomeMenuFragment : Fragment() {
savedInstanceState: Bundle?
): View {
_binding = HomeMenuFragmentBinding.inflate(inflater, container, false)
initView()
return binding.root
}
private fun initView() {
binding.rvFamilyList.layoutManager = LinearLayoutManager(requireContext())
binding.rvFamilyList.adapter = familyAdapter
familyAdapter.submitList(familyList, currentFamily)
binding.rvDeviceList.layoutManager = GridLayoutManager(requireContext(), 2)
binding.rvDeviceList.adapter = deviceAdapter
updateDeviceList()
binding.tvCurrentFamily.text = currentFamily
binding.familyHeaderLayout.setOnClickListener {
toggleFamilyList()
}
binding.familyPopupMask.setOnClickListener {
if (isFamilyExpanded) {
toggleFamilyList()
}
}
}
private fun toggleFamilyList() {
isFamilyExpanded = !isFamilyExpanded
val visibility = if (isFamilyExpanded) View.VISIBLE else View.GONE
binding.familyPopupMask.visibility = visibility
binding.familyPopupCard.visibility = visibility
binding.ivFamilyArrow.animate()
.rotation(if (isFamilyExpanded) 180f else 0f)
.setDuration(200L)
.start()
}
private fun onFamilySelected(familyName: String) {
currentFamily = familyName
binding.tvCurrentFamily.text = familyName
familyAdapter.submitList(familyList, currentFamily)
updateDeviceList()
if (isFamilyExpanded) {
toggleFamilyList()
}
}
private fun updateDeviceList() {
deviceAdapter.submitList(familyDeviceMap[currentFamily].orEmpty())
}
override fun onDestroyView() {
super.onDestroyView()
binding.rvFamilyList.adapter = null
binding.rvDeviceList.adapter = null
_binding = null // 避免内存泄漏
}
}

View File

@@ -3,7 +3,6 @@ package com.example.defenseapplication.login
import android.os.Bundle
import android.os.CountDownTimer
import android.text.InputType
import android.text.TextUtils
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
@@ -12,6 +11,7 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.example.defenseapplication.R
import com.example.defenseapplication.databinding.ForgetPasswordActivityBinding
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.util.TextUtils
import java.util.regex.Pattern
import kotlin.random.Random

View File

@@ -1,16 +0,0 @@
package com.example.defenseapplication.okhttp
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.IOException
object ApiService {
private const val BASE_URL = "https://stem-boozy-margarine.ngrok-free.dev"
private val client = OkHttpClient()
private val gson = Gson()
}

View File

@@ -0,0 +1,14 @@
package com.example.defenseapplication.utils
import com.google.gson.Gson
import okhttp3.OkHttpClient
import retrofit2.http.GET
interface ApiService {
// 获取登录注册验证码
@GET("/resource/sms/code")
suspend fun getCode(): String
}

View File

@@ -1,4 +1,4 @@
package com.example.defenseapplication.okhttp
package com.example.defenseapplication.utils
data class Post(
val userId: Int,

View File

@@ -0,0 +1,43 @@
package com.example.defenseapplication.utils
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
//网络工具类
object HttpUtils {
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.writeTimeout(3, TimeUnit.MINUTES)
.readTimeout(3, TimeUnit.MINUTES)
.addInterceptor(object : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val s = UserinfoUtil.getUserInfo()
val aa = chain.request().newBuilder()
.addHeader("token", s?.token ?: "")
.addHeader("userId", "${s?.userId ?: 0}")
.build()
return chain.proceed(aa)
}
})
.build()
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.baseUrl("https://stem-boozy-margarine.ngrok-free.dev")
.build()
val api = retrofit.create(ApiService::class.java)
}

View File

@@ -0,0 +1,27 @@
package com.example.defenseapplication.utils
import bean.LoginBean
import com.blankj.utilcode.util.SPUtils
import com.google.gson.Gson
//存储登录的token和userid
object UserinfoUtil {
private const val KEY_USER = "NAME_USER"
private val gson = Gson()
fun saveUserInfo(userinfo: LoginBean?) {
userinfo ?: return
val json = gson.toJson(userinfo)
SPUtils.getInstance().put(KEY_USER, json)
}
fun getUserInfo(): LoginBean? {
val string = SPUtils.getInstance().getString(KEY_USER)
if (string.isNullOrEmpty()) {
return null
}
return gson.fromJson(string, LoginBean::class.java)
}
}

View File

@@ -1,13 +1,174 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout 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:background="#F9F9F9 "
tools:context=".home.HomeMenuFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="16dp"
android:paddingTop="20dp"
android:paddingBottom="12dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/familyHeaderLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/tvCurrentFamily"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="首页 Fragment"
android:textSize="20sp"/>
</LinearLayout>
android:text="我的家庭"
android:textColor="@color/black"
android:textSize="20dp"
android:textStyle="bold" />
<ImageView
android:id="@+id/ivFamilyArrow"
android:layout_width="12dp"
android:layout_height="6dp"
android:layout_marginStart="6dp"
android:src="@drawable/triangle" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<FrameLayout
android:layout_width="38dp"
android:layout_height="38dp"
android:layout_marginEnd="10dp"
android:background="@drawable/bg_home_top_icon_light"
android:padding="7dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/message"/>
<View
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_gravity="end|top"
android:background="@drawable/ic_red_dot" />
</FrameLayout>
<FrameLayout
android:layout_width="38dp"
android:layout_height="38dp"
android:layout_marginEnd="10dp"
android:background="@drawable/bg_home_top_icon_light"
android:padding="7dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/scan_icon"/>
</FrameLayout>
<FrameLayout
android:layout_width="38dp"
android:layout_height="38dp"
android:background="@drawable/bg_home_top_icon_green"
android:padding="7dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/add"/>
</FrameLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/tvDeviceTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="14dp"
android:layout_marginEnd="16dp"
android:text="设备"
android:textColor="@color/black"
android:textSize="16dp"
android:textStyle="bold" />
<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:columnCount="2"
android:orientation="vertical"
android:rowCount="1" >
</GridLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvDeviceList"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
android:layout_weight="1"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:paddingBottom="16dp"
tools:itemCount="3"
tools:listitem="@layout/item_device" />
</LinearLayout>
<View
android:id="@+id/familyPopupMask"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#4D000000"
android:clickable="true"
android:focusable="true"
android:visibility="gone" />
<LinearLayout
android:id="@+id/familyPopupCard"
android:layout_width="180dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="62dp"
android:background="@drawable/textinput_white_bg"
android:elevation="8dp"
android:orientation="vertical"
android:visibility="gone"
android:padding="10dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFamilyList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
tools:itemCount="3"
tools:listitem="@layout/item_family" />
</LinearLayout>
</FrameLayout>

View File

@@ -8,6 +8,7 @@ appcompat = "1.6.1"
material = "1.10.0"
activity = "1.8.0"
constraintlayout = "2.1.4"
firebaseCrashlyticsBuildtools = "3.0.7"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -18,6 +19,7 @@ androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
firebase-crashlytics-buildtools = { group = "com.google.firebase", name = "firebase-crashlytics-buildtools", version.ref = "firebaseCrashlyticsBuildtools" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }