Files
Android-key-of-love/app/src/main/java/com/example/myapplication/MainActivity.kt
pengxiaolong d10524c597 手机适配
2026-02-10 18:26:31 +08:00

794 lines
30 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import eightbitlab.com.blurview.BlurView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.navOptions
import com.example.myapplication.network.AuthEvent
import com.example.myapplication.network.AuthEventBus
import com.example.myapplication.network.BehaviorReporter
import com.example.myapplication.network.NetworkEvent
import com.example.myapplication.network.NetworkEventBus
import com.example.myapplication.utils.EncryptedSharedPreferencesUtil
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.button.MaterialButton
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private lateinit var bottomNav: BottomNavigationView
private val TAB_HOME = "tab_home"
private val TAB_SHOP = "tab_shop"
private val TAB_MINE = "tab_mine"
private val TAB_CIRCLE = "tab_circle"
private val GLOBAL_HOST = "global_host"
private var currentTabTag = TAB_HOME
private var pendingTabAfterLogin: String? = null
private var isSwitchingTab = false
private var pendingTabSwitchTag: 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,
R.id.circle_graph to TAB_CIRCLE
)
}
private lateinit var homeHost: NavHostFragment
private lateinit var shopHost: NavHostFragment
private lateinit var circleHost: NavHostFragment
private lateinit var mineHost: NavHostFragment
private lateinit var globalHost: NavHostFragment
private var lastGlobalDestId: Int = R.id.globalEmptyFragment
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
private val currentTabHost: NavHostFragment
get() = when (currentTabTag) {
TAB_SHOP -> shopHost
TAB_MINE -> mineHost
TAB_CIRCLE -> circleHost
else -> homeHost
}
private val currentTabNavController: NavController
get() = currentTabHost.navController
private val globalNavController: NavController
get() = globalHost.navController
// =======================
// 全局路由埋点:新增字段
// =======================
private val ROUTE_TAG = "RouteReport"
private var lastHomeDestIdForReport: Int? = null
private var lastShopDestIdForReport: Int? = null
private var lastCircleDestIdForReport: 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 circleRouteListener =
NavController.OnDestinationChangedListener { _, dest, _ ->
if (lastCircleDestIdForReport == dest.id) return@OnDestinationChangedListener
lastCircleDestIdForReport = dest.id
reportPageView(source = "circle_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)
bottomNav = findViewById(R.id.bottom_nav)
bottomNavBlur = findViewById(R.id.bottom_nav_blur)
bottomNav.itemIconTintList = null
bottomNav.setOnItemReselectedListener { /* ignore */ }
setupBottomNavBlur()
// 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
TAB_CIRCLE -> R.id.circle_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 ->
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_LONG).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_CIRCLE -> R.id.circle_graph
TAB_MINE -> R.id.mine_graph
else -> R.id.home_graph
}
}
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, event.clearGlobalBackStack)
}
// Circle 页面内跳转(保留原页面栈)
is AuthEvent.OpenCirclePage -> {
switchTab(TAB_CIRCLE, force = true)
try {
circleHost.navController.navigate(event.destinationId, event.bundle)
} catch (e: IllegalArgumentException) {
e.printStackTrace()
}
}
is AuthEvent.UserUpdated -> {
// 不需要处理
}
is AuthEvent.CharacterDeleted -> {
// 不需要处理
}
is AuthEvent.CharacterAdded -> {
// 不需要处理由HomeFragment处理
}
}
}
}
// 5) intent跳转充值等统一走全局 overlay
handleNavigationFromIntent()
// 6) 返回键规则优先关闭global其次pop当前tab
setupBackPress()
// 7) 初始选中正确tab不会触发二次创建
bottomNav.post {
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
}
updateBottomNavVisibility()
}
setupNetworkMonitor()
}
override fun onResume() {
super.onResume()
// ✅ 最终兜底:从后台回来 / 某些场景没触发 listener也能恢复底栏
bottomNav.post { updateBottomNavVisibility() }
}
override fun onDestroy() {
// ✅ 防泄漏移除路由监听Activity 销毁时)
runCatching {
homeHost.navController.removeOnDestinationChangedListener(homeRouteListener)
shopHost.navController.removeOnDestinationChangedListener(shopRouteListener)
circleHost.navController.removeOnDestinationChangedListener(circleRouteListener)
mineHost.navController.removeOnDestinationChangedListener(mineRouteListener)
globalHost.navController.removeOnDestinationChangedListener(globalRouteListener)
}
connectivityManager?.let { cm ->
networkCallback?.let { cm.unregisterNetworkCallback(it) }
}
networkCallback = null
noNetworkDialog?.dismiss()
noNetworkDialog = null
super.onDestroy()
}
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)
circleHost = fm.findFragmentByTag(TAB_CIRCLE) as? NavHostFragment
?: NavHostFragment.create(R.navigation.circle_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, circleHost, TAB_CIRCLE).hide(circleHost)
.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()
// ✅ 全局路由埋点监听(每次导航变化上报)
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.languageFragment,
R.id.feedbackFragment,
R.id.MyKeyboard,
R.id.PersonalSettings,
R.id.circleCharacterDetailsFragment,
R.id.circleAiCharacterReportFragment,
R.id.CircleMyAiCharacterFragment
)
}
/**
* 统一底栏显隐逻辑:任何地方状态变化都调用它
*/
private fun updateBottomNavVisibility() {
if (isGlobalVisible()) {
bottomNav.visibility = View.GONE
bottomNavBlur.visibility = View.GONE
return
}
val destId = currentTabNavController.currentDestination?.id
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
}
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
// ✅ 底栏统一走 update
updateBottomNavVisibility()
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
}
if (!isLoggedIn() && currentTabGraphId in protectedTabs) {
switchTab(TAB_HOME, force = true)
bottomNav.selectedItemId = R.id.home_graph
}
if (!isLoggedIn()) {
pendingTabAfterLogin = null
}
bottomNav.post { updateBottomNavVisibility() }
}
}
}
private fun switchTab(targetTag: String, force: Boolean = false) {
if (!force && targetTag == currentTabTag) return
val fm = supportFragmentManager
if (fm.isStateSaved) return
if (isSwitchingTab) {
pendingTabSwitchTag = targetTag
return
}
val targetHost = when (targetTag) {
TAB_SHOP -> shopHost
TAB_MINE -> mineHost
TAB_CIRCLE -> circleHost
else -> homeHost
}
val currentHost = currentTabHost
currentTabTag = targetTag
isSwitchingTab = true
val transaction = fm.beginTransaction()
.setReorderingAllowed(true)
if (force) {
transaction
.hide(homeHost)
.hide(shopHost)
.hide(mineHost)
.hide(circleHost)
.show(targetHost)
} else if (currentHost != targetHost) {
transaction
.hide(currentHost)
.show(targetHost)
}
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)
.setMaxLifecycle(circleHost, if (targetHost == circleHost) Lifecycle.State.RESUMED else Lifecycle.State.STARTED)
.setPrimaryNavigationFragment(targetHost)
.runOnCommit {
isSwitchingTab = false
if (targetTag == TAB_CIRCLE) applyCircleTabBackground() else resetBottomNavBackground()
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)
}
}
.commit()
}
private fun applyCircleTabBackground() {
bottomNav.itemBackground = null
bottomNav.backgroundTintList = null
// Circle 页底栏保持完全透明
bottomNavBlur.visibility = View.GONE
bottomNav.background = ColorDrawable(android.graphics.Color.TRANSPARENT)
}
private fun resetBottomNavBackground() {
bottomNav.itemBackground = null
bottomNav.background = ColorDrawable(ContextCompat.getColor(this, android.R.color.white))
bottomNav.backgroundTintList = null
}
private fun setupBottomNavBlur() {
// 全局移除底栏毛玻璃效果
blurReady = false
bottomNavBlur.visibility = View.GONE
}
/** 打开全局页login/recharge等 */
private fun openGlobal(destId: Int, bundle: Bundle? = null, clearBackStack: Boolean = false) {
val fm = supportFragmentManager
if (fm.isStateSaved) return
val navOptions = if (clearBackStack) {
navOptions {
// 清理 global overlay 栈,但不短暂显示 empty避免闪屏
popUpTo(R.id.globalEmptyFragment) { inclusive = false }
}
} else {
null
}
try {
if (bundle != null) globalNavController.navigate(destId, bundle, navOptions)
else globalNavController.navigate(destId, null, navOptions)
} catch (e: IllegalArgumentException) {
e.printStackTrace()
}
bottomNav.post { updateBottomNavVisibility() }
}
/** Tab 内页面变化时刷新底栏显隐 */
private fun bindBottomNavVisibilityForTabs() {
val listener = NavController.OnDestinationChangedListener { _, _, _ ->
updateBottomNavVisibility()
}
homeHost.navController.addOnDestinationChangedListener(listener)
shopHost.navController.addOnDestinationChangedListener(listener)
mineHost.navController.addOnDestinationChangedListener(listener)
circleHost.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)
circleHost.navController.addOnDestinationChangedListener(circleRouteListener)
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()
// ✅ pop 后刷新一次注意currentDestination 可能要等一帧才更新,所以 post
bottomNav.post { updateBottomNavVisibility() }
// popped = true 表示确实 pop 了;即使 popped=false 也可能已经在 empty 了
return popped || !isGlobalVisible()
}
/**
* ✅ 改这里:不要再用 View.visibility 判断 overlay
* 以 NavController 的目的地为准
*/
private fun isGlobalVisible(): Boolean {
return globalNavController.currentDestination?.id != R.id.globalEmptyFragment
}
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) {
bottomNav.post { updateBottomNavVisibility() }
return
}
// 3) 当前tab到根了如果不是home切回home否则退出
if (currentTabTag != TAB_HOME) {
bottomNav.post { bottomNav.selectedItemId = R.id.home_graph }
switchTab(TAB_HOME)
} else {
finish()
}
}
})
}
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
}
openGlobal(R.id.rechargeFragment)
}
}
if (navigateTo == "login_fragment") {
bottomNav.post {
openGlobal(R.id.loginFragment)
}
}
}
private fun isLoggedIn(): Boolean {
return EncryptedSharedPreferencesUtil.contains(this, "user")
}
override fun onSaveInstanceState(outState: Bundle) {
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.languageFragment -> "language_settings" // 语言切换页
/** ==================== 登录 & 注册 ==================== */
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,
)
}
// 网络检测:无网络时弹窗提醒,恢复后分发刷新事件
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()
}
}