2025-11-26 16:47:15 +08:00
|
|
|
|
package com.example.myapplication
|
|
|
|
|
|
|
|
|
|
|
|
import android.os.Bundle
|
2026-01-15 13:01:31 +08:00
|
|
|
|
import android.util.Log
|
2025-11-26 16:47:15 +08:00
|
|
|
|
import android.view.View
|
2025-12-31 18:36:55 +08:00
|
|
|
|
import android.widget.Toast
|
|
|
|
|
|
import androidx.activity.OnBackPressedCallback
|
2025-11-26 16:47:15 +08:00
|
|
|
|
import androidx.appcompat.app.AppCompatActivity
|
2025-12-26 22:01:04 +08:00
|
|
|
|
import androidx.lifecycle.lifecycleScope
|
2025-11-26 16:47:15 +08:00
|
|
|
|
import androidx.navigation.NavController
|
2025-12-31 18:36:55 +08:00
|
|
|
|
import androidx.navigation.fragment.NavHostFragment
|
2025-12-26 22:01:04 +08:00
|
|
|
|
import com.example.myapplication.network.AuthEvent
|
2025-12-31 18:36:55 +08:00
|
|
|
|
import com.example.myapplication.network.AuthEventBus
|
2026-01-15 13:01:31 +08:00
|
|
|
|
import com.example.myapplication.network.BehaviorReporter
|
2025-12-31 18:36:55 +08:00
|
|
|
|
import com.example.myapplication.utils.EncryptedSharedPreferencesUtil
|
|
|
|
|
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
2025-12-26 22:01:04 +08:00
|
|
|
|
import kotlinx.coroutines.flow.collectLatest
|
|
|
|
|
|
import kotlinx.coroutines.launch
|
2025-11-26 16:47:15 +08:00
|
|
|
|
|
|
|
|
|
|
class MainActivity : AppCompatActivity() {
|
|
|
|
|
|
|
|
|
|
|
|
private lateinit var bottomNav: BottomNavigationView
|
2025-12-31 18:36:55 +08:00
|
|
|
|
|
|
|
|
|
|
private val TAB_HOME = "tab_home"
|
|
|
|
|
|
private val TAB_SHOP = "tab_shop"
|
|
|
|
|
|
private val TAB_MINE = "tab_mine"
|
|
|
|
|
|
private val GLOBAL_HOST = "global_host"
|
|
|
|
|
|
|
|
|
|
|
|
private var currentTabTag = TAB_HOME
|
|
|
|
|
|
private var pendingTabAfterLogin: String? = null
|
|
|
|
|
|
|
|
|
|
|
|
private val protectedTabs = setOf(
|
|
|
|
|
|
R.id.shop_graph,
|
|
|
|
|
|
R.id.mine_graph
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
private val tabMap by lazy {
|
|
|
|
|
|
mapOf(
|
|
|
|
|
|
R.id.home_graph to TAB_HOME,
|
|
|
|
|
|
R.id.shop_graph to TAB_SHOP,
|
|
|
|
|
|
R.id.mine_graph to TAB_MINE
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private lateinit var homeHost: NavHostFragment
|
|
|
|
|
|
private lateinit var shopHost: NavHostFragment
|
|
|
|
|
|
private lateinit var mineHost: NavHostFragment
|
|
|
|
|
|
private lateinit var globalHost: NavHostFragment
|
|
|
|
|
|
private var lastGlobalDestId: Int = R.id.globalEmptyFragment
|
|
|
|
|
|
|
|
|
|
|
|
private val currentTabHost: NavHostFragment
|
|
|
|
|
|
get() = when (currentTabTag) {
|
|
|
|
|
|
TAB_SHOP -> shopHost
|
|
|
|
|
|
TAB_MINE -> mineHost
|
|
|
|
|
|
else -> homeHost
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private val currentTabNavController: NavController
|
|
|
|
|
|
get() = currentTabHost.navController
|
|
|
|
|
|
|
|
|
|
|
|
private val globalNavController: NavController
|
|
|
|
|
|
get() = globalHost.navController
|
2025-11-26 16:47:15 +08:00
|
|
|
|
|
2026-01-15 13:01:31 +08:00
|
|
|
|
// =======================
|
|
|
|
|
|
// 全局路由埋点:新增字段
|
|
|
|
|
|
// =======================
|
|
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 16:47:15 +08:00
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
|
|
setContentView(R.layout.activity_main)
|
|
|
|
|
|
|
2025-12-31 18:36:55 +08:00
|
|
|
|
bottomNav = findViewById(R.id.bottom_nav)
|
|
|
|
|
|
bottomNav.itemIconTintList = null
|
|
|
|
|
|
bottomNav.setOnItemReselectedListener { /* ignore */ }
|
|
|
|
|
|
|
|
|
|
|
|
// 1) 恢复当前tab
|
|
|
|
|
|
currentTabTag = savedInstanceState?.getString("current_tab_tag") ?: TAB_HOME
|
|
|
|
|
|
|
|
|
|
|
|
// 2) 初始化/找回 3个Tab host + global host
|
|
|
|
|
|
initHosts()
|
|
|
|
|
|
|
|
|
|
|
|
// 3) 底栏点击:show/hide 切 tab(带登录拦截 + 防 stateSaved)
|
|
|
|
|
|
bottomNav.setOnItemSelectedListener { item ->
|
|
|
|
|
|
val tabTag = tabMap[item.itemId] ?: return@setOnItemSelectedListener false
|
|
|
|
|
|
|
|
|
|
|
|
// 登录拦截:未登录点受保护tab => 打开全局login
|
|
|
|
|
|
if (!isLoggedIn() && item.itemId in protectedTabs) {
|
|
|
|
|
|
// 强制回到当前tab的选中状态,避免底栏短暂闪一下
|
|
|
|
|
|
bottomNav.selectedItemId = when (currentTabTag) {
|
|
|
|
|
|
TAB_SHOP -> R.id.shop_graph
|
|
|
|
|
|
TAB_MINE -> R.id.mine_graph
|
|
|
|
|
|
else -> R.id.home_graph
|
|
|
|
|
|
}
|
|
|
|
|
|
pendingTabAfterLogin = tabTag // 记录目标tab
|
|
|
|
|
|
openGlobal(R.id.loginFragment)
|
|
|
|
|
|
return@setOnItemSelectedListener false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switchTab(tabTag)
|
|
|
|
|
|
true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4) token过期:走全局 overlay,且避免重复打开
|
2025-12-26 22:01:04 +08:00
|
|
|
|
lifecycleScope.launch {
|
|
|
|
|
|
AuthEventBus.events.collectLatest { event ->
|
2025-12-31 18:36:55 +08:00
|
|
|
|
when (event) {
|
|
|
|
|
|
is AuthEvent.TokenExpired -> {
|
|
|
|
|
|
pendingTabAfterLogin = null
|
|
|
|
|
|
// 已经在login就别重复
|
|
|
|
|
|
if (!isGlobalVisible() || globalNavController.currentDestination?.id != R.id.loginFragment) {
|
|
|
|
|
|
openGlobal(R.id.loginFragment)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-15 13:01:31 +08:00
|
|
|
|
|
2025-12-31 18:36:55 +08:00
|
|
|
|
is AuthEvent.GenericError -> {
|
2026-01-15 13:01:31 +08:00
|
|
|
|
Toast.makeText(this@MainActivity, event.message, Toast.LENGTH_LONG).show()
|
2025-12-31 18:36:55 +08:00
|
|
|
|
}
|
2026-01-15 13:01:31 +08:00
|
|
|
|
|
2025-12-31 18:36:55 +08:00
|
|
|
|
// 登录成功事件处理
|
|
|
|
|
|
is AuthEvent.LoginSuccess -> {
|
|
|
|
|
|
// 关闭 global overlay:回到 empty
|
|
|
|
|
|
globalNavController.popBackStack(R.id.globalEmptyFragment, false)
|
|
|
|
|
|
|
|
|
|
|
|
// 如果之前想去商城/我的,登录成功后自动切过去
|
|
|
|
|
|
pendingTabAfterLogin?.let { tag ->
|
|
|
|
|
|
switchTab(tag)
|
|
|
|
|
|
bottomNav.selectedItemId = when (tag) {
|
|
|
|
|
|
TAB_SHOP -> R.id.shop_graph
|
|
|
|
|
|
TAB_MINE -> R.id.mine_graph
|
|
|
|
|
|
else -> R.id.home_graph
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
pendingTabAfterLogin = null
|
2026-01-15 13:01:31 +08:00
|
|
|
|
|
|
|
|
|
|
// 处理intent跳转目标页
|
|
|
|
|
|
if (pendingNavigationAfterLogin == "recharge_fragment") {
|
|
|
|
|
|
openGlobal(R.id.rechargeFragment)
|
|
|
|
|
|
pendingNavigationAfterLogin = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 登录成功后也刷新一次
|
|
|
|
|
|
bottomNav.post { updateBottomNavVisibility() }
|
2025-12-31 18:36:55 +08:00
|
|
|
|
}
|
2026-01-15 13:01:31 +08:00
|
|
|
|
|
2025-12-31 18:36:55 +08:00
|
|
|
|
// 登出事件处理
|
|
|
|
|
|
is AuthEvent.Logout -> {
|
|
|
|
|
|
pendingTabAfterLogin = event.returnTabTag
|
2026-01-15 13:01:31 +08:00
|
|
|
|
|
2025-12-31 18:36:55 +08:00
|
|
|
|
// ✅ 用户没登录按返回,应回首页,所以先切到首页
|
|
|
|
|
|
switchTab(TAB_HOME, force = true)
|
2026-01-15 13:01:31 +08:00
|
|
|
|
|
2025-12-31 18:36:55 +08:00
|
|
|
|
bottomNav.post {
|
|
|
|
|
|
bottomNav.selectedItemId = R.id.home_graph
|
|
|
|
|
|
openGlobal(R.id.loginFragment) // ✅ 退出登录后立刻打开登录页
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-15 13:01:31 +08:00
|
|
|
|
|
2025-12-31 18:36:55 +08:00
|
|
|
|
// 打开全局页面事件处理
|
|
|
|
|
|
is AuthEvent.OpenGlobalPage -> {
|
|
|
|
|
|
openGlobal(event.destinationId, event.bundle)
|
2025-12-26 22:01:04 +08:00
|
|
|
|
}
|
2026-01-15 13:01:31 +08:00
|
|
|
|
|
|
|
|
|
|
is AuthEvent.UserUpdated -> {
|
|
|
|
|
|
// 不需要处理
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
is AuthEvent.CharacterDeleted -> {
|
|
|
|
|
|
// 不需要处理
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
is AuthEvent.CharacterAdded -> {
|
|
|
|
|
|
// 不需要处理,由HomeFragment处理
|
|
|
|
|
|
}
|
2025-12-26 22:01:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-31 18:36:55 +08:00
|
|
|
|
// 5) intent跳转(充值等)统一走全局 overlay
|
|
|
|
|
|
handleNavigationFromIntent()
|
2025-11-26 16:47:15 +08:00
|
|
|
|
|
2025-12-31 18:36:55 +08:00
|
|
|
|
// 6) 返回键规则:优先关闭global,其次pop当前tab
|
|
|
|
|
|
setupBackPress()
|
2025-11-26 16:47:15 +08:00
|
|
|
|
|
2025-12-31 18:36:55 +08:00
|
|
|
|
// 7) 初始选中正确tab(不会触发二次创建)
|
|
|
|
|
|
bottomNav.post {
|
|
|
|
|
|
bottomNav.selectedItemId = when (currentTabTag) {
|
|
|
|
|
|
TAB_SHOP -> R.id.shop_graph
|
|
|
|
|
|
TAB_MINE -> R.id.mine_graph
|
|
|
|
|
|
else -> R.id.home_graph
|
|
|
|
|
|
}
|
2026-01-15 13:01:31 +08:00
|
|
|
|
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)
|
2025-12-31 18:36:55 +08:00
|
|
|
|
}
|
2026-01-15 13:01:31 +08:00
|
|
|
|
super.onDestroy()
|
2025-12-31 18:36:55 +08:00
|
|
|
|
}
|
2025-11-26 16:47:15 +08:00
|
|
|
|
|
2025-12-31 18:36:55 +08:00
|
|
|
|
private fun initHosts() {
|
|
|
|
|
|
val fm = supportFragmentManager
|
2025-11-26 16:47:15 +08:00
|
|
|
|
|
2025-12-31 18:36:55 +08:00
|
|
|
|
homeHost = fm.findFragmentByTag(TAB_HOME) as? NavHostFragment
|
|
|
|
|
|
?: NavHostFragment.create(R.navigation.home_graph)
|
|
|
|
|
|
shopHost = fm.findFragmentByTag(TAB_SHOP) as? NavHostFragment
|
|
|
|
|
|
?: NavHostFragment.create(R.navigation.shop_graph)
|
|
|
|
|
|
mineHost = fm.findFragmentByTag(TAB_MINE) as? NavHostFragment
|
|
|
|
|
|
?: NavHostFragment.create(R.navigation.mine_graph)
|
2025-12-11 14:52:29 +08:00
|
|
|
|
|
2025-12-31 18:36:55 +08:00
|
|
|
|
globalHost = fm.findFragmentByTag(GLOBAL_HOST) as? NavHostFragment
|
|
|
|
|
|
?: NavHostFragment.create(R.navigation.global_graph)
|
|
|
|
|
|
|
|
|
|
|
|
// 第一次创建时 add;后续进程重建会自动恢复,无需重复 add
|
|
|
|
|
|
if (fm.findFragmentByTag(TAB_HOME) == null) {
|
|
|
|
|
|
fm.beginTransaction()
|
|
|
|
|
|
.setReorderingAllowed(true)
|
|
|
|
|
|
.add(R.id.tab_container, homeHost, TAB_HOME)
|
|
|
|
|
|
.add(R.id.tab_container, shopHost, TAB_SHOP).hide(shopHost)
|
|
|
|
|
|
.add(R.id.tab_container, mineHost, TAB_MINE).hide(mineHost)
|
|
|
|
|
|
.commitNow()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (fm.findFragmentByTag(GLOBAL_HOST) == null) {
|
|
|
|
|
|
fm.beginTransaction()
|
|
|
|
|
|
.setReorderingAllowed(true)
|
|
|
|
|
|
.add(R.id.global_container, globalHost, GLOBAL_HOST)
|
|
|
|
|
|
.commitNow()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确保当前tab可见
|
|
|
|
|
|
switchTab(currentTabTag, force = true)
|
|
|
|
|
|
|
|
|
|
|
|
// 绑定全局导航可见性监听
|
|
|
|
|
|
bindGlobalVisibility()
|
2026-01-15 13:01:31 +08:00
|
|
|
|
|
2025-12-31 18:36:55 +08:00
|
|
|
|
// 绑定底部导航栏可见性监听
|
|
|
|
|
|
bindBottomNavVisibilityForTabs()
|
2026-01-15 13:01:31 +08:00
|
|
|
|
|
|
|
|
|
|
// ✅ 全局路由埋点监听(每次导航变化上报)
|
|
|
|
|
|
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
|
2025-12-31 18:36:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun bindGlobalVisibility() {
|
|
|
|
|
|
globalNavController.addOnDestinationChangedListener { _, dest, _ ->
|
|
|
|
|
|
val isEmpty = dest.id == R.id.globalEmptyFragment
|
2026-01-15 13:01:31 +08:00
|
|
|
|
|
|
|
|
|
|
findViewById<View>(R.id.global_container).visibility =
|
2025-12-31 18:36:55 +08:00
|
|
|
|
if (isEmpty) View.GONE else View.VISIBLE
|
|
|
|
|
|
|
2026-01-15 13:01:31 +08:00
|
|
|
|
// ✅ 底栏统一走 update
|
|
|
|
|
|
updateBottomNavVisibility()
|
|
|
|
|
|
|
|
|
|
|
|
val justClosedOverlay =
|
|
|
|
|
|
(dest.id == R.id.globalEmptyFragment && lastGlobalDestId != R.id.globalEmptyFragment)
|
2025-12-31 18:36:55 +08:00
|
|
|
|
lastGlobalDestId = dest.id
|
|
|
|
|
|
|
|
|
|
|
|
if (justClosedOverlay) {
|
|
|
|
|
|
val currentTabGraphId = when (currentTabTag) {
|
|
|
|
|
|
TAB_SHOP -> R.id.shop_graph
|
|
|
|
|
|
TAB_MINE -> R.id.mine_graph
|
|
|
|
|
|
else -> R.id.home_graph
|
|
|
|
|
|
}
|
2026-01-15 13:01:31 +08:00
|
|
|
|
|
2025-12-31 18:36:55 +08:00
|
|
|
|
if (!isLoggedIn() && currentTabGraphId in protectedTabs) {
|
|
|
|
|
|
switchTab(TAB_HOME, force = true)
|
|
|
|
|
|
bottomNav.selectedItemId = R.id.home_graph
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!isLoggedIn()) {
|
|
|
|
|
|
pendingTabAfterLogin = null
|
|
|
|
|
|
}
|
2026-01-15 13:01:31 +08:00
|
|
|
|
|
|
|
|
|
|
bottomNav.post { updateBottomNavVisibility() }
|
2025-12-31 18:36:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun switchTab(targetTag: String, force: Boolean = false) {
|
|
|
|
|
|
if (!force && targetTag == currentTabTag) return
|
|
|
|
|
|
|
|
|
|
|
|
val fm = supportFragmentManager
|
2026-01-15 13:01:31 +08:00
|
|
|
|
if (fm.isStateSaved) return
|
2025-12-31 18:36:55 +08:00
|
|
|
|
|
|
|
|
|
|
currentTabTag = targetTag
|
|
|
|
|
|
|
|
|
|
|
|
fm.beginTransaction()
|
|
|
|
|
|
.setReorderingAllowed(true)
|
|
|
|
|
|
.hide(homeHost)
|
|
|
|
|
|
.hide(shopHost)
|
|
|
|
|
|
.hide(mineHost)
|
|
|
|
|
|
.also { ft ->
|
|
|
|
|
|
when (targetTag) {
|
|
|
|
|
|
TAB_SHOP -> ft.show(shopHost)
|
|
|
|
|
|
TAB_MINE -> ft.show(mineHost)
|
|
|
|
|
|
else -> ft.show(homeHost)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
.commit()
|
2026-01-15 13:01:31 +08:00
|
|
|
|
|
|
|
|
|
|
// ✅ 关键:hide/show 切 tab 不会触发 destinationChanged,所以手动刷新
|
|
|
|
|
|
bottomNav.post { updateBottomNavVisibility() }
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:切 tab 后补一次路由上报(不改变其它逻辑)
|
|
|
|
|
|
if (!force) {
|
|
|
|
|
|
currentTabNavController.currentDestination?.id?.let { destId ->
|
|
|
|
|
|
reportPageView(source = "switch_tab", destId = destId)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-31 18:36:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 打开全局页(login/recharge等) */
|
|
|
|
|
|
private fun openGlobal(destId: Int, bundle: Bundle? = null) {
|
|
|
|
|
|
val fm = supportFragmentManager
|
2026-01-15 13:01:31 +08:00
|
|
|
|
if (fm.isStateSaved) return
|
2025-12-31 18:36:55 +08:00
|
|
|
|
|
|
|
|
|
|
try {
|
2026-01-15 13:01:31 +08:00
|
|
|
|
if (bundle != null) globalNavController.navigate(destId, bundle)
|
|
|
|
|
|
else globalNavController.navigate(destId)
|
2025-12-31 18:36:55 +08:00
|
|
|
|
} catch (e: IllegalArgumentException) {
|
|
|
|
|
|
e.printStackTrace()
|
|
|
|
|
|
}
|
2026-01-15 13:01:31 +08:00
|
|
|
|
|
|
|
|
|
|
bottomNav.post { updateBottomNavVisibility() }
|
2025-12-31 18:36:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 13:01:31 +08:00
|
|
|
|
/** Tab 内页面变化时刷新底栏显隐 */
|
2025-12-31 18:36:55 +08:00
|
|
|
|
private fun bindBottomNavVisibilityForTabs() {
|
2026-01-15 13:01:31 +08:00
|
|
|
|
val listener = NavController.OnDestinationChangedListener { _, _, _ ->
|
|
|
|
|
|
updateBottomNavVisibility()
|
2025-12-31 18:36:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
homeHost.navController.addOnDestinationChangedListener(listener)
|
|
|
|
|
|
shopHost.navController.addOnDestinationChangedListener(listener)
|
|
|
|
|
|
mineHost.navController.addOnDestinationChangedListener(listener)
|
2026-01-15 13:01:31 +08:00
|
|
|
|
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) }
|
|
|
|
|
|
// }
|
2025-12-31 18:36:55 +08:00
|
|
|
|
}
|
2025-12-05 20:48:22 +08:00
|
|
|
|
|
2025-12-31 18:36:55 +08:00
|
|
|
|
private fun closeGlobalIfPossible(): Boolean {
|
|
|
|
|
|
if (!isGlobalVisible()) return false
|
|
|
|
|
|
|
|
|
|
|
|
val popped = globalNavController.popBackStack()
|
2026-01-15 13:01:31 +08:00
|
|
|
|
|
|
|
|
|
|
// ✅ pop 后刷新一次(注意:currentDestination 可能要等一帧才更新,所以 post)
|
|
|
|
|
|
bottomNav.post { updateBottomNavVisibility() }
|
|
|
|
|
|
|
|
|
|
|
|
// popped = true 表示确实 pop 了;即使 popped=false 也可能已经在 empty 了
|
|
|
|
|
|
return popped || !isGlobalVisible()
|
2025-12-31 18:36:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 13:01:31 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 改这里:不要再用 View.visibility 判断 overlay
|
|
|
|
|
|
* 以 NavController 的目的地为准
|
|
|
|
|
|
*/
|
2025-12-31 18:36:55 +08:00
|
|
|
|
private fun isGlobalVisible(): Boolean {
|
2026-01-15 13:01:31 +08:00
|
|
|
|
return globalNavController.currentDestination?.id != R.id.globalEmptyFragment
|
2025-12-31 18:36:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun setupBackPress() {
|
|
|
|
|
|
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
|
|
|
|
|
override fun handleOnBackPressed() {
|
|
|
|
|
|
// 1) 优先关 global
|
|
|
|
|
|
if (closeGlobalIfPossible()) return
|
|
|
|
|
|
|
|
|
|
|
|
// 2) 再 pop 当前tab
|
|
|
|
|
|
val popped = currentTabNavController.popBackStack()
|
2026-01-15 13:01:31 +08:00
|
|
|
|
if (popped) {
|
|
|
|
|
|
bottomNav.post { updateBottomNavVisibility() }
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-12-31 18:36:55 +08:00
|
|
|
|
|
|
|
|
|
|
// 3) 当前tab到根了:如果不是home,切回home;否则退出
|
2026-01-15 13:01:31 +08:00
|
|
|
|
if (currentTabTag != TAB_HOME) {
|
|
|
|
|
|
bottomNav.post { bottomNav.selectedItemId = R.id.home_graph }
|
|
|
|
|
|
switchTab(TAB_HOME)
|
2025-12-31 18:36:55 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
finish()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-12-05 20:48:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 13:01:31 +08:00
|
|
|
|
private var pendingNavigationAfterLogin: String? = null
|
|
|
|
|
|
|
2025-12-05 20:48:22 +08:00
|
|
|
|
private fun handleNavigationFromIntent() {
|
|
|
|
|
|
val navigateTo = intent.getStringExtra("navigate_to")
|
|
|
|
|
|
if (navigateTo == "recharge_fragment") {
|
|
|
|
|
|
bottomNav.post {
|
2025-12-31 18:36:55 +08:00
|
|
|
|
if (!isLoggedIn()) {
|
2026-01-15 13:01:31 +08:00
|
|
|
|
pendingNavigationAfterLogin = navigateTo
|
2025-12-31 18:36:55 +08:00
|
|
|
|
openGlobal(R.id.loginFragment)
|
2026-01-15 13:01:31 +08:00
|
|
|
|
return@post
|
2025-12-05 20:48:22 +08:00
|
|
|
|
}
|
2025-12-31 18:36:55 +08:00
|
|
|
|
openGlobal(R.id.rechargeFragment)
|
2025-12-05 20:48:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-15 13:01:31 +08:00
|
|
|
|
if (navigateTo == "login_fragment") {
|
|
|
|
|
|
bottomNav.post {
|
|
|
|
|
|
openGlobal(R.id.loginFragment)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-26 16:47:15 +08:00
|
|
|
|
}
|
2025-12-31 18:36:55 +08:00
|
|
|
|
|
|
|
|
|
|
private fun isLoggedIn(): Boolean {
|
|
|
|
|
|
return EncryptedSharedPreferencesUtil.contains(this, "user")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onSaveInstanceState(outState: Bundle) {
|
|
|
|
|
|
outState.putString("current_tab_tag", currentTabTag)
|
|
|
|
|
|
super.onSaveInstanceState(outState)
|
|
|
|
|
|
}
|
2026-01-15 13:01:31 +08:00
|
|
|
|
|
|
|
|
|
|
// =======================
|
|
|
|
|
|
// 全局路由埋点: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,
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|