From fe04012057d024770e0180543483d393281a542f Mon Sep 17 00:00:00 2001 From: zhujie <leon.zhu@cloudroam.com.cn> Date: 星期四, 03 四月 2025 09:18:16 +0800 Subject: [PATCH] Merge branch 'master' of http://47.96.225.205:8888/r/FirstApp2 --- app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt | 73 app/src/main/res/drawable/error_placeholder.xml | 9 app/src/main/java/com/example/firstapp/network/TokenExpiredInterceptor.kt | 19 app/src/main/java/com/example/firstapp/view/UnderlineTextView.kt | 43 app/src/main/res/drawable/more_benefit_shape.xml | 10 .idea/misc.xml | 2 app/src/main/res/drawable/ic_reminder.xml | 12 app/src/main/res/mipmap/vip_more.png | 0 .idea/gradle.xml | 3 app/src/main/java/com/example/firstapp/database/response/OAuth2TokenResponse.kt | 17 app/src/main/res/layout/vip_protocol_dialog_custom.xml | 44 app/src/main/java/com/example/firstapp/adapter/CategorySelectorAdapter.kt | 66 app/src/main/res/mipmap/vip_bar_sta.png | 0 app/src/main/res/drawable/vip_white_background.xml | 16 app/src/main/java/com/example/firstapp/adapter/IncomeAdapter.kt | 111 + app/src/main/res/drawable/round_red_bg.xml | 5 app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt | 3 app/src/main/java/com/example/firstapp/util/CategoryDragCallback.kt | 25 app/src/main/res/layout/fragment_notifications.xml | 6 app/src/main/res/drawable/wechat_shape.xml | 6 app/src/main/res/mipmap/vip_ling.png | 0 app/src/main/res/layout/fragment_second.xml | 35 app/src/main/res/mipmap/vip_ai_ass_icon.png | 0 app/src/main/java/com/example/firstapp/activity/SecondFragment.kt | 45 app/src/main/res/drawable/dialog_background.xml | 16 app/src/main/java/com/example/firstapp/database/request/SmsLoginRequest.kt | 7 app/src/main/res/drawable/round_transition_gray_bg.xml | 9 app/src/main/java/com/example/firstapp/adapter/CardAdapter.kt | 126 + app/src/main/res/layout/item_card_yearly.xml | 115 + app/src/main/res/values/themes.xml | 6 app/src/main/java/com/example/firstapp/database/dao/CodeDao.kt | 28 app/src/main/res/layout/vip_protocol_dialog_custom_bak.xml | 52 app/src/main/res/xml/network_security_config.xml | 1 app/src/main/res/drawable/gold_border_shape.xml | 7 app/src/main/java/com/example/firstapp/model/IncomeGroup.kt | 14 app/src/main/res/mipmap/vip_wechat.png | 0 app/src/main/res/drawable/vip_black_background.xml | 17 app/src/main/res/mipmap/vip_copy.png | 0 app/src/main/res/layout/item_income_package_home.xml | 47 app/src/main/java/com/example/firstapp/ui/notifications/NotificationsFragment.kt | 74 app/src/main/java/com/example/firstapp/activity/ContentDetailActivity.kt | 14 app/src/main/res/mipmap/vip_free_member.png | 0 app/src/main/res/mipmap/vip_card.png | 0 app/src/main/res/mipmap/vip_cancel.png | 0 app/src/main/java/com/example/firstapp/database/service/ApiService.kt | 63 app/src/main/java/com/example/firstapp/database/request/ProductOrdersRequest.kt | 11 app/src/main/java/com/example/firstapp/model/CardData.kt | 26 app/src/main/res/layout/fragment_home.xml | 236 +- app/src/main/java/com/example/firstapp/model/CategoryConfigSync.kt | 6 app/src/main/java/com/example/firstapp/model/MemberBenefitItem.kt | 5 app/src/main/java/com/example/firstapp/network/AuthInterceptor.kt | 24 app/src/main/res/mipmap/avatar_default.png | 0 app/src/main/java/com/example/firstapp/activity/FirstFragment.kt | 45 app/src/main/java/com/example/firstapp/model/CategoryConfig.kt | 11 app/src/main/res/drawable/tab_selected_background.xml | 9 app/src/main/res/layout/item_income_group.xml | 56 app/src/main/java/com/example/firstapp/database/response/UserInfo.kt | 6 app/src/main/res/drawable/member_background.xml | 8 app/src/main/res/values-v23/themes.xml | 12 .idea/deploymentTargetSelector.xml | 4 app/src/main/res/mipmap/overlay2.jpg | 0 app/src/main/res/drawable/gray_border_shape.xml | 7 app/src/main/java/com/example/firstapp/ui/vip/MemberInfoCardViewModel.kt | 92 + app/src/main/res/layout/content_vip.xml | 19 app/src/main/res/layout/item_card_single_month.xml | 68 app/src/main/res/navigation/nav_graph.xml | 28 app/src/main/res/mipmap/vip_no.png | 0 app/src/main/res/mipmap/vip_ad_no.png | 0 app/src/main/res/layout/item_category_selector.xml | 22 app/build.gradle | 10 app/src/main/res/drawable/checkbox_round.xml | 7 app/src/main/res/values-w1240dp/dimens.xml | 3 app/src/main/res/values/dimens.xml | 1 app/src/main/java/com/example/firstapp/activity/PickupActivity.kt | 55 app/src/main/res/layout/fragment_member_info_card.xml | 96 + app/src/main/java/com/example/firstapp/model/MoreMemberBenefitItem.kt | 4 app/src/main/java/com/example/firstapp/database/repository/CodeRepository.kt | 14 app/src/main/res/drawable/home_add.xml | 9 app/src/main/res/layout/activity_vip.xml | 518 ++++++ app/src/main/java/com/example/firstapp/activity/PhoneLoginActivity.kt | 6 app/src/main/java/com/example/firstapp/database/service/ModelService.kt | 33 app/src/main/res/drawable/checkbox_round_selected.xml | 7 app/src/main/res/drawable/ic_check.xml | 14 app/src/main/res/mipmap/vip_right.png | 0 app/src/main/res/drawable/ic_category.xml | 12 app/src/main/res/mipmap/vip_crown.png | 0 app/src/main/res/values-w600dp/dimens.xml | 3 app/src/main/java/com/example/firstapp/App.kt | 3 app/src/main/res/layout/fragment_first.xml | 35 app/src/main/java/com/example/firstapp/database/request/SmsSendRequest.kt | 6 app/src/main/res/drawable/overlay_black_background.xml | 17 app/src/main/res/drawable/up.png | 0 .gitignore | 3 app/src/main/res/mipmap/vip_life_insurance.png | 0 app/src/main/java/com/example/firstapp/ui/login/LoginViewModel.kt | 58 app/src/main/java/com/example/firstapp/activity/VipActivity.kt | 544 ++++++ app/src/main/res/mipmap/vip_cancel_link.png | 0 app/src/main/res/mipmap/vip_circle_pie.png | 0 app/src/main/res/values-land/dimens.xml | 3 app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt | 238 ++ app/src/main/res/drawable/up_back.jpg | 0 app/src/main/res/mipmap/vip.png | 0 app/src/main/res/mipmap/overlay.png | 0 app/src/main/res/layout/fragment_dashboard.xml | 51 app/src/main/java/com/example/firstapp/MainActivity.kt | 261 +- app/src/main/res/mipmap/vip_alipay.png | 0 app/src/main/res/drawable/set.xml | 13 app/src/main/java/com/example/firstapp/database/response/ContentResponse.kt | 11 app/src/main/res/layout/activity_phone_login.xml | 1 app/src/main/res/drawable/alipay_shape.xml | 6 app/src/main/AndroidManifest.xml | 14 app/src/main/java/com/example/firstapp/ui/vip/MemberInfoCardFragment.kt | 162 ++ app/src/main/java/com/example/firstapp/utils/PreferencesManager.kt | 58 app/src/main/res/layout/item_card.xml | 46 app/src/main/java/com/example/firstapp/model/StationGroup.kt | 6 /dev/null | 29 app/src/main/java/com/example/firstapp/ui/home/HomeFragment.kt | 387 ++++ app/src/main/java/com/example/firstapp/util/SecureStorage.kt | 49 app/src/main/res/layout/dialog_category_selector.xml | 29 app/src/main/res/drawable/black_button_background.xml | 6 app/src/main/res/drawable/ic_stats.xml | 12 app/src/main/res/layout/item_card_continue_monthly.xml | 163 ++ 122 files changed, 4,341 insertions(+), 503 deletions(-) diff --git a/.gitignore b/.gitignore index aa724b7..8654057 100644 --- a/.gitignore +++ b/.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 diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index 3b8cd93..e918287 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.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> diff --git a/.idea/gradle.xml b/.idea/gradle.xml index ad84da2..cb865f6 100644 --- a/.idea/gradle.xml +++ b/.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$" /> diff --git a/.idea/misc.xml b/.idea/misc.xml index d7916a5..8978d23 100644 --- a/.idea/misc.xml +++ b/.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"> diff --git a/app/build.gradle b/app/build.gradle index 88aea3e..3afa1de 100644 --- a/app/build.gradle +++ b/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' - - } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d826628..99381b1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/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"/> diff --git a/app/src/main/java/com/example/firstapp/App.kt b/app/src/main/java/com/example/firstapp/App.kt index 6663556..7c60e3a 100644 --- a/app/src/main/java/com/example/firstapp/App.kt +++ b/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 diff --git a/app/src/main/java/com/example/firstapp/MainActivity.kt b/app/src/main/java/com/example/firstapp/MainActivity.kt index 8b689c9..c59ca87 100644 --- a/app/src/main/java/com/example/firstapp/MainActivity.kt +++ b/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}") } } } diff --git a/app/src/main/java/com/example/firstapp/activity/ContentDetailActivity.kt b/app/src/main/java/com/example/firstapp/activity/ContentDetailActivity.kt index f82b1c0..a87d06e 100644 --- a/app/src/main/java/com/example/firstapp/activity/ContentDetailActivity.kt +++ b/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> diff --git a/app/src/main/java/com/example/firstapp/activity/FirstFragment.kt b/app/src/main/java/com/example/firstapp/activity/FirstFragment.kt new file mode 100644 index 0000000..9cc51ef --- /dev/null +++ b/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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/activity/PhoneLoginActivity.kt b/app/src/main/java/com/example/firstapp/activity/PhoneLoginActivity.kt index 457e213..8432373 100644 --- a/app/src/main/java/com/example/firstapp/activity/PhoneLoginActivity.kt +++ b/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, diff --git a/app/src/main/java/com/example/firstapp/activity/PickupActivity.kt b/app/src/main/java/com/example/firstapp/activity/PickupActivity.kt index d7dda6d..48a6dc1 100644 --- a/app/src/main/java/com/example/firstapp/activity/PickupActivity.kt +++ b/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) } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/activity/SecondFragment.kt b/app/src/main/java/com/example/firstapp/activity/SecondFragment.kt new file mode 100644 index 0000000..8f44618 --- /dev/null +++ b/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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/activity/VipActivity.kt b/app/src/main/java/com/example/firstapp/activity/VipActivity.kt new file mode 100644 index 0000000..9cf1f11 --- /dev/null +++ b/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) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/adapter/CardAdapter.kt b/app/src/main/java/com/example/firstapp/adapter/CardAdapter.kt new file mode 100644 index 0000000..a12ce57 --- /dev/null +++ b/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) + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/adapter/CategorySelectorAdapter.kt b/app/src/main/java/com/example/firstapp/adapter/CategorySelectorAdapter.kt new file mode 100644 index 0000000..f3c46b9 --- /dev/null +++ b/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() +} \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/adapter/IncomeAdapter.kt b/app/src/main/java/com/example/firstapp/adapter/IncomeAdapter.kt new file mode 100644 index 0000000..856ba45 --- /dev/null +++ b/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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/database/dao/CodeDao.kt b/app/src/main/java/com/example/firstapp/database/dao/CodeDao.kt index f79e67a..58bcc20 100644 --- a/app/src/main/java/com/example/firstapp/database/dao/CodeDao.kt +++ b/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> } diff --git a/app/src/main/java/com/example/firstapp/database/repository/CodeRepository.kt b/app/src/main/java/com/example/firstapp/database/repository/CodeRepository.kt index a9352e2..ad83877 100644 --- a/app/src/main/java/com/example/firstapp/database/repository/CodeRepository.kt +++ b/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) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/database/request/ProductOrdersRequest.kt b/app/src/main/java/com/example/firstapp/database/request/ProductOrdersRequest.kt new file mode 100644 index 0000000..5d40cb9 --- /dev/null +++ b/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 +) \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/database/request/SmsLoginRequest.kt b/app/src/main/java/com/example/firstapp/database/request/SmsLoginRequest.kt new file mode 100644 index 0000000..d76dce6 --- /dev/null +++ b/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, +) \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/database/request/SmsSendRequest.kt b/app/src/main/java/com/example/firstapp/database/request/SmsSendRequest.kt new file mode 100644 index 0000000..ea46ff7 --- /dev/null +++ b/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 +) \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/database/response/ContentResponse.kt b/app/src/main/java/com/example/firstapp/database/response/ContentResponse.kt index 9cfa6a5..9fb7e80 100644 --- a/app/src/main/java/com/example/firstapp/database/response/ContentResponse.kt +++ b/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? ) \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/database/response/OAuth2TokenResponse.kt b/app/src/main/java/com/example/firstapp/database/response/OAuth2TokenResponse.kt new file mode 100644 index 0000000..18ea0b5 --- /dev/null +++ b/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? +) \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/database/response/UserInfo.kt b/app/src/main/java/com/example/firstapp/database/response/UserInfo.kt index 3e5c46c..dcea53b 100644 --- a/app/src/main/java/com/example/firstapp/database/response/UserInfo.kt +++ b/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 ) \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/database/service/ApiService.kt b/app/src/main/java/com/example/firstapp/database/service/ApiService.kt index 3171ff6..2fbbb90 100644 --- a/app/src/main/java/com/example/firstapp/database/service/ApiService.kt +++ b/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) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/firstapp/database/service/ModelService.kt b/app/src/main/java/com/example/firstapp/database/service/ModelService.kt new file mode 100644 index 0000000..27bfbd9 --- /dev/null +++ b/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) + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/model/CardData.kt b/app/src/main/java/com/example/firstapp/model/CardData.kt new file mode 100644 index 0000000..1752dcf --- /dev/null +++ b/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() +} \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/model/CategoryConfig.kt b/app/src/main/java/com/example/firstapp/model/CategoryConfig.kt new file mode 100644 index 0000000..29a37e9 --- /dev/null +++ b/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 +) \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/model/CategoryConfigSync.kt b/app/src/main/java/com/example/firstapp/model/CategoryConfigSync.kt new file mode 100644 index 0000000..82bf6d0 --- /dev/null +++ b/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> +) \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/model/IncomeGroup.kt b/app/src/main/java/com/example/firstapp/model/IncomeGroup.kt new file mode 100644 index 0000000..d76f9f7 --- /dev/null +++ b/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 // 账户余额 +) \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/model/MemberBenefitItem.kt b/app/src/main/java/com/example/firstapp/model/MemberBenefitItem.kt new file mode 100644 index 0000000..179daa5 --- /dev/null +++ b/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 ) \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/model/MoreMemberBenefitItem.kt b/app/src/main/java/com/example/firstapp/model/MoreMemberBenefitItem.kt new file mode 100644 index 0000000..ceb96ad --- /dev/null +++ b/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) \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/model/StationGroup.kt b/app/src/main/java/com/example/firstapp/model/StationGroup.kt new file mode 100644 index 0000000..c37e008 --- /dev/null +++ b/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 +) \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/network/AuthInterceptor.kt b/app/src/main/java/com/example/firstapp/network/AuthInterceptor.kt new file mode 100644 index 0000000..1f1a191 --- /dev/null +++ b/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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/network/TokenExpiredInterceptor.kt b/app/src/main/java/com/example/firstapp/network/TokenExpiredInterceptor.kt new file mode 100644 index 0000000..1c73d50 --- /dev/null +++ b/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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt b/app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt index 3d732b4..9a611bb 100644 --- a/app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt +++ b/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") { // 获取当前时间 diff --git a/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt b/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt index 967a83c..21cb233 100644 --- a/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt +++ b/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() + } + } + + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/ui/home/HomeFragment.kt b/app/src/main/java/com/example/firstapp/ui/home/HomeFragment.kt index 230fb9c..3fcc370 100644 --- a/app/src/main/java/com/example/firstapp/ui/home/HomeFragment.kt +++ b/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() + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt b/app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt index 7b5c3fb..d40df61 100644 --- a/app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt +++ b/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() + } + } \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/ui/login/LoginViewModel.kt b/app/src/main/java/com/example/firstapp/ui/login/LoginViewModel.kt index f61b646..92f03e9 100644 --- a/app/src/main/java/com/example/firstapp/ui/login/LoginViewModel.kt +++ b/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) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/ui/notifications/NotificationsFragment.kt b/app/src/main/java/com/example/firstapp/ui/notifications/NotificationsFragment.kt index 38a6f08..91fcafb 100644 --- a/app/src/main/java/com/example/firstapp/ui/notifications/NotificationsFragment.kt +++ b/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) { diff --git a/app/src/main/java/com/example/firstapp/ui/vip/MemberInfoCardFragment.kt b/app/src/main/java/com/example/firstapp/ui/vip/MemberInfoCardFragment.kt new file mode 100644 index 0000000..0d3247e --- /dev/null +++ b/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 + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/ui/vip/MemberInfoCardViewModel.kt b/app/src/main/java/com/example/firstapp/ui/vip/MemberInfoCardViewModel.kt new file mode 100644 index 0000000..fa18bcf --- /dev/null +++ b/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 + } + } + + + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/util/CategoryDragCallback.kt b/app/src/main/java/com/example/firstapp/util/CategoryDragCallback.kt new file mode 100644 index 0000000..5c8c5ae --- /dev/null +++ b/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) { + // 不需要实现 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/util/SecureStorage.kt b/app/src/main/java/com/example/firstapp/util/SecureStorage.kt new file mode 100644 index 0000000..ee656c7 --- /dev/null +++ b/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() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/utils/PreferencesManager.kt b/app/src/main/java/com/example/firstapp/utils/PreferencesManager.kt new file mode 100644 index 0000000..34b85f4 --- /dev/null +++ b/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, "") ?: "" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/view/UnderlineTextView.kt b/app/src/main/java/com/example/firstapp/view/UnderlineTextView.kt new file mode 100644 index 0000000..b07faa0 --- /dev/null +++ b/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 + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/alipay_shape.xml b/app/src/main/res/drawable/alipay_shape.xml new file mode 100644 index 0000000..df5109c --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/drawable/black_button_background.xml b/app/src/main/res/drawable/black_button_background.xml new file mode 100644 index 0000000..73a1897 --- /dev/null +++ b/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> diff --git a/app/src/main/res/drawable/checkbox_round.xml b/app/src/main/res/drawable/checkbox_round.xml new file mode 100644 index 0000000..2f3d2fb --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/drawable/checkbox_round_selected.xml b/app/src/main/res/drawable/checkbox_round_selected.xml new file mode 100644 index 0000000..f3ce711 --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/drawable/dialog_background.xml b/app/src/main/res/drawable/dialog_background.xml new file mode 100644 index 0000000..530fb77 --- /dev/null +++ b/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> diff --git a/app/src/main/res/drawable/error_placeholder.xml b/app/src/main/res/drawable/error_placeholder.xml new file mode 100644 index 0000000..c556fa1 --- /dev/null +++ b/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> diff --git a/app/src/main/res/drawable/gold_border_shape.xml b/app/src/main/res/drawable/gold_border_shape.xml new file mode 100644 index 0000000..b660c93 --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/drawable/gray_border_shape.xml b/app/src/main/res/drawable/gray_border_shape.xml new file mode 100644 index 0000000..843e22f --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/drawable/home_add.xml b/app/src/main/res/drawable/home_add.xml new file mode 100644 index 0000000..7ece592 --- /dev/null +++ b/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> diff --git a/app/src/main/res/drawable/ic_category.xml b/app/src/main/res/drawable/ic_category.xml new file mode 100644 index 0000000..2c57814 --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_check.xml b/app/src/main/res/drawable/ic_check.xml new file mode 100644 index 0000000..a9a2051 --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_reminder.xml b/app/src/main/res/drawable/ic_reminder.xml new file mode 100644 index 0000000..310d415 --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_stats.xml b/app/src/main/res/drawable/ic_stats.xml new file mode 100644 index 0000000..957f134 --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/drawable/member_background.xml b/app/src/main/res/drawable/member_background.xml new file mode 100644 index 0000000..675c783 --- /dev/null +++ b/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> diff --git a/app/src/main/res/drawable/more_benefit_shape.xml b/app/src/main/res/drawable/more_benefit_shape.xml new file mode 100644 index 0000000..f305ef0 --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/drawable/overlay_black_background.xml b/app/src/main/res/drawable/overlay_black_background.xml new file mode 100644 index 0000000..5afeda5 --- /dev/null +++ b/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> diff --git a/app/src/main/res/drawable/round_red_bg.xml b/app/src/main/res/drawable/round_red_bg.xml new file mode 100644 index 0000000..8fcd59b --- /dev/null +++ b/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> diff --git a/app/src/main/res/drawable/round_transition_gray_bg.xml b/app/src/main/res/drawable/round_transition_gray_bg.xml new file mode 100644 index 0000000..0bcc740 --- /dev/null +++ b/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> diff --git a/app/src/main/res/drawable/set.xml b/app/src/main/res/drawable/set.xml index cec8ed9..4ff2b5e 100644 --- a/app/src/main/res/drawable/set.xml +++ b/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> diff --git a/app/src/main/res/drawable/tab_selected_background.xml b/app/src/main/res/drawable/tab_selected_background.xml new file mode 100644 index 0000000..e0872bf --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/drawable/up.png b/app/src/main/res/drawable/up.png new file mode 100644 index 0000000..f7379a8 --- /dev/null +++ b/app/src/main/res/drawable/up.png Binary files differ diff --git a/app/src/main/res/drawable/up.jpg b/app/src/main/res/drawable/up_back.jpg similarity index 100% rename from app/src/main/res/drawable/up.jpg rename to app/src/main/res/drawable/up_back.jpg Binary files differ diff --git a/app/src/main/res/drawable/vip_black_background.xml b/app/src/main/res/drawable/vip_black_background.xml new file mode 100644 index 0000000..c6e6a0b --- /dev/null +++ b/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> diff --git a/app/src/main/res/drawable/vip_white_background.xml b/app/src/main/res/drawable/vip_white_background.xml new file mode 100644 index 0000000..e5f1307 --- /dev/null +++ b/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> diff --git a/app/src/main/res/drawable/wechat_shape.xml b/app/src/main/res/drawable/wechat_shape.xml new file mode 100644 index 0000000..5c6381a --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_phone_login.xml b/app/src/main/res/layout/activity_phone_login.xml index 757b041..cfb7880 100644 --- a/app/src/main/res/layout/activity_phone_login.xml +++ b/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" diff --git a/app/src/main/res/layout/activity_vip.xml b/app/src/main/res/layout/activity_vip.xml new file mode 100644 index 0000000..2d46ec5 --- /dev/null +++ b/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" /> + + + <!-- 会员权益--动态--> + <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> \ No newline at end of file diff --git a/app/src/main/res/layout/content_vip.xml b/app/src/main/res/layout/content_vip.xml new file mode 100644 index 0000000..a2d7fbb --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_category_selector.xml b/app/src/main/res/layout/dialog_category_selector.xml new file mode 100644 index 0000000..6828364 --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml index ad51843..21efdba 100644 --- a/app/src/main/res/layout/fragment_dashboard.xml +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_first.xml b/app/src/main/res/layout/fragment_first.xml new file mode 100644 index 0000000..44baecd --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 18a5e11..5305951 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/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> diff --git a/app/src/main/res/layout/fragment_member_info_card.xml b/app/src/main/res/layout/fragment_member_info_card.xml new file mode 100644 index 0000000..d1789c5 --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_notifications.xml b/app/src/main/res/layout/fragment_notifications.xml index 23b5b81..e176684 100644 --- a/app/src/main/res/layout/fragment_notifications.xml +++ b/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> diff --git a/app/src/main/res/layout/fragment_second.xml b/app/src/main/res/layout/fragment_second.xml new file mode 100644 index 0000000..d074310 --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/layout/item_card.xml b/app/src/main/res/layout/item_card.xml new file mode 100644 index 0000000..9728346 --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/layout/item_card_continue_monthly.xml b/app/src/main/res/layout/item_card_continue_monthly.xml new file mode 100644 index 0000000..6e7cb52 --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/layout/item_card_single_month.xml b/app/src/main/res/layout/item_card_single_month.xml new file mode 100644 index 0000000..739b66c --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/layout/item_card_yearly.xml b/app/src/main/res/layout/item_card_yearly.xml new file mode 100644 index 0000000..b7b61fa --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/layout/item_category_selector.xml b/app/src/main/res/layout/item_category_selector.xml new file mode 100644 index 0000000..557638f --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/layout/item_income_group.xml b/app/src/main/res/layout/item_income_group.xml new file mode 100644 index 0000000..6ae5c47 --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/layout/item_income_package_home.xml b/app/src/main/res/layout/item_income_package_home.xml new file mode 100644 index 0000000..ef12f23 --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/layout/vip_protocol_dialog_custom.xml b/app/src/main/res/layout/vip_protocol_dialog_custom.xml new file mode 100644 index 0000000..94d6cab --- /dev/null +++ b/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> diff --git a/app/src/main/res/layout/vip_protocol_dialog_custom_bak.xml b/app/src/main/res/layout/vip_protocol_dialog_custom_bak.xml new file mode 100644 index 0000000..e3b23ec --- /dev/null +++ b/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> diff --git a/app/src/main/res/mipmap/avatar_default.png b/app/src/main/res/mipmap/avatar_default.png new file mode 100644 index 0000000..6b95b68 --- /dev/null +++ b/app/src/main/res/mipmap/avatar_default.png Binary files differ diff --git a/app/src/main/res/mipmap/overlay.png b/app/src/main/res/mipmap/overlay.png new file mode 100644 index 0000000..9905124 --- /dev/null +++ b/app/src/main/res/mipmap/overlay.png Binary files differ diff --git a/app/src/main/res/mipmap/overlay2.jpg b/app/src/main/res/mipmap/overlay2.jpg new file mode 100644 index 0000000..0dc91e8 --- /dev/null +++ b/app/src/main/res/mipmap/overlay2.jpg Binary files differ diff --git a/app/src/main/res/mipmap/vip.png b/app/src/main/res/mipmap/vip.png new file mode 100644 index 0000000..6cdf762 --- /dev/null +++ b/app/src/main/res/mipmap/vip.png Binary files differ diff --git a/app/src/main/res/mipmap/vip_ad_no.png b/app/src/main/res/mipmap/vip_ad_no.png new file mode 100644 index 0000000..981fdce --- /dev/null +++ b/app/src/main/res/mipmap/vip_ad_no.png Binary files differ diff --git a/app/src/main/res/mipmap/vip_ai_ass_icon.png b/app/src/main/res/mipmap/vip_ai_ass_icon.png new file mode 100644 index 0000000..a8ec7b9 --- /dev/null +++ b/app/src/main/res/mipmap/vip_ai_ass_icon.png Binary files differ diff --git a/app/src/main/res/mipmap/vip_alipay.png b/app/src/main/res/mipmap/vip_alipay.png new file mode 100644 index 0000000..d770490 --- /dev/null +++ b/app/src/main/res/mipmap/vip_alipay.png Binary files differ diff --git a/app/src/main/res/mipmap/vip_bar_sta.png b/app/src/main/res/mipmap/vip_bar_sta.png new file mode 100644 index 0000000..77a41cc --- /dev/null +++ b/app/src/main/res/mipmap/vip_bar_sta.png Binary files differ diff --git a/app/src/main/res/mipmap/vip_cancel.png b/app/src/main/res/mipmap/vip_cancel.png new file mode 100644 index 0000000..9fc891d --- /dev/null +++ b/app/src/main/res/mipmap/vip_cancel.png Binary files differ diff --git a/app/src/main/res/mipmap/vip_cancel_link.png b/app/src/main/res/mipmap/vip_cancel_link.png new file mode 100644 index 0000000..1f5f091 --- /dev/null +++ b/app/src/main/res/mipmap/vip_cancel_link.png Binary files differ diff --git a/app/src/main/res/mipmap/vip_card.png b/app/src/main/res/mipmap/vip_card.png new file mode 100644 index 0000000..75d9e76 --- /dev/null +++ b/app/src/main/res/mipmap/vip_card.png Binary files differ diff --git a/app/src/main/res/mipmap/vip_circle_pie.png b/app/src/main/res/mipmap/vip_circle_pie.png new file mode 100644 index 0000000..94e153d --- /dev/null +++ b/app/src/main/res/mipmap/vip_circle_pie.png Binary files differ diff --git a/app/src/main/res/mipmap/vip_copy.png b/app/src/main/res/mipmap/vip_copy.png new file mode 100644 index 0000000..c6a5e8a --- /dev/null +++ b/app/src/main/res/mipmap/vip_copy.png Binary files differ diff --git a/app/src/main/res/mipmap/vip_crown.png b/app/src/main/res/mipmap/vip_crown.png new file mode 100644 index 0000000..c86537f --- /dev/null +++ b/app/src/main/res/mipmap/vip_crown.png Binary files differ diff --git a/app/src/main/res/mipmap/vip_free_member.png b/app/src/main/res/mipmap/vip_free_member.png new file mode 100644 index 0000000..d6bd848 --- /dev/null +++ b/app/src/main/res/mipmap/vip_free_member.png Binary files differ diff --git a/app/src/main/res/mipmap/vip_life_insurance.png b/app/src/main/res/mipmap/vip_life_insurance.png new file mode 100644 index 0000000..445c54e --- /dev/null +++ b/app/src/main/res/mipmap/vip_life_insurance.png Binary files differ diff --git a/app/src/main/res/mipmap/vip_ling.png b/app/src/main/res/mipmap/vip_ling.png new file mode 100644 index 0000000..3de1c6a --- /dev/null +++ b/app/src/main/res/mipmap/vip_ling.png Binary files differ diff --git a/app/src/main/res/mipmap/vip_more.png b/app/src/main/res/mipmap/vip_more.png new file mode 100644 index 0000000..34dfb7f --- /dev/null +++ b/app/src/main/res/mipmap/vip_more.png Binary files differ diff --git a/app/src/main/res/mipmap/vip_no.png b/app/src/main/res/mipmap/vip_no.png new file mode 100644 index 0000000..e07563a --- /dev/null +++ b/app/src/main/res/mipmap/vip_no.png Binary files differ diff --git a/app/src/main/res/mipmap/vip_right.png b/app/src/main/res/mipmap/vip_right.png new file mode 100644 index 0000000..5d46f9f --- /dev/null +++ b/app/src/main/res/mipmap/vip_right.png Binary files differ diff --git a/app/src/main/res/mipmap/vip_wechat.png b/app/src/main/res/mipmap/vip_wechat.png new file mode 100644 index 0000000..d66bbb2 --- /dev/null +++ b/app/src/main/res/mipmap/vip_wechat.png Binary files differ diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml new file mode 100644 index 0000000..b22fff5 --- /dev/null +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml new file mode 100644 index 0000000..22d7f00 --- /dev/null +++ b/app/src/main/res/values-land/dimens.xml @@ -0,0 +1,3 @@ +<resources> + <dimen name="fab_margin">48dp</dimen> +</resources> \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml deleted file mode 100644 index 4925b55..0000000 --- a/app/src/main/res/values-night/themes.xml +++ /dev/null @@ -1,29 +0,0 @@ -<resources xmlns:tools="http://schemas.android.com/tools"> - <!-- Base application theme. --> - <style name="Theme.FirstApp" parent="Theme.MaterialComponents.DayNight.NoActionBar"> - <!-- Primary brand color. --> - <item name="colorPrimary">@color/purple_200</item> - <item name="colorPrimaryVariant">@color/purple_700</item> - <item name="colorOnPrimary">@color/black</item> - <!-- Secondary brand color. --> - <item name="colorSecondary">@color/teal_200</item> - <item name="colorSecondaryVariant">@color/teal_200</item> - <item name="colorOnSecondary">@color/black</item> - <!-- Status bar color. --> - <item name="android:statusBarColor">?attr/colorPrimaryVariant</item> - <!-- 确保没有ActionBar --> - <item name="windowActionBar">false</item> - <item name="windowNoTitle">true</item> - <!-- Customize your theme here. --> - </style> - - <style name="ThemeOverlay.FirstApp.FullscreenContainer" parent=""> - <item name="fullscreenBackgroundColor">@color/light_blue_900</item> - <item name="fullscreenTextColor">@color/light_blue_A400</item> - </style> - <!-- Base application theme. --> - <style name="Base.Theme.FirstApp" parent="Theme.Material3.DayNight.NoActionBar"> - <!-- Customize your dark theme here. --> - <!-- <item name="colorPrimary">@color/my_dark_primary</item> --> - </style> -</resources> \ No newline at end of file diff --git a/app/src/main/res/values-v23/themes.xml b/app/src/main/res/values-v23/themes.xml index b806642..940849b 100644 --- a/app/src/main/res/values-v23/themes.xml +++ b/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">--> +<!-- <!– 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>--> </resources> \ No newline at end of file diff --git a/app/src/main/res/values-w1240dp/dimens.xml b/app/src/main/res/values-w1240dp/dimens.xml new file mode 100644 index 0000000..d73f4a3 --- /dev/null +++ b/app/src/main/res/values-w1240dp/dimens.xml @@ -0,0 +1,3 @@ +<resources> + <dimen name="fab_margin">200dp</dimen> +</resources> \ No newline at end of file diff --git a/app/src/main/res/values-w600dp/dimens.xml b/app/src/main/res/values-w600dp/dimens.xml new file mode 100644 index 0000000..22d7f00 --- /dev/null +++ b/app/src/main/res/values-w600dp/dimens.xml @@ -0,0 +1,3 @@ +<resources> + <dimen name="fab_margin">48dp</dimen> +</resources> \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 29be459..dd87850 100644 --- a/app/src/main/res/values/dimens.xml +++ b/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> \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 02c10a9..74c31b4 100644 --- a/app/src/main/res/values/themes.xml +++ b/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=""> diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index d1689af..f8bbdd2 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/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>--> -- Gitblit v1.9.3