From 8a5f599266846db90860fd9fdb040e192428d11b Mon Sep 17 00:00:00 2001 From: maxinyu Date: Tue, 28 Apr 2026 11:01:10 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A6=96=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 17 +- app/src/main/AndroidManifest.xml | 1 + .../defenseapplication/MyApplication.kt | 10 + .../adapter/FamilyAdapter.kt | 44 +++++ .../defenseapplication/bean/GetCodeBean.kt | 7 + .../defenseapplication/bean/LoginBean.kt | 17 ++ .../home/HomeMenuFragment.kt | 76 +++++++- .../login/ForgetPasswordActivity.kt | 2 +- .../defenseapplication/okhttp/ApiService.kt | 16 -- .../defenseapplication/utils/ApiService.kt | 14 ++ .../{okhttp => utils}/DataClass.kt | 2 +- .../defenseapplication/utils/HttpUtils.kt | 43 +++++ .../defenseapplication/utils/UserinfoUtil.kt | 27 +++ .../main/res/layout/home_menu_fragment.xml | 173 +++++++++++++++++- gradle/libs.versions.toml | 2 + 15 files changed, 419 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/com/example/defenseapplication/MyApplication.kt create mode 100644 app/src/main/java/com/example/defenseapplication/adapter/FamilyAdapter.kt create mode 100644 app/src/main/java/com/example/defenseapplication/bean/GetCodeBean.kt create mode 100644 app/src/main/java/com/example/defenseapplication/bean/LoginBean.kt delete mode 100644 app/src/main/java/com/example/defenseapplication/okhttp/ApiService.kt create mode 100644 app/src/main/java/com/example/defenseapplication/utils/ApiService.kt rename app/src/main/java/com/example/defenseapplication/{okhttp => utils}/DataClass.kt (68%) create mode 100644 app/src/main/java/com/example/defenseapplication/utils/HttpUtils.kt create mode 100644 app/src/main/java/com/example/defenseapplication/utils/UserinfoUtil.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3f0c202..2eba624 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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") + // OkHttp(Retrofit 会自动依赖 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") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58db0e4..4e1da14 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ android:required="true" /> Unit +) : RecyclerView.Adapter() { + + private val items = mutableListOf() + private var selectedFamily: String? = null + + fun submitList(familyList: List, 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) } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/defenseapplication/bean/GetCodeBean.kt b/app/src/main/java/com/example/defenseapplication/bean/GetCodeBean.kt new file mode 100644 index 0000000..780cce5 --- /dev/null +++ b/app/src/main/java/com/example/defenseapplication/bean/GetCodeBean.kt @@ -0,0 +1,7 @@ +package com.example.defenseapplication.bean + +data class GetCodeBean ( + val code:Int, + val msg:String, + val data:T +) \ No newline at end of file diff --git a/app/src/main/java/com/example/defenseapplication/bean/LoginBean.kt b/app/src/main/java/com/example/defenseapplication/bean/LoginBean.kt new file mode 100644 index 0000000..92e3a4d --- /dev/null +++ b/app/src/main/java/com/example/defenseapplication/bean/LoginBean.kt @@ -0,0 +1,17 @@ +package bean + +//登录-bean +data class Login( + 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 +) diff --git a/app/src/main/java/com/example/defenseapplication/home/HomeMenuFragment.kt b/app/src/main/java/com/example/defenseapplication/home/HomeMenuFragment.kt index 88e5b3a..e3f3aca 100644 --- a/app/src/main/java/com/example/defenseapplication/home/HomeMenuFragment.kt +++ b/app/src/main/java/com/example/defenseapplication/home/HomeMenuFragment.kt @@ -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 // 避免内存泄漏 } } \ No newline at end of file diff --git a/app/src/main/java/com/example/defenseapplication/login/ForgetPasswordActivity.kt b/app/src/main/java/com/example/defenseapplication/login/ForgetPasswordActivity.kt index 49cd003..3e6e72c 100644 --- a/app/src/main/java/com/example/defenseapplication/login/ForgetPasswordActivity.kt +++ b/app/src/main/java/com/example/defenseapplication/login/ForgetPasswordActivity.kt @@ -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 diff --git a/app/src/main/java/com/example/defenseapplication/okhttp/ApiService.kt b/app/src/main/java/com/example/defenseapplication/okhttp/ApiService.kt deleted file mode 100644 index b5eadfc..0000000 --- a/app/src/main/java/com/example/defenseapplication/okhttp/ApiService.kt +++ /dev/null @@ -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() - -} \ No newline at end of file diff --git a/app/src/main/java/com/example/defenseapplication/utils/ApiService.kt b/app/src/main/java/com/example/defenseapplication/utils/ApiService.kt new file mode 100644 index 0000000..6d7e224 --- /dev/null +++ b/app/src/main/java/com/example/defenseapplication/utils/ApiService.kt @@ -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 + + + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/defenseapplication/okhttp/DataClass.kt b/app/src/main/java/com/example/defenseapplication/utils/DataClass.kt similarity index 68% rename from app/src/main/java/com/example/defenseapplication/okhttp/DataClass.kt rename to app/src/main/java/com/example/defenseapplication/utils/DataClass.kt index a8014f3..f3e09a6 100644 --- a/app/src/main/java/com/example/defenseapplication/okhttp/DataClass.kt +++ b/app/src/main/java/com/example/defenseapplication/utils/DataClass.kt @@ -1,4 +1,4 @@ -package com.example.defenseapplication.okhttp +package com.example.defenseapplication.utils data class Post( val userId: Int, diff --git a/app/src/main/java/com/example/defenseapplication/utils/HttpUtils.kt b/app/src/main/java/com/example/defenseapplication/utils/HttpUtils.kt new file mode 100644 index 0000000..ced5f36 --- /dev/null +++ b/app/src/main/java/com/example/defenseapplication/utils/HttpUtils.kt @@ -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) + + + + + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/defenseapplication/utils/UserinfoUtil.kt b/app/src/main/java/com/example/defenseapplication/utils/UserinfoUtil.kt new file mode 100644 index 0000000..63ffd18 --- /dev/null +++ b/app/src/main/java/com/example/defenseapplication/utils/UserinfoUtil.kt @@ -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) + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/home_menu_fragment.xml b/app/src/main/res/layout/home_menu_fragment.xml index 2acd362..4488f81 100644 --- a/app/src/main/res/layout/home_menu_fragment.xml +++ b/app/src/main/res/layout/home_menu_fragment.xml @@ -1,13 +1,174 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + 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"> + + + + + \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 24a8ed4..deeecec 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" }