Procházet zdrojové kódy

add(course): add course group buy

zhaoyadi před 1 rokem
rodič
revize
6b4bf14b4c
33 změnil soubory, kde provedl 431 přidání a 285 odebrání
  1. 9 5
      app/src/main/AndroidManifest.xml
  2. 60 0
      app/src/main/java/com/zaojiao/app/SplashActivity.kt
  3. 0 30
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  4. 0 170
      app/src/main/res/drawable/ic_launcher_background.xml
  5. 0 5
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  6. 0 5
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  7. 0 6
      app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml
  8. 0 6
      app/src/main/res/mipmap-anydpi-v33/ic_launcher_round.xml
  9. binární
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  10. binární
      app/src/main/res/mipmap-xhdpi/ic_launcher_debug.png
  11. binární
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  12. binární
      app/src/main/res/mipmap-xxhdpi/ic_launcher_debug.png
  13. binární
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  14. binární
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_debug.png
  15. binární
      app/src/main/res/mipmap/launch_image.png
  16. 0 1
      app/src/main/res/values/themes.xml
  17. 0 19
      app/src/main/res/xml/data_extraction_rules.xml
  18. 9 0
      built/convention/src/main/kotlin/ApplicationConventionPlugin.kt
  19. 2 0
      core/http/build.gradle.kts
  20. 2 0
      core/http/src/main/kotlin/com/zaojiao/app/core/http/di/HttpModule.kt
  21. 23 0
      data/model/src/main/kotlin/com/zaojiao/app/data/model/groupbuy/GroupBuyModel.kt
  22. 19 0
      data/remote/src/main/kotlin/com/zaojiao/app/data/remote/RemoteGroupBuyData.kt
  23. 2 2
      data/remote/src/main/kotlin/com/zaojiao/app/data/remote/api/BindApi.kt
  24. 10 0
      data/remote/src/main/kotlin/com/zaojiao/app/data/remote/api/GroupBuyApi.kt
  25. 3 3
      data/remote/src/main/kotlin/com/zaojiao/app/data/remote/api/LoginApi.kt
  26. 7 0
      data/repo/src/main/kotlin/com/zaojiao/app/data/repo/GroupBuyRepository.kt
  27. 8 0
      data/repo/src/main/kotlin/com/zaojiao/app/data/repo/di/RepoModule.kt
  28. 14 0
      data/repo/src/main/kotlin/com/zaojiao/app/data/repo/impl/GroupBuyRepositoryImpl.kt
  29. 1 0
      feat/design/src/main/kotlin/com/zaojiao/app/feat/design/Colors.kt
  30. 36 3
      feat/design/src/main/kotlin/com/zaojiao/app/feat/design/StatePage.kt
  31. 204 19
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCourseGroupBuy.kt
  32. 14 4
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCoursePage.kt
  33. 8 7
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCourseViewModel.kt

+ 9 - 5
app/src/main/AndroidManifest.xml

@@ -9,23 +9,27 @@
     <application
         android:name=".LJGApplication"
         android:allowBackup="false"
-        android:dataExtractionRules="@xml/data_extraction_rules"
-        android:enableOnBackInvokedCallback="true"
         android:fullBackupContent="@xml/backup_rules"
-        android:icon="@mipmap/ic_launcher"
+        android:icon="${icon}"
         android:label="@string/app_name"
         android:supportsRtl="true"
         android:theme="@style/Theme.Luojigou">
 
         <activity
-            android:name="com.zaojiao.app.MainActivity"
+            android:name=".SplashActivity"
             android:exported="true"
-            android:launchMode="singleInstance"
             android:theme="@style/Theme.Luojigou.LightBar">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
+        </activity>
+
+        <activity
+            android:name="com.zaojiao.app.MainActivity"
+            android:exported="true"
+            android:launchMode="singleInstance"
+            android:theme="@style/Theme.Luojigou.LightBar">
 
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />

+ 60 - 0
app/src/main/java/com/zaojiao/app/SplashActivity.kt

