zhujie
2025-04-03 fe04012057d024770e0180543483d393281a542f
Merge branch 'master' of http://47.96.225.205:8888/r/FirstApp2

# Conflicts:
# app/build.gradle
# app/src/main/java/com/example/firstapp/ui/notifications/NotificationsFragment.kt
已删除1个文件
已修改31个文件
已重命名1个文件
已添加89个文件
4844 ■■■■ 文件已修改
.gitignore 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/deploymentTargetSelector.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/gradle.xml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/misc.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/build.gradle 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/AndroidManifest.xml 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/App.kt 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/MainActivity.kt 261 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/activity/ContentDetailActivity.kt 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/activity/FirstFragment.kt 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/activity/PhoneLoginActivity.kt 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/activity/PickupActivity.kt 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/activity/SecondFragment.kt 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/activity/VipActivity.kt 544 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/adapter/CardAdapter.kt 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/adapter/CategorySelectorAdapter.kt 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/adapter/IncomeAdapter.kt 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/dao/CodeDao.kt 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/repository/CodeRepository.kt 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/request/ProductOrdersRequest.kt 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/request/SmsLoginRequest.kt 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/request/SmsSendRequest.kt 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/response/ContentResponse.kt 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/response/OAuth2TokenResponse.kt 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/response/UserInfo.kt 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/service/ApiService.kt 63 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/service/ModelService.kt 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/model/CardData.kt 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/model/CategoryConfig.kt 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/model/CategoryConfigSync.kt 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/model/IncomeGroup.kt 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/model/MemberBenefitItem.kt 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/model/MoreMemberBenefitItem.kt 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/model/StationGroup.kt 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/network/AuthInterceptor.kt 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/network/TokenExpiredInterceptor.kt 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/home/HomeFragment.kt 387 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt 238 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/login/LoginViewModel.kt 58 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/notifications/NotificationsFragment.kt 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/vip/MemberInfoCardFragment.kt 162 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/vip/MemberInfoCardViewModel.kt 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/util/CategoryDragCallback.kt 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/util/SecureStorage.kt 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/PreferencesManager.kt 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/view/UnderlineTextView.kt 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/alipay_shape.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/black_button_background.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/checkbox_round.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/checkbox_round_selected.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/dialog_background.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/error_placeholder.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/gold_border_shape.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/gray_border_shape.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/home_add.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/ic_category.xml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/ic_check.xml 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/ic_reminder.xml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/ic_stats.xml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/member_background.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/more_benefit_shape.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/overlay_black_background.xml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/round_red_bg.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/round_transition_gray_bg.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/set.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/tab_selected_background.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/up.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/up_back.jpg 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/vip_black_background.xml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/vip_white_background.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/wechat_shape.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/activity_phone_login.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/activity_vip.xml 518 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/content_vip.xml 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/dialog_category_selector.xml 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/fragment_dashboard.xml 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/fragment_first.xml 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/fragment_home.xml 236 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/fragment_member_info_card.xml 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/fragment_notifications.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/fragment_second.xml 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/item_card.xml 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/item_card_continue_monthly.xml 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/item_card_single_month.xml 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/item_card_yearly.xml 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/item_category_selector.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/item_income_group.xml 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/item_income_package_home.xml 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/vip_protocol_dialog_custom.xml 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/vip_protocol_dialog_custom_bak.xml 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/avatar_default.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/overlay.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/overlay2.jpg 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/vip.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/vip_ad_no.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/vip_ai_ass_icon.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/vip_alipay.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/vip_bar_sta.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/vip_cancel.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/vip_cancel_link.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/vip_card.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/vip_circle_pie.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/vip_copy.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/vip_crown.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/vip_free_member.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/vip_life_insurance.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/vip_ling.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/vip_more.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/vip_no.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/vip_right.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap/vip_wechat.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/navigation/nav_graph.xml 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values-land/dimens.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values-night/themes.xml 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values-v23/themes.xml 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values-w1240dp/dimens.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values-w600dp/dimens.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values/dimens.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values/themes.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/xml/network_security_config.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -7,6 +7,9 @@
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
/.idea/deploymentTargetSelector.xml
/.idea/gradle.xml
/.idea/misc.xml
.DS_Store
/build
/captures
.idea/deploymentTargetSelector.xml
@@ -4,10 +4,10 @@
    <selectionStates>
      <SelectionState runConfigName="app">
        <option name="selectionMode" value="DROPDOWN" />
        <DropdownSelection timestamp="2025-02-25T09:44:45.437232500Z">
        <DropdownSelection timestamp="2025-03-28T01:17:48.834725500Z">
          <Target type="DEFAULT_BOOT">
            <handle>
              <DeviceId pluginId="PhysicalDevice" identifier="serial=88Y5T19C14010323" />
              <DeviceId pluginId="PhysicalDevice" identifier="serial=HMQNW19A24001406" />
            </handle>
          </Target>
        </DropdownSelection>
.idea/gradle.xml
@@ -4,9 +4,8 @@
  <component name="GradleSettings">
    <option name="linkedExternalProjectsSettings">
      <GradleProjectSettings>
        <option name="testRunner" value="CHOOSE_PER_TEST" />
        <option name="externalProjectPath" value="$PROJECT_DIR$" />
        <option name="gradleJvm" value="17" />
        <option name="gradleJvm" value="jbr-17" />
        <option name="modules">
          <set>
            <option value="$PROJECT_DIR$" />
.idea/misc.xml
@@ -1,6 +1,6 @@
<project version="4">
  <component name="ExternalStorageConfigurationManager" enabled="true" />
  <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
  <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
    <output url="file://$PROJECT_DIR$/build/classes" />
  </component>
  <component name="ProjectType">
app/build.gradle
@@ -283,11 +283,15 @@
    // 支付宝支付SDK
    api 'com.alipay.sdk:alipaysdk-android:+@aar'
    implementation 'com.google.android.material:material:1.4.0'
    //implementation 'com.google.android.material:material:1.4.0'
    // 加密SharedPreferences
    implementation "androidx.security:security-crypto:1.1.0-alpha06"
    // Material Design组件im
    implementation 'com.google.android.material:material:1.9.0'
    implementation 'de.hdodenhof:circleimageview:3.1.0'
    implementation 'com.github.castorflex:SmoothProgressBar:1.1.0'
}
app/src/main/AndroidManifest.xml
@@ -83,7 +83,13 @@
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.FirstApp"
        tools:targetApi="31">
        tools:targetApi="31"
        android:usesCleartextTraffic="true"
        >
        <activity
            android:name=".activity.VipActivity"
            android:exported="false"
            android:theme="@style/Theme.FirstApp" />
        <activity
            android:name=".ui.reminderOther.ReminderOtherAddActivity2"
            android:configChanges="orientation|keyboardHidden|screenSize"
@@ -106,11 +112,11 @@
            android:exported="false" />
        <activity
            android:name=".activity.PickupActivity"
            android:exported="false"/>
            android:exported="false" />
        <activity
            android:name=".activity.ContentDetailActivity"
            android:theme="@style/Theme.ContentDetail"
            android:exported="false" />
            android:exported="false"
            android:theme="@style/Theme.ContentDetail" />
        <activity
            android:name=".ui.profile.EditProfileActivity"
            android:exported="false"/>
