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 eightbitlab.com.blurview.RenderEffectBlur import eightbitlab.com.blurview.RenderScriptBlur 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) } 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, ) } /** * 统一底栏显隐逻辑:任何地方状态变化都调用它 */ 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(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 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(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 } } /** 打开全局页(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(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() } }