Quellcode durchsuchen

feat: JoinUs模块

DoggyZhang vor 2 Monaten
Ursprung
Commit
2420c6f1e8
100 geänderte Dateien mit 3317 neuen und 88 gelöschten Zeilen
  1. 1 0
      app/build.gradle
  2. 5 0
      app/src/main/java/com/adealink/weparty/commonui/text/TextEx.kt
  3. 1 1
      app/src/main/java/com/adealink/weparty/commonui/widget/CommonTopBar.kt
  4. 19 0
      app/src/main/java/com/adealink/weparty/constant/Constants.kt
  5. 5 12
      app/src/main/java/com/adealink/weparty/country/CountrySelectDialog.kt
  6. 3 3
      app/src/main/java/com/adealink/weparty/image/Constants.kt
  7. 3 13
      app/src/main/java/com/adealink/weparty/module/account/comp/VerifyCodeComp.kt
  8. 7 0
      app/src/main/java/com/adealink/weparty/module/joinus/IJoinUsService.kt
  9. 31 0
      app/src/main/java/com/adealink/weparty/module/joinus/JoinUsModule.kt
  10. 20 0
      app/src/main/java/com/adealink/weparty/module/joinus/Router.kt
  11. 4 0
      app/src/main/java/com/adealink/weparty/module/joinus/listener/IJoinUsListener.kt
  12. 4 0
      app/src/main/java/com/adealink/weparty/module/joinus/viewmodel/IJoinUsViewModel.kt
  13. 0 8
      app/src/main/java/com/adealink/weparty/module/profile/data/ProfileConstants.kt
  14. 14 1
      app/src/main/java/com/adealink/weparty/module/profile/data/ProfileData.kt
  15. 0 0
      app/src/main/res/drawable-xhdpi/common_phone_country_code_select_ic.png
  16. 0 0
      app/src/main/res/drawable/common_code_input_box_bg.xml
  17. 0 0
      app/src/main/res/drawable/common_phone_input_bg.xml
  18. 0 0
      app/src/main/res/drawable/profile_edit_gender_bg.xml
  19. 0 0
      app/src/main/res/drawable/profile_edit_gender_selected_bg.xml
  20. 0 0
      app/src/main/res/drawable/profile_edit_gender_unselect_bg.xml
  21. 4 4
      app/src/main/res/layout/dialog_select_country.xml
  22. 0 0
      app/src/main/res/layout/layout_verify_code.xml
  23. 1 0
      app/src/main/res/values/no_translate_strings.xml
  24. 11 0
      app/src/main/res/values/strings.xml
  25. 15 0
      app/src/main/res/values/styles.xml
  26. 1 0
      app/src/main/resources/META-INF/services/com.adealink.frame.router.IRouterInit
  27. 3 2
      module/account/src/main/java/com/adealink/weparty/account/login/phone/fragment/InputPhoneFragment.kt
  28. 16 8
      module/account/src/main/java/com/adealink/weparty/account/login/phone/fragment/VerifyPhoneFragment.kt
  29. 3 3
      module/account/src/main/java/com/adealink/weparty/account/login/phone/viewmodel/PhoneLoginViewModel.kt
  30. 8 5
      module/account/src/main/res/layout/fragment_account_input_phone.xml
  31. 1 1
      module/account/src/main/res/layout/fragment_account_verify_phone.xml
  32. 0 10
      module/account/src/main/res/values/strings.xml
  33. 0 16
      module/account/src/main/res/values/styles.xml
  34. 3 0
      module/im/src/main/java/com/adealink/weparty/im/session/comp/SessionBottomInputComp.kt
  35. 0 1
      module/im/src/main/res/layout/layout_session_bottom_emotion_bar.xml
  36. 1 0
      module/joinus/.gitignore
  37. 56 0
      module/joinus/build.gradle
  38. 0 0
      module/joinus/consumer-rules.pro
  39. 21 0
      module/joinus/proguard-rules.pro
  40. 25 0
      module/joinus/src/main/AndroidManifest.xml
  41. 156 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/JoinUsActivity.kt
  42. 24 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/JoinUsServiceImpl.kt
  43. 66 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/comp/JoinStepProgressComp.kt
  44. 146 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/data/JoinUsData.kt
  45. 4 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/data/Tags.kt
  46. 18 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/datasource/local/JoinUsLocalService.kt
  47. 38 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/datasource/remote/JoinUsHttpService.kt
  48. 16 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/review/JoinUsStepReviewFragment.kt
  49. 150 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/step1/InputPhoneFragment.kt
  50. 161 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/step1/InputVerifyFragment.kt
  51. 57 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/step1/JoinUsStep1Fragment.kt
  52. 6 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/step1/data/Step1Data.kt
  53. 156 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/step1/viewmodel/Step1ViewModel.kt
  54. 114 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/step2/JoinUsStep2Fragment.kt
  55. 85 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/step2/comp/EditAvatarComp.kt
  56. 48 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/step2/comp/EditBirthdayComp.kt
  57. 63 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/step2/comp/EditGenderComp.kt
  58. 65 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/step2/comp/EditIntroductionComp.kt
  59. 47 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/step2/comp/EditLanguageComp.kt
  60. 74 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/step2/comp/EditNicknameComp.kt
  61. 44 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/step2/dialog/ExampleAvatarDialog.kt
  62. 128 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/step2/viewmodel/Step2ViewModel.kt
  63. 16 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/step3/JoinUsStep3Fragment.kt
  64. 47 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/viewmodel/JoinUsViewModel.kt
  65. 29 0
      module/joinus/src/main/java/com/adealink/weparty/joinus/viewmodel/JoinUsViewModelFactory.kt
  66. BIN
      module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_pass_ic.png
  67. BIN
      module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_pass_tag_ic.png
  68. BIN
      module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_wrong_1.png
  69. BIN
      module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_wrong_2.png
  70. BIN
      module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_wrong_3.png
  71. BIN
      module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_wrong_4.png
  72. BIN
      module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_wrong_5.png
  73. BIN
      module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_wrong_6.png
  74. BIN
      module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_wrong_7.png
  75. BIN
      module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_wrong_8.png
  76. BIN
      module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_wrong_tag_ic.png
  77. BIN
      module/joinus/src/main/res/drawable-xhdpi/join_us_top_bg.png
  78. BIN
      module/joinus/src/main/res/drawable-xhdpi/join_us_under_review_ic.png
  79. BIN
      module/joinus/src/main/res/drawable-xhdpi/join_us_under_review_top_bg.png
  80. 7 0
      module/joinus/src/main/res/drawable/join_us_avatar_tip_tag_ic.xml
  81. 8 0
      module/joinus/src/main/res/drawable/join_us_code_input_box_bg.xml
  82. 9 0
      module/joinus/src/main/res/drawable/join_us_edit_gender_bg.xml
  83. 6 0
      module/joinus/src/main/res/drawable/join_us_progress_bg.xml
  84. 5 0
      module/joinus/src/main/res/drawable/join_us_progress_index_bg.xml
  85. 5 0
      module/joinus/src/main/res/drawable/join_us_progress_index_selected_bg.xml
  86. 48 0
      module/joinus/src/main/res/layout/activity_join_us.xml
  87. 270 0
      module/joinus/src/main/res/layout/dialog_example_avatar.xml
  88. 5 0
      module/joinus/src/main/res/layout/fragment_join_us_step_1.xml
  89. 119 0
      module/joinus/src/main/res/layout/fragment_join_us_step_1_input_phone.xml
  90. 93 0
      module/joinus/src/main/res/layout/fragment_join_us_step_1_input_verify.xml
  91. 74 0
      module/joinus/src/main/res/layout/fragment_join_us_step_2.xml
  92. 7 0
      module/joinus/src/main/res/layout/fragment_join_us_step_3.xml
  93. 54 0
      module/joinus/src/main/res/layout/fragment_join_us_step_under_review.xml
  94. 44 0
      module/joinus/src/main/res/layout/layout_example_wrong_avatar.xml
  95. 129 0
      module/joinus/src/main/res/layout/layout_join_us_progress.xml
  96. 60 0
      module/joinus/src/main/res/layout/layout_join_us_step2_edit_avatar.xml
  97. 55 0
      module/joinus/src/main/res/layout/layout_join_us_step2_edit_avatar_selector.xml
  98. 80 0
      module/joinus/src/main/res/layout/layout_join_us_step2_edit_birthday.xml
  99. 115 0
      module/joinus/src/main/res/layout/layout_join_us_step2_edit_gender.xml
  100. 80 0
      module/joinus/src/main/res/layout/layout_join_us_step2_edit_introduction.xml

+ 1 - 0
app/build.gradle

@@ -228,6 +228,7 @@ android {
             ':module:wallet',
             ':module:share',
             ':module:image',
+            ':module:joinus',
     ]
     buildFeatures {
         viewBinding true

+ 5 - 0
app/src/main/java/com/adealink/weparty/commonui/text/TextEx.kt

@@ -1,5 +1,6 @@
 package com.adealink.weparty.commonui.text
 
+import android.text.InputFilter
 import android.widget.TextView
 import kotlin.math.min
 
@@ -45,4 +46,8 @@ fun TextView.getEllipsisCount(maxLines: Int): Int {
         }
         0
     } ?: 0
+}
+
+fun TextView.setMaxLength(maxLength: Int){
+    filters = arrayOf(InputFilter.LengthFilter(maxLength))
 }

+ 1 - 1
app/src/main/java/com/adealink/weparty/commonui/widget/CommonTopBar.kt

@@ -73,7 +73,7 @@ class CommonTopBar @JvmOverloads constructor(
         setTitleTextColor(typedArray.getResourceId(R.styleable.CommonTopBar_title_color,
             R.color.color_222222))
         binding.root.setBackgroundResource(typedArray.getResourceId(R.styleable.CommonTopBar_bar_background,
-            R.color.white))
+            R.color.transparent))
     }
 
     fun setTitle(titleStr: String) {

+ 19 - 0
app/src/main/java/com/adealink/weparty/constant/Constants.kt

@@ -0,0 +1,19 @@
+package com.adealink.weparty.constant
+
+//输入最大长度配置
+//用户昵称
+const val PROFILE_NICKNAME_MAX_LENGTH = 25
+//用户自我介绍
+const val PROFILE_INTRODUCTION_MAX_LENGTH = 100
+//IM最大可输入长度
+const val IM_MESSAGE_MAX_LENGTH = 200
+//退款原因
+const val REFUND_REASON_MAX_LENGTH = 500
+//订单备注
+const val ORDER_REMARK_MAX_LENGTH = 150
+//订单评价
+const val ORDER_COMMENT_MAX_LENGTH = 200
+//帮助中心文本内容
+const val HELP_CENTER_CONTENT_MAX_LENGTH = 200
+//举报文本内容
+const val REPORT_CONTENT_MAX_LENGTH = 200

+ 5 - 12
app/src/main/java/com/adealink/weparty/country/CountrySelectDialog.kt

@@ -1,6 +1,5 @@
 package com.adealink.weparty.country
 
-import android.view.ViewGroup
 import androidx.fragment.app.viewModels
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
@@ -28,14 +27,12 @@ class CountrySelectDialog : BottomDialogFragment(R.layout.dialog_select_country)
 
     private val countryViewModel by viewModels<CountryViewModel>()
 
-    private var onItemSelect: ((item: CountryItemData) -> Unit)? = null
+    private var onItemSelectListener: ((item: CountryItemData) -> Unit)? = null
 
     private var titleToContent = mutableListOf<Int>()
 
-    override val height: Int = ViewGroup.LayoutParams.WRAP_CONTENT
-
     fun setOnItemSelect(select: ((item: CountryItemData) -> Unit)? = null) {
-        this.onItemSelect = select
+        this.onItemSelectListener = select
     }
 
     override fun initViews() {
@@ -45,8 +42,9 @@ class CountrySelectDialog : BottomDialogFragment(R.layout.dialog_select_country)
         }
 
         listAdapter.register(CountryTitleItemViewBinder())
-        listAdapter.register(CountryItemViewBinder {
-            onItemSelect
+        listAdapter.register(CountryItemViewBinder { item ->
+            onItemSelectListener?.invoke(item)
+            dismiss()
         })
 
         binding.rvCountry.adapter = listAdapter
@@ -97,9 +95,4 @@ class CountrySelectDialog : BottomDialogFragment(R.layout.dialog_select_country)
         })
     }
 
-    private fun onItemSelect(item: CountryItemData) {
-        onItemSelect?.invoke(item)
-        dismiss()
-    }
-
 }

+ 3 - 3
app/src/main/java/com/adealink/weparty/image/Constants.kt

@@ -1,8 +1,8 @@
 package com.adealink.weparty.image
 
-const val AVATAR_IMAGE_MAX_WIDTH = 300
-const val AVATAR_IMAGE_MAX_HEIGHT = 300
-const val AVATAR_IMAGE_MAX_SIZE_KB = 100
+const val AVATAR_IMAGE_MAX_WIDTH = 600
+const val AVATAR_IMAGE_MAX_HEIGHT = 600
+const val AVATAR_IMAGE_MAX_SIZE_KB = 200
 const val AVATAR_IMAGE_MIN_QUALITY = 50
 
 const val PHOTO_WALL_IMAGE_MAX_WIDTH = 750

+ 3 - 13
module/account/src/main/java/com/adealink/weparty/account/login/phone/comp/VerifyCodeComp.kt → app/src/main/java/com/adealink/weparty/module/account/comp/VerifyCodeComp.kt

@@ -1,27 +1,17 @@
-package com.adealink.weparty.account.login.phone.comp
+package com.adealink.weparty.module.account.comp
 
 import android.text.Editable
 import android.text.TextWatcher
 import android.view.KeyEvent
+import androidx.appcompat.widget.AppCompatEditText
 import androidx.lifecycle.LifecycleOwner
 import com.adealink.frame.mvvm.view.ViewComponent
