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

355 lines
13 KiB
Kotlin
Raw Normal View History

2025-11-26 16:47:15 +08:00
package com.example.myapplication
import android.os.Bundle
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
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
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)
}
}
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)
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
}
}
}
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-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()
2025-12-31 18:36:55 +08:00
// 绑定底部导航栏可见性监听
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 {
2025-12-31 18:36:55 +08:00
globalNavController.navigate(destId)
2025-11-26 16:47:15 +08:00
}
2025-12-31 18:36:55 +08:00
} 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
2025-11-26 16:47:15 +08:00
}
2025-12-31 18:36:55 +08:00
homeHost.navController.addOnDestinationChangedListener(listener)
shopHost.navController.addOnDestinationChangedListener(listener)
mineHost.navController.addOnDestinationChangedListener(listener)
}
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()
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()
}
}
})
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()) {
openGlobal(R.id.loginFragment)
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
}
}
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)
}
}