优化
This commit is contained in:
@@ -2,92 +2,354 @@ package com.example.myapplication
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDestination
|
||||
import com.example.myapplication.network.AuthEventBus
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import com.example.myapplication.network.AuthEvent
|
||||
import com.example.myapplication.network.AuthEventBus
|
||||
import com.example.myapplication.utils.EncryptedSharedPreferencesUtil
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var bottomNav: BottomNavigationView
|
||||
private lateinit var navController: NavController
|
||||
|
||||
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
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
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,且避免重复打开
|
||||
lifecycleScope.launch {
|
||||
AuthEventBus.events.collectLatest { event ->
|
||||
if (event is AuthEvent.TokenExpired) {
|
||||
val navController = (supportFragmentManager
|
||||
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment)
|
||||
.navController
|
||||
|
||||
// 避免重复跳转(比如已经在登录页)
|
||||
if (navController.currentDestination?.id != R.id.loginFragment) {
|
||||
navController.navigate(R.id.action_global_loginFragment)
|
||||
when (event) {
|
||||
is AuthEvent.TokenExpired -> {
|
||||
pendingTabAfterLogin = null
|
||||
// 已经在login就别重复
|
||||
if (!isGlobalVisible() || globalNavController.currentDestination?.id != R.id.loginFragment) {
|
||||
openGlobal(R.id.loginFragment)
|
||||
}
|
||||
}
|
||||
is AuthEvent.GenericError -> {
|
||||
Toast.makeText(this@MainActivity, event.message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
// 登录成功事件处理
|
||||
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
|
||||
}
|
||||
// 登出事件处理
|
||||
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)
|
||||
}
|
||||
} else if (event is AuthEvent.GenericError) {
|
||||
android.widget.Toast.makeText(this@MainActivity, "${event.message}", android.widget.Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 找到 NavHostFragment
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
|
||||
navController = navHostFragment.navController
|
||||
// 5) intent跳转(充值等)统一走全局 overlay
|
||||
handleNavigationFromIntent()
|
||||
|
||||
// 2. 找到 BottomNavigationView
|
||||
bottomNav = findViewById(R.id.bottom_nav)
|
||||
// 6) 返回键规则:优先关闭global,其次pop当前tab
|
||||
setupBackPress()
|
||||
|
||||
// 3. 绑定导航控制器(负责切换 Fragment、保持选中状态)
|
||||
bottomNav.setupWithNavController(navController)
|
||||
|
||||
// 4. 取消图标颜色 tint —— 使用原图标颜色
|
||||
bottomNav.itemIconTintList = null
|
||||
|
||||
// 5. 添加导航监听(用于某些 Fragment 隐藏底部导航栏)
|
||||
navController.addOnDestinationChangedListener { _, destination, _ ->
|
||||
|
||||
// 只有这些页面显示 BottomNav
|
||||
val pagesWithBottomNav = setOf(
|
||||
R.id.mineFragment,
|
||||
R.id.homeFragment,
|
||||
R.id.shopFragment
|
||||
)
|
||||
|
||||
if (destination.id in pagesWithBottomNav) {
|
||||
bottomNav.visibility = View.VISIBLE
|
||||
} else {
|
||||
bottomNav.visibility = View.GONE
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 6. 检查是否有导航参数,处理从键盘跳转过来的请求
|
||||
handleNavigationFromIntent()
|
||||
private fun initHosts() {
|
||||
val fm = supportFragmentManager
|
||||
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
// 绑定底部导航栏可见性监听
|
||||
bindBottomNavVisibilityForTabs()
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
}
|
||||
// 未登录且当前处在受保护tab:强制回首页
|
||||
if (!isLoggedIn() && currentTabGraphId in protectedTabs) {
|
||||
switchTab(TAB_HOME, force = true)
|
||||
bottomNav.selectedItemId = R.id.home_graph
|
||||
}
|
||||
|
||||
// ✅ 只有"没登录就关闭登录页"才清 pending
|
||||
if (!isLoggedIn()) {
|
||||
pendingTabAfterLogin = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun switchTab(targetTag: String, force: Boolean = false) {
|
||||
if (!force && targetTag == currentTabTag) return
|
||||
|
||||
val fm = supportFragmentManager
|
||||
if (fm.isStateSaved) return // ✅ 防崩:stateSaved 时不做事务
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
/** 打开全局页(login/recharge等) */
|
||||
private fun openGlobal(destId: Int, bundle: Bundle? = null) {
|
||||
val fm = supportFragmentManager
|
||||
if (fm.isStateSaved) return // ✅ 防崩
|
||||
|
||||
try {
|
||||
if (bundle != null) {
|
||||
globalNavController.navigate(destId, bundle)
|
||||
} else {
|
||||
globalNavController.navigate(destId)
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// 可选:防止偶发重复 navigate 崩溃
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
/** 关闭全局页:pop到 empty */
|
||||
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 { _, 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)
|
||||
}
|
||||
|
||||
private fun closeGlobalIfPossible(): Boolean {
|
||||
if (!isGlobalVisible()) return false
|
||||
|
||||
val popped = globalNavController.popBackStack()
|
||||
val stillVisible = globalNavController.currentDestination?.id != R.id.globalEmptyFragment
|
||||
return popped || stillVisible
|
||||
}
|
||||
|
||||
private fun isGlobalVisible(): Boolean {
|
||||
return findViewById<View>(R.id.global_container).visibility == View.VISIBLE
|
||||
}
|
||||
|
||||
private fun setupBackPress() {
|
||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
// 1) 优先关 global
|
||||
if (closeGlobalIfPossible()) return
|
||||
|
||||
// 2) 再 pop 当前tab
|
||||
val popped = currentTabNavController.popBackStack()
|
||||
if (popped) return
|
||||
|
||||
// 3) 当前tab到根了:如果不是home,切回home;否则退出
|
||||
if (currentTabTag != TAB_HOME) {
|
||||
bottomNav.post {
|
||||
bottomNav.selectedItemId = R.id.home_graph
|
||||
}
|
||||
switchTab(TAB_HOME)
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun handleNavigationFromIntent() {
|
||||
val navigateTo = intent.getStringExtra("navigate_to")
|
||||
if (navigateTo == "recharge_fragment") {
|
||||
// 延迟执行导航,确保导航控制器已经准备好
|
||||
bottomNav.post {
|
||||
try {
|
||||
navController.navigate(R.id.action_global_rechargeFragment)
|
||||
} catch (e: Exception) {
|
||||
// 如果导航失败,记录错误日志
|
||||
android.util.Log.e("MainActivity", "Failed to navigate to recharge fragment", e)
|
||||
if (!isLoggedIn()) {
|
||||
openGlobal(R.id.loginFragment)
|
||||
return@post
|
||||
}
|
||||
openGlobal(R.id.rechargeFragment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isLoggedIn(): Boolean {
|
||||
return EncryptedSharedPreferencesUtil.contains(this, "user")
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putString("current_tab_tag", currentTabTag)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user