国际化
This commit is contained in:
@@ -1,20 +1,37 @@
|
||||
package com.example.myapplication
|
||||
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
|
||||
|
||||
@@ -25,6 +42,7 @@ class MainActivity : AppCompatActivity() {
|
||||
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
|
||||
@@ -41,20 +59,29 @@ class MainActivity : AppCompatActivity() {
|
||||
mapOf(
|
||||
R.id.home_graph to TAB_HOME,
|
||||
R.id.shop_graph to TAB_SHOP,
|
||||
R.id.mine_graph to TAB_MINE
|
||||
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
|
||||
}
|
||||
|
||||
@@ -71,6 +98,7 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
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
|
||||
|
||||
@@ -89,6 +117,13 @@ class MainActivity : AppCompatActivity() {
|
||||
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
|
||||
@@ -108,8 +143,10 @@ class MainActivity : AppCompatActivity() {
|
||||
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
|
||||
@@ -125,9 +162,10 @@ class MainActivity : AppCompatActivity() {
|
||||
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
|
||||
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)
|
||||
@@ -164,6 +202,7 @@ class MainActivity : AppCompatActivity() {
|
||||
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
|
||||
}
|
||||
@@ -195,7 +234,7 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
// 打开全局页面事件处理
|
||||
is AuthEvent.OpenGlobalPage -> {
|
||||
openGlobal(event.destinationId, event.bundle)
|
||||
openGlobal(event.destinationId, event.bundle, event.clearGlobalBackStack)
|
||||
}
|
||||
|
||||
is AuthEvent.UserUpdated -> {
|
||||
@@ -221,13 +260,16 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
// 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
|
||||
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() {
|
||||
@@ -241,9 +283,16 @@ class MainActivity : AppCompatActivity() {
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -254,6 +303,8 @@ class MainActivity : AppCompatActivity() {
|
||||
?: 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)
|
||||
|
||||
@@ -266,6 +317,7 @@ class MainActivity : AppCompatActivity() {
|
||||
.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()
|
||||
}
|
||||
@@ -293,7 +345,7 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 这些页面需要隐藏底部导航栏:你按需加/减
|
||||
* 需要隐藏底部导航栏的页面
|
||||
*/
|
||||
private fun shouldHideBottomNav(destId: Int): Boolean {
|
||||
return destId in setOf(
|
||||
@@ -301,6 +353,7 @@ class MainActivity : AppCompatActivity() {
|
||||
R.id.searchResultFragment,
|
||||
R.id.MySkin,
|
||||
R.id.notificationFragment,
|
||||
R.id.languageFragment,
|
||||
R.id.feedbackFragment,
|
||||
R.id.MyKeyboard,
|
||||
R.id.PersonalSettings,
|
||||
@@ -308,19 +361,19 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ 统一底栏显隐逻辑:任何地方状态变化都调用它
|
||||
* 统一底栏显隐逻辑:任何地方状态变化都调用它
|
||||
*/
|
||||
private fun updateBottomNavVisibility() {
|
||||
// ✅ 只要 global overlay 不在 empty,底栏必须隐藏(用 NavController 判断,别用 View.visibility)
|
||||
if (isGlobalVisible()) {
|
||||
bottomNav.visibility = View.GONE
|
||||
bottomNavBlur.visibility = View.GONE
|
||||
return
|
||||
}
|
||||
|
||||
// 否则按“当前可见 tab 的当前目的地”判断
|
||||
val destId = currentTabNavController.currentDestination?.id
|
||||
bottomNav.visibility =
|
||||
if (destId != null && shouldHideBottomNav(destId)) View.GONE else View.VISIBLE
|
||||
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() {
|
||||
@@ -371,6 +424,7 @@ class MainActivity : AppCompatActivity() {
|
||||
val targetHost = when (targetTag) {
|
||||
TAB_SHOP -> shopHost
|
||||
TAB_MINE -> mineHost
|
||||
TAB_CIRCLE -> circleHost
|
||||
else -> homeHost
|
||||
}
|
||||
val currentHost = currentTabHost
|
||||
@@ -386,6 +440,7 @@ class MainActivity : AppCompatActivity() {
|
||||
.hide(homeHost)
|
||||
.hide(shopHost)
|
||||
.hide(mineHost)
|
||||
.hide(circleHost)
|
||||
.show(targetHost)
|
||||
} else if (currentHost != targetHost) {
|
||||
transaction
|
||||
@@ -397,9 +452,11 @@ class MainActivity : AppCompatActivity() {
|
||||
.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) {
|
||||
@@ -417,14 +474,72 @@ class MainActivity : AppCompatActivity() {
|
||||
.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<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
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开全局页(login/recharge等) */
|
||||
private fun openGlobal(destId: Int, bundle: Bundle? = null) {
|
||||
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)
|
||||
else globalNavController.navigate(destId)
|
||||
if (bundle != null) globalNavController.navigate(destId, bundle, navOptions)
|
||||
else globalNavController.navigate(destId, null, navOptions)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
@@ -441,6 +556,7 @@ class MainActivity : AppCompatActivity() {
|
||||
homeHost.navController.addOnDestinationChangedListener(listener)
|
||||
shopHost.navController.addOnDestinationChangedListener(listener)
|
||||
mineHost.navController.addOnDestinationChangedListener(listener)
|
||||
circleHost.navController.addOnDestinationChangedListener(listener)
|
||||
BehaviorReporter.report(
|
||||
isNewUser = false,
|
||||
"page_id" to "home",
|
||||
@@ -452,6 +568,7 @@ class MainActivity : AppCompatActivity() {
|
||||
homeHost.navController.addOnDestinationChangedListener(homeRouteListener)
|
||||
shopHost.navController.addOnDestinationChangedListener(shopRouteListener)
|
||||
mineHost.navController.addOnDestinationChangedListener(mineRouteListener)
|
||||
circleHost.navController.addOnDestinationChangedListener(circleRouteListener)
|
||||
globalHost.navController.addOnDestinationChangedListener(globalRouteListener)
|
||||
|
||||
// ✅ 删除:初始化手动上报(否则启动时会重复上报)
|
||||
@@ -559,6 +676,7 @@ class MainActivity : AppCompatActivity() {
|
||||
R.id.notificationFragment -> "notice" // 消息通知
|
||||
R.id.feedbackFragment -> "feedback" // 意见反馈
|
||||
R.id.consumptionRecordFragment -> "consumption_record" // 消费记录
|
||||
R.id.languageFragment -> "language_settings" // 语言切换页
|
||||
|
||||
/** ==================== 登录 & 注册 ==================== */
|
||||
R.id.loginFragment -> "login" // 登录页
|
||||
@@ -598,4 +716,97 @@ class MainActivity : AppCompatActivity() {
|
||||
"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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user