@@ -0,0 +1,60 @@
+package com.zaojiao.app
+
+import android.animation.ObjectAnimator
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.animation.AnticipateInterpolator
+import android.widget.FrameLayout
+import android.widget.ImageView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.animation.doOnEnd
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowInsetsControllerCompat
+
+class SplashActivity : AppCompatActivity() {
+    private lateinit var windowInsetsController: WindowInsetsControllerCompat
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        configureSystemBar()
+        super.onCreate(savedInstanceState)
+
+        ImageView(this).apply {
+            layoutParams = ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT,
+            )
+
+            fitsSystemWindows = false
+            scaleType = ImageView.ScaleType.CENTER_CROP
+            cropToPadding = true
+
+            setImageResource(R.mipmap.launch_image)
+            setContentView(this)
+        }
+    }
+
+    private fun configureSystemBar() {
+        WindowCompat.setDecorFitsSystemWindows(window, false)
+        windowInsetsController = WindowInsetsControllerCompat(window, window.decorView)
+
+        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
+        window.statusBarColor = Color.TRANSPARENT
+        window.navigationBarColor = Color.TRANSPARENT
+        window.decorView.background = ColorDrawable(Color.WHITE)
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            window.navigationBarDividerColor = Color.TRANSPARENT
+            window.addFlags(WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)
+        }
+        windowInsetsController.apply {
+            hide(WindowInsetsCompat.Type.systemBars())
+        }
+    }
+}

+ 0 - 30
app/src/main/res/drawable-v24/ic_launcher_foreground.xml

@@ -1,30 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:aapt="http://schemas.android.com/aapt"
-    android:width="108dp"
-    android:height="108dp"
-    android:viewportWidth="108"
-    android:viewportHeight="108">
-    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
-        <aapt:attr name="android:fillColor">
-            <gradient
-                android:endX="85.84757"
-                android:endY="92.4963"
-                android:startX="42.9492"
-                android:startY="49.59793"
-                android:type="linear">
-                <item
-                    android:color="#44000000"
-                    android:offset="0.0" />
-                <item
-                    android:color="#00000000"
-                    android:offset="1.0" />
-            </gradient>
-        </aapt:attr>
-    </path>
-    <path
-        android:fillColor="#FFFFFF"
-        android:fillType="nonZero"
-        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
-        android:strokeWidth="1"
-        android:strokeColor="#00000000" />
-</vector>

+ 0 - 170
app/src/main/res/drawable/ic_launcher_background.xml

@@ -1,170 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="108dp"
-    android:height="108dp"
-    android:viewportWidth="108"
-    android:viewportHeight="108">
-    <path
-        android:fillColor="#3DDC84"
-        android:pathData="M0,0h108v108h-108z" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M9,0L9,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,0L19,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M29,0L29,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M39,0L39,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M49,0L49,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M59,0L59,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M69,0L69,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M79,0L79,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M89,0L89,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M99,0L99,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,9L108,9"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,19L108,19"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,29L108,29"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,39L108,39"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,49L108,49"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,59L108,59"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,69L108,69"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,79L108,79"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,89L108,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,99L108,99"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,29L89,29"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,39L89,39"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,49L89,49"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,59L89,59"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,69L89,69"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,79L89,79"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M29,19L29,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M39,19L39,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M49,19L49,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M59,19L59,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M69,19L69,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M79,19L79,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-</vector>

+ 0 - 5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@drawable/ic_launcher_background" />
-    <foreground android:drawable="@drawable/ic_launcher_foreground" />
-</adaptive-icon>

+ 0 - 5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@drawable/ic_launcher_background" />
-    <foreground android:drawable="@drawable/ic_launcher_foreground" />
-</adaptive-icon>

+ 0 - 6
app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml

@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@drawable/ic_launcher_background" />
-    <foreground android:drawable="@drawable/ic_launcher_foreground" />
-    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
-</adaptive-icon>

+ 0 - 6
app/src/main/res/mipmap-anydpi-v33/ic_launcher_round.xml

@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@drawable/ic_launcher_background" />
-    <foreground android:drawable="@drawable/ic_launcher_foreground" />
-    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
-</adaptive-icon>

binární
app/src/main/res/mipmap-xhdpi/ic_launcher.png


binární
app/src/main/res/mipmap-xhdpi/ic_launcher_debug.png


binární
app/src/main/res/mipmap-xxhdpi/ic_launcher.png


