国际化

This commit is contained in:
pengxiaolong
2026-01-21 21:53:34 +08:00
parent 066cea19df
commit bab447c23f
90 changed files with 1699 additions and 343 deletions

View File

@@ -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()
}
}