ソースを参照

add(course): add course page

zhaoyadi 1 年間 前
コミット
ad6ee4264e
37 ファイル変更496 行追加264 行削除
  1. 1 0
      core/http/build.gradle.kts
  2. 11 1
      core/http/src/main/kotlin/com/zaojiao/app/core/http/di/HttpModule.kt
  3. 7 0
      data/model/src/main/kotlin/com/zaojiao/app/data/model/common/CourseCategoryModel.kt
  4. 12 0
      data/model/src/main/kotlin/com/zaojiao/app/data/model/course/CourseModel.kt
  5. 3 1
      data/model/src/main/kotlin/com/zaojiao/app/data/model/micropage/MicroPageConfigModel.kt
  6. 19 0
      data/model/src/main/kotlin/com/zaojiao/app/data/model/micropage/MicroPageImageModel.kt
  7. 36 1
      data/model/src/main/kotlin/com/zaojiao/app/data/model/micropage/MicroPageJsonModel.kt
  8. 6 0
      data/model/src/main/kotlin/com/zaojiao/app/data/model/micropage/MicroPageTitleModel.kt
  9. 1 1
      data/model/src/main/kotlin/com/zaojiao/app/data/model/studyplan/StudyPlanCourseModel.kt
  10. 27 4
      data/remote/src/main/kotlin/com/zaojiao/app/data/remote/RemoteCourseData.kt
  11. 15 5
      data/remote/src/main/kotlin/com/zaojiao/app/data/remote/api/CourseApi.kt
  12. 14 0
      data/repo/src/main/kotlin/com/zaojiao/app/data/repo/CourseRepository.kt
  13. 4 3
      data/repo/src/main/kotlin/com/zaojiao/app/data/repo/StudyPlanRepository.kt
  14. 8 0
      data/repo/src/main/kotlin/com/zaojiao/app/data/repo/di/RepoModule.kt
  15. 29 0
      data/repo/src/main/kotlin/com/zaojiao/app/data/repo/impl/CourseRepositoryImpl.kt
  16. 4 3
      data/repo/src/main/kotlin/com/zaojiao/app/data/repo/impl/StudyPlanRepositoryImpl.kt
  17. 7 1
      feat/design/src/main/kotlin/com/zaojiao/app/feat/design/Images.kt
  18. 1 15
      feat/design/src/main/kotlin/com/zaojiao/app/feat/design/StatePage.kt
  19. 2 1
      feat/design/src/main/kotlin/com/zaojiao/app/feat/design/Swiper.kt
  20. 20 83
      feat/design/src/main/kotlin/com/zaojiao/app/feat/design/TabBar.kt
  21. 1 4
      feat/design/src/main/kotlin/com/zaojiao/app/feat/design/viewmodel/BaseViewModel.kt
  22. 18 9
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCourseCategory.kt
  23. 37 35
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCourseGroupBuy.kt
  24. 70 42
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCoursePage.kt
  25. 15 7
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCourseRecommend.kt
  26. 7 12
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCourseTopSwiper.kt
  27. 55 2
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCourseViewModel.kt
  28. 0 1
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/plan/state/HomePlanIndexUiState.kt
  29. 59 28
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/plan/total/HomePlanTotalPage.kt
  30. 6 5
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/plan/total/HomePlanTotalViewModel.kt
  31. BIN
      feat/home/src/main/res/mipmap-xhdpi/study_plan_hide_category.png
  32. BIN
      feat/home/src/main/res/mipmap-xhdpi/study_plan_show_category.png
  33. BIN
      feat/home/src/main/res/mipmap-xxhdpi/study_plan_hide_category.png
  34. BIN
      feat/home/src/main/res/mipmap-xxhdpi/study_plan_show_category.png
  35. BIN
      feat/home/src/main/res/mipmap-xxxhdpi/study_plan_hide_category.png
  36. BIN
      feat/home/src/main/res/mipmap-xxxhdpi/study_plan_show_category.png
  37. 1 0
      gradle/libs.versions.toml

+ 1 - 0
core/http/build.gradle.kts