binární
app/src/main/res/mipmap-xxhdpi/ic_launcher_debug.png


binární
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


binární
app/src/main/res/mipmap-xxxhdpi/ic_launcher_debug.png


binární
app/src/main/res/mipmap/launch_image.png


+ 0 - 1
app/src/main/res/values/themes.xml

@@ -20,7 +20,6 @@
 
     <style name="Theme.App.Starting" parent="Theme.SplashScreen">
         <item name="windowSplashScreenBackground">@android:color/white</item>
-        <item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher_foreground</item>
         <item name="postSplashScreenTheme">@style/Theme.Luojigou.LightBar</item>
     </style>
 

+ 0 - 19
app/src/main/res/xml/data_extraction_rules.xml

@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-   Sample data extraction rules file; uncomment and customize as necessary.
-   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
-   for details.
--->
-<data-extraction-rules>
-    <cloud-backup>
-        <!-- TODO: Use <include> and <exclude> to control what is backed up.
-        <include .../>
-        <exclude .../>
-        -->
-    </cloud-backup>
-    <!--
-    <device-transfer>
-        <include .../>
-        <exclude .../>
-    </device-transfer>
-    -->
-</data-extraction-rules>

+ 9 - 0
built/convention/src/main/kotlin/ApplicationConventionPlugin.kt

