Commit 3690bb45 authored by wanglei's avatar wanglei

...

parent 142354bf
/build
\ No newline at end of file
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "com.koko.batteryinfo"
compileSdk = 34
defaultConfig {
minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
viewBinding = true
buildConfig = true
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
implementation(project(":Router"))
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
package com.koko.batteryinfo
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.koko.batteryinfo.test", appContext.packageName)
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<activity
android:name=".BatteryInfoActivity"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi" />
</application>
</manifest>
\ No newline at end of file
package com.koko.batteryinfo
import android.content.Intent
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.ActivityResultCaller
import androidx.activity.result.contract.ActivityResultContracts
class ActivityLauncher(activityResultCaller: ActivityResultCaller) {
//region 权限
private var permissionCallback: ActivityResultCallback<Map<String, Boolean>>? = null
private val permissionLauncher =
activityResultCaller.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result: Map<String, Boolean> ->
permissionCallback?.onActivityResult(result)
}
fun launch(
permissionArray: Array<String>,
permissionCallback: ActivityResultCallback<Map<String, Boolean>>?
) {
this.permissionCallback = permissionCallback
permissionLauncher.launch(permissionArray)
}
//endregion
//region intent跳转
private var activityResultCallback: ActivityResultCallback<ActivityResult>? = null
private val intentLauncher =
activityResultCaller.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult: ActivityResult ->
activityResultCallback?.onActivityResult(activityResult)
}
/**
* it.resultCode == Activity.RESULT_OK
*/
fun launch(
intent: Intent,
activityResultCallback: ActivityResultCallback<ActivityResult>? = null
) {
this.activityResultCallback = activityResultCallback
intentLauncher.launch(intent)
}
//endregion
//region saf
// private var safResultCallback: ActivityResultCallback<Uri?>? = null
// private val safLauncher =
// activityResultCaller.registerForActivityResult(
// ActivityResultContracts.OpenDocument(),
// ) { uri ->
// safResultCallback?.onActivityResult(uri)
// }
//
// fun launch(array: Array<String>, safResultCallback: ActivityResultCallback<Uri?>?) {
// this.safResultCallback = safResultCallback
// safLauncher.launch(array)
// }
//end region
}
\ No newline at end of file
package com.koko.batteryinfo
import android.app.Dialog
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding
abstract class BaseActivity<T : ViewBinding> : AppCompatActivity() {
private val TAG = "BaseActivity"
protected abstract val binding: T
lateinit var launcher: ActivityLauncher
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
launcher = ActivityLauncher(this)
setContentView(binding.root)
// EventUtils.event("page_${javaClass.simpleName}")
initView()
initListener()
}
protected abstract fun initView()
protected open fun initListener() {}
var dialog: Dialog? = null
override fun onDestroy() {
super.onDestroy()
}
override fun onResume() {
super.onResume()
}
}
\ No newline at end of file
package com.koko.batteryinfo
import android.animation.ValueAnimator
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Color
import android.os.Bundle
import android.view.animation.LinearInterpolator
import androidx.activity.SystemBarStyle
import androidx.activity.addCallback
import androidx.activity.enableEdgeToEdge
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.koko.batteryinfo.databinding.ActivityBatteryInfoBinding
import java.math.BigDecimal
import java.math.RoundingMode
class BatteryInfoActivity : BaseActivity<ActivityBatteryInfoBinding>() {
private var batteryReceiver: BatteryReceiver? = null
override val binding: ActivityBatteryInfoBinding by lazy {
ActivityBatteryInfoBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
useDefaultImmersive()
initView()
// AdmobNativeUtils.showNativeAd(this, binding.flAdNative, R.layout.layout_admob_document_in)
// if (AdmobHelper.isShowOpenDocumentInter()) {
// AdmobInterstitialUtils.showInterstitialAd(this) {
// startProgressAnimation(binding.tvBattery.text.toString().toInt())
// }
// } else {
// startProgressAnimation(binding.tvBattery.text.toString().toInt())
// }
}
override fun initView() {
registerReceiver()
onBackPressedDispatcher.addCallback {
// if (AdmobHelper.isBackShowAd()) {
// AdmobInterstitialUtils.showInterstitialAd(this@com.koko.batteryinfo.BatteryInfoActivity) {
// finishToMain()
// }
// }
}
binding.flBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() }
}
private fun useDefaultImmersive() {
enableEdgeToEdge(SystemBarStyle.light(Color.TRANSPARENT, Color.TRANSPARENT))
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
}
override fun onDestroy() {
super.onDestroy()
batteryReceiver?.let { unregisterReceiver(batteryReceiver) }
}
private fun registerReceiver() {
val intentFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
batteryReceiver = BatteryReceiver { setBatteryInfo() }
registerReceiver(batteryReceiver, intentFilter)
}
fun formatTime(totalMinutes: Int): String {
val hours = totalMinutes / 60
val minutes = totalMinutes % 60
return "${hours}h${minutes}m"
}
private fun setBatteryInfo() {
val percent = BatteryReceiver.level / BatteryReceiver.scale.toFloat()
val remainingCapacity = BatteryReceiver.mAh * percent
val wTime = (remainingCapacity / 6.0f).toInt()
val battery = (percent * 100).toInt()
val temperature = calculate(BatteryReceiver.temperature, 10)
val voltage = calculate(BatteryReceiver.voltage, 1000)
binding.tvBattery.text = "$battery"
binding.tvEstimated.text = formatTime(wTime)
binding.tvTemperature.text = "$temperature℃"
binding.tvVoltage.text = "${voltage}V"
binding.tvPower.text = "${(BatteryReceiver.mAh * percent).toInt()}mAh"
binding.tvBatteryType.text = "${BatteryReceiver.technology}"
binding.tvBatteryCapacity.text = "${BatteryReceiver.mAh.toInt()}mAh"
binding.tvStatus.text = if (BatteryReceiver.isCharging) "Charging" else "Normal"
}
private fun startProgressAnimation(endProgress: Int) {
ValueAnimator.ofInt(0, endProgress).apply {
duration = 700
interpolator = LinearInterpolator()
addUpdateListener { animation ->
val progress = animation.animatedValue as Int
binding.pbBattery.setProgress(progress / 100f)
}
}.start()
}
private fun calculate(dividend: Int, divisor: Int): String {
val bigDecimalDividend = BigDecimal(dividend)
val bigDecimalDivisor = BigDecimal(divisor)
// 执行除法操作,并保留一位小数,不四舍五入
val result = bigDecimalDividend.divide(bigDecimalDivisor, 1, RoundingMode.DOWN)
// 将结果转换回Double
return result.toString()
}
}
\ No newline at end of file
package com.koko.batteryinfo
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build
/**
* 作者:马海钊
* 时间:2025/2/11 11:59
* 功能:
*/
class BatteryReceiver(
private val block: (() -> Unit?)? = null
) : BroadcastReceiver() {
companion object {
var level = 0
var scale = 0
var voltage = 0
var temperature = 0
var technology = ""
var health = 0
var status = 0
var mAh = 0f
var isCharging = false
@JvmStatic
fun register(context: Context) {
val intentFilter = IntentFilter().apply {
addAction(Intent.ACTION_BATTERY_CHANGED)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(BatteryReceiver(), intentFilter, Context.RECEIVER_EXPORTED)
} else {
context.registerReceiver(BatteryReceiver(), intentFilter)
}
}
}
override fun onReceive(context: Context, intent: Intent) {
if (Intent.ACTION_BATTERY_CHANGED != intent.action) return
val batteryManager = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0)
voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0)
temperature = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0)
technology = intent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY).toString()
health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH, 0)
status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN)
isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL
val chargeCounter = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER)
val propertyCapacity = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
if (chargeCounter != Int.MIN_VALUE && propertyCapacity != Int.MIN_VALUE) {
mAh = (chargeCounter / (propertyCapacity.toFloat() / 100f)) / 1000f
}
block?.invoke()
}
}
\ No newline at end of file
package com.base.pdfreaderpro.ui.battery
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.LinearGradient
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
import android.graphics.Shader
import android.util.AttributeSet
import android.view.View
class CircleProgressBar @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val arcPaint: Paint
private val bgPaint: Paint
private val path: Path
private val bounds: RectF
private var progress = 1f // 进度
private var colorArray = intArrayOf()
init {
arcPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.STROKE
strokeWidth = dpToPx(6f)
strokeCap = Paint.Cap.ROUND
}
bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.TRANSPARENT
style = Paint.Style.STROKE
strokeWidth = dpToPx(6f)
strokeCap = Paint.Cap.ROUND
}
path = Path().apply {
}
bounds = RectF()
colorArray = intArrayOf(
Color.parseColor("#00a8ce"),
Color.parseColor("#00a7fa"),
Color.parseColor("#8afbff"),
)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 考虑Paint的strokeWidth,调整RectF的边界
val strokeWidth: Float = bgPaint.getStrokeWidth()
val halfStrokeWidth = strokeWidth / 2
// 绘制完整圆的背景
path.reset()
path.addCircle(
width / 2f,
height / 2f,
(Math.max(width, height) - strokeWidth) / 2,
Path.Direction.CW
)
canvas.drawPath(path, bgPaint)
// 绘制完整圆的进度
path.reset()
val sweepAngleForProgress: Float = 360 * progress
// 设置渐变色
// 计算渐变的终点X坐标,基于进度
val endX = width / 2f + (sweepAngleForProgress / 360) * (width / 2f - halfStrokeWidth)
// 设置渐变色
// 设置渐变色
val shader = LinearGradient(
width / 2f, 0f, // 起点坐标
width / 2f, height.toFloat(), // 终点坐标
colorArray, // 渐变颜色
null, // 颜色位置(null表示均匀分布)
Shader.TileMode.CLAMP // 填充模式
)
arcPaint.shader = shader
// 从圆的顶部开始绘制
bounds.set(halfStrokeWidth, halfStrokeWidth, width - halfStrokeWidth, height - halfStrokeWidth)
path.addArc(bounds, 270f, sweepAngleForProgress)
canvas.drawPath(path, arcPaint)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val desiredWidth = dpToPx(144f)
val desiredHeight = dpToPx(144f)
setMeasuredDimension(resolveSize(desiredWidth, widthMeasureSpec), resolveSize(desiredHeight, heightMeasureSpec))
}
fun set(strokeWidth: Float, colorArray: IntArray) {
val width = dpToPx(strokeWidth)
arcPaint.strokeWidth = width
bgPaint.strokeWidth = width
this.colorArray = colorArray
}
fun setProgress(progress: Float) {
this.progress = progress
invalidate()
}
private fun dpToPx(dp: Float): Float {
return dp * resources.displayMetrics.density
}
private fun resolveSize(desiredSize: Float, measureSpec: Int): Int {
val mode = MeasureSpec.getMode(measureSpec)
val size = MeasureSpec.getSize(measureSpec)
return when (mode) {
MeasureSpec.EXACTLY -> size
MeasureSpec.AT_MOST -> Math.min(desiredSize.toInt(), size)
else -> desiredSize.toInt()
}
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#F2FAF8" />
<corners android:radius="15dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ffffff" />
<corners android:radius="12dp" />
</shape>
\ No newline at end of file
This diff is collapsed.
package com.koko.batteryinfo
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
\ No newline at end of file
......@@ -80,9 +80,11 @@ dependencies {
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)
implementation(project(":BatteryInfo"))
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
implementation(project(":Router"))
//网络请求
implementation("com.google.code.gson:gson:2.11.0")
......
package com.base.appzxhy.ui.start
import android.annotation.SuppressLint
import android.content.Intent
import com.base.appzxhy.base.BaseActivity
import com.base.appzxhy.bean.config.ConfigBean
import com.base.appzxhy.databinding.ActivityStartBinding
import com.base.appzxhy.ui.views.LogDialog.showLogDialog
import com.base.appzxhy.utils.ClickTimesUtils.isContinuousTimesClick
import com.google.gson.Gson
import com.koko.batteryinfo.BatteryInfoActivity
class StartActivity : BaseActivity<ActivityStartBinding>(ActivityStartBinding::inflate) {
......@@ -19,16 +17,18 @@ class StartActivity : BaseActivity<ActivityStartBinding>(ActivityStartBinding::i
override fun initListener() {
super.initListener()
binding.tv.setOnClickListener {
if (isContinuousTimesClick()) {
val sb = StringBuilder()
val gson = Gson()
sb.append("ut:${ConfigBean.configBean.ut}").append("\n")
val adLog = gson.toJson(ConfigBean.configBean.adConfigBean).replace(",", ",\n")
sb.append(adLog).append("\n")
val popupLog = gson.toJson(ConfigBean.configBean.popupConfigBean).replace(",", ",\n")
sb.append(popupLog)
showLogDialog(sb.toString())
}
// if (isContinuousTimesClick()) {
// val sb = StringBuilder()
// val gson = Gson()
// sb.append("ut:${ConfigBean.configBean.ut}").append("\n")
// val adLog = gson.toJson(ConfigBean.configBean.adConfigBean).replace(",", ",\n")
// sb.append(adLog).append("\n")
// val popupLog = gson.toJson(ConfigBean.configBean.popupConfigBean).replace(",", ",\n")
// sb.append(popupLog)
// showLogDialog(sb.toString())
// }
startActivity(Intent(this, BatteryInfoActivity::class.java))
}
}
}
\ No newline at end of file
......@@ -29,3 +29,4 @@ dependencyResolutionManagement {
rootProject.name = "appzxhy"
include(":app")
include(":Router")
include(":BatteryInfo")
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment