Files
Android-key-of-love/app/src/main/java/com/example/myapplication/MainActivity.kt

813 lines
31 KiB
Kotlin
Raw Normal View History

2026-01-21 21:53:34 +08:00
package com.example.myapplication
import android.app.AlertDialog
import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
2025-11-26 16:47:15 +08:00
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
2026-01-21 21:53:34 +08:00
import android.view.ViewGroup
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
2026-01-21 21:53:34 +08:00
import androidx.core.content.ContextCompat
import eightbitlab.com.blurview.BlurView
import eightbitlab.com.blurview.RenderEffectBlur
import eightbitlab.com.blurview.RenderScriptBlur
2026-01-15 21:32:32 +08:00
import androidx.lifecycle.Lifecycle
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
2026-01-21 21:53:34 +08:00
import androidx.navigation.navOptions
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
2026-01-21 21:53:34 +08:00
import com.example.myapplication.network.NetworkEvent
import com.example.myapplication.network.NetworkEventBus
2025-12-31 18:36:55 +08:00
import com.example.myapplication.utils.EncryptedSharedPreferencesUtil
import com.google.android.material.bottomnavigation.BottomNavigationView
2026-01-21 21:53:34 +08:00
import com.google.android.material.button.MaterialButton
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"
2026-01-21 21:53:34 +08:00
private val TAB_CIRCLE = "tab_circle"
2025-12-31 18:36:55 +08:00
private val GLOBAL_HOST = "global_host"
private var currentTabTag = TAB_HOME
private var pendingTabAfterLogin: String? = null
2026-01-15 21:32:32 +08:00
private var isSwitchingTab = false
private var pendingTabSwitchTag: String? = null
2025-12-31 18:36:55 +08:00
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,
2026-01-21 21:53:34 +08:00
R.id.mine_graph to TAB_MINE,
R.id.circle_graph to TAB_CIRCLE
2025-12-31 18:36:55 +08:00
)
}
private lateinit var homeHost: NavHostFragment
private lateinit var shopHost: NavHostFragment
2026-01-21 21:53:34 +08:00
private lateinit var circleHost: NavHostFragment
2025-12-31 18:36:55 +08:00
private lateinit var mineHost: NavHostFragment
private lateinit var globalHost: NavHostFragment
private var lastGlobalDestId: Int = R.id.globalEmptyFragment
2026-01-21 21:53:34 +08:00
private lateinit var bottomNavBlur: BlurView
private var blurReady: Boolean = false
private var connectivityManager: ConnectivityManager? = null
private var networkCallback: ConnectivityManager.NetworkCallback? = null
private var hasNetworkConnection: Boolean = true
private var noNetworkDialog: AlertDialog? = null
2025-12-31 18:36:55 +08:00
private val currentTabHost: NavHostFragment
get() = when (currentTabTag) {
TAB_SHOP -> shopHost
TAB_MINE -> mineHost
2026-01-21 21:53:34 +08:00
TAB_CIRCLE -> circleHost
2025-12-31 18:36:55 +08:00
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
2026-01-21 21:53:34 +08:00
private var lastCircleDestIdForReport: Int? = null
2026-01-15 13:01:31 +08:00
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)
}
2026-01-21 21:53:34 +08:00
private val circleRouteListener =
NavController.OnDestinationChangedListener { _, dest, _ ->
if (lastCircleDestIdForReport == dest.id) return@OnDestinationChangedListener
lastCircleDestIdForReport = dest.id
reportPageView(source = "circle_tab", destId = dest.id)
}
2026-01-15 13:01:31 +08:00
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)
2026-01-21 21:53:34 +08:00
bottomNavBlur = findViewById(R.id.bottom_nav_blur)
2025-12-31 18:36:55 +08:00
bottomNav.itemIconTintList = null
bottomNav.setOnItemReselectedListener { /* ignore */ }
2026-01-21 21:53:34 +08:00
setupBottomNavBlur()
2025-12-31 18:36:55 +08:00
// 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) {
2026-01-21 21:53:34 +08:00
TAB_SHOP -> R.id.shop_graph
TAB_MINE -> R.id.mine_graph
TAB_CIRCLE -> R.id.circle_graph
else -> R.id.home_graph
2025-12-31 18:36:55 +08:00
}
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
2026-01-21 21:53:34 +08:00
TAB_CIRCLE -> R.id.circle_graph
2025-12-31 18:36:55 +08:00
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 -> {
2026-01-21 21:53:34 +08:00
openGlobal(event.destinationId, event.bundle, event.clearGlobalBackStack)
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 {
2026-01-21 21:53:34 +08:00
bottomNav.selectedItemId = when (currentTabTag) {
TAB_SHOP -> R.id.shop_graph
TAB_MINE -> R.id.mine_graph
TAB_CIRCLE -> R.id.circle_graph
else -> R.id.home_graph
2025-12-31 18:36:55 +08:00
}
2026-01-15 13:01:31 +08:00
updateBottomNavVisibility()
}
2026-01-21 21:53:34 +08:00
setupNetworkMonitor()
2026-01-15 13:01:31 +08:00
}
override fun onResume() {
super.onResume()
// ✅ 最终兜底:从后台回来 / 某些场景没触发 listener也能恢复底栏
bottomNav.post { updateBottomNavVisibility() }
}
override fun onDestroy() {
// ✅ 防泄漏移除路由监听Activity 销毁时)
runCatching {
homeHost.navController.removeOnDestinationChangedListener(homeRouteListener)
shopHost.navController.removeOnDestinationChangedListener(shopRouteListener)
2026-01-21 21:53:34 +08:00
circleHost.navController.removeOnDestinationChangedListener(circleRouteListener)
2026-01-15 13:01:31 +08:00
mineHost.navController.removeOnDestinationChangedListener(mineRouteListener)
globalHost.navController.removeOnDestinationChangedListener(globalRouteListener)
2025-12-31 18:36:55 +08:00
}
2026-01-21 21:53:34 +08:00
connectivityManager?.let { cm ->
networkCallback?.let { cm.unregisterNetworkCallback(it) }
}
networkCallback = null
noNetworkDialog?.dismiss()
noNetworkDialog = null
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)
2026-01-21 21:53:34 +08:00
circleHost = fm.findFragmentByTag(TAB_CIRCLE) as? NavHostFragment
?: NavHostFragment.create(R.navigation.circle_graph)
2025-12-31 18:36:55 +08:00
mineHost = fm.findFragmentByTag(TAB_MINE) as? NavHostFragment
?: NavHostFragment.create(R.navigation.mine_graph)
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)
2026-01-21 21:53:34 +08:00
.add(R.id.tab_container, circleHost, TAB_CIRCLE).hide(circleHost)
2025-12-31 18:36:55 +08:00
.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() }
}
/**
2026-01-21 21:53:34 +08:00
* 需要隐藏底部导航栏的页面
2026-01-15 13:01:31 +08:00
*/
private fun shouldHideBottomNav(destId: Int): Boolean {
return destId in setOf(
R.id.searchFragment,
R.id.searchResultFragment,
R.id.MySkin,
R.id.notificationFragment,
2026-01-21 21:53:34 +08:00
R.id.languageFragment,
2026-01-15 13:01:31 +08:00
R.id.feedbackFragment,
R.id.MyKeyboard,
R.id.PersonalSettings,
)
}
/**
2026-01-21 21:53:34 +08:00
* 统一底栏显隐逻辑任何地方状态变化都调用它
2026-01-15 13:01:31 +08:00
*/
private fun updateBottomNavVisibility() {
if (isGlobalVisible()) {
bottomNav.visibility = View.GONE
2026-01-21 21:53:34 +08:00
bottomNavBlur.visibility = View.GONE
2026-01-15 13:01:31 +08:00
return
}
val destId = currentTabNavController.currentDestination?.id
2026-01-21 21:53:34 +08:00
val shouldShow = destId == null || !shouldHideBottomNav(destId)
bottomNav.visibility = if (shouldShow) View.VISIBLE else View.GONE
bottomNavBlur.visibility = if (shouldShow && blurReady) View.VISIBLE else View.GONE
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
2026-01-15 21:32:32 +08:00
if (isSwitchingTab) {
pendingTabSwitchTag = targetTag
return
}
val targetHost = when (targetTag) {
TAB_SHOP -> shopHost
TAB_MINE -> mineHost
2026-01-21 21:53:34 +08:00
TAB_CIRCLE -> circleHost
2026-01-15 21:32:32 +08:00
else -> homeHost
}
val currentHost = currentTabHost
2025-12-31 18:36:55 +08:00
currentTabTag = targetTag
2026-01-15 21:32:32 +08:00
isSwitchingTab = true
2025-12-31 18:36:55 +08:00
2026-01-15 21:32:32 +08:00
val transaction = fm.beginTransaction()
2025-12-31 18:36:55 +08:00
.setReorderingAllowed(true)
2026-01-15 13:01:31 +08:00
2026-01-15 21:32:32 +08:00
if (force) {
transaction
.hide(homeHost)
.hide(shopHost)
.hide(mineHost)
2026-01-21 21:53:34 +08:00
.hide(circleHost)
2026-01-15 21:32:32 +08:00
.show(targetHost)
} else if (currentHost != targetHost) {
transaction
.hide(currentHost)
.show(targetHost)
}
2026-01-15 13:01:31 +08:00
2026-01-15 21:32:32 +08:00
transaction
.setMaxLifecycle(homeHost, if (targetHost == homeHost) Lifecycle.State.RESUMED else Lifecycle.State.STARTED)
.setMaxLifecycle(shopHost, if (targetHost == shopHost) Lifecycle.State.RESUMED else Lifecycle.State.STARTED)
.setMaxLifecycle(mineHost, if (targetHost == mineHost) Lifecycle.State.RESUMED else Lifecycle.State.STARTED)
2026-01-21 21:53:34 +08:00
.setMaxLifecycle(circleHost, if (targetHost == circleHost) Lifecycle.State.RESUMED else Lifecycle.State.STARTED)
2026-01-15 21:32:32 +08:00
.setPrimaryNavigationFragment(targetHost)
.runOnCommit {
isSwitchingTab = false
2026-01-21 21:53:34 +08:00
if (targetTag == TAB_CIRCLE) applyCircleTabBackground() else resetBottomNavBackground()
2026-01-15 21:32:32 +08:00
updateBottomNavVisibility()
if (!force) {
currentTabNavController.currentDestination?.id?.let { destId ->
reportPageView(source = "switch_tab", destId = destId)
}
}
val pendingTag = pendingTabSwitchTag
pendingTabSwitchTag = null
if (pendingTag != null && pendingTag != currentTabTag) {
switchTab(pendingTag)
}
2026-01-15 13:01:31 +08:00
}
2026-01-15 21:32:32 +08:00
.commit()
2025-12-31 18:36:55 +08:00
}
2026-01-21 21:53:34 +08:00
private fun applyCircleTabBackground() {
bottomNav.itemBackground = null
if (blurReady) {
bottomNavBlur.visibility = View.VISIBLE
bottomNav.background = ColorDrawable(android.graphics.Color.TRANSPARENT)
} else {
bottomNavBlur.visibility = View.GONE
bottomNav.background = ColorDrawable(ContextCompat.getColor(this, R.color.black_30_percent))
}
}
private fun resetBottomNavBackground() {
bottomNav.itemBackground = null
bottomNav.background = ColorDrawable(ContextCompat.getColor(this, android.R.color.white))
bottomNav.backgroundTintList = null
}
private fun setupBottomNavBlur() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
blurReady = false
bottomNavBlur.visibility = View.GONE
return
}
val rootView = findViewById<ViewGroup>(android.R.id.content)?.getChildAt(0) as? ViewGroup
?: run { blurReady = false; bottomNavBlur.visibility = View.GONE; return }
// Lighter blur for higher transparency
val blurRadius = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) 8f else 6f
try {
val algorithm = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
RenderEffectBlur()
} else {
RenderScriptBlur(this)
}
bottomNavBlur.setupWith(rootView, algorithm)
.setFrameClearDrawable(window.decorView.background)
.setBlurRadius(blurRadius)
.setBlurAutoUpdate(true)
.setOverlayColor(ContextCompat.getColor(this, R.color.frosted_glass_bg))
blurReady = true
} catch (e: Exception) {
blurReady = false
bottomNavBlur.visibility = View.GONE
}
}
2025-12-31 18:36:55 +08:00
/** 打开全局页login/recharge等 */
2026-01-21 21:53:34 +08:00
private fun openGlobal(destId: Int, bundle: Bundle? = null, clearBackStack: Boolean = false) {
2025-12-31 18:36:55 +08:00
val fm = supportFragmentManager
2026-01-15 13:01:31 +08:00
if (fm.isStateSaved) return
2025-12-31 18:36:55 +08:00
2026-01-21 21:53:34 +08:00
val navOptions = if (clearBackStack) {
navOptions {
// 清理 global overlay 栈,但不短暂显示 empty避免闪屏
popUpTo(R.id.globalEmptyFragment) { inclusive = false }
}
} else {
null
}
2025-12-31 18:36:55 +08:00
try {
2026-01-21 21:53:34 +08:00
if (bundle != null) globalNavController.navigate(destId, bundle, navOptions)
else globalNavController.navigate(destId, null, navOptions)
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-21 21:53:34 +08:00
circleHost.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)
2026-01-21 21:53:34 +08:00
circleHost.navController.addOnDestinationChangedListener(circleRouteListener)
2026-01-15 13:01:31 +08:00
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" // 消费记录
2026-01-21 21:53:34 +08:00
R.id.languageFragment -> "language_settings" // 语言切换页
2026-01-15 13:01:31 +08:00
/** ==================== 登录 & 注册 ==================== */
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,
)
}
2026-01-21 21:53:34 +08:00
// 网络检测:无网络时弹窗提醒,恢复后分发刷新事件
private fun setupNetworkMonitor() {
val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
connectivityManager = cm
if (cm == null) return
hasNetworkConnection = isNetworkConnected(cm)
if (!hasNetworkConnection) {
showNoNetworkDialog()
NetworkEventBus.emit(NetworkEvent.NetworkLost)
}
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
handleNetworkStateChange(true)
}
override fun onLost(network: Network) {
handleNetworkStateChange(isNetworkConnected(cm))
}
}
networkCallback = callback
runCatching {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
cm.registerDefaultNetworkCallback(callback)
} else {
cm.registerNetworkCallback(buildNetworkRequest(), callback)
}
}.onFailure { e ->
Log.e("MainActivity", "registerNetworkCallback failed: ${e.message}")
}
}
private fun handleNetworkStateChange(connected: Boolean) {
if (connected == hasNetworkConnection) return
hasNetworkConnection = connected
runOnUiThread {
if (connected) {
dismissNoNetworkDialog()
NetworkEventBus.emit(NetworkEvent.NetworkAvailable)
} else {
showNoNetworkDialog()
NetworkEventBus.emit(NetworkEvent.NetworkLost)
}
}
}
private fun showNoNetworkDialog() {
if (isFinishing || isDestroyed) return
if (noNetworkDialog?.isShowing == true) return
val dialogView = layoutInflater.inflate(R.layout.dialog_no_network, null)
dialogView.findViewById<MaterialButton>(R.id.btnDismiss)?.setOnClickListener {
noNetworkDialog?.dismiss()
}
noNetworkDialog = AlertDialog.Builder(this)
.setView(dialogView)
.setCancelable(false)
.create()
noNetworkDialog?.window?.setBackgroundDrawable(ColorDrawable(android.graphics.Color.TRANSPARENT))
noNetworkDialog?.show()
}
private fun dismissNoNetworkDialog() {
noNetworkDialog?.dismiss()
noNetworkDialog = null
}
private fun isNetworkConnected(cm: ConnectivityManager): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = cm.activeNetwork ?: return false
val capabilities = cm.getNetworkCapabilities(network) ?: return false
capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) ||
capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
} else {
@Suppress("DEPRECATION")
cm.activeNetworkInfo?.isConnected == true
}
}
private fun buildNetworkRequest(): NetworkRequest {
val builder = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
// 让低版本也能触达 Wi-Fi / 蜂窝网络
builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
builder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
return builder.build()
}
2026-01-15 13:01:31 +08:00
}