-import com.adealink.frame.mvvm.viewmodel.activityViewModels
-import com.adealink.weparty.account.databinding.LayoutAccountVerifyCodeBinding
-import com.adealink.weparty.account.login.phone.viewmodel.PhoneLoginViewModel
-import com.adealink.weparty.account.viewModel.AccountViewModelFactory
 
 class VerifyCodeComp(
     lifecycleOwner: LifecycleOwner,
-    private val binding: LayoutAccountVerifyCodeBinding,
+    private val inputBoxes: List<AppCompatEditText>,
     private val onCodeCompleted: (code: String) -> Unit,
 ) : ViewComponent(lifecycleOwner) {
-
-    private val phoneViewModel by activityViewModels<PhoneLoginViewModel> { AccountViewModelFactory() }
-
-    private val inputBoxes = listOf(
-        binding.et1, binding.et2, binding.et3, binding.et4
-    )
-
     override fun onCreate() {
         super.onCreate()
         initView()

+ 7 - 0
app/src/main/java/com/adealink/weparty/module/joinus/IJoinUsService.kt

@@ -0,0 +1,7 @@
+package com.adealink.weparty.module.joinus
+
+import com.adealink.weparty.aab.IService
+
+interface IJoinUsService : IService<IJoinUsService> {
+
+}

+ 31 - 0
app/src/main/java/com/adealink/weparty/module/joinus/JoinUsModule.kt

@@ -0,0 +1,31 @@
+package com.adealink.weparty.module.joinus
+
+import com.adealink.frame.aab.BaseDynamicModule
+import com.adealink.weparty.R
+
+object JoinUsModule : BaseDynamicModule<IJoinUsService>(IJoinUsService::class),
+    IJoinUsService {
+
+    override val featureName: String
+        get() = "joinus"
+
+    override val moduleNameResId: Int
+        get() = R.string.module_joinus
+
+    override fun emptyService(): IJoinUsService {
+        return object : IJoinUsService {
+
+            override fun getService(): IJoinUsService? {
+                return null
+            }
+
+            override fun onLogout() {
+
+            }
+        }
+    }
+
+    override fun onLogout() {
+        getService().onLogout()
+    }
+}

+ 20 - 0
app/src/main/java/com/adealink/weparty/module/joinus/Router.kt

@@ -0,0 +1,20 @@
+package com.adealink.weparty.module.joinus
+
+
+interface JoinUs {
+
+    interface Common {
+
+        companion object {
+            const val PATH = "/joinus"
+        }
+
+    }
+
+    interface JoinUs {
+        companion object {
+            const val PATH = Common.PATH
+        }
+    }
+
+}

+ 4 - 0
app/src/main/java/com/adealink/weparty/module/joinus/listener/IJoinUsListener.kt

@@ -0,0 +1,4 @@
+package com.adealink.weparty.module.joinus.listener
+
+interface IJoinUsListener {
+}

+ 4 - 0
app/src/main/java/com/adealink/weparty/module/joinus/viewmodel/IJoinUsViewModel.kt

@@ -0,0 +1,4 @@
+package com.adealink.weparty.module.joinus.viewmodel
+
+interface IJoinUsViewModel {
+}

+ 0 - 8
app/src/main/java/com/adealink/weparty/module/profile/data/ProfileConstants.kt

@@ -1,11 +1,3 @@
 package com.adealink.weparty.module.profile.data
 
-import com.adealink.frame.base.fastLazy
-import com.adealink.frame.oss.ossService
-
-val DEFAULT_AVATAR_URL by fastLazy { ossService.getUrlByPath("/audit/avatar_default.png") }
-
 const val TAB_INDEX_PERSONAL = 0
-
-
-const val QUERY_NOTE_PAGE_SIZE = 3000

+ 14 - 1
app/src/main/java/com/adealink/weparty/module/profile/data/ProfileData.kt

@@ -172,6 +172,12 @@ class EditUserInfo() {
             field = value
         }
 
+    var languages: List<String>? = null
+        set(value) {
+            field = value
+            isEdited = true
+        }
+
     fun resetBy(userInfo: UserInfo?) {
         avatar = PhotoData(url = userInfo?.avatar, null, null)
         nickname = userInfo?.nickName
@@ -183,6 +189,7 @@ class EditUserInfo() {
         birthday = userInfo?.birthday
         interest = userInfo?.interests
         voice = VoiceData(url = userInfo?.voiceBar, null)
+        languages = userInfo?.languageNames
         isEdited = false
     }
 
@@ -233,4 +240,10 @@ data class VoiceData(
     fun isNull(): Boolean {
         return url.isNullOrEmpty() && path.isNullOrEmpty()
     }
-}
+}
+
+
+data class LanguageData(
+    val key: String,
+    val language: String
+)

+ 0 - 0
module/account/src/main/res/drawable-xhdpi/account_phone_country_code_select_ic.png → app/src/main/res/drawable-xhdpi/common_phone_country_code_select_ic.png


+ 0 - 0
module/account/src/main/res/drawable/account_code_input_box_bg.xml → app/src/main/res/drawable/common_code_input_box_bg.xml


+ 0 - 0
module/account/src/main/res/drawable/account_phone_input_bg.xml → app/src/main/res/drawable/common_phone_input_bg.xml


+ 0 - 0
module/profile/src/main/res/drawable/profile_edit_gender_bg.xml → app/src/main/res/drawable/profile_edit_gender_bg.xml


+ 0 - 0
module/profile/src/main/res/drawable/profile_edit_gender_selected_bg.xml → app/src/main/res/drawable/profile_edit_gender_selected_bg.xml


+ 0 - 0
module/profile/src/main/res/drawable/profile_edit_gender_unselect_bg.xml → app/src/main/res/drawable/profile_edit_gender_unselect_bg.xml


+ 4 - 4
app/src/main/res/layout/dialog_select_country.xml

@@ -35,19 +35,19 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:maxHeight="480dp"
+        app:layout_constrainedHeight="true"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/tv_title"
-        app:layout_constraintVertical_bias="0" />
+        app:layout_constraintTop_toBottomOf="@id/tv_title" />
 
     <com.adealink.weparty.commonui.scroller.BubbleScroller
         android:id="@+id/v_scroller"
         android:layout_width="32dp"
-        android:layout_height="wrap_content"
+        android:layout_height="0dp"
+        android:layout_gravity="center"
         app:bubbleScroller_showHighlight="false"
         app:bubbleScroller_textColor="@color/color_FF878A99"
         app:bubbleScroller_textSize="16sp"
-        app:layout_constrainedHeight="true"
         app:layout_constraintBottom_toBottomOf="@id/rv_country"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintTop_toTopOf="@id/rv_country" />

+ 0 - 0
module/account/src/main/res/layout/layout_account_verify_code.xml → app/src/main/res/layout/layout_verify_code.xml


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

@@ -12,5 +12,6 @@
     <string name="module_wallet" translatable="false">wallet</string>
     <string name="module_share" translatable="false">share</string>
     <string name="module_image" translatable="false">image</string>
+    <string name="module_joinus" translatable="false">activity</string>
 
 </resources>

+ 11 - 0
app/src/main/res/values/strings.xml

@@ -308,4 +308,15 @@
     <string name="common_choose_country">选择国家或地区</string>
     <string name="common_mic_is_being_used_cant_record">麦克风正在被其他功能使用,无法录音。</string>
     <string name="common_record_audio_failed">录音失败。</string>
+    <string name="common_input_phone_hint">手机号码</string>
+    <string name="account_input_phone_country_code_empty">请选择手机区号</string>
+    <string name="account_input_phone_number_empty">请输入手机号码</string>
+    <string name="account_input_phone_number_invalid">手机号码不完整</string>
+    <string name="account_input_verify_code_title">输入验证码</string>
+    <string name="account_input_verify_code_desc">验证码已发送至\n%s</string>
+    <string name="account_resend_verify_code_duration">重新发送验证码 (%s)</string>
+    <string name="account_resend_verify_code_title">还没收到验证码? %s</string>
+    <string name="account_resend_verify_code_content">重新发送</string>
+    <string name="account_send_verify_code_duration_limit">每次发送验证码需要间隔60秒(剩余%s秒)</string>
+    <string name="common_example">example</string>
 </resources>

+ 15 - 0
app/src/main/res/values/styles.xml

@@ -175,4 +175,19 @@
         <item name="ripple_circle_count">2</item>
         <item name="ripple_circle_stroke_width">2dp</item>
     </style>
+
+    <style name="CodeInputBox">
+        <item name="android:layout_width">64dp</item>
+        <item name="android:layout_height">64dp</item>
+        <item name="fontFamily">@font/poppins_semibold</item>
+        <item name="android:background">@drawable/common_code_input_box_bg</item> <!-- 你的自定义背景图 -->
+        <item name="android:gravity">center</item>
+        <item name="android:inputType">number</item>
+        <item name="android:includeFontPadding">false</item>
+        <item name="android:maxLength">1</item>
+        <item name="android:textSize">32sp</item>
+        <item name="android:textColor">@color/color_FF1D2129</item>
+        <item name="android:cursorVisible">true</item>
+        <!--        <item name="android:textCursorDrawable">@drawable/edittext_cursor</item>-->
+    </style>
 </resources>

+ 1 - 0
app/src/main/resources/META-INF/services/com.adealink.frame.router.IRouterInit

@@ -9,3 +9,4 @@ com.adealink.frame.router.RouterInit_module_order
 com.adealink.frame.router.RouterInit_module_wallet
 com.adealink.frame.router.RouterInit_module_share
 com.adealink.frame.router.RouterInit_module_image
+com.adealink.frame.router.RouterInit_module_joinus

+ 3 - 2
module/account/src/main/java/com/adealink/weparty/account/login/phone/fragment/InputPhoneFragment.kt

@@ -25,6 +25,7 @@ import com.adealink.weparty.commonui.toast.util.showToast
 import com.adealink.weparty.country.CountrySelectDialog
 import com.adealink.weparty.country.data.CountryData
 import com.adealink.weparty.country.viewmodel.CountryViewModel
+import com.adealink.weparty.R as APP_R
 
 class InputPhoneFragment : BaseFragment(R.layout.fragment_account_input_phone) {
 
@@ -121,7 +122,7 @@ class InputPhoneFragment : BaseFragment(R.layout.fragment_account_input_phone) {
             return
         }
         if (selectCountry == null) {
-            showToast(R.string.account_input_phone_country_code_empty)
+            showToast(APP_R.string.account_input_phone_country_code_empty)
             return
         }
         val regionCode = selectCountry?.regionCode
@@ -134,7 +135,7 @@ class InputPhoneFragment : BaseFragment(R.layout.fragment_account_input_phone) {
         }
         val number = binding.etNumberInput.text?.toString()
         if (number.isNullOrEmpty()) {
-            showToast(R.string.account_input_phone_number_empty)
+            showToast(APP_R.string.account_input_phone_number_empty)
             return
         }
         showLoading()

+ 16 - 8
module/account/src/main/java/com/adealink/weparty/account/login/phone/fragment/VerifyPhoneFragment.kt

@@ -17,7 +17,6 @@ import com.adealink.frame.util.runOnUiThread
 import com.adealink.frame.util.statusBarHeight
 import com.adealink.weparty.account.R
 import com.adealink.weparty.account.databinding.FragmentAccountVerifyPhoneBinding
-import com.adealink.weparty.account.login.phone.comp.VerifyCodeComp
 import com.adealink.weparty.account.login.phone.data.Page
 import com.adealink.weparty.account.login.phone.viewmodel.PhoneLoginViewModel
 import com.adealink.weparty.account.login.viewmodel.LoginViewModel
@@ -47,8 +46,8 @@ class VerifyPhoneFragment : BaseFragment(R.layout.fragment_account_verify_phone)
             topMargin = activity?.statusBarHeight() ?: 0
         }
 
-        val resendBtn = getCompatString(R.string.account_resend_verify_code_content)
-        val resendText = getCompatString(R.string.account_resend_verify_code_title, resendBtn)
+        val resendBtn = getCompatString(APP_R.string.account_resend_verify_code_content)
+        val resendText = getCompatString(APP_R.string.account_resend_verify_code_title, resendBtn)
         binding.tvResend.text = SpannableStringBuilder(resendText).apply {
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                 findAndSetSpan(
@@ -70,15 +69,24 @@ class VerifyPhoneFragment : BaseFragment(R.layout.fragment_account_verify_phone)
 
     override fun initComponents() {
         super.initComponents()
-        VerifyCodeComp(this, binding.vVerifyCode) { code ->
-            checkVerifyCode(code)
-        }.attach()
+//        VerifyCodeComp(
+//            this,
+//            listOf(
+//                binding.vVerifyCode.et1,
+//                binding.vVerifyCode.et2,
+//                binding.vVerifyCode.et3,
+//                binding.vVerifyCode.et4
+//            )
+//        ) { code ->
+//            checkVerifyCode(code)
+//        }.attach()
     }
 
     override fun observeViewModel() {
         super.observeViewModel()
         phoneViewModel.verifyPhoneNumberLD.observe(viewLifecycleOwner) { number ->
-            binding.tvDesc.text = getCompatString(R.string.account_input_verify_code_desc, number)
+            binding.tvDesc.text =
+                getCompatString(APP_R.string.account_input_verify_code_desc, number)
         }
         phoneViewModel.sendVerifyEnableLD.observe(viewLifecycleOwner) {
             updateResendText(it)
@@ -95,7 +103,7 @@ class VerifyPhoneFragment : BaseFragment(R.layout.fragment_account_verify_phone)
         if (isViewBindingValid()) {
             resendCountDownS = max(resendCountDownS - 1, 0)
             binding.tvResendDuration.text = getCompatString(
-                R.string.account_resend_verify_code_duration,
+                APP_R.string.account_resend_verify_code_duration,
                 resendCountDownS.toString()
             )
             if (resendCountDownS > 0) {

+ 3 - 3
module/account/src/main/java/com/adealink/weparty/account/login/phone/viewmodel/PhoneLoginViewModel.kt

@@ -10,13 +10,13 @@ import com.adealink.frame.mvvm.livedata.OnceMutableLiveData
 import com.adealink.frame.mvvm.viewmodel.BaseViewModel
 import com.adealink.frame.network.data.Res
 import com.adealink.weparty.App
-import com.adealink.weparty.account.R
 import com.adealink.weparty.account.constant.TAG_PHONE_LOGIN
 import com.adealink.weparty.account.login.datasource.remote.LoginHttpService
 import com.adealink.weparty.account.login.phone.data.Page
 import com.adealink.weparty.account.login.phone.data.SendPhoneVerifyReq
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
+import com.adealink.weparty.R as APP_R
 
 data class SendVerifyCode(
     val rlt: Rlt<Any>,
@@ -50,7 +50,7 @@ class PhoneLoginViewModel : BaseViewModel() {
             return Rlt.Failed(
                 IError(
                     getCompatString(
-                        R.string.account_send_verify_code_duration_limit,
+                        APP_R.string.account_send_verify_code_duration_limit,
                         (sendDuration / 1000).toString()
                     )
                 )
@@ -71,7 +71,7 @@ class PhoneLoginViewModel : BaseViewModel() {
             if (regionCode.isNullOrEmpty() || number.isNullOrEmpty()) {
                 liveData.send(
                     SendVerifyCode(
-                        Rlt.Failed(IError(getCompatString(R.string.account_input_phone_number_invalid))),
+                        Rlt.Failed(IError(getCompatString(APP_R.string.account_input_phone_number_invalid))),
                         lastSendVerifyCodeTs
                     )
                 )

+ 8 - 5
module/account/src/main/res/layout/fragment_account_input_phone.xml

@@ -74,7 +74,7 @@
             android:layout_width="match_parent"
             android:layout_height="52dp"
             android:layout_marginTop="30dp"
-            android:background="@drawable/account_phone_input_bg"
+            android:background="@drawable/common_phone_input_bg"
             android:paddingHorizontal="16dp"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
@@ -91,13 +91,16 @@
 
             <androidx.appcompat.widget.AppCompatTextView
                 android:id="@+id/tv_country_code"
-                android:layout_width="42dp"
+                android:layout_width="54dp"
                 android:layout_height="wrap_content"
                 android:fontFamily="@font/poppins_semibold"
                 android:gravity="center"
                 android:includeFontPadding="false"
                 android:textColor="@color/color_FF1D2129"
                 android:textSize="18sp"
+                app:autoSizeMaxTextSize="18sp"
+                app:autoSizeMinTextSize="14sp"
+                app:autoSizeTextType="uniform"
                 app:layout_constraintBottom_toBottomOf="parent"
                 app:layout_constraintStart_toEndOf="@id/iv_country"
                 app:layout_constraintTop_toTopOf="parent"
@@ -109,17 +112,17 @@
                 app:layout_constraintBottom_toBottomOf="parent"
                 app:layout_constraintStart_toEndOf="@id/tv_country_code"
                 app:layout_constraintTop_toTopOf="parent"
-                app:srcCompat="@drawable/account_phone_country_code_select_ic" />
+                app:srcCompat="@drawable/common_phone_country_code_select_ic" />
 
             <androidx.appcompat.widget.AppCompatEditText
                 android:id="@+id/et_number_input"
                 android:layout_width="0dp"
                 android:layout_height="0dp"
-                android:layout_marginStart="24dp"
+                android:layout_marginStart="12dp"
                 android:background="@null"
                 android:fontFamily="@font/poppins_semibold"
                 android:gravity="start|center_vertical"
-                android:hint="@string/account_input_phone_hint"
+                android:hint="@string/common_input_phone_hint"
                 android:includeFontPadding="false"
                 android:inputType="number"
                 android:lines="1"

+ 1 - 1
module/account/src/main/res/layout/fragment_account_verify_phone.xml

@@ -59,7 +59,7 @@
 
     <include
         android:id="@+id/v_verify_code"
-        layout="@layout/layout_account_verify_code"
+        layout="@layout/layout_verify_code"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginHorizontal="22dp"

+ 0 - 10
module/account/src/main/res/values/strings.xml

@@ -53,15 +53,5 @@
    <string name="account_phone_login_title">登录后</string>
    <string name="account_phone_login_desc">开启你的专属游戏之旅</string>
    <string name="account_other_login_way">其他登录方式</string>
-   <string name="account_input_phone_hint">手机号码</string>
    <string name="account_phone_get_verify_code">获取验证码</string>
-   <string name="account_input_phone_country_code_empty">请选择手机区号</string>
-   <string name="account_input_phone_number_empty">请输入手机号码</string>
-   <string name="account_input_phone_number_invalid">手机号码不完整</string>
-   <string name="account_input_verify_code_title">输入验证码</string>
-   <string name="account_input_verify_code_desc">验证码已发送至\n%s</string>
-   <string name="account_resend_verify_code_duration">重新发送验证码 (%s)</string>
-   <string name="account_resend_verify_code_title">还没收到验证码? %s</string>
-   <string name="account_resend_verify_code_content">重新发送</string>
-   <string name="account_send_verify_code_duration_limit">每次发送验证码需要间隔60秒(剩余%s秒)</string>
 </resources>

+ 0 - 16
module/account/src/main/res/values/styles.xml

@@ -1,19 +1,3 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-
-    <style name="CodeInputBox">
-        <item name="android:layout_width">64dp</item>
-        <item name="android:layout_height">64dp</item>
-        <item name="fontFamily">@font/poppins_semibold</item>
-        <item name="android:background">@drawable/account_code_input_box_bg</item> <!-- 你的自定义背景图 -->
-        <item name="android:gravity">center</item>
-        <item name="android:inputType">number</item>
-        <item name="android:includeFontPadding">false</item>
-        <item name="android:maxLength">1</item>
-        <item name="android:textSize">32sp</item>
-        <item name="android:textColor">@color/color_FF1D2129</item>
-        <item name="android:cursorVisible">true</item>
-        <!--        <item name="android:textCursorDrawable">@drawable/edittext_cursor</item>-->
-    </style>
-
 </resources>

+ 3 - 0
module/im/src/main/java/com/adealink/weparty/im/session/comp/SessionBottomInputComp.kt

@@ -21,6 +21,8 @@ import com.adealink.frame.mvvm.viewmodel.activityViewModels
 import com.adealink.frame.util.onClick
 import com.adealink.weparty.commonui.ext.gone
 import com.adealink.weparty.commonui.ext.show
+import com.adealink.weparty.commonui.text.setMaxLength
+import com.adealink.weparty.constant.IM_MESSAGE_MAX_LENGTH
 import com.adealink.weparty.im.R
 import com.adealink.weparty.im.constant.TAG_IM_FLOW
 import com.adealink.weparty.im.databinding.LayoutSessionBottomInputBarBinding
@@ -131,6 +133,7 @@ class SessionBottomInputComp(
                 Log.e(TAG_IM_SESSION, " getCustomJsonMap error:${e.message}", e)
             }
         }
+        inputBar.etInputMessage.setMaxLength(IM_MESSAGE_MAX_LENGTH)
         inputBar.etInputMessage.setText(inputContent)
         inputBar.etInputMessage.setSelection(inputBar.etInputMessage.getText()?.length ?: 0)
 //        isTextInput = !inputBar.etInputMessage.text.isNullOrEmpty()

+ 0 - 1
module/im/src/main/res/layout/layout_session_bottom_emotion_bar.xml

@@ -41,7 +41,6 @@
             android:gravity="start|center_vertical"
             android:hint="@string/im_session_input_message"
             android:includeFontPadding="false"
-            android:maxLength="200"
             android:textColor="@color/color_FF1D2129"
             android:textColorHint="@color/color_FFC9CDD4"
             android:textSize="14sp"

+ 1 - 0
module/joinus/.gitignore

@@ -0,0 +1 @@
+/build

+ 56 - 0
module/joinus/build.gradle

@@ -0,0 +1,56 @@
+plugins {
+    id 'com.android.dynamic-feature'
+    id 'org.jetbrains.kotlin.android'
+    id 'org.jetbrains.kotlin.kapt'
+    id 'kotlin-parcelize'
+}
+
+android {
+    namespace 'com.adealink.weparty.joinus'
+    compileSdk libs.versions.compileSdk.get().toInteger()
+
+    defaultConfig {
+        minSdk libs.versions.minSdk.get().toInteger()
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles "consumer-rules.pro"
+        javaCompileOptions {
+            annotationProcessorOptions {
+                arguments += [
+                        "room.schemaLocation"  : "$projectDir/schemas".toString(),
+                        "room.incremental"     : "true",
+                        "room.expandProjection": "true"]
+            }
+        }
+    }
+
+
+
+    buildTypes {
+        release {
+            debuggable false
+        }
+    }
+    viewBinding {
+        enabled = true
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_17
+        targetCompatibility JavaVersion.VERSION_17
+    }
+
+    kotlinOptions {
+        jvmTarget = JavaVersion.VERSION_17.majorVersion
+    }
+}
+
+dependencies {
+    implementation project(":app")
+    //frame
+    kapt libs.frame.router.compiler
+
+    //test
+    testImplementation libs.junit
+    androidTestImplementation libs.androidx.junit
+    androidTestImplementation libs.androidx.espresso.core
+}

+ 0 - 0
module/joinus/consumer-rules.pro


+ 21 - 0
module/joinus/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 25 - 0
module/joinus/src/main/AndroidManifest.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:dist="http://schemas.android.com/apk/distribution"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="LockedOrientationActivity">
+
+    <dist:module
+        dist:instant="false"
+        dist:title="@string/module_joinus">
+        <dist:fusing dist:include="true" />
+        <dist:delivery>
+            <dist:install-time>
+                <dist:removable dist:value="true" />
+            </dist:install-time>
+        </dist:delivery>
+    </dist:module>
+
+    <application>
+        <activity
+            android:name="com.adealink.weparty.joinus.JoinUsActivity"
+            android:launchMode="singleTop"
+            android:screenOrientation="portrait"
+            android:theme="@style/AppTheme" />
+    </application>
+</manifest>

+ 156 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/JoinUsActivity.kt

@@ -0,0 +1,156 @@
+package com.adealink.weparty.joinus
+
+import androidx.activity.viewModels
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.updateLayoutParams
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.router.Router
+import com.adealink.frame.router.annotation.RouterUri
+import com.adealink.frame.util.statusBarHeight
+import com.adealink.weparty.commonui.BaseActivity
+import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.show
+import com.adealink.weparty.commonui.toast.util.showFailedToast
+import com.adealink.weparty.joinus.comp.JoinStepProgressComp
+import com.adealink.weparty.joinus.data.JoinUsStep
+import com.adealink.weparty.joinus.databinding.ActivityJoinUsBinding
+import com.adealink.weparty.joinus.review.JoinUsStepReviewFragment
+import com.adealink.weparty.joinus.step1.JoinUsStep1Fragment
+import com.adealink.weparty.joinus.step2.JoinUsStep2Fragment
+import com.adealink.weparty.joinus.step3.JoinUsStep3Fragment
+import com.adealink.weparty.joinus.viewmodel.JoinUsViewModel
+import com.adealink.weparty.joinus.viewmodel.JoinUsViewModelFactory
+import com.adealink.weparty.module.joinus.JoinUs
+import com.adealink.weparty.R as APP_R
+
+@RouterUri(path = [JoinUs.JoinUs.PATH], desc = "陪玩师入住")
+class JoinUsActivity : BaseActivity() {
+
+    private val binding by viewBinding(ActivityJoinUsBinding::inflate)
+
+    private val viewModel by viewModels<JoinUsViewModel> { JoinUsViewModelFactory() }
+    override fun onBeforeCreate() {
+        super.onBeforeCreate()
+        Router.bind(this)
+    }
+
+    override fun initViews() {
+        super.initViews()
+        setContentView(binding.root)
+        binding.topBar.updateLayoutParams<ConstraintLayout.LayoutParams> {
+            topMargin = statusBarHeight()
+        }
+    }
+
+    override fun initComponents() {
+        super.initComponents()
+        JoinStepProgressComp(this, binding.vProgress).attach()
+    }
+
+    override fun loadData() {
+        super.loadData()
+        viewModel.loadApplyProgress().observe(this) {
+            showFailedToast(it)
+        }
+    }
+
+    override fun observeViewModel() {
+        super.observeViewModel()
+        viewModel.joinProgressLD.observe(this) { progressData ->
+            when (progressData?.getCurrentStep()) {
+                JoinUsStep.UNDER_REVIEW -> {
+                    viewModel.goStep(JoinUsStep.UNDER_REVIEW)
+                }
+
+                JoinUsStep.STEP_1 -> {
+                    viewModel.goStep(JoinUsStep.STEP_1)
+                }
+
+                JoinUsStep.STEP_2 -> {
+                    viewModel.goStep(JoinUsStep.STEP_2)
+                }
+
+                JoinUsStep.STEP_3 -> {
+                    viewModel.goStep(JoinUsStep.STEP_3)
+                }
+
+                null -> {
+                    viewModel.goStep(JoinUsStep.STEP_1)
+                }
+            }
+        }
+        viewModel.stepLD.observe(this) { step ->
+            when (step) {
+                JoinUsStep.UNDER_REVIEW -> {
+                    binding.vProgress.root.gone()
+                    binding.ivTopBg.setImageResource(R.drawable.join_us_under_review_top_bg)
+                    binding.flContent.setBackgroundResource(0)
+                    inflateUnderReviewPage()
+                }
+
+                JoinUsStep.STEP_1 -> {
+                    binding.vProgress.root.show()
+                    binding.ivTopBg.setImageResource(R.drawable.join_us_top_bg)
+                    binding.flContent.setBackgroundResource(APP_R.drawable.common_bottom_dialog_bg)
+                    inflateStep1ReviewPage()
+                }
+
+                JoinUsStep.STEP_2 -> {
+                    binding.vProgress.root.show()
+                    binding.ivTopBg.setImageResource(R.drawable.join_us_top_bg)
+                    binding.flContent.setBackgroundResource(APP_R.drawable.common_bottom_dialog_bg)
+                    inflateStep2ReviewPage()
+                }
+
+                JoinUsStep.STEP_3 -> {
+                    binding.vProgress.root.show()
+                    binding.ivTopBg.setImageResource(R.drawable.join_us_top_bg)
+                    binding.flContent.setBackgroundResource(APP_R.drawable.common_bottom_dialog_bg)
+                    inflateStep3ReviewPage()
+                }
+            }
+        }
+    }
+
+    private fun inflateUnderReviewPage() {
+        val fragment = supportFragmentManager.findFragmentByTag(JoinUsStep.UNDER_REVIEW.step)
+        if (fragment != null && fragment.isAdded) {
+            return
+        }
+        supportFragmentManager.beginTransaction()
+            .replace(binding.flContent.id, JoinUsStepReviewFragment(), JoinUsStep.UNDER_REVIEW.step)
+            .commitAllowingStateLoss()
+    }
+
+    private fun inflateStep1ReviewPage() {
+        val fragment = supportFragmentManager.findFragmentByTag(JoinUsStep.STEP_1.step)
+        if (fragment != null && fragment.isAdded) {
+            return
+        }
+        supportFragmentManager.beginTransaction()
+            .replace(binding.flContent.id, JoinUsStep1Fragment(), JoinUsStep.UNDER_REVIEW.step)
+            .commitAllowingStateLoss()
+    }
+
+    private fun inflateStep2ReviewPage() {
+        val fragment = supportFragmentManager.findFragmentByTag(JoinUsStep.STEP_2.step)
+        if (fragment != null && fragment.isAdded) {
+            return
+        }
+        supportFragmentManager.beginTransaction()
+            .replace(binding.flContent.id, JoinUsStep2Fragment(), JoinUsStep.UNDER_REVIEW.step)
+            .commitAllowingStateLoss()
+    }
+
+    private fun inflateStep3ReviewPage() {
+        val fragment = supportFragmentManager.findFragmentByTag(JoinUsStep.STEP_3.step)
+        if (fragment != null && fragment.isAdded) {
+            return
+        }
+        supportFragmentManager.beginTransaction()
+            .replace(binding.flContent.id, JoinUsStep3Fragment(), JoinUsStep.UNDER_REVIEW.step)
+            .commitAllowingStateLoss()
+    }
+
+
+}

+ 24 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/JoinUsServiceImpl.kt

@@ -0,0 +1,24 @@
+package com.adealink.weparty.joinus
+
+import com.adealink.frame.spi.RegisterService
+import com.adealink.weparty.App
+import com.adealink.weparty.joinus.datasource.remote.JoinUsHttpService
+import com.adealink.weparty.module.joinus.IJoinUsService
+
+@RegisterService(IJoinUsService::class)
+class JoinUsServiceImpl : IJoinUsService {
+
+    private val activityHttpService by lazy {
+        App.instance.networkService.getHttpService(JoinUsHttpService::class.java)
+    }
+
+
+    override fun getService(): IJoinUsService {
+        return this
+    }
+
+    override fun onLogout() {
+
+    }
+
+}

+ 66 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/comp/JoinStepProgressComp.kt

@@ -0,0 +1,66 @@
+package com.adealink.weparty.joinus.comp
+
+import androidx.lifecycle.LifecycleOwner
+import com.adealink.frame.aab.util.getCompatColor
+import com.adealink.frame.mvvm.view.ViewComponent
+import com.adealink.frame.mvvm.viewmodel.viewModels
+import com.adealink.weparty.joinus.R
+import com.adealink.weparty.joinus.data.JoinUsStep
+import com.adealink.weparty.joinus.databinding.LayoutJoinUsProgressBinding
+import com.adealink.weparty.joinus.viewmodel.JoinUsViewModel
+import com.adealink.weparty.joinus.viewmodel.JoinUsViewModelFactory
+import com.adealink.weparty.R as APP_R
+
+class JoinStepProgressComp(
+    lifecycleOwner: LifecycleOwner,
+    val binding: LayoutJoinUsProgressBinding
+) : ViewComponent(lifecycleOwner) {
+
+    private val viewModel by viewModels<JoinUsViewModel> { JoinUsViewModelFactory() }
+
+    override fun onCreate() {
+        super.onCreate()
+        initView()
+        observeViewModel()
+    }
+
+    private fun initView() {
+
+    }
+
+    private fun observeViewModel() {
+        viewModel.stepLD.observe(viewLifecycleOwner) { step ->
+            updateStep(step)
+        }
+    }
+
+    private fun updateStep(step: JoinUsStep) {
+        when (step) {
+            JoinUsStep.STEP_1 -> {
+                binding.tvIndex1.setBackgroundResource(R.drawable.join_us_progress_index_selected_bg)
+                binding.vLine1To2.setBackgroundColor(getCompatColor(APP_R.color.color_FFC9CDD4))
+                binding.tvIndex2.setBackgroundResource(R.drawable.join_us_progress_index_bg)
+                binding.vLine2To3.setBackgroundColor(getCompatColor(APP_R.color.color_FFC9CDD4))
+                binding.tvIndex3.setBackgroundResource(R.drawable.join_us_progress_index_bg)
+            }
+
+            JoinUsStep.STEP_2 -> {
+                binding.tvIndex1.setBackgroundResource(R.drawable.join_us_progress_index_selected_bg)
+                binding.vLine1To2.setBackgroundColor(getCompatColor(APP_R.color.color_FF15E5E2))
+                binding.tvIndex2.setBackgroundResource(R.drawable.join_us_progress_index_selected_bg)
+                binding.vLine2To3.setBackgroundColor(getCompatColor(APP_R.color.color_FFC9CDD4))
+                binding.tvIndex3.setBackgroundResource(R.drawable.join_us_progress_index_bg)
+            }
+
+            JoinUsStep.STEP_3,
+            JoinUsStep.UNDER_REVIEW -> {
+                binding.tvIndex1.setBackgroundResource(R.drawable.join_us_progress_index_selected_bg)
+                binding.vLine1To2.setBackgroundColor(getCompatColor(APP_R.color.color_FF15E5E2))
+                binding.tvIndex2.setBackgroundResource(R.drawable.join_us_progress_index_selected_bg)
+                binding.vLine2To3.setBackgroundColor(getCompatColor(APP_R.color.color_FF15E5E2))
+                binding.tvIndex3.setBackgroundResource(R.drawable.join_us_progress_index_selected_bg)
+            }
+        }
+    }
+
+}

+ 146 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/data/JoinUsData.kt

@@ -0,0 +1,146 @@
+package com.adealink.weparty.joinus.data
+
+import com.google.gson.annotations.GsonNullable
+import com.google.gson.annotations.SerializedName
+
+enum class JoinUsStep(val step: String) {
+    UNDER_REVIEW("under_review"), //等待审核
+    STEP_1("step_1"), //步骤1
+    STEP_2("step_2"), //步骤2
+    STEP_3("step_3"), //步骤3
+}
+
+data class JoinUsData(
+    @SerializedName("underReview") val underReview: Boolean, //是否有申请正在审核中
+
+    @SerializedName("step1Complete") val step1Complete: Boolean,//步骤1是否完成
+    @SerializedName("step2Complete") val step2Complete: Boolean,//步骤2是否完成
+    @SerializedName("step3Complete") val step3Complete: Boolean,//步骤3是否完成
+) {
+    fun getCurrentStep(): JoinUsStep {
+        return when {
+            underReview -> {
+                JoinUsStep.UNDER_REVIEW
+            }
+
+            !step1Complete -> {
+                JoinUsStep.STEP_1
+            }
+
+            step1Complete && !step2Complete -> {
+                JoinUsStep.STEP_2
+            }
+
+            step1Complete && step2Complete && !step3Complete -> {
+                JoinUsStep.STEP_3
+            }
+
+            else -> {
+                JoinUsStep.STEP_1
+            }
+        }
+    }
+}
+
+
+data class JoinStep1(
+    @SerializedName("mobileNum") val mobile: String, //手机号
+    @SerializedName("mobileCode") val regionCode: String, //手机区号
+)
+
+
+data class SendPhoneVerifyReq(
+    @SerializedName("code") val code: String,
+    @SerializedName("num") val num: String,
+)
+
+
+data class BindPhoneReq(
+    @SerializedName("mobile") val mobile: MobileData,
+    @SerializedName("code") val verifyCode: String,
+)
+
+data class MobileData(
+    @SerializedName("code") val code: String,
+    @SerializedName("num") val num: String,
+)
+
+data class JoinStep2(
+    @GsonNullable
+    @SerializedName("avatar") val avatar: String?, //头像
+    @GsonNullable
+    @SerializedName("nickname") val nickname: String?, //昵称
+    @GsonNullable
+    @SerializedName("gender") val gender: Int?, //昵称
+    @GsonNullable
+    @SerializedName("birthday") val birthday: String?, //	生日:格式yyyy-MM-dd
+    @GsonNullable
+    @SerializedName("languageCodes") val languageCodes: List<String>?, //语言-从通用常量配置获取
+    @GsonNullable
+    @SerializedName("intro") val intro: String?, //简介
+)
+
+
+data class UpdateUserInfoReq(
+    @SerializedName("avatar") val avatar: String?, //头像
+    @SerializedName("nickname") val nickname: String?, //昵称
+    @SerializedName("gender") val gender: Int?, //昵称
+    @SerializedName("birthday") val birthday: String?, //	生日:格式yyyy-MM-dd
+    @SerializedName("languageCodes") val languageCodes: List<String>?, //语言-从通用常量配置获取
+    @SerializedName("intro") val intro: String?, //简介
+)
+
+data class JoinStep3(
+    @SerializedName("bizCategoryCode") val categoryCode: String, //品类
+    @SerializedName("fields") val fields: List<StepField>, //申请步骤
+)
+
+
+data class StepField(
+    @SerializedName("fieldCode") val fieldCode: String, //字段编码
+    @SerializedName("value") val fieldValue: String, //字段值
+    @SerializedName("fieldName") val fieldName: String, //字段名称
+    @SerializedName("fieldDesc") val fieldDesc: String, //字段描述
+    @SerializedName("valueClassType") val valueClassType: Int, //0:字符串1:整数,2:浮点数
+    @SerializedName("type") val fieldType: Int, //0:单行文本,1:多行文本,2:单选,3:多选,4:数字,5:图片,6:日期,7:语音,8:视频
+
+    @GsonNullable
+    @SerializedName("duration") val duration: Int?, //时长秒,type类型时语音/视频时有效
+    @GsonNullable
+    @SerializedName("constants") val constants: List<String>?, //当type=2/3时,可读取这个常量列表作为选择项
+    @GsonNullable
+    @SerializedName("validate") val validate: StepFieldValidate?, //校验器
+    @GsonNullable
+    @SerializedName("example") val example: StepFieldExample?, //示例
+)
+
+data class StepFieldValidate(
+    @GsonNullable
+    @SerializedName("required") val required: Boolean?, //是否必填
+    @GsonNullable
+    @SerializedName("size") val size: StepFieldValueSize?, //输入内容长度限制
+    @GsonNullable
+    @SerializedName("numLimit") val numLimit: StepFieldNumLimit?, //数字最大,最小
+    @GsonNullable
+    @SerializedName("regex") val desc: String?, //正则
+)
+
+data class StepFieldValueSize(
+    @SerializedName("min") val min: Int, //最小长度
+    @SerializedName("max") val max: Int, //最大长度
+)
+
+data class StepFieldNumLimit(
+    @SerializedName("min") val min: Int, //最小数字
+    @SerializedName("max") val max: Int, //最大数字
+)
+
+
+data class StepFieldExample(
+    @SerializedName("type") val type: Int, //0:图片,1:声音,2:视频,3:url
+    @SerializedName("value") val value: String, //数值
+    @GsonNullable
+    @SerializedName("title") val title: String?, //标题
+    @GsonNullable
+    @SerializedName("desc") val desc: String?, //描述
+)

+ 4 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/data/Tags.kt

@@ -0,0 +1,4 @@
+package com.adealink.weparty.joinus.data
+
+const val TAG_JOINUS = "tag_joinus"
+const val TAG_JOINUS_STEP1 = "${TAG_JOINUS}_step1"

+ 18 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/datasource/local/JoinUsLocalService.kt

@@ -0,0 +1,18 @@
+package com.adealink.weparty.joinus.datasource.local
+
+import android.content.Context
+import com.adealink.frame.storage.sp.TypeDelegationPrefs
+import com.adealink.frame.util.AppUtil
+import com.adealink.weparty.module.profile.ProfileModule
+import com.adealink.weparty.storage.PREF_ACTIVITY
+
+object JoinUsLocalService : TypeDelegationPrefs(
+    prefs = {
+        AppUtil.appContext.getSharedPreferences(PREF_ACTIVITY, Context.MODE_PRIVATE)
+    },
+    userId = {
+        ProfileModule.getMyUid()
+    }
+) {
+
+}

+ 38 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/datasource/remote/JoinUsHttpService.kt

@@ -0,0 +1,38 @@
+package com.adealink.weparty.joinus.datasource.remote
+
+import com.adealink.frame.base.Rlt
+import com.adealink.frame.network.data.Res
+import com.adealink.weparty.joinus.data.BindPhoneReq
+import com.adealink.weparty.joinus.data.JoinUsData
+import com.adealink.weparty.joinus.data.SendPhoneVerifyReq
+import com.adealink.weparty.joinus.data.UpdateUserInfoReq
+import retrofit2.http.Body
+import retrofit2.http.POST
+
+interface JoinUsHttpService {
+
+    /**
+     * 查询陪玩师申请1-3步骤现有信息
+     */
+    @POST("playmate/curInfo")
+    suspend fun loadJoinUsProgress(): Rlt<Res<JoinUsData>>
+
+
+    /**
+     * 获取验证码
+     */
+    @POST("user/bind/mobile/sendCode")
+    suspend fun sendPhoneVerifyCode(
+        @Body req: SendPhoneVerifyReq
+    ): Rlt<Res<Any>>
+
+    /**
+     * 绑定手机
+     */
+    @POST("user/bind/mobile")
+    suspend fun bindPhone(@Body req: BindPhoneReq): Rlt<Res<Any>>
+
+    @POST("playmate/improve/info")
+    suspend fun updateUserInfo(@Body req: UpdateUserInfoReq): Rlt<Res<Any>>
+
+}

+ 16 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/review/JoinUsStepReviewFragment.kt

@@ -0,0 +1,16 @@
+package com.adealink.weparty.joinus.review
+
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.weparty.commonui.BaseFragment
+import com.adealink.weparty.joinus.R
+import com.adealink.weparty.joinus.databinding.FragmentJoinUsStepUnderReviewBinding
+
+class JoinUsStepReviewFragment : BaseFragment(R.layout.fragment_join_us_step_under_review) {
+
+    private val binding by viewBinding(FragmentJoinUsStepUnderReviewBinding::bind)
+
+    override fun initViews() {
+        super.initViews()
+    }
+
+}

+ 150 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/step1/InputPhoneFragment.kt

@@ -0,0 +1,150 @@
+package com.adealink.weparty.joinus.step1
+
+import android.annotation.SuppressLint
+import android.text.Editable
+import android.text.TextWatcher
+import androidx.fragment.app.activityViewModels
+import com.adealink.frame.base.Rlt
+import com.adealink.frame.locale.country.CountryCode
+import com.adealink.frame.log.Log
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.util.onClick
+import com.adealink.weparty.commonui.BaseFragment
+import com.adealink.weparty.commonui.toast.util.showFailedToast
+import com.adealink.weparty.commonui.toast.util.showToast
+import com.adealink.weparty.country.CountrySelectDialog
+import com.adealink.weparty.country.data.CountryData
+import com.adealink.weparty.country.viewmodel.CountryViewModel
+import com.adealink.weparty.joinus.R
+import com.adealink.weparty.joinus.data.TAG_JOINUS_STEP1
+import com.adealink.weparty.joinus.databinding.FragmentJoinUsStep1InputPhoneBinding
+import com.adealink.weparty.joinus.step1.data.Step1Page
+import com.adealink.weparty.joinus.step1.viewmodel.Step1ViewModel
+import com.adealink.weparty.viewmodel.parentFragmentViewModels
+import com.adealink.weparty.R as APP_R
+
+class InputPhoneFragment : BaseFragment(R.layout.fragment_join_us_step_1_input_phone) {
+
+
+    private val binding by viewBinding(FragmentJoinUsStep1InputPhoneBinding::bind)
+
+    private val step1ViewModel by parentFragmentViewModels<Step1ViewModel, JoinUsStep1Fragment>(
+        JoinUsStep1Fragment::class.java
+    )
+
+    private val countryViewModel by activityViewModels<CountryViewModel>()
+
+    private var selectCountry: CountryData? = null
+    override fun initViews() {
+        super.initViews()
+        binding.ivCountry.onClick {
+            selectCountry()
+        }
+        binding.tvCountryCode.onClick {
+            selectCountry()
+        }
+
+        binding.etNumberInput.addTextChangedListener(object : TextWatcher {
+            override fun beforeTextChanged(
+                s: CharSequence?,
+                start: Int,
+                count: Int,
+                after: Int
+            ) {
+            }
+
+            override fun onTextChanged(
+                s: CharSequence?,
+                start: Int,
+                before: Int,
+                count: Int
+            ) {
+            }
+
+            @SuppressLint("SetTextI18n")
+            override fun afterTextChanged(s: Editable?) {
+                updateSendButton()
+            }
+        })
+        binding.btnVerify.isEnabled = false
+
+        binding.btnVerify.onClick {
+            sendVerifyCode()
+        }
+    }
+
+    override fun loadData() {
+        super.loadData()
+        countryViewModel.loadCountryList().observe(viewLifecycleOwner) { list ->
+            //选择默认国家
+            //ID -> EN
+            val id = list.find { it.code == CountryCode.ID.code }
+            if (id != null) {
+                onCountrySelect(id)
+            } else {
+                onCountrySelect(list.firstOrNull())
+            }
+        }
+    }
+
+
+    private fun selectCountry() {
+        CountrySelectDialog().apply {
+            setOnItemSelect {
+                onCountrySelect(it.data)
+            }
+        }.show(childFragmentManager, "CountrySelectDialog")
+    }
+
+    private fun onCountrySelect(country: CountryData?) {
+        this@InputPhoneFragment.selectCountry = country
+        binding.ivCountry.setImageUrl(country?.icon)
+        binding.tvCountryCode.text = country?.regionCode
+        updateSendButton()
+    }
+
+    private fun updateSendButton() {
+        binding.btnVerify.isEnabled = selectCountry != null
+                && !binding.etNumberInput.text.isNullOrEmpty()
+    }
+
+    private fun sendVerifyCode() {
+        val checkRlt = step1ViewModel.checkCanSendVerifyCode()
+        if (checkRlt is Rlt.Failed) {
+            showFailedToast(checkRlt)
+            return
+        }
+        if (selectCountry == null) {
+            showToast(APP_R.string.account_input_phone_country_code_empty)
+            return
+        }
+        val regionCode = selectCountry?.regionCode
+        if (regionCode.isNullOrEmpty()) {
+            Log.e(
+                TAG_JOINUS_STEP1,
+                "sendVerifyCode fail, for selectCountry${selectCountry} code is null"
+            )
+            return
+        }
+        val number = binding.etNumberInput.text?.toString()
+        if (number.isNullOrEmpty()) {
+            showToast(APP_R.string.account_input_phone_number_empty)
+            return
+        }
+        showLoading()
+        step1ViewModel.sendVerifyCode(regionCode, number)
+            .observe(viewLifecycleOwner) { sendVerifyRlt ->
+                dismissLoading()
+                when (sendVerifyRlt.rlt) {
+                    is Rlt.Failed -> {
+                        showFailedToast(sendVerifyRlt.rlt)
+                    }
+
+                    is Rlt.Success -> {
+                        step1ViewModel.showPage(Step1Page.INPUT_VERIFY)
+                    }
+                }
+            }
+    }
+
+}

+ 161 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/step1/InputVerifyFragment.kt

@@ -0,0 +1,161 @@
+package com.adealink.weparty.joinus.step1
+
+import android.os.Build
+import android.text.SpannableStringBuilder
+import android.text.method.LinkMovementMethod
+import android.text.style.TypefaceSpan
+import androidx.fragment.app.activityViewModels
+import com.adealink.frame.aab.util.getCompatColor
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.base.Rlt
+import com.adealink.frame.ext.findAndSetSpan
+import com.adealink.frame.ext.isViewBindingValid
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.util.removeUiCallbacks
+import com.adealink.frame.util.runOnUiThread
+import com.adealink.weparty.commonui.BaseFragment
+import com.adealink.weparty.commonui.DEFAULT_FONT_BOLD
+import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.show
+import com.adealink.weparty.commonui.toast.util.showFailedToast
+import com.adealink.weparty.joinus.R
+import com.adealink.weparty.joinus.data.JoinUsStep
+import com.adealink.weparty.joinus.databinding.FragmentJoinUsStep1InputVerifyBinding
+import com.adealink.weparty.joinus.step1.viewmodel.Step1ViewModel
+import com.adealink.weparty.joinus.viewmodel.JoinUsViewModel
+import com.adealink.weparty.joinus.viewmodel.JoinUsViewModelFactory
+import com.adealink.weparty.module.account.comp.VerifyCodeComp
+import com.adealink.weparty.viewmodel.parentFragmentViewModels
+import com.tencent.qcloud.tuikit.timcommon.util.TextUtil
+import kotlin.math.max
+import com.adealink.weparty.R as APP_R
+
+class InputVerifyFragment : BaseFragment(R.layout.fragment_join_us_step_1_input_verify) {
+
+
+    private val binding by viewBinding(FragmentJoinUsStep1InputVerifyBinding::bind)
+    private val step1ViewModel by parentFragmentViewModels<Step1ViewModel, JoinUsStep1Fragment>(
+        JoinUsStep1Fragment::class.java
+    )
+    private val joinUsViewModel by activityViewModels<JoinUsViewModel> { JoinUsViewModelFactory() }
+    override fun initViews() {
+        super.initViews()
+        val resendBtn = getCompatString(APP_R.string.account_resend_verify_code_content)
+        val resendText = getCompatString(APP_R.string.account_resend_verify_code_title, resendBtn)
+        binding.tvResend.text = SpannableStringBuilder(resendText).apply {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                findAndSetSpan(
+                    TypefaceSpan(DEFAULT_FONT_BOLD),
+                    resendBtn
+                )
+            }
+            findAndSetSpan(
+                TextUtil.ForegroundColorClickableSpan(
+                    getCompatColor(APP_R.color.color_FF3FBFBD)
+                ) {
+                    resendVerifyCode()
+                },
+                resendBtn
+            )
+        }
+        binding.tvResend.movementMethod = LinkMovementMethod.getInstance()
+    }
+
+    override fun initComponents() {
+        super.initComponents()
+        VerifyCodeComp(
+            this,
+            listOf(
+                binding.vVerifyCode.et1,
+                binding.vVerifyCode.et2,
+                binding.vVerifyCode.et3,
+                binding.vVerifyCode.et4
+            )
+        ) { code ->
+            checkVerifyCode(code)
+        }.attach()
+    }
+
+    override fun observeViewModel() {
+        super.observeViewModel()
+        step1ViewModel.verifyPhoneNumberLD.observe(viewLifecycleOwner) { number ->
+            binding.tvDesc.text =
+                getCompatString(APP_R.string.account_input_verify_code_desc, number)
+        }
+        step1ViewModel.sendVerifyEnableLD.observe(viewLifecycleOwner) {
+            updateResendText(it)
+        }
+    }
+
+    private var resendCountDownS = 0L
+
+    private val updateResendDurationRunnable = {
+        updateResendDuration()
+    }
+
+    private fun updateResendDuration() {
+        if (isViewBindingValid()) {
+            resendCountDownS = max(resendCountDownS - 1, 0)
+            binding.tvResendDuration.text = getCompatString(
+                APP_R.string.account_resend_verify_code_duration,
+                resendCountDownS.toString()
+            )
+            if (resendCountDownS > 0) {
+                runOnUiThread(updateResendDurationRunnable, 1000)
+            }
+        }
+    }
+
+    private fun updateResendText(enableSend: Boolean) {
+        if (enableSend) {
+            binding.tvResendDuration.gone()
+            binding.tvResend.show()
+        } else {
+            binding.tvResendDuration.show()
+            val countDown =
+                max(
+                    Step1ViewModel.lastSendVerifyCodeTs + Step1ViewModel.SEND_DURATION - System.currentTimeMillis(),
+                    0
+                ) / 1000
+            resendCountDownS = countDown
+            updateResendDuration()
+            binding.tvResend.gone()
+        }
+    }
+
+    private fun resendVerifyCode() {
+        showLoading()
+        step1ViewModel.resendVerifyCode().observe(viewLifecycleOwner) {
+            dismissLoading()
+            showFailedToast(it.rlt)
+        }
+    }
+
+    private fun checkVerifyCode(verifyCode: String?) {
+        val regionCode = step1ViewModel.verifyPhoneRegion
+        val phoneNumber = step1ViewModel.verifyPhoneNumber
+        if (regionCode.isNullOrEmpty() || phoneNumber.isNullOrEmpty() || verifyCode.isNullOrEmpty()) {
+            return
+        }
+        showLoading()
+        step1ViewModel.bindPhone(regionCode, phoneNumber, verifyCode)
+            .observe(viewLifecycleOwner) { rlt ->
+                dismissLoading()
+                when (rlt) {
+                    is Rlt.Failed -> {
+                        showFailedToast(rlt)
+                    }
+
+                    is Rlt.Success -> {
+                        joinUsViewModel.goStep(JoinUsStep.STEP_2)
+                    }
+                }
+            }
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        removeUiCallbacks(updateResendDurationRunnable)
+    }
+
+}

+ 57 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/step1/JoinUsStep1Fragment.kt

@@ -0,0 +1,57 @@
+package com.adealink.weparty.joinus.step1
+
+import androidx.fragment.app.viewModels
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.weparty.commonui.BaseFragment
+import com.adealink.weparty.joinus.R
+import com.adealink.weparty.joinus.databinding.FragmentJoinUsStep1Binding
+import com.adealink.weparty.joinus.step1.data.Step1Page
+import com.adealink.weparty.joinus.step1.viewmodel.Step1ViewModel
+
+class JoinUsStep1Fragment : BaseFragment(R.layout.fragment_join_us_step_1) {
+
+    private val binding by viewBinding(FragmentJoinUsStep1Binding::bind)
+
+    private val step1ViewModel by viewModels<Step1ViewModel>()
+
+    override fun loadData() {
+        super.loadData()
+        step1ViewModel.showPage(Step1Page.INPUT_PHONE)
+    }
+
+    override fun observeViewModel() {
+        super.observeViewModel()
+        step1ViewModel.pageLD.observe(viewLifecycleOwner) { page ->
+            when (page) {
+                Step1Page.INPUT_PHONE -> {
+                    inflateInputPhone()
+                }
+
+                Step1Page.INPUT_VERIFY -> {
+                    inflateInputVerify()
+                }
+            }
+        }
+    }
+
+    private fun inflateInputPhone() {
+        val fragment = childFragmentManager.findFragmentByTag(Step1Page.INPUT_PHONE.page)
+        if (fragment != null && fragment.isAdded) {
+            return
+        }
+        childFragmentManager.beginTransaction()
+            .replace(binding.flContent.id, InputPhoneFragment(), Step1Page.INPUT_PHONE.page)
+            .commitAllowingStateLoss()
+    }
+
+    private fun inflateInputVerify() {
+        val fragment = childFragmentManager.findFragmentByTag(Step1Page.INPUT_VERIFY.page)
+        if (fragment != null && fragment.isAdded) {
+            return
+        }
+        childFragmentManager.beginTransaction()
+            .replace(binding.flContent.id, InputVerifyFragment(), Step1Page.INPUT_VERIFY.page)
+            .commitAllowingStateLoss()
+    }
+
+}

+ 6 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/step1/data/Step1Data.kt

@@ -0,0 +1,6 @@
+package com.adealink.weparty.joinus.step1.data
+
+enum class Step1Page(val page:String){
+    INPUT_PHONE("input_phone"),
+    INPUT_VERIFY("input_verify"),
+}

+ 156 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/step1/viewmodel/Step1ViewModel.kt

@@ -0,0 +1,156 @@
+package com.adealink.weparty.joinus.step1.viewmodel
+
+import androidx.lifecycle.LiveData
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.base.IError
+import com.adealink.frame.base.Rlt
+import com.adealink.frame.log.Log
+import com.adealink.frame.mvvm.livedata.ExtMutableLiveData
+import com.adealink.frame.mvvm.livedata.OnceMutableLiveData
+import com.adealink.frame.mvvm.viewmodel.BaseViewModel
+import com.adealink.frame.network.data.Res
+import com.adealink.weparty.App
+import com.adealink.weparty.joinus.data.BindPhoneReq
+import com.adealink.weparty.joinus.data.MobileData
+import com.adealink.weparty.joinus.data.SendPhoneVerifyReq
+import com.adealink.weparty.joinus.data.TAG_JOINUS_STEP1
+import com.adealink.weparty.joinus.datasource.remote.JoinUsHttpService
+import com.adealink.weparty.joinus.step1.data.Step1Page
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import com.adealink.weparty.R as APP_R
+
+data class SendVerifyCode(
+    val rlt: Rlt<Any>,
+    val lastSendVerifyCodeTs: Long,
+)
+
+
+class Step1ViewModel : BaseViewModel() {
+
+    companion object {
+        const val SEND_DURATION = 60_000
+
+        @JvmStatic
+        var lastSendVerifyCodeTs = 0L
+    }
+
+    private val joinUsHttpService by lazy {
+        App.instance.networkService.getHttpService(
+            JoinUsHttpService::class.java
+        )
+    }
+
+    val pageLD = ExtMutableLiveData<Step1Page>()
+
+    fun showPage(page: Step1Page) {
+        pageLD.send(page)
+    }
+
+    fun checkCanSendVerifyCode(): Rlt<Any> {
+        val sendDuration = System.currentTimeMillis() - lastSendVerifyCodeTs
+        if (sendDuration < SEND_DURATION) {
+            return Rlt.Failed(
+                IError(
+                    getCompatString(
+                        APP_R.string.account_send_verify_code_duration_limit,
+                        (sendDuration / 1000).toString()
+                    )
+                )
+            )
+        }
+        return Rlt.Success(Unit)
+    }
+
+    var verifyPhoneRegion: String? = null
+    var verifyPhoneNumber: String? = null
+
+    val verifyPhoneNumberLD = ExtMutableLiveData<String>()
+    val sendVerifyEnableLD = ExtMutableLiveData<Boolean>()
+
+    fun sendVerifyCode(regionCode: String?, number: String?): LiveData<SendVerifyCode> {
+        val liveData = OnceMutableLiveData<SendVerifyCode>()
+        viewModelScope.launch {
+            if (regionCode.isNullOrEmpty() || number.isNullOrEmpty()) {
+                liveData.send(
+                    SendVerifyCode(
+                        Rlt.Failed(IError(getCompatString(APP_R.string.account_input_phone_number_invalid))),
+                        lastSendVerifyCodeTs
+                    )
+                )
+                return@launch
+            }
+            if (System.currentTimeMillis() - lastSendVerifyCodeTs < SEND_DURATION) {
+                //小于60s,不重新发送
+                liveData.send(
+                    SendVerifyCode(
+                        Rlt.Success(Unit),
+                        lastSendVerifyCodeTs
+                    )
+                )
+                return@launch
+            }
+
+            verifyPhoneRegion = regionCode
+            verifyPhoneNumber = number
+            verifyPhoneNumberLD.send("$regionCode $number")
+            val rlt = retrySendVerifyCode(regionCode, number)
+            liveData.send(
+                SendVerifyCode(
+                    rlt,
+                    lastSendVerifyCodeTs
+                )
+            )
+        }
+        return liveData
+    }
+
+    fun resendVerifyCode(): LiveData<SendVerifyCode> {
+        return sendVerifyCode(verifyPhoneRegion, verifyPhoneNumber)
+    }
+
+    private suspend fun retrySendVerifyCode(code: String, number: String): Rlt<Any> {
+        var tryTimes = 3 //尝试发送3次
+        var rlt: Rlt<Res<Any>>? = null
+        while (tryTimes > 0) {
+            rlt = joinUsHttpService.sendPhoneVerifyCode(SendPhoneVerifyReq(code, number))
+            when (rlt) {
+                is Rlt.Failed -> {
+                    Log.w(TAG_JOINUS_STEP1, "sendPhoneVerifyCode fail, $rlt")
+                    delay(100) //延时100毫秒后再发送
+                    tryTimes--
+                    continue
+                }
+
+                is Rlt.Success -> {
+                    lastSendVerifyCodeTs = System.currentTimeMillis()
+                    sendVerifyCodeCountDown()
+                    break
+                }
+            }
+        }
+        return rlt ?: Rlt.Failed(IError("Unknow error"))
+    }
+
+
+    private fun sendVerifyCodeCountDown() {
+        viewModelScope.launch {
+            sendVerifyEnableLD.send(false)
+            delay(60_000)
+            val enable = System.currentTimeMillis() - lastSendVerifyCodeTs > SEND_DURATION
+            Log.d(TAG_JOINUS_STEP1, "sendVerifyCodeCountDown, enable:${enable}")
+            sendVerifyEnableLD.send(enable)
+        }
+    }
+
+    fun bindPhone(region: String, number: String, verify: String): LiveData<Rlt<Any>> {
+        val liveData = OnceMutableLiveData<Rlt<Any>>()
+        viewModelScope.launch {
+            val result =
+                joinUsHttpService.bindPhone(BindPhoneReq(MobileData(region, number), verify))
+                    .apply { Log.logRltD(TAG_JOINUS_STEP1, "bindPhone", this) }
+            liveData.send(result)
+        }
+        return liveData
+    }
+}

+ 114 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/step2/JoinUsStep2Fragment.kt

@@ -0,0 +1,114 @@
+package com.adealink.weparty.joinus.step2
+
+import androidx.fragment.app.activityViewModels
+import androidx.fragment.app.viewModels
+import com.adealink.frame.base.Rlt
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.util.onClick
+import com.adealink.weparty.commonui.BaseFragment
+import com.adealink.weparty.commonui.toast.util.showFailedToast
+import com.adealink.weparty.commonui.toast.util.showToast
+import com.adealink.weparty.joinus.R
+import com.adealink.weparty.joinus.data.JoinUsStep
+import com.adealink.weparty.joinus.databinding.FragmentJoinUsStep2Binding
+import com.adealink.weparty.joinus.step2.comp.EditAvatarComp
+import com.adealink.weparty.joinus.step2.comp.EditBirthdayComp
+import com.adealink.weparty.joinus.step2.comp.EditGenderComp
+import com.adealink.weparty.joinus.step2.comp.EditIntroductionComp
+import com.adealink.weparty.joinus.step2.comp.EditLanguageComp
+import com.adealink.weparty.joinus.step2.comp.EditNicknameComp
+import com.adealink.weparty.joinus.step2.viewmodel.Step2ViewModel
+import com.adealink.weparty.joinus.viewmodel.JoinUsViewModel
+import com.adealink.weparty.joinus.viewmodel.JoinUsViewModelFactory
+import com.adealink.weparty.module.profile.data.EditUserInfo
+
+class JoinUsStep2Fragment : BaseFragment(R.layout.fragment_join_us_step_2) {
+
+    private val binding by viewBinding(FragmentJoinUsStep2Binding::bind)
+    private val step2ViewModel by viewModels<Step2ViewModel>()
+    private val joinUsViewModel by activityViewModels<JoinUsViewModel> { JoinUsViewModelFactory() }
+
+    private var nickNameComp: EditNicknameComp? = null
+    private var introComp: EditIntroductionComp? = null
+
+    override fun initViews() {
+        super.initViews()
+        binding.btnSubmit.isEnabled = false
+        binding.btnSubmit.onClick {
+            submit()
+        }
+    }
+
+    override fun initComponents() {
+        super.initComponents()
+        EditAvatarComp(this, binding.vEditAvatar).attach()
+        EditNicknameComp(this, binding.vEditNickname).also {
+            nickNameComp = it
+        }.attach()
+        EditGenderComp(this, binding.vEditGender).attach()
+        EditBirthdayComp(this, binding.vEditBirthday).attach()
+        EditLanguageComp(this, binding.vEditLanguage).attach()
+        EditIntroductionComp(this, binding.vEditIntroduction).also {
+            introComp = it
+        }.attach()
+    }
+
+    override fun observeViewModel() {
+        super.observeViewModel()
+        step2ViewModel.editUserInfoLD.observe(viewLifecycleOwner) {
+            val enable = checkSubmitEnable(it)
+            binding.btnSubmit.isEnabled = enable
+        }
+    }
+
+    private fun checkSubmitEnable(userInfo: EditUserInfo?): Boolean {
+        if (userInfo == null) {
+            return false
+        }
+        if (userInfo.avatar == null || userInfo.avatar?.isNull() == true) {
+            return false
+        }
+        val nickName = nickNameComp?.getEditNickName()
+        if (nickName.isNullOrEmpty()) {
+            return false
+        }
+        if (userInfo.birthday == null || (userInfo.birthday ?: 0) <= 0) {
+            return false
+        }
+        if (userInfo.languages.isNullOrEmpty()) {
+            return false
+        }
+        val intro = introComp?.getIntro()
+        if (intro.isNullOrEmpty()) {
+            return false
+        }
+        return true
+    }
+
+    private fun submit() {
+        val nickName = nickNameComp?.getEditNickName()
+        if (nickName.isNullOrEmpty()) {
+            showToast(R.string.join_us_step2_nickname_is_empty)
+            return
+        }
+        step2ViewModel.setNickName(nickName)
+
+        val intro = introComp?.getIntro()
+        step2ViewModel.setIntro(intro)
+
+        showLoading()
+        step2ViewModel.updateUserInfo().observe(viewLifecycleOwner) { rlt ->
+            dismissLoading()
+            when (rlt) {
+                is Rlt.Failed -> {
+                    showFailedToast(rlt)
+                }
+
+                is Rlt.Success -> {
+                    joinUsViewModel.goStep(JoinUsStep.STEP_3)
+                }
+            }
+        }
+    }
+
+}

+ 85 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/step2/comp/EditAvatarComp.kt

@@ -0,0 +1,85 @@
+package com.adealink.weparty.joinus.step2.comp
+
+import androidx.lifecycle.LifecycleOwner
+import com.adealink.frame.mvvm.view.ViewComponent
+import com.adealink.frame.mvvm.viewmodel.viewModels
+import com.adealink.frame.util.onClick
+import com.adealink.weparty.commonui.commonSelectedDrawable
+import com.adealink.weparty.commonui.ext.dpf
+import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.show
+import com.adealink.weparty.imageselect.clip.avatarClipParam
+import com.adealink.weparty.imageselect.comp.TakeFromAlbumComp
+import com.adealink.weparty.joinus.databinding.LayoutJoinUsStep2EditAvatarBinding
+import com.adealink.weparty.joinus.step2.dialog.ExampleAvatarDialog
+import com.adealink.weparty.joinus.step2.viewmodel.Step2ViewModel
+import com.adealink.weparty.joinus.viewmodel.JoinUsViewModelFactory
+import com.adealink.weparty.module.image.data.PhotoData
+
+class EditAvatarComp(
+    lifecycleOwner: LifecycleOwner,
+    private val binding: LayoutJoinUsStep2EditAvatarBinding
+) : ViewComponent(lifecycleOwner) {
+
+    companion object {
+        private const val TAG = "EditAvatarComp"
+    }
+
+    private val editViewModel by viewModels<Step2ViewModel> { JoinUsViewModelFactory() }
+    private lateinit var takeFromAlbumComp: TakeFromAlbumComp
+    override fun onCreate() {
+        super.onCreate()
+        initView()
+        initViewComponent()
+        observeViewModel()
+    }
+
+    private fun initView() {
+        binding.vAvatar.root.onClick {
+            modifyAvatar()
+        }
+        binding.btnExample.background = commonSelectedDrawable(200.dpf(), 1.dpf())
+        binding.btnExample.onClick {
+            ExampleAvatarDialog().show(fragmentManager, "ExampleAvatarDialog")
+        }
+    }
+
+    private fun initViewComponent() {
+        TakeFromAlbumComp(
+            lifecycleOwner,
+            clipParamData = avatarClipParam,
+            onPhotoSelect = { path, uri ->
+                editViewModel.setAvatar(null, path, uri)
+            },
+            source = "EditAvatarComp"
+        ).also {
+            takeFromAlbumComp = it
+        }.attach()
+    }
+
+    private fun observeViewModel() {
+        editViewModel.editUserInfoLD.observe(viewLifecycleOwner) {
+            updateAvatar(it.avatar)
+        }
+    }
+
+    private fun updateAvatar(photoData: PhotoData?) {
+        if (photoData == null || photoData.isNull()) {
+            binding.vAvatar.vModifyAvatar.show()
+            binding.vAvatar.vRemoveAvatar.gone()
+            return
+        }
+        binding.vAvatar.vModifyAvatar.gone()
+        binding.vAvatar.vRemoveAvatar.show()
+        if (photoData.isRemoteImage()) {
+            binding.vAvatar.ivAvatar.setImageUrl(photoData.url)
+        } else {
+            binding.vAvatar.ivAvatar.setImageURI(photoData.uri)
+        }
+    }
+
+    private fun modifyAvatar() {
+        takeFromAlbumComp.takeFromAlbum()
+    }
+
+}

+ 48 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/step2/comp/EditBirthdayComp.kt

@@ -0,0 +1,48 @@
+package com.adealink.weparty.joinus.step2.comp
+
+import androidx.lifecycle.LifecycleOwner
+import com.adealink.frame.mvvm.view.ViewComponent
+import com.adealink.frame.mvvm.viewmodel.viewModels
+import com.adealink.frame.util.onClick
+import com.adealink.weparty.joinus.databinding.LayoutJoinUsStep2EditBirthdayBinding
+import com.adealink.weparty.joinus.step2.viewmodel.Step2ViewModel
+import com.adealink.weparty.joinus.viewmodel.JoinUsViewModelFactory
+import com.adealink.weparty.util.formatTimeTo
+
+class EditBirthdayComp(
+    lifecycleOwner: LifecycleOwner,
+    private val binding: LayoutJoinUsStep2EditBirthdayBinding
+) : ViewComponent(lifecycleOwner) {
+
+    companion object {
+        private const val TAG = "EditBirthdayComp"
+    }
+
+    private val editViewModel by viewModels<Step2ViewModel> { JoinUsViewModelFactory() }
+    override fun onCreate() {
+        super.onCreate()
+        initView()
+        observeViewModel()
+    }
+
+    private fun initView() {
+        binding.clBirthday.onClick {
+
+        }
+    }
+
+    private fun observeViewModel() {
+        editViewModel.editUserInfoLD.observe(viewLifecycleOwner) {
+            updateUI(it?.birthday)
+        }
+    }
+
+    private fun updateUI(birthday: Long?) {
+        if (birthday == null || birthday <= 0) {
+            binding.tvBirthday.text = null
+            return
+        }
+        binding.tvBirthday.text = formatTimeTo(birthday, "dd/MM/yyyy")
+    }
+
+}

+ 63 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/step2/comp/EditGenderComp.kt

@@ -0,0 +1,63 @@
+package com.adealink.weparty.joinus.step2.comp
+
+import androidx.lifecycle.LifecycleOwner
+import com.adealink.frame.mvvm.view.ViewComponent
+import com.adealink.frame.mvvm.viewmodel.viewModels
+import com.adealink.frame.util.onClick
+import com.adealink.weparty.joinus.databinding.LayoutJoinUsStep2EditGenderBinding
+import com.adealink.weparty.joinus.step2.viewmodel.Step2ViewModel
+import com.adealink.weparty.joinus.viewmodel.JoinUsViewModelFactory
+import com.adealink.weparty.module.profile.data.Gender
+import com.adealink.weparty.R as APP_R
+
+class EditGenderComp(
+    lifecycleOwner: LifecycleOwner,
+    private val binding: LayoutJoinUsStep2EditGenderBinding
+) : ViewComponent(lifecycleOwner) {
+
+    companion object {
+        private const val TAG = "EditGenderComp"
+    }
+
+    private val editViewModel by viewModels<Step2ViewModel> { JoinUsViewModelFactory() }
+    override fun onCreate() {
+        super.onCreate()
+        initView()
+        observeViewModel()
+    }
+
+    private fun initView() {
+        binding.btnMale.onClick {
+            editViewModel.setGender(Gender.MALE)
+        }
+        binding.btnFemale.onClick {
+            editViewModel.setGender(Gender.FEMALE)
+        }
+    }
+
+    private fun observeViewModel() {
+        editViewModel.editUserInfoLD.observe(viewLifecycleOwner) {
+            updateUI(it?.gender)
+        }
+    }
+
+    private fun updateUI(gender: Gender?) {
+        when (gender) {
+            Gender.MALE -> {
+                binding.btnMale.setBackgroundResource(APP_R.drawable.profile_edit_gender_selected_bg)
+                binding.btnFemale.setBackgroundResource(APP_R.drawable.profile_edit_gender_bg)
+            }
+
+            Gender.FEMALE -> {
+                binding.btnMale.setBackgroundResource(APP_R.drawable.profile_edit_gender_bg)
+                binding.btnFemale.setBackgroundResource(APP_R.drawable.profile_edit_gender_selected_bg)
+            }
+
+            else -> {
+                binding.btnMale.setBackgroundResource(APP_R.drawable.profile_edit_gender_bg)
+                binding.btnFemale.setBackgroundResource(APP_R.drawable.profile_edit_gender_bg)
+            }
+        }
+    }
+
+}

+ 65 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/step2/comp/EditIntroductionComp.kt

@@ -0,0 +1,65 @@
+package com.adealink.weparty.joinus.step2.comp
+
+import android.annotation.SuppressLint
+import android.text.Editable
+import android.text.TextWatcher
+import androidx.lifecycle.LifecycleOwner
+import com.adealink.frame.mvvm.view.ViewComponent
+import com.adealink.frame.mvvm.viewmodel.viewModels
+import com.adealink.weparty.commonui.text.setMaxLength
+import com.adealink.weparty.constant.PROFILE_INTRODUCTION_MAX_LENGTH
+import com.adealink.weparty.joinus.databinding.LayoutJoinUsStep2EditIntroductionBinding
+import com.adealink.weparty.joinus.step2.viewmodel.Step2ViewModel
+import com.adealink.weparty.joinus.viewmodel.JoinUsViewModelFactory
+
+class EditIntroductionComp(
+    lifecycleOwner: LifecycleOwner,
+    private val binding: LayoutJoinUsStep2EditIntroductionBinding
+) : ViewComponent(lifecycleOwner) {
+
+    companion object {
+        private const val TAG = "EditIntroductionComp"
+    }
+
+    private val editViewModel by viewModels<Step2ViewModel> { JoinUsViewModelFactory() }
+    override fun onCreate() {
+        super.onCreate()
+        initView()
+    }
+
+    private fun initView() {
+        binding.etIntroInput.setMaxLength(PROFILE_INTRODUCTION_MAX_LENGTH)
+        binding.etIntroInput.addTextChangedListener(object : TextWatcher {
+            override fun beforeTextChanged(
+                s: CharSequence?,
+                start: Int,
+                count: Int,
+                after: Int
+            ) {
+            }
+
+            override fun onTextChanged(
+                s: CharSequence?,
+                start: Int,
+                before: Int,
+                count: Int
+            ) {
+            }
+
+            @SuppressLint("SetTextI18n")
+            override fun afterTextChanged(s: Editable?) {
+                val length = s?.length ?: 0
+                binding.tvInputLength.text = "${length}/${PROFILE_INTRODUCTION_MAX_LENGTH}"
+            }
+        })
+        binding.etIntroInput.setText(editViewModel.getEditUserInfo().intro)
+    }
+
+    fun getIntro(): String? {
+        if (!isValid) {
+            return null
+        }
+        return binding.etIntroInput.text?.toString()
+    }
+
+}

+ 47 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/step2/comp/EditLanguageComp.kt

@@ -0,0 +1,47 @@
+package com.adealink.weparty.joinus.step2.comp
+
+import androidx.lifecycle.LifecycleOwner
+import com.adealink.frame.mvvm.view.ViewComponent
+import com.adealink.frame.mvvm.viewmodel.viewModels
+import com.adealink.frame.util.onClick
+import com.adealink.weparty.joinus.databinding.LayoutJoinUsStep2EditLanguageBinding
+import com.adealink.weparty.joinus.step2.viewmodel.Step2ViewModel
+import com.adealink.weparty.joinus.viewmodel.JoinUsViewModelFactory
+
+class EditLanguageComp(
+    lifecycleOwner: LifecycleOwner,
+    private val binding: LayoutJoinUsStep2EditLanguageBinding
+) : ViewComponent(lifecycleOwner) {
+
+    companion object {
+        private const val TAG = "EditLanguageComp"
+    }
+
+    private val editViewModel by viewModels<Step2ViewModel> { JoinUsViewModelFactory() }
+    override fun onCreate() {
+        super.onCreate()
+        initView()
+        observeViewModel()
+    }
+
+    private fun initView() {
+        binding.clLanguage.onClick {
+
+        }
+    }
+
+    private fun observeViewModel() {
+        editViewModel.editUserInfoLD.observe(viewLifecycleOwner) {
+            updateUI(it.languages)
+        }
+    }
+
+    private fun updateUI(languages: List<String>?) {
+        if (languages.isNullOrEmpty()) {
+            binding.tvLanguage.text = null
+            return
+        }
+        binding.tvLanguage.text = languages.joinToString(separator = ",")
+    }
+
+}

+ 74 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/step2/comp/EditNicknameComp.kt

@@ -0,0 +1,74 @@
+package com.adealink.weparty.joinus.step2.comp
+
+import android.annotation.SuppressLint
+import android.text.Editable
+import android.text.TextWatcher
+import androidx.lifecycle.LifecycleOwner
+import com.adealink.frame.mvvm.view.ViewComponent
+import com.adealink.frame.mvvm.viewmodel.viewModels
+import com.adealink.weparty.commonui.text.setMaxLength
+import com.adealink.weparty.constant.PROFILE_NICKNAME_MAX_LENGTH
+import com.adealink.weparty.joinus.databinding.LayoutJoinUsStep2EditNicknameBinding
+import com.adealink.weparty.joinus.step2.viewmodel.Step2ViewModel
+import com.adealink.weparty.joinus.viewmodel.JoinUsViewModelFactory
+import com.adealink.weparty.module.profile.data.EditUserInfo
+
+class EditNicknameComp(
+    lifecycleOwner: LifecycleOwner,
+    private val binding: LayoutJoinUsStep2EditNicknameBinding
+) : ViewComponent(lifecycleOwner) {
+
+    companion object {
+        private const val TAG = "EditNicknameComp"
+    }
+
+    private val editViewModel by viewModels<Step2ViewModel> { JoinUsViewModelFactory() }
+    override fun onCreate() {
+        super.onCreate()
+        initView()
+    }
+
+    private fun initView() {
+        binding.etNameInput.setMaxLength(PROFILE_NICKNAME_MAX_LENGTH)
+        binding.etNameInput.addTextChangedListener(object : TextWatcher {
+            override fun beforeTextChanged(
+                s: CharSequence?,
+                start: Int,
+                count: Int,
+                after: Int
+            ) {
+            }
+
+            override fun onTextChanged(
+                s: CharSequence?,
+                start: Int,
+                before: Int,
+                count: Int
+            ) {
+            }
+
+            @SuppressLint("SetTextI18n")
+            override fun afterTextChanged(s: Editable?) {
+                val length = s?.length ?: 0
+                binding.tvInputLength.text = "${length}/${PROFILE_NICKNAME_MAX_LENGTH}"
+            }
+        })
+        val nickName = editViewModel.getEditUserInfo().nickname
+        binding.etNameInput.setText(nickName)
+    }
+
+    @SuppressLint("SetTextI18n")
+    private fun updateName(userInfo: EditUserInfo?) {
+        val name = userInfo?.nickname
+        binding.etNameInput.setText(name)
+        binding.tvInputLength.text = "${name?.length ?: 0}/${PROFILE_NICKNAME_MAX_LENGTH}"
+    }
+
+    fun getEditNickName(): String? {
+        if (!isValid) {
+            return null
+        }
+        return binding.etNameInput.text?.toString()
+    }
+
+}

+ 44 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/step2/dialog/ExampleAvatarDialog.kt

@@ -0,0 +1,44 @@
+package com.adealink.weparty.joinus.step2.dialog
+
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.util.onClick
+import com.adealink.weparty.commonui.widget.BottomDialogFragment
+import com.adealink.weparty.joinus.R
+import com.adealink.weparty.joinus.databinding.DialogExampleAvatarBinding
+
+class ExampleAvatarDialog : BottomDialogFragment(R.layout.dialog_example_avatar) {
+
+    private val binding by viewBinding(DialogExampleAvatarBinding::bind)
+    override fun initViews() {
+        super.initViews()
+        binding.btnClose.onClick {
+            dismiss()
+        }
+
+        binding.vWrong1.ivWrong.setImageResource(R.drawable.join_us_avatar_wrong_1)
+        binding.vWrong1.tvWrong.text = getCompatString(R.string.join_us_example_avatar_wrong_tip1)
+
+        binding.vWrong2.ivWrong.setImageResource(R.drawable.join_us_avatar_wrong_2)
+        binding.vWrong2.tvWrong.text = getCompatString(R.string.join_us_example_avatar_wrong_tip2)
+
+        binding.vWrong3.ivWrong.setImageResource(R.drawable.join_us_avatar_wrong_3)
+        binding.vWrong3.tvWrong.text = getCompatString(R.string.join_us_example_avatar_wrong_tip3)
+
+        binding.vWrong4.ivWrong.setImageResource(R.drawable.join_us_avatar_wrong_4)
+        binding.vWrong4.tvWrong.text = getCompatString(R.string.join_us_example_avatar_wrong_tip4)
+
+        binding.vWrong5.ivWrong.setImageResource(R.drawable.join_us_avatar_wrong_5)
+        binding.vWrong5.tvWrong.text = getCompatString(R.string.join_us_example_avatar_wrong_tip5)
+
+        binding.vWrong6.ivWrong.setImageResource(R.drawable.join_us_avatar_wrong_6)
+        binding.vWrong6.tvWrong.text = getCompatString(R.string.join_us_example_avatar_wrong_tip6)
+
+        binding.vWrong7.ivWrong.setImageResource(R.drawable.join_us_avatar_wrong_7)
+        binding.vWrong7.tvWrong.text = getCompatString(R.string.join_us_example_avatar_wrong_tip7)
+
+        binding.vWrong8.ivWrong.setImageResource(R.drawable.join_us_avatar_wrong_8)
+        binding.vWrong8.tvWrong.text = getCompatString(R.string.join_us_example_avatar_wrong_tip8)
+    }
+
+}

+ 128 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/step2/viewmodel/Step2ViewModel.kt

@@ -0,0 +1,128 @@
+package com.adealink.weparty.joinus.step2.viewmodel
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.adealink.frame.base.Rlt
+import com.adealink.frame.mvvm.livedata.OnceMutableLiveData
+import com.adealink.frame.mvvm.viewmodel.BaseViewModel
+import com.adealink.frame.oss.data.UploadFile
+import com.adealink.frame.util.formatTime
+import com.adealink.weparty.App
+import com.adealink.weparty.image.AVATAR_IMAGE_MAX_HEIGHT
+import com.adealink.weparty.image.AVATAR_IMAGE_MAX_SIZE_KB
+import com.adealink.weparty.image.AVATAR_IMAGE_MAX_WIDTH
+import com.adealink.weparty.image.AVATAR_IMAGE_MIN_QUALITY
+import com.adealink.weparty.joinus.data.UpdateUserInfoReq
+import com.adealink.weparty.joinus.datasource.remote.JoinUsHttpService
+import com.adealink.weparty.module.image.data.PhotoData
+import com.adealink.weparty.module.profile.ProfileModule
+import com.adealink.weparty.module.profile.data.EditUserInfo
+import com.adealink.weparty.module.profile.data.Gender
+import com.adealink.weparty.module.profile.data.UserInfo
+import com.adealink.weparty.util.uploadPhotos
+import kotlinx.coroutines.launch
+
+class Step2ViewModel : BaseViewModel() {
+
+    private val joinUsHttpService by lazy {
+        App.instance.networkService.getHttpService(
+            JoinUsHttpService::class.java
+        )
+    }
+    private val editUserInfo = EditUserInfo()
+    val editUserInfoLD = MutableLiveData<EditUserInfo>()
+
+    fun getEditUserInfo(): EditUserInfo {
+        return editUserInfo
+    }
+
+    fun setUserInfo(userInfo: UserInfo?) {
+        editUserInfo.resetBy(userInfo)
+        editUserInfoLD.send(editUserInfo)
+    }
+
+    fun setAvatar(
+        url: String?,
+        path: String?,
+        uri: String?
+    ) {
+        editUserInfo.avatar = PhotoData(url, path, uri)
+        editUserInfoLD.send(editUserInfo)
+    }
+
+    fun setNickName(name: String?) {
+        editUserInfo.nickname = name
+        editUserInfoLD.send(editUserInfo)
+    }
+
+    fun setBirthday(birthday: Long) {
+        editUserInfo.birthday = birthday
+        editUserInfoLD.send(editUserInfo)
+    }
+
+    fun setGender(gender: Gender) {
+        editUserInfo.gender = gender
+        editUserInfoLD.send(editUserInfo)
+    }
+
+    fun setIntro(intro: String?) {
+        editUserInfo.intro = intro
+        editUserInfoLD.send(editUserInfo)
+    }
+
+
+    fun updateUserInfo(): LiveData<Rlt<Any>> {
+        val liveData = OnceMutableLiveData<Rlt<Any>>()
+        viewModelScope.launch {
+            //检查头像
+            editUserInfo.avatar?.let { avatar ->
+                if (avatar.isLocal() && !avatar.isNull()) {
+                    val rlt = uploadPhotos(
+                        listOf(avatar),
+                        AVATAR_IMAGE_MAX_WIDTH,
+                        AVATAR_IMAGE_MAX_HEIGHT,
+                        AVATAR_IMAGE_MAX_SIZE_KB,
+                        AVATAR_IMAGE_MIN_QUALITY,
+                        UploadFile.FileType.AVATAR
+                    )
+                    when (rlt) {
+                        is Rlt.Failed -> {
+                            liveData.send(rlt)
+                            return@launch
+                        }
+
+                        is Rlt.Success -> {
+                            editUserInfo.avatar?.let {
+                                it.url = rlt.data.firstOrNull()?.url
+                            }
+                        }
+                    }
+                }
+            }
+            val rlt = joinUsHttpService.updateUserInfo(
+                UpdateUserInfoReq(
+                    avatar = editUserInfo.avatar?.url,
+                    nickname = editUserInfo.nickname,
+                    gender = editUserInfo.gender?.gender,
+                    birthday = editUserInfo.birthday?.let { ts ->
+                        formatTime(ts, "yyyy-MM-dd")
+                    },
+                    languageCodes = editUserInfo.languages,
+                    intro = editUserInfo.intro,
+                )
+            )
+            when (rlt) {
+                is Rlt.Failed -> {
+
+                }
+
+                is Rlt.Success -> {
+                    ProfileModule.getUserInfoByUid(ProfileModule.getMyUid(), false)
+                }
+            }
+            liveData.send(rlt)
+        }
+        return liveData
+    }
+
+}

+ 16 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/step3/JoinUsStep3Fragment.kt

@@ -0,0 +1,16 @@
+package com.adealink.weparty.joinus.step3
+
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.weparty.commonui.BaseFragment
+import com.adealink.weparty.joinus.R
+import com.adealink.weparty.joinus.databinding.FragmentJoinUsStep3Binding
+
+class JoinUsStep3Fragment: BaseFragment(R.layout.fragment_join_us_step_3) {
+
+    private val binding by viewBinding(FragmentJoinUsStep3Binding::bind)
+
+    override fun initViews() {
+        super.initViews()
+    }
+
+}

+ 47 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/viewmodel/JoinUsViewModel.kt

@@ -0,0 +1,47 @@
+package com.adealink.weparty.joinus.viewmodel
+
+import androidx.lifecycle.LiveData
+import com.adealink.frame.base.Rlt
+import com.adealink.frame.mvvm.livedata.ExtMutableLiveData
+import com.adealink.frame.mvvm.livedata.OnceMutableLiveData
+import com.adealink.frame.mvvm.viewmodel.BaseViewModel
+import com.adealink.weparty.App
+import com.adealink.weparty.joinus.data.JoinUsData
+import com.adealink.weparty.joinus.data.JoinUsStep
+import com.adealink.weparty.joinus.datasource.remote.JoinUsHttpService
+import com.adealink.weparty.module.joinus.viewmodel.IJoinUsViewModel
+import kotlinx.coroutines.launch
+
+class JoinUsViewModel : BaseViewModel(), IJoinUsViewModel {
+
+    private val joinUsHttpService by lazy {
+        App.instance.networkService.getHttpService(JoinUsHttpService::class.java)
+    }
+
+    val stepLD = ExtMutableLiveData<JoinUsStep>()
+
+    fun goStep(step: JoinUsStep) {
+        stepLD.send(step)
+    }
+
+    val joinProgressLD = ExtMutableLiveData<JoinUsData?>()
+    fun loadApplyProgress(): LiveData<Rlt<Any>> {
+        val liveData = OnceMutableLiveData<Rlt<Any>>()
+        viewModelScope.launch {
+            val rlt = joinUsHttpService.loadJoinUsProgress()
+            when (rlt) {
+                is Rlt.Failed -> {
+                    //Ntd.
+                }
+
+                is Rlt.Success -> {
+                    val progressData = rlt.data.data
+                    joinProgressLD.send(progressData)
+                }
+            }
+            liveData.send(rlt)
+        }
+        return liveData
+    }
+
+}

+ 29 - 0
module/joinus/src/main/java/com/adealink/weparty/joinus/viewmodel/JoinUsViewModelFactory.kt

@@ -0,0 +1,29 @@
+package com.adealink.weparty.joinus.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.adealink.weparty.joinus.step1.viewmodel.Step1ViewModel
+import com.adealink.weparty.joinus.step2.viewmodel.Step2ViewModel
+
+@Suppress("UNCHECKED_CAST")
+class JoinUsViewModelFactory : ViewModelProvider.NewInstanceFactory() {
+
+    override fun <T : ViewModel> create(modelClass: Class<T>): T {
+        return with(modelClass) {
+            when {
+                isAssignableFrom(JoinUsViewModel::class.java) ->
+                    JoinUsViewModel()
+
+                isAssignableFrom(Step1ViewModel::class.java) ->
+                    Step1ViewModel()
+
+                isAssignableFrom(Step2ViewModel::class.java) ->
+                    Step2ViewModel()
+
+                else ->
+                    throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
+            } as T
+        }
+    }
+
+}

BIN
module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_pass_ic.png


BIN
module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_pass_tag_ic.png


BIN
module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_wrong_1.png


BIN
module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_wrong_2.png


BIN
module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_wrong_3.png


BIN
module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_wrong_4.png


BIN
module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_wrong_5.png


BIN
module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_wrong_6.png


BIN
module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_wrong_7.png


BIN
module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_wrong_8.png


BIN
module/joinus/src/main/res/drawable-xhdpi/join_us_avatar_wrong_tag_ic.png


BIN
module/joinus/src/main/res/drawable-xhdpi/join_us_top_bg.png


BIN
module/joinus/src/main/res/drawable-xhdpi/join_us_under_review_ic.png


BIN
module/joinus/src/main/res/drawable-xhdpi/join_us_under_review_top_bg.png


+ 7 - 0
module/joinus/src/main/res/drawable/join_us_avatar_tip_tag_ic.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+
+    <solid android:color="@color/color_FF15E5E2" />
+
+</shape>

+ 8 - 0
module/joinus/src/main/res/drawable/join_us_code_input_box_bg.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <corners android:radius="12dp" />
+    <solid android:color="@color/color_FFF2F3F5" />
+
+</shape>

+ 9 - 0
module/joinus/src/main/res/drawable/join_us_edit_gender_bg.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <solid android:color="@color/color_FFF1F2F5" />
+
+    <corners android:radius="30dp" />
+
+</shape>

+ 6 - 0
module/joinus/src/main/res/drawable/join_us_progress_bg.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/color_80FFFFFF" />
+    <corners android:radius="100dp" />
+</shape>

+ 5 - 0
module/joinus/src/main/res/drawable/join_us_progress_index_bg.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <solid android:color="@color/color_FFC9CDD4" />
+</shape>

+ 5 - 0
module/joinus/src/main/res/drawable/join_us_progress_index_selected_bg.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <solid android:color="@color/color_FF15E5E2" />
+</shape>

+ 48 - 0
module/joinus/src/main/res/layout/activity_join_us.xml

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/white">
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/iv_top_bg"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintDimensionRatio="375:235"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:srcCompat="@drawable/join_us_top_bg" />
+
+    <com.adealink.weparty.commonui.widget.CommonTopBar
+        android:id="@+id/top_bar"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/common_top_bar_height"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:top_bar_title="@string/join_us_title" />
+
+    <include
+        android:id="@+id/v_progress"
+        layout="@layout/layout_join_us_progress"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/top_bar" />
+
+    <androidx.fragment.app.FragmentContainerView
+        android:id="@+id/fl_content"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginTop="8dp"
+        android:background="@drawable/common_bottom_dialog_bg"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/v_progress" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 270 - 0
module/joinus/src/main/res/layout/dialog_example_avatar.xml

@@ -0,0 +1,270 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/common_bottom_dialog_bg"
+    android:clipChildren="false"
+    android:paddingBottom="24dp">
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_title"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/common_top_bar_height"
+        android:fontFamily="@font/poppins_semibold"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:text="@string/join_us_example_avatar"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="16sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/btn_close"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_marginEnd="12dp"
+        app:layout_constraintBottom_toBottomOf="@id/tv_title"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:srcCompat="@drawable/common_close_ic" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_pass"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:clipChildren="false"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_title">
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:id="@+id/iv_avatar"
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_marginStart="16dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/cl_pass_tips"
+            app:layout_constraintHorizontal_chainStyle="packed"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/join_us_avatar_pass_ic" />
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:layout_width="25dp"
+            android:layout_height="25dp"
+            android:layout_marginEnd="-1.3dp"
+            android:layout_marginBottom="-1.3dp"
+            app:layout_constraintBottom_toBottomOf="@id/iv_avatar"
+            app:layout_constraintEnd_toEndOf="@id/iv_avatar"
+            app:srcCompat="@drawable/join_us_avatar_pass_tag_ic" />
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/cl_pass_tips"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="12dp"
+            android:layout_marginEnd="16dp"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintBottom_toBottomOf="@id/iv_avatar"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0"
+            app:layout_constraintStart_toEndOf="@id/iv_avatar"
+            app:layout_constraintTop_toTopOf="@id/iv_avatar">
+
+
+            <androidx.appcompat.widget.AppCompatImageView
+                android:id="@+id/iv_tag_1"
+                android:layout_width="16dp"
+                android:layout_height="16dp"
+                android:padding="5dp"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="@id/tv_tips_1"
+                app:srcCompat="@drawable/join_us_avatar_tip_tag_ic" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@+id/tv_tips_1"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:includeFontPadding="false"
+                android:lineSpacingExtra="0dp"
+                android:lineSpacingMultiplier="1"
+                android:text="@string/join_us_example_avatar_pass_tip1"
+                android:textColor="@color/color_FF1D2129"
+                android:textSize="14sp"
+                app:layout_constrainedWidth="true"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintHorizontal_bias="0"
+                app:layout_constraintStart_toEndOf="@id/iv_tag_1"
+                app:layout_constraintTop_toTopOf="parent"
+                app:lineHeight="16dp" />
+
+            <androidx.appcompat.widget.AppCompatImageView
+                android:id="@+id/iv_tag_2"
+                android:layout_width="16dp"
+                android:layout_height="16dp"
+                android:padding="5dp"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="@id/tv_tips_2"
+                app:srcCompat="@drawable/join_us_avatar_tip_tag_ic" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@+id/tv_tips_2"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="8dp"
+                android:includeFontPadding="false"
+                android:lineSpacingMultiplier="1"
+                android:text="@string/join_us_example_avatar_pass_tip2"
+                android:textColor="@color/color_FF1D2129"
+                android:textSize="14sp"
+                app:layout_constrainedWidth="true"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintHorizontal_bias="0"
+                app:layout_constraintStart_toEndOf="@id/iv_tag_1"
+                app:layout_constraintTop_toBottomOf="@id/tv_tips_1"
+                app:lineHeight="16dp" />
+
+            <androidx.appcompat.widget.AppCompatImageView
+                android:id="@+id/iv_tag_3"
+                android:layout_width="16dp"
+                android:layout_height="16dp"
+                android:padding="5dp"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="@id/tv_tips_3"
+                app:srcCompat="@drawable/join_us_avatar_tip_tag_ic" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@+id/tv_tips_3"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="8dp"
+                android:includeFontPadding="false"
+                android:lineSpacingMultiplier="1"
+                android:text="@string/join_us_example_avatar_pass_tip3"
+                android:textColor="@color/color_FF1D2129"
+                android:textSize="14sp"
+                app:layout_constrainedWidth="true"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintHorizontal_bias="0"
+                app:layout_constraintStart_toEndOf="@id/iv_tag_1"
+                app:layout_constraintTop_toBottomOf="@id/tv_tips_2"
+                app:lineHeight="16dp" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <!--错误示例-->
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_wrong_1"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="20dp"
+        app:layout_constraintTop_toBottomOf="@id/cl_pass">
+
+        <include
+            android:id="@+id/v_wrong_1"
+            layout="@layout/layout_example_wrong_avatar"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:maxWidth="76dp"
+            app:layout_constraintEnd_toStartOf="@id/v_wrong_2"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <include
+            android:id="@+id/v_wrong_2"
+            layout="@layout/layout_example_wrong_avatar"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="13dp"
+            android:maxWidth="76dp"
+            app:layout_constraintEnd_toStartOf="@id/v_wrong_3"
+            app:layout_constraintStart_toEndOf="@id/v_wrong_1"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <include
+            android:id="@+id/v_wrong_3"
+            layout="@layout/layout_example_wrong_avatar"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="13dp"
+            android:maxWidth="76dp"
+            app:layout_constraintEnd_toStartOf="@id/v_wrong_4"
+            app:layout_constraintStart_toEndOf="@id/v_wrong_2"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <include
+            android:id="@+id/v_wrong_4"
+            layout="@layout/layout_example_wrong_avatar"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="13dp"
+            android:layout_marginEnd="16dp"
+            android:maxWidth="76dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@id/v_wrong_3"
+            app:layout_constraintTop_toTopOf="parent" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_wrong_2"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="12dp"
+        app:layout_constraintTop_toBottomOf="@id/cl_wrong_1">
+
+        <include
+            android:id="@+id/v_wrong_5"
+            layout="@layout/layout_example_wrong_avatar"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:maxWidth="76dp"
+            app:layout_constraintEnd_toStartOf="@id/v_wrong_6"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <include
+            android:id="@+id/v_wrong_6"
+            layout="@layout/layout_example_wrong_avatar"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="13dp"
+            android:maxWidth="76dp"
+            app:layout_constraintEnd_toStartOf="@id/v_wrong_7"
+            app:layout_constraintStart_toEndOf="@id/v_wrong_5"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <include
+            android:id="@+id/v_wrong_7"
+            layout="@layout/layout_example_wrong_avatar"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="13dp"
+            android:maxWidth="76dp"
+            app:layout_constraintEnd_toStartOf="@id/v_wrong_8"
+            app:layout_constraintStart_toEndOf="@id/v_wrong_6"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <include
+            android:id="@+id/v_wrong_8"
+            layout="@layout/layout_example_wrong_avatar"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="13dp"
+            android:layout_marginEnd="16dp"
+            android:maxWidth="76dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@id/v_wrong_7"
+            app:layout_constraintTop_toTopOf="parent" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 5 - 0
module/joinus/src/main/res/layout/fragment_join_us_step_1.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/fl_content"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" />

+ 119 - 0
module/joinus/src/main/res/layout/fragment_join_us_step_1_input_phone.xml

@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="16dp"
+        android:layout_marginTop="26dp"
+        android:fontFamily="@font/poppins_semibold"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:text="@string/join_us_step1_title"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="18sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_content"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginTop="12dp"
+        android:background="@drawable/common_bottom_dialog_bg"
+        android:paddingHorizontal="22dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_title">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/cl_input"
+            android:layout_width="match_parent"
+            android:layout_height="52dp"
+            android:layout_marginTop="30dp"
+            android:background="@drawable/common_phone_input_bg"
+            android:paddingHorizontal="16dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent">
+
+            <com.adealink.frame.image.view.NetworkImageView
+                android:id="@+id/iv_country"
+                android:layout_width="24dp"
+                android:layout_height="24dp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:roundAsCircle="true" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@+id/tv_country_code"
+                android:layout_width="54dp"
+                android:layout_height="wrap_content"
+                android:fontFamily="@font/poppins_semibold"
+                android:gravity="center"
+                android:includeFontPadding="false"
+                android:textColor="@color/color_FF1D2129"
+                android:textSize="18sp"
+                app:autoSizeMaxTextSize="18sp"
+                app:autoSizeMinTextSize="14sp"
+                app:autoSizeTextType="uniform"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toEndOf="@id/iv_country"
+                app:layout_constraintTop_toTopOf="parent"
+                tools:text="+62" />
+
+            <androidx.appcompat.widget.AppCompatImageView
+                android:layout_width="11dp"
+                android:layout_height="10dp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toEndOf="@id/tv_country_code"
+                app:layout_constraintTop_toTopOf="parent"
+                app:srcCompat="@drawable/common_phone_country_code_select_ic" />
+
+            <androidx.appcompat.widget.AppCompatEditText
+                android:id="@+id/et_number_input"
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:layout_marginStart="24dp"
+                android:background="@null"
+                android:fontFamily="@font/poppins_semibold"
+                android:gravity="start|center_vertical"
+                android:hint="@string/common_input_phone_hint"
+                android:includeFontPadding="false"
+                android:inputType="number"
+                android:lines="1"
+                android:singleLine="true"
+                android:textColor="@color/color_FF1D2129"
+                android:textColorHint="@color/color_FFC9CDD4"
+                android:textSize="16sp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@id/tv_country_code"
+                app:layout_constraintTop_toTopOf="parent" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <com.adealink.weparty.commonui.widget.CommonButton
+            android:id="@+id/btn_verify"
+            android:layout_width="0dp"
+            android:layout_height="@dimen/common_button_height"
+            android:layout_marginTop="24dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/cl_input"
+            app:text="@string/common_submit"
+            app:textSize="16sp" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 93 - 0
module/joinus/src/main/res/layout/fragment_join_us_step_1_input_verify.xml

@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="26dp"
+        android:ellipsize="end"
+        android:fontFamily="@font/poppins_semibold"
+        android:gravity="start"
+        android:includeFontPadding="false"
+        android:text="@string/join_us_step1_input_verify_code"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="18sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_desc"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="22dp"
+        android:layout_marginTop="20dp"
+        android:ellipsize="end"
+        android:gravity="start|center_vertical"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_FF4E5969"
+        android:textSize="14sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_title"
+        tools:text="@string/join_us_step1_input_verify_code_desc" />
+
+    <include
+        android:id="@+id/v_verify_code"
+        layout="@layout/layout_join_us_verify_code"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="22dp"
+        android:layout_marginTop="20dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_desc" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_resend_duration"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="22dp"
+        android:layout_marginTop="20dp"
+        android:ellipsize="end"
+        android:gravity="start|center_vertical"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_FF86909C"
+        android:textSize="14sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/v_verify_code"
+        tools:text="@string/account_resend_verify_code_duration" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_resend"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="22dp"
+        android:layout_marginTop="0dp"
+        android:ellipsize="end"
+        android:gravity="start|center_vertical"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="14sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_resend_duration"
+        app:layout_goneMarginTop="20dp"
+        tools:text="@string/account_resend_verify_code_title" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 74 - 0
module/joinus/src/main/res/layout/fragment_join_us_step_2.xml

@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <androidx.appcompat.widget.LinearLayoutCompat
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:paddingHorizontal="16dp"
+        android:paddingTop="20dp"
+        android:paddingBottom="24dp">
+
+        <!-- 头像 -->
+        <include
+            android:id="@+id/v_edit_avatar"
+            layout="@layout/layout_join_us_step2_edit_avatar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+        <!-- 昵称 -->
+        <include
+            android:id="@+id/v_edit_nickname"
+            layout="@layout/layout_join_us_step2_edit_nickname"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="24dp" />
+
+        <!-- 性别 -->
+        <include
+            android:id="@+id/v_edit_gender"
+            layout="@layout/layout_join_us_step2_edit_gender"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="24dp" />
+
+        <!-- 生日 -->
+        <include
+            android:id="@+id/v_edit_birthday"
+            layout="@layout/layout_join_us_step2_edit_birthday"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="24dp" />
+
+        <!-- 语言 -->
+        <include
+            android:id="@+id/v_edit_language"
+            layout="@layout/layout_join_us_step2_edit_language"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="24dp" />
+
+        <!-- 自我介绍 -->
+        <include
+            android:id="@+id/v_edit_introduction"
+            layout="@layout/layout_join_us_step2_edit_introduction"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="24dp" />
+
+        <com.adealink.weparty.commonui.widget.CommonButton
+            android:id="@+id/btn_submit"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/common_button_height"
+            android:layout_marginTop="32dp"
+            app:text="@string/common_submit"
+            app:textColor="@color/white"
+            app:textSize="16sp" />
+
+    </androidx.appcompat.widget.LinearLayoutCompat>
+
+</androidx.core.widget.NestedScrollView>

+ 7 - 0
module/joinus/src/main/res/layout/fragment_join_us_step_3.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 54 - 0
module/joinus/src/main/res/layout/fragment_join_us_step_under_review.xml

@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/iv_under_review"
+        android:layout_width="100dp"
+        android:layout_height="100dp"
+        app:layout_constraintBottom_toTopOf="@id/tv_title"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_bias="0.24"
+        app:layout_constraintVertical_chainStyle="packed"
+        app:srcCompat="@drawable/join_us_under_review_ic" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="16dp"
+        android:layout_marginTop="10dp"
+        android:fontFamily="@font/poppins_semibold"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:text="@string/join_us_step_under_review_title"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="16sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintBottom_toTopOf="@id/tv_desc"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/iv_under_review" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_desc"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="16dp"
+        android:layout_marginTop="4dp"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:text="@string/join_us_step_under_review_desc"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="12sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_title" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 44 - 0
module/joinus/src/main/res/layout/layout_example_wrong_avatar.xml

@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    tools:layout_height="wrap_content"
+    tools:layout_width="76dp">
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/iv_wrong"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintDimensionRatio="1:1"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:srcCompat="@drawable/join_us_avatar_wrong_3" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:layout_width="16dp"
+        android:layout_height="16dp"
+        app:layout_constraintBottom_toBottomOf="@id/iv_wrong"
+        app:layout_constraintEnd_toEndOf="@id/iv_wrong"
+        app:layout_constraintStart_toStartOf="@id/iv_wrong"
+        app:layout_constraintTop_toBottomOf="@id/iv_wrong"
+        app:srcCompat="@drawable/join_us_avatar_wrong_tag_ic" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_wrong"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:maxLines="2"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="11sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/iv_wrong"
+        tools:text="@string/join_us_example_avatar_wrong_tip1" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 129 - 0
module/joinus/src/main/res/layout/layout_join_us_progress.xml

@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="73dp"
+    android:background="@drawable/join_us_progress_bg"
+    android:paddingVertical="9dp"
+    tools:ignore="HardcodedText">
+
+    <View
+        android:id="@+id/v_line_1_to_2"
+        android:layout_width="0dp"
+        android:layout_height="1dp"
+        android:layout_marginHorizontal="10dp"
+        android:background="@color/color_FFC9CDD4"
+        app:layout_constraintBottom_toBottomOf="@id/tv_index_1"
+        app:layout_constraintEnd_toEndOf="@id/tv_index_2"
+        app:layout_constraintStart_toStartOf="@id/tv_index_1"
+        app:layout_constraintTop_toTopOf="@id/tv_index_1" />
+
+    <View
+        android:id="@+id/v_line_2_to_3"
+        android:layout_width="0dp"
+        android:layout_height="1dp"
+        android:layout_marginHorizontal="10dp"
+        android:background="@color/color_FFC9CDD4"
+        app:layout_constraintBottom_toBottomOf="@id/tv_index_2"
+        app:layout_constraintEnd_toEndOf="@id/tv_index_3"
+        app:layout_constraintStart_toStartOf="@id/tv_index_2"
+        app:layout_constraintTop_toTopOf="@id/tv_index_2" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_index_1"
+        android:layout_width="21dp"
+        android:layout_height="21dp"
+        android:layout_marginStart="40dp"
+        android:background="@drawable/join_us_progress_index_selected_bg"
+        android:fontFamily="@font/poppins_semibold"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:text="1"
+        android:textColor="@color/white"
+        android:textSize="14sp"
+        app:layout_constraintEnd_toStartOf="@id/tv_index_2"
+        app:layout_constraintHorizontal_chainStyle="spread_inside"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_index_1_desc"
+        android:layout_width="72dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="6dp"
+        android:ellipsize="end"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:lines="2"
+        android:text="@string/join_us_step_1"
+        android:textColor="@color/color_FF86909C"
+        android:textSize="12sp"
+        app:layout_constraintEnd_toEndOf="@id/tv_index_1"
+        app:layout_constraintStart_toStartOf="@id/tv_index_1"
+        app:layout_constraintTop_toBottomOf="@id/tv_index_1" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_index_2"
+        android:layout_width="21dp"
+        android:layout_height="21dp"
+        android:background="@drawable/join_us_progress_index_bg"
+        android:fontFamily="@font/poppins_semibold"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:text="2"
+        android:textColor="@color/white"
+        android:textSize="14sp"
+        app:layout_constraintEnd_toStartOf="@id/tv_index_3"
+        app:layout_constraintStart_toEndOf="@id/tv_index_1"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_index_2_desc"
+        android:layout_width="72dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="6dp"
+        android:ellipsize="end"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:lines="2"
+        android:text="@string/join_us_step_2"
+        android:textColor="@color/color_FF86909C"
+        android:textSize="12sp"
+        app:layout_constraintEnd_toEndOf="@id/tv_index_2"
+        app:layout_constraintStart_toStartOf="@id/tv_index_2"
+        app:layout_constraintTop_toBottomOf="@id/tv_index_2" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_index_3"
+        android:layout_width="21dp"
+        android:layout_height="21dp"
+        android:layout_marginEnd="40dp"
+        android:background="@drawable/join_us_progress_index_bg"
+        android:fontFamily="@font/poppins_semibold"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:text="3"
+        android:textColor="@color/white"
+        android:textSize="14sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/tv_index_2"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_index_3_desc"
+        android:layout_width="72dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="6dp"
+        android:ellipsize="end"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:lines="2"
+        android:text="@string/join_us_step_3"
+        android:textColor="@color/color_FF86909C"
+        android:textSize="12sp"
+        app:layout_constraintEnd_toEndOf="@id/tv_index_3"
+        app:layout_constraintStart_toStartOf="@id/tv_index_3"
+        app:layout_constraintTop_toBottomOf="@id/tv_index_3" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 60 - 0
module/joinus/src/main/res/layout/layout_join_us_step2_edit_avatar.xml

@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/poppins_semibold"
+        android:includeFontPadding="false"
+        android:text="@string/join_us_step2_edit_avatar_title"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="16sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_desc"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:includeFontPadding="false"
+        android:text="@string/join_us_step2_edit_avatar_desc"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="14sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_title" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/btn_example"
+        android:layout_width="wrap_content"
+        android:layout_height="24dp"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:paddingHorizontal="6dp"
+        android:text="@string/common_example"
+        android:textColor="@color/color_FF3FBFBD"
+        android:textSize="11sp"
+        app:layout_constraintBottom_toBottomOf="@id/tv_desc"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/tv_title" />
+
+    <include
+        android:id="@+id/v_avatar"
+        layout="@layout/layout_join_us_step2_edit_avatar_selector"
+        android:layout_width="105dp"
+        android:layout_height="105dp"
+        android:layout_marginTop="8dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_desc" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 55 - 0
module/joinus/src/main/res/layout/layout_join_us_step2_edit_avatar_selector.xml

@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    tools:layout_height="100dp"
+    tools:layout_width="100dp">
+
+    <View
+        android:id="@+id/v_modify_bg"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="@drawable/common_add_photo_bg"
+        app:layout_constraintDimensionRatio="1:1"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/iv_modify_picture"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_chainStyle="packed"
+        app:srcCompat="@drawable/common_add_photo_ic" />
+
+    <androidx.constraintlayout.widget.Group
+        android:id="@+id/v_modify_avatar"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="visible"
+        app:constraint_referenced_ids="v_modify_bg,iv_modify_picture" />
+
+    <com.adealink.frame.image.view.NetworkImageView
+        android:id="@+id/iv_avatar"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:roundedCornerRadius="12dp" />
+
+    <androidx.constraintlayout.widget.Group
+        android:id="@+id/v_remove_avatar"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="visible"
+        app:constraint_referenced_ids="iv_avatar" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 80 - 0
module/joinus/src/main/res/layout/layout_join_us_step2_edit_birthday.xml

@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/poppins_semibold"
+        android:includeFontPadding="false"
+        android:text="@string/join_us_step2_edit_birthday_title"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="16sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_desc"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:includeFontPadding="false"
+        android:text="@string/join_us_step2_edit_birthday_desc"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="14sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_title" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_birthday"
+        android:layout_width="match_parent"
+        android:layout_height="38dp"
+        android:layout_marginTop="8dp"
+        android:background="@drawable/common_input_edit_bg"
+        android:paddingHorizontal="16dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_desc">
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_birthday"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="8dp"
+            android:background="@null"
+            android:ellipsize="end"
+            android:fontFamily="@font/poppins_semibold"
+            android:gravity="start|center_vertical"
+            android:hint="@string/join_us_step2_edit_birthday_hint"
+            android:includeFontPadding="false"
+            android:singleLine="true"
+            android:textColor="@color/color_FF1D2129"
+            android:textColorHint="@color/color_FFC9CDD4"
+            android:textSize="14sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/iv_go"
+            app:layout_constraintHorizontal_bias="0"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:id="@+id/iv_go"
+            android:layout_width="20dp"
+            android:layout_height="20dp"
+            android:rotationY="@integer/locale_mirror_flip"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/common_go_ic" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 115 - 0
module/joinus/src/main/res/layout/layout_join_us_step2_edit_gender.xml

@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/poppins_semibold"
+        android:includeFontPadding="false"
+        android:text="@string/join_us_step2_edit_gender_title"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="16sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_desc"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:includeFontPadding="false"
+        android:text="@string/join_us_step2_edit_gender_desc"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="14sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_title" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/btn_male"
+        android:layout_width="0dp"
+        android:layout_height="40dp"
+        android:layout_marginTop="8dp"
+        android:background="@drawable/join_us_edit_gender_bg"
+        app:layout_constraintEnd_toStartOf="@id/btn_female"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_desc">
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:id="@+id/iv_male"
+            android:layout_width="14dp"
+            android:layout_height="14dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/tv_male"
+            app:layout_constraintHorizontal_chainStyle="packed"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/common_male_ic" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_male"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="6dp"
+            android:fontFamily="@font/poppins_semibold"
+            android:gravity="center"
+            android:includeFontPadding="false"
+            android:text="@string/common_male"
+            android:textColor="@color/color_FF1D2129"
+            android:textSize="14sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@id/iv_male"
+            app:layout_constraintTop_toTopOf="parent" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/btn_female"
+        android:layout_width="0dp"
+        android:layout_height="40dp"
+        android:layout_marginStart="24dp"
+        android:layout_marginTop="8dp"
+        android:background="@drawable/join_us_edit_gender_bg"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/btn_male"
+        app:layout_constraintTop_toBottomOf="@id/tv_desc">
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:id="@+id/iv_female"
+            android:layout_width="14dp"
+            android:layout_height="14dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/tv_female"
+            app:layout_constraintHorizontal_chainStyle="packed"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/common_female_ic" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_female"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="6dp"
+            android:fontFamily="@font/poppins_semibold"
+            android:gravity="center"
+            android:includeFontPadding="false"
+            android:text="@string/common_female"
+            android:textColor="@color/color_FF1D2129"
+            android:textSize="14sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@id/iv_female"
+            app:layout_constraintTop_toTopOf="parent" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 80 - 0
module/joinus/src/main/res/layout/layout_join_us_step2_edit_introduction.xml

@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/poppins_semibold"
+        android:includeFontPadding="false"
+        android:text="@string/join_us_step2_edit_introduction_title"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="16sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_desc"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:includeFontPadding="false"
+        android:text="@string/join_us_step2_edit_introduction_desc"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="14sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_title" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:background="@drawable/common_input_edit_bg"
+        android:paddingHorizontal="16dp"
+        android:paddingVertical="10dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_desc">
+
+        <androidx.appcompat.widget.AppCompatEditText
+            android:id="@+id/et_intro_input"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@null"
+            android:fontFamily="@font/poppins_semibold"
+            android:gravity="top|center_vertical"
+            android:hint="@string/join_us_step2_edit_introduction_hint"
+            android:includeFontPadding="false"
+            android:minHeight="98dp"
+            android:singleLine="true"
+            android:textColor="@color/color_FF1D2129"
+            android:textColorHint="@color/color_FFC9CDD4"
+            android:textSize="14sp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_input_length"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:includeFontPadding="false"
+            android:textColor="@color/color_FFC9CDD4"
+            android:textSize="14sp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/et_intro_input"
+            tools:text="0/200" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.