@@ -14,6 +14,7 @@ dependencies {
 
     implementation(libs.coil.kt)
     implementation(libs.coil.kt.svg)
+    implementation(libs.coil.kt.gif)
 
     implementation("org.jetbrains.kotlin:kotlin-reflect:${libs.versions.kotlin.get()}")
 

+ 11 - 1
core/http/src/main/kotlin/com/zaojiao/app/core/http/di/HttpModule.kt

@@ -1,7 +1,10 @@
 package com.zaojiao.app.core.http.di
 
 import android.content.Context
+import android.os.Build
 import coil.ImageLoader
+import coil.decode.GifDecoder
+import coil.decode.ImageDecoderDecoder
 import coil.decode.SvgDecoder
 import com.squareup.moshi.Moshi
 import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter
@@ -108,7 +111,14 @@ object HttpModule {
 
         return ImageLoader.Builder(application)
             .callFactory(okHttpClient)
-            .components { add(SvgDecoder.Factory()) }
+            .components {
+                add(SvgDecoder.Factory())
+                if (Build.VERSION.SDK_INT >= 28) {
+                    add(ImageDecoderDecoder.Factory())
+                } else {
+                    add(GifDecoder.Factory())
+                }
+            }
             .respectCacheHeaders(false)
             .build()
     }

+ 7 - 0
data/model/src/main/kotlin/com/zaojiao/app/data/model/common/CourseCategoryModel.kt

@@ -0,0 +1,7 @@
+package com.zaojiao.app.data.model.common
+
+data class CourseCategoryModel(
+    val id: String,
+    val name: String,
+    val iconImg: String,
+)

+ 12 - 0
data/model/src/main/kotlin/com/zaojiao/app/data/model/course/CourseModel.kt

@@ -0,0 +1,12 @@
+package com.zaojiao.app.data.model.course
+
+import com.zaojiao.app.core.json.adapter.StateInt
+
+data class CourseModel(
+    val id: String,
+    val name: String,
+    val imgCover: String?,
+    val price: Double,
+    val markingPrice: Double,
+    @StateInt val hasPaid: Boolean,
+)

+ 3 - 1
data/model/src/main/kotlin/com/zaojiao/app/data/model/micropage/MicroPageConfigModel.kt

@@ -1,7 +1,9 @@
 package com.zaojiao.app.data.model.micropage
 
+import kotlinx.serialization.Serializable
+
+@Serializable
 data class MicroPageConfigModel(
-    val type: String,
     val title: String,
     val description: String,
     val category: String,

+ 19 - 0
data/model/src/main/kotlin/com/zaojiao/app/data/model/micropage/MicroPageImageModel.kt

@@ -0,0 +1,19 @@
+package com.zaojiao.app.data.model.micropage
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class MicroPageImageModel(
+    val imageList: List<MicroPageImageModelItem>,
+    val indicator: Int,
+) : MicroPageJsonModel
+
+@Serializable
+data class MicroPageImageModelItem(
+    val jumpPath: String,
+    val link: String,
+    val mainColor: String,
+    val title: String,
+    val type: Int,
+    val url: String
+)

+ 36 - 1
data/model/src/main/kotlin/com/zaojiao/app/data/model/micropage/MicroPageJsonModel.kt

@@ -1,3 +1,38 @@
 package com.zaojiao.app.data.model.micropage
 
-sealed interface MicroPageJsonModel
+import kotlinx.serialization.json.Json
+import org.json.JSONArray
+
+sealed interface MicroPageJsonModel
+
+fun String.toMicroPageJsonModel(): List<MicroPageJsonModel> {
+    val result = mutableListOf<MicroPageJsonModel>()
+
+    val jsonArray = JSONArray(this)
+    val jsonDecoder = Json {
+        ignoreUnknownKeys = true
+        coerceInputValues = true
+    }
+
+    for (i in 0 until jsonArray.length()) {
+        val item = jsonArray.getJSONObject(i)
+        val type = item.getString("type")
+        val data = item.getString("dataField")
+
+        when (type) {
+            "config" -> {
+                result.add(jsonDecoder.decodeFromString<MicroPageConfigModel>(data))
+            }
+
+            "image_ad" -> {
+                result.add(jsonDecoder.decodeFromString<MicroPageImageModel>(data))
+            }
+
+            else -> {
+
+            }
+        }
+    }
+
+    return result
+}

+ 6 - 0
data/model/src/main/kotlin/com/zaojiao/app/data/model/micropage/MicroPageTitleModel.kt

@@ -1,5 +1,11 @@
 package com.zaojiao.app.data.model.micropage
 
+import kotlinx.serialization.Serializable
+
+@Serializable
 data class MicroPageTitleModel(
     val title: String,
+    val description: String,
+    val category: String,
+    val gmtStart: String,
 ) : MicroPageJsonModel

+ 1 - 1
data/model/src/main/kotlin/com/zaojiao/app/data/model/studyplan/StudyPlanCourseItemModel.kt → data/model/src/main/kotlin/com/zaojiao/app/data/model/studyplan/StudyPlanCourseModel.kt

@@ -1,6 +1,6 @@
 package com.zaojiao.app.data.model.studyplan
 
-data class StudyPlanCourseItemModel(
+data class StudyPlanCourseModel(
     val completeItemQuantity: Int,
     val courseImgCover: String?,
     val courseImgCoverMini: String?,

+ 27 - 4
data/remote/src/main/kotlin/com/zaojiao/app/data/remote/RemoteCourseData.kt

@@ -4,7 +4,8 @@ import androidx.paging.Pager
 import androidx.paging.PagingConfig
 import androidx.paging.PagingData
 import com.zaojiao.app.core.http.common.NetPageResult
-import com.zaojiao.app.data.model.studyplan.StudyPlanCourseItemModel
+import com.zaojiao.app.data.model.course.CourseModel
+import com.zaojiao.app.data.model.studyplan.StudyPlanCourseModel
 import com.zaojiao.app.data.model.studyplan.StudyPlanCoursePageRequest
 import com.zaojiao.app.data.remote.api.CourseApi
 import com.zaojiao.app.data.remote.paging.BasingPagingResource
@@ -19,6 +20,20 @@ class RemoteCourseData @Inject constructor(
 ) {
     private val courseApi = retrofit.create(CourseApi::class.java)
 
+    suspend fun getIndexBanner() = courseApi.getIndexBanner().data
+
+    suspend fun getRecommendCourse(): Flow<PagingData<CourseModel>> {
+        return Pager(
+            config = PagingConfig(
+                pageSize = 10,
+                enablePlaceholders = false,
+            ),
+            pagingSourceFactory = {
+                RecommendCoursePagingSource(courseApi)
+            },
+        ).flow
+    }
+
     suspend fun getAllTypeCategory() = courseApi.getAllTypeCategory().data
 
     suspend fun getAllMediaCategory() = courseApi.getAllMediaCategory().data
@@ -30,7 +45,7 @@ class RemoteCourseData @Inject constructor(
         mediaType: String?,
         sortType: String,
         courseType: String,
-    ): Flow<PagingData<StudyPlanCourseItemModel>> {
+    ): Flow<PagingData<StudyPlanCourseModel>> {
         return Pager(
             config = PagingConfig(
                 pageSize = 10,
@@ -44,13 +59,21 @@ class RemoteCourseData @Inject constructor(
         ).flow
     }
 
+    class RecommendCoursePagingSource(
+        private val courseApi: CourseApi,
+    ) : BasingPagingResource<CourseModel>() {
+        override suspend fun getData(page: Int): NetPageResult<CourseModel> {
+            return courseApi.getRecommendCourse(page, 10)
+        }
+    }
+
     class StudyPlanCoursePagingSource(
         private val courseApi: CourseApi,
         private val mediaType: String?,
         private val sortType: String,
         private val courseType: String,
-    ) : BasingPagingResource<StudyPlanCourseItemModel>() {
-        override suspend fun getData(page: Int): NetPageResult<StudyPlanCourseItemModel> {
+    ) : BasingPagingResource<StudyPlanCourseModel>() {
+        override suspend fun getData(page: Int): NetPageResult<StudyPlanCourseModel> {
             return courseApi.getCourseByCategory(
                 StudyPlanCoursePageRequest(page, 10, mediaType, sortType, courseType)
             )

+ 15 - 5
data/remote/src/main/kotlin/com/zaojiao/app/data/remote/api/CourseApi.kt

@@ -3,16 +3,26 @@ package com.zaojiao.app.data.remote.api
 import com.zaojiao.app.core.http.common.NetPageResult
 import com.zaojiao.app.core.http.common.NetResult
 import com.zaojiao.app.data.model.common.CategoryModel
+import com.zaojiao.app.data.model.common.CourseCategoryModel
 import com.zaojiao.app.data.model.common.MediaCategoryModel
-import com.zaojiao.app.data.model.studyplan.StudyPlanCourseItemModel
+import com.zaojiao.app.data.model.course.CourseModel
+import com.zaojiao.app.data.model.studyplan.StudyPlanCourseModel
 import com.zaojiao.app.data.model.studyplan.StudyPlanCoursePageRequest
 import retrofit2.http.Body
-import retrofit2.http.Field
-import retrofit2.http.FormUrlEncoded
 import retrofit2.http.GET
 import retrofit2.http.POST
+import retrofit2.http.Query
 
 interface CourseApi {
+    @GET("/app/page/key/APP_SPACE_EXPLORATION_PHONE")
+    suspend fun getIndexBanner(): NetResult<String>
+
+    @GET("/app/app/course/recommend/v2")
+    suspend fun getRecommendCourse(
+        @Query("page") page: Int,
+        @Query("pageSize") pageSize: Int,
+    ): NetPageResult<CourseModel>
+
     @GET("/app/app/course/media/list")
     suspend fun getAllMediaCategory(): NetResult<List<MediaCategoryModel>>
 
@@ -20,7 +30,7 @@ interface CourseApi {
     suspend fun getAllMediaCategoryV2(): NetResult<List<MediaCategoryModel>>
 
     @GET("/app/app/course/categoryAll")
-    suspend fun getAllTypeCategory(): NetResult<List<CategoryModel>>
+    suspend fun getAllTypeCategory(): NetResult<List<CourseCategoryModel>>
 
     @GET("/app/app/course/age/list")
     suspend fun getAllAgeCategory(): NetResult<List<String>>
@@ -28,5 +38,5 @@ interface CourseApi {
     @POST("/app/ai/course/page")
     suspend fun getCourseByCategory(
         @Body request: StudyPlanCoursePageRequest,
-    ): NetPageResult<StudyPlanCourseItemModel>
+    ): NetPageResult<StudyPlanCourseModel>
 }

+ 14 - 0
data/repo/src/main/kotlin/com/zaojiao/app/data/repo/CourseRepository.kt

@@ -0,0 +1,14 @@
+package com.zaojiao.app.data.repo
+
+import androidx.paging.PagingData
+import com.zaojiao.app.data.model.common.CourseCategoryModel
+import com.zaojiao.app.data.model.course.CourseModel
+import kotlinx.coroutines.flow.Flow
+
+interface CourseRepository {
+    suspend fun getIndexBanner(): String
+
+    suspend fun getCategoryList(): List<CourseCategoryModel>
+
+    suspend fun getRecommendCourse(): Flow<PagingData<CourseModel>>
+}

+ 4 - 3
data/repo/src/main/kotlin/com/zaojiao/app/data/repo/StudyPlanRepository.kt

@@ -2,9 +2,10 @@ package com.zaojiao.app.data.repo
 
 import androidx.paging.PagingData
 import com.zaojiao.app.data.model.common.CategoryModel
+import com.zaojiao.app.data.model.common.CourseCategoryModel
 import com.zaojiao.app.data.model.common.MediaCategoryModel
 import com.zaojiao.app.data.model.common.SortCategoryModel
-import com.zaojiao.app.data.model.studyplan.StudyPlanCourseItemModel
+import com.zaojiao.app.data.model.studyplan.StudyPlanCourseModel
 import com.zaojiao.app.data.model.studyplan.StudyPlanHistoryModel
 import com.zaojiao.app.data.model.studyplan.StudyPlanTodayModel
 import com.zaojiao.app.data.model.studyplan.StudyPlanTomorrowModel
@@ -30,11 +31,11 @@ interface StudyPlanRepository {
 
     suspend fun getCourseSortCategory(): List<SortCategoryModel>
 
-    suspend fun getCourseTypeCategory(): List<CategoryModel>
+    suspend fun getCourseTypeCategory(): List<CourseCategoryModel>
 
     suspend fun getStudyPlanCourse(
         mediaType: String?,
         sortType: String,
         courseType: String,
-    ): Flow<PagingData<StudyPlanCourseItemModel>>
+    ): Flow<PagingData<StudyPlanCourseModel>>
 }

+ 8 - 0
data/repo/src/main/kotlin/com/zaojiao/app/data/repo/di/RepoModule.kt

@@ -1,10 +1,12 @@
 package com.zaojiao.app.data.repo.di
 
 import com.zaojiao.app.data.repo.BabyRepository
+import com.zaojiao.app.data.repo.CourseRepository
 import com.zaojiao.app.data.repo.HomeRepository
 import com.zaojiao.app.data.repo.StudyPlanRepository
 import com.zaojiao.app.data.repo.UserRepository
 import com.zaojiao.app.data.repo.impl.BabyRepositoryImpl
+import com.zaojiao.app.data.repo.impl.CourseRepositoryImpl
 import com.zaojiao.app.data.repo.impl.HomeRepositoryImpl
 import com.zaojiao.app.data.repo.impl.StudyPlanRepositoryImpl
 import com.zaojiao.app.data.repo.impl.UserRepositoryImpl
@@ -35,6 +37,12 @@ interface RepoModule {
         homeRepositoryImpl: HomeRepositoryImpl,
     ): HomeRepository
 
+    @Binds
+    @Singleton
+    fun bindsCourseRepository(
+        courseRepositoryImpl: CourseRepositoryImpl,
+    ): CourseRepository
+
     @Binds
     @Singleton
     fun bindsStudyPlanRepository(

+ 29 - 0
data/repo/src/main/kotlin/com/zaojiao/app/data/repo/impl/CourseRepositoryImpl.kt

@@ -0,0 +1,29 @@
+package com.zaojiao.app.data.repo.impl
+
+import androidx.paging.PagingData
+import com.zaojiao.app.data.model.common.CourseCategoryModel
+import com.zaojiao.app.data.model.course.CourseModel
+import com.zaojiao.app.data.remote.RemoteCourseData
+import com.zaojiao.app.data.repo.CourseRepository
+import kotlinx.coroutines.flow.Flow
+import java.lang.IllegalArgumentException
+import javax.inject.Inject
+import javax.inject.Singleton
+
+class CourseRepositoryImpl @Inject constructor(
+    private val remoteCourseData: RemoteCourseData,
+) : CourseRepository {
+    override suspend fun getIndexBanner(): String {
+        return remoteCourseData.getIndexBanner() ?: throw IllegalArgumentException("")
+    }
+
+    override suspend fun getCategoryList(): List<CourseCategoryModel> {
+        return remoteCourseData.getAllTypeCategory().run {
+            this ?: throw IllegalArgumentException()
+        }
+    }
+
+    override suspend fun getRecommendCourse(): Flow<PagingData<CourseModel>> {
+        return remoteCourseData.getRecommendCourse()
+    }
+}

+ 4 - 3
data/repo/src/main/kotlin/com/zaojiao/app/data/repo/impl/StudyPlanRepositoryImpl.kt

@@ -3,9 +3,10 @@ package com.zaojiao.app.data.repo.impl
 import androidx.paging.PagingData
 import com.zaojiao.app.core.common.remote.di.ApplicationScope
 import com.zaojiao.app.data.model.common.CategoryModel
+import com.zaojiao.app.data.model.common.CourseCategoryModel
 import com.zaojiao.app.data.model.common.MediaCategoryModel
 import com.zaojiao.app.data.model.common.SortCategoryModel
-import com.zaojiao.app.data.model.studyplan.StudyPlanCourseItemModel
+import com.zaojiao.app.data.model.studyplan.StudyPlanCourseModel
 import com.zaojiao.app.data.model.studyplan.StudyPlanHistoryModel
 import com.zaojiao.app.data.model.studyplan.StudyPlanTodayModel
 import com.zaojiao.app.data.model.studyplan.StudyPlanTomorrowModel
@@ -66,7 +67,7 @@ class StudyPlanRepositoryImpl @Inject constructor(
         )
     }
 
-    override suspend fun getCourseTypeCategory(): List<CategoryModel> {
+    override suspend fun getCourseTypeCategory(): List<CourseCategoryModel> {
         return remoteCourseData.getAllTypeCategory().run {
             this ?: throw IllegalArgumentException("")
         }
@@ -76,7 +77,7 @@ class StudyPlanRepositoryImpl @Inject constructor(
         mediaType: String?,
         sortType: String,
         courseType: String,
-    ): Flow<PagingData<StudyPlanCourseItemModel>> {
+    ): Flow<PagingData<StudyPlanCourseModel>> {
         return remoteCourseData.getCourseByCategory(mediaType, sortType, courseType)
     }
 }

+ 7 - 1
feat/design/src/main/kotlin/com/zaojiao/app/feat/design/Images.kt

@@ -7,8 +7,11 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.painter.ColorPainter
 import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.painterResource
+import coil.ImageLoader
 import coil.compose.AsyncImage
+import coil.request.ImageRequest
 
 object Images {
     @Composable
@@ -20,7 +23,10 @@ object Images {
     ) {
         if (url != null) {
             AsyncImage(
-                model = url,
+                model = ImageRequest.Builder(LocalContext.current)
+                    .data(url)
+                    .crossfade(true)
+                    .build(),
                 contentDescription = description,
                 modifier = modifier,
                 contentScale = contentScale,

+ 1 - 15
feat/design/src/main/kotlin/com/zaojiao/app/feat/design/StatePage.kt

@@ -54,21 +54,7 @@ inline fun <reified T> StatePage(uiState: UiState<T>, onSuccess: @Composable (T)
                         .fillMaxWidth(),
                     horizontalAlignment = Alignment.CenterHorizontally,
                 ) {
-                    Text(
-                        text = "error state",
-                        style = TextStyle(
-                            fontSize = 18.sp,
-                            color = Color.Red,
-                        )
-                    )
-
-                    Text(
-                        text = "code: ${uiState.code}",
-                        style = TextStyle(
-                            fontSize = 18.sp,
-                            color = Color.Red,
-                        )
-                    )
+                    EmptyState.NoNetwork()
                 }
             }
         }

+ 2 - 1
feat/design/src/main/kotlin/com/zaojiao/app/feat/design/Swiper.kt

@@ -27,7 +27,7 @@ fun <T> LJGBanner(
     onImagePath: (index: Int, data: T) -> String,
     modifier: Modifier = Modifier,
     contentScale: ContentScale = ContentScale.Crop,
-    isLoopBanner: Boolean = true,
+    isLoopBanner: Boolean = data.size > 1,
     loopDelay: Long = 3000,
     loopPeriod: Long = 3000,
     desc: @Composable ((index: Int, data: T) -> String)? = null,
@@ -69,6 +69,7 @@ fun <T> LJGBanner(
         state = state,
         pageSpacing = 0.dp,
         modifier = modifier,
+        userScrollEnabled = isLoopBanner,
     ) { index ->
         val actualIndex = index % data.size
         val item = data[actualIndex]

+ 20 - 83
feat/design/src/main/kotlin/com/zaojiao/app/feat/design/TabBar.kt

@@ -3,62 +3,24 @@ package com.zaojiao.app.feat.design
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.widthIn
-import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.layout.wrapContentWidth
 import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.ScrollableTabRow
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Immutable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.layout.positionInParent
-import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.round
 import kotlinx.coroutines.launch
-import kotlin.math.roundToInt
 
-@Immutable
-private class TabItemPosition(val left: Dp, val width: Dp) {
-    val right: Dp get() = left + width
-
-    val center: Dp get() = left + width / 2
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is TabItemPosition) return false
-
-        if (left != other.left) return false
-        if (width != other.width) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = left.hashCode()
-        result = 31 * result + width.hashCode()
-        return result
-    }
-
-    override fun toString(): String {
-        return "TabItemPosition(left=$left, right=$right, width=$width)"
-    }
-}
 
 @Composable
 fun TabBar(
@@ -71,58 +33,30 @@ fun TabBar(
     unselectStyle: TextStyle,
     onChange: ((Int) -> Unit)? = null,
 ) {
-    val density = LocalDensity.current.density
-
     val coroutineScope = rememberCoroutineScope()
 
-    val lazyListState = rememberLazyListState()
-
-    var lazyListWidth by remember { mutableStateOf(0) }
-
-    val itemPositions = remember { MutableList(tabs.size) { TabItemPosition(0.dp, 0.dp) } }
-
-    var selectIndex by remember { mutableStateOf(initial) }
-
-    LazyRow(
-        state = lazyListState,
-        modifier = Modifier
-            .onGloballyPositioned {
-                lazyListWidth = it.size.width
-            }
-            .fillMaxWidth()
-            .wrapContentHeight(),
+    ScrollableTabRow(
+        selectedTabIndex = initial,
+        edgePadding = 10.dp,
+        indicator = {},
+        divider = {},
     ) {
-        items(tabs.size) { index ->
+        repeat(tabs.size) {
             TabBarItem(
-                title = tabs[index],
-                style = if (index == selectIndex) selectStyle else unselectStyle,
+                title = tabs[it],
+                style = if (it == initial) selectStyle else unselectStyle,
                 modifier = Modifier
                     .padding(horizontal = 8.dp)
-                    .onGloballyPositioned {
-                        val left = it
-                            .positionInParent()
-                            .round().x
-                        val width = it.size.width
-
-                        itemPositions[index] = TabItemPosition(
-                            (left / density).roundToInt().dp,
-                            (width / density).roundToInt().dp,
-                        )
-                    }
                     .clip(shape = RoundedCornerShape(100.dp))
                     .clickable {
                         coroutineScope.launch {
-                            if (selectIndex == index) return@launch
-                            selectIndex = index
-                            onChange?.invoke(index)
-                            lazyListState.animateScrollToItem(
-                                index,
-                                (-lazyListWidth / 2 + itemPositions[index].width.value * density / 2).roundToInt()
-                            )
+                            if (initial == it) return@launch
+                            onChange?.invoke(it)
                         }
                     }
-                    .background(color = if (index == selectIndex) selectColor else unselectColor)
+                    .background(color = if (it == initial) selectColor else unselectColor)
                     .widthIn(min = 73.dp)
+                    .height(30.dp)
                     .wrapContentSize(),
             )
         }
@@ -135,13 +69,16 @@ private fun TabBarItem(
     modifier: Modifier = Modifier,
     style: TextStyle,
 ) {
-    Box(modifier = modifier) {
+    Box(
+        modifier = modifier
+            .wrapContentWidth(),
+    ) {
         Text(
             text = title,
             style = style,
             modifier = Modifier
                 .align(Alignment.Center)
-                .padding(5.dp, 7.dp, 3.dp, 10.dp)
+                .padding(5.dp, 2.dp, 3.dp, 5.dp)
                 .wrapContentSize(),
         )
     }

+ 1 - 4
feat/design/src/main/kotlin/com/zaojiao/app/feat/design/viewmodel/BaseViewModel.kt

@@ -1,9 +1,6 @@
 package com.zaojiao.app.feat.design.viewmodel
 
 import android.util.Log
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import com.zaojiao.app.feat.design.UiState
@@ -11,7 +8,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
 
-abstract class BaseViewModel<T>() : ViewModel() {
+abstract class BaseViewModel<T> : ViewModel() {
     private val _stateFlow = MutableStateFlow<UiState<T>>(UiState.Loading)
     val stateFlow: StateFlow<UiState<T>> get() = _stateFlow
 

+ 18 - 9
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCourseCategory.kt

@@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.lazy.rememberLazyListState
@@ -22,9 +23,11 @@ import androidx.compose.ui.BiasAlignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import com.zaojiao.app.data.model.common.CourseCategoryModel
 import com.zaojiao.app.feat.home.R
 import com.zaojiao.app.feat.design.Colors
 import com.zaojiao.app.feat.design.Images
@@ -33,7 +36,8 @@ import com.zaojiao.app.feat.design.list
 
 @Composable
 fun HomeCourseCategory(
-    itemWidth: Dp
+    itemWidth: Dp,
+    categoryList: List<CourseCategoryModel>,
 ) {
     val state = rememberLazyListState()
 
@@ -53,31 +57,36 @@ fun HomeCourseCategory(
             contentPadding = PaddingValues(horizontal = 16.dp)
         ) {
             list(
-                10,
+                categoryList.size,
                 itemContent = { index ->
+                    val category = categoryList[index]
+
                     Column(
                         modifier = Modifier
-                            .width(itemWidth)
+                            .widthIn(min = itemWidth)
                             .wrapContentHeight(),
                         horizontalAlignment = Alignment.CenterHorizontally,
                     ) {
-                        Images.Resource(
-                            id = R.mipmap.default_avatar,
+                        Images.Network(
+                            url = category.iconImg,
                             modifier = Modifier.size(42.dp),
                             contentScale = ContentScale.Fit,
                         )
                         Spacer(height = 3.dp)
                         Text(
-                            text = "$index", style = TextStyle(
+                            text = category.name,
+                            maxLines = 1,
+                            overflow = TextOverflow.Visible,
+                            style = TextStyle(
                                 fontSize = 13.sp,
                                 lineHeight = 18.sp,
                                 color = Colors.FF333333,
-                            )
+                            ),
                         )
                     }
                 },
                 itemSeparation = {
-                    Box(modifier = Modifier.width(20.dp))
+                    Box(modifier = Modifier.width(16.dp))
                 },
             )
         }
@@ -100,7 +109,7 @@ fun HomeCourseCategory(
             }
         }
 
-        if (5 >= 5) {
+        if (categoryList.size > 5) {
             Spacer(height = 8.dp)
 
             Box(

+ 37 - 35
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCourseGroupBuy.kt

@@ -32,6 +32,7 @@ import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import com.zaojiao.app.data.model.micropage.MicroPageImageModelItem
 import com.zaojiao.app.feat.home.R
 import com.zaojiao.app.feat.design.Colors
 import com.zaojiao.app.feat.design.Expanded
@@ -40,14 +41,16 @@ import com.zaojiao.app.feat.design.LJGBanner
 import com.zaojiao.app.feat.design.Spacer
 
 @Composable
-internal fun HomeCourseGroupBuy() {
+internal fun HomeCourseGroupBuy(
+    activityList: List<MicroPageImageModelItem>,
+) {
     Row(
         modifier = Modifier
             .padding(horizontal = 16.dp)
             .fillMaxWidth()
             .wrapContentHeight()
     ) {
-        HomeCourseGroupBuySwiper()
+        HomeCourseGroupBuySwiper(activityList)
         Spacer(width = 10.dp)
         HomeCourseGroupBuyRecommend()
     }
@@ -55,7 +58,9 @@ internal fun HomeCourseGroupBuy() {
 
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
-private fun RowScope.HomeCourseGroupBuySwiper() {
+private fun RowScope.HomeCourseGroupBuySwiper(
+    activityList: List<MicroPageImageModelItem>,
+) {
     Box(
         modifier = Modifier
             .weight(1f)
@@ -64,41 +69,38 @@ private fun RowScope.HomeCourseGroupBuySwiper() {
         val pagerState = rememberPagerState(initialPage = 200 * 4)
 
         LJGBanner(
-            data = listOf(
-                "https://t7.baidu.com/it/u=1595072465,3644073269&fm=193&f=GIF",
-                "https://t7.baidu.com/it/u=4198287529,2774471735&fm=193&f=GIF",
-                "https://t7.baidu.com/it/u=1956604245,3662848045&fm=193&f=GIF",
-                "https://t7.baidu.com/it/u=2529476510,3041785782&fm=193&f=GIF",
-            ), pagerState = pagerState, onImagePath = { index, item ->
-                item
-            }, modifier = Modifier
+            data = activityList, pagerState = pagerState,
+            onImagePath = { index, item -> item.url },
+            modifier = Modifier
                 .clip(RoundedCornerShape(20.dp))
-                .fillMaxSize()
+                .fillMaxSize(),
         )
 
-        Row(
-            modifier = Modifier
-                .padding(bottom = 5.dp)
-                .align(Alignment.BottomCenter)
-                .wrapContentHeight()
-                .height(7.dp),
-            horizontalArrangement = Arrangement.spacedBy(6.dp),
-        ) {
-            repeat(4) { index ->
-                if (index == (pagerState.currentPage % 4)) {
-                    Box(
-                        modifier = Modifier
-                            .clip(RoundedCornerShape(7.dp))
-                            .background(color = Color.White)
-                            .size(7.dp, 7.dp)
-                    )
-                } else {
-                    Box(
-                        modifier = Modifier
-                            .clip(RoundedCornerShape(7.dp))
-                            .background(color = Color.White.copy(alpha = 0.3f))
-                            .size(7.dp, 7.dp)
-                    )
+        if (activityList.size > 1) {
+            Row(
+                modifier = Modifier
+                    .padding(bottom = 5.dp)
+                    .align(Alignment.BottomCenter)
+                    .wrapContentHeight()
+                    .height(7.dp),
+                horizontalArrangement = Arrangement.spacedBy(6.dp),
+            ) {
+                repeat(activityList.size) { index ->
+                    if (index == (pagerState.currentPage % activityList.size)) {
+                        Box(
+                            modifier = Modifier
+                                .clip(RoundedCornerShape(7.dp))
+                                .background(color = Color.White)
+                                .size(7.dp, 7.dp)
+                        )
+                    } else {
+                        Box(
+                            modifier = Modifier
+                                .clip(RoundedCornerShape(7.dp))
+                                .background(color = Color.White.copy(alpha = 0.3f))
+                                .size(7.dp, 7.dp)
+                        )
+                    }
                 }
             }
         }

+ 70 - 42
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCoursePage.kt

@@ -6,72 +6,100 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.material3.Surface
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.paging.compose.LazyPagingItems
+import androidx.paging.compose.collectAsLazyPagingItems
+import com.zaojiao.app.data.model.common.CourseCategoryModel
+import com.zaojiao.app.data.model.course.CourseModel
+import com.zaojiao.app.data.model.micropage.MicroPageImageModel
+import com.zaojiao.app.data.model.micropage.MicroPageImageModelItem
 import com.zaojiao.app.feat.design.Screen
 import com.zaojiao.app.feat.design.StatePage
-import com.zaojiao.app.feat.design.UiState
 import com.zaojiao.app.feat.design.grid
 
 @Composable
 internal fun HomeCourseRoute(
     viewModel: HomeCourseViewModel = hiltViewModel(),
 ) {
+    val uiState by viewModel.stateFlow.collectAsState()
+    val courseList = viewModel.pagingData.collectAsLazyPagingItems()
 
-    HomeCoursePage()
-}
-
-@OptIn(ExperimentalFoundationApi::class)
-@Composable
-fun HomeCoursePage() {
     Surface(
         color = Color.White,
         modifier = Modifier.fillMaxSize(),
     ) {
-        val width = Screen.width()
+        StatePage(uiState = uiState) { state ->
+            HomeCoursePage(
+                bannerList = state.bannerList,
+                categoryList = state.categoryList,
+                activityList = state.activityList,
+                groupBuyList = emptyList(),
+                courseList = courseList,
+            )
+        }
+    }
+}
 
-        StatePage(uiState = UiState.Success(0)) { state ->
-            LazyColumn(
-                modifier = Modifier.fillMaxWidth()
-            ) {
-                stickyHeader {
-                    HomeCourseTopBar()
-                }
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+fun HomeCoursePage(
+    bannerList: List<MicroPageImageModelItem>,
+    categoryList: List<CourseCategoryModel>,
+    activityList: List<MicroPageImageModelItem>,
+    groupBuyList: List<MicroPageImageModelItem>,
+    courseList: LazyPagingItems<CourseModel>,
+) {
 
-                item {
-                    HomeCourseTopSwiper()
-                }
+    val width = Screen.width()
 
-                item {
-                    val itemWidth = (width - 112.dp) / 5
-                    HomeCourseCategory(itemWidth)
-                }
+    LazyColumn(
+        modifier = Modifier.fillMaxWidth()
+    ) {
+        stickyHeader {
+            HomeCourseTopBar()
+        }
 
-                item {
-                    HomeCourseZone()
-                }
+        item {
+            HomeCourseTopSwiper(
+                bannerList = bannerList,
+            )
+        }
 
-                item {
-                    HomeCourseGroupBuy()
-                }
+        item {
+            val itemWidth = (width - 96.dp) / 5
+            HomeCourseCategory(
+                itemWidth = itemWidth,
+                categoryList = categoryList,
+            )
+        }
 
-                item {
-                    HomeCourseRecommend()
-                }
+        item {
+            HomeCourseZone()
+        }
+
+        item {
+            HomeCourseGroupBuy(activityList)
+        }
+
+        item {
+            HomeCourseRecommend()
+        }
 
-                grid(
-                    count = 100,
-                    span = 2,
-                    hSpace = 12.dp,
-                    vSpace = 20.dp,
-                    padding = 16.dp,
-                    isVertical = true,
-                ) {
-                    HomeCourseRecommendItem()
-                }
-            }
+        grid(
+            count = courseList.itemCount,
+            span = 2,
+            hSpace = 12.dp,
+            vSpace = 20.dp,
+            padding = 16.dp,
+            isVertical = true,
+        ) {
+            val course = courseList[it] ?: return@grid
+            HomeCourseRecommendItem(course)
         }
     }
 }

+ 15 - 7
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCourseRecommend.kt

@@ -1,5 +1,7 @@
 package com.zaojiao.app.feat.home.course
 
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.basicMarquee
 import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -25,6 +27,7 @@ import androidx.compose.ui.text.style.TextDecoration
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import com.zaojiao.app.data.model.course.CourseModel
 import com.zaojiao.app.feat.design.Colors
 import com.zaojiao.app.feat.design.Expanded
 import com.zaojiao.app.feat.design.Icons
@@ -82,16 +85,19 @@ fun HomeCourserRecommendTitle() {
     }
 }
 
+@OptIn(ExperimentalFoundationApi::class)
 @Composable
-fun HomeCourseRecommendItem() {
+fun HomeCourseRecommendItem(
+    courseModel: CourseModel,
+) {
     Column(
         modifier = Modifier
             .wrapContentHeight()
             .fillMaxWidth()
     ) {
         Images.Network(
-            url = "https://t7.baidu.com/it/u=1595072465,3644073269&fm=193&f=GIF",
-            description = "人文地理学家",
+            url = courseModel.imgCover,
+            description = courseModel.name,
             modifier = Modifier
                 .clip(RoundedCornerShape(20.dp))
                 .aspectRatio(1f)
@@ -101,13 +107,16 @@ fun HomeCourseRecommendItem() {
         Spacer(height = 13.dp)
 
         Text(
-            text = "人文地理学家人文地理学家人文地理学家人文地理学家",
+            text = courseModel.name,
             style = TextStyle(
                 fontSize = 15.sp,
                 lineHeight = 16.sp,
                 fontWeight = FontWeight.SemiBold,
                 color = Colors.FF333333,
             ),
+            modifier = Modifier.basicMarquee(
+                iterations = Int.MAX_VALUE,
+            ),
             maxLines = 1,
             overflow = TextOverflow.Ellipsis,
         )
@@ -138,7 +147,7 @@ fun HomeCourseRecommendItem() {
                 modifier = Modifier.padding(bottom = 2.dp)
             )
             Text(
-                text = "30.0",
+                text = "${courseModel.price}",
                 style = TextStyle(
                     fontSize = 18.sp,
                     lineHeight = 18.sp,
@@ -149,7 +158,7 @@ fun HomeCourseRecommendItem() {
             )
             Spacer(width = 3.dp)
             Text(
-                text = "¥158.0",
+                text = "¥${courseModel.markingPrice}",
                 style = TextStyle(
                     fontSize = 10.sp,
                     lineHeight = 14.sp,
@@ -158,7 +167,6 @@ fun HomeCourseRecommendItem() {
                     textDecoration = TextDecoration.LineThrough,
                 ),
                 modifier = Modifier.padding(bottom = 2.dp),
-
             )
         }
     }

+ 7 - 12
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCourseTopSwiper.kt

@@ -9,26 +9,21 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.unit.dp
+import com.zaojiao.app.data.model.micropage.MicroPageImageModelItem
 import com.zaojiao.app.feat.design.LJGBanner
 
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
-fun HomeCourseTopSwiper() {
+fun HomeCourseTopSwiper(
+    bannerList: List<MicroPageImageModelItem>,
+) {
     LJGBanner(
-        data = listOf(
-            "https://t7.baidu.com/it/u=1595072465,3644073269&fm=193&f=GIF",
-            "https://t7.baidu.com/it/u=4198287529,2774471735&fm=193&f=GIF",
-            "https://t7.baidu.com/it/u=1956604245,3662848045&fm=193&f=GIF",
-            "https://t7.baidu.com/it/u=2529476510,3041785782&fm=193&f=GIF",
-        ),
-        onImagePath = { _, item ->
-            item
-        },
+        data = bannerList,
+        onImagePath = { _, item -> item.url },
         modifier = Modifier
             .padding(horizontal = 16.dp, vertical = 14.dp)
             .clip(RoundedCornerShape(20.dp))
             .height(151.dp)
-            .fillMaxWidth()
-
+            .fillMaxWidth(),
     )
 }

+ 55 - 2
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCourseViewModel.kt

@@ -1,10 +1,63 @@
 package com.zaojiao.app.feat.home.course
 
-import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import androidx.paging.PagingData
+import androidx.paging.cachedIn
+import com.zaojiao.app.data.model.common.CourseCategoryModel
+import com.zaojiao.app.data.model.course.CourseModel
+import com.zaojiao.app.data.model.micropage.MicroPageImageModel
+import com.zaojiao.app.data.model.micropage.MicroPageImageModelItem
+import com.zaojiao.app.data.model.micropage.toMicroPageJsonModel
+import com.zaojiao.app.data.model.studyplan.StudyPlanCourseModel
+import com.zaojiao.app.data.repo.CourseRepository
+import com.zaojiao.app.feat.design.viewmodel.BaseViewModel
 import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.json.JSONArray
+import org.json.JSONObject
 import javax.inject.Inject
 
+data class HomeCourseUiState(
+    val bannerList: List<MicroPageImageModelItem>,
+    val categoryList: List<CourseCategoryModel>,
+    val activityList: List<MicroPageImageModelItem>,
+    val groupBuyList: List<MicroPageImageModelItem>,
+)
+
 @HiltViewModel
-class HomeCourseViewModel @Inject constructor() : ViewModel() {
+class HomeCourseViewModel @Inject constructor(
+    private val courseRepository: CourseRepository,
+) : BaseViewModel<HomeCourseUiState>() {
+
+    val pagingData: Flow<PagingData<CourseModel>> = flow {
+        emit(0)
+    }.flatMapLatest {
+        courseRepository.getRecommendCourse()
+    }.cachedIn(viewModelScope)
+
+
+    init {
+        initialState()
+    }
+
+    override suspend fun productState(): HomeCourseUiState {
+        val json = courseRepository.getIndexBanner()
+        val jsonModel = json.toMicroPageJsonModel()
+
+        val banner = jsonModel[1] as MicroPageImageModel
+        val activity = jsonModel[2] as MicroPageImageModel
+
+        val categoryList = courseRepository.getCategoryList()
 
+        return HomeCourseUiState(
+            bannerList = banner.imageList,
+            categoryList = categoryList,
+            activityList = activity.imageList,
+            groupBuyList = emptyList(),
+        )
+    }
 }

+ 0 - 1
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/plan/state/HomePlanIndexUiState.kt

@@ -1,7 +1,6 @@
 package com.zaojiao.app.feat.home.plan.state
 
 import com.zaojiao.app.data.model.common.CategoryModel
-import com.zaojiao.app.data.model.studyplan.StudyPlanCourseItemModel
 import com.zaojiao.app.data.model.studyplan.StudyPlanHistoryModel
 import com.zaojiao.app.data.model.studyplan.StudyPlanTodayModel
 import com.zaojiao.app.data.model.studyplan.StudyPlanTomorrowModel

+ 59 - 28
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/plan/total/HomePlanTotalPage.kt

@@ -1,6 +1,8 @@
 package com.zaojiao.app.feat.home.plan.total
 
 import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.IntrinsicSize
@@ -18,6 +20,9 @@ import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
@@ -31,9 +36,10 @@ import androidx.compose.ui.unit.sp
 import androidx.hilt.navigation.compose.hiltViewModel
 import androidx.paging.compose.collectAsLazyPagingItems
 import com.zaojiao.app.data.model.common.CategoryModel
+import com.zaojiao.app.data.model.common.CourseCategoryModel
 import com.zaojiao.app.data.model.common.MediaCategoryModel
 import com.zaojiao.app.data.model.common.SortCategoryModel
-import com.zaojiao.app.data.model.studyplan.StudyPlanCourseItemModel
+import com.zaojiao.app.data.model.studyplan.StudyPlanCourseModel
 import com.zaojiao.app.feat.design.Colors
 import com.zaojiao.app.feat.design.Images
 import com.zaojiao.app.feat.design.Spacer
@@ -96,7 +102,7 @@ fun HomePlanTotalPage(
 fun HomePlanTotalCategory(
     mediaTypeList: List<MediaCategoryModel>,
     sortTypeList: List<SortCategoryModel>,
-    categoryList: List<CategoryModel>,
+    categoryList: List<CourseCategoryModel>,
     initialMediaType: Int,
     initialSortType: Int,
     initialCategory: Int,
@@ -104,6 +110,12 @@ fun HomePlanTotalCategory(
     onChangeSortType: (Int) -> Unit,
     onChangeCategory: (Int) -> Unit,
 ) {
+    var mediaType by remember { mutableStateOf(initialMediaType) }
+    var sortType by remember { mutableStateOf(initialSortType) }
+    var category by remember { mutableStateOf(initialCategory) }
+
+    var categoryState by remember { mutableStateOf(true) }
+
     Column(
         modifier = Modifier
             .background(
@@ -120,7 +132,7 @@ fun HomePlanTotalCategory(
     ) {
 
         TabBar(
-            initial = initialMediaType,
+            initial = mediaType,
             tabs = mediaTypeList.map { model -> model.typeName },
             selectColor = Colors.from("#FFF2F0F3"),
             unselectColor = Colors.from("#FFF5F6F8"),
@@ -135,7 +147,10 @@ fun HomePlanTotalCategory(
                 fontSize = 14.sp,
                 lineHeight = 20.sp,
             ),
-            onChange = onChangeMediaType,
+            onChange = {
+                mediaType = it
+                onChangeMediaType.invoke(it)
+            },
         )
 
         Spacer(height = 16.dp)
@@ -150,7 +165,7 @@ fun HomePlanTotalCategory(
                     .wrapContentHeight(),
             ) {
                 TabBar(
-                    initial = initialSortType,
+                    initial = sortType,
                     tabs = sortTypeList.map { model -> model.typeName },
                     selectColor = Colors.from("#FFF2F0F3"),
                     unselectColor = Colors.from("#FFF5F6F8"),
@@ -165,43 +180,59 @@ fun HomePlanTotalCategory(
                         fontSize = 14.sp,
                         lineHeight = 20.sp,
                     ),
-                    onChange = onChangeSortType,
+                    onChange = {
+                        sortType = it
+                        onChangeSortType.invoke(it)
+                    },
                 )
             }
 
             Images.Resource(
-                id = R.mipmap.personal_clockin,
-                modifier = Modifier.size(16.dp),
+                id = if (categoryState) R.mipmap.study_plan_show_category else R.mipmap.study_plan_hide_category,
+                modifier = Modifier
+                    .clickable(
+                        indication = null,
+                        interactionSource = MutableInteractionSource(),
+                    ) {
+                        categoryState = !categoryState
+                    }
+                    .padding(horizontal = 16.dp, vertical = 6.dp)
+                    .size(18.dp),
             )
         }
 
-        Spacer(height = 16.dp)
+        if (categoryState) {
+            Spacer(height = 16.dp)
 
-        TabBar(
-            initial = initialCategory,
-            tabs = categoryList.map { model -> model.name },
-            selectColor = Colors.from("#FFF2F0F3"),
-            unselectColor = Colors.from("#FFF5F6F8"),
-            selectStyle = TextStyle(
-                color = Colors.from("#FF0744AE"),
-                fontSize = 14.sp,
-                lineHeight = 20.sp,
-                fontWeight = FontWeight.Medium,
-            ),
-            unselectStyle = TextStyle(
-                color = Colors.FF333333,
-                fontSize = 14.sp,
-                lineHeight = 20.sp,
-            ),
-            onChange = onChangeCategory,
-        )
+            TabBar(
+                initial = category,
+                tabs = categoryList.map { model -> model.name },
+                selectColor = Colors.from("#FFF2F0F3"),
+                unselectColor = Colors.from("#FFF5F6F8"),
+                selectStyle = TextStyle(
+                    color = Colors.from("#FF0744AE"),
+                    fontSize = 14.sp,
+                    lineHeight = 20.sp,
+                    fontWeight = FontWeight.Medium,
+                ),
+                unselectStyle = TextStyle(
+                    color = Colors.FF333333,
+                    fontSize = 14.sp,
+                    lineHeight = 20.sp,
+                ),
+                onChange = {
+                    category = it
+                    onChangeCategory.invoke(it)
+                },
+            )
+        }
 
         Spacer(height = 16.dp)
     }
 }
 
 @Composable
-fun HomePlanTotalCourseItem(model: StudyPlanCourseItemModel) {
+fun HomePlanTotalCourseItem(model: StudyPlanCourseModel) {
     Row(
         modifier = Modifier
             .padding(horizontal = 16.dp)

+ 6 - 5
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/plan/total/HomePlanTotalViewModel.kt

@@ -4,9 +4,10 @@ import androidx.lifecycle.viewModelScope
 import androidx.paging.PagingData
 import androidx.paging.cachedIn
 import com.zaojiao.app.data.model.common.CategoryModel
+import com.zaojiao.app.data.model.common.CourseCategoryModel
 import com.zaojiao.app.data.model.common.MediaCategoryModel
 import com.zaojiao.app.data.model.common.SortCategoryModel
-import com.zaojiao.app.data.model.studyplan.StudyPlanCourseItemModel
+import com.zaojiao.app.data.model.studyplan.StudyPlanCourseModel
 import com.zaojiao.app.data.repo.StudyPlanRepository
 import com.zaojiao.app.feat.design.viewmodel.BaseViewModel
 import dagger.hilt.android.lifecycle.HiltViewModel
@@ -21,7 +22,7 @@ import javax.inject.Inject
 data class HomePlanTotalUiState(
     val mediaTypeList: List<MediaCategoryModel> = emptyList(),
     val sortTypeList: List<SortCategoryModel> = emptyList(),
-    val categoryList: List<CategoryModel> = emptyList(),
+    val categoryList: List<CourseCategoryModel> = emptyList(),
     val selectMediaType: Int = 0,
     val selectSortType: Int = 0,
     val selectCategory: Int = 0,
@@ -37,7 +38,7 @@ sealed class HomePlanTotalAction {
     ) : HomePlanTotalAction()
 
     data class Category(
-        val categoryModel: CategoryModel,
+        val categoryModel: CourseCategoryModel,
     ) : HomePlanTotalAction()
 }
 
@@ -51,14 +52,14 @@ private data class Query(
 class HomePlanTotalViewModel @Inject constructor(
     private val studyPlanRepository: StudyPlanRepository,
 ) : BaseViewModel<HomePlanTotalUiState>() {
-    val pagingData: Flow<PagingData<StudyPlanCourseItemModel>>
+    val pagingData: Flow<PagingData<StudyPlanCourseModel>>
 
     val sendAction: (HomePlanTotalAction) -> Unit
 
     init {
         val mediaTypeFlow = MutableSharedFlow<MediaCategoryModel>()
         val sortTypeFlow = MutableSharedFlow<SortCategoryModel>()
-        val categoryFlow = MutableSharedFlow<CategoryModel>()
+        val categoryFlow = MutableSharedFlow<CourseCategoryModel>()
 
         pagingData = combine(mediaTypeFlow, sortTypeFlow, categoryFlow) { c1, c2, c3 ->
             Query(c1.mediaType, c2.sortType, c3.id)

BIN
feat/home/src/main/res/mipmap-xhdpi/study_plan_hide_category.png


BIN
feat/home/src/main/res/mipmap-xhdpi/study_plan_show_category.png


BIN
feat/home/src/main/res/mipmap-xxhdpi/study_plan_hide_category.png


BIN
feat/home/src/main/res/mipmap-xxhdpi/study_plan_show_category.png


BIN
feat/home/src/main/res/mipmap-xxxhdpi/study_plan_hide_category.png


BIN
feat/home/src/main/res/mipmap-xxxhdpi/study_plan_show_category.png


+ 1 - 0
gradle/libs.versions.toml

@@ -10,6 +10,7 @@ ksp = "1.8.20-1.0.11"
 coil-kt = { group = "io.coil-kt", name = "coil", version.ref = "coil" }
 coil-kt-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
 coil-kt-svg = { group = "io.coil-kt", name = "coil-svg", version.ref = "coil" }
+coil-kt-gif = { group = "io.coil-kt", name = "coil-gif", version.ref = "coil" }
 
 kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinx" }
 kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinx" }