| | |
| | | package com.example.firstapp.ui.invitation |
| | | |
| | | import android.app.Dialog |
| | | import android.content.ClipData |
| | | import android.content.ClipboardManager |
| | | import android.content.Context |
| | | import android.content.Intent |
| | | import android.graphics.Bitmap |
| | | import android.graphics.Color |
| | | import android.graphics.drawable.BitmapDrawable |
| | | import android.graphics.drawable.ColorDrawable |
| | | import android.net.Uri |
| | | import android.os.Bundle |
| | | import android.os.Handler |
| | |
| | | import android.text.Html |
| | | import android.util.TypedValue |
| | | import android.view.View |
| | | import android.view.ViewGroup |
| | | import android.widget.Button |
| | | import android.widget.ImageView |
| | | import android.widget.LinearLayout |
| | | import android.widget.TextView |
| | | import android.widget.Toast |
| | | import androidx.appcompat.app.AppCompatActivity |
| | | import androidx.core.content.ContextCompat |
| | | import androidx.core.content.FileProvider |
| | | import androidx.lifecycle.lifecycleScope |
| | | import androidx.recyclerview.widget.LinearLayoutManager |
| | | import androidx.recyclerview.widget.LinearSmoothScroller |
| | | import androidx.recyclerview.widget.RecyclerView |
| | | import com.example.firstapp.App.Companion.context |
| | | import com.example.firstapp.R |
| | | import com.example.firstapp.adapter.InvitationAdapter |
| | | import com.example.firstapp.adapter.InvitationRecord2Adapter |
| | | import com.example.firstapp.adapter.InvitationRecordAdapter |
| | | import com.example.firstapp.database.request.SmsLoginRequest |
| | | import com.example.firstapp.database.service.RetrofitClient |
| | | import com.example.firstapp.entity.InvitationRecord |
| | | import com.example.firstapp.utils.Log |
| | | import com.example.firstapp.utils.PreferencesManager |
| | | import kotlinx.coroutines.Dispatchers |
| | | import kotlinx.coroutines.launch |
| | | import kotlinx.coroutines.withContext |
| | | import java.io.File |
| | | import java.io.FileOutputStream |
| | | import kotlin.math.abs |
| | | |
| | | class InvitationActivity : AppCompatActivity() { |
| | |
| | | private lateinit var recyclerSuccessView: RecyclerView |
| | | private lateinit var recyclerRecordView: RecyclerView |
| | | private lateinit var adapter: InvitationAdapter |
| | | private lateinit var recordadapter: InvitationRecordAdapter |
| | | private val data = mutableListOf<InvitationRecord>() |
| | | private val recorddata = mutableListOf<InvitationRecord>() |
| | | // private lateinit var recordadapter: InvitationRecordAdapter |
| | | private var data = mutableListOf<InvitationRecord>() |
| | | private var recorddata = mutableListOf<InvitationRecord>() |
| | | private val handler = Handler(Looper.getMainLooper()) |
| | | private val scrollInterval = 3000L |
| | | private var currentScrollPosition = 0 |
| | | private var currentRecordScrollPosition = 0 |
| | | private var itemHeight = 0 // 动态存储item高度 |
| | | |
| | | private lateinit var invitedRecordRecyclerView2: RecyclerView |
| | | private lateinit var invitationAdapter: InvitationRecord2Adapter |
| | | |
| | | |
| | | |
| | | override fun onCreate(savedInstanceState: Bundle?) { |
| | | super.onCreate(savedInstanceState) |
| | |
| | | //初始化Adapter |
| | | initSuccessAdapter() |
| | | |
| | | initRecorddapter() |
| | | // initRecorddapter() |
| | | |
| | | //加载数据 |
| | | loadData() |
| | | |
| | | loadRecordData() |
| | | getInvitereward() |
| | | |
| | | //启动轮播 |
| | | startAutoScroll() |
| | |
| | | //分享 |
| | | val btnInvite = findViewById<Button>(R.id.btnInvite) |
| | | btnInvite.setOnClickListener { |
| | | shareImageToWechat() |
| | | showImagePreviewDialog() |
| | | } |
| | | |
| | | //邀请码 |
| | | val invitationCodeText = findViewById<TextView>(R.id.invitationCodeText) |
| | | invitationCodeText.text = "A1B2" |
| | | //invitationCodeText.text = formatInvitationCode(PreferencesManager.getInviteCode()); |
| | | invitationCodeText.text = formatInvitationCode(PreferencesManager.getInviteCode()); |
| | | findViewById<Button>(R.id.copyButton).setOnClickListener { |
| | | val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager |
| | | val clip = ClipData.newPlainText( |
| | |
| | | clipboard.setPrimaryClip(clip) |
| | | Toast.makeText(this, "已复制邀请码", Toast.LENGTH_SHORT).show() |
| | | } |
| | | |
| | | |
| | | } |
| | | |
| | | |
| | | private fun initViews() { |
| | | findViewById<TextView>(R.id.tv_notic).apply { |
| | | text = Html.fromHtml(getString(R.string.invite_reward_text), Html.FROM_HTML_MODE_LEGACY) |
| | | } |
| | | |
| | | // 成功列表 |
| | | recyclerSuccessView = findViewById(R.id.invitationsuccessRecyclerView) |
| | | addSuccessListener() |
| | | |
| | | recyclerRecordView = findViewById(R.id.invitationrecordRecyclerView) |
| | | addRecordListener() |
| | | } |
| | | |
| | | private fun addSuccessListener() { |
| | | recyclerSuccessView.viewTreeObserver.addOnGlobalLayoutListener { |
| | | if (recyclerSuccessView.childCount > 0) { |
| | | // 计算预期高度(60dp转px) |
| | | val expectedHeight = dpToPx(60f) |
| | | if (itemHeight != expectedHeight) { |
| | | // 修正所有item的高度 |
| | | for (i in 0 until recyclerSuccessView.childCount) { |
| | | recyclerSuccessView.getChildAt(i).layoutParams.height = expectedHeight |
| | | } |
| | | // 请求重新布局 |
| | | recyclerSuccessView.requestLayout() |
| | | itemHeight = expectedHeight |
| | | } |
| | | } |
| | | } |
| | | recyclerSuccessView.layoutManager = object : LinearLayoutManager(this@InvitationActivity) { |
| | | override fun onMeasure( |
| | | recycler: RecyclerView.Recycler, state: RecyclerView.State, |
| | | widthSpec: Int, heightSpec: Int |
| | | ) { |
| | | if (itemHeight > 0) { |
| | | // 使用实际测量的高度 |
| | | setMeasuredDimension( |
| | | View.resolveSize(widthSpec, width), |
| | | itemHeight |
| | | ) |
| | | } else { |
| | | // 默认高度60dp |
| | | val defaultHeight = TypedValue.applyDimension( |
| | | TypedValue.COMPLEX_UNIT_DIP, 60f, |
| | | resources.displayMetrics |
| | | ).toInt() |
| | | setMeasuredDimension( |
| | | View.resolveSize(widthSpec, width), |
| | | defaultHeight |
| | | ) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private fun addRecordListener() { |
| | | recyclerRecordView.viewTreeObserver.addOnGlobalLayoutListener { |
| | | if (recyclerSuccessView.childCount > 0) { |
| | | // 计算预期高度(60dp转px) |
| | | val expectedHeight = dpToPx(60f) |
| | | if (itemHeight != expectedHeight) { |
| | | // 修正所有item的高度 |
| | | for (i in 0 until recyclerSuccessView.childCount) { |
| | | recyclerSuccessView.getChildAt(i).layoutParams.height = expectedHeight |
| | | } |
| | | // 请求重新布局 |
| | | recyclerSuccessView.requestLayout() |
| | | itemHeight = expectedHeight |
| | | } |
| | | } |
| | | } |
| | | recyclerRecordView.layoutManager = object : LinearLayoutManager(this@InvitationActivity) { |
| | | |
| | | recyclerSuccessView.layoutManager = object : LinearLayoutManager(this) { |
| | | override fun onMeasure( |
| | | recycler: RecyclerView.Recycler, state: RecyclerView.State, |
| | | recycler: RecyclerView.Recycler, |
| | | state: RecyclerView.State, |
| | | widthSpec: Int, heightSpec: Int |
| | | ) { |
| | | if (itemHeight > 0) { |
| | | // 使用实际测量的高度 |
| | | setMeasuredDimension( |
| | | View.resolveSize(widthSpec, width), |
| | | itemHeight |
| | | ) |
| | | } else { |
| | | // 默认高度60dp |
| | | val defaultHeight = TypedValue.applyDimension( |
| | | TypedValue.COMPLEX_UNIT_DIP, 60f, |
| | | resources.displayMetrics |
| | |
| | | } |
| | | } |
| | | } |
| | | // 记录列表 |
| | | // recyclerRecordView = findViewById(R.id.invitationrecordRecyclerView) |
| | | // recyclerRecordView.layoutManager = LinearLayoutManager(this) |
| | | } |
| | | |
| | | private fun initSuccessAdapter() { |
| | |
| | | recyclerSuccessView.adapter = adapter |
| | | } |
| | | |
| | | private fun initRecorddapter() { |
| | | recordadapter = InvitationRecordAdapter(this, recorddata).apply { |
| | | registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { |
| | | override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { |
| | | // 数据插入时检查高度 |
| | | recyclerRecordView.post { |
| | | if (recyclerRecordView.childCount > 0) { |
| | | itemHeight = recyclerRecordView.getChildAt(0).height |
| | | // private fun initRecorddapter() { |
| | | // recordadapter = InvitationRecordAdapter(this, recorddata) |
| | | // recyclerRecordView.adapter = recordadapter |
| | | // } |
| | | // private fun initRecorddapter() { |
| | | // recordadapter = InvitationRecordAdapter(this, recorddata).apply { |
| | | // registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { |
| | | // override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { |
| | | // // 数据插入时检查高度 |
| | | // recyclerRecordView.post { |
| | | // if (recyclerRecordView.childCount > 0) { |
| | | // itemHeight = recyclerRecordView.getChildAt(0).height |
| | | // } |
| | | // } |
| | | // } |
| | | // }) |
| | | // } |
| | | // recyclerRecordView.adapter = recordadapter |
| | | // } |
| | | |
| | | private fun getInvitereward() { |
| | | lifecycleScope.launch { |
| | | try { |
| | | val response = RetrofitClient.apiService.getInvitereward() |
| | | if (response.code == "0") { |
| | | response.data?.let { records -> |
| | | // 正确更新现有列表(保持原有引用) |
| | | data.apply { |
| | | clear() |
| | | addAll(records.successInvite ?: emptyList()) |
| | | } |
| | | |
| | | recorddata.apply { |
| | | clear() |
| | | addAll(records.myInvite ?: emptyList()) // 注意这里使用myInvite |
| | | } |
| | | |
| | | // 加载适配器 |
| | | invitedRecordRecyclerView2 = findViewById(R.id.invited_record_recycler_view_2) |
| | | invitedRecordRecyclerView2.layoutManager = LinearLayoutManager(this@InvitationActivity) |
| | | invitationAdapter = InvitationRecord2Adapter(records.myInvite) |
| | | invitedRecordRecyclerView2.adapter = invitationAdapter |
| | | |
| | | |
| | | // 在UI线程更新适配器 |
| | | withContext(Dispatchers.Main) { |
| | | adapter.notifyDataSetChanged() |
| | | // recordadapter.notifyDataSetChanged() |
| | | } |
| | | } ?: run { |
| | | Log.w("API", "Response data is null") |
| | | } |
| | | } else { |
| | | Log.w("API", "Server error: ${response.msg}") |
| | | } |
| | | }) |
| | | } catch (e: Exception) { |
| | | Log.e("getinviterewardError", "message: ${e.message}", e) |
| | | withContext(Dispatchers.Main) { |
| | | Toast.makeText(context, "请求失败: ${e.message}", Toast.LENGTH_SHORT).show() |
| | | } |
| | | } |
| | | } |
| | | recyclerRecordView.adapter = recordadapter |
| | | } |
| | | |
| | | private fun loadData() { |
| | | data.clear() |
| | | data.addAll( |
| | | listOf( |
| | | InvitationRecord("H****e", "获得了1天会员", "已注册"), |
| | | InvitationRecord("U****r", "获得了2天会员", "已注册"), |
| | | InvitationRecord("A****e", "获得了免广告特权", "已注册"), |
| | | InvitationRecord("B****e", "获得了3天会员", "已注册"), |
| | | InvitationRecord("C****o", "获得了4天会员", "已注册") |
| | | ) |
| | | ) |
| | | adapter.notifyDataSetChanged() |
| | | private fun showImagePreviewDialog() { |
| | | val dialog = Dialog(this, android.R.style.Theme_Translucent_NoTitleBar).apply { |
| | | // 设置全屏+透明背景 |
| | | window?.apply { |
| | | setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) |
| | | setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) |
| | | } |
| | | |
| | | setContentView(R.layout.dialog_image_preview) |
| | | setCancelable(true) // 点击空白处可关闭 |
| | | |
| | | val imageView = findViewById<ImageView>(R.id.ivPreview) |
| | | |
| | | // 长按分享(不关闭对话框) |
| | | imageView.setOnLongClickListener { |
| | | shareImageToWechat() |
| | | true |
| | | } |
| | | |
| | | // 点击图片关闭(可选) |
| | | imageView.setOnClickListener { |
| | | dismiss() |
| | | } |
| | | } |
| | | |
| | | dialog.show() |
| | | } |
| | | |
| | | private fun loadRecordData() { |
| | | recorddata.clear() |
| | | recorddata.addAll( |
| | | listOf( |
| | | InvitationRecord("M****e", "", "未注册"), |
| | | InvitationRecord("Q****r", "", "已注册"), |
| | | InvitationRecord("W****e", "", "未注册"), |
| | | InvitationRecord("E****e", "", "未注册"), |
| | | InvitationRecord("R****o", "", "已注册") |
| | | ) |
| | | ) |
| | | recordadapter.notifyDataSetChanged() |
| | | } |
| | | |
| | | // 分享资源图片到微信 |
| | | private fun shareImageToWechat() { |
| | | try { |
| | | // 获取资源图片的URI |
| | | val imageUri = Uri.parse("android.resource://${packageName}/${R.drawable.location}") |
| | | // 1. 将 drawable 图片保存到缓存目录 |
| | | val drawable = ContextCompat.getDrawable(this, R.drawable.invite)!! |
| | | val bitmap = (drawable as BitmapDrawable).bitmap |
| | | |
| | | // 2. 创建临时图片文件 |
| | | val cacheDir = File(cacheDir, "shared_images").apply { mkdirs() } |
| | | val imageFile = File(cacheDir, "invite.jpg") // 指定文件名 |
| | | FileOutputStream(imageFile).use { out -> |
| | | bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out) |
| | | } |
| | | |
| | | // 3. 使用 FileProvider 获取 URI |
| | | val imageUri = FileProvider.getUriForFile( |
| | | this, |
| | | "${packageName}.fileprovider", |
| | | imageFile |
| | | ) |
| | | |
| | | // 4. 创建分享 Intent |
| | | val intent = Intent().apply { |
| | | action = Intent.ACTION_SEND |
| | | type = "image/*" |
| | | type = "image/jpeg" |
| | | putExtra(Intent.EXTRA_STREAM, imageUri) |
| | | flags = Intent.FLAG_GRANT_READ_URI_PERMISSION |
| | | setPackage("com.tencent.mm") // 指定微信包名 |
| | | } |
| | | |
| | | // 创建选择器,即使微信不可用也能选择其他应用 |
| | | // 5. 启动分享选择器 |
| | | val chooserIntent = Intent.createChooser(intent, "分享邀请图片") |
| | | |
| | | // 检查是否有应用能处理这个Intent |
| | | if (intent.resolveActivity(packageManager) != null) { |
| | | startActivity(chooserIntent) |
| | | } else { |
| | |
| | | } |
| | | |
| | | private fun formatInvitationCode(code: String): String { |
| | | return if (code.length > 2) { |
| | | code.chunked(2).joinToString(" ") |
| | | return if (code.length > 1) { |
| | | code.chunked(1).joinToString(" ") |
| | | } else { |
| | | code |
| | | } |
| | |
| | | |
| | | // 使用取模运算确保位置在有效范围内 |
| | | currentScrollPosition++ |
| | | currentRecordScrollPosition++ |
| | | |
| | | // 创建自定义平滑滚动器 |
| | | val smoothScroller = object : LinearSmoothScroller(this@InvitationActivity) { |
| | |
| | | targetPosition = currentScrollPosition |
| | | } |
| | | |
| | | val smoothRecordScroller = object : LinearSmoothScroller(this@InvitationActivity) { |
| | | override fun getVerticalSnapPreference(): Int = SNAP_TO_START |
| | | |
| | | override fun calculateDyToMakeVisible(view: View, snapPreference: Int): Int { |
| | | // 计算需要滚动的距离,确保完整显示下一个item |
| | | val top = view.top |
| | | val height = view.height |
| | | return when { |
| | | snapPreference == SNAP_TO_START -> -top |
| | | snapPreference == SNAP_TO_END -> -(top - (recyclerRecordView.height - height)) |
| | | else -> -(top - (recyclerRecordView.height / 2 - height / 2)) |
| | | } |
| | | } |
| | | |
| | | override fun calculateTimeForScrolling(dx: Int): Int { |
| | | // 根据滚动距离动态计算时间,保持匀速 |
| | | return maxOf((500f * abs(dx) / itemHeight).toInt(), 200) |
| | | } |
| | | }.apply { |
| | | targetPosition = currentRecordScrollPosition |
| | | } |
| | | |
| | | // 启动平滑滚动 |
| | | recyclerSuccessView.layoutManager?.startSmoothScroll(smoothScroller) |
| | | |
| | | recyclerRecordView.layoutManager?.startSmoothScroll(smoothRecordScroller) |
| | | |
| | | // 更智能的边界检测 |
| | | (recyclerSuccessView.layoutManager as? LinearLayoutManager)?.let { lm -> |
| | |
| | | } |
| | | } |
| | | |
| | | (recyclerRecordView.layoutManager as? LinearLayoutManager)?.let { lm -> |
| | | val lastVisible = lm.findLastVisibleItemPosition() |
| | | val totalItems = recordadapter.itemCount |
| | | |
| | | // 当接近"虚拟列表"末尾时,跳转到中间位置 |
| | | if (lastVisible >= totalItems - 3) { |
| | | val jumpPosition = (totalItems / 2) * (Int.MAX_VALUE / totalItems) |
| | | currentRecordScrollPosition = jumpPosition |
| | | recyclerRecordView.scrollToPosition(jumpPosition) |
| | | } |
| | | } |
| | | |
| | | handler.postDelayed(this, scrollInterval) |
| | | } |
| | | } |
| | | |
| | | private fun pxToDp(px: Int, context: Context): Int { |
| | | return (px / (context.resources.displayMetrics.density)).toInt() |
| | | } |
| | | |
| | | private fun dpToPx(dp: Float): Int { |