@@ -16,6 +16,7 @@ class ApplicationConventionPlugin : Plugin<Project> {
             extensions.configure<ApplicationExtension> {
                 configureKotlinAndroid(this)
                 defaultConfig.targetSdk = 33
+                defaultConfig.ndk.abiFilters.add("arm64-v8a")
 
                 signingConfigs {
                     create("release") {
@@ -38,12 +39,20 @@ class ApplicationConventionPlugin : Plugin<Project> {
                         proguardFiles("proguard-rules.pro")
 
                         signingConfig = signingConfigs.getByName("release")
+
+                        manifestPlaceholders.apply {
+                            this["icon"] = "@mipmap/ic_launcher"
+                        }
                     }
 
                     debug {
                         applicationIdSuffix = ".debug"
                         isDebuggable = true
                         isDefault = true
+
+                        manifestPlaceholders.apply {
+                            this["icon"] = "@mipmap/ic_launcher_debug"
+                        }
                     }
                 }
             }

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

@@ -23,6 +23,8 @@ dependencies {
 
     api("com.squareup.retrofit2:retrofit:2.9.0")
     api("com.squareup.retrofit2:converter-moshi:2.9.0")
+    api("com.squareup.retrofit2:converter-scalars:2.9.0")
+
     api("com.squareup.moshi:moshi:1.15.0")
     api("com.squareup.moshi:moshi-kotlin:1.15.0")
     api("com.squareup.moshi:moshi-adapters:1.14.0")

+ 2 - 0
core/http/src/main/kotlin/com/zaojiao/app/core/http/di/HttpModule.kt

@@ -25,6 +25,7 @@ import okhttp3.OkHttpClient
 import okhttp3.logging.HttpLoggingInterceptor
 import retrofit2.Retrofit
 import retrofit2.converter.moshi.MoshiConverterFactory
+import retrofit2.converter.scalars.ScalarsConverterFactory
 import java.util.Date
 import java.util.concurrent.TimeUnit
 import javax.inject.Named
@@ -89,6 +90,7 @@ object HttpModule {
         return Retrofit.Builder()
             .client(okHttpClient)
             .baseUrl(endpoint)
+            .addConverterFactory(ScalarsConverterFactory.create())
             .addConverterFactory(MoshiConverterFactory.create(moshi))
             .addConverterFactory(ResultConverterFactory.create(moshi))
             .build()

+ 23 - 0
data/model/src/main/kotlin/com/zaojiao/app/data/model/groupbuy/GroupBuyModel.kt

@@ -0,0 +1,23 @@
+package com.zaojiao.app.data.model.groupbuy
+
+data class GroupBuyPageModel(
+    val records: List<GroupBuyModel>,
+    val total: Int,
+)
+
+data class GroupBuyModel(
+    val id: String,
+    val groupMinPrice: Double,
+    val originalPrice: Double,
+    val population: Int,
+    val productImage: String,
+    val productName: String,
+    val productSpuId: String,
+    val productType: Int,
+    val userList: List<GroupBuyUser>,
+)
+
+data class GroupBuyUser(
+    val avatar: String,
+    val nickname: String,
+)

+ 19 - 0
data/remote/src/main/kotlin/com/zaojiao/app/data/remote/RemoteGroupBuyData.kt

@@ -0,0 +1,19 @@
+package com.zaojiao.app.data.remote
+
+import com.zaojiao.app.data.model.groupbuy.GroupBuyModel
+import com.zaojiao.app.data.model.groupbuy.GroupBuyPageModel
+import com.zaojiao.app.data.remote.api.GroupBuyApi
+import retrofit2.Retrofit
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class RemoteGroupBuyData @Inject constructor(
+    private val retrofit: Retrofit,
+) {
+    private val groupBuyApi = retrofit.create(GroupBuyApi::class.java)
+
+    suspend fun getRecommendGroupBuy(): GroupBuyPageModel {
+        return groupBuyApi.getRecommendGroupBuy().data ?: throw IllegalArgumentException()
+    }
+}

+ 2 - 2
data/remote/src/main/kotlin/com/zaojiao/app/data/remote/api/BindApi.kt

@@ -3,7 +3,7 @@ package com.zaojiao.app.data.remote.api
 import com.zaojiao.app.core.http.common.NetResult
 
 interface BindApi {
-    fun bindPhone(): NetResult<Boolean>
+    suspend fun bindPhone(): NetResult<Boolean>
 
-    fun bindWechat(): NetResult<Boolean>
+    suspend fun bindWechat(): NetResult<Boolean>
 }

+ 10 - 0
data/remote/src/main/kotlin/com/zaojiao/app/data/remote/api/GroupBuyApi.kt

@@ -0,0 +1,10 @@
+package com.zaojiao.app.data.remote.api
+
+import com.zaojiao.app.core.http.common.NetResult
+import com.zaojiao.app.data.model.groupbuy.GroupBuyPageModel
+import retrofit2.http.GET
+
+interface GroupBuyApi {
+    @GET("/mall/mobile/group-activity/recommend")
+    suspend fun getRecommendGroupBuy(): NetResult<GroupBuyPageModel>
+}

+ 3 - 3
data/remote/src/main/kotlin/com/zaojiao/app/data/remote/api/LoginApi.kt

@@ -9,18 +9,18 @@ import retrofit2.http.Path
 
 interface LoginApi {
     @POST("/app/sms/v2/checkCode/app/{phone}/{code}")
-    fun loginBySms(
+    suspend fun loginBySms(
         @Path(value = "phone") phone: String,
         @Path(value = "code") code: String,
     ): NetResult<TokenModel>
 
     @GET("/app/login/v2/{code}")
-    fun loginByWechat(
+    suspend fun loginByWechat(
         @Path(value = "code") code: String,
     ): NetResult<TokenModel>
 
     @POST("/app/login/oneClickLogin")
-    fun loginByOneClick(
+    suspend fun loginByOneClick(
         @Body token: String,
     ): NetResult<TokenModel>
 }

+ 7 - 0
data/repo/src/main/kotlin/com/zaojiao/app/data/repo/GroupBuyRepository.kt

@@ -0,0 +1,7 @@
+package com.zaojiao.app.data.repo
+
+import com.zaojiao.app.data.model.groupbuy.GroupBuyPageModel
+
+interface GroupBuyRepository {
+    suspend fun getRecommendGroupBuy(): GroupBuyPageModel
+}

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

@@ -2,11 +2,13 @@ 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.GroupBuyRepository
 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.GroupBuyRepositoryImpl
 import com.zaojiao.app.data.repo.impl.HomeRepositoryImpl
 import com.zaojiao.app.data.repo.impl.StudyPlanRepositoryImpl
 import com.zaojiao.app.data.repo.impl.UserRepositoryImpl
@@ -48,4 +50,10 @@ interface RepoModule {
     fun bindsStudyPlanRepository(
         studyPlanRepositoryImpl: StudyPlanRepositoryImpl,
     ): StudyPlanRepository
+
+    @Binds
+    @Singleton
+    fun bindsGroupBuyRepository(
+        groupBuyRepositoryImpl: GroupBuyRepositoryImpl,
+    ): GroupBuyRepository
 }

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

@@ -0,0 +1,14 @@
+package com.zaojiao.app.data.repo.impl
+
+import com.zaojiao.app.data.model.groupbuy.GroupBuyPageModel
+import com.zaojiao.app.data.remote.RemoteGroupBuyData
+import com.zaojiao.app.data.repo.GroupBuyRepository
+import javax.inject.Inject
+
+class GroupBuyRepositoryImpl @Inject constructor(
+    private val remoteGroupBuyData: RemoteGroupBuyData,
+) : GroupBuyRepository {
+    override suspend fun getRecommendGroupBuy(): GroupBuyPageModel {
+        return remoteGroupBuyData.getRecommendGroupBuy()
+    }
+}

+ 1 - 0
feat/design/src/main/kotlin/com/zaojiao/app/feat/design/Colors.kt

@@ -3,6 +3,7 @@ package com.zaojiao.app.feat.design
 import androidx.compose.ui.graphics.Color
 
 object Colors {
+    val Primary = Color(0xFF0B57C7)
     val FF000000 = Color(0xFF000000)
     val FF333333 = Color(0xFF333333)
     val FF666666 = Color(0xFF666666)

+ 36 - 3
feat/design/src/main/kotlin/com/zaojiao/app/feat/design/StatePage.kt

@@ -1,16 +1,22 @@
 package com.zaojiao.app.feat.design
 
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 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.text.TextStyle
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 
 sealed interface UiState<out T> {
@@ -22,18 +28,22 @@ sealed interface UiState<out T> {
 }
 
 @Composable
-inline fun <reified T> StatePage(uiState: UiState<T>, onSuccess: @Composable (T) -> Unit) {
+inline fun <reified T> StatePage(
+    uiState: UiState<T>,
+    noinline onError: (() -> Unit)? = null,
+    onSuccess: @Composable (T) -> Unit,
+) {
     when (uiState) {
         is UiState.Loading -> {
             Box(
                 modifier = Modifier.fillMaxSize(),
             ) {
                 Text(
-                    text = "idle state",
+                    text = "Loading...",
                     modifier = Modifier.align(Alignment.Center),
                     style = TextStyle(
                         fontSize = 18.sp,
-                        color = Color.Green,
+                        color = Colors.Primary,
                     )
                 )
             }
@@ -55,6 +65,29 @@ inline fun <reified T> StatePage(uiState: UiState<T>, onSuccess: @Composable (T)
                     horizontalAlignment = Alignment.CenterHorizontally,
                 ) {
                     EmptyState.NoNetwork()
+                    if (onError != null) {
+                        Spacer(height = 20.dp)
+                        Box(modifier = Modifier
+                            .clip(shape = RoundedCornerShape(100.dp))
+                            .clickable {
+                                onError.invoke()
+                            }
+                            .border(
+                                width = 1.dp,
+                                color = Colors.Primary,
+                                shape = RoundedCornerShape(100.dp)
+                            )
+                            .padding(horizontal = 20.dp, vertical = 6.dp)) {
+                            Text(
+                                text = "刷新",
+                                style = TextStyle(
+                                    color = Colors.Primary,
+                                    fontSize = 14.sp,
+                                    lineHeight = 17.sp,
+                                ),
+                            )
+                        }
+                    }
                 }
             }
         }

+ 204 - 19
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCourseGroupBuy.kt

@@ -3,46 +3,60 @@ package com.zaojiao.app.feat.home.course
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.scrollable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.absoluteOffset
 import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.layout.wrapContentWidth
 import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextDecoration
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import com.zaojiao.app.data.model.groupbuy.GroupBuyModel
 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
 import com.zaojiao.app.feat.design.Icons
+import com.zaojiao.app.feat.design.Images
 import com.zaojiao.app.feat.design.LJGBanner
 import com.zaojiao.app.feat.design.Spacer
+import com.zaojiao.app.feat.home.R
 
 @Composable
 internal fun HomeCourseGroupBuy(
     activityList: List<MicroPageImageModelItem>,
+    totalCount: Int,
+    groupBuyList: List<GroupBuyModel>,
 ) {
     Row(
         modifier = Modifier
@@ -52,7 +66,7 @@ internal fun HomeCourseGroupBuy(
     ) {
         HomeCourseGroupBuySwiper(activityList)
         Spacer(width = 10.dp)
-        HomeCourseGroupBuyRecommend()
+        HomeCourseGroupBuyRecommend(totalCount, groupBuyList)
     }
 }
 
@@ -108,7 +122,10 @@ private fun RowScope.HomeCourseGroupBuySwiper(
 }
 
 @Composable
-private fun RowScope.HomeCourseGroupBuyRecommend() {
+private fun RowScope.HomeCourseGroupBuyRecommend(
+    totalCount: Int,
+    groupBuyList: List<GroupBuyModel>,
+) {
     Box(
         modifier = Modifier
             .weight(1f)
@@ -121,15 +138,17 @@ private fun RowScope.HomeCourseGroupBuyRecommend() {
         Column(
             modifier = Modifier.fillMaxSize(),
         ) {
-            HomeCourseGroupBuyRecommendTitle()
+            HomeCourseGroupBuyRecommendTitle(totalCount)
             Spacer(height = 10.dp)
-            HomeCourseGroupBuyRecommendBody()
+            HomeCourseGroupBuyRecommendBody(groupBuyList)
         }
     }
 }
 
 @Composable
-private fun ColumnScope.HomeCourseGroupBuyRecommendTitle() {
+private fun ColumnScope.HomeCourseGroupBuyRecommendTitle(
+    totalCount: Int,
+) {
     Row(
         modifier = Modifier
             .weight(1f)
@@ -150,7 +169,7 @@ private fun ColumnScope.HomeCourseGroupBuyRecommendTitle() {
             verticalAlignment = Alignment.CenterVertically,
         ) {
             Text(
-                text = "22款上线",
+                text = "${totalCount}款上线",
                 style = TextStyle(
                     fontSize = 10.sp,
                     lineHeight = 11.sp,
@@ -160,9 +179,9 @@ private fun ColumnScope.HomeCourseGroupBuyRecommendTitle() {
             )
             Spacer(width = 2.dp)
             Icons.Forward(
-                size = Size(4f, 6f),
+                size = Size(2f, 5f),
                 color = Colors.from("#FFFB6C07"),
-                width = 0.5.dp,
+                width = 1.dp,
             )
         }
         Spacer(width = 11.dp)
@@ -170,7 +189,9 @@ private fun ColumnScope.HomeCourseGroupBuyRecommendTitle() {
 }
 
 @Composable
-private fun HomeCourseGroupBuyRecommendBody() {
+private fun HomeCourseGroupBuyRecommendBody(
+    groupBuyList: List<GroupBuyModel>,
+) {
     Column(
         modifier = Modifier
             .padding(horizontal = 11.dp)
@@ -186,16 +207,120 @@ private fun HomeCourseGroupBuyRecommendBody() {
         ) {
             Box(
                 modifier = Modifier
-                    .background(color = Color.Blue)
                     .width(64.dp)
-                    .height(64.dp),
-            )
+                    .height(64.dp)
+                    .padding(vertical = 2.dp),
+            ) {
+                if (groupBuyList.isNotEmpty()) {
+                    Column(
+                        modifier = Modifier.fillMaxSize(),
+                        verticalArrangement = Arrangement.SpaceBetween,
+                    ) {
+                        Box(
+                            modifier = Modifier
+                                .background(
+                                    brush = Brush.horizontalGradient(
+                                        colors = listOf(
+                                            Colors.from("#FFFF9957"),
+                                            Colors.from("#FFFF7979")
+                                        ),
+                                    ),
+                                    shape = RoundedCornerShape(4.dp),
+                                )
+                                .padding(bottom = 1.dp)
+                                .padding(horizontal = 4.dp)
+                                .wrapContentSize()
+                        ) {
+                            Text(
+                                text = "¥${groupBuyList[0].population}人拼团价",
+                                style = TextStyle(
+                                    color = Color.White,
+                                    fontSize = 10.sp,
+                                    lineHeight = 12.sp,
+                                ),
+                            )
+                        }
+                        Spacer(height = 4.dp)
+                        Row(
+                            modifier = Modifier.wrapContentSize(),
+                            verticalAlignment = Alignment.Bottom,
+                        ) {
+                            Text(
+                                text = "¥${groupBuyList[0].groupMinPrice}",
+                                style = TextStyle(
+                                    color = Colors.from("#FFFB6C07"),
+                                    fontSize = 14.sp,
+                                    lineHeight = 17.sp,
+                                    fontWeight = FontWeight.Medium,
+                                ),
+                            )
+                            Text(
+                                text = "¥${groupBuyList[0].originalPrice}",
+                                style = TextStyle(
+                                    color = Colors.from("#FFB1B1B1"),
+                                    fontSize = 10.sp,
+                                    lineHeight = 12.sp,
+                                    fontWeight = FontWeight.Normal,
+                                    textDecoration = TextDecoration.LineThrough,
+                                ),
+                            )
+                        }
+                        Expanded()
+                        Row(
+                            modifier = Modifier
+                                .scrollable(
+                                    state = rememberScrollState(),
+                                    orientation = Orientation.Horizontal,
+                                )
+                                .wrapContentHeight()
+                                .wrapContentWidth(unbounded = true),
+                        ) {
+                            val users = groupBuyList[0].userList
+
+                            val opacityList = List(users.size) {
+                                0.4f + 0.6f * it.toFloat() / users.size
+                            }
+
+                            repeat(users.size) {
+                                Images.Network(
+                                    url = users[it].avatar,
+                                    modifier = Modifier
+                                        .absoluteOffset(
+                                            x = -(it * 5).dp,
+                                        )
+                                        .alpha(opacityList[it])
+                                        .border(
+                                            width = 0.5.dp,
+                                            brush = Brush.horizontalGradient(
+                                                colors = listOf(
+                                                    Colors.from("#FFFF6141"),
+                                                    Colors.from("#00FF4D46")
+                                                ),
+                                            ),
+                                            shape = RoundedCornerShape(15.dp),
+                                        )
+                                        .clip(shape = RoundedCornerShape(15.dp))
+                                        .requiredSize(14.dp),
+                                )
+                            }
+                        }
+                    }
+                }
+            }
             Box(
                 modifier = Modifier
-                    .background(color = Color.Blue)
                     .width(64.dp)
                     .height(64.dp),
-            )
+            ) {
+                if (groupBuyList.isNotEmpty()) {
+                    Images.Network(
+                        url = groupBuyList[0].productImage,
+                        modifier = Modifier
+                            .clip(RoundedCornerShape(10.dp))
+                            .size(64.dp),
+                    )
+                }
+            }
         }
         Spacer(height = 15.dp)
         Row(
@@ -206,16 +331,76 @@ private fun HomeCourseGroupBuyRecommendBody() {
         ) {
             Box(
                 modifier = Modifier
-                    .background(color = Color.Blue)
                     .width(64.dp)
                     .height(74.5.dp),
-            )
+            ) {
+                if (groupBuyList.size >= 2) {
+                    Images.Network(
+                        url = groupBuyList[1].productImage,
+                        modifier = Modifier
+                            .align(Alignment.TopCenter)
+                            .clip(RoundedCornerShape(10.dp))
+                            .size(64.dp),
+                    )
+                    Box(
+                        modifier = Modifier
+                            .border(
+                                width = 1.dp,
+                                color = Colors.from("#FFFB6C07"),
+                                shape = RoundedCornerShape(100.dp),
+                            )
+                            .padding(horizontal = 8.dp, vertical = 2.dp)
+                            .align(Alignment.BottomCenter)
+                            .wrapContentSize(),
+                    ) {
+                        Text(
+                            text = "¥${groupBuyList[1].groupMinPrice}",
+                            style = TextStyle(
+                                color = Colors.from("#FFFB6C07"),
+                                fontSize = 14.sp,
+                                lineHeight = 17.sp,
+                                fontWeight = FontWeight.Medium,
+                            ),
+                        )
+                    }
+                }
+            }
             Box(
                 modifier = Modifier
-                    .background(color = Color.Blue)
                     .width(64.dp)
                     .height(74.5.dp),
-            )
+            ) {
+                if (groupBuyList.size >= 3) {
+                    Images.Network(
+                        url = groupBuyList[2].productImage,
+                        modifier = Modifier
+                            .align(Alignment.TopCenter)
+                            .clip(RoundedCornerShape(10.dp))
+                            .size(64.dp),
+                    )
+                    Box(
+                        modifier = Modifier
+                            .border(
+                                width = 1.dp,
+                                color = Colors.from("#FFFB6C07"),
+                                shape = RoundedCornerShape(100.dp),
+                            )
+                            .padding(horizontal = 8.dp, vertical = 2.dp)
+                            .align(Alignment.BottomCenter)
+                            .wrapContentSize(),
+                    ) {
+                        Text(
+                            text = "¥${groupBuyList[2].groupMinPrice}",
+                            style = TextStyle(
+                                color = Colors.from("#FFFB6C07"),
+                                fontSize = 14.sp,
+                                lineHeight = 17.sp,
+                                fontWeight = FontWeight.Medium,
+                            ),
+                        )
+                    }
+                }
+            }
         }
     }
 }

+ 14 - 4
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCoursePage.kt

@@ -16,6 +16,7 @@ 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.groupbuy.GroupBuyModel
 import com.zaojiao.app.data.model.micropage.MicroPageImageModel
 import com.zaojiao.app.data.model.micropage.MicroPageImageModelItem
 import com.zaojiao.app.feat.design.Screen
@@ -33,12 +34,16 @@ internal fun HomeCourseRoute(
         color = Color.White,
         modifier = Modifier.fillMaxSize(),
     ) {
-        StatePage(uiState = uiState) { state ->
+        StatePage(
+            uiState = uiState,
+            onError = { viewModel.initialState(true) }
+        ) { state ->
             HomeCoursePage(
                 bannerList = state.bannerList,
                 categoryList = state.categoryList,
                 activityList = state.activityList,
-                groupBuyList = emptyList(),
+                groupBuyList = state.groupBuyList,
+                totalCount = state.totalCount,
                 courseList = courseList,
             )
         }
@@ -51,7 +56,8 @@ fun HomeCoursePage(
     bannerList: List<MicroPageImageModelItem>,
     categoryList: List<CourseCategoryModel>,
     activityList: List<MicroPageImageModelItem>,
-    groupBuyList: List<MicroPageImageModelItem>,
+    groupBuyList: List<GroupBuyModel>,
+    totalCount: Int,
     courseList: LazyPagingItems<CourseModel>,
 ) {
 
@@ -83,7 +89,11 @@ fun HomeCoursePage(
         }
 
         item {
-            HomeCourseGroupBuy(activityList)
+            HomeCourseGroupBuy(
+                activityList = activityList,
+                totalCount = totalCount,
+                groupBuyList = groupBuyList,
+            )
         }
 
         item {

+ 8 - 7
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCourseViewModel.kt

@@ -5,32 +5,31 @@ 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.groupbuy.GroupBuyModel
 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.data.repo.GroupBuyRepository
 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>,
+    val groupBuyList: List<GroupBuyModel>,
+    val totalCount: Int,
 )
 
 @HiltViewModel
 class HomeCourseViewModel @Inject constructor(
     private val courseRepository: CourseRepository,
+    private val groupBuyRepository: GroupBuyRepository,
 ) : BaseViewModel<HomeCourseUiState>() {
 
     val pagingData: Flow<PagingData<CourseModel>> = flow {
@@ -51,13 +50,15 @@ class HomeCourseViewModel @Inject constructor(
         val banner = jsonModel[1] as MicroPageImageModel
         val activity = jsonModel[2] as MicroPageImageModel
 
+        val groupBuyList = groupBuyRepository.getRecommendGroupBuy()
         val categoryList = courseRepository.getCategoryList()
 
         return HomeCourseUiState(
             bannerList = banner.imageList,
             categoryList = categoryList,
             activityList = activity.imageList,
-            groupBuyList = emptyList(),
+            groupBuyList = groupBuyList.records,
+            totalCount = groupBuyList.total,
         )
     }
 }