This commit is contained in:
pengxiaolong
2026-01-15 13:01:31 +08:00
parent c1a80dd4cf
commit a1fbc6417f
219 changed files with 25793 additions and 951 deletions

View File

@@ -1,6 +1,7 @@
package com.example.myapplication
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
@@ -10,6 +11,7 @@ import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import com.example.myapplication.network.AuthEvent
import com.example.myapplication.network.AuthEventBus
import com.example.myapplication.network.BehaviorReporter
import com.example.myapplication.utils.EncryptedSharedPreferencesUtil
import com.google.android.material.bottomnavigation.BottomNavigationView
import kotlinx.coroutines.flow.collectLatest
@@ -59,6 +61,45 @@ class MainActivity : AppCompatActivity() {
private val globalNavController: NavController
get() = globalHost.navController
// =======================
// 全局路由埋点:新增字段
// =======================
private val ROUTE_TAG = "RouteReport"
private var lastHomeDestIdForReport: Int? = null
private var lastShopDestIdForReport: Int? = null
private var lastMineDestIdForReport: Int? = null
private var lastGlobalDestIdForReport: Int? = null
// 统一 listener方便 add/remove
private val homeRouteListener =
NavController.OnDestinationChangedListener { _, dest, _ ->
if (lastHomeDestIdForReport == dest.id) return@OnDestinationChangedListener
lastHomeDestIdForReport = dest.id
reportPageView(source = "home_tab", destId = dest.id)
}
private val shopRouteListener =
NavController.OnDestinationChangedListener { _, dest, _ ->
if (lastShopDestIdForReport == dest.id) return@OnDestinationChangedListener
lastShopDestIdForReport = dest.id
reportPageView(source = "shop_tab", destId = dest.id)
}
private val mineRouteListener =
NavController.OnDestinationChangedListener { _, dest, _ ->
if (lastMineDestIdForReport == dest.id) return@OnDestinationChangedListener
lastMineDestIdForReport = dest.id
reportPageView(source = "mine_tab", destId = dest.id)
}
private val globalRouteListener =
NavController.OnDestinationChangedListener { _, dest, _ ->
if (lastGlobalDestIdForReport == dest.id) return@OnDestinationChangedListener
lastGlobalDestIdForReport = dest.id
reportPageView(source = "global_overlay", destId = dest.id)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
@@ -105,9 +146,11 @@ class MainActivity : AppCompatActivity() {
openGlobal(R.id.loginFragment)
}
}
is AuthEvent.GenericError -> {
Toast.makeText(this@MainActivity, event.message, Toast.LENGTH_SHORT).show()
Toast.makeText(this@MainActivity, event.message, Toast.LENGTH_LONG).show()
}
// 登录成功事件处理
is AuthEvent.LoginSuccess -> {
// 关闭 global overlay回到 empty
@@ -123,24 +166,46 @@ class MainActivity : AppCompatActivity() {
}
}
pendingTabAfterLogin = null
// 处理intent跳转目标页
if (pendingNavigationAfterLogin == "recharge_fragment") {
openGlobal(R.id.rechargeFragment)
pendingNavigationAfterLogin = null
}
// ✅ 登录成功后也刷新一次
bottomNav.post { updateBottomNavVisibility() }
}
// 登出事件处理
is AuthEvent.Logout -> {
pendingTabAfterLogin = event.returnTabTag
// ✅ 用户没登录按返回,应回首页,所以先切到首页
switchTab(TAB_HOME, force = true)
bottomNav.post {
bottomNav.selectedItemId = R.id.home_graph
openGlobal(R.id.loginFragment) // ✅ 退出登录后立刻打开登录页
}
}
// 打开全局页面事件处理
is AuthEvent.OpenGlobalPage -> {
// 打开指定的全局页面
openGlobal(event.destinationId, event.bundle)
}
is AuthEvent.UserUpdated -> {
// 不需要处理
}
is AuthEvent.CharacterDeleted -> {
// 不需要处理
}
is AuthEvent.CharacterAdded -> {
// 不需要处理由HomeFragment处理
}
}
}
}
@@ -158,9 +223,27 @@ class MainActivity : AppCompatActivity() {
TAB_MINE -> R.id.mine_graph
else -> R.id.home_graph
}
updateBottomNavVisibility()
}
}
override fun onResume() {
super.onResume()
// ✅ 最终兜底:从后台回来 / 某些场景没触发 listener也能恢复底栏
bottomNav.post { updateBottomNavVisibility() }
}
override fun onDestroy() {
// ✅ 防泄漏移除路由监听Activity 销毁时)
runCatching {
homeHost.navController.removeOnDestinationChangedListener(homeRouteListener)
shopHost.navController.removeOnDestinationChangedListener(shopRouteListener)
mineHost.navController.removeOnDestinationChangedListener(mineRouteListener)
globalHost.navController.removeOnDestinationChangedListener(globalRouteListener)
}
super.onDestroy()
}
private fun initHosts() {
val fm = supportFragmentManager
@@ -196,22 +279,59 @@ class MainActivity : AppCompatActivity() {
// 绑定全局导航可见性监听
bindGlobalVisibility()
// 绑定底部导航栏可见性监听
bindBottomNavVisibilityForTabs()
// ✅ 全局路由埋点监听(每次导航变化上报)
bindGlobalRouteReporting()
bottomNav.post { updateBottomNavVisibility() }
}
/**
* 这些页面需要隐藏底部导航栏:你按需加/减
*/
private fun shouldHideBottomNav(destId: Int): Boolean {
return destId in setOf(
R.id.searchFragment,
R.id.searchResultFragment,
R.id.MySkin,
R.id.notificationFragment,
R.id.feedbackFragment,
R.id.MyKeyboard,
R.id.PersonalSettings,
)
}
/**
* ✅ 统一底栏显隐逻辑:任何地方状态变化都调用它
*/
private fun updateBottomNavVisibility() {
// ✅ 只要 global overlay 不在 empty底栏必须隐藏用 NavController 判断,别用 View.visibility
if (isGlobalVisible()) {
bottomNav.visibility = View.GONE
return
}
// 否则按“当前可见 tab 的当前目的地”判断
val destId = currentTabNavController.currentDestination?.id
bottomNav.visibility =
if (destId != null && shouldHideBottomNav(destId)) View.GONE else View.VISIBLE
}
private fun bindGlobalVisibility() {
globalNavController.addOnDestinationChangedListener { _, dest, _ ->
val isEmpty = dest.id == R.id.globalEmptyFragment
findViewById<View>(R.id.global_container).visibility =
if (isEmpty) View.GONE else View.VISIBLE
bottomNav.visibility =
if (isEmpty) View.VISIBLE else View.GONE
// ✅ 只在"刚从某个全局页关闭回 empty"时触发回退逻辑
val justClosedOverlay = (dest.id == R.id.globalEmptyFragment && lastGlobalDestId != R.id.globalEmptyFragment)
findViewById<View>(R.id.global_container).visibility =
if (isEmpty) View.GONE else View.VISIBLE
// ✅ 底栏统一走 update
updateBottomNavVisibility()
val justClosedOverlay =
(dest.id == R.id.globalEmptyFragment && lastGlobalDestId != R.id.globalEmptyFragment)
lastGlobalDestId = dest.id
if (justClosedOverlay) {
@@ -220,16 +340,17 @@ class MainActivity : AppCompatActivity() {
TAB_MINE -> R.id.mine_graph
else -> R.id.home_graph
}
// 未登录且当前处在受保护tab强制回首页
if (!isLoggedIn() && currentTabGraphId in protectedTabs) {
switchTab(TAB_HOME, force = true)
bottomNav.selectedItemId = R.id.home_graph
}
// ✅ 只有"没登录就关闭登录页"才清 pending
if (!isLoggedIn()) {
pendingTabAfterLogin = null
}
bottomNav.post { updateBottomNavVisibility() }
}
}
}
@@ -238,7 +359,7 @@ class MainActivity : AppCompatActivity() {
if (!force && targetTag == currentTabTag) return
val fm = supportFragmentManager
if (fm.isStateSaved) return // ✅ 防崩stateSaved 时不做事务
if (fm.isStateSaved) return
currentTabTag = targetTag
@@ -255,57 +376,80 @@ class MainActivity : AppCompatActivity() {
}
}
.commit()
// ✅ 关键hide/show 切 tab 不会触发 destinationChanged所以手动刷新
bottomNav.post { updateBottomNavVisibility() }
// ✅ 新增:切 tab 后补一次路由上报(不改变其它逻辑)
if (!force) {
currentTabNavController.currentDestination?.id?.let { destId ->
reportPageView(source = "switch_tab", destId = destId)
}
}
}
/** 打开全局页login/recharge等 */
private fun openGlobal(destId: Int, bundle: Bundle? = null) {
val fm = supportFragmentManager
if (fm.isStateSaved) return // ✅ 防崩
if (fm.isStateSaved) return
try {
if (bundle != null) {
globalNavController.navigate(destId, bundle)
} else {
globalNavController.navigate(destId)
}
if (bundle != null) globalNavController.navigate(destId, bundle)
else globalNavController.navigate(destId)
} catch (e: IllegalArgumentException) {
// 可选:防止偶发重复 navigate 崩溃
e.printStackTrace()
}
bottomNav.post { updateBottomNavVisibility() }
}
/** 关闭全局页pop到 empty */
/** Tab 内页面变化时刷新底栏显隐 */
private fun bindBottomNavVisibilityForTabs() {
fun shouldHideBottomNav(destId: Int): Boolean {
return destId in setOf(
R.id.searchFragment,
R.id.searchResultFragment,
R.id.MySkin
// 你还有其他需要全屏的页,也加在这里
)
val listener = NavController.OnDestinationChangedListener { _, _, _ ->
updateBottomNavVisibility()
}
val listener = NavController.OnDestinationChangedListener { _, dest, _ ->
// 只要 global overlay 打开了,仍然以 overlay 为准(你已有逻辑)
if (isGlobalVisible()) return@OnDestinationChangedListener
bottomNav.visibility = if (shouldHideBottomNav(dest.id)) View.GONE else View.VISIBLE
}
homeHost.navController.addOnDestinationChangedListener(listener)
shopHost.navController.addOnDestinationChangedListener(listener)
mineHost.navController.addOnDestinationChangedListener(listener)
BehaviorReporter.report(
isNewUser = false,
"page_id" to "home",
)
}
// ✅ 绑定全局路由埋点(四个 NavController
private fun bindGlobalRouteReporting() {
homeHost.navController.addOnDestinationChangedListener(homeRouteListener)
shopHost.navController.addOnDestinationChangedListener(shopRouteListener)
mineHost.navController.addOnDestinationChangedListener(mineRouteListener)
globalHost.navController.addOnDestinationChangedListener(globalRouteListener)
// ✅ 删除:初始化手动上报(否则启动时会重复上报)
// runCatching {
// currentTabNavController.currentDestination?.id?.let { reportPageView("init_current_tab", it) }
// globalNavController.currentDestination?.id?.let { reportPageView("init_global", it) }
// }
}
private fun closeGlobalIfPossible(): Boolean {
if (!isGlobalVisible()) return false
val popped = globalNavController.popBackStack()
val stillVisible = globalNavController.currentDestination?.id != R.id.globalEmptyFragment
return popped || stillVisible
// ✅ pop 后刷新一次注意currentDestination 可能要等一帧才更新,所以 post
bottomNav.post { updateBottomNavVisibility() }
// popped = true 表示确实 pop 了;即使 popped=false 也可能已经在 empty 了
return popped || !isGlobalVisible()
}
/**
* ✅ 改这里:不要再用 View.visibility 判断 overlay
* 以 NavController 的目的地为准
*/
private fun isGlobalVisible(): Boolean {
return findViewById<View>(R.id.global_container).visibility == View.VISIBLE
return globalNavController.currentDestination?.id != R.id.globalEmptyFragment
}
private fun setupBackPress() {
@@ -316,14 +460,15 @@ class MainActivity : AppCompatActivity() {
// 2) 再 pop 当前tab
val popped = currentTabNavController.popBackStack()
if (popped) return
if (popped) {
bottomNav.post { updateBottomNavVisibility() }
return
}
// 3) 当前tab到根了如果不是home切回home否则退出
if (currentTabTag != TAB_HOME) {
bottomNav.post {
bottomNav.selectedItemId = R.id.home_graph
}
switchTab(TAB_HOME)
if (currentTabTag != TAB_HOME) {
bottomNav.post { bottomNav.selectedItemId = R.id.home_graph }
switchTab(TAB_HOME)
} else {
finish()
}
@@ -331,17 +476,25 @@ class MainActivity : AppCompatActivity() {
})
}
private var pendingNavigationAfterLogin: String? = null
private fun handleNavigationFromIntent() {
val navigateTo = intent.getStringExtra("navigate_to")
if (navigateTo == "recharge_fragment") {
bottomNav.post {
if (!isLoggedIn()) {
pendingNavigationAfterLogin = navigateTo
openGlobal(R.id.loginFragment)
return@post
return@post
}
openGlobal(R.id.rechargeFragment)
}
}
if (navigateTo == "login_fragment") {
bottomNav.post {
openGlobal(R.id.loginFragment)
}
}
}
private fun isLoggedIn(): Boolean {
@@ -352,4 +505,68 @@ class MainActivity : AppCompatActivity() {
outState.putString("current_tab_tag", currentTabTag)
super.onSaveInstanceState(outState)
}
}
// =======================
// 全局路由埋点page_id 映射 + 上报
// =======================
private fun pageIdForDest(destId: Int): String {
return when (destId) {
/** ==================== 首页 Home ==================== */
R.id.homeFragment -> "home_main" // 首页-主页面
R.id.keyboardDetailFragment -> "skin_detail" // 键盘详情页
R.id.MyKeyboard -> "my_keyboard" // 键盘设置
/** ==================== 商城 Shop ==================== */
R.id.shopFragment -> "shop" // 商城首页
R.id.searchFragment -> "search" // 搜索页
R.id.searchResultFragment -> "search_result" // 搜索结果页
R.id.MySkin -> "my_skin" // 我的皮肤
/** ==================== 我的 Mine ==================== */
R.id.mineFragment -> "my" // 我的-首页
R.id.PersonalSettings -> "person_info" // 个人设置
R.id.notificationFragment -> "notice" // 消息通知
R.id.feedbackFragment -> "feedback" // 意见反馈
R.id.consumptionRecordFragment -> "consumption_record" // 消费记录
/** ==================== 登录 & 注册 ==================== */
R.id.loginFragment -> "login" // 登录页
R.id.registerFragment -> "register_email" // 注册页
R.id.registerVerifyFragment -> "register_verify_email" // 注册验证码
R.id.forgetPasswordEmailFragment -> "forgot_password_email" // 忘记密码-邮箱
R.id.forgetPasswordVerifyFragment -> "forgot_password_verify" // 忘记密码-验证码
R.id.forgetPasswordResetFragment -> "forgot_password_newpwd" // 忘记密码-重置密码
/** ==================== 充值相关 ==================== */
R.id.rechargeFragment -> "vip_pay" // 充值首页
R.id.goldCoinRechargeFragment -> "points_recharge" // 金币充值
/** ==================== 全局 / 占位 ==================== */
R.id.globalEmptyFragment -> "global_empty" // 全局占位页(兜底)
/** ==================== 兜底处理 ==================== */
else -> "unknown_$destId" // 未配置的页面,方便排查遗漏
}
}
private fun reportPageView(source: String, destId: Int) {
val pageId = pageIdForDest(destId)
if (destId == R.id.globalEmptyFragment) return
if (destId == R.id.loginFragment || destId == R.id.registerFragment){
BehaviorReporter.report(
isNewUser = true,
"page_id" to pageId,
)
return
}
Log.d(ROUTE_TAG, "route: source=$source destId=$destId page_id=$pageId")
BehaviorReporter.report(
isNewUser = false,
"page_id" to pageId,
)
}
}