app/src/main/java/com/example/firstapp/App.kt
@@ -39,6 +39,7 @@
import com.example.firstapp.utils.FRPC_LIB_VERSION
import com.example.firstapp.utils.HistoryUtils
import com.example.firstapp.utils.Log
import com.example.firstapp.utils.PreferencesManager
import com.example.firstapp.utils.SettingUtils
import com.example.firstapp.utils.SharedPreference
@@ -154,7 +155,7 @@
        try {
            context = applicationContext
            initLibs()
            PreferencesManager.init(this)
            //纯客户端模式
            if (SettingUtils.enablePureClientMode) return
app/src/main/java/com/example/firstapp/MainActivity.kt
@@ -17,28 +17,20 @@
import android.net.Uri
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import com.example.firstapp.activity.LoginActivity
import com.example.firstapp.adapter.MyAdapter
import com.example.firstapp.core.Core
import com.example.firstapp.database.entity.Code
import com.example.firstapp.database.entity.Msg
import com.example.firstapp.database.service.RetrofitClient
import com.example.firstapp.ui.home.HomeViewModel
import com.example.firstapp.database.service.RetrofitModelClient
import com.example.firstapp.utils.Log
import com.example.firstapp.workers.KeywordUpdateWorker
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.time.LocalDateTime
import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.concurrent.TimeUnit
import java.time.ZoneId
class MainActivity : AppCompatActivity() {
    // 安全防护关键词数组
@@ -47,9 +39,6 @@
    private lateinit var binding: ActivityMainBinding
    private var smsReceiver: SmsReceiver? = null
    private lateinit var adapter: MyAdapter
    private lateinit var homeViewModel: HomeViewModel
    private val multiplePermissionRequest =
        registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
@@ -60,8 +49,6 @@
                ) && permissions.getOrDefault(Manifest.permission.READ_SMS, false) -> {
                    // 两个权限都获得授权
                    registerSmsReceiver()
//                syncRecentSms()
//                initializeSecurityKeywords()
                }
                else -> {
@@ -82,13 +69,6 @@
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        setupViews()
//        binding.btnLogout.setOnClickListener {
//            logout()
//        }
        // 在此位置初始化 homeViewModel
//        homeViewModel = ViewModelProvider(this).get(HomeViewModel::class.java)
//
//        val navView: BottomNavigationView = binding.navView
        val navView = binding.navView
        val navController = findNavController(R.id.nav_host_fragment_activity_main)
@@ -113,35 +93,6 @@
            registerSmsReceiver()
            syncRecentSms()
        }
//        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
//        recyclerView.layoutManager = LinearLayoutManager(this)
//
//        // 初始化适配器
//        adapter = MyAdapter()
//        recyclerView.adapter = adapter
//
//        // 观察 LiveData 数据
//        homeViewModel.codeList.observe(this) { codeList ->
//            // 如果 codeList 为 null,避免闪退
//            if (codeList != null) {
//                adapter.submitList(codeList)
//                // 滚动到顶部
//                recyclerView.scrollToPosition(0)
//            } else {
//                // 如果数据为空,可以显示空列表或其他处理
//                Toast.makeText(this, "No data available", Toast.LENGTH_SHORT).show()
//            }
//        }
//        // 注册广播接收器来监听数据更新
//        val filter = IntentFilter("com.example.firstapp.DATA_UPDATED")
//        registerReceiver(object : BroadcastReceiver() {
//            override fun onReceive(context: Context, intent: Intent) {
//                // 数据已更新,刷新 LiveData
//                homeViewModel.loadData()
//            }
//        }, filter)
    }
    private fun registerSmsReceiver() {
@@ -161,25 +112,9 @@
        registerReceiver(smsReceiver, filter)
    }
    private fun setupKeywordUpdate() {
        val updateRequest = PeriodicWorkRequestBuilder<KeywordUpdateWorker>(
            1, TimeUnit.HOURS,  // 每小时更新一次
            15, TimeUnit.MINUTES  // 灵活时间窗口
        ).build()
        WorkManager.getInstance(this).enqueueUniquePeriodicWork(
            "keyword_update", ExistingPeriodicWorkPolicy.REPLACE, updateRequest
        )
    }
    private fun setupViews() {
        // 获取并显示当前登录的手机号
        val phone =
            getSharedPreferences("user_info", Context.MODE_PRIVATE).getString("phone", "") ?: ""
//        binding.apply {
//            tvPhone.text = "当前登录手机号:$phone"
//        }
        getSharedPreferences("user_info", Context.MODE_PRIVATE).getString("phone", "") ?: ""
    }
    private fun logout() {
@@ -247,120 +182,126 @@
                    val msgId = Core.msg.insert(msg)
                    // 禁用关键词拦截
                    if (securityKeywordsList.any { it in messageBody }) {
                        android.util.Log.d("MainActivity", "历史短信含有禁用关键词,跳过处理")
                        continue
                    }
//                    if (securityKeywordsList.any { it in messageBody }) {
//                        android.util.Log.d("MainActivity", "历史短信含有禁用关键词,跳过处理")
//                        continue
//                    }
                    // 使用协程处理API调用和数据库操作
                    CoroutineScope(Dispatchers.IO).launch {
                        try {
                            // API调用移到synchronized块外
                            val response = RetrofitClient.apiService.processSms(mapOf("content" to messageBody))
                            val response = RetrofitModelClient.modelService.processSms(mapOf("content" to messageBody))
                            
                            // 数据库操作放在synchronized块内
                            synchronized(syncLock) {
                                if (response.status == "success") {
                                    when (response.data.category) {
                                        "快递" -> {
                                            val existingCode = Core.code.queryByTypeAndCodeAndDate(
                                                response.data.category,
                                                response.data.details.pickupCode ?: "",
                                                dateString
                                            )
                                            if (existingCode == null) {
                                                val code = Code(
                                                    id = 0,
                                                    category = response.data.category,
                                                    categoryId = 1,
                                                    typeId = 1,
                                                    ruleId = 1,
                                                    msgId = msgId,
                                                    createTime = dateString,
                                                    oneLevel = response.data.details.post ?: "",
                                                    secondLevel = response.data.details.company ?: "",
                                                    code = response.data.details.pickupCode ?: "",
                                                    pickup = 0,
                                                    pickupTime = "",
                                                    overTime = "",
                                                    address = response.data.details.address ?: "",
                                                    remarks = response.data.details.time ?: "",
                                            val pickupCode = response.data.details.pickupCode ?: ""
                                            if (pickupCode.isNotEmpty()) {
                                                val existingCode = Core.code.queryByTypeAndCodeAndDate(
                                                    response.data.category,
                                                    pickupCode,
                                                    dateString
                                                )
                                                if(code.oneLevel!=""  && code.secondLevel!="" && code.code!="") {
                                                    Core.code.insert(code)
                                                if (existingCode == null) {
                                                    val code = Code(
                                                        id = 0,
                                                        category = response.data.category,
                                                        categoryId = 1,
                                                        typeId = 1,
                                                        ruleId = 1,
                                                        msgId = msgId,
                                                        createTime = dateString,
                                                        oneLevel = response.data.details.post ?: "",
                                                        secondLevel = response.data.details.company ?: "",
                                                        code = pickupCode,
                                                        pickup = 0,
                                                        pickupTime = "",
                                                        overTime = "",
                                                        address = response.data.details.address ?: "",
                                                        remarks = response.data.details.time ?: "",
                                                    )
                                                    if(code.oneLevel.isNotEmpty() && code.secondLevel.isNotEmpty() && code.code.isNotEmpty()) {
                                                        Core.code.insert(code)
                                                        android.util.Log.d("MainActivity", "历史快递短信已保存: $pickupCode")
                                                    }
                                                } else {
                                                    android.util.Log.d("MainActivity", "发现重复快递短信,跳过保存: $pickupCode")
                                                }
                                                android.util.Log.d("MainActivity", "历史快递短信已保存: ${response.data.details.pickupCode}")
                                            } else {
                                                android.util.Log.d("MainActivity", "发现重复快递短信,跳过保存: ${response.data.details.pickupCode}")
                                            }
                                        }
                                        "还款" -> {
                                            val existingCode = Core.code.queryByTypeAndCodeAndDate(
                                                response.data.category,
                                                response.data.details.amount ?: "",
                                                dateString
                                            )
                                            if (existingCode == null) {
                                                val code = Code(
                                                    id = 0,
                                                    category = response.data.category,
                                                    categoryId = 2,
                                                    typeId = 1,
                                                    ruleId = 2,
                                                    msgId = msgId,
                                                    createTime = dateString,
                                                    oneLevel = response.data.details.type ?: "",
                                                    secondLevel = response.data.details.bank ?: "",
                                                    code = response.data.details.amount ?: "",
                                                    pickup = 0,
                                                    pickupTime = "",
                                                    overTime = response.data.details.date ?: "",
                                                    address = response.data.details.address ?: "",
                                                    remarks = "最小还款金额${response.data.details.min_amount}还款卡号${response.data.details.number}"
                                            val amount = response.data.details.amount ?: ""
                                            if (amount.isNotEmpty()) {
                                                val existingCode = Core.code.queryByTypeAndCodeAndDate(
                                                    response.data.category,
                                                    amount,
                                                    dateString
                                                )
                                                if(code.oneLevel!=""  && code.secondLevel!="" && code.code!="") {
                                                    Core.code.insert(code)
                                                if (existingCode == null) {
                                                    val code = Code(
                                                        id = 0,
                                                        category = response.data.category,
                                                        categoryId = 2,
                                                        typeId = 1,
                                                        ruleId = 2,
                                                        msgId = msgId,
                                                        createTime = dateString,
                                                        oneLevel = response.data.details.type ?: "",
                                                        secondLevel = response.data.details.bank ?: "",
                                                        code = amount,
                                                        pickup = 0,
                                                        pickupTime = "",
                                                        overTime = response.data.details.date ?: "",
                                                        address = response.data.details.address ?: "",
                                                        remarks = "最小还款金额${response.data.details.min_amount}还款卡号${response.data.details.number}"
                                                    )
                                                    if(code.oneLevel.isNotEmpty() && code.secondLevel.isNotEmpty() && code.code.isNotEmpty()) {
                                                        Core.code.insert(code)
                                                        android.util.Log.d("MainActivity", "历史还款短信已保存: $amount")
                                                    }
                                                } else {
                                                    android.util.Log.d("MainActivity", "发现重复还款短信,跳过保存: $amount")
                                                }
                                                android.util.Log.d("MainActivity", "历史还款短信已保存: ${response.data.details.amount}")
                                            } else {
                                                android.util.Log.d("MainActivity", "发现重复还款短信,跳过保存: ${response.data.details.amount}")
                                            }
                                        }
                                        "收入" -> {
                                            val existingCode = Core.code.queryByTypeAndCodeAndDate(
                                                response.data.category,
                                                response.data.details.amount ?: "",
                                                dateString
                                            )
                                            if (existingCode == null) {
                                                val code = Code(
                                                    id = 0,
                                                    category = response.data.category,
                                                    categoryId = 3, // 3-收入类型
                                                    typeId = 1,     //暂时没有根据type分类
                                                    ruleId = 2,     //1-还款类型
                                                    msgId = msgId,
                                                    createTime = dateString,
                                                    oneLevel = response.data.details.bank ?: "",
                                                    secondLevel = response.data.details.bank ?: "",
                                                    code = response.data.details.amount ?: "",
                                                    pickup = 0, // 0-未取件,1-已取件
                                                    pickupTime = "", // 取件时间为空
                                                    overTime = response.data.details.datetime
                                                        ?: "",  // 超时时间为空,暂时没有这块处理逻辑
                                                    address = response.data.details.address ?: "",
                                                    remarks = "余额" + response.data.details.balance ?: "",
                                            val amount = response.data.details.amount ?: ""
                                            if (amount.isNotEmpty()) {
                                                val existingCode = Core.code.queryByTypeAndCodeAndDate(
                                                    response.data.category,
                                                    amount,
                                                    dateString
                                                )
                                                if(code.oneLevel!=""  && code.secondLevel!="" && code.code!="") {
                                                    Core.code.insert(code)
                                                if (existingCode == null) {
                                                    val code = Code(
                                                        id = 0,
                                                        category = response.data.category,
                                                        categoryId = 3, // 3-收入类型
                                                        typeId = 1,     //暂时没有根据type分类
                                                        ruleId = 2,     //1-还款类型
                                                        msgId = msgId,
                                                        createTime = dateString,
                                                        oneLevel = response.data.details.bank ?: "",
                                                        secondLevel = response.data.details.bank ?: "",
                                                        code = amount,
                                                        pickup = 0, // 0-未取件,1-已取件
                                                        pickupTime = "", // 取件时间为空
                                                        overTime = response.data.details.datetime
                                                            ?: "",  // 超时时间为空,暂时没有这块处理逻辑
                                                        address = response.data.details.address ?: "",
                                                        remarks = "余额" + response.data.details.balance ?: "",
                                                    )
                                                    if(code.oneLevel.isNotEmpty() && code.secondLevel.isNotEmpty() && code.code.isNotEmpty()) {
                                                        Core.code.insert(code)
                                                        android.util.Log.d("MainActivity", "历史还款短信已保存: $amount")
                                                    }
                                                } else {
                                                    android.util.Log.d("MainActivity", "发现重复还款短信,跳过保存: $amount")
                                                }
                                                android.util.Log.d("MainActivity", "历史还款短信已保存: ${response.data.details.amount}")
                                            } else {
                                                android.util.Log.d("MainActivity", "发现重复还款短信,跳过保存: ${response.data.details.amount}")
                                            }
                                        }
                                    }
app/src/main/java/com/example/firstapp/activity/ContentDetailActivity.kt
@@ -14,7 +14,7 @@
    private lateinit var toolbar: androidx.appcompat.widget.Toolbar
    companion object {
        const val EXTRA_CONTENT_TYPE = "content_type"
        const val ID = "id"
        const val EXTRA_TITLE = "title"
    }
@@ -34,20 +34,20 @@
        toolbar.setNavigationOnClickListener { finish() }
        // 获取内容类型
        val contentType = intent.getStringExtra(EXTRA_CONTENT_TYPE)
        if (contentType != null) {
            loadContent(contentType)
        val id = intent.getStringExtra(ID)
        if (id != null) {
            loadContent(id)
        } else {
            Toast.makeText(this, "参数错误", Toast.LENGTH_SHORT).show()
            finish()
        }
    }
    private fun loadContent(type: String) {
    private fun loadContent(id: String) {
        lifecycleScope.launch {
            try {
                val response = RetrofitClient.apiService.getContentByType(type)
                if (response.code == 200 && response.data != null) {
                val response = RetrofitClient.apiService.getContentById(id)
                if (response.code == "0" && response.data != null) {
                    // 构建HTML内容
                    val htmlContent = """
                        <!DOCTYPE html>
app/src/main/java/com/example/firstapp/activity/FirstFragment.kt
对比新文件
@@ -0,0 +1,45 @@
package com.example.firstapp.activity
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import com.example.firstapp.R
import com.example.firstapp.databinding.FragmentFirstBinding
/**
 * A simple [Fragment] subclass as the default destination in the navigation.
 */
class FirstFragment : Fragment() {
    private var _binding: FragmentFirstBinding? = null
    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentFirstBinding.inflate(inflater, container, false)
        return binding.root
    }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.buttonFirst.setOnClickListener {
            findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
        }
    }
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}
app/src/main/java/com/example/firstapp/activity/PhoneLoginActivity.kt
@@ -10,6 +10,7 @@
import com.example.firstapp.databinding.ActivityPhoneLoginBinding
import com.example.firstapp.ui.login.LoginViewModel
import com.example.firstapp.utils.Log
import com.example.firstapp.utils.PreferencesManager
class PhoneLoginActivity : AppCompatActivity() {
    private lateinit var binding: ActivityPhoneLoginBinding
@@ -28,6 +29,9 @@
    private fun setupViews() {
        binding.apply {
            // 设置上次登录的手机号
            etPhone.setText(PreferencesManager.getLastLoginPhone())
            btnBack.setOnClickListener {
                startActivity(Intent(this@PhoneLoginActivity, LoginActivity::class.java))
                finish()
@@ -48,6 +52,8 @@
                val phone = etPhone.text.toString()
                val code = etCode.text.toString()
                if (phone.length == 11 && code.length == 6) {
                    // 保存登录的手机号
                    PreferencesManager.saveLastLoginPhone(phone)
                    viewModel.login(phone, code)
                } else {
                    Toast.makeText(this@PhoneLoginActivity,
app/src/main/java/com/example/firstapp/activity/PickupActivity.kt
@@ -14,11 +14,23 @@
class PickupActivity : AppCompatActivity() {
    private lateinit var binding: ActivityPickupBinding
    private lateinit var expressAdapter: ExpressPackageAdapter
    // 添加类型常量
    companion object {
        const val TYPE_EXPRESS = "express"
        const val TYPE_REPAYMENT = "repayment"
        const val TYPE_INCOME = "income"
    }
    private var pageType = TYPE_EXPRESS
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityPickupBinding.inflate(layoutInflater)
        setContentView(binding.root)
        // 获取页面类型
        pageType = intent.getStringExtra("page_type") ?: TYPE_EXPRESS
        initViews()
        loadData()
@@ -39,17 +51,37 @@
            finish()
        }
        // 修改底部按钮点击事件
        binding.btnPickupAll.setOnClickListener {
            showPickupConfirmDialog()
        // 设置底部按钮文本并添加点击事件
        binding.btnPickupAll.apply {
            text = getButtonText()
            setOnClickListener {
                showPickupConfirmDialog()
            }
        }
    }
    private fun getConfirmMessage(): String {
        return when (pageType) {
            TYPE_EXPRESS -> "是否确认取出所有包裹?"
            TYPE_REPAYMENT -> "是否确认处理所有还款?"
            TYPE_INCOME -> "是否确认处理所有收入?"
            else -> "是否确认处理所有项目?"
        }
    }
    private fun getButtonText(): String {
        return when (pageType) {
            TYPE_EXPRESS -> "全部取件"
            TYPE_REPAYMENT -> "全部还款"
            TYPE_INCOME -> "全部收款"
            else -> "全部处理"
        }
    }
    private fun showPickupConfirmDialog() {
        AlertDialog.Builder(this)
            .setTitle("确认取件")
            .setMessage("是否确认取出所有包裹?")
            .setTitle(getButtonText())
            .setMessage(getConfirmMessage())
            .setPositiveButton("确认") { _, _ ->
                handlePickupAll()
            }
@@ -84,7 +116,7 @@
                // 清空列表
                expressAdapter.submitList(emptyList())
                // 更新包裹数量显示
                binding.tvPackageCount.text = "共0个包裹"
                binding.tvPackageCount.text = getCountText(0)
                // 通知MainActivity刷新
                setResult(RESULT_OK)
            } catch (e: Exception) {
@@ -92,6 +124,15 @@
                // 如果出错则重新加载数据
                loadData()
            }
        }
    }
    private fun getCountText(count: Int): String {
        return when (pageType) {
            TYPE_EXPRESS -> "共${count}个包裹"
            TYPE_REPAYMENT -> "共${count}笔还款"
            TYPE_INCOME -> "共${count}笔收入"
            else -> "共${count}个"
        }
    }
@@ -112,7 +153,7 @@
            
            expressAdapter.submitList(packages)
            binding.tvStationName.text = stationName
            binding.tvPackageCount.text = "共${packages.size}个包裹"
            binding.tvPackageCount.text = getCountText(packages.size)
        }
    }
app/src/main/java/com/example/firstapp/activity/SecondFragment.kt
对比新文件
@@ -0,0 +1,45 @@
package com.example.firstapp.activity
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import com.example.firstapp.R
import com.example.firstapp.databinding.FragmentSecondBinding
/**
 * A simple [Fragment] subclass as the second destination in the navigation.
 */
class SecondFragment : Fragment() {
    private var _binding: FragmentSecondBinding? = null
    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentSecondBinding.inflate(inflater, container, false)
        return binding.root
    }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.buttonSecond.setOnClickListener {
            findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment)
        }
    }
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}
app/src/main/java/com/example/firstapp/activity/VipActivity.kt
对比新文件
@@ -0,0 +1,544 @@
package com.example.firstapp.activity
import android.app.AlertDialog
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.Button
import android.widget.CheckBox
import android.widget.CompoundButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContentProviderCompat.requireContext
import androidx.core.content.ContextCompat
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.ui.AppBarConfiguration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.example.firstapp.R
import com.example.firstapp.adapter.CardAdapter
import com.example.firstapp.database.request.ProductOrdersRequest
import com.example.firstapp.database.response.UserInfo
import com.example.firstapp.database.service.RetrofitClient
import com.example.firstapp.databinding.ActivityVipBinding
import com.example.firstapp.model.CardData
import com.example.firstapp.model.MemberBenefitItem
import com.example.firstapp.pay.PayAbility
import com.example.firstapp.ui.vip.MemberInfoCardFragment
import com.example.firstapp.utils.Log
import com.example.firstapp.utils.PreferencesManager
import com.google.gson.Gson
import kotlinx.coroutines.launch
import java.math.BigDecimal
class VipActivity : AppCompatActivity() {
    private lateinit var appBarConfiguration: AppBarConfiguration
    private lateinit var binding: ActivityVipBinding
    private var isPaymentSelected = false // 全局变量,默认未选中
    private var orderName = ""
    private var orderType=""
    private var currentPrice = BigDecimal.ZERO
    private var originalPrice = BigDecimal.ZERO
    private var paymentMethod = ""
    private var currentUserInfo: UserInfo? = null // 确保使用你的实际数据类
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityVipBinding.inflate(layoutInflater)
        setContentView(binding.root)
        setSupportActionBar(binding.toolbar)
        // 显式设置 MaterialToolbar 的标题
        supportActionBar?.title = ""
        // 如果不需要 ActionBar 的导航功能,可以禁用
//        supportActionBar?.setDisplayHomeAsUpEnabled(false)
//        supportActionBar?.setDisplayShowHomeEnabled(false)
        // 返回事件
        binding.ivBack.setOnClickListener{
            finish()
        }
//        会员基本信息初始化
        loadUserInfo()
//        会员卡片初始化
        vipCardInit()
//        会员权益初始化
        memberBenefitInit()
        // 支付宝点击事件
        handleAlipayClick()
        // 微信点击事件
        handleWechatClick()
        // 勾选协议点击事件
        handlePrototalClick()
        // VIP会员服务协议
        handleVipProtocolClick()
    }
    private fun handlePrototalClick(){
        val protocol: CheckBox = findViewById(R.id.protocol_checkbox);
        // 监听 RadioButton 选中状态
        protocol.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
            isPaymentSelected = isChecked // 更新全局变量
//            if(isChecked){
//                protocol.background = ContextCompat.getDrawable(this, R.drawable.checkbox_round_selected)
//            }else{
//                protocol.background = ContextCompat.getDrawable(this, R.drawable.checkbox_round)
//            }
        })
    }
    // 这里添加alipay_layout的点击事件
    private fun handleAlipayClick() {
        // 绑定按钮
        val btnAlipay: LinearLayout = findViewById(R.id.alipay_layout);
        btnAlipay.setOnClickListener(View.OnClickListener {
            // 判断协议是否勾选
            if (!isPaymentSelected){
                // 这里需要弹出框
                showConfirmDialog()
            }else{
                doAlipay()
            }
        })
    }
    private fun doAlipay(){
        lifecycleScope.launch {
            try {
                var request = ProductOrdersRequest(
                    orderName=orderName,
                    orderType=orderType,
                    originalPrice=originalPrice,
                    currentPrice=currentPrice,
                    paymentMethod=""
                )
                Log.d("REQUEST", Gson().toJson(request))
                val response = RetrofitClient.apiService.getPayOrderInfo(request)
                Log.d("API_RESPONSE", response.toString())
                var orderInfo=response.data
                Log.d("AliPayHelper","获取订单信息时: ${response}")
                // 这里调用支付宝
                PayAbility.aliPay(this@VipActivity, orderInfo, Observer {
                    when (it.resultStatus) {
                        "9000" -> {
//                                Snackbar.make(binding.root, "支付成功", Snackbar.LENGTH_LONG).show()
                            runOnUiThread {
                                Toast.makeText(this@VipActivity, "支付成功", Toast.LENGTH_LONG).show()
                                loadUserInfo()
                            }
                        }
                        else -> {
//                                Snackbar.make(binding.root, "支付失败", Snackbar.LENGTH_LONG).show()
                            runOnUiThread {
                                Toast.makeText(this@VipActivity, "支付失败", Toast.LENGTH_LONG).show()
                            }
                        }
                    }
                })
            } catch (e: Exception) {
                Toast.makeText(this@VipActivity, e.message, Toast.LENGTH_LONG).show()
                Log.d("AliPayHelper","获取订单信息时发生错误: ${e.message}")
            }
        }
    }
    private fun showConfirmDialog() {
        val dialogView = LayoutInflater.from(this).inflate(R.layout.vip_protocol_dialog_custom, null)
        val dialog = AlertDialog.Builder(this)
            .setView(dialogView)
            .create()
        // 设置自定义背景
        dialog.window?.setBackgroundDrawableResource(R.drawable.dialog_background)
        // 获取按钮并设置点击事件
        val btnConfirm = dialogView.findViewById<Button>(R.id.btnConfirm)
        btnConfirm.setOnClickListener {
            // 将协议勾选上
            val protocol: CheckBox = findViewById(R.id.protocol_checkbox);
            protocol.isChecked = true
            doAlipay()
            dialog.dismiss()
        }
        // 显示对话框
        dialog.show()
    }
    private fun showConfirmDialog2() {
        // 创建标题 TextView 并居中
        val titleView = TextView(this).apply {
            text = "确认开通"
            textSize = 20f
            gravity = Gravity.CENTER
            setPadding(20, 20, 20, 20)
        }
        // 创建内容 TextView 并居中
        val messageView = TextView(this).apply {
            text = "请阅读并同意《会员协议》?"
            textSize = 16f
            gravity = Gravity.CENTER
            setPadding(40, 20, 40, 20)
        }
        AlertDialog.Builder(this)
            .setCustomTitle(titleView)  // 自定义标题
            .setView(messageView)  // 自定义内容
            .setPositiveButton("继续开通") { _, _ ->
                // 确认支付
                doAlipay()
            }
            .setNegativeButton("取消") { dialog, _ ->
                dialog.dismiss() // 关闭对话框
            }
            .show()
    }
    // 这里添加alipay_layout的点击事件
    private fun handleWechatClick() {
        // 绑定按钮
        val wechatAlipay: LinearLayout = findViewById(R.id.wechat_layout);
        wechatAlipay.setOnClickListener(View.OnClickListener {
            lifecycleScope.launch {
                try {
                    // 判断协议是否勾选
                    if (!isPaymentSelected){
                        Toast.makeText(this@VipActivity, "请勾选协议", Toast.LENGTH_LONG).show()
                        return@launch // 直接 return,不往下执行
                    }
                    Toast.makeText(this@VipActivity, "功能暂未开放", Toast.LENGTH_LONG).show()
                    return@launch
                } catch (e: Exception) {
                    Toast.makeText(this@VipActivity, e.message, Toast.LENGTH_LONG).show()
                    Log.d("AliPayHelper","获取订单信息时发生错误: ${e.message}")
                }
            }
        })
    }
    private fun vipCardInit() {
        val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
        recyclerView.layoutManager =
            LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
        val cardList = listOf(
            CardData.ContinueMonthly(
                "首月限时特惠",
                "连续包月",
                "首次开通",
                BigDecimal("0.01"),
                "次月起12元/月",
                "自动续费可随时取消",
            ),
            CardData.Yearly(
                "折合9元/月",
                "年卡",
                BigDecimal("0.01"),
                BigDecimal("168")
            ),
            CardData.SingleMonth(
                "1个月",
                BigDecimal("0.01")
            )
        )
        // 这里获取列表的第一项作为初始化的值
        // 获取第一项作为初始化的值
        try {
            val curentPriceTmp = cardList.firstOrNull()
            // 确保 currentPrice 不为空并获取价格
            currentPrice = when (curentPriceTmp) {
                is CardData.ContinueMonthly -> curentPriceTmp.price
                is CardData.Yearly -> curentPriceTmp.price
                is CardData.SingleMonth -> curentPriceTmp.price
                else -> BigDecimal.ZERO // 兜底处理,避免 null 时报错
            }
            val title = when (curentPriceTmp) {
                is CardData.ContinueMonthly -> curentPriceTmp.title
                is CardData.Yearly -> curentPriceTmp.title
                is CardData.SingleMonth -> curentPriceTmp.title
                else -> ""
            }
            originalPrice =  when (curentPriceTmp) {
                is CardData.ContinueMonthly -> BigDecimal.ZERO
                is CardData.Yearly -> curentPriceTmp.originalPrice
                is CardData.SingleMonth -> BigDecimal.ZERO
                else -> BigDecimal.ZERO
            }
            // 初始化支付
            orderName = title
            orderType = title
            val alipayAmount:TextView = findViewById(R.id.alipay_amount);
            val wechatAmount:TextView = findViewById(R.id.wechat_amount);
            val protocolDescLayout:TextView = findViewById(R.id.protocol_desc_layout);
            alipayAmount.text=currentPrice.toString()
            wechatAmount.text=currentPrice.toString()
            protocolDescLayout.text="自动续费可随时取消,开通后每月按${currentPrice.toString()}元自动续费,可随时取消自动续费"
        }catch (e: Exception) {
            Toast.makeText(this@VipActivity, e.message, Toast.LENGTH_LONG).show()
            Log.d("AliPayHelper","支付失败: ${e.message}")
        }
        val adapter = CardAdapter(cardList) { cardViewList, cardData, cardView ->
            handleVipCardClick(cardViewList, cardData, cardView)
        }
        recyclerView.adapter = adapter
    }
    private fun memberBenefitInit() {
        val container = findViewById<ConstraintLayout>(R.id.dynamicContainer)
        // 传递过来的数据列表
        val itemList = listOf(
            MemberBenefitItem(R.mipmap.vip_life_insurance, "自定义更多分类", true),
            MemberBenefitItem(R.mipmap.vip_ad_no, "自动去广告", false),
            MemberBenefitItem(R.mipmap.vip_ling, "公众号提醒待办", true),
            MemberBenefitItem(R.mipmap.vip_circle_pie, "数据统计", false)
        )
        var previousViewId = R.id.dynamicContainer
        for ((index, item) in itemList.withIndex()) {
            val iconView = ImageView(this).apply {
                id = View.generateViewId()
                setImageResource(item.iconRes)
                layoutParams = ConstraintLayout.LayoutParams(
                    TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30f, resources.displayMetrics).toInt(),
                    TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30f, resources.displayMetrics).toInt()
                ).apply {
                    // 设置 margin
                    setMargins(
                        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20f, resources.displayMetrics).toInt(), // left
                        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30f, resources.displayMetrics).toInt(), // top
                        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20f, resources.displayMetrics).toInt(), // right
                        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30f, resources.displayMetrics).toInt()  // bottom
                    )
                }
            }
            val textView = TextView(this).apply {
                id = View.generateViewId()
                text = item.text
                textSize = 16f
                setTextColor(Color.parseColor("#DFC08E"))
            }
            val checkView = ImageView(this).apply {
                id = View.generateViewId()
//                setImageResource(R.drawable.ic_check)
                setImageResource(R.mipmap.vip_right)
                layoutParams = ConstraintLayout.LayoutParams(
                    TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30f, resources.displayMetrics).toInt(),
                    TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30f, resources.displayMetrics).toInt()
                )
            }
            container.addView(iconView)
            container.addView(textView)
            container.addView(checkView)
            val constraintSet = ConstraintSet()
            constraintSet.clone(container)
            // Icon 位置
            constraintSet.connect(
                iconView.id,
                ConstraintSet.TOP,
                previousViewId,
                if (index == 0) ConstraintSet.TOP else ConstraintSet.BOTTOM,
                32
            )
            constraintSet.connect(
                iconView.id,
                ConstraintSet.START,
                ConstraintSet.PARENT_ID,
                ConstraintSet.START,
                32
            )
            // Text 位置
            constraintSet.connect(textView.id, ConstraintSet.TOP, iconView.id, ConstraintSet.TOP)
            constraintSet.connect(
                textView.id,
                ConstraintSet.START,
                iconView.id,
                ConstraintSet.END,
                32
            )
            // Check 位置
            constraintSet.connect(checkView.id, ConstraintSet.TOP, iconView.id, ConstraintSet.TOP)
            constraintSet.connect(
                checkView.id,
                ConstraintSet.END,
                ConstraintSet.PARENT_ID,
                ConstraintSet.END,
                32
            )
            constraintSet.applyTo(container)
            previousViewId = iconView.id
        }
    }
    /**
     * 会员卡样式点击事件
     */
    private fun handleVipCardClick(cardViewList: MutableList<CardView>, cardData: CardData, cardView: CardView) {
        // 处理点击事件,修改样式
        // 修改所有的边框色都是灰色
        cardViewList.forEach { card ->
            card.foreground = ContextCompat.getDrawable(this, R.drawable.gray_border_shape)  // 设置自定义背景
        }
        // 单独将点击的卡片边框颜色改为金色
//            cardView.setCardBackgroundColor(Color.parseColor("#FF0000")) // 举例:设置背景颜色
        cardView.foreground = ContextCompat.getDrawable(this, R.drawable.gold_border_shape)  // 设置自定义背景
        val title = when (cardData) {
            is CardData.ContinueMonthly -> cardData.title
            is CardData.Yearly -> cardData.title
            is CardData.SingleMonth -> cardData.title
        }
        currentPrice = when (cardData) {
            is CardData.ContinueMonthly -> cardData.price
            is CardData.Yearly -> cardData.price
            is CardData.SingleMonth -> cardData.price
        }
        originalPrice =  when (cardData) {
            is CardData.ContinueMonthly -> BigDecimal.ZERO
            is CardData.Yearly -> cardData.originalPrice
            is CardData.SingleMonth -> BigDecimal.ZERO
        }
        val alipayAmount:TextView = findViewById(R.id.alipay_amount);
        val wechatAmount:TextView = findViewById(R.id.wechat_amount);
        alipayAmount.text=currentPrice.toString()
        wechatAmount.text=currentPrice.toString()
        // 处理点击事件,这里我们只是简单地展示一个 Toast
//        Toast.makeText(this, "点击了: ${title}", Toast.LENGTH_SHORT).show()
        // 只有连续包月才展示
        val xieyiLayout:LinearLayout = findViewById(R.id.xieyi_layout);
        if (title == "连续包月") {
            xieyiLayout.visibility = View.VISIBLE
        }else{
            xieyiLayout.visibility = View.GONE
        }
        orderName = title
        orderType=title
    }
    private fun loadUserInfo() {
        // 获取Fragment实例
        val memberInfoCardFragment = supportFragmentManager.findFragmentById(R.id.memberInfoCardFragment) as MemberInfoCardFragment?
        lifecycleScope.launch {
            try {
                // 从本地获取保存的手机号
                val savedPhone = PreferencesManager.getPhone()
                if (savedPhone.isNullOrEmpty()) {
                    Toast.makeText(this@VipActivity, "用户未登录", Toast.LENGTH_SHORT).show()
                    return@launch
                }
                val response = RetrofitClient.apiService.getUserInfo(savedPhone)
                if (response.code == "0" && response.data != null) {
                    // 保存用户信息
                    currentUserInfo = response.data
                    val userInfo = response.data
                    // 调用ViewModel的刷新方法
                    memberInfoCardFragment?.viewModel?.updateMemberStatus(userInfo)
                }
            } catch (e: Exception) {
                e.printStackTrace()
                Toast.makeText(this@VipActivity, "获取用户信息失败", Toast.LENGTH_SHORT).show()
            }
        }
    }
    // VIP会员服务协议
    private fun handleVipProtocolClick(){
        binding.protocolVip.setOnClickListener{
            startContentActivity("VIP会员服务协议", "VIP会员服务协议")
        }
    }
    private fun startContentActivity(type: String, title: String) {
        val intent = Intent(this, ContentDetailActivity::class.java).apply {
            putExtra(ContentDetailActivity.ID, type)
            putExtra(ContentDetailActivity.EXTRA_TITLE, title)
        }
        startActivity(intent)
    }
}
app/src/main/java/com/example/firstapp/adapter/CardAdapter.kt
对比新文件
@@ -0,0 +1,126 @@
package com.example.firstapp.adapter
import android.graphics.Color
import android.graphics.Paint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.RecyclerView
import com.example.firstapp.R
import com.example.firstapp.model.CardData
class CardAdapter(private val cardList: List<CardData>,
                  private val itemClickListener: (MutableList<CardView>, CardData, CardView) -> Unit
    ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private val CONTINUE_MONTHLY_VIEW_TYPE = 0
    private val YEARLY_VIEW_TYPE = 1
    private val SINGLE_MONTH_VIEW_TYPE = 2
    private val cardViewList: MutableList<CardView> = mutableListOf()
    override fun getItemViewType(position: Int): Int {
        return when (cardList[position]) {
            is CardData.ContinueMonthly -> CONTINUE_MONTHLY_VIEW_TYPE
            is CardData.Yearly -> YEARLY_VIEW_TYPE
            is CardData.SingleMonth -> SINGLE_MONTH_VIEW_TYPE
        }
    }
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            CONTINUE_MONTHLY_VIEW_TYPE -> {
                val view = LayoutInflater.from(parent.context).inflate(R.layout.item_card_continue_monthly, parent, false)
                ContinueMonthlyViewHolder(view)
            }
            YEARLY_VIEW_TYPE -> {
                val view = LayoutInflater.from(parent.context).inflate(R.layout.item_card_yearly, parent, false)
                YearlyViewHolder(view)
            }
            SINGLE_MONTH_VIEW_TYPE -> {
                val view = LayoutInflater.from(parent.context).inflate(R.layout.item_card_single_month, parent, false)
                SingleMonthViewHolder(view)
            }
            else -> throw IllegalArgumentException("Unknown view type")
        }
    }
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is ContinueMonthlyViewHolder -> {
                val data = cardList[position] as CardData.ContinueMonthly
                holder.firstMonthTag.text = data.firstMonthTag
                holder.titleText.text = data.title
                holder.subTitleText.text = data.subTitle
                holder.priceText.text = data.price.toString()
                holder.nextPriceText.text = data.nextPrice
                holder.autoRenewText.text = data.autoRenew
                cardViewList.add(holder.cardView)
                holder.itemView.setOnClickListener { itemClickListener(cardViewList,data, holder.cardView) } // 设置点击事件
            }
            is YearlyViewHolder -> {
                val data = cardList[position] as CardData.Yearly
                holder.discountTag.text = data.discountTag
                holder.titleText.text = data.title
                holder.priceText.text = data.price.toString()
                holder.originalPriceText.text = data.originalPrice.toString()
                // 设置中划线
                val paint = holder.originalPriceText.paint
                paint.flags = paint.flags or Paint.STRIKE_THRU_TEXT_FLAG
                paint.color = Color.RED // 设置中划线的颜色
                cardViewList.add(holder.cardView)
                holder.itemView.setOnClickListener { itemClickListener(cardViewList,data, holder.cardView) } // 设置点击事件
            }
            is SingleMonthViewHolder -> {
                val data = cardList[position] as CardData.SingleMonth
                holder.titleText.text = data.title
                holder.priceText.text = data.price.toString()
                cardViewList.add(holder.cardView)
                holder.itemView.setOnClickListener { itemClickListener(cardViewList,data, holder.cardView) } // 设置点击事件
            }
        }
    }
    override fun getItemCount(): Int {
        return cardList.size
    }
    inner class ContinueMonthlyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val cardView: CardView = view.findViewById(R.id.vip_first_month_card_view)
        val firstMonthTag: TextView = view.findViewById(R.id.first_month_tag)
        val titleText: TextView = view.findViewById(R.id.title_text)
        val subTitleText: TextView = view.findViewById(R.id.sub_title_text)
        val priceText: TextView = view.findViewById(R.id.price_text)
        val nextPriceText: TextView = view.findViewById(R.id.next_price_text)
        val autoRenewText: TextView = view.findViewById(R.id.auto_renew_text)
    }
    inner class YearlyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val cardView: CardView = view.findViewById(R.id.vip_year_card_view)
        val discountTag: TextView = view.findViewById(R.id.discount_tag)
        val titleText: TextView = view.findViewById(R.id.yearly_title_text)
        val priceText: TextView = view.findViewById(R.id.yearly_price_text)
        val originalPriceText: TextView = view.findViewById(R.id.yearly_original_price_text)
    }
    inner class SingleMonthViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val cardView: CardView = view.findViewById(R.id.vip_month_card_view)
        val titleText: TextView = view.findViewById(R.id.single_month_title_text)
        val priceText: TextView = view.findViewById(R.id.single_month_price_text)
    }
}
app/src/main/java/com/example/firstapp/adapter/CategorySelectorAdapter.kt
对比新文件
@@ -0,0 +1,66 @@
package com.example.firstapp.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.firstapp.databinding.ItemCategorySelectorBinding
import com.example.firstapp.model.CategoryConfig
import java.util.Collections
/**
 * Adapter for the category selector recycler view.分类选择的适配器
 */
class CategorySelectorAdapter : RecyclerView.Adapter<CategorySelectorAdapter.ViewHolder>() {
    private var categories = mutableListOf<CategoryConfig>()
    private var lastCheckedPosition = -1 // 记录最后一个选中的位置
    class ViewHolder(val binding: ItemCategorySelectorBinding) : RecyclerView.ViewHolder(binding.root)
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemCategorySelectorBinding.inflate(
            LayoutInflater.from(parent.context), parent, false
        )
        return ViewHolder(binding)
    }
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val category = categories[position]
        holder.binding.apply {
            categoryName.text = category.name
            categoryCheckBox.isChecked = category.isEnabled
            // 如果只有一个选中的项,禁用其复选框
            categoryCheckBox.isEnabled = !(getEnabledCount() == 1 && category.isEnabled)
            categoryCheckBox.setOnCheckedChangeListener { _, isChecked ->
                if (isChecked || getEnabledCount() > 1) {
                    categories[position] = category.copy(isEnabled = isChecked)
                    notifyDataSetChanged() // 刷新所有项以更新禁用状态
                } else {
                    // 如果要取消选中且只有一个选中项,恢复选中状态
                    categoryCheckBox.isChecked = true
                }
            }
        }
    }
    private fun getEnabledCount(): Int {
        return categories.count { it.isEnabled }
    }
    override fun getItemCount() = categories.size
    fun moveItem(fromPosition: Int, toPosition: Int) {
        Collections.swap(categories, fromPosition, toPosition)
        notifyItemMoved(fromPosition, toPosition)
    }
    fun setCategories(newCategories: List<CategoryConfig>) {
        categories.clear()
        categories.addAll(newCategories)
        notifyDataSetChanged()
    }
    fun getCategories() = categories.toList()
}
app/src/main/java/com/example/firstapp/adapter/IncomeAdapter.kt
对比新文件
@@ -0,0 +1,111 @@
package com.example.firstapp.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.firstapp.databinding.ItemIncomeGroupBinding
import com.example.firstapp.databinding.ItemIncomePackageHomeBinding
import com.example.firstapp.model.IncomeGroup
import com.example.firstapp.model.IncomePackage
class IncomeAdapter : ListAdapter<IncomeGroup, IncomeAdapter.ViewHolder>(IncomeGroupDiffCallback()) {
    private var onPackageClickListener: (IncomeGroup, IncomePackage) -> Unit = { _, _ -> }
    fun setOnPackageClickListener(listener: (IncomeGroup, IncomePackage) -> Unit) {
        onPackageClickListener = listener
    }
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemIncomeGroupBinding.inflate(
            LayoutInflater.from(parent.context), parent, false
        )
        return ViewHolder(binding)
    }
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val group = getItem(position)
        holder.bind(group)
    }
    inner class ViewHolder(private val binding: ItemIncomeGroupBinding) :
        RecyclerView.ViewHolder(binding.root) {
        private val packagesAdapter = IncomePackageHomeAdapter { pack ->
            currentGroup?.let { group ->
                onPackageClickListener(group, pack)
            }
        }
        private var currentGroup: IncomeGroup? = null
        init {
            binding.rvPackages.apply {
                layoutManager = LinearLayoutManager(context)
                adapter = packagesAdapter
            }
        }
        fun bind(group: IncomeGroup) {
            currentGroup = group
            binding.tvStationName.text = group.stationName
            binding.tvPackageCount.text = "共${group.packages.size}笔收入"
            packagesAdapter.submitList(group.packages)
        }
    }
}
class IncomePackageHomeAdapter(private val onPackageClick: (IncomePackage) -> Unit) :
    ListAdapter<IncomePackage, IncomePackageHomeAdapter.ViewHolder>(IncomePackageDiffCallback()) {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemIncomePackageHomeBinding.inflate(
            LayoutInflater.from(parent.context), parent, false
        )
        return ViewHolder(binding)
    }
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val pack = getItem(position)
        holder.bind(pack)
    }
    inner class ViewHolder(private val binding: ItemIncomePackageHomeBinding) :
        RecyclerView.ViewHolder(binding.root) {
        init {
            binding.root.setOnClickListener {
                val pack = getItem(adapterPosition)
                onPackageClick(pack)
            }
        }
        fun bind(pack: IncomePackage) {
            binding.tvCompany.text = pack.company
            binding.tvCreateTime.text = pack.createTime
            binding.tvTrackingNumber.text = "¥${pack.trackingNumber}"
//            binding.tvBalance.text = "余额: ¥${pack.balance}"
        }
    }
}
private class IncomeGroupDiffCallback : DiffUtil.ItemCallback<IncomeGroup>() {
    override fun areItemsTheSame(oldItem: IncomeGroup, newItem: IncomeGroup): Boolean {
        return oldItem.stationName == newItem.stationName
    }
    override fun areContentsTheSame(oldItem: IncomeGroup, newItem: IncomeGroup): Boolean {
        return oldItem == newItem
    }
}
private class IncomePackageDiffCallback : DiffUtil.ItemCallback<IncomePackage>() {
    override fun areItemsTheSame(oldItem: IncomePackage, newItem: IncomePackage): Boolean {
        return oldItem.id == newItem.id
    }
    override fun areContentsTheSame(oldItem: IncomePackage, newItem: IncomePackage): Boolean {
        return oldItem == newItem
    }
}
app/src/main/java/com/example/firstapp/database/dao/CodeDao.kt
@@ -10,6 +10,7 @@
import com.example.firstapp.model.CourierStat
import com.example.firstapp.model.DailyStat
import com.example.firstapp.model.HeatmapStat
import com.example.firstapp.model.StationGroup
import io.reactivex.Completable
import kotlinx.coroutines.flow.Flow
@@ -54,8 +55,14 @@
    """)
    fun getByKeyword(keyword: String): List<Code>
    @Query("SELECT * FROM Code WHERE oneLevel = :content and code= :code and createTime = :dateString LIMIT 1")
    fun queryByTypeAndCodeAndDate(content: String, code: String, dateString: String): Code
    @Query("""
        SELECT * FROM Code
        WHERE category = :category
        AND code = :code
        AND substr(createTime, 1, 10) = substr(:dateString, 1, 10)
        LIMIT 1
    """)
    fun queryByTypeAndCodeAndDate(category: String, code: String, dateString: String): Code?
    @Query("update  Code set pickup = '1' ,  pickuptime = CURRENT_TIMESTAMP  where id=:id")
@@ -261,4 +268,21 @@
    WHERE strftime('%Y', createTime) = strftime('%Y', datetime(:date/1000, 'unixepoch', 'localtime'))
""")
    fun getCurrentYearStats(date: Long): Flow<List<DailyStat>>
    @Query("""
        SELECT oneLevel as stationName, COUNT(*) as count
        FROM Code
        WHERE category = :type AND pickup = '0'
        GROUP BY oneLevel
        ORDER BY createTime DESC
    """)
    fun getStationsByType(type: String): List<StationGroup>
    @Query("""
        SELECT * FROM Code
        WHERE category = :type AND pickup = '0'
        AND oneLevel = :stationName
        ORDER BY createTime DESC
    """)
    fun getPackagesByTypeAndStation(type: String, stationName: String): List<Code>
}
app/src/main/java/com/example/firstapp/database/repository/CodeRepository.kt
@@ -3,6 +3,7 @@
import androidx.annotation.WorkerThread
import com.example.firstapp.database.dao.CodeDao
import com.example.firstapp.database.entity.Code
import com.example.firstapp.model.StationGroup
import kotlinx.coroutines.flow.Flow
@@ -23,7 +24,8 @@
        return codeDao.getByKeyword(keyword)
    }
    fun queryByTypeAndCodeAndDate(content: String, code: String, dateString: String): Code {
    @WorkerThread
    fun queryByTypeAndCodeAndDate(content: String, code: String, dateString: String): Code? {
        return codeDao.queryByTypeAndCodeAndDate(content, code, dateString)
    }
@@ -75,4 +77,14 @@
    fun getCurrentYearStats(date: Long) = codeDao.getCurrentYearStats(date)
    @WorkerThread
    fun getStationsByType(type: String): List<StationGroup> {
        return codeDao.getStationsByType(type)
    }
    @WorkerThread
    fun getPackagesByTypeAndStation(type: String, stationName: String): List<Code> {
        return codeDao.getPackagesByTypeAndStation(type, stationName)
    }
}
app/src/main/java/com/example/firstapp/database/request/ProductOrdersRequest.kt
对比新文件
@@ -0,0 +1,11 @@
package com.example.firstapp.database.request
import java.math.BigDecimal
data class ProductOrdersRequest(
    val orderName: String,
    val orderType: String,
    val originalPrice: BigDecimal,
    val currentPrice: BigDecimal,
    val paymentMethod: String
)
app/src/main/java/com/example/firstapp/database/request/SmsLoginRequest.kt
对比新文件
@@ -0,0 +1,7 @@
package com.example.firstapp.database.request
data class SmsLoginRequest(
    val username: String,
    val smsCode: String,
    val userType: String,
)
app/src/main/java/com/example/firstapp/database/request/SmsSendRequest.kt
对比新文件
@@ -0,0 +1,6 @@
package com.example.firstapp.database.request
data class SmsSendRequest(
    val tel: String,
    val userType: String
)
app/src/main/java/com/example/firstapp/database/response/ContentResponse.kt
@@ -1,20 +1,13 @@
package com.example.firstapp.database.response
data class ContentResponse(
    val code: Int,
    val code: String,
    val msg: String,
    val data: ContentData?
)
data class ContentData(
    val id: Long,
    val type: String,
    val id: String,
    val title: String,
    val content: String,
    val version: String,
    val status: Int,
    val createTime: String,
    val updateTime: String,
    val creator: String?,
    val updater: String?
)
app/src/main/java/com/example/firstapp/database/response/OAuth2TokenResponse.kt
对比新文件
@@ -0,0 +1,17 @@
import com.google.gson.annotations.SerializedName
data class OAuth2AccessToken(
    @SerializedName("access_token")
    val value: String,          // token值
    @SerializedName("token_type")
    val tokenType: String,      // token类型
    @SerializedName("refresh_token")
    val refreshToken: String,   // 刷新token
    val scope: String          // 作用域
)
data class TokenResponse(
    val code: String,          // 注意这里改为 String 类型
    val msg: String,
    val data: OAuth2AccessToken?
)
app/src/main/java/com/example/firstapp/database/response/UserInfo.kt
@@ -6,7 +6,7 @@
    val name: String,
    val cover: String,
    val contactTel: String,
    val passTime: String,
    val overTime: String,
    val showed: Boolean
    val memberOvertime: String,
    val memberOverDate: String,
    val isMember: Boolean
)
app/src/main/java/com/example/firstapp/database/service/ApiService.kt
@@ -1,25 +1,33 @@
package com.example.firstapp.database.service
import TokenResponse
import com.example.firstapp.database.entity.ApiResponse
import com.example.firstapp.database.entity.KeywordConfig
import com.example.firstapp.database.request.ProductOrdersRequest
import com.example.firstapp.database.request.SmsLoginRequest
import com.example.firstapp.database.request.SmsSendRequest
import com.example.firstapp.database.response.AlipayOrderInfoResponse
import com.example.firstapp.database.response.ContentResponse
import com.example.firstapp.database.response.DictResponse
import com.example.firstapp.database.response.LoginResponse
import com.example.firstapp.database.response.SecurityResponse
import com.example.firstapp.database.response.SmsProcessResponse
import com.example.firstapp.database.response.UserInfo
import com.example.firstapp.model.CategoryConfig
import com.example.firstapp.model.CategoryConfigSync
import com.example.firstapp.network.AuthInterceptor
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.RequestBody
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Path
import retrofit2.http.Query
import retrofit2.http.Body
import java.util.concurrent.TimeUnit
/**
 * API调用接口
@@ -29,48 +37,67 @@
    @GET("keywords")
    suspend fun getKeywords():ApiResponse<List<KeywordConfig>>  //异步挂起
    @GET("cloudContent/getByType")
    suspend fun getContentByType(@Query("type") type: String): ContentResponse
    @GET("api/config/content/list/view")
    suspend fun getContentById(@Query("id") id: String): ContentResponse
    @GET("sysDict/getByDictCodeAndItemText")
    suspend fun getDictValue(@Query("dictCode") dictCode: String, @Query("itemText") itemText: String): DictResponse
    @POST("sms/send-code")
    suspend fun sendVerificationCode(@Query("phone") phone: String): LoginResponse
    // 发送短信验证码
    @POST("api/sms/send/code")
    suspend fun sendVerificationCode(@Body request: SmsSendRequest): LoginResponse
    @POST("sms/login")
    suspend fun verifyCode(@Query("phone") phone: String, @Query("code") code: String): LoginResponse
    // 验证短信验证码
    @POST("api/login/customer/phone/v2")
    suspend fun verifyCode(@Body request: SmsLoginRequest): TokenResponse
    @GET("config-security/enable-list-all")
    suspend fun getSecurityList(): SecurityResponse
    @GET("alipay/pay-order-info")
    suspend fun getPayOrderInfo(): AlipayOrderInfoResponse
    @POST("v2/alipay/pay-order-info")
    suspend fun getPayOrderInfo(@Body request: ProductOrdersRequest): AlipayOrderInfoResponse
    @GET("flower/api/supplier/info/{phone}")
    // 获取用户信息
    @GET("api/customer/info/{phone}")
    suspend fun getUserInfo(@Path("phone") phone: String): ApiResponse<UserInfo>
    @Multipart
    @POST("api/supplier/operation/update")
    @POST("flower/api/supplier/operation/update")
    suspend fun updateProfile(
        @Part("nickname") nickname: RequestBody,
        @Part avatar: MultipartBody.Part?
    ): ApiResponse<Unit>
    @POST("process-sms")
    suspend fun processSms(@Body body: Map<String, String>): SmsProcessResponse
    fun getUserCategories(currentUserId: String): List<CategoryConfig>
    fun saveUserCategories(categoryConfigSync: CategoryConfigSync)
}
// 创建Retrofit实例(单例)
object RetrofitClient{
//    private const val BASE_URL ="http://192.168.1.213:8888/jshERP-boot/"
    private const val BASE_URL ="http://192.168.1.213:5000/"
    private const val BASE_URL ="http://192.168.1.213:8080/flower/"
    // 创建OkHttpClient,配置拦截器和超时时间
    private val okHttpClient = OkHttpClient.Builder()
        .addInterceptor(AuthInterceptor())
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .writeTimeout(30, TimeUnit.SECONDS)
        .build()
    //添加Gson解析器,用于自动将JSON响应转换为Kotlin/Java对象
    private val retrofit = Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create()).build()
    private val retrofit = Retrofit
        .Builder()
        .client(okHttpClient)
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()
    //通过动态代理技术创建ApiService接口的具体实现类
    val apiService:ApiService = retrofit.create(ApiService::class.java)
}
}
app/src/main/java/com/example/firstapp/database/service/ModelService.kt
对比新文件
@@ -0,0 +1,33 @@
package com.example.firstapp.database.service
import com.example.firstapp.database.response.SmsProcessResponse
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.Body
import retrofit2.http.POST
/**
 * 模型相关接口
 */
interface ModelService {
    @POST("process-sms")
    suspend fun processSms(@Body body: Map<String, String>): SmsProcessResponse
}
// 创建Retrofit实例(单例)
object RetrofitModelClient {
    private const val BASE_URL = "http://192.168.1.213:5000/"
    //添加Gson解析器,用于自动将JSON响应转换为Kotlin/Java对象
    private val retrofit = Retrofit
        .Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()
    //通过动态代理技术创建ApiService接口的具体实现类
    val modelService: ModelService = retrofit.create(ModelService::class.java)
}
app/src/main/java/com/example/firstapp/model/CardData.kt
对比新文件
@@ -0,0 +1,26 @@
package com.example.firstapp.model
import java.math.BigDecimal
sealed class CardData {
    data class ContinueMonthly(
        val firstMonthTag: String,
        val title: String,
        val subTitle: String,
        val price: BigDecimal,
        val nextPrice: String,
        val autoRenew: String
    ) : CardData()
    data class Yearly(
        val discountTag: String,
        val title: String,
        val price: BigDecimal,
        val originalPrice: BigDecimal
    ) : CardData()
    data class SingleMonth(
        val title: String,
        val price: BigDecimal
    ) : CardData()
}
app/src/main/java/com/example/firstapp/model/CategoryConfig.kt
对比新文件
@@ -0,0 +1,11 @@
package com.example.firstapp.model
/**
 * Created by fanghaowei on 2025/03/27. 首页数据配置类
 */
data class CategoryConfig(
    val id: Int,
    val name: String,
    val order: Int,
    val isEnabled: Boolean = true
)
app/src/main/java/com/example/firstapp/model/CategoryConfigSync.kt
对比新文件
@@ -0,0 +1,6 @@
package com.example.firstapp.model
data class CategoryConfigSync(
    val userId: String,
    val categories: List<CategoryConfig>
)
app/src/main/java/com/example/firstapp/model/IncomeGroup.kt
对比新文件
@@ -0,0 +1,14 @@
package com.example.firstapp.model
data class IncomeGroup(
    val stationName: String,  // 银行名称
    val packages: List<IncomePackage>
)
data class IncomePackage(
    var id: Long,
    val company: String,    // 交易对方
    val trackingNumber: String,  // 金额
    val createTime: String,  // 交易时间
    val balance: String     // 账户余额
)
app/src/main/java/com/example/firstapp/model/MemberBenefitItem.kt
对比新文件
@@ -0,0 +1,5 @@
package com.example.firstapp.model
class MemberBenefitItem ( val iconRes: Int,
                          val text: String,
                          val isChecked: Boolean )
app/src/main/java/com/example/firstapp/model/MoreMemberBenefitItem.kt
对比新文件
@@ -0,0 +1,4 @@
package com.example.firstapp.model
class MoreMemberBenefitItem (val iconRes: Int,
                             val text: String)
app/src/main/java/com/example/firstapp/model/StationGroup.kt
对比新文件
@@ -0,0 +1,6 @@
package com.example.firstapp.model
data class StationGroup(
    val stationName: String,
    val count: Int
)
app/src/main/java/com/example/firstapp/network/AuthInterceptor.kt
对比新文件
@@ -0,0 +1,24 @@
package com.example.firstapp.network
import com.example.firstapp.utils.PreferencesManager
import okhttp3.Interceptor
import okhttp3.Response
class AuthInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val originalRequest = chain.request()
        // 获取token
        val token = PreferencesManager.getToken()
        // 如果token存在,添加到请求头
        return if (!token.isNullOrEmpty()) {
            val newRequest = originalRequest.newBuilder()
                .header("Authorization", "Bearer $token")
                .build()
            chain.proceed(newRequest)
        } else {
            chain.proceed(originalRequest)
        }
    }
}
app/src/main/java/com/example/firstapp/network/TokenExpiredInterceptor.kt
对比新文件
@@ -0,0 +1,19 @@
package com.example.firstapp.network
import com.example.firstapp.utils.PreferencesManager
import okhttp3.Interceptor
import okhttp3.Response
class TokenExpiredInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val response = chain.proceed(chain.request())
        // 如果返回401,说明token可能过期
        if (response.code == 401) {
            PreferencesManager.clearUserData() // 清除本地token
            // TODO: 处理token过期,例如跳转到登录页面
        }
        return response
    }
}
app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt
@@ -13,6 +13,7 @@
import com.example.firstapp.database.entity.Code
import com.example.firstapp.database.entity.Msg
import com.example.firstapp.database.service.RetrofitClient
import com.example.firstapp.database.service.RetrofitModelClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -58,7 +59,7 @@
                CoroutineScope(Dispatchers.IO).launch {
                    try {
                        val response =
                            RetrofitClient.apiService.processSms(mapOf("content" to messageBody.toString()))
                            RetrofitModelClient.modelService.processSms(mapOf("content" to messageBody.toString()))
                        if (response.status == "success") {
                            // 获取当前时间
app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt
@@ -21,9 +21,18 @@
import java.util.*
import java.text.SimpleDateFormat
import android.graphics.Color
import android.widget.Button
import android.widget.GridLayout
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
import com.example.firstapp.database.response.UserInfo
import com.example.firstapp.database.service.RetrofitClient
import com.example.firstapp.model.DailyStat
import com.example.firstapp.utils.PreferencesManager
import com.github.mikephil.charting.components.YAxis
import kotlinx.coroutines.launch
class DashboardFragment : Fragment() {
@@ -35,6 +44,9 @@
    private lateinit var barChart: BarChart
    private lateinit var pieChart: PieChart
    private lateinit var heatmapView: View
    private var currentUserInfo: UserInfo? = null // 确保使用你的实际数据类
    enum class DateType {
        DAY, WEEK, MONTH, YEAR
    }
@@ -49,6 +61,12 @@
        return binding.root
    }
    override fun onResume() {
        super.onResume()
        // 重新加载用户信息
        loadUserInfo()
    }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
@@ -61,7 +79,31 @@
        setupView(view)
        updateDateDisplay()
        loadPackages()
        // 遮罩层
        loadUserInfo()
        // 遮罩层点击时间
        binding.overlayContent.setOnClickListener {
            // 跳转到vipActivity
            val intent = android.content.Intent(requireContext(), com.example.firstapp.activity.VipActivity::class.java)
            startActivity(intent)
        }
    }
    private fun showOverlay() {
        binding.viewOverlay.visibility = View.VISIBLE
        binding.overlayContent.visibility = View.VISIBLE
    }
    private fun hiddleOverlay() {
        binding.viewOverlay.visibility = View.GONE
        binding.overlayContent.visibility = View.GONE
    }
    private fun setupRecyclerView() {
        binding.recyclerPackages.apply {
@@ -554,4 +596,35 @@
        super.onDestroyView()
        _binding = null
    }
    private fun loadUserInfo() {
        lifecycleScope.launch {
            try {
                // 从本地获取保存的手机号
                val savedPhone = PreferencesManager.getPhone()
                if (savedPhone.isNullOrEmpty()) {
                    Toast.makeText(context, "用户未登录", Toast.LENGTH_SHORT).show()
                    return@launch
                }
                val response = RetrofitClient.apiService.getUserInfo(savedPhone)
                if (response.code == "0" && response.data != null) {
                    // 保存用户信息
                    currentUserInfo = response.data
                    val userInfo = response.data
                    if(userInfo.isMember){
                        hiddleOverlay()
                    }else{
                        // 显示遮罩层
                        showOverlay()
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
                Toast.makeText(context, "获取用户信息失败", Toast.LENGTH_SHORT).show()
            }
        }
    }
}
app/src/main/java/com/example/firstapp/ui/home/HomeFragment.kt
@@ -8,29 +8,45 @@
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide
import com.example.firstapp.R
import com.example.firstapp.activity.ContentDetailActivity
import com.example.firstapp.activity.PickupActivity
import com.example.firstapp.activity.VipActivity
import com.example.firstapp.adapter.ExpressAdapter
import com.example.firstapp.adapter.FinanceAdapter
import com.example.firstapp.adapter.CategorySelectorAdapter
import com.example.firstapp.adapter.IncomeAdapter
import com.example.firstapp.database.service.RetrofitClient
import com.example.firstapp.databinding.FragmentHomeBinding
import com.example.firstapp.databinding.DialogCategorySelectorBinding
import com.example.firstapp.model.CategoryConfig
import com.example.firstapp.model.IncomeGroup
import com.example.firstapp.model.IncomePackage
import com.example.firstapp.utils.PreferencesManager
import com.google.android.material.bottomsheet.BottomSheetDialog
import kotlinx.coroutines.launch
import com.example.firstapp.view.UnderlineTextView
class HomeFragment : Fragment() {
    private var _binding: FragmentHomeBinding? = null
    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!
    private lateinit var homeViewModel: HomeViewModel
    private lateinit var expressAdapter: ExpressAdapter
    private lateinit var financeAdapter: FinanceAdapter
//    private lateinit var memorialAdapter: MemorialAdapter
    private lateinit var incomeAdapter: IncomeAdapter
    private lateinit var flightAdapter: FinanceAdapter
    private lateinit var trainAdapter: FinanceAdapter
    private lateinit var dataUpdateReceiver: BroadcastReceiver
    //onCreateView这个方法创建后被调用,通常是初始化视图组件和观察者
@@ -46,15 +62,37 @@
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //通过 ViewModelProvider 获取 HomeViewModel 的实例,以便在视图中使用。
        homeViewModel = ViewModelProvider(this).get(HomeViewModel::class.java)
        // 加载广告图片
        //loadAdvertisements()
        //调用这个方法来设置 RecyclerView用于设置 RecyclerView 的布局和适配器。
        setupRecyclerViews()
        val userId = "123456"
        homeViewModel.initialize(requireContext(), userId)
        // 检查是否是首次安装
        val isFirstInstall = PreferencesManager.isFirstInstall()
        if (isFirstInstall) {
            // 首次安装,设置默认显示快递和还款
            val defaultCategories = listOf(
                CategoryConfig(
                    id = 1,
                    name = "快递",
                    order = 1,
                    isEnabled = true
                ),
                CategoryConfig(
                    id = 2,
                    name = "还款",
                    order = 2,
                    isEnabled = true
                )
            )
            homeViewModel.saveCategories(defaultCategories)
            // 标记为非首次安装
            PreferencesManager.setFirstInstall(false)
        }
        setupAdapters()
        setupTabSwitching()
        //调用这个方法来观察 ViewModel 中的数据变化
        observeViewModelData()
        setupObservers()
        setupCategorySelector()
    }
    override fun onCreate(savedInstanceState: Bundle?) {
@@ -71,7 +109,7 @@
        }
    }
    private fun setupRecyclerViews() {
    private fun setupAdapters() {
        binding.expressRecycler.apply {
            layoutManager = LinearLayoutManager(context)
            expressAdapter = ExpressAdapter()
@@ -83,6 +121,7 @@
                val intent = Intent(requireContext(), PickupActivity::class.java).apply {
                    putExtra("station_name", group.stationName)
                    putExtra("company", pack.company)
                    putExtra("page_type", PickupActivity.TYPE_EXPRESS)
                }
                startActivity(intent)
            }
@@ -103,17 +142,47 @@
                val intent = Intent(requireContext(), PickupActivity::class.java).apply {
                    putExtra("station_name", group.stationName)
                    putExtra("company", pack.company)
                    putExtra("page_type", PickupActivity.TYPE_REPAYMENT)
                }
                startActivity(intent)
            }
        }
//
//        // 纪念日列表
//        binding.memorialRecycler.apply {
//            layoutManager = LinearLayoutManager(context)
//            memorialAdapter = MemorialAdapter()
//            adapter = memorialAdapter
//        }
        // 添加新的 RecyclerView
        binding.incomeRecycler.apply {
            layoutManager = LinearLayoutManager(context)
            incomeAdapter = IncomeAdapter()
            adapter = incomeAdapter
            // 设置初始状态 - 添加这行
            binding.incomeRecycler.visibility = View.GONE
            // 设置点击监听
            incomeAdapter.setOnPackageClickListener { group, pack ->
                // 跳转到取件页面
                val intent = Intent(requireContext(), PickupActivity::class.java).apply {
                    putExtra("station_name", group.stationName)
                    putExtra("company", pack.company)
                    putExtra("page_type", PickupActivity.TYPE_INCOME)
                }
                startActivity(intent)
            }
        }
        binding.flightRecycler.apply {
            layoutManager = LinearLayoutManager(context)
            flightAdapter = FinanceAdapter()
            adapter = flightAdapter
            visibility = View.GONE
        }
        binding.trainRecycler.apply {
            layoutManager = LinearLayoutManager(context)
            trainAdapter = FinanceAdapter()
            adapter = trainAdapter
            visibility = View.GONE
        }
    }
    private fun setupTabSwitching() {
@@ -121,51 +190,105 @@
            // 设置初始状态
            tabExpress.setTextColor(ContextCompat.getColor(requireContext(), R.color.tab_selected))
            tabFinance.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray))
            others.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray))
            // 快递标签点击事件
            tabExpress.setOnClickListener {
                hideAllRecyclers()
                expressRecycler.visibility = View.VISIBLE
                financeRecycler.visibility = View.GONE
                tabExpress.setTextColor(ContextCompat.getColor(requireContext(), R.color.tab_selected))
                tabFinance.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray))
                others.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray))
                tabExpress.textSize = 16f
                tabFinance.textSize = 14f
                others.textSize = 14f
                updateTabStyles(tabExpress)
                homeViewModel.loadExpressData()
            }
            // 财务标签点击事件
            tabFinance.setOnClickListener {
                expressRecycler.visibility = View.GONE
                financeRecycler.visibility = View.VISIBLE
                tabExpress.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray))
                tabFinance.setTextColor(ContextCompat.getColor(requireContext(), R.color.tab_selected))
                others.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray))
                tabExpress.textSize = 14f
                tabFinance.textSize = 16f
                others.textSize = 14f
                // 在切换到财务标签时加载数据 - 添加这行
            // 还款标签点击事件 - 非会员也可以使用
            tabFinance.setOnClickListener {
                hideAllRecyclers()
                financeRecycler.visibility = View.VISIBLE
                updateTabStyles(tabFinance)
                homeViewModel.loadFinanceData()
            }
            // 其他标签点击事件需要检查会员状态
            val memberOnlyTabs = mapOf(
                tabIncome to { homeViewModel.loadIncomeData() },
                tabFlight to { homeViewModel.loadFlightData() },
                tabTrain to { homeViewModel.loadTrainData() }
            )
            // 其他标签点击事件
            others.setOnClickListener {
                expressRecycler.visibility = View.GONE
                financeRecycler.visibility = View.GONE
                tabExpress.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray))
                tabFinance.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray))
                others.setTextColor(ContextCompat.getColor(requireContext(), R.color.tab_selected))
                tabExpress.textSize = 14f
                tabFinance.textSize = 14f
                others.textSize = 16f
            memberOnlyTabs.forEach { (tab, loadAction) ->
                tab.setOnClickListener {
                    checkMembershipAndExecute(tab) {
                        hideAllRecyclers()
                        when (tab) {
                            tabIncome -> incomeRecycler.visibility = View.VISIBLE
                            tabFlight -> flightRecycler.visibility = View.VISIBLE
                            tabTrain -> trainRecycler.visibility = View.VISIBLE
                        }
                        updateTabStyles(tab)
                        loadAction()
                    }
                }
            }
        }
    }
    //这个方法用于观察 homeViewModel 中的 expressItems 数据。
    private fun observeViewModelData() {
    private fun checkMembershipAndExecute(tab: TextView, action: () -> Unit) {
        // 从本地获取保存的手机号
        val savedPhone = PreferencesManager.getPhone()
        if (savedPhone.isNullOrEmpty()) {
            Toast.makeText(requireContext(), "请先登录", Toast.LENGTH_SHORT).show()
            return
        }
        // 使用协程检查会员状态
        lifecycleScope.launch {
            try {
                val response = RetrofitClient.apiService.getUserInfo(savedPhone)
                if (response.code == "0" && response.data != null) {
                    if (response.data.isMember) {
                        action()
                    } else {
                        Toast.makeText(requireContext(), "该功能仅对会员开放", Toast.LENGTH_SHORT).show()
                        // 切回快递标签
                        binding.tabExpress.performClick()
                    }
                } else {
                    Toast.makeText(requireContext(), "获取用户信息失败", Toast.LENGTH_SHORT).show()
                    binding.tabExpress.performClick()
                }
            } catch (e: Exception) {
                e.printStackTrace()
                Toast.makeText(requireContext(), "网络错误,请稍后重试", Toast.LENGTH_SHORT).show()
                binding.tabExpress.performClick()
            }
        }
    }
    private fun hideAllRecyclers() {
        binding.apply {
            expressRecycler.visibility = View.GONE
            financeRecycler.visibility = View.GONE
            incomeRecycler.visibility = View.GONE
            flightRecycler.visibility = View.GONE
            trainRecycler.visibility = View.GONE
        }
    }
    private fun updateTabStyles(selectedTab: UnderlineTextView) {
        binding.apply {
            val tabs = listOf(tabExpress, tabFinance, tabIncome, tabFlight, tabTrain)
            tabs.forEach { tab ->
                // 设置文字颜色为黑色或灰色
                tab.setTextColor(ContextCompat.getColor(requireContext(),
                    if (tab == selectedTab) android.R.color.black else R.color.gray))
                // 设置文字大小
                tab.textSize = if (tab == selectedTab) 16f else 14f
                // 设置下划线
                tab.setUnderlineVisible(tab == selectedTab)
            }
        }
    }
    private fun setupObservers() {
        //当 expressItems 数据发生变化时,更新 RecyclerView 的数据。
        homeViewModel.expressItems.observe(viewLifecycleOwner) { items ->
            //将新的数据列表提交给适配器,以更新 RecyclerView 的显示内容。
@@ -175,10 +298,83 @@
        homeViewModel.financeItems.observe(viewLifecycleOwner) { items ->
            financeAdapter.submitList(items)
        }
//
//        homeViewModel.memorialItems.observe(viewLifecycleOwner) { items ->
//            memorialAdapter.submitList(items)
//        }
        // 观察收入数据变化
        homeViewModel.incomeItems.observe(viewLifecycleOwner) { items ->
            incomeAdapter.submitList(items)
        }
        homeViewModel.flightItems.observe(viewLifecycleOwner) { items ->
            flightAdapter.submitList(items)
        }
        homeViewModel.trainItems.observe(viewLifecycleOwner) { items ->
            trainAdapter.submitList(items)
        }
        // 观察可见分类的变化
        homeViewModel.visibleCategories.observe(viewLifecycleOwner) { categories: List<CategoryConfig> ->
            binding.apply {
                // 隐藏所有标签
                tabExpress.visibility = View.GONE
                tabFinance.visibility = View.GONE
                tabIncome.visibility = View.GONE
                tabFlight.visibility = View.GONE
                tabTrain.visibility = View.GONE
                // 获取用户信息判断是否是会员
                val savedPhone = PreferencesManager.getPhone()
                lifecycleScope.launch {
                    try {
                        val response = RetrofitClient.apiService.getUserInfo(savedPhone ?: "")
                        val isMember = response.code == "0" && response.data?.isMember == true
                        if (!isMember) {
                            // 非会员只显示快递和还款
                            tabExpress.visibility = View.VISIBLE
                            tabFinance.visibility = View.VISIBLE
                            if (categories.firstOrNull()?.name == "快递") {
                                tabExpress.performClick()
                            } else {
                                tabFinance.performClick()
                            }
                        } else {
                            // 会员显示所有选中的分类
                            categories.forEach { category ->
                                when (category.name) {
                                    "快递" -> {
                                        tabExpress.visibility = if (category.isEnabled) View.VISIBLE else View.GONE
                                        if (categories.indexOf(category) == 0 && category.isEnabled) tabExpress.performClick()
                                    }
                                    "还款" -> {
                                        tabFinance.visibility = if (category.isEnabled) View.VISIBLE else View.GONE
                                        if (categories.indexOf(category) == 0 && category.isEnabled) tabFinance.performClick()
                                    }
                                    "收入" -> {
                                        tabIncome.visibility = if (category.isEnabled) View.VISIBLE else View.GONE
                                        if (categories.indexOf(category) == 0 && category.isEnabled) tabIncome.performClick()
                                    }
                                    "航班" -> {
                                        tabFlight.visibility = if (category.isEnabled) View.VISIBLE else View.GONE
                                        if (categories.indexOf(category) == 0 && category.isEnabled) tabFlight.performClick()
                                    }
                                    "火车票" -> {
                                        tabTrain.visibility = if (category.isEnabled) View.VISIBLE else View.GONE
                                        if (categories.indexOf(category) == 0 && category.isEnabled) tabTrain.performClick()
                                    }
                                }
                            }
                        }
                    } catch (e: Exception) {
                        e.printStackTrace()
                        // 发生错误时默认显示快递和还款
                        tabExpress.visibility = View.VISIBLE
                        tabFinance.visibility = View.VISIBLE
                        tabExpress.performClick()
                    }
                }
            }
        }
    }
    override fun onResume() {
@@ -220,4 +416,85 @@
            .load("http://192.168.1.235:9999/advertisement/down.png")
            .into(binding.bottomAdBanner)
    }
    // 设置分类选择器 检查会员状态
    private fun setupCategorySelector() {
        binding.categoryButton.setOnClickListener {
            // 从本地获取保存的手机号
            val savedPhone = PreferencesManager.getPhone()
            if (savedPhone.isNullOrEmpty()) {
                Toast.makeText(requireContext(), "请先登录", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }
            // 使用协程检查会员状态
            lifecycleScope.launch {
                try {
                    val response = RetrofitClient.apiService.getUserInfo(savedPhone)
                    if (response.code == "0" && response.data != null) {
                        if (response.data.isMember) {
                            showCategorySelectorDialog()
                        } else {
                            // 非会员跳转到VIP开通页面
                            val intent = Intent(requireContext(), VipActivity::class.java)
                            startActivity(intent)
                        }
                    } else {
                        Toast.makeText(requireContext(), "获取用户信息失败", Toast.LENGTH_SHORT).show()
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                    Toast.makeText(requireContext(), "网络错误,请稍后重试", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
    private fun showCategorySelectorDialog() {
        val dialog = BottomSheetDialog(requireContext())
        val dialogBinding = DialogCategorySelectorBinding.inflate(layoutInflater)
        dialog.setContentView(dialogBinding.root)
        val adapter = CategorySelectorAdapter()
        dialogBinding.categoryRecyclerView.apply {
            layoutManager = LinearLayoutManager(context)
            this.adapter = adapter
        }
        // 加载所有分类
        homeViewModel.categories.observe(viewLifecycleOwner) { categories ->
            // 如果是会员,显示所有分类供选择
            lifecycleScope.launch {
                try {
                    val savedPhone = PreferencesManager.getPhone()
                    val response = RetrofitClient.apiService.getUserInfo(savedPhone ?: "")
                    val isMember = response.code == "0" && response.data?.isMember == true
                    if (isMember) {
                        // 会员可以看到所有分类
                        adapter.setCategories(categories)
                    } else {
                        // 非会员只能看到快递和还款
                        val limitedCategories = categories.filter {
                            it.name == "快递" || it.name == "还款"
                        }
                        adapter.setCategories(limitedCategories)
                    }
                } catch (e: Exception) {
                    // 发生错误时只显示基础分类
                    val limitedCategories = categories.filter {
                        it.name == "快递" || it.name == "还款"
                    }
                    adapter.setCategories(limitedCategories)
                }
            }
        }
        dialogBinding.saveButton.setOnClickListener {
            homeViewModel.saveCategories(adapter.getCategories())
            dialog.dismiss()
        }
        dialog.show()
    }
}
app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt
@@ -1,23 +1,47 @@
package com.example.firstapp.ui.home
import android.content.Context
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.firstapp.core.Core
import com.example.firstapp.database.entity.Code
import com.example.firstapp.database.service.RetrofitClient
import com.example.firstapp.model.CategoryConfig
import com.example.firstapp.model.CategoryConfigSync
import com.example.firstapp.model.ExpressGroup
import com.example.firstapp.model.ExpressPackage
import com.example.firstapp.model.FinanceGroup
import com.example.firstapp.model.FinancePackage
import com.example.firstapp.model.IncomeGroup
import com.example.firstapp.model.IncomePackage
import com.example.firstapp.util.SecureStorage
import com.example.firstapp.utils.PreferencesManager
import kotlinx.coroutines.launch
class HomeViewModel : ViewModel() {
    private val _expressItems = MutableLiveData<List<ExpressGroup>>()
    private val _financeItems = MutableLiveData<List<FinanceGroup>>()
    private val _incomeItems = MutableLiveData<List<IncomeGroup>>()
    private val _flightItems = MutableLiveData<List<FinanceGroup>>()
    private val _trainItems = MutableLiveData<List<FinanceGroup>>()
    val expressItems: LiveData<List<ExpressGroup>> = _expressItems
    val financeItems: LiveData<List<FinanceGroup>> = _financeItems
    val incomeItems: LiveData<List<IncomeGroup>> = _incomeItems
    val flightItems: LiveData<List<FinanceGroup>> = _flightItems
    val trainItems: LiveData<List<FinanceGroup>> = _trainItems
    private val _categories = MutableLiveData<List<CategoryConfig>>()
    val categories: LiveData<List<CategoryConfig>> = _categories
    private val _visibleCategories = MutableLiveData<List<CategoryConfig>>()
    val visibleCategories: LiveData<List<CategoryConfig>> = _visibleCategories
    private lateinit var secureStorage: SecureStorage
    private lateinit var currentUserId: String
    init {
        // 初始化时加载包裹列表数据
@@ -26,52 +50,194 @@
        //  loadFinanceData()
    }
    fun loadExpressData() {
    fun initialize(context: Context, userId: String) {
        secureStorage = SecureStorage(context)
        currentUserId = userId
        loadCategories()
        // 初始化时更新可见分类
        _categories.value?.let { updateVisibleCategories(it) }
    }
    private fun loadDataByType(type: String) {
        viewModelScope.launch {
            // 1. 获取所有驿站类型的提醒设置
            val stations = Core.reminder.getByType("快递")
            // 2. 按驿站分组获取包裹信息
            val groups = stations.map { station ->
                val packages = Core.code.getByKeyword(station.nickname).map { code ->
                    ExpressPackage(
                        id = code.id, //ID
                        company = code.secondLevel, //快递公司
                        trackingNumber = code.code, // 取件码
                        createTime = code.createTime  //快递时间
                    )
            try {
                // 获取该类型下的所有站点分组
                val stations = Core.code.getStationsByType(type)
                when (type) {
                    "快递" -> {
                        // 处理快递类型
                        val groups = stations.map { station ->
                            val packages = Core.code.getPackagesByTypeAndStation(type, station.stationName).map { code ->
                                ExpressPackage(
                                    id = code.id,
                                    company = code.secondLevel,
                                    trackingNumber = code.code,
                                    createTime = code.createTime
                                )
                            }
                            ExpressGroup(stationName = station.stationName, packages = packages)
                        }
                        _expressItems.postValue(groups)
                    }
                    "收入" -> {
                        // 特殊处理收入类型
                        val groups = stations.map { station ->
                            val packages = Core.code.getPackagesByTypeAndStation(type, station.stationName).map { code ->
                                IncomePackage(
                                    id = code.id,
                                    company = code.secondLevel,  // 交易对方
                                    trackingNumber = code.code,  // 金额
                                    createTime = code.createTime,
                                    balance = code.remarks.replace("余额", "") // 去掉"余额"前缀
                                )
                            }
                            IncomeGroup(stationName = station.stationName, packages = packages)
                        }
                        _incomeItems.postValue(groups)
                    }
                    else -> {
                        // 处理其他类型(还款、航班、火车票)
                        val groups = stations.map { station ->
                            val packages = Core.code.getPackagesByTypeAndStation(type, station.stationName).map { code ->
                                FinancePackage(
                                    id = code.id,
                                    company = code.secondLevel,
                                    trackingNumber = code.code,
                                    createTime = code.createTime
                                )
                            }
                            FinanceGroup(stationName = station.stationName, packages = packages)
                        }
                        // 根据类型更新对应的 LiveData
                        when (type) {
                            "还款" -> _financeItems.postValue(groups)
                            "航班" -> _flightItems.postValue(groups)
                            "火车票" -> _trainItems.postValue(groups)
                        }
                    }
                }
                ExpressGroup(
                    stationName = station.nickname, packages = packages
                )
            } catch (e: Exception) {
                Log.e("HomeViewModel", "Failed to load $type data: ${e.message}")
            }
            _expressItems.postValue(groups)
        }
    }
    fun loadExpressData() {
        loadDataByType("快递")
    }
    fun loadFinanceData() {
        loadDataByType("还款")
    }
    fun loadIncomeData() {
        loadDataByType("收入")
    }
    fun loadFlightData() {
        loadDataByType("航班")
    }
    fun loadTrainData() {
        loadDataByType("火车票")
    }
    private fun loadCategories() {
        viewModelScope.launch {
            // 1. 获取所有驿站类型的提醒设置
            val stations = Core.reminder.getByType("还款")
            // 2. 按驿站分组获取包裹信息
            val groups = stations.map { station ->
                val packages = Core.code.getByKeyword(station.nickname).map { code ->
                    FinancePackage(
                        id = code.id, //ID
                        company = code.secondLevel, //快递公司
                        trackingNumber = code.code, // 取件码
                        createTime = code.createTime  //快递时间
                    )
                }
                FinanceGroup(
                    stationName = station.nickname, packages = packages
            try {
                // 先尝试从本地获取配置
                val localCategories = secureStorage.getCategories(currentUserId)
                // 默认完整分类列表
                val fullCategories = listOf(
                    CategoryConfig(1, "快递", 0, true),
                    CategoryConfig(2, "还款", 1, true),
                    CategoryConfig(3, "收入", 2, true),
                    CategoryConfig(4, "航班", 3, true),
                    CategoryConfig(5, "火车票", 4, true)
                )
            }
                // 基础分类(非会员可见)
                val basicCategories = listOf(
                    CategoryConfig(1, "快递", 0, true),
                    CategoryConfig(2, "还款", 1, true)
                )
            _financeItems.postValue(groups)
                if (localCategories.isNotEmpty()) {
                    // 如果本地有配置,直接使用本地配置
                    _categories.value = localCategories
                } else {
                    try {
                        // 尝试从服务器获取用户信息判断是否是会员
                        val savedPhone = PreferencesManager.getPhone()
                        val response = RetrofitClient.apiService.getUserInfo(savedPhone ?: "")
                        val isMember = response.code == "0" && response.data?.isMember == true
                        // 根据会员状态设置默认分类
                        val defaultCategories = if (isMember) fullCategories else basicCategories
                        _categories.value = defaultCategories
                        secureStorage.saveCategories(currentUserId, defaultCategories)
                        // 同步到服务器
                        try {
                            syncCategoriesToServer(defaultCategories)
                        } catch (e: Exception) {
                            Log.e("HomeViewModel", "Failed to sync categories: ${e.message}")
                        }
                    } catch (e: Exception) {
                        // 如果获取用户信息失败,使用基础分类
                        _categories.value = basicCategories
                        secureStorage.saveCategories(currentUserId, basicCategories)
                    }
                }
                // 更新可见分类
                _categories.value?.let { updateVisibleCategories(it) }
            } catch (e: Exception) {
                Log.e("HomeViewModel", "Failed to load categories: ${e.message}")
            }
        }
    }
    private fun syncCategoriesToServer(categories: List<CategoryConfig>) {
        viewModelScope.launch {
            try {
                RetrofitClient.apiService.saveUserCategories(
                    CategoryConfigSync(currentUserId, categories)
                )
            } catch (e: Exception) {
                // 同步失败,可以稍后重试或者显示提示
                Log.e("CategorySync", "Failed to sync categories: ${e.message}")
            }
        }
    }
    fun saveCategories(categories: List<CategoryConfig>) {
        _categories.value = categories
        // 保存到本地存储
        secureStorage.saveCategories(currentUserId, categories)
        // 同步到服务器
        syncCategoriesToServer(categories)
        // 更新可见分类
        updateVisibleCategories(categories)
    }
    private fun updateVisibleCategories(categories: List<CategoryConfig>) {
        val visibleNames = categories
            .filter { it.isEnabled }
            .sortedBy { it.order }
            .map { it.name }
        _visibleCategories.value = categories.filter { it.isEnabled }
    }
    // 登出时不再清除本地数据
    fun logout() {
        // 只清除内存中的数据
        _categories.value = emptyList()
    }
}
app/src/main/java/com/example/firstapp/ui/login/LoginViewModel.kt
@@ -5,8 +5,12 @@
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import androidx.lifecycle.ViewModel
import com.example.firstapp.database.request.SmsLoginRequest
import com.example.firstapp.database.request.SmsSendRequest
import com.example.firstapp.database.service.RetrofitClient
import com.example.firstapp.utils.Log
import com.example.firstapp.ui.home.HomeViewModel
import com.example.firstapp.utils.PreferencesManager
class LoginViewModel : ViewModel() {
@@ -19,16 +23,26 @@
    private val _isLoading = MutableLiveData<Boolean>()
    val isLoading: LiveData<Boolean> = _isLoading
    private lateinit var homeViewModel: HomeViewModel
    fun sendVerificationCode(phone: String) {
        viewModelScope.launch {
            _isLoading.value = true
            try {
//                val response = RetrofitClient.apiService.sendVerificationCode(phone)
//                if (response.code == 200) {
                // 创建 SmsSendRequest 对象
                val request = SmsSendRequest(
                    tel = phone,
                    userType = "customer"
                )
                //Retrofit 进行网络请求时,类名不需要完全一致,只要保证类的属性名称和类型与后端 DTO 对象的属性一致即可。
                //Retrofit + Gson 在序列化时会将对象转换为 JSON,后端 Spring 框架会将 JSON 反序列化为 SmsSendDTO 对象
                //HTTP 请求实际传输的是 JSON 格式的数据,而不是 Java/Kotlin 对象。
                val response = RetrofitClient.apiService.sendVerificationCode(request)
                if (response.code == 0) {
                    _loginMessage.value = "验证码已发送"
//                } else {
//                    _loginMessage.value = response.msg.ifEmpty { "发送验证码失败" }
//                }
                } else {
                    _loginMessage.value = response.msg.ifEmpty { "发送验证码失败" }
                }
            } catch (e: Exception) {
                Log.e("LoginError", "Login failed: ${e.message}", e)
                _loginMessage.value = "网络错误,请稍后重试"
@@ -42,12 +56,20 @@
        viewModelScope.launch {
            _isLoading.value = true
            try {
//                val response = RetrofitClient.apiService.verifyCode(phone, code)
//                if (response.code == 200 && response.data) {
                val request = SmsLoginRequest(
                    username = phone,
                    smsCode = code,
                    userType = "customer"
                )
                //HttpServletRequest request这是后端 Spring 框架中的一个特殊参数,
                //用于获取 HTTP 请求的相关信息(如请求头、Cookie 等),它会由 Spring 框架自动注入,不需要客户端显式传递。
                val response = RetrofitClient.apiService.verifyCode(request)
                if (response.code == "0" && response.data != null) {
                    saveToken(response.data.value,phone)  // 这里获取的是 access_token
                    _loginState.value = true
//                } else {
//                    _loginMessage.value = response.msg.ifEmpty { "登录失败" }
//                }
                } else {
                    _loginMessage.value = response.msg.ifEmpty { "登录失败" }
                }
            } catch (e: Exception) {
                Log.e("LoginError", "Login failed: ${e.message}", e)
                _loginMessage.value = "网络错误,请稍后重试"
@@ -57,4 +79,20 @@
        }
    }
    fun logout() {
        viewModelScope.launch {
            // 不再清除用户数据,只执行登出操作
            homeViewModel.logout()
            // 其他登出操作...
        }
    }
    private fun saveToken(token: String,phone:String) {
        // TODO: 实现token存储逻辑
        // 可能还需要存储 refresh_token
        PreferencesManager.saveToken(token)
        // 保存登录的手机号
        PreferencesManager.savePhone(phone)
    }
}
app/src/main/java/com/example/firstapp/ui/notifications/NotificationsFragment.kt
@@ -31,9 +31,10 @@
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.launch
import com.bumptech.glide.Glide
import com.example.firstapp.MainActivity
import com.example.firstapp.activity.VipActivity
import com.example.firstapp.database.response.UserInfo
import com.example.firstapp.ui.invitation.InvitationActivity
import com.example.firstapp.utils.PreferencesManager
class NotificationsFragment : Fragment() {
@@ -131,39 +132,6 @@
    private fun setupClickListeners() {
        // 支付插件
//        binding.payPlugin.setOnClickListener {
//            // 跳转到支付插件页面
//            lifecycleScope.launch {
//                try {
//                    val response = RetrofitClient.apiService.getPayOrderInfo()
//                    var orderInfo=response.data
//                    Log.d("AliPayHelper","获取订单信息时: ${response}")
//                    // 这里调用支付宝
//                    PayAbility.aliPay(requireActivity(), orderInfo, Observer {
//                        when (it.resultStatus) {
//                            "9000" -> {
////                                Snackbar.make(binding.root, "支付成功", Snackbar.LENGTH_LONG).show()
//                                requireActivity().runOnUiThread {
//                                    Toast.makeText(requireContext(), "支付成功", Toast.LENGTH_LONG).show()
//                                }
//                            }
//                            else -> {
////                                Snackbar.make(binding.root, "支付失败", Snackbar.LENGTH_LONG).show()
//                                requireActivity().runOnUiThread {
//                                    Toast.makeText(requireContext(), "支付失败", Toast.LENGTH_LONG).show()
//                                }
//
//                            }
//                        }
//                    })
//
//                } catch (e: Exception) {
//                    Log.d("AliPayHelper","获取订单信息时发生错误: ${e.message}")
//                }
//            }
//        }
        // 设置提醒
        binding.layoutReminder.setOnClickListener {
@@ -213,12 +181,12 @@
        // 隐私协议
        binding.layoutPrivacy.setOnClickListener {
            startContentActivity("privacy_policy", "隐私协议")
            startContentActivity("隐私协议", "隐私协议")
        }
        // 使用教程
        binding.layoutTutorial.setOnClickListener {
            startContentActivity("user_guide", "使用教程")
            startContentActivity("使用教程", "使用教程")
        }
        // 头像点击老的处理逻辑
@@ -236,8 +204,20 @@
        // VIP续费
        binding.btnRenew.setOnClickListener {
            Toast.makeText(context, "VIP续费功能开发中", Toast.LENGTH_SHORT).show()
//            Toast.makeText(context, "VIP续费功能开发中", Toast.LENGTH_SHORT).show()
            // 跳转到vipActivity
            val intent = Intent(requireContext(), VipActivity::class.java)
            startActivity(intent)
        }
        binding.cardVip.setOnClickListener {
//            Toast.makeText(context, "VIP续费功能开发中", Toast.LENGTH_SHORT).show()
            // 跳转到vipActivity
            val intent = Intent(requireContext(), VipActivity::class.java)
            startActivity(intent)
        }
    }
    private fun showEmailDialog() {
@@ -292,9 +272,9 @@
        }
    }
    private fun startContentActivity(type: String, title: String) {
    private fun startContentActivity(id: String, title: String) {
        val intent = Intent(requireContext(), ContentDetailActivity::class.java).apply {
            putExtra(ContentDetailActivity.EXTRA_CONTENT_TYPE, type)
            putExtra(ContentDetailActivity.ID, id)
            putExtra(ContentDetailActivity.EXTRA_TITLE, title)
        }
        startActivity(intent)
@@ -302,7 +282,14 @@
    private suspend fun loadUserInfo() {
        try {
            val response = RetrofitClient.apiService.getUserInfo("17586582287")
            // 从本地获取保存的手机号
            val savedPhone = PreferencesManager.getPhone()
            if (savedPhone.isNullOrEmpty()) {
                Toast.makeText(context, "用户未登录", Toast.LENGTH_SHORT).show()
                return
            }
            val response = RetrofitClient.apiService.getUserInfo(savedPhone)
            if (response.code == "0" && response.data != null) {
                // 保存用户信息
                currentUserInfo = response.data
@@ -319,15 +306,16 @@
                binding.tvUserId.text = "个人账号:${userInfo.contactTel}"
                // 设置VIP信息
                if (userInfo.showed) {
                if (userInfo.isMember) {
                    binding.ivVip.visibility = View.VISIBLE
                    binding.cardVip.visibility = View.VISIBLE
                    binding.tvVipExpire.text = "${userInfo.overTime} 到期"
                    binding.tvVipExpire.text = "${userInfo.memberOverDate} 到期"
                } else {
                    //非会员信息
                    binding.ivVip.visibility = View.GONE
                    binding.cardVip.visibility = View.VISIBLE
                    binding.btnRenew.text = "立即开通"
                    binding.linearVipContainer.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.gray))
                    binding.cardVip.visibility = View.GONE
                }
            }
        } catch (e: Exception) {
app/src/main/java/com/example/firstapp/ui/vip/MemberInfoCardFragment.kt
对比新文件
@@ -0,0 +1,162 @@
package com.example.firstapp.ui.vip
import android.graphics.Color
import android.graphics.drawable.Drawable
import androidx.fragment.app.viewModels
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.load.DataSource
import com.example.firstapp.R
import com.example.firstapp.databinding.FragmentMemberInfoCardBinding
import kotlinx.coroutines.launch
class MemberInfoCardFragment : Fragment() {
    companion object {
        fun newInstance() = MemberInfoCardFragment()
    }
     val viewModel: MemberInfoCardViewModel by viewModels()
    // 绑定变量
    private var _binding: FragmentMemberInfoCardBinding? = null
    private val binding get() = _binding!!
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // 使用 DataBindingUtil.inflate 来绑定布局
        _binding = FragmentMemberInfoCardBinding.inflate(inflater, container, false)
        binding.viewModel = viewModel
        binding.lifecycleOwner = this
        // 绑定头像
        Glide.with(this)
            .load(viewModel.cover)
            .transform(RoundedCorners(100))  // 设置圆角
//            .error(R.drawable.error_placeholder) // 如果加载失败,显示占位图
            .error(R.mipmap.avatar_default)
            .listener(object : RequestListener<Drawable> {
                override fun onResourceReady(
                    resource: Drawable?,
                    model: Any?,
                    target: Target<Drawable>?,  // 正确使用 Target 作为泛型参数
                    dataSource: DataSource?,  // 导入正确的 DataSource
                    isFirstResource: Boolean
                ): Boolean {
                    Log.d("Glide", "Image loaded successfully")
                    return false
                }
                override fun onLoadFailed(
                    e: GlideException?,
                    model: Any?,
                    target: Target<Drawable>?,  // 正确使用 Target 作为泛型参数
                    isFirstResource: Boolean
                ): Boolean {
                    Log.e("Glide", "Image load failed", e)
                    return false
                }
            })
            .into(binding.memberAvatarView)
        // 绑定会员徽章
        Glide.with(this)
            .load(viewModel.vipCover)
            .transform(RoundedCorners(100))  // 设置圆角
//            .error(R.drawable.error_placeholder) // 如果加载失败,显示占位图
            .error(R.mipmap.vip_no)
            .listener(object : RequestListener<Drawable> {
                override fun onResourceReady(
                    resource: Drawable?,
                    model: Any?,
                    target: Target<Drawable>?,  // 正确使用 Target 作为泛型参数
                    dataSource: DataSource?,  // 导入正确的 DataSource
                    isFirstResource: Boolean
                ): Boolean {
                    Log.d("Glide", "Image loaded successfully")
                    return false
                }
                override fun onLoadFailed(
                    e: GlideException?,
                    model: Any?,
                    target: Target<Drawable>?,  // 正确使用 Target 作为泛型参数
                    isFirstResource: Boolean
                ): Boolean {
                    Log.e("Glide", "Image load failed", e)
                    return false
                }
            })
            .into(binding.memberImageView)
        // 监听 cover 变化
        viewLifecycleOwner.lifecycleScope.launch {
            viewModel.cover.collect { coverUrl ->
                Glide.with(this@MemberInfoCardFragment)
                    .load(coverUrl)
                    .transform(RoundedCorners(100))
                    .error(R.drawable.error_placeholder)
                    .into(binding.memberAvatarView)
            }
        }
        // 监听 vipCover 变化
        viewLifecycleOwner.lifecycleScope.launch {
            viewModel.vipCover.collect { vipCoverUrl ->
                Glide.with(this@MemberInfoCardFragment)
                    .load(vipCoverUrl)
                    .transform(RoundedCorners(100))
                    .error(R.drawable.error_placeholder)
                    .into(binding.memberImageView)
            }
        }
        // 监听 background 变化
        viewLifecycleOwner.lifecycleScope.launch {
            viewModel.background.collect { backgroundColor ->
//                binding.memberCardCardView.setBackgroundColor(Color.parseColor(backgroundColor))
                binding.memberCardCardView.setBackgroundResource(backgroundColor)
            }
        }
        viewLifecycleOwner.lifecycleScope.launch {
            viewModel.color.collect { color ->
                binding.phoneNumberText.setTextColor(Color.parseColor(color))
                binding.memberStatus.setTextColor(Color.parseColor(color))
            }
        }
        return binding.root
    }
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}
app/src/main/java/com/example/firstapp/ui/vip/MemberInfoCardViewModel.kt
对比新文件
@@ -0,0 +1,92 @@
package com.example.firstapp.ui.vip
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.firstapp.R
import com.example.firstapp.database.response.UserInfo
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class MemberInfoCardViewModel : ViewModel() {
    // 联系方式
    private val _phoneNumber = MutableStateFlow("18099999999")
    val phoneNumber: StateFlow<String> = _phoneNumber
    // 开通状态
    private val _memberStatus = MutableStateFlow("未开通")
    val memberStatus: StateFlow<String> = _memberStatus
    // 头像
    private val _cover = MutableStateFlow("http://192.168.1.201:9000/sms/avatar/avatar_default.png")
    val cover: StateFlow<String> = _cover
    // vip头像
    private val _vipCover = MutableStateFlow("")
    val vipCover: StateFlow<String> = _vipCover
    // 背景色
    private val _background = MutableStateFlow(R.drawable.dialog_background)
    val background: StateFlow<Int> = _background
    // 背景色
    private val _color = MutableStateFlow("#E8EAEE")
    val color: StateFlow<String> = _color
    // 更新数据的方法
    fun updatePhoneNumber(phoneNumber: String) {
        viewModelScope.launch {
            _phoneNumber.value = phoneNumber
        }
    }
    fun updateMemberStatus(userInfo: UserInfo) {
        viewModelScope.launch {
            // 电话号码
            _phoneNumber.value=userInfo.contactTel
            // 开通状态
            if(userInfo.isMember){
                // 判断userInfo.memberOvertime是否为空,如果不为空,则截取字符串到年月日
//                if(userInfo.memberOvertime.isNotEmpty()){
//                    _memberStatus.value = userInfo.memberOvertime.substring(0,10)+" 到期"
//                }
                _memberStatus.value = " 到期"
                userInfo.memberOvertime?.let {
                    if (it.isNotEmpty()) {
                        _memberStatus.value = it.substring(0, 10) + " 到期"
                    }
                }
                // vip头像
                _vipCover.value ="http://192.168.1.201:9000/sms/member/vip.png"
                _background.value = R.drawable.vip_black_background
                _color.value ="#A9A9AB"
            }else{
                _memberStatus.value = "未开通"
                _vipCover.value ="http://192.168.1.201:9000/sms/member/vip_no.png"
                _background.value =R.drawable.vip_white_background
                _color.value ="#A4A4A4"
            }
            // 头像
            // 如果头像不为空的话,则更新头像
//            if(userInfo.cover.isNotEmpty()){
//                _cover.value = userInfo.cover
//            }
            userInfo.cover?.let {
                if (it.isNotEmpty()) {
                    _cover.value = it
                }
            }
        }
    }
}
app/src/main/java/com/example/firstapp/util/CategoryDragCallback.kt
对比新文件
@@ -0,0 +1,25 @@
package com.example.firstapp.util
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.example.firstapp.adapter.CategorySelectorAdapter
/**
 * 拖拽回调
 */
class CategoryDragCallback(private val adapter: CategorySelectorAdapter) : ItemTouchHelper.Callback() {
    override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
        val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
        return makeMovementFlags(dragFlags, 0)
    }
    override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
        adapter.moveItem(viewHolder.adapterPosition, target.adapterPosition)
        return true
    }
    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        // 不需要实现
    }
}
app/src/main/java/com/example/firstapp/util/SecureStorage.kt
对比新文件
@@ -0,0 +1,49 @@
package com.example.firstapp.util
import android.content.Context
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import com.google.gson.Gson
import com.example.firstapp.model.CategoryConfig
class SecureStorage(context: Context) {
    private val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
    private val sharedPreferences = EncryptedSharedPreferences.create(
        "secure_prefs",
        masterKeyAlias,
        context,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )
    private val gson = Gson()
    private fun getStorageKey(userId: String): String {
        return "categories_$userId"
    }
    fun saveCategories(userId: String, categories: List<CategoryConfig>) {
        val json = gson.toJson(categories)
        sharedPreferences.edit().putString(getStorageKey(userId), json).apply()
    }
    fun getCategories(userId: String): List<CategoryConfig> {
        val json = sharedPreferences.getString(getStorageKey(userId), null)
        return if (json != null) {
            gson.fromJson(json, Array<CategoryConfig>::class.java).toList()
        } else {
            emptyList()
        }
    }
    // 清除指定用户的数据
    fun clearUserData(userId: String) {
        sharedPreferences.edit().remove(getStorageKey(userId)).apply()
    }
    // 清除所有数据
    fun clearAllData() {
        sharedPreferences.edit().clear().apply()
    }
}
app/src/main/java/com/example/firstapp/utils/PreferencesManager.kt
对比新文件
@@ -0,0 +1,58 @@
package com.example.firstapp.utils
import android.content.Context
import android.content.SharedPreferences
object PreferencesManager {
    private const val PREF_NAME = "app_preferences"
    private const val KEY_TOKEN = "user_token"
    private const val KEY_PHONE = "user_phone"
    private const val KEY_FIRST_INSTALL = "first_install"
    private const val LAST_LOGIN_PHONE = "last_login_phone"
    private lateinit var preferences: SharedPreferences
    fun init(context: Context) {
        preferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
    }
    fun saveToken(token: String) {
        preferences.edit().putString(KEY_TOKEN, token).apply()
    }
    fun getToken(): String? {
        return preferences.getString(KEY_TOKEN, null)
    }
    fun savePhone(phone: String) {
        preferences.edit().putString(KEY_PHONE, phone).apply()
    }
    fun getPhone(): String? {
        return preferences.getString(KEY_PHONE, null)
    }
    fun clearUserData() {
        preferences.edit().apply {
            remove(KEY_TOKEN)
            remove(KEY_PHONE)
            apply()
        }
    }
    fun isFirstInstall(): Boolean {
        return preferences.getBoolean(KEY_FIRST_INSTALL, true)
    }
    fun setFirstInstall(isFirst: Boolean) {
        preferences.edit().putBoolean(KEY_FIRST_INSTALL, isFirst).apply()
    }
    fun saveLastLoginPhone(phone: String) {
        preferences.edit().putString(LAST_LOGIN_PHONE, phone).apply()
    }
    fun getLastLoginPhone(): String {
        return preferences.getString(LAST_LOGIN_PHONE, "") ?: ""
    }
}
app/src/main/java/com/example/firstapp/view/UnderlineTextView.kt
对比新文件
@@ -0,0 +1,43 @@
package com.example.firstapp.view
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.content.ContextCompat
class UnderlineTextView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {
    private val underlinePaint = Paint().apply {
        color = ContextCompat.getColor(context, android.R.color.black)
        strokeWidth = context.resources.displayMetrics.density * 2 // 2dp
    }
    private var showUnderline = false
    fun setUnderlineVisible(visible: Boolean) {
        showUnderline = visible
        invalidate()
    }
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (showUnderline) {
            val textWidth = paint.measureText(text.toString())
            val startX = (width - textWidth) / 2
            canvas.drawLine(
                startX,
                height - underlinePaint.strokeWidth,
                startX + textWidth,
                height - underlinePaint.strokeWidth,
                underlinePaint
            )
        }
    }
}
app/src/main/res/drawable/alipay_shape.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="#D5EBFF"/>
    <corners android:radius="30dp"/>
</shape>
app/src/main/res/drawable/black_button_background.xml
对比新文件
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:radius="5dp" />
    <solid android:color="#000000" />
    <padding android:left="10dp" android:right="10dp" android:top="5dp" android:bottom="5dp"/>
</shape>
app/src/main/res/drawable/checkbox_round.xml
对比新文件
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@android:color/transparent" /> <!-- 设置圆形的背景颜色 -->
    <corners android:radius="300dp" /> <!-- 设置圆角半径为50%来形成圆形 -->
    <stroke
        android:color="#6B6A70" android:width="2dp" />     <!-- 设置边框宽度 -->
</shape>
app/src/main/res/drawable/checkbox_round_selected.xml
对比新文件
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#FF6200EE" /> <!-- 设置圆形的背景颜色 -->
    <corners android:radius="300dp"  /> <!-- 设置圆角半径为50%来形成圆形 -->
    <stroke
        android:color="#6B6A70" android:width="2dp" />     <!-- 设置边框宽度 -->
</shape>
app/src/main/res/drawable/dialog_background.xml
对比新文件
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 圆角 10dp -->
    <corners android:radius="10dp" />
    <!-- 背景色:黑色 -->
    <solid android:color="#FFFFFF" />
    <!-- 内边距 -->
    <padding android:left="10dp"
        android:top="10dp"
        android:right="10dp"
        android:bottom="10dp"/>
    <stroke android:width="0dp" android:color="#FFFFFF"/>
</shape>
app/src/main/res/drawable/error_placeholder.xml
对比新文件
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="100dp"
    android:height="100dp"
    android:viewportWidth="24"
    android:viewportHeight="24">
    <path
        android:fillColor="#FF6666"
        android:pathData="M12,2L2,22h20L12,2zM13,18h-2v-2h2v2zm0,-4h-2v-4h2v4z"/>
</vector>
app/src/main/res/drawable/gold_border_shape.xml
对比新文件
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="@android:color/transparent"/> <!-- 背景透明 -->
    <stroke android:width="2dp" android:color="#F9EBC6"/> <!-- 金色边框 -->
    <corners android:radius="16dp"/> <!-- 圆角大小 -->
</shape>
app/src/main/res/drawable/gray_border_shape.xml
对比新文件
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="@android:color/transparent"/> <!-- 背景透明 -->
    <stroke android:width="1dp" android:color="#FFFFFF"/> <!-- 金色边框 -->
    <corners android:radius="16dp"/> <!-- 圆角大小 -->
</shape>
app/src/main/res/drawable/home_add.xml
对比新文件
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="48dp"
    android:height="48dp"
    android:viewportWidth="1024"
    android:viewportHeight="1024">
  <path
      android:fillColor="#FF000000"
      android:pathData="M991.7,915.6V90.9c0,-36.9 -29.9,-66.8 -66.8,-66.8H99.1c-36.9,0 -66.8,29.9 -66.8,66.8v824.7c0,36.9 29.9,66.8 66.8,66.8h825.7c36.9,0 66.8,-29.9 66.8,-66.8zM822.8,503.2c0,30.9 -25.1,56 -56,56H568v198.9c0,30.9 -25.1,56 -56,56s-56,-25.1 -56,-56V559.2H257.2c-30.9,0 -56,-25.1 -56,-56s25.1,-56 56,-56h198.9V248.4c0,-30.9 25.1,-56 56,-56s56,25.1 56,56v198.9h198.9c30.9,0 56,25.1 56,56z"/>
</vector>
app/src/main/res/drawable/ic_category.xml
对比新文件
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24">
    <path
        android:fillColor="#00BFFF"
        android:pathData="M10,6L8.59,7.41 13.17,12 8.59,16.59 10,18l6,-6z"/>
</vector>
app/src/main/res/drawable/ic_check.xml
对比新文件
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24">
    <path
        android:fillColor="#8A5C37"
        android:strokeWidth="2"
        android:strokeColor="#8A5C37"
        android:pathData="M10.29,15.29 4,9 5.41,7.59 10.29,12.47 19.59,3.17 21,4.59 10.29,15.29"/>
</vector>
app/src/main/res/drawable/ic_reminder.xml
对比新文件
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24">
    <path
        android:fillColor="#FFD700"
        android:pathData="M12,1a11,11 0 0,1 0,22a11,11 0 0,1 0,-22zM12,18a2,2 0 0,1 0,4h-2v-4h2zm0,-12a3,3 0 0,0 -3,3v5h6V9a3,3 0 0,0 -3,-3z"/>
</vector>
app/src/main/res/drawable/ic_stats.xml
对比新文件
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24">
    <path
        android:fillColor="#FF5733"
        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.42,0 -8 -3.58 -8 -8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z M16,6h-4v10h4V6z"/>
</vector>
app/src/main/res/drawable/member_background.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">
    <gradient
        android:startColor="#2B225C"
        android:endColor="#726C8E"
        android:angle="90"/>
</shape>
app/src/main/res/drawable/more_benefit_shape.xml
对比新文件
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <gradient
        android:angle="90"
        android:startColor="#130F27"
        android:endColor="#15102D"
        android:type="linear"/>
    <corners android:radius="30dp"/>
</shape>
app/src/main/res/drawable/overlay_black_background.xml
对比新文件
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 圆角 10dp -->
    <corners android:radius="100dp" />
    <!-- 背景色:黑色 -->
<!--    <solid android:color="#FFFFFF" />-->
    <solid android:color="#000000" />
    <!-- 内边距 -->
    <padding android:left="10dp"
        android:top="10dp"
        android:right="10dp"
        android:bottom="10dp"/>
    <stroke android:width="0dp" android:color="#FFFFFF"/>
</shape>
app/src/main/res/drawable/round_red_bg.xml
对比新文件
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#FD6478"/> <!-- 设置背景颜色 -->
    <corners android:radius="100dp"/> <!-- 设置圆角半径 -->
</shape>
app/src/main/res/drawable/round_transition_gray_bg.xml
对比新文件
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 设置透明背景 -->
    <solid android:color="@android:color/transparent"/>
    <!-- 设置灰色边框 -->
    <stroke android:color="#808080" android:width="1dp"/> <!-- 可以调整宽度 -->
    <!-- 设置圆角 -->
    <corners android:radius="16dp"/> <!-- 设置圆角半径 -->
</shape>
app/src/main/res/drawable/set.xml
@@ -1,12 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="217.6dp"
    android:height="200dp"
    android:viewportWidth="1114"
    android:width="48dp"
    android:height="48dp"
    android:viewportWidth="1024"
    android:viewportHeight="1024">
  <path
      android:pathData="M1081,394.7l-162.5,-278.6c-41.8,-72 -118.4,-116.1 -199.7,-116.1L393.7,-0c-81.3,0 -160.2,44.1 -199.7,116.1l-162.5,278.6c-41.8,72 -41.8,162.5 0,234.5l162.5,278.6c41.8,72 118.4,116.1 199.7,116.1h325.1c81.3,0 160.2,-44.1 199.7,-116.1l162.5,-278.6c41.8,-74.3 41.8,-162.5 0,-234.5zM1002,580.5l-162.5,278.6c-25.5,41.8 -72,69.7 -120.7,69.7L393.7,928.8c-48.8,0 -95.2,-25.5 -120.7,-69.7l-162.5,-278.6c-25.5,-44.1 -25.5,-97.5 0,-139.3l162.5,-278.6c25.5,-41.8 72,-69.7 120.7,-69.7h325.1c48.8,0 95.2,25.5 120.7,69.7l162.5,278.6c25.5,41.8 25.5,97.5 0,139.3z"
      android:fillColor="#707070"/>
  <path
      android:pathData="M556.2,301.9c-116.1,0 -209,92.9 -209,209s92.9,209 209,209 209,-92.9 209,-209 -92.9,-209 -209,-209zM556.2,626.9c-65,0 -116.1,-51.1 -116.1,-116.1s51.1,-116.1 116.1,-116.1 116.1,51.1 116.1,116.1 -51.1,116.1 -116.1,116.1z"
      android:fillColor="#707070"/>
      android:fillColor="#FF000000"
      android:pathData="M737,647c-5.6,-5.6 -11.3,-11.3 -16.9,-11.3 -16.9,0 -28.1,11.3 -28.1,28.1 0,11.3 5.6,16.9 11.3,22.5C770.8,737 815.8,815.8 821.4,905.8h50.6c-5.6,-106.9 -56.3,-196.9 -135,-258.8M523.3,551.4c-101.3,0 -185.6,-84.4 -185.6,-185.6C332,258.9 416.4,174.5 523.3,174.5 624.5,174.5 708.9,258.9 708.9,360.1c0,106.9 -84.4,191.3 -185.6,191.3zM765.1,365.8C765.1,230.8 658.3,118.3 517.6,118.3c-129.4,0 -241.9,106.9 -241.9,247.5 0,90 50.6,168.8 123.8,213.8 -140.6,39.4 -241.9,168.8 -247.5,326.3H202.6c5.6,-163.1 140.6,-292.5 303.8,-298.1H523.3c135,0 241.9,-106.9 241.9,-241.9z"/>
</vector>
app/src/main/res/drawable/tab_selected_background.xml
对比新文件
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:gravity="bottom">
        <shape android:shape="rectangle">
            <size android:height="2dp" />
            <solid android:color="@android:color/black" />
        </shape>
    </item>
</layer-list>
app/src/main/res/drawable/up.png
app/src/main/res/drawable/up_back.jpg

app/src/main/res/drawable/vip_black_background.xml
对比新文件
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 圆角 10dp -->
    <corners android:radius="10dp" />
    <!-- 背景色:黑色 -->
<!--    <solid android:color="#FFFFFF" />-->
    <solid android:color="#000000" />
    <!-- 内边距 -->
    <padding android:left="10dp"
        android:top="10dp"
        android:right="10dp"
        android:bottom="10dp"/>
    <stroke android:width="0dp" android:color="#FFFFFF"/>
</shape>
app/src/main/res/drawable/vip_white_background.xml
对比新文件
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 圆角 10dp -->
    <corners android:radius="10dp" />
    <!-- 背景色:白色 -->
    <solid android:color="#FFFFFF" />
    <!-- 内边距 -->
    <padding android:left="10dp"
        android:top="10dp"
        android:right="10dp"
        android:bottom="10dp"/>
    <stroke android:width="0dp" android:color="#FFFFFF"/>
</shape>
app/src/main/res/drawable/wechat_shape.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="#E7F8E7"/>
    <corners android:radius="30dp"/>
</shape>
app/src/main/res/layout/activity_phone_login.xml
@@ -43,7 +43,6 @@
            android:layout_height="wrap_content"
            android:background="@null"
            android:hint="请输入手机号"
            android:text="17712345678"
            android:inputType="phone"
            android:maxLength="11"
            android:textSize="16sp"
app/src/main/res/layout/activity_vip.xml
对比新文件
@@ -0,0 +1,518 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
    tools:context=".activity.VipActivity">
    <androidx.core.widget.NestedScrollView 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"
        android:fillViewport="true"
        tools:context=".activity.VipActivity">
        <androidx.coordinatorlayout.widget.CoordinatorLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fitsSystemWindows="true"
            android:background="@drawable/member_background">
            <!-- 这里是原来CoordinatorLayout中的内容,保持不变 -->
            <com.google.android.material.appbar.AppBarLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:fitsSystemWindows="true"
                android:background="@android:color/transparent">
                <com.google.android.material.appbar.MaterialToolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize">
                    <!-- 直接在Toolbar中添加TextView -->
                    <ImageView
                        android:id="@+id/ivBack"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="left"
                        android:layout_marginStart="16dp"
                        android:src="@drawable/ic_back" />
                    <TextView
                        android:id="@+id/tvTitle"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="智信会员"
                        android:textColor="#FFFFFF"
                        android:textSize="18sp"
                        android:layout_gravity="left"
                        android:layout_marginEnd="16dp" />
                </com.google.android.material.appbar.MaterialToolbar>
            </com.google.android.material.appbar.AppBarLayout>
            <!-- 引入MemberInfoCardFragment -->
            <fragment
                android:name="com.example.firstapp.ui.vip.MemberInfoCardFragment"
                android:id="@+id/memberInfoCardFragment"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:layout_behavior="@string/appbar_scrolling_view_behavior" />
            <!-- 滑动框 -->
            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="165dp"
                android:layout_marginLeft="15dp"
                app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                android:background="@android:color/transparent" />
            <!--        会员权益&#45;&#45;动态-->
            <androidx.constraintlayout.widget.ConstraintLayout
                xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                android:id="@+id/benefit_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="300dp"
                android:layout_marginLeft="15dp"
                android:layout_marginRight="15dp"
                android:padding="0dp"
                android:background="@android:color/transparent"
                app:cardBackgroundColor="@android:color/transparent"
                android:foreground="@drawable/gray_border_shape"
                tools:context=".MainActivity">
                <!-- 标题 -->
                <TextView
                    android:id="@+id/title_text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="会员权益"
                    android:textColor="#8A5C37"
                    android:textSize="20sp"
                    android:textStyle="bold"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintHorizontal_bias="0.5"
                    android:layout_marginTop="5dp" />
                <!-- 分割线 -->
                <View
                    android:id="@+id/divider"
                    android:layout_width="0dp"
                    android:layout_height="1dp"
                    android:background="#615B7F"
                    app:layout_constraintTop_toBottomOf="@id/title_text"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    android:layout_marginTop="8dp"
                    android:layout_marginHorizontal="16dp" />
                <androidx.constraintlayout.widget.ConstraintLayout
                    xmlns:android="http://schemas.android.com/apk/res/android"
                    xmlns:app="http://schemas.android.com/apk/res-auto"
                    android:id="@+id/dynamicContainer"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="10dp"
                    android:layout_marginTop="0dp"
                    android:background="@android:color/transparent"
                    app:layout_constraintTop_toBottomOf="@id/divider"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    />
            </androidx.constraintlayout.widget.ConstraintLayout>
            <!--        更多权益-->
            <LinearLayout
                android:id="@+id/more_benefit_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@drawable/more_benefit_shape"
                android:layout_marginTop="550dp"
                android:layout_marginLeft="13dp"
                android:layout_marginRight="13dp"
                android:padding="15dp"
                android:orientation="vertical"
                android:gravity="center">
                <TextView
                    android:id="@+id/more_benefit_text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="更多权益敬请期待"
                    android:textColor="#D5D4DA"
                    android:textStyle="italic|bold"
                    android:textSize="16sp"
                    android:layout_marginBottom="10dp"
                    android:gravity="center"
                    android:layout_gravity="center"/>
                <!-- 第一项 -->
                <LinearLayout
                    android:layout_width="200dp"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    android:gravity="start"
                    android:baselineAligned="false"
                    android:layout_marginBottom="8dp"
                    android:padding="5dp"
                    >
                    <ImageView
                        android:layout_width="30dp"
                        android:layout_height="30dp"
                        android:src="@mipmap/vip_ai_ass_icon"
                        android:layout_marginEnd="8dp"/>
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="解锁AI语音助手"
                        android:textColor="#93B7D9"
                        android:textSize="16sp"
                        android:gravity="start"/>
                </LinearLayout>
                <!-- 第二项 -->
                <LinearLayout
                    android:layout_width="200dp"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    android:gravity="start"
                    android:baselineAligned="false"
                    android:layout_marginBottom="8dp"
                    android:padding="5dp"
                    >
                    <ImageView
                        android:layout_width="30dp"
                        android:layout_height="30dp"
                        android:src="@mipmap/vip_bar_sta"
                        android:layout_marginEnd="8dp"
                        />
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="数据智能预测"
                        android:textColor="#93B7D9"
                        android:textSize="16sp"
                        android:gravity="start"/>
                </LinearLayout>
                <!-- 第三项 -->
                <LinearLayout
                    android:layout_width="200dp"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    android:gravity="start"
                    android:baselineAligned="false"
                    android:layout_marginBottom="8dp"
                    android:padding="5dp"
                    >
                    <ImageView
                        android:layout_width="30dp"
                        android:layout_height="30dp"
                        android:src="@mipmap/vip_life_insurance"
                        android:layout_marginEnd="8dp"/>
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="家庭分类共享"
                        android:textColor="#93B7D9"
                        android:textSize="16sp"
                        android:gravity="start"/>
                </LinearLayout>
                <LinearLayout
                    android:layout_width="160dp"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    android:gravity="start"
                    android:baselineAligned="false"
                    android:layout_marginBottom="8dp"
                    android:padding="5dp"
                    >
                    <ImageView
                        android:layout_width="25dp"
                        android:layout_height="25dp"
                        android:src="@mipmap/vip_more"
                        android:layout_marginEnd="8dp"/>
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="敬请期待......"
                        android:textColor="#D6D5DB"
                        android:textSize="16sp"
                        android:gravity="start"/>
                </LinearLayout>
            </LinearLayout>
            <View
                android:id="@+id/divider2"
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#646174"
                app:layout_constraintTop_toBottomOf="@id/more_benefit_layout"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                android:layout_marginTop="840dp"
                android:layout_marginHorizontal="0dp" />
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="180dp"
                android:layout_marginTop="880dp"
                >
            </LinearLayout>
        </androidx.coordinatorlayout.widget.CoordinatorLayout>
    </androidx.core.widget.NestedScrollView>
    <androidx.cardview.widget.CardView
        android:id="@+id/card_view_fixed"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="0dp"
        android:layout_marginTop="10dp"
        android:layout_gravity="bottom"
        app:cardCornerRadius="0dp"
        app:cardElevation="6dp"
        android:background="#030308"
        app:cardBackgroundColor="#030308"
        android:layout_alignParentBottom="true">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="10dp"
            android:gravity="center_horizontal">
            <LinearLayout
                android:id="@+id/alipay_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@drawable/alipay_shape"
                android:layout_marginTop="15dp"
                android:layout_marginLeft="15dp"
                android:layout_marginRight="15dp"
                android:padding="3dp"
                android:orientation="vertical"
                android:gravity="center">
                <LinearLayout
                    android:layout_width="250dp"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    android:gravity="center"
                    android:baselineAligned="false"
                    >
                    <ImageView
                        android:layout_width="30dp"
                        android:layout_height="30dp"
                        android:src="@mipmap/vip_alipay"
                        android:layout_marginEnd="8dp"/>
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="确认协议并支付"
                        android:textColor="#419AE4"
                        android:textSize="16sp"
                        android:gravity="start"/>
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="¥"
                        android:textColor="#419AE4"
                        android:textSize="16sp"
                        android:layout_marginLeft="10dp"
                        android:layout_marginRight="5dp"
                        android:gravity="start"/>
                    <TextView
                        android:id="@+id/alipay_amount"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="30"
                        android:textColor="#419AE4"
                        android:textSize="16sp"
                        android:gravity="start"/>
                </LinearLayout>
            </LinearLayout>
            <LinearLayout
                android:id="@+id/wechat_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@drawable/wechat_shape"
                android:layout_marginTop="15dp"
                android:layout_marginLeft="15dp"
                android:layout_marginRight="15dp"
                android:padding="3dp"
                android:orientation="vertical"
                android:gravity="center">
                <LinearLayout
                    android:layout_width="250dp"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    android:gravity="center"
                    android:baselineAligned="false"
                    >
                    <ImageView
                        android:layout_width="30dp"
                        android:layout_height="30dp"
                        android:src="@mipmap/vip_wechat"
                        android:layout_marginEnd="8dp"/>
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="确认协议并支付"
                        android:textColor="#77E382"
                        android:textSize="16sp"
                        android:gravity="start"/>
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="¥"
                        android:textColor="#77E382"
                        android:textSize="16sp"
                        android:layout_marginLeft="10dp"
                        android:layout_marginRight="5dp"
                        android:gravity="start"/>
                    <TextView
                        android:id="@+id/wechat_amount"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="30"
                        android:textColor="#77E382"
                        android:textSize="16sp"
                        android:gravity="start"/>
                </LinearLayout>
            </LinearLayout>
            <LinearLayout
                android:id="@+id/xieyi_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/transparent"
                android:layout_marginTop="15dp"
                android:layout_marginLeft="15dp"
                android:layout_marginRight="15dp"
                android:padding="1dp"
                android:orientation="vertical"
                android:gravity="center">
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    android:gravity="start"
                    android:baselineAligned="false"
                    >
                    <TextView
                        android:id="@+id/protocol_desc_layout"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="自动续费可随时取消,开通后每月按12元自动续费,可随时取消自动续费"
                        android:textColor="#A19FA7"
                        android:textSize="12sp"
                        android:gravity="start"/>
                </LinearLayout>
            </LinearLayout>
            <LinearLayout
                android:id="@+id/protocol_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/transparent"
                android:layout_marginTop="15dp"
                android:layout_marginLeft="15dp"
                android:layout_marginRight="15dp"
                android:padding="1dp"
                android:orientation="vertical"
                android:gravity="center">
                <LinearLayout
                    android:layout_width="300dp"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    android:gravity="center_vertical"
                    android:baselineAligned="false"
                    >
    <!--                <CheckBox-->
    <!--                    android:id="@+id/protocol_checkbox"-->
    <!--                    android:layout_width="20dp"-->
    <!--                    android:layout_height="20dp"-->
    <!--                    android:text=""-->
    <!--                    android:orientation="horizontal"-->
    <!--                    android:gravity="start"-->
    <!--                    />-->
                    <CheckBox
                        android:id="@+id/protocol_checkbox"
                        android:layout_width="20dp"
                        android:layout_height="20dp"
                        />
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="已阅读并同意"
                        android:textColor="#7E5C3C"
                        android:textSize="14sp"
                        android:gravity="start"
                        android:layout_marginLeft="10dp"
                        android:orientation="horizontal"
                        />
                    <TextView
                        android:id="@+id/protocol_vip"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="《VIP会员服务协议》"
                        android:textColor="#15759E"
                        android:textSize="14sp"
                        android:gravity="start"
                        android:orientation="horizontal"
                        />
                </LinearLayout>
            </LinearLayout>
        </LinearLayout>
    </androidx.cardview.widget.CardView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
app/src/main/res/layout/content_vip.xml
对比新文件
@@ -0,0 +1,19 @@
<?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"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">
    <fragment
        android:id="@+id/nav_host_fragment_content_vip"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
app/src/main/res/layout/dialog_category_selector.xml
对比新文件
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="12dp">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="选择分类"
        android:textSize="18sp"
        android:textStyle="bold"
        android:layout_marginBottom="8dp"/>
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/categoryRecyclerView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/saveButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="保存"
        android:layout_marginTop="12dp"
        android:backgroundTint="#000000"
        android:textColor="#FFFFFF"/>
</LinearLayout>
app/src/main/res/layout/fragment_dashboard.xml
@@ -159,4 +159,55 @@
        </ViewFlipper>
    </androidx.core.widget.NestedScrollView>
    <!-- 🔹 遮罩层 -->
    <View
        android:id="@+id/view_overlay"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@mipmap/overlay2"
        android:elevation="10dp"
        android:visibility="gone"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent" />
    <!-- 🔹 遮罩层上的按钮 -->
    <!-- 🔹 遮罩层上的内容容器(LinearLayout) -->
    <LinearLayout
        android:id="@+id/overlay_content"
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal"
        android:padding="10dp"
        android:background="@drawable/overlay_black_background"
        android:elevation="20dp"
        android:translationZ="20dp"
        android:visibility="gone"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">
        <ImageView
            android:id="@+id/overlay_content_vip_image"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:scaleType="centerCrop"
            android:src="@mipmap/vip_crown"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
        <!-- 🔹 示例内容:标题 -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="免费查看统计数据"
            android:textSize="18sp"
            android:textColor="#EEBC93" />
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
app/src/main/res/layout/fragment_first.xml
对比新文件
@@ -0,0 +1,35 @@
<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FirstFragment">
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp">
        <Button
            android:id="@+id/button_first"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/next"
            app:layout_constraintBottom_toTopOf="@id/textview_first"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
        <TextView
            android:id="@+id/textview_first"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:text="@string/lorem_ipsum"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/button_first" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
app/src/main/res/layout/fragment_home.xml
@@ -1,117 +1,177 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout 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">
<ScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginTop="40dp"> <!-- 留出顶部广告位的高度 -->
    >
<!--    LinearLayout的作用是按照垂直或者水平方向排列其子视图-->
<!--    CardView组件是用于实现卡片式布局-->
<!--    RecyclerView 回收商视图 它使用适配器(Adapter)来管理数据的显示,-->
<!--    开发者可以根据自己的需求实现适配器的方法,将数据与视图进行绑定。-->
<!--    这使得 RecyclerView 能够轻松地处理各种类型的数据,并按照自定义的布局方式展示。-->
<!--    支持局部刷新 通知数据集变化-->
    <LinearLayout
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        android:layout_height="match_parent"
        android:layout_marginTop="40dp"> <!-- 留出顶部广告位的高度 -->
        >
        <!-- 快递/财务切换区域 -->
        <!--    LinearLayout的作用是按照垂直或者水平方向排列其子视图-->
        <!--    CardView组件是用于实现卡片式布局-->
        <!--    RecyclerView 回收商视图 它使用适配器(Adapter)来管理数据的显示,-->
        <!--    开发者可以根据自己的需求实现适配器的方法,将数据与视图进行绑定。-->
        <!--    这使得 RecyclerView 能够轻松地处理各种类型的数据,并按照自定义的布局方式展示。-->
        <!--    支持局部刷新 通知数据集变化-->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginBottom="8dp">
            android:orientation="vertical">
            <TextView
                android:id="@+id/tabExpress"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="快递"
                android:gravity="center"
                android:padding="8dp"
                android:textSize="16sp"
                android:textStyle="bold"/>
            <TextView
                android:id="@+id/tabFinance"
                android:layout_width="0dp"
            <!-- 快递/财务切换区域 -->
            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="财务"
                android:gravity="center"
                android:padding="8dp"
                android:textSize="16sp"/>
                android:layout_marginBottom="8dp"
                android:layout_marginTop="8dp"
                android:layout_marginHorizontal="8dp">
            <TextView
                android:id="@+id/others"
                android:layout_width="0dp"
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginEnd="30dp"
                    android:orientation="horizontal">
                    <com.example.firstapp.view.UnderlineTextView
                        android:id="@+id/tabExpress"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1"
                        android:gravity="center"
                        android:padding="6dp"
                        android:text="快递"
                        android:textSize="14sp"
                        android:textStyle="bold" />
                    <com.example.firstapp.view.UnderlineTextView
                        android:id="@+id/tabFinance"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1"
                        android:gravity="center"
                        android:padding="6dp"
                        android:text="还款"
                        android:textSize="14sp" />
                    <com.example.firstapp.view.UnderlineTextView
                        android:id="@+id/tabIncome"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1"
                        android:gravity="center"
                        android:padding="6dp"
                        android:text="收入"
                        android:textSize="14sp" />
                    <com.example.firstapp.view.UnderlineTextView
                        android:id="@+id/tabFlight"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1"
                        android:gravity="center"
                        android:padding="6dp"
                        android:text="航班"
                        android:textSize="14sp" />
                    <com.example.firstapp.view.UnderlineTextView
                        android:id="@+id/tabTrain"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1"
                        android:gravity="center"
                        android:padding="6dp"
                        android:text="火车票"
                        android:textSize="14sp" />
                </LinearLayout>
                <ImageButton
                    android:id="@+id/categoryButton"
                    android:layout_width="28dp"
                    android:layout_height="28dp"
                    android:layout_gravity="center_vertical|end"
                    android:background="?attr/selectableItemBackgroundBorderless"
                    android:contentDescription="分类设置"
                    android:padding="4dp"
                    android:scaleType="fitCenter"
                    android:src="@drawable/home_add" />
            </FrameLayout>
            <!-- 内容区域 -->
            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="其他"
                android:gravity="center"
                android:padding="8dp"
                android:textSize="16sp"/>
                android:layout_margin="16dp">
                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/express_recycler"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="8dp" />
                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/finance_recycler"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="8dp" />
                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/income_recycler"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="8dp" />
                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/flight_recycler"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="8dp"
                    android:visibility="gone" />
                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/train_recycler"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="8dp"
                    android:visibility="gone" />
            </FrameLayout>
            <!-- 底部广告位 -->
            <androidx.cardview.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:cardCornerRadius="8dp"
                app:cardElevation="2dp">
                <ImageView
                    android:id="@+id/bottomAdBanner"
                    android:layout_width="match_parent"
                    android:layout_height="80dp"
                    android:scaleType="centerCrop" />
            </androidx.cardview.widget.CardView>
        </LinearLayout>
        <!-- 内容区域 -->
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="16dp">
            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/express_recycler"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="8dp"/>
            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/finance_recycler"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="8dp"/>
        </FrameLayout>
        <!-- 底部广告位 -->
        <androidx.cardview.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:cardCornerRadius="8dp"
            app:cardElevation="2dp">
            <ImageView
                android:id="@+id/bottomAdBanner"
                android:layout_width="match_parent"
                android:layout_height="80dp"
                android:scaleType="centerCrop"/>
        </androidx.cardview.widget.CardView>
    </LinearLayout>
</ScrollView>
    </ScrollView>
    <!-- 顶部广告位 -->
    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
       >
        android:layout_height="wrap_content">
        <ImageView
            android:id="@+id/adBanner"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:scaleType="centerCrop"
            android:src="@drawable/up"/>
            android:src="@drawable/up_back" />
    </androidx.cardview.widget.CardView>
    <!-- 在适当的位置添加 -->
</FrameLayout>
app/src/main/res/layout/fragment_member_info_card.xml
对比新文件
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="viewModel"
            type="com.example.firstapp.ui.vip.MemberInfoCardViewModel"/>
    </data>
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.vip.MemberInfoCardFragment">
        <!-- CardView 内部的内容 -->
        <androidx.cardview.widget.CardView
            android:id="@+id/memberCardCardView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="15dp"
            android:layout_marginTop="10dp"
            android:layout_marginRight="15dp"
            android:elevation="0dp"
            android:clipChildren="false"
            android:clipToOutline="true"
            app:cardCornerRadius="10dp">  <!-- 设置CardView的elevation -->
            <androidx.constraintlayout.widget.ConstraintLayout
                android:id="@+id/memberCard"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                tools:ignore="MissingConstraints"
                android:background="@android:color/transparent"
                >
                <ImageView
                    android:id="@+id/memberAvatarView"
                    android:layout_width="40dp"
                    android:layout_height="40dp"
                    android:layout_marginLeft="10dp"
                    android:layout_marginRight="10dp"
                    android:scaleType="centerCrop"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent" />
                <TextView
                    android:id="@+id/phoneNumberText"
                    android:layout_width="120dp"
                    android:layout_height="40dp"
                    android:gravity="center"
                    android:text="@{viewModel.phoneNumber}"
                    app:layout_constraintStart_toEndOf="@id/memberAvatarView"
                    app:layout_constraintTop_toTopOf="parent" />
<!--                <TextView-->
<!--                    android:id="@+id/memberStatus"-->
<!--                    android:layout_width="match_parent"-->
<!--                    android:layout_height="wrap_content"-->
<!--                    android:gravity="start"-->
<!--                    android:text="@{viewModel.memberStatus}"-->
<!--                    app:layout_constraintStart_toEndOf="@id/memberCard"-->
<!--                    app:layout_constraintTop_toTopOf="parent" />-->
                <TextView
                    android:id="@+id/memberStatus"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:gravity="start"
                    android:text="@{viewModel.memberStatus}"
                    android:layout_marginLeft="8dp"
                    android:padding="2dp"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toBottomOf="@id/memberAvatarView" />
            </androidx.constraintlayout.widget.ConstraintLayout>
        </androidx.cardview.widget.CardView>
        <!-- 悬浮的 ImageView, 位于右侧 -->
        <ImageView
            android:id="@+id/memberImageView"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:layout_marginTop="-10dp"
            android:layout_marginRight="40dp"
            android:scaleType="centerCrop"
            android:layout_gravity="end|top"
            android:elevation="4dp" />  <!-- 设置memberImageView的elevation,确保它在上层 -->
    </FrameLayout>
</layout>
app/src/main/res/layout/fragment_notifications.xml
@@ -110,7 +110,7 @@
                            android:id="@+id/tv_vip_expire"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:textColor="#FFD700"
                            android:textColor="#F2F2F2"
                            android:textSize="14sp"/>
                    </LinearLayout>
@@ -120,7 +120,7 @@
                        android:layout_height="wrap_content"
                        android:layout_marginTop="4dp"
                        android:text="续费畅享更多会员权益"
                        android:textColor="#B8741A"
                        android:textColor="#F0DCBF"
                        android:textSize="12sp"
                        android:textStyle="bold"/>
                </LinearLayout>
@@ -131,7 +131,7 @@
                    android:layout_height="wrap_content"
                    android:backgroundTint="#7A441E"
                    android:text="立即续费"
                    android:textColor="#FFFFFF" />
                    android:textColor="#F0DCBF" />
            </LinearLayout>
        </androidx.cardview.widget.CardView>
app/src/main/res/layout/fragment_second.xml
对比新文件
@@ -0,0 +1,35 @@
<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SecondFragment">
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp">
        <Button
            android:id="@+id/button_second"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/previous"
            app:layout_constraintBottom_toTopOf="@id/textview_second"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
        <TextView
            android:id="@+id/textview_second"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:text="@string/lorem_ipsum"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/button_second" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
app/src/main/res/layout/item_card.xml
对比新文件
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    app:cardBackgroundColor="#F0F0F0"
    app:cardCornerRadius="16dp">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="16dp">
        <TextView
            android:id="@+id/tag_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#FF0000"
            android:text="首月限时特惠"
            android:textColor="#FFFFFF"
            android:padding="16dp"
             />
        <TextView
            android:id="@+id/title_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="连续包月"
            android:textSize="18sp"
            android:textColor="#333333"
            android:layout_marginTop="8dp" />
        <TextView
            android:id="@+id/price_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="¥9.9"
            android:textSize="24sp"
            android:textColor="#FF0000"
            android:layout_marginTop="8dp" />
    </LinearLayout>
</androidx.cardview.widget.CardView>
app/src/main/res/layout/item_card_continue_monthly.xml
对比新文件
@@ -0,0 +1,163 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="240dp"
    android:layout_height="wrap_content"
    android:layout_marginTop="10dp"
    android:layout_marginRight="10dp"
    android:background="@android:color/transparent"
    app:cardBackgroundColor="@android:color/transparent"
    app:cardElevation="0dp"
    app:cardUseCompatPadding="false"
    >
    <androidx.cardview.widget.CardView
        android:id="@+id/vip_first_month_card_view"
        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="100dp"
        android:layout_marginTop="10dp"
        app:cardCornerRadius="16dp"
        android:foreground="@drawable/gold_border_shape"
        app:cardBackgroundColor="@android:color/transparent"
        app:cardElevation="0dp"
        app:cardUseCompatPadding="false">
        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="5dp"
            android:paddingStart="15dp"
            android:background="@android:color/transparent">
            <!-- Title Text -->
            <TextView
                android:id="@+id/title_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="连续包月"
                android:textSize="18sp"
                android:textColor="#F9EBC6"
                android:background="@android:color/transparent"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toStartOf="parent" />
            <!-- Subtitle Text -->
            <TextView
                android:id="@+id/sub_title_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="首次开通"
                android:textSize="10dp"
                android:textColor="#CCCCCC"
                android:paddingStart="5dp"
                android:paddingTop="2dp"
                android:paddingEnd="5dp"
                android:paddingBottom="2dp"
                android:background="@drawable/round_transition_gray_bg"
                app:layout_constraintTop_toBottomOf="@id/title_text"
                app:layout_constraintStart_toStartOf="parent"
                android:layout_marginStart="85dp"
                android:layout_marginTop="-20dp"
                />
            <!-- Price Text -->
            <TextView
                android:id="@+id/price_text_tag"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="¥"
                android:textSize="20sp"
                android:textColor="#F9EBC6"
                android:layout_marginTop="10dp"
                android:layout_marginLeft="135dp"
                android:background="@android:color/transparent"
                app:layout_constraintTop_toBottomOf="@id/sub_title_text"
                app:layout_constraintStart_toStartOf="parent" />
            <TextView
                android:id="@+id/price_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="9.9"
                android:textSize="30sp"
                android:textColor="#F9EBC6"
                android:layout_marginTop="0dp"
                android:layout_marginLeft="150dp"
                android:background="@android:color/transparent"
                app:layout_constraintTop_toBottomOf="@id/sub_title_text"
                app:layout_constraintStart_toStartOf="parent" />
            <!-- Next Price Text -->
            <TextView
                android:id="@+id/next_price_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="次月起12元/月"
                android:textSize="14sp"
                android:textColor="#CCCCCC"
                android:layout_marginTop="10dp"
                android:background="@android:color/transparent"
                app:layout_constraintTop_toBottomOf="@id/title_text"
                app:layout_constraintStart_toStartOf="parent" />
            <!-- Auto Renew Text -->
            <TextView
                android:id="@+id/auto_renew_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="自动续费可随时取消"
                android:textSize="12sp"
                android:textColor="#CCCCCC"
                android:layout_marginTop="4dp"
                android:background="@android:color/transparent"
                app:layout_constraintTop_toBottomOf="@id/next_price_text"
                app:layout_constraintStart_toStartOf="parent" />
<!--            <TextView-->
<!--                android:id="@+id/auto_renew_text2"-->
<!--                android:layout_width="wrap_content"-->
<!--                android:layout_height="wrap_content"-->
<!--                android:text="活动须知"-->
<!--                android:textSize="12sp"-->
<!--                android:textColor="#CCCCCC"-->
<!--                android:layout_marginTop="4dp"-->
<!--                android:background="@android:color/transparent"-->
<!--                app:layout_constraintTop_toBottomOf="@id/auto_renew_text"-->
<!--                app:layout_constraintStart_toStartOf="parent" />-->
<!--            <TextView-->
<!--                android:id="@+id/auto_renew_text3"-->
<!--                android:layout_width="wrap_content"-->
<!--                android:layout_height="wrap_content"-->
<!--                android:text=">"-->
<!--                android:textSize="16sp"-->
<!--                android:textColor="#CCCCCC"-->
<!--                android:layout_marginTop="0dp"-->
<!--                android:background="@android:color/transparent"-->
<!--                android:layout_marginLeft="60dp"-->
<!--                app:layout_constraintTop_toBottomOf="@id/auto_renew_text"-->
<!--                app:layout_constraintStart_toStartOf="parent" />-->
        </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
    <TextView
        android:id="@+id/first_month_tag"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="0dp"
        android:layout_marginRight="5dp"
        android:scaleType="centerCrop"
        android:layout_gravity="end|top"
        android:text="首月限时特惠"
        android:textColor="#FFFFFF"
        android:padding="4dp"
        android:elevation="4dp"
        android:textSize="10sp"
        android:background="@drawable/round_red_bg"
        />
</androidx.cardview.widget.CardView>
app/src/main/res/layout/item_card_single_month.xml
对比新文件
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="160dp"
    android:layout_height="wrap_content"
    android:layout_marginTop="10dp"
    android:layout_marginRight="10dp"
    android:background="@android:color/transparent"
    app:cardBackgroundColor="@android:color/transparent"
    app:cardElevation="0dp"
    app:cardUseCompatPadding="false"
    >
    <androidx.cardview.widget.CardView
        android:id="@+id/vip_month_card_view"
        android:layout_width="160dp"
        android:layout_height="100dp"
        android:padding="8dp"
        android:layout_marginTop="10dp"
        android:layout_marginEnd="20dp"
        android:background="@android:color/transparent"
        android:foreground="@drawable/gray_border_shape"
        app:cardBackgroundColor="@android:color/transparent"
        app:cardCornerRadius="16dp"
        app:cardElevation="0dp"
        app:cardUseCompatPadding="false">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginRight="20dp"
            android:gravity="center"
            android:orientation="vertical"
            android:padding="5dp">
            <TextView
                android:id="@+id/single_month_title_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:text="1个月"
                android:textColor="#F9EBC6"
                android:textSize="18sp" />
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">
                <TextView
                    android:id="@+id/single_month_price_tag"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    android:text="¥"
                    android:textColor="#F9EBC6"
                    android:textSize="20sp" />
                <TextView
                    android:id="@+id/single_month_price_text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    android:text="15"
                    android:textColor="#F9EBC6"
                    android:textSize="30sp" />
            </LinearLayout>
        </LinearLayout>
    </androidx.cardview.widget.CardView>
</androidx.cardview.widget.CardView>
app/src/main/res/layout/item_card_yearly.xml
对比新文件
@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="160dp"
    android:layout_height="wrap_content"
    android:layout_marginTop="10dp"
    android:layout_marginRight="10dp"
    android:background="@android:color/transparent"
    app:cardBackgroundColor="@android:color/transparent"
    app:cardElevation="0dp"
    app:cardUseCompatPadding="false"
    >
    <androidx.cardview.widget.CardView
        android:id="@+id/vip_year_card_view"
        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:layout_marginTop="10dp"
        app:cardCornerRadius="16dp"
        android:foreground="@drawable/gray_border_shape"
        app:cardBackgroundColor="@android:color/transparent"
        app:cardElevation="0dp"
        app:cardUseCompatPadding="false">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:orientation="vertical"
            android:gravity="center"
            android:padding="0dp">
            <TextView
                android:id="@+id/yearly_title_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="年卡"
                android:textSize="18sp"
                android:textColor="#F9EBC6"
                android:layout_marginTop="8dp"/>
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">
                <TextView
                    android:id="@+id/yearly_price_tag"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="¥"
                    android:textSize="20sp"
                    android:textColor="#F9EBC6"
                    android:layout_marginTop="2dp"/>
                <TextView
                    android:id="@+id/yearly_price_text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="108"
                    android:textSize="20sp"
                    android:textColor="#F9EBC6"
                    android:layout_marginTop="2dp"/>
                <TextView
                    android:id="@+id/yearly_price_unit"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="/年"
                    android:textSize="20sp"
                    android:textColor="#F9EBC6"
                    android:layout_marginTop="2dp"/>
            </LinearLayout>
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
            <TextView
                android:id="@+id/yearly_original_price_tag"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="¥"
                android:textSize="12sp"
                android:textColor="#CCCCCC"
                android:layout_marginTop="2dp"/>
            <TextView
                android:id="@+id/yearly_original_price_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="168"
                android:textSize="12sp"
                android:textColor="#CCCCCC"
                android:layout_marginTop="2dp"
                />
        </LinearLayout>
    </LinearLayout>
</androidx.cardview.widget.CardView>
    <TextView
        android:id="@+id/discount_tag"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="0dp"
        android:layout_marginRight="5dp"
        android:scaleType="centerCrop"
        android:layout_gravity="end|top"
        android:text="折合9元/月"
        android:textColor="#FFFFFF"
        android:padding="4dp"
        android:textSize="10sp"
        android:elevation="4dp"
        android:background="@drawable/round_red_bg"
        />
</androidx.cardview.widget.CardView>
app/src/main/res/layout/item_category_selector.xml
对比新文件
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="8dp"
    android:gravity="center_vertical">
    <TextView
        android:id="@+id/categoryName"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="16sp"/>
    <CheckBox
        android:id="@+id/categoryCheckBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"/>
</LinearLayout>
app/src/main/res/layout/item_income_group.xml
对比新文件
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
    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:layout_marginHorizontal="12dp"
    android:layout_marginVertical="6dp"
    app:cardCornerRadius="8dp"
    app:cardElevation="2dp"
    app:cardBackgroundColor="@android:color/white"
    app:strokeColor="#FF000000"
    app:strokeWidth="2dp">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="16dp">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginBottom="12dp">
            <ImageView
                android:id="@+id/iv_station_icon"
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_gravity="center_vertical"
                android:src="@drawable/location"/>
            <TextView
                android:id="@+id/tv_station_name"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:layout_marginStart="8dp"
                android:textSize="16sp"
                android:textColor="#333333"/>
            <TextView
                android:id="@+id/tv_package_count"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="14sp"
                android:textColor="#666666"/>
        </LinearLayout>
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_packages"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>
</com.google.android.material.card.MaterialCardView>
app/src/main/res/layout/item_income_package_home.xml
对比新文件
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="12dp"
    android:gravity="center_vertical">
    <ImageView
        android:id="@+id/iv_company_logo"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_marginEnd="6dp"/>
    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="vertical">
        <TextView
            android:id="@+id/tv_company"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="12sp"
            android:textColor="#333333"
            android:textStyle="bold"/>
        <TextView
            android:id="@+id/tv_create_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="10sp"
            android:textColor="#666666"
            android:layout_marginTop="4dp"/>
    </LinearLayout>
    <TextView
        android:id="@+id/tv_tracking_number"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="25sp"
        android:textColor="#333333"
        android:layout_marginStart="12dp"
        android:textStyle="bold"/>
</LinearLayout>
app/src/main/res/layout/vip_protocol_dialog_custom.xml
对比新文件
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="300dp"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="@drawable/dialog_background"
    android:padding="20dp"
    android:gravity="center"
    >
    <!-- 标题 -->
    <TextView
        android:id="@+id/dialogTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="确认开通"
        android:textSize="20sp"
        android:textColor="#000000"
        android:gravity="center"
        android:paddingBottom="10dp" />
    <!-- 内容 -->
    <TextView
        android:id="@+id/dialogMessage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="请阅读并同意《会员协议》?"
        android:textSize="16sp"
        android:textColor="#000000"
        android:gravity="center"
        android:paddingBottom="20dp" />
    <!-- 按钮 -->
    <Button
        android:id="@+id/btnConfirm"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:text="继续开通"
        android:textColor="@android:color/white"
        android:backgroundTint="#000000"
        android:gravity="center" />
</LinearLayout>
app/src/main/res/layout/vip_protocol_dialog_custom_bak.xml
对比新文件
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="300dp"
    android:layout_height="wrap_content"
    app:cardCornerRadius="10dp"
    app:cardElevation="5dp"
    app:cardBackgroundColor="#FFFFFF"
    android:layout_gravity="center">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="20dp"
        android:gravity="center">
        <!-- 标题 -->
        <TextView
            android:id="@+id/dialogTitle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="确认开通"
            android:textSize="20sp"
            android:textColor="#000000"
            android:gravity="center"
            android:paddingBottom="10dp" />
        <!-- 内容 -->
        <TextView
            android:id="@+id/dialogMessage"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="请阅读并同意《会员协议》?"
            android:textSize="16sp"
            android:textColor="#000000"
            android:gravity="center"
            android:paddingBottom="20dp" />
        <!-- 按钮 -->
        <Button
            android:id="@+id/btnConfirm"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:text="继续开通"
            android:textColor="@android:color/white"
            android:backgroundTint="#000000"
            android:textColorHint="@android:color/black"
            android:gravity="center" />
    </LinearLayout>
</androidx.cardview.widget.CardView>
app/src/main/res/mipmap/avatar_default.png
app/src/main/res/mipmap/overlay.png
app/src/main/res/mipmap/overlay2.jpg
app/src/main/res/mipmap/vip.png
app/src/main/res/mipmap/vip_ad_no.png
app/src/main/res/mipmap/vip_ai_ass_icon.png
app/src/main/res/mipmap/vip_alipay.png
app/src/main/res/mipmap/vip_bar_sta.png
app/src/main/res/mipmap/vip_cancel.png
app/src/main/res/mipmap/vip_cancel_link.png
app/src/main/res/mipmap/vip_card.png
app/src/main/res/mipmap/vip_circle_pie.png
app/src/main/res/mipmap/vip_copy.png
app/src/main/res/mipmap/vip_crown.png
app/src/main/res/mipmap/vip_free_member.png
app/src/main/res/mipmap/vip_life_insurance.png
app/src/main/res/mipmap/vip_ling.png
app/src/main/res/mipmap/vip_more.png
app/src/main/res/mipmap/vip_no.png
app/src/main/res/mipmap/vip_right.png
app/src/main/res/mipmap/vip_wechat.png
app/src/main/res/navigation/nav_graph.xml
对比新文件
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/nav_graph"
    app:startDestination="@id/FirstFragment">
    <fragment
        android:id="@+id/FirstFragment"
        android:name="com.example.firstapp.activity.FirstFragment"
        android:label="@string/first_fragment_label"
        tools:layout="@layout/fragment_first">
        <action
            android:id="@+id/action_FirstFragment_to_SecondFragment"
            app:destination="@id/SecondFragment" />
    </fragment>
    <fragment
        android:id="@+id/SecondFragment"
        android:name="com.example.firstapp.activity.SecondFragment"
        android:label="@string/second_fragment_label"
        tools:layout="@layout/fragment_second">
        <action
            android:id="@+id/action_SecondFragment_to_FirstFragment"
            app:destination="@id/FirstFragment" />
    </fragment>
</navigation>
app/src/main/res/values-land/dimens.xml
对比新文件
@@ -0,0 +1,3 @@
<resources>
    <dimen name="fab_margin">48dp</dimen>
</resources>
app/src/main/res/values-night/themes.xml
文件已删除
app/src/main/res/values-v23/themes.xml
@@ -1,9 +1,9 @@
<resources xmlns:tools="http://schemas.android.com/tools">
    <style name="Theme.FirstApp" parent="Base.Theme.FirstApp">
        <!-- Transparent system bars for edge-to-edge. -->
        <item name="android:navigationBarColor">@android:color/transparent</item>
        <item name="android:statusBarColor">@android:color/transparent</item>
        <item name="android:windowLightStatusBar">?attr/isLightTheme</item>
    </style>
<!--    <style name="Theme.FirstApp" parent="Base.Theme.FirstApp">-->
<!--        &lt;!&ndash; Transparent system bars for edge-to-edge. &ndash;&gt;-->
<!--        <item name="android:navigationBarColor">@android:color/transparent</item>-->
<!--        <item name="android:statusBarColor">@android:color/transparent</item>-->
<!--        <item name="android:windowLightStatusBar">?attr/isLightTheme</item>-->
<!--    </style>-->
</resources>
app/src/main/res/values-w1240dp/dimens.xml
对比新文件
@@ -0,0 +1,3 @@
<resources>
    <dimen name="fab_margin">200dp</dimen>
</resources>
app/src/main/res/values-w600dp/dimens.xml
对比新文件
@@ -0,0 +1,3 @@
<resources>
    <dimen name="fab_margin">48dp</dimen>
</resources>
app/src/main/res/values/dimens.xml
@@ -5,4 +5,5 @@
    <dimen name="list_item_spacing">16dp</dimen>
    <dimen name="list_item_spacing_half">8dp</dimen>
    <dimen name="heatmap_cell_size">12dp</dimen>
    <dimen name="fab_margin">16dp</dimen>
</resources>
app/src/main/res/values/themes.xml
@@ -19,9 +19,9 @@
    </style>
    <style name="Theme.FirstApp.Fullscreen" parent="Theme.FirstApp">
        <item name="android:actionBarStyle">@style/Widget.Theme.FirstApp.ActionBar.Fullscreen</item>
        <item name="android:windowActionBarOverlay">true</item>
        <item name="android:windowBackground">@null</item>
<!--        <item name="android:actionBarStyle">@style/Widget.Theme.FirstApp.ActionBar.Fullscreen</item>-->
<!--        <item name="android:windowActionBarOverlay">true</item>-->
<!--        <item name="android:windowBackground">@null</item>-->
    </style>
    <style name="ThemeOverlay.FirstApp.FullscreenContainer" parent="">
app/src/main/res/xml/network_security_config.xml
@@ -5,6 +5,7 @@
        <domain includeSubdomains="true">192.168.1.213</domain>
        <domain includeSubdomains="true">192.168.1.198</domain>
        <domain includeSubdomains="true">192.168.1.199</domain>
        <domain includeSubdomains="true">192.168.1.201</domain>
        <!-- 可添加其他域名或IP(如192.168.0.101) -->
<!--        如果本地服务使用自签名证书,需在 network_security_config.xml 中信任该证书:-->
<!--        <trust-anchors>-->