Merge branch 'master' of http://47.96.225.205:8888/r/FirstApp2
# Conflicts:
# app/build.gradle
# app/src/main/java/com/example/firstapp/ui/notifications/NotificationsFragment.kt
已删除1个文件
已修改31个文件
已重命名1个文件
已添加89个文件
| | |
| | | /.idea/workspace.xml |
| | | /.idea/navEditor.xml |
| | | /.idea/assetWizardSettings.xml |
| | | /.idea/deploymentTargetSelector.xml |
| | | /.idea/gradle.xml |
| | | /.idea/misc.xml |
| | | .DS_Store |
| | | /build |
| | | /captures |
| | |
| | | <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> |
| | |
| | | <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$" /> |
| | |
| | | <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"> |
| | |
| | | // 支付宝支付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' |
| | | |
| | | |
| | | } |
| | |
| | | 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" |
| | |
| | | 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"/> |
| | |
| | | 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 |
| | | |
| | |
| | | try { |
| | | context = applicationContext |
| | | initLibs() |
| | | |
| | | PreferencesManager.init(this) |
| | | //纯客户端模式 |
| | | if (SettingUtils.enablePureClientMode) return |
| | | |
| | |
| | | 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() { |
| | | // 安全防护关键词数组 |
| | |
| | | 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 -> |
| | |
| | | ) && permissions.getOrDefault(Manifest.permission.READ_SMS, false) -> { |
| | | // 两个权限都获得授权 |
| | | registerSmsReceiver() |
| | | // syncRecentSms() |
| | | // initializeSecurityKeywords() |
| | | } |
| | | |
| | | else -> { |
| | |
| | | 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) |
| | | |
| | |
| | | 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() { |
| | |
| | | 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() { |
| | |
| | | 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}") |
| | | } |
| | | } |
| | | } |
| | |
| | | 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" |
| | | } |
| | | |
| | |
| | | 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> |
对比新文件 |
| | |
| | | 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 |
| | | } |
| | | } |
| | |
| | | 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 |
| | |
| | | |
| | | private fun setupViews() { |
| | | binding.apply { |
| | | // 设置上次登录的手机号 |
| | | etPhone.setText(PreferencesManager.getLastLoginPhone()) |
| | | |
| | | btnBack.setOnClickListener { |
| | | startActivity(Intent(this@PhoneLoginActivity, LoginActivity::class.java)) |
| | | finish() |
| | |
| | | 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, |
| | |
| | | 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() |
| | |
| | | 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() |
| | | } |
| | |
| | | // 清空列表 |
| | | expressAdapter.submitList(emptyList()) |
| | | // 更新包裹数量显示 |
| | | binding.tvPackageCount.text = "共0个包裹" |
| | | binding.tvPackageCount.text = getCountText(0) |
| | | // 通知MainActivity刷新 |
| | | setResult(RESULT_OK) |
| | | } catch (e: Exception) { |
| | |
| | | // 如果出错则重新加载数据 |
| | | loadData() |
| | | } |
| | | } |
| | | } |
| | | |
| | | private fun getCountText(count: Int): String { |
| | | return when (pageType) { |
| | | TYPE_EXPRESS -> "共${count}个包裹" |
| | | TYPE_REPAYMENT -> "共${count}笔还款" |
| | | TYPE_INCOME -> "共${count}笔收入" |
| | | else -> "共${count}个" |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | expressAdapter.submitList(packages) |
| | | binding.tvStationName.text = stationName |
| | | binding.tvPackageCount.text = "共${packages.size}个包裹" |
| | | binding.tvPackageCount.text = getCountText(packages.size) |
| | | } |
| | | } |
| | | } |
对比新文件 |
| | |
| | | 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 |
| | | } |
| | | } |
对比新文件 |
| | |
| | | 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) |
| | | } |
| | | |
| | | } |
对比新文件 |
| | |
| | | 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) |
| | | |
| | | } |
| | | } |
对比新文件 |
| | |
| | | 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() |
| | | } |
对比新文件 |
| | |
| | | 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 |
| | | } |
| | | } |
| | |
| | | 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 |
| | | |
| | |
| | | """) |
| | | 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") |
| | |
| | | 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> |
| | | } |
| | |
| | | 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 |
| | | |
| | | |
| | |
| | | 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) |
| | | } |
| | | |
| | |
| | | |
| | | 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) |
| | | } |
| | | |
| | | } |
对比新文件 |
| | |
| | | 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 |
| | | ) |
对比新文件 |
| | |
| | | package com.example.firstapp.database.request |
| | | |
| | | data class SmsLoginRequest( |
| | | val username: String, |
| | | val smsCode: String, |
| | | val userType: String, |
| | | ) |
对比新文件 |
| | |
| | | package com.example.firstapp.database.request |
| | | |
| | | data class SmsSendRequest( |
| | | val tel: String, |
| | | val userType: String |
| | | ) |
| | |
| | | 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? |
| | | ) |
对比新文件 |
| | |
| | | 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? |
| | | ) |
| | |
| | | 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 |
| | | ) |
| | |
| | | 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调用接口 |
| | |
| | | @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) |
| | | |
| | | } |
| | | } |
对比新文件 |
| | |
| | | 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) |
| | | |
| | | } |
对比新文件 |
| | |
| | | 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() |
| | | } |
对比新文件 |
| | |
| | | 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 |
| | | ) |
对比新文件 |
| | |
| | | package com.example.firstapp.model |
| | | |
| | | data class CategoryConfigSync( |
| | | val userId: String, |
| | | val categories: List<CategoryConfig> |
| | | ) |
对比新文件 |
| | |
| | | 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 // 账户余额 |
| | | ) |
对比新文件 |
| | |
| | | package com.example.firstapp.model |
| | | |
| | | class MemberBenefitItem ( val iconRes: Int, |
| | | val text: String, |
| | | val isChecked: Boolean ) |
对比新文件 |
| | |
| | | package com.example.firstapp.model |
| | | |
| | | class MoreMemberBenefitItem (val iconRes: Int, |
| | | val text: String) |
对比新文件 |
| | |
| | | package com.example.firstapp.model |
| | | |
| | | data class StationGroup( |
| | | val stationName: String, |
| | | val count: Int |
| | | ) |
对比新文件 |
| | |
| | | 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) |
| | | } |
| | | } |
| | | } |
对比新文件 |
| | |
| | | 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 |
| | | } |
| | | } |
| | |
| | | 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 |
| | |
| | | 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") { |
| | | // 获取当前时间 |
| | |
| | | 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() { |
| | | |
| | |
| | | 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 |
| | | } |
| | |
| | | return binding.root |
| | | } |
| | | |
| | | override fun onResume() { |
| | | super.onResume() |
| | | // 重新加载用户信息 |
| | | loadUserInfo() |
| | | } |
| | | |
| | | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
| | | super.onViewCreated(view, savedInstanceState) |
| | | |
| | |
| | | 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 { |
| | |
| | | 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() |
| | | } |
| | | } |
| | | |
| | | } |
| | | } |
| | |
| | | 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这个方法创建后被调用,通常是初始化视图组件和观察者 |
| | |
| | | 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?) { |
| | |
| | | } |
| | | } |
| | | |
| | | private fun setupRecyclerViews() { |
| | | private fun setupAdapters() { |
| | | binding.expressRecycler.apply { |
| | | layoutManager = LinearLayoutManager(context) |
| | | expressAdapter = ExpressAdapter() |
| | |
| | | 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) |
| | | } |
| | |
| | | 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() { |
| | |
| | | // 设置初始状态 |
| | | 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 的显示内容。 |
| | |
| | | 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() { |
| | |
| | | .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() |
| | | } |
| | | } |
| | |
| | | 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 { |
| | | // 初始化时加载包裹列表数据 |
| | |
| | | // 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() |
| | | } |
| | | |
| | | } |
| | |
| | | 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() { |
| | |
| | | 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 = "网络错误,请稍后重试" |
| | |
| | | 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 = "网络错误,请稍后重试" |
| | |
| | | } |
| | | } |
| | | |
| | | fun logout() { |
| | | viewModelScope.launch { |
| | | // 不再清除用户数据,只执行登出操作 |
| | | homeViewModel.logout() |
| | | // 其他登出操作... |
| | | } |
| | | } |
| | | |
| | | private fun saveToken(token: String,phone:String) { |
| | | // TODO: 实现token存储逻辑 |
| | | // 可能还需要存储 refresh_token |
| | | PreferencesManager.saveToken(token) |
| | | // 保存登录的手机号 |
| | | PreferencesManager.savePhone(phone) |
| | | } |
| | | |
| | | } |
| | |
| | | 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() { |
| | | |
| | |
| | | |
| | | 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 { |
| | |
| | | |
| | | // 隐私协议 |
| | | binding.layoutPrivacy.setOnClickListener { |
| | | startContentActivity("privacy_policy", "隐私协议") |
| | | startContentActivity("隐私协议", "隐私协议") |
| | | } |
| | | |
| | | // 使用教程 |
| | | binding.layoutTutorial.setOnClickListener { |
| | | startContentActivity("user_guide", "使用教程") |
| | | startContentActivity("使用教程", "使用教程") |
| | | } |
| | | |
| | | // 头像点击老的处理逻辑 |
| | |
| | | |
| | | // 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() { |
| | |
| | | } |
| | | } |
| | | |
| | | 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) |
| | |
| | | |
| | | 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 |
| | |
| | | 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) { |
对比新文件 |
| | |
| | | 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 |
| | | } |
| | | |
| | | |
| | | } |
对比新文件 |
| | |
| | | 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 |
| | | } |
| | | } |
| | | |
| | | |
| | | } |
| | | } |
| | | |
| | | |
| | | } |
对比新文件 |
| | |
| | | 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) { |
| | | // 不需要实现 |
| | | } |
| | | } |
对比新文件 |
| | |
| | | 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() |
| | | } |
| | | } |
对比新文件 |
| | |
| | | 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, "") ?: "" |
| | | } |
| | | } |
对比新文件 |
| | |
| | | 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 |
| | | ) |
| | | } |
| | | } |
| | | } |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
| | |
| | | <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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
| | |
| | | android:layout_height="wrap_content" |
| | | android:background="@null" |
| | | android:hint="请输入手机号" |
| | | android:text="17712345678" |
| | | android:inputType="phone" |
| | | android:maxLength="11" |
| | | android:textSize="16sp" |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
| | |
| | | </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> |
对比新文件 |
| | |
| | | <?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> |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
| | |
| | | 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> |
| | | |
| | |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginTop="4dp" |
| | | android:text="续费畅享更多会员权益" |
| | | android:textColor="#B8741A" |
| | | android:textColor="#F0DCBF" |
| | | android:textSize="12sp" |
| | | android:textStyle="bold"/> |
| | | </LinearLayout> |
| | |
| | | android:layout_height="wrap_content" |
| | | android:backgroundTint="#7A441E" |
| | | android:text="立即续费" |
| | | android:textColor="#FFFFFF" /> |
| | | android:textColor="#F0DCBF" /> |
| | | </LinearLayout> |
| | | </androidx.cardview.widget.CardView> |
| | | |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <resources> |
| | | <dimen name="fab_margin">48dp</dimen> |
| | | </resources> |
| | |
| | | <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> |
对比新文件 |
| | |
| | | <resources> |
| | | <dimen name="fab_margin">200dp</dimen> |
| | | </resources> |
对比新文件 |
| | |
| | | <resources> |
| | | <dimen name="fab_margin">48dp</dimen> |
| | | </resources> |
| | |
| | | <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> |
| | |
| | | </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=""> |
| | |
| | | <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>--> |