新增viewmodel,修改完善hardware

This commit is contained in:
yuqian 2026-01-16 11:36:43 +08:00
parent d3de7d8907
commit 596daf5fcc
11 changed files with 763 additions and 353 deletions

View File

@ -6,12 +6,18 @@ import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.xyzshell.myphoneinfo.R import com.xyzshell.myphoneinfo.R
import com.xyzshell.myphoneinfo.main.MainViewModel
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() { abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
protected lateinit var binding: VB protected lateinit var binding: VB
protected val mainViewModel: MainViewModel by lazy {
ViewModelProvider(this)[MainViewModel::class.java]
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
@ -28,7 +34,9 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
protected abstract fun inflateBinding(inflater: LayoutInflater): VB protected abstract fun inflateBinding(inflater: LayoutInflater): VB
protected open fun initView() {} protected open fun initView() {
mainViewModel
}
protected open fun initData() {} protected open fun initData() {}
} }

View File

@ -0,0 +1,76 @@
package com.xyzshell.myphoneinfo.custom
import android.os.Handler
import android.os.Looper
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
object RefreshManager {
private var isLoading = false
private val handler = Handler(Looper.getMainLooper())
private var timeoutRunnable: Runnable? = null
/**
* 开始刷新
* @param swipeRefreshLayout 可选的 SwipeRefreshLayout会自动显示刷新动画
* @param timeoutMs 超时时间毫秒默认10秒
* @param onTimeout 超时回调
*/
fun load(
swipeRefreshLayout: SwipeRefreshLayout? = null,
timeoutMs: Long = 10000L,
onTimeout: (() -> Unit)? = null
) {
if (isLoading) return
isLoading = true
swipeRefreshLayout?.isRefreshing = true
// 设置超时
timeoutRunnable = Runnable {
if (isLoading) {
isLoading = false
swipeRefreshLayout?.isRefreshing = false
onTimeout?.invoke()
}
}
handler.postDelayed(timeoutRunnable!!, timeoutMs)
}
/**
* 停止刷新
* @param swipeRefreshLayout 可选的 SwipeRefreshLayout会自动停止刷新动画
*/
fun stop(swipeRefreshLayout: SwipeRefreshLayout? = null) {
if (!isLoading) return
isLoading = false
timeoutRunnable?.let { handler.removeCallbacks(it) }
timeoutRunnable = null
swipeRefreshLayout?.isRefreshing = false
}
/**
* 检查是否正在刷新
*/
fun isLoading(): Boolean = isLoading
/**
* 安全的执行刷新操作最简用法
* @param refreshAction 刷新逻辑
* @param swipeRefreshLayout SwipeRefreshLayout
* @param timeoutMs 超时时间
*/
fun safeRefresh(
refreshAction: () -> Unit,
swipeRefreshLayout: SwipeRefreshLayout,
timeoutMs: Long = 10000L
) {
if (isLoading) return
load(swipeRefreshLayout, timeoutMs) {
// 超时回调
}
refreshAction()
}
}

View File

@ -6,16 +6,20 @@ import androidx.fragment.app.Fragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.xyzshell.andinfo.AndInfo import com.xyzshell.andinfo.AndInfo
import com.xyzshell.andinfo.libs.AppDetails import com.xyzshell.andinfo.libs.AppDetails
import com.xyzshell.myphoneinfo.R import com.xyzshell.myphoneinfo.R
import com.xyzshell.myphoneinfo.adapter.AppListAdapter import com.xyzshell.myphoneinfo.adapter.AppListAdapter
import com.xyzshell.myphoneinfo.custom.RefreshManager.load
import com.xyzshell.myphoneinfo.custom.RefreshManager.stop
import com.xyzshell.myphoneinfo.databinding.FragmentAppsBinding import com.xyzshell.myphoneinfo.databinding.FragmentAppsBinding
import com.xyzshell.myphoneinfo.dialog.AppDialogFragment import com.xyzshell.myphoneinfo.dialog.AppDialogFragment
import com.xyzshell.myphoneinfo.dialog.BottomDialogFragment import com.xyzshell.myphoneinfo.dialog.BottomDialogFragment
import com.xyzshell.myphoneinfo.main.MainScrollActivity import com.xyzshell.myphoneinfo.main.MainScrollActivity
import com.xyzshell.myphoneinfo.main.MainViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -25,7 +29,9 @@ class AppsFragment : Fragment(),AppListAdapter.OnShowDialogListener {
private lateinit var binding: FragmentAppsBinding private lateinit var binding: FragmentAppsBinding
private lateinit var dialogFragment: AppDialogFragment private lateinit var dialogFragment: AppDialogFragment
private lateinit var bottomDialog: BottomDialogFragment private lateinit var bottomDialog: BottomDialogFragment
private val viewModel: MainViewModel by activityViewModels()
private var sel: Int? = 0 private var sel: Int? = 0
private var userSize=0
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
} }
@ -34,17 +40,15 @@ class AppsFragment : Fragment(),AppListAdapter.OnShowDialogListener {
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
// Inflate the layout for this fragment
binding = FragmentAppsBinding.inflate(inflater, container, false)
val adapter= AppListAdapter() val adapter= AppListAdapter()
val installedApps = AndInfo.instance.app.getInstalledApps() binding = FragmentAppsBinding.inflate(inflater, container, false)
val userSize=installedApps.size viewModel.installedApps.observe(viewLifecycleOwner){
viewLifecycleOwner.lifecycleScope.launch { appDetails ->
delay(50) // 短暂延迟确保UI完成布局 stop(binding.swipeRefresh)
withContext(Dispatchers.Main) { adapter.setList(appDetails)
adapter.setList(installedApps) userSize=appDetails.size
adapter.setOnclickListener(this@AppsFragment) }
}} adapter.setOnclickListener(this@AppsFragment)
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext()) binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
binding.tvTitle.text=when(0){//todo replace with user/system/all/none binding.tvTitle.text=when(0){//todo replace with user/system/all/none
@ -53,6 +57,10 @@ class AppsFragment : Fragment(),AppListAdapter.OnShowDialogListener {
2 -> "All(23)" 2 -> "All(23)"
else -> "None" else -> "None"
} }
binding.swipeRefresh.setOnRefreshListener {
load(binding.swipeRefresh)
viewModel.loadApps()
}
binding.llTitle.setOnClickListener { binding.llTitle.setOnClickListener {
bottomDialog= BottomDialogFragment(type = "apps", sel = sel, invoke = {item-> bottomDialog= BottomDialogFragment(type = "apps", sel = sel, invoke = {item->
sel = item sel = item

View File

@ -9,6 +9,7 @@ import android.os.Bundle
import android.os.CountDownTimer import android.os.CountDownTimer
import android.provider.Settings import android.provider.Settings
import android.text.format.Formatter import android.text.format.Formatter
import android.util.Log
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -16,8 +17,10 @@ import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.fragment.app.activityViewModels
import com.xyzshell.andinfo.AndInfo import com.xyzshell.andinfo.AndInfo
import com.xyzshell.andinfo.libs.CpuInfo import com.xyzshell.andinfo.libs.CpuInfo
import com.xyzshell.andinfo.libs.MemInfo
import com.xyzshell.myphoneinfo.R import com.xyzshell.myphoneinfo.R
import com.xyzshell.myphoneinfo.custom.SetNumberOrWordUtils.convertToApproximateAspectRatio import com.xyzshell.myphoneinfo.custom.SetNumberOrWordUtils.convertToApproximateAspectRatio
import com.xyzshell.myphoneinfo.databinding.FragmentHardWareBinding import com.xyzshell.myphoneinfo.databinding.FragmentHardWareBinding
@ -25,6 +28,8 @@ import com.xyzshell.myphoneinfo.dialog.DialogBlueTooth
import com.xyzshell.myphoneinfo.dialog.DialogDiskPart import com.xyzshell.myphoneinfo.dialog.DialogDiskPart
import com.xyzshell.myphoneinfo.dialog.DialogExtension import com.xyzshell.myphoneinfo.dialog.DialogExtension
import com.xyzshell.myphoneinfo.dialog.DialogInput import com.xyzshell.myphoneinfo.dialog.DialogInput
import com.xyzshell.myphoneinfo.main.MainViewModel
import kotlin.getValue
import kotlin.math.roundToInt import kotlin.math.roundToInt
class HardWareFragment : Fragment() { class HardWareFragment : Fragment() {
@ -34,6 +39,8 @@ class HardWareFragment : Fragment() {
private var dialogInput: DialogInput? = null private var dialogInput: DialogInput? = null
private var dialogExtension: DialogExtension? = null private var dialogExtension: DialogExtension? = null
private var dialogDiskPart: DialogDiskPart? = null private var dialogDiskPart: DialogDiskPart? = null
private val mainViewModel: MainViewModel by activityViewModels()
private val requestPermissionLauncher = registerForActivityResult( private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions() ActivityResultContracts.RequestMultiplePermissions()
@ -59,6 +66,7 @@ class HardWareFragment : Fragment() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
checkAndDisplayBluetoothInfo() checkAndDisplayBluetoothInfo()
mainViewModel.refreshHardwareInfo(requireContext())
} }
@ -68,7 +76,7 @@ class HardWareFragment : Fragment() {
): View? { ): View? {
binding = FragmentHardWareBinding.inflate(inflater, container, false) binding = FragmentHardWareBinding.inflate(inflater, container, false)
dialogExtension = dialogExtension ?: DialogExtension() dialogExtension = dialogExtension ?: DialogExtension()
initText() initView()
binding.cpuBtn.setOnClickListener { binding.cpuBtn.setOnClickListener {
var intent = Intent(requireContext(), AnalysisActivity::class.java) var intent = Intent(requireContext(), AnalysisActivity::class.java)
startActivity(intent) startActivity(intent)
@ -84,7 +92,7 @@ class HardWareFragment : Fragment() {
return binding.root return binding.root
} }
private fun initText() { private fun initView() {
setProcessorInfo() setProcessorInfo()
setGpuInfo() setGpuInfo()
setDisplayInfo() setDisplayInfo()
@ -107,27 +115,31 @@ class HardWareFragment : Fragment() {
* Input信息 * Input信息
* */ * */
private fun setInputInfo() { private fun setInputInfo() {
val inputInfo = AndInfo.instance.input
binding.otherCheck1.content.text = getString(R.string.usb_host_support) binding.otherCheck1.content.text = getString(R.string.usb_host_support)
binding.otherCheck1.image.isSelected = inputInfo.hasUsbHostSupport()
binding.otherCheck2.content.text = getString(R.string.usb_accessory_support) binding.otherCheck2.content.text = getString(R.string.usb_accessory_support)
binding.otherCheck2.image.isSelected = inputInfo.hasUsbAccessorySupport()
binding.otherCheck3.content.text = getString(R.string.fingerprint) binding.otherCheck3.content.text = getString(R.string.fingerprint)
binding.otherCheck3.image.isSelected = inputInfo.hasFingerprintSensor()
binding.otherCheck4.content.text = getString(R.string.infrared_transmitter) binding.otherCheck4.content.text = getString(R.string.infrared_transmitter)
binding.otherCheck4.image.isSelected = inputInfo.hasInfraredSensor()
binding.otherCheck5.content.text = getString(R.string.uwb_support) binding.otherCheck5.content.text = getString(R.string.uwb_support)
binding.otherCheck5.image.isSelected = inputInfo.hasUwbSupport()
binding.otherCheck6.content.text = getString(R.string.nfc_support) binding.otherCheck6.content.text = getString(R.string.nfc_support)
binding.otherCheck6.image.isSelected = inputInfo.hasNfcSupport()
binding.otherCheck7.content.text = getString(R.string.secure_nfc_support) binding.otherCheck7.content.text = getString(R.string.secure_nfc_support)
binding.otherCheck7.image.isSelected = inputInfo.hasSecureNfcSupport()
binding.otherCheck8.content.text = getString(R.string.gps) binding.otherCheck8.content.text = getString(R.string.gps)
binding.otherCheck8.image.isSelected = inputInfo.hasGpsSupport()
binding.othertext.setOnClickListener { binding.othertext.setOnClickListener {
dialogInput = dialogInput ?: DialogInput() dialogInput = dialogInput ?: DialogInput()
dialogInput?.show(childFragmentManager, "Input") dialogInput?.show(childFragmentManager, "Input")
} }
mainViewModel.input.observe(viewLifecycleOwner) { inputInfo ->
inputInfo?.let {
binding.otherCheck1.image.isSelected = inputInfo.hasUsbHostSupport()
binding.otherCheck2.image.isSelected = inputInfo.hasUsbAccessorySupport()
binding.otherCheck3.image.isSelected = inputInfo.hasFingerprintSensor()
binding.otherCheck4.image.isSelected = inputInfo.hasInfraredSensor()
binding.otherCheck5.image.isSelected = inputInfo.hasUwbSupport()
binding.otherCheck6.image.isSelected = inputInfo.hasNfcSupport()
binding.otherCheck7.image.isSelected = inputInfo.hasSecureNfcSupport()
binding.otherCheck8.image.isSelected = inputInfo.hasGpsSupport()
}
}
} }
@ -135,233 +147,293 @@ class HardWareFragment : Fragment() {
* memory信息 * memory信息
* */ * */
private fun setMemoryInfo() { private fun setMemoryInfo() {
val storageInfo = AndInfo.instance.storage//存储信息
// 获取详细的存储分解
val breakdown = storageInfo.getInternalStorageBreakdown()
//内部存储器
val interused = storageInfo.formatBytes(breakdown.usedSpace)
val interfree = storageInfo.formatBytes(breakdown.freeSpace)
val intertotal = storageInfo.formatBytes(breakdown.totalSpace)
mainViewModel.memInfo.observe(viewLifecycleOwner){memInfo ->
memInfo?.let {
//内存memory
val memUsed = memInfo.formatBytes(memInfo.getMemoryInfo().usedRam)
val memFree = memInfo.formatBytes(memInfo.getMemoryInfo().availableRam)
val memTotal = memInfo.formatBytes(memInfo.getMemoryInfo().totalRam)
val zramTotal = memInfo.formatBytes(memInfo.getMemoryInfo().zramTotal)
val zramUsed = memInfo.formatBytes(memInfo.getMemoryInfo().zramUsed)
val zramFree =
memInfo.formatBytes(memInfo.getMemoryInfo().zramTotal - memInfo.getMemoryInfo().zramUsed)
//总存储信息 binding.memoryLayout.memText0.textTitle.text = getString(R.string.type)
val outused =Formatter.formatFileSize(AndInfo.instance.context, storageInfo.internalStorageUsedSpace) binding.memoryLayout.memText0.textContent.text = memInfo.getMemoryInfo().memType
val outfree = Formatter.formatFileSize(AndInfo.instance.context, storageInfo.internalStorageAvailableSpace) binding.memoryLayout.memText1.textTitle.text = getString(R.string.ram_size)
val outtotal = Formatter.formatFileSize(AndInfo.instance.context, storageInfo.internalStorageTotalSpace) binding.memoryLayout.memText1.textContent.text = memTotal
binding.memoryLayout.ram1.text = "$memUsed used"
binding.memoryLayout.ram2.text = "$memFree free"
binding.memoryLayout.seekbar.progress =
(memUsed.substringBefore(" ").toDouble() / memTotal
.substringBefore(" ")
.toDouble() * 100).toInt()
//zarm
//内存todo binding.memoryLayout.memText3.textTitle.text = getString(R.string.zram)
binding.memoryLayout.memText1.textTitle.text = getString(R.string.ram_size) binding.memoryLayout.memText3.textContent.text = zramTotal
// binding.memoryLayout.memText1.textContent.text = total binding.memoryLayout.ram3.text = "$zramUsed used"
// binding.memoryLayout.ram1.text = used + " used" binding.memoryLayout.ram4.text = "$zramFree free"
// binding.memoryLayout.ram2.text = free + " free" binding.memoryLayout.seekbar2.progress =
// binding.memoryLayout.seekbar.progress = (zramTotal.substringBefore(" ").toDouble() / zramTotal.substringBefore(" ")
// (used.substringBefore(" ").toDouble() / total.substringBefore(" ") .toDouble() * 100).toInt()
// .toDouble() * 100).toInt() }
}
//appsAndData
val appsAndData = storageInfo.getFormattedAppsAndDataSize()
val system = storageInfo.getFormattedSystemSize()
val other= storageInfo.getFormattedFreeSpace()+storageInfo.getFormattedCacheSize()
//zarm
binding.memoryLayout.memText3.textTitle.text = getString(R.string.zram)
binding.memoryLayout.memText3.textContent.text = Formatter.formatFileSize(
AndInfo.instance.context,
storageInfo.internalStorageTotalSpace
)
binding.memoryLayout.ram3.text = outused + " used"
binding.memoryLayout.ram4.text = outfree + " free"
binding.memoryLayout.seekbar2.progress =
(outused.substringBefore(" ").toDouble() / outtotal.substringBefore(" ")
.toDouble() * 100).toInt()
//storage //storage
// 获取总存储详细的存储分解
mainViewModel.storageInfo.observe(viewLifecycleOwner){storageInfo ->
storageInfo?.let {
val breakdown = storageInfo.getInternalStorageBreakdown()
val stoUsed = storageInfo.formatBytes(breakdown.usedSpace)
val stoFree = storageInfo.formatBytes(breakdown.freeSpace)
val stoTotal = storageInfo.formatBytes(breakdown.totalSpace)
//appsAndData
val appsAndData = storageInfo.getFormattedAppsAndDataSize()
val system = storageInfo.getFormattedSystemSize()
val other = storageInfo.getFormattedFreeSpace()
binding.memoryLayout.storText1.textTitle.text = getString(R.string.size) binding.memoryLayout.storText1.textTitle.text = getString(R.string.size)
binding.memoryLayout.storText1.textContent.text = outtotal binding.memoryLayout.storText1.textContent.text = stoTotal
binding.memoryLayout.stor1.text = outused + " used" binding.memoryLayout.stor1.text = stoUsed + " used"
binding.memoryLayout.stor2.text = outtotal + " total" binding.memoryLayout.stor2.text = stoTotal + " total"
binding.memoryLayout.radius1.text = appsAndData binding.memoryLayout.radius1.text = appsAndData
binding.memoryLayout.radius2.text = system binding.memoryLayout.radius2.text = system
binding.memoryLayout.radius3.text = other binding.memoryLayout.radius3.text = other
binding.memoryLayout.seekbar3.progress = binding.memoryLayout.seekbar3.progress =
(outused.substringBefore(" ").toDouble() / outtotal.substringBefore(" ") (stoUsed.substringBefore(" ").toDouble() / stoTotal.substringBefore(" ")
.toDouble() * 100).toInt() .toDouble() * 100).toInt()
//internalStorage
val interUsed = storageInfo.getInternalStorageBreakdown().usedSpace
val interFree = storageInfo.getInternalStorageBreakdown().freeSpace
val interTotal = storageInfo.getInternalStorageBreakdown().totalSpace
binding.memoryLayout.interText1.textTitle.text = getString(R.string.filesystem)
binding.memoryLayout.interText1.textContent.text =
storageInfo.internalStorageFileSystemType.toString()
binding.memoryLayout.interText2.textTitle.text = getString(R.string.block_size)
binding.memoryLayout.interText2.textContent.text = storageInfo.formatBytes(interTotal)
binding.memoryLayout.interText3.textTitle.text = "/data"
binding.memoryLayout.interText3.textContent.text = storageInfo.formatBytes(storageInfo.dataDirectoryTotalSpace)
binding.memoryLayout.stor3.text = "${storageInfo.formatBytes(interUsed)} used"
binding.memoryLayout.stor4.text = "${storageInfo.formatBytes(interFree)} free"
binding.memoryLayout.seekbar5.progress =
(interUsed.toDouble() / interTotal.toDouble() * 100).toInt()
storageInfo.abOtaPartitions?.forEach { text ->
Log.d("TTTTTTTT","disk part:${text}")
}
}
}
//internalStorage
binding.memoryLayout.interText1.textTitle.text = getString(R.string.filesystem)
binding.memoryLayout.interText1.textContent.text =
storageInfo.internalStorageEncryptionType.toString()
binding.memoryLayout.interText2.textTitle.text = getString(R.string.block_size)
binding.memoryLayout.interText2.textContent.text = intertotal
binding.memoryLayout.interText3.textTitle.text = "/data"
binding.memoryLayout.interText3.textContent.text = intertotal
binding.memoryLayout.stor3.text = interused + " used"
binding.memoryLayout.stor4.text = interfree + " free"
binding.memoryLayout.seekbar2.progress =
(interused.substringBefore(" ").toDouble() / intertotal.substringBefore(" ")
.toDouble() * 100).toInt()
} }
/** /**
* display信息 * display信息
* */ * */
private fun setDisplayInfo() { private fun setDisplayInfo() {
val display = AndInfo.instance.display//显示信息
val defaultDisplayInfo = display.getDefaultDisplayInfo()
val width = defaultDisplayInfo?.widthPixels.toString()
val height = defaultDisplayInfo?.heightPixels.toString()
if (defaultDisplayInfo != null) {
binding.disText1.textContent.text = width + "x" + height
}
binding.disText1.textTitle.text = getString(R.string.resolution) binding.disText1.textTitle.text = getString(R.string.resolution)
binding.disText2.textTitle.text = getString(R.string.screen_density)//这个是px binding.disText2.textTitle.text = getString(R.string.screen_density)//这个是px
binding.disText2.textContent.text = defaultDisplayInfo?.ppi.toString()
binding.disText3.textTitle.text = getString(R.string.screen_density_d) binding.disText3.textTitle.text = getString(R.string.screen_density_d)
val dpiStr =
defaultDisplayInfo?.densityDpi.toString() + "(xxhdpi)\n" + defaultDisplayInfo?.xdpi?.roundToInt() + "dpx" + defaultDisplayInfo?.ydpi?.roundToInt() + "dp"
binding.disText3.textContent.text = dpiStr
binding.disText4.textTitle.text = getString(R.string.screen_size_e) binding.disText4.textTitle.text = getString(R.string.screen_size_e)
val screenSize = "${String.format("%.2f", display.getScreenSize()?.diagonalInches)}/ ${String.format("%.1f", display.getScreenSize()?.diagonalMm)} mm"
binding.disText4.textContent.text = screenSize
binding.disText5.textTitle.text = getString(R.string.aspect_ratio) binding.disText5.textTitle.text = getString(R.string.aspect_ratio)
binding.disText5.textContent.text = convertToApproximateAspectRatio(
width = width.toInt(),
height = height.toInt()
)
binding.disText6.textTitle.text = getString(R.string.refresh_rate) binding.disText6.textTitle.text = getString(R.string.refresh_rate)
binding.disText6.textContent.text = defaultDisplayInfo?.refreshRate.toString() + "HZ"
binding.disText7.textTitle.text = getString(R.string.wide_color_gamut) binding.disText7.textTitle.text = getString(R.string.wide_color_gamut)
binding.disText7.textContent.text = defaultDisplayInfo?.isWideColorGamut.toString()
binding.disText8.textTitle.text = getString(R.string.hdr_support) binding.disText8.textTitle.text = getString(R.string.hdr_support)
if (defaultDisplayInfo?.isHdr == true) { mainViewModel.displayInfo.observe(viewLifecycleOwner) { display ->
defaultDisplayInfo.hdrTypes.joinToString() display?.let {
binding.disText8.textContent.text = defaultDisplayInfo.hdrTypes.joinToString() val defaultDisplayInfo = display.getDefaultDisplayInfo()
} else { val width = defaultDisplayInfo?.widthPixels.toString()
binding.disText8.textContent.text = "No" val height = defaultDisplayInfo?.heightPixels.toString()
if (defaultDisplayInfo != null) {
binding.disText1.textContent.text = width + "x" + height
}
binding.disText2.textContent.text = defaultDisplayInfo?.ppi.toString()
val dpiStr =
defaultDisplayInfo?.densityDpi.toString() + "(xxhdpi)\n" + defaultDisplayInfo?.xdpi?.roundToInt() + "dpx" + defaultDisplayInfo?.ydpi?.roundToInt() + "dp"
binding.disText3.textContent.text = dpiStr
val screenSize = "${
String.format(
"%.2f",
display.getScreenSize()?.diagonalInches
)
}/ ${String.format("%.1f", display.getScreenSize()?.diagonalMm)} mm"
binding.disText4.textContent.text = screenSize
binding.disText5.textContent.text = convertToApproximateAspectRatio(
width = width.toInt(),
height = height.toInt()
)
binding.disText6.textContent.text =
defaultDisplayInfo?.refreshRate.toString() + "HZ"
binding.disText7.textContent.text = defaultDisplayInfo?.isWideColorGamut.toString()
if (defaultDisplayInfo?.isHdr == true) {
defaultDisplayInfo.hdrTypes.joinToString()
binding.disText8.textContent.text = defaultDisplayInfo.hdrTypes.joinToString()
} else {
binding.disText8.textContent.text = "No"
}
}
} }
} }
/** /**
* gpu信息*/ * gpu信息*/
private fun setGpuInfo() { private fun setGpuInfo() {
val gpu = AndInfo.instance.gpu//gpu信息
binding.gpuText1.textTitle.text = getString(R.string.vendor) binding.gpuText1.textTitle.text = getString(R.string.vendor)
binding.gpuText1.textContent.text = gpu.getVendorName()
binding.gpuText2.textTitle.text = getString(R.string.gpu) binding.gpuText2.textTitle.text = getString(R.string.gpu)
gpu.getGpuInformation().vkPhysicalDevices?.let { devices ->
if (devices.isNotEmpty()) {
devices.forEachIndexed { index, vkPhysicalDevice ->
binding.gpuText2.textContent.text = vkPhysicalDevice.deviceName
}
}
}
binding.gpuText3.textTitle.text = getString(R.string.max_frequency) binding.gpuText3.textTitle.text = getString(R.string.max_frequency)
binding.gpuText3.textContent.text = "${gpu.getMaxFrequency()} MHz"
binding.gpuText4.textTitle.text = getString(R.string.architecture) binding.gpuText4.textTitle.text = getString(R.string.architecture)
binding.gpuText4.textContent.text = gpu.getArchitecture()
binding.gpuText5.textTitle.text = getString(R.string.cores) binding.gpuText5.textTitle.text = getString(R.string.cores)
binding.gpuText5.textContent.text = gpu.getOpenGLExtensionCount().toString()
binding.gpuText6.textTitle.text = getString(R.string.total_l2) binding.gpuText6.textTitle.text = getString(R.string.total_l2)
binding.gpuText6.textContent.text = "${gpu.getCacheSize()} KB"
binding.gpuText7.textTitle.text = getString(R.string.bus_width) binding.gpuText7.textTitle.text = getString(R.string.bus_width)
binding.gpuText7.textContent.text = "${gpu.getBandwidth()} GB/s"
binding.gpuText8.textTitle.text = getString(R.string.vulkan_support) binding.gpuText8.textTitle.text = getString(R.string.vulkan_support)
binding.gpuText8.textContent.text = gpu.isVulkanSupported().toString()
binding.gpuText9.textTitle.text = getString(R.string.vulkan_API) binding.gpuText9.textTitle.text = getString(R.string.vulkan_API)
binding.gpuText9.textContent.text = gpu.getVulkanApiVersion()
binding.open1.text = getString(R.string.opengl) binding.open1.text = getString(R.string.opengl)
gpu.getGpuInformation().eglInformation?.let { eglInfo -> mainViewModel.gpuInfo.observe(viewLifecycleOwner) { gpu ->
eglInfo.eglExtensions?.let { gpu?.let {
dialogExtension?.setContent(it.joinToString()) binding.gpuText1.textContent.text = gpu.getVendorName()
gpu.getGpuInformation().vkPhysicalDevices?.let { devices ->
if (devices.isNotEmpty()) {
devices.forEachIndexed { index, vkPhysicalDevice ->
binding.gpuText2.textContent.text = vkPhysicalDevice.deviceName
}
}
}
binding.gpuText3.textContent.text = "${gpu.getMaxFrequency()} MHz"
binding.gpuText4.textContent.text = gpu.getArchitecture()
binding.gpuText5.textContent.text = gpu.getOpenGLExtensionCount().toString()
binding.gpuText6.textContent.text = "${gpu.getCacheSize()} KB"
binding.gpuText7.textContent.text = "${gpu.getBandwidth()} GB/s"
binding.gpuText8.textContent.text = gpu.isVulkanSupported().toString()
binding.gpuText9.textContent.text = gpu.getVulkanApiVersion()
gpu.getGpuInformation().eglInformation?.let { eglInfo ->
eglInfo.eglExtensions?.let {
dialogExtension?.setContent(it.joinToString())
}
}
binding.openItem3.text = gpu.getOpenGLVersion()
} }
} }
// binding.openItem1.text = gpu.getVendorName()
// binding.openItem2.text = gpu.getRendererName()
binding.openItem3.text = gpu.getOpenGLVersion()
} }
/** /**
* processor信息*/ * processor信息*/
private fun setProcessorInfo() { private fun setProcessorInfo() {
val cpu = AndInfo.instance.cpu//cpu信息
//处理器 //处理器
binding.text0.textTitle.text = getString(R.string.processor) binding.text0.textTitle.text = getString(R.string.processor)
binding.text0.textContent.text = cpu.getProcessorName()
//制造商 //制造商
binding.text1.textTitle.text = getString(R.string.vendor) binding.text1.textTitle.text = getString(R.string.vendor)
binding.text1.textContent.text = cpu.getVendor()
//硬件 //硬件
binding.text2.textTitle.text = getString(R.string.hardware) binding.text2.textTitle.text = getString(R.string.hardware)
binding.text2.textContent.text = cpu.getProcessorName()
//核心数 //核心数
binding.text3.textTitle.text = getString(R.string.cores) binding.text3.textTitle.text = getString(R.string.cores)
binding.text3.textContent.text = cpu.cores.size.toString()
//cpu内核详情 //cpu内核详情
binding.text4.textTitle.text = getString(R.string.CPU) binding.text4.textTitle.text = getString(R.string.CPU)
binding.text4.textContent.text = cpu.getAllArchitectures().joinToString()
//制程工艺 //制程工艺
binding.text5.textTitle.text = getString(R.string.process) binding.text5.textTitle.text = getString(R.string.process)
binding.text5.textContent.text = cpu.getProcessInfo().process
//架构 //架构
binding.text6.textTitle.text = getString(R.string.architecture) binding.text6.textTitle.text = getString(R.string.architecture)
binding.text6.textContent.text = cpu.getArchitecture()
//ABI //ABI
binding.text7.textTitle.text = getString(R.string.ABI) binding.text7.textTitle.text = getString(R.string.ABI)
binding.text7.textContent.text = cpu.getAbi()
//supported_ABls //supported_ABls
binding.text8.textTitle.text = getString(R.string.supported_ABls) binding.text8.textTitle.text = getString(R.string.supported_ABls)
binding.text8.textContent.text = cpu.getSupportedAbis().joinToString()
//频率 //频率
binding.text9.textTitle.text = getString(R.string.frequencies) binding.text9.textTitle.text = getString(R.string.frequencies)
binding.text9.textContent.text = cpu.getFrequencyText() mainViewModel.cpuInfo.observe(viewLifecycleOwner) { cpu ->
cpu?.let {
binding.text0.textContent.text = cpu.getProcessorName()
binding.text1.textContent.text = cpu.getVendor()
binding.text2.textContent.text = cpu.getProcessorName()
binding.text3.textContent.text = cpu.cores.size.toString()
binding.text4.textContent.text = cpu.getAllArchitectures().joinToString()
binding.text5.textContent.text = cpu.getProcessInfo().process
binding.text6.textContent.text = cpu.getArchitecture()
binding.text7.textContent.text = cpu.getAbi()
binding.text8.textContent.text = cpu.getSupportedAbis().joinToString()
binding.text9.textContent.text = cpu.getFrequencyText()
}
}
} }
/** /**
* bluetooth相关信息*/ * bluetooth相关信息*/
@SuppressLint("SuspiciousIndentation") @SuppressLint("SuspiciousIndentation")
private fun setBlueToothInfo() { private fun setBlueToothInfo() {
val bluetoothInfo = AndInfo.instance.bluetooth//蓝牙信息 binding.bluetoothLayout.blueCheck1.content.text = getString(R.string.bluetooth_le)
if (!bluetoothInfo.isEnabled) return binding.bluetoothLayout.blueCheck2.content.text =
getString(R.string.multiple_advertisement)
binding.bluetoothLayout.blueCheck3.content.text =
getString(R.string.offloaded_filtering)
binding.bluetoothLayout.blueCheck4.content.text =
getString(R.string.offloaded_scan_batching)
binding.bluetoothLayout.blueCheck01.content.text =
getString(R.string.le_periodic_advertising)
binding.bluetoothLayout.blueCheck02.content.text =
getString(R.string.le_extended_advertising)
binding.bluetoothLayout.blueCheck03.content.text =
getString(R.string.le_2m_phy_high_speed)
binding.bluetoothLayout.blueCheck04.content.text =
getString(R.string.le_2m_phy_low_power)
binding.bluetoothLayout.blueCheck05.content.text = getString(R.string.le_audio_support)
binding.bluetoothLayout.showLayout.setOnClickListener { binding.bluetoothLayout.showLayout.setOnClickListener {
requestBluetoothPermissions() requestBluetoothPermissions()
} }
//蓝牙features支持
bluetoothInfo.getBluetoothFeatures().let { features ->
binding.bluetoothLayout.blueCheck1.content.text = getString(R.string.bluetooth_le)
binding.bluetoothLayout.blueCheck1.image.isSelected = features.bluetoothLe
binding.bluetoothLayout.blueCheck2.content.text =
getString(R.string.multiple_advertisement)
binding.bluetoothLayout.blueCheck2.image.isSelected = features.multipleAdvertisement
binding.bluetoothLayout.blueCheck3.content.text =
getString(R.string.offloaded_filtering)
binding.bluetoothLayout.blueCheck3.image.isSelected = features.offloadedFiltering
binding.bluetoothLayout.blueCheck4.content.text =
getString(R.string.offloaded_scan_batching)
binding.bluetoothLayout.blueCheck4.image.isSelected = features.offloadedScanBatching
binding.bluetoothLayout.blueCheck01.content.text =
getString(R.string.le_periodic_advertising)
binding.bluetoothLayout.blueCheck01.image.isSelected = features.lePeriodicAdvertising
binding.bluetoothLayout.blueCheck02.content.text =
getString(R.string.le_extended_advertising)
binding.bluetoothLayout.blueCheck02.image.isSelected = features.leExtendedAdvertising
binding.bluetoothLayout.blueCheck03.content.text =
getString(R.string.le_2m_phy_high_speed)
binding.bluetoothLayout.blueCheck03.image.isSelected = features.leCodedPhy
binding.bluetoothLayout.blueCheck04.content.text =
getString(R.string.le_2m_phy_low_power)
binding.bluetoothLayout.blueCheck04.image.isSelected = features.le2MbPhy
binding.bluetoothLayout.blueCheck05.content.text = getString(R.string.le_audio_support)
binding.bluetoothLayout.blueCheck05.image.isSelected = features.leAudioSupport
}
mainViewModel.bluetoothInfo.observe(viewLifecycleOwner) { features ->
features?.let {
if (!it.isEnabled) return@let
//蓝牙features支持
it.getBluetoothFeatures().let { features ->
binding.bluetoothLayout.blueCheck1.image.isSelected = features.bluetoothLe
binding.bluetoothLayout.blueCheck2.image.isSelected =
features.multipleAdvertisement
binding.bluetoothLayout.blueCheck3.image.isSelected =
features.offloadedFiltering
binding.bluetoothLayout.blueCheck4.image.isSelected =
features.offloadedScanBatching
binding.bluetoothLayout.blueCheck01.image.isSelected =
features.lePeriodicAdvertising
binding.bluetoothLayout.blueCheck02.image.isSelected =
features.leExtendedAdvertising
binding.bluetoothLayout.blueCheck03.image.isSelected = features.leCodedPhy
binding.bluetoothLayout.blueCheck04.image.isSelected = features.le2MbPhy
binding.bluetoothLayout.blueCheck05.image.isSelected = features.leAudioSupport
}
}
}
} }
// 请求蓝牙权限的方法 // 请求蓝牙权限的方法

View File

@ -9,15 +9,20 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.annotation.RequiresPermission import androidx.annotation.RequiresPermission
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.xyzshell.andinfo.AndInfo import com.xyzshell.andinfo.AndInfo
import com.xyzshell.andinfo.libs.NetworkInfo import com.xyzshell.andinfo.libs.NetworkInfo
import com.xyzshell.myphoneinfo.R import com.xyzshell.myphoneinfo.R
import com.xyzshell.myphoneinfo.base.BaseActivity
import com.xyzshell.myphoneinfo.custom.PermissionChecker import com.xyzshell.myphoneinfo.custom.PermissionChecker
import com.xyzshell.myphoneinfo.custom.RefreshManager.load
import com.xyzshell.myphoneinfo.custom.RefreshManager.stop
import com.xyzshell.myphoneinfo.custom.SetNumberOrWordUtils.getHoursString import com.xyzshell.myphoneinfo.custom.SetNumberOrWordUtils.getHoursString
import com.xyzshell.myphoneinfo.custom.SetNumberOrWordUtils.setYesOrNo import com.xyzshell.myphoneinfo.custom.SetNumberOrWordUtils.setYesOrNo
import com.xyzshell.myphoneinfo.databinding.FragmentNetworkBinding import com.xyzshell.myphoneinfo.databinding.FragmentNetworkBinding
import com.xyzshell.myphoneinfo.dialog.ShowLoadFragment import com.xyzshell.myphoneinfo.dialog.ShowLoadFragment
import com.xyzshell.myphoneinfo.main.MainViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -26,13 +31,20 @@ import kotlin.toString
class NetworkFragment : Fragment() { class NetworkFragment : Fragment() {
private var status=0//0:未连接1wifi 2:数据流量 private var status = 0//0:未连接1wifi 2:数据流量
private lateinit var networkInfo: NetworkInfo
private lateinit var binding:FragmentNetworkBinding private val mainViewModel: MainViewModel by activityViewModels()
private var refreshStatus =false//刷新状态
private lateinit var binding: FragmentNetworkBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
} }
override fun onResume(){
super.onResume()
checkPermissionsAndRefresh()
mainViewModel.refreshNetworkInfo(requireContext())
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
@ -40,210 +52,258 @@ private lateinit var binding:FragmentNetworkBinding
): View { ): View {
// Inflate the layout for this fragment // Inflate the layout for this fragment
binding = FragmentNetworkBinding.inflate(inflater, container, false) binding = FragmentNetworkBinding.inflate(inflater, container, false)
// binding.signalStrength.setStrength(4)
return binding.root return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
networkInfo= AndInfo.instance.network initView()
viewLifecycleOwner.lifecycleScope.launch { setObservers()
delay(50) // 短暂延迟确保UI完成布局
withContext(Dispatchers.Main) {
refreshStatus()
if(PermissionChecker.getMissingPermissions(context = requireContext()).isNotEmpty()){
PermissionChecker.requestSimple(
requireActivity(),
onGranted = {
// 权限已授予,执行操作
refreshStatus()
},
onDenied = {
// 权限被拒绝
Toast.makeText(context, getString(R.string.permissions_required), Toast.LENGTH_SHORT).show()
}
)
}
//wifi
binding.networkLayout.wfText1.textTitle.text=getString(R.string.status)
// binding.networkLayout.wfText2.textTitle.text=getString(R.string.network)
binding.networkLayout.wfText3.textTitle.text=getString(R.string.bssid)
binding.networkLayout.wfText4.textTitle.text=getString(R.string.function)
binding.networkLayout.wfText5.textTitle.text=getString(R.string.connection_speed)
binding.networkLayout.wfText6.textTitle.text=getString(R.string.signal_strength)
binding.networkLayout.wfText7.textTitle.text=getString(R.string.frequency)
binding.networkLayout.wfText8.textTitle.text=getString(R.string.channel)
binding.networkLayout.wfText10.textTitle.text=getString(R.string.standard)
//dhcp
binding.networkLayout.dhText1.textTitle.text=getString(R.string.dhcp_server)
binding.networkLayout.dhText2.textTitle.text=getString(R.string.dhcp_lease_time)
binding.networkLayout.dhText3.textTitle.text=getString(R.string.gateway)
binding.networkLayout.dhText4.textTitle.text=getString(R.string.subnet_mask)
binding.networkLayout.dhText5.textTitle.text=getString(R.string.dns1)
binding.networkLayout.dhText6.textTitle.text=getString(R.string.dns2)
binding.networkLayout.dhText7.textTitle.text=getString(R.string.ip_address)
binding.networkLayout.dhText8.textTitle.text=getString(R.string.ipv6)
//Hardware
binding.networkLayout.hardCheck1.content.text=getString(R.string.standard_all)
binding.networkLayout.hardCheck2.content.text=getString(R.string.wifi_direct_support)
binding.networkLayout.hardCheck3.content.text=getString(R.string.wifi_aware_support)
binding.networkLayout.hardCheck4.content.text=getString(R.string.wifi_passpoint_support)
binding.networkLayout.hardCheck5.content.text=getString(R.string.ghz_band_support)
binding.networkLayout.hardCheck6.content.text=getString(R.string.ghz6_band_support)
//mobel
binding.mdText1.textTitle.text=getString(R.string.dual_sim_dual_standby)
binding.mdText2.textTitle.text=getString(R.string.phone_type)
binding.mdText3.textTitle.text=getString(R.string.esim)
binding.conTexts.textTitle.text=getString(R.string.status)
binding.defText1.textTitle.text=getString(R.string.data)
binding.defText2.textTitle.text=getString(R.string.voice)
binding.defText3.textTitle.text=getString(R.string.short_message)
binding.networkLayout.pubShow.setOnClickListener{
val tag = "showLoadFragment"
if (requireActivity().supportFragmentManager.findFragmentByTag(tag) == null) {
val showLoadFragment = ShowLoadFragment()
showLoadFragment.show(requireActivity().supportFragmentManager, tag)
}
}
}}
} }
private fun refreshStatus() { private fun setObservers() {
val currentNetworkType = networkInfo.getCurrentNetworkType() mainViewModel.networkStatus.observe(viewLifecycleOwner) { type ->
Log.d("NetworkFragment", "currentNetworkType: $currentNetworkType") stop(binding.swipeRefresh)
when (currentNetworkType) { when (type) {
NetworkInfo.NetworkType.WIFI -> { NetworkInfo.NetworkType.WIFI -> {
status = 1 status = 1
setWifiDetail() binding.conText1.text = getString(R.string.wifi)
setMobileInfo() binding.connectData.visibility = View.VISIBLE
binding.noConnect.visibility = View.GONE
}
NetworkInfo.NetworkType.MOBILE -> {
status = 2
binding.conText1.text = getString(R.string.mobile)
binding.connectData.visibility = View.VISIBLE
binding.noConnect.visibility = View.GONE
}
else -> {
status = 0
setNoConnect()
}
} }
NetworkInfo.NetworkType.MOBILE -> { }
status = 2 // 观察 WiFi 详情
setWifiDetail() mainViewModel.wifiDetails.observe(viewLifecycleOwner) { wifiDetails ->
setMobileInfo() wifiDetails?.let {
setWifiDetail(it)
} ?: run {
binding.networkLayout.wifiList.visibility = View.GONE
} }
else -> { }
status = 0
setNoConnect() // 观察移动网络详情
mainViewModel.mobileDetails.observe(viewLifecycleOwner) { mobileDetails ->
mobileDetails?.let {
setMobileInfo(it)
}
}
// 观察权限状态
mainViewModel.networkPermissionGranted.observe(viewLifecycleOwner) { granted ->
if (!granted) {
requestPermissions()
} }
} }
} }
private fun checkPermissionsAndRefresh() {
mainViewModel.checkNetworkPermissions(requireContext())
}
private fun requestPermissions() {
PermissionChecker.requestSimple(
requireActivity(),
onGranted = {
mainViewModel.refreshNetworkInfo(requireContext())
},
onDenied = {
Toast.makeText(
context,
getString(R.string.permissions_required),
Toast.LENGTH_SHORT
).show()
setNoConnect()
}
)
}
private fun initView() {
binding.networkLayout.wfText1.textTitle.text = getString(R.string.status)
// binding.networkLayout.wfText2.textTitle.text=getString(R.string.network)
binding.networkLayout.wfText3.textTitle.text = getString(R.string.bssid)
binding.networkLayout.wfText4.textTitle.text = getString(R.string.function)
binding.networkLayout.wfText5.textTitle.text = getString(R.string.connection_speed)
binding.networkLayout.wfText6.textTitle.text = getString(R.string.signal_strength)
binding.networkLayout.wfText7.textTitle.text = getString(R.string.frequency)
binding.networkLayout.wfText8.textTitle.text = getString(R.string.channel)
binding.networkLayout.wfText10.textTitle.text = getString(R.string.standard)
//dhcp
binding.networkLayout.dhText1.textTitle.text = getString(R.string.dhcp_server)
binding.networkLayout.dhText2.textTitle.text = getString(R.string.dhcp_lease_time)
binding.networkLayout.dhText3.textTitle.text = getString(R.string.gateway)
binding.networkLayout.dhText4.textTitle.text = getString(R.string.subnet_mask)
binding.networkLayout.dhText5.textTitle.text = getString(R.string.dns1)
binding.networkLayout.dhText6.textTitle.text = getString(R.string.dns2)
binding.networkLayout.dhText7.textTitle.text = getString(R.string.ip_address)
binding.networkLayout.dhText8.textTitle.text = getString(R.string.ipv6)
//Hardware
binding.networkLayout.hardCheck1.content.text = getString(R.string.standard_all)
binding.networkLayout.hardCheck2.content.text = getString(R.string.wifi_direct_support)
binding.networkLayout.hardCheck3.content.text = getString(R.string.wifi_aware_support)
binding.networkLayout.hardCheck4.content.text = getString(R.string.wifi_passpoint_support)
binding.networkLayout.hardCheck5.content.text = getString(R.string.ghz_band_support)
binding.networkLayout.hardCheck6.content.text = getString(R.string.ghz6_band_support)
//mobel
binding.mdText1.textTitle.text = getString(R.string.dual_sim_dual_standby)
binding.mdText2.textTitle.text = getString(R.string.phone_type)
binding.mdText3.textTitle.text = getString(R.string.esim)
binding.conTexts.textTitle.text = getString(R.string.status)
binding.defText1.textTitle.text = getString(R.string.data)
binding.defText2.textTitle.text = getString(R.string.voice)
binding.defText3.textTitle.text = getString(R.string.short_message)
binding.networkLayout.pubShow.setOnClickListener {
val tag = "showLoadFragment"
if (requireActivity().supportFragmentManager.findFragmentByTag(tag) == null) {
val showLoadFragment = ShowLoadFragment()
showLoadFragment.show(requireActivity().supportFragmentManager, tag)
}
}
binding.swipeRefresh.setOnRefreshListener {
load(binding.swipeRefresh){
Log.d("TTTTTTTT","刷新超时")
}
mainViewModel.refreshNetworkInfo(requireContext())
}
}
private fun setNoConnect() { private fun setNoConnect() {
binding.noConnect.visibility=View.VISIBLE binding.noConnect.visibility = View.VISIBLE
binding.connectData.visibility=View.GONE binding.connectData.visibility = View.GONE
binding.networkLayout.wfText1.textContent.text="Disconnected" binding.networkLayout.wfText1.textContent.text = getString(R.string.not_connected)
binding.networkLayout.wifiList.visibility=View.GONE binding.networkLayout.wifiList.visibility = View.GONE
binding.networkLayout.llPubShow.visibility=View.GONE binding.networkLayout.llPubShow.visibility = View.GONE
} }
/** /**
* 设置流量信息 * 设置流量信息
* */ * */
private fun setMobileInfo() { private fun setMobileInfo(mobileDetails: NetworkInfo.MobileDetails) {
binding.noConnect.visibility=View.GONE binding.noConnect.visibility = View.GONE
binding.conText1.text=getString(R.string.mobile) binding.conText1.text = getString(R.string.mobile)
binding.connectData.visibility=View.VISIBLE binding.connectData.visibility = View.VISIBLE
Log.d("TTTTTTTT", "wifi not connected") Log.d("TTTTTTTT", "wifi not connected")
binding.networkLayout.wifiList.visibility=View.GONE binding.networkLayout.wifiList.visibility = View.GONE
binding.networkLayout.llPubShow.visibility=View.GONE binding.networkLayout.llPubShow.visibility = View.GONE
networkInfo.getMobileDetails()?.let { mobileDetails -> binding.mdText1.textContent.text = setYesOrNo(mobileDetails.dualSim)
binding.mdText1.textContent.text=setYesOrNo(mobileDetails.dualSim) binding.mdText2.textContent.text = "SIM${mobileDetails.phoneType}"
binding.mdText2.textContent.text="SIM${mobileDetails.phoneType}" binding.mdText3.textContent.text = setYesOrNo(mobileDetails.esimSupported)
binding.mdText3.textContent.text=setYesOrNo(mobileDetails.esimSupported) binding.conTexts.textContent.text =
binding.conTexts.textContent.text=if(mobileDetails.dataEnabled) getString(R.string.connected) else getString(R.string.not_connected) if (mobileDetails.dataEnabled) getString(R.string.connected) else getString(R.string.not_connected)
binding.defText1.textContent.text=mobileDetails.dataSim.toString() binding.defText1.textContent.text = mobileDetails.dataSim.toString()
binding.defText2.textContent.text=mobileDetails.voiceSim.toString() binding.defText2.textContent.text = mobileDetails.voiceSim.toString()
binding.defText3.textContent.text=mobileDetails.smsSim.toString() binding.defText3.textContent.text = mobileDetails.smsSim.toString()
if(mobileDetails.simInfos.isNotEmpty()){ if (mobileDetails.simInfos.isNotEmpty()) {
binding.simInfo1.root.visibility=View.VISIBLE binding.simInfo1.root.visibility = View.VISIBLE
binding.simInfo1.simText1.textTitle.text=getString(R.string.status) binding.simInfo1.simText1.textTitle.text = getString(R.string.status)
binding.simInfo1.simText2.textTitle.text=getString(R.string.nation) binding.simInfo1.simText2.textTitle.text = getString(R.string.nation)
binding.simInfo1.simText3.textTitle.text=getString(R.string.roaming) binding.simInfo1.simText3.textTitle.text = getString(R.string.roaming)
binding.simInfo1.simText4.textTitle.text=getString(R.string.network_type) binding.simInfo1.simText4.textTitle.text = getString(R.string.network_type)
binding.simInfo1.simText1.textContent.text=if(mobileDetails.dataEnabled) getString(R.string.ready) else getString(R.string.not_ready) binding.simInfo1.simText1.textContent.text =
binding.simInfo1.simText2.textContent.text=mobileDetails.simInfos[0].countryIso.toString() if (mobileDetails.dataEnabled) getString(R.string.ready) else getString(R.string.not_ready)
binding.simInfo1.simText3.textContent.text=if(mobileDetails.simInfos[0].roaming) getString(R.string.enable) else getString(R.string.disable) binding.simInfo1.simText2.textContent.text =
binding.simInfo1.simText4.textContent.text=mobileDetails.simInfos[0].networkTypeText mobileDetails.simInfos[0].countryIso.toString()
if(mobileDetails.simInfos.size>1){ binding.simInfo1.simText3.textContent.text =
binding.simInfo2.root.visibility=View.VISIBLE if (mobileDetails.simInfos[0].roaming) getString(R.string.enable) else getString(R.string.disable)
binding.simInfo2.simTitle.text=getString(R.string.sim_card_2) binding.simInfo1.simText4.textContent.text = mobileDetails.simInfos[0].networkTypeText
binding.simInfo2.simText1.textTitle.text=getString(R.string.status) if (mobileDetails.simInfos.size > 1) {
binding.simInfo2.simText2.textTitle.text=getString(R.string.nation) binding.simInfo2.root.visibility = View.VISIBLE
binding.simInfo2.simText3.textTitle.text=getString(R.string.roaming) binding.simInfo2.simTitle.text = getString(R.string.sim_card_2)
binding.simInfo2.simText4.textTitle.text=getString(R.string.network_type) binding.simInfo2.simText1.textTitle.text = getString(R.string.status)
binding.simInfo2.simText1.textContent.text=if(mobileDetails.dataEnabled) getString(R.string.ready) else getString(R.string.not_ready) binding.simInfo2.simText2.textTitle.text = getString(R.string.nation)
binding.simInfo2.simText2.textContent.text=mobileDetails.simInfos[1].countryIso.toString() binding.simInfo2.simText3.textTitle.text = getString(R.string.roaming)
binding.simInfo2.simText3.textContent.text=if(mobileDetails.simInfos[1].roaming) getString(R.string.enable) else getString(R.string.disable) binding.simInfo2.simText4.textTitle.text = getString(R.string.network_type)
binding.simInfo2.simText4.textContent.text=mobileDetails.simInfos[1].networkTypeText binding.simInfo2.simText1.textContent.text =
} if (mobileDetails.dataEnabled) getString(R.string.ready) else getString(R.string.not_ready)
}else{ binding.simInfo2.simText2.textContent.text =
binding.simInfo1.root.visibility=View.GONE mobileDetails.simInfos[1].countryIso.toString()
binding.simInfo2.root.visibility=View.GONE binding.simInfo2.simText3.textContent.text =
if (mobileDetails.simInfos[1].roaming) getString(R.string.enable) else getString(
R.string.disable
)
binding.simInfo2.simText4.textContent.text =
mobileDetails.simInfos[1].networkTypeText
} }
} else {
binding.simInfo1.root.visibility = View.GONE
binding.simInfo2.root.visibility = View.GONE
} }
} }
/** /**
* 设置wifi信息 * 设置wifi信息
* */ * */
private fun setWifiDetail() { private fun setWifiDetail(wifiDetails: NetworkInfo.WifiDetails) {
binding.noConnect.visibility=View.GONE binding.noConnect.visibility = View.GONE
binding.conText1.text=getString(R.string.wifi) binding.conText1.text = getString(R.string.wifi)
binding.connectData.visibility=View.VISIBLE binding.connectData.visibility = View.VISIBLE
binding.networkLayout.wifiList.visibility=View.VISIBLE binding.networkLayout.wifiList.visibility = View.VISIBLE
binding.networkLayout.llPubShow.visibility=View.VISIBLE binding.networkLayout.llPubShow.visibility = View.VISIBLE
networkInfo.getWifiDetails()?.let { wifiDetails -> Log.d("TTTTTTTT", wifiDetails.connected.toString())
Log.d("TTTTTTTT", wifiDetails.connected.toString()) binding.networkLayout.wifiList.visibility = View.VISIBLE
binding.networkLayout.wifiList.visibility=View.VISIBLE binding.networkLayout.wfText1.textContent.text = getString(R.string.connected)
binding.networkLayout.wfText1.textContent.text=getString(R.string.connected) wifiDetails.signalLevelPercent?.let { percent ->
wifiDetails.signalLevelPercent?.let { percent-> binding.strengthView.setStrength(calculateSignalLevel(percent = percent))
binding.strengthView.setStrength(calculateSignalLevel(percent = percent))
}
binding.conText2.text="${wifiDetails.ssid}·${wifiDetails.linkSpeedMbps} Mbps"
binding.conTExt3.text="${wifiDetails.signalLevelPercent}% ${wifiDetails.rssi} dBm"
binding.networkLayout.wfText3.textContent.text=wifiDetails.bssid
binding.networkLayout.wfText4.textContent.text=wifiDetails.capabilities
binding.networkLayout.wfText5.textContent.text="${wifiDetails.linkSpeedMbps} Mbps"
binding.networkLayout.wfText6.textContent.text="${wifiDetails.rssi} dBm"
binding.networkLayout.wfText7.textContent.text="${wifiDetails.frequency} MHz"
binding.networkLayout.wfText8.textContent.text=wifiDetails.channel.toString()
binding.networkLayout.wfText10.textContent.text=wifiDetails.standard.toString()
binding.networkLayout.dhText1.textContent.text=wifiDetails.dhcpServer
binding.networkLayout.dhText2.textContent.text=getHoursString(wifiDetails.leaseDuration?: 0)
binding.networkLayout.dhText3.textContent.text=wifiDetails.gateway
binding.networkLayout.dhText4.textContent.text=wifiDetails.subnetMask
binding.networkLayout.dhText5.textContent.text=wifiDetails.dns1
binding.networkLayout.dhText6.textContent.text=wifiDetails.dns2
binding.networkLayout.dhText7.textContent.text=wifiDetails.ip
val toString = wifiDetails.ipv6.toString()
val ipv6 = toString.replace("[", "").replace("]", "")
binding.networkLayout.dhText8.textContent.text=ipv6
binding.networkLayout.hardCheck1.image.isSelected= wifiDetails.supportedStandards?.size==5
binding.networkLayout.hardCheck2.image.isSelected= wifiDetails.wifiDirect == true
binding.networkLayout.hardCheck3.image.isSelected= wifiDetails.wifiAware == true
binding.networkLayout.hardCheck4.image.isSelected= wifiDetails.wifiPasspoint == true
binding.networkLayout.hardCheck5.image.isSelected= wifiDetails.support5G == true
binding.networkLayout.hardCheck6.image.isSelected= wifiDetails.support6G == true
} }
binding.conText2.text = "${wifiDetails.ssid}·${wifiDetails.linkSpeedMbps} Mbps"
binding.conTExt3.text = "${wifiDetails.signalLevelPercent}% ${wifiDetails.rssi} dBm"
binding.networkLayout.wfText3.textContent.text = wifiDetails.bssid
binding.networkLayout.wfText4.textContent.text = wifiDetails.capabilities
binding.networkLayout.wfText5.textContent.text = "${wifiDetails.linkSpeedMbps} Mbps"
binding.networkLayout.wfText6.textContent.text = "${wifiDetails.rssi} dBm"
binding.networkLayout.wfText7.textContent.text = "${wifiDetails.frequency} MHz"
binding.networkLayout.wfText8.textContent.text = wifiDetails.channel.toString()
binding.networkLayout.wfText10.textContent.text = wifiDetails.standard.toString()
binding.networkLayout.dhText1.textContent.text = wifiDetails.dhcpServer
binding.networkLayout.dhText2.textContent.text =
getHoursString(wifiDetails.leaseDuration ?: 0)
binding.networkLayout.dhText3.textContent.text = wifiDetails.gateway
binding.networkLayout.dhText4.textContent.text = wifiDetails.subnetMask
binding.networkLayout.dhText5.textContent.text = wifiDetails.dns1
binding.networkLayout.dhText6.textContent.text = wifiDetails.dns2
binding.networkLayout.dhText7.textContent.text = wifiDetails.ip
val toString = wifiDetails.ipv6.toString()
val ipv6 = toString.replace("[", "").replace("]", "")
binding.networkLayout.dhText8.textContent.text = ipv6
binding.networkLayout.hardCheck1.image.isSelected =
wifiDetails.supportedStandards?.size == 5
binding.networkLayout.hardCheck2.image.isSelected = wifiDetails.wifiDirect == true
binding.networkLayout.hardCheck3.image.isSelected = wifiDetails.wifiAware == true
binding.networkLayout.hardCheck4.image.isSelected = wifiDetails.wifiPasspoint == true
binding.networkLayout.hardCheck5.image.isSelected = wifiDetails.support5G == true
binding.networkLayout.hardCheck6.image.isSelected = wifiDetails.support6G == true
} }
private fun calculateSignalLevel( percent: Int) : Int { private fun calculateSignalLevel(percent: Int): Int {
return when { return when {
percent >= 80 -> 5 // 优秀 percent >= 80 -> 5 // 优秀
percent >= 60 -> 4 // 良好 percent >= 60 -> 4 // 良好
percent >= 40 -> 3 // 一般 percent >= 40 -> 3 // 一般
@ -251,6 +311,7 @@ private lateinit var binding:FragmentNetworkBinding
else -> 1 // 非常差/无信号 else -> 1 // 非常差/无信号
} }
} }
companion object { companion object {
@JvmStatic @JvmStatic

View File

@ -2,6 +2,7 @@ package com.xyzshell.myphoneinfo.main
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.fragment.app.activityViewModels
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import com.xyzshell.andinfo.AndInfo import com.xyzshell.andinfo.AndInfo
@ -18,6 +19,7 @@ import com.xyzshell.myphoneinfo.dashboard.SensorsFragment
import com.xyzshell.myphoneinfo.dashboard.SystemShowFragment import com.xyzshell.myphoneinfo.dashboard.SystemShowFragment
import com.xyzshell.myphoneinfo.databinding.ActivityMainBinding import com.xyzshell.myphoneinfo.databinding.ActivityMainBinding
import com.xyzshell.myphoneinfo.dialog.SlideInPopView import com.xyzshell.myphoneinfo.dialog.SlideInPopView
import kotlin.getValue
class MainScrollActivity : BaseActivity<ActivityMainBinding>() { class MainScrollActivity : BaseActivity<ActivityMainBinding>() {
override fun inflateBinding(inflater: LayoutInflater): ActivityMainBinding = override fun inflateBinding(inflater: LayoutInflater): ActivityMainBinding =
@ -48,12 +50,6 @@ class MainScrollActivity : BaseActivity<ActivityMainBinding>() {
}) })
}) { tab, position -> }) { tab, position ->
tab.text = stringArray[position] tab.text = stringArray[position]
// tab.setCustomView(R.layout.custom_ta
//
// tab.customView?.run {
// val indicator: ImageView = findViewById(R.id.image_indicator)
// indicator.isVisible = position == 0
// }
}.attach() }.attach()
} }

View File

@ -0,0 +1,182 @@
package com.xyzshell.myphoneinfo.main
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.xyzshell.andinfo.AndInfo
import com.xyzshell.andinfo.libs.AppDetails
import com.xyzshell.andinfo.libs.BluetoothInfo
import com.xyzshell.andinfo.libs.CpuInfo
import com.xyzshell.andinfo.libs.DisplayInfo
import com.xyzshell.andinfo.libs.GpuInfo
import com.xyzshell.andinfo.libs.InputInfo
import com.xyzshell.andinfo.libs.MemInfo
import com.xyzshell.andinfo.libs.MemoryInfo
import com.xyzshell.andinfo.libs.NetworkInfo
import com.xyzshell.andinfo.libs.StorageInfo
import com.xyzshell.myphoneinfo.custom.PermissionChecker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MainViewModel: ViewModel() {
// 硬件相关数据
private val _cpuInfo = MutableLiveData<CpuInfo?>()
val cpuInfo: LiveData<CpuInfo?> = _cpuInfo
private val _gpuInfo = MutableLiveData<GpuInfo?>()
val gpuInfo: LiveData<GpuInfo?> = _gpuInfo
private val _displayInfo = MutableLiveData<DisplayInfo?>()
val displayInfo: LiveData<DisplayInfo?> = _displayInfo
private val _storageInfo = MutableLiveData<StorageInfo?>()
val storageInfo: LiveData<StorageInfo?> = _storageInfo
private val _memInfo = MutableLiveData<MemInfo?>()
val memInfo: LiveData<MemInfo?> = _memInfo
private val _bluetoothInfo = MutableLiveData<BluetoothInfo?>()
val bluetoothInfo: LiveData<BluetoothInfo?> = _bluetoothInfo
private val _inputInfo= MutableLiveData<InputInfo?>()
val input: LiveData<InputInfo?> = _inputInfo
//网络相关
private val networkInfo= AndInfo.instance.network
private val _networkStatus= MutableLiveData<NetworkInfo.NetworkType?>()
val networkStatus: LiveData<NetworkInfo.NetworkType?> = _networkStatus
private val _wifiDetails = MutableLiveData<NetworkInfo.WifiDetails?>()
val wifiDetails: LiveData<NetworkInfo.WifiDetails?> = _wifiDetails
private val _mobileDetails = MutableLiveData<NetworkInfo.MobileDetails?>()
val mobileDetails: LiveData<NetworkInfo.MobileDetails?> = _mobileDetails
private val _networkPermissionGranted = MutableLiveData<Boolean>()
val networkPermissionGranted: LiveData<Boolean> = _networkPermissionGranted
//应用列表
private val _installedApps= MutableLiveData<List<AppDetails>>()
val installedApps: LiveData<List<AppDetails>> = _installedApps
init{
loadApps()
}
fun refreshApps() {
loadApps()
}
/**
* 刷新网络信息带权限检查
*/
fun refreshNetworkInfo(context: Context){
viewModelScope.launch(Dispatchers.IO){
// 检查权限
val hasPermissions = PermissionChecker.getMissingPermissions(context).isEmpty()
_networkPermissionGranted.postValue(hasPermissions)
if (!hasPermissions) {
return@launch
}
try {
// 获取网络类型
val type = networkInfo.getCurrentNetworkType()
_networkStatus.postValue(type)
// 根据网络类型获取详细信息
when (type) {
NetworkInfo.NetworkType.WIFI -> {
_wifiDetails.postValue(networkInfo.getWifiDetails())
_mobileDetails.postValue(networkInfo.getMobileDetails())
}
NetworkInfo.NetworkType.MOBILE -> {
_mobileDetails.postValue(networkInfo.getMobileDetails())
_wifiDetails.postValue(networkInfo.getWifiDetails())
}
else -> {
_wifiDetails.postValue(null)
_mobileDetails.postValue(null)
}
}
} catch (e: Exception) {
e.printStackTrace()
// 处理异常
}
}
}
/**
* 仅检查网络权限
*/
fun checkNetworkPermissions(context: Context) {
viewModelScope.launch(Dispatchers.IO) {
val hasPermissions = PermissionChecker.getMissingPermissions(context).isEmpty()
_networkPermissionGranted.postValue(hasPermissions)
}
}
/**
* 获取当前网络状态快速检查
*/
fun getCurrentNetworkType(context: Context): NetworkInfo.NetworkType? {
return try {
if (PermissionChecker.getMissingPermissions(context).isEmpty()) {
networkInfo.getCurrentNetworkType()
} else {
null
}
} catch (e: Exception) {
null
}
}
fun loadApps() {
viewModelScope.launch(Dispatchers.IO){
val apps= AndInfo.instance.app.getInstalledApps()
_installedApps.postValue(apps)
}
}
/**
* 硬件刷新方法
* */
fun refreshHardwareInfo(context: Context){
viewModelScope.launch(Dispatchers.IO){
try {
val cpu= AndInfo.instance.cpu
_cpuInfo.postValue(cpu)
val gpu= AndInfo.instance.gpu
_gpuInfo.postValue(gpu)
val display = AndInfo.instance.display
_displayInfo.postValue(display)
val storage = AndInfo.instance.storage
_storageInfo.postValue(storage)
val mem = AndInfo.instance.memory
_memInfo.postValue(mem)
// 检查权限
val bluetoothInfo = AndInfo.instance.bluetooth
val inputInfo= AndInfo.instance.input
_inputInfo.postValue(inputInfo)
val hasPermissions= bluetoothInfo.requiredPermissions.all { permission ->
ActivityCompat.checkSelfPermission(context, permission) ==
PackageManager.PERMISSION_GRANTED
}
if (!hasPermissions) {
return@launch
}
_bluetoothInfo.postValue(bluetoothInfo)
//蓝牙信息
}catch (e: Exception){
e.printStackTrace()
}
}
}
}

View File

@ -40,11 +40,12 @@
android:id="@+id/dialog_title" android:id="@+id/dialog_title"
style="@style/TextHeavy20" style="@style/TextHeavy20"
android:textSize="18sp" android:textSize="18sp"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="80dp" android:layout_marginStart="80dp"
android:text="@string/screenreader" android:text="@string/screenreader"
app:layout_constraintLeft_toRightOf="@id/image_icon" app:layout_constraintLeft_toRightOf="@id/image_icon"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/image_icon" app:layout_constraintTop_toTopOf="@id/image_icon"
app:layout_constraintBottom_toBottomOf="@id/image_icon"/> app:layout_constraintBottom_toBottomOf="@id/image_icon"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/swipeRefresh"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"

View File

@ -2,6 +2,7 @@
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:id="@+id/swipeRefresh"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/background_color" android:background="@color/background_color"
tools:context=".dashboard.NetworkFragment"> tools:context=".dashboard.NetworkFragment">

View File

@ -51,7 +51,11 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
layout="@layout/mem_text_style" /> layout="@layout/mem_text_style" />
<include
android:id="@+id/memText0"
android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="@layout/mem_text_style" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"