android: 修复源码GBK编码导致的中文乱码+重绘图标+USB插入自动授权+媒体权限+多点测温/色标条/追踪开关

This commit is contained in:
ZXCLI
2026-09-06 21:56:34 +08:00
parent 6d0805af90
commit 430bf373e4
17 changed files with 205 additions and 34 deletions
+9
View File
@@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.usb.host" android:required="true" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<application
android:label="MAG160C"
@@ -20,6 +22,13 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Auto-launch / system permission dialog when the camera is plugged in -->
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
</application>
</manifest>
@@ -30,10 +30,10 @@ import com.mag160c.thermal.ui.settings.SettingsScreen
private data class Tab(val label: String, val icon: Int)
private val TABS = listOf(
Tab("实时", R.drawable.ic_ffc),
Tab("相册", R.drawable.ic_gallery),
Tab("分析", R.drawable.ic_analysis),
Tab("设置", R.drawable.ic_settings),
Tab("实时", R.drawable.ic_ffc),
Tab("相册", R.drawable.ic_gallery),
Tab("分析", R.drawable.ic_analysis),
Tab("设置", R.drawable.ic_settings),
)
@Composable
@@ -45,8 +45,8 @@ fun AppRoot() {
val content: @Composable () -> Unit = {
when (tab) {
0 -> LiveScreen()
1 -> Placeholder("媒体库(阶段4实现)")
2 -> Placeholder("MDT 离线分析(阶段5实现)")
1 -> Placeholder("媒体库(阶段4实现)")
2 -> Placeholder("MDT 离线分析(阶段5实现)")
else -> SettingsScreen()
}
}
@@ -41,6 +41,19 @@ fun GalleryScreen(vm: GalleryViewModel = viewModel()) {
val context = LocalContext.current
var showViewer by remember { mutableStateOf(false) }
// runtime media permission (API 33+: READ_MEDIA_IMAGES, else READ_EXTERNAL_STORAGE)
val perm = if (android.os.Build.VERSION.SDK_INT >= 33)
android.Manifest.permission.READ_MEDIA_IMAGES
else android.Manifest.permission.READ_EXTERNAL_STORAGE
val launcher = androidx.activity.compose.rememberLauncherForActivityResult(
androidx.activity.result.contract.ActivityResultContracts.RequestPermission(),
) { granted -> vm.refresh() }
LaunchedEffect(Unit) {
val granted = androidx.core.content.ContextCompat.checkSelfPermission(context, perm) ==
android.content.pm.PackageManager.PERMISSION_GRANTED
if (granted) vm.refresh() else launcher.launch(perm)
}
Column(modifier = Modifier.fillMaxSize()) {
Row(
modifier = Modifier.fillMaxWidth().padding(8.dp),
@@ -116,6 +116,46 @@ class LiveRenderer(
textPaint.color = Color.WHITE
}
private fun drawOsd(canvas: Canvas, state: LiveViewModel.LiveState) {
textPaint.color = Color.WHITE
state.centerTempC?.let {
canvas.drawText("中心 %.1f℃".format(it), 10f, textPaint.textSize + 8f, textPaint)
}
if (state.maxTraceOn) {
drawTempMarker(canvas, state.maxPos, state.maxTempC)
}
drawTempMarker(canvas, state.minPos, state.minTempC)
for (p in state.probes) {
drawTempMarker(canvas, p.x + p.y * 160, p.tempC, p.label)
}
drawColorBar(canvas, state)
}
/** Side color bar: palette gradient + min/max temperature labels. */
private fun drawColorBar(canvas: Canvas, state: LiveViewModel.LiveState) {
if (state.maxTempC == null || state.minTempC == null) return
val pal = com.mag160c.thermal.core.Palettes.buildAll()[state.paletteIndex]
val barW = 14f
val barH = viewport.height() * 0.66f
val x = viewport.right - barW - 8f
val y0 = viewport.top + (viewport.height() - barH) / 2f
val seg = Paint()
val n = 64
for (i in 0 until n) {
// top = hot (palette 255), bottom = cold (palette 0)
val c = pal[255 - i * 255 / (n - 1)]
val sy = y0 + barH * i / n
val ey = y0 + barH * (i + 1) / n
seg.color = c
canvas.drawRect(x, sy, x + barW, ey + 0.5f, seg)
}
textPaint.color = Color.WHITE
val maxT = "%.1f".format(state.maxTempC)
val minT = "%.1f".format(state.minTempC)
canvas.drawText(maxT, x - textPaint.measureText(maxT) / 2, y0 - 4f, textPaint)
canvas.drawText(minT, x - textPaint.measureText(minT) / 2, y0 + barH + textPaint.textSize, textPaint)
}
private fun sensorToScreen(pos: Int): FloatArray {
val x = pos % 160
val y = pos / 160
@@ -124,10 +164,10 @@ class LiveRenderer(
return floatArrayOf(sx, sy)
}
private fun drawTempMarker(canvas: Canvas, pos: Int, tempC: Float?) {
private fun drawTempMarker(canvas: Canvas, pos: Int, tempC: Float?, label: String? = null) {
if (pos < 0 || tempC == null) return
val p = sensorToScreen(pos)
val text = "%.1f℃".format(tempC)
val text = (label?.let { "$it " } ?: "") + "%.1f℃".format(tempC)
val tw = textPaint.measureText(text)
val cx = p[0]
val cy = p[1]
@@ -138,13 +178,4 @@ class LiveRenderer(
canvas.drawCircle(cx, cy, 4f, textPaint)
canvas.drawText(text, tx, ty, textPaint)
}
private fun drawOsd(canvas: Canvas, state: LiveViewModel.LiveState) {
textPaint.color = Color.WHITE
state.centerTempC?.let {
canvas.drawText("中心 %.1f℃".format(it), 10f, textPaint.textSize + 8f, textPaint)
}
drawTempMarker(canvas, state.maxPos, state.maxTempC)
drawTempMarker(canvas, state.minPos, state.minTempC)
}
}
@@ -1,6 +1,7 @@
package com.mag160c.thermal.ui.live
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -27,6 +28,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
@@ -56,6 +58,19 @@ fun LiveScreen(vm: LiveViewModel = viewModel()) {
.fillMaxSize(),
) {
AndroidSurface(vm)
// tap overlay: add / remove probe points
Box(
modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectTapGestures { offset ->
vm.tapImage(
offset.x, offset.y,
size.width.toFloat(), size.height.toFloat(),
)
}
},
)
if (!state.connected) {
Text(
text = when (state.status) {
@@ -92,6 +107,15 @@ private fun ControlBar(state: LiveViewModel.LiveState, vm: LiveViewModel) {
IconButton(onClick = { vm.setZoom(vm.state.value.zoom % 4 + 1) }) {
Icon(painterResource(R.drawable.ic_zoom), contentDescription = "数码变倍 x${state.zoom}")
}
Text(
text = if (state.maxTraceOn) "追踪·开" else "追踪",
style = MaterialTheme.typography.labelMedium,
color = if (state.maxTraceOn) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.clickable { vm.toggleMaxTrace() }
.padding(6.dp),
)
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = state.centerTempC?.let { "%.1f℃".format(it) } ?: "--",
@@ -12,6 +12,8 @@ import kotlinx.coroutines.flow.StateFlow
* Live view state machine: USB permission -> link -> stream -> OSD stats.
*/
class LiveViewModel(app: Application) : AndroidViewModel(app) {
data class ProbePoint(val x: Int, val y: Int, val label: String, val tempC: Float?)
data class LiveState(
val connected: Boolean = false,
val streaming: Boolean = false,
@@ -24,6 +26,8 @@ class LiveViewModel(app: Application) : AndroidViewModel(app) {
val maxPos: Int = -1,
val minPos: Int = -1,
val identity: IrSession.CameraIdentity? = null,
val maxTraceOn: Boolean = true,
val probes: List<ProbePoint> = emptyList(),
)
private val _state = MutableStateFlow(LiveState())
@@ -36,6 +40,10 @@ class LiveViewModel(app: Application) : AndroidViewModel(app) {
private val session = IrSession(app)
private var usbReceiver: android.content.BroadcastReceiver? = null
private var recorder: com.mag160c.thermal.media.Mp4Recorder? = null
private val sessionListener = object : IrSession.Listener {
override fun onStateChanged(state: IrSession.State, message: String?) {
_state.value = _state.value.copy(
@@ -56,6 +64,18 @@ class LiveViewModel(app: Application) : AndroidViewModel(app) {
init {
session.setListener(sessionListener)
// auto permission + start when the camera is plugged in while running
val ctx = getApplication<Application>()
usbReceiver = object : android.content.BroadcastReceiver() {
override fun onReceive(c: android.content.Context?, i: android.content.Intent?) {
connect()
}
}
ctx.registerReceiver(
usbReceiver,
android.content.IntentFilter(android.hardware.usb.UsbManager.ACTION_USB_DEVICE_ATTACHED),
if (android.os.Build.VERSION.SDK_INT >= 33) android.content.Context.RECEIVER_NOT_EXPORTED else 0,
)
// push frames into the MP4 recorder while recording
session.recorderHook = { argb ->
val rec = recorder
@@ -95,6 +115,11 @@ class LiveViewModel(app: Application) : AndroidViewModel(app) {
_state.value = _state.value.copy(zoom = z.coerceIn(1, 4))
}
/** Toggle max-temperature trace marker. */
fun toggleMaxTrace() {
_state.value = _state.value.copy(maxTraceOn = !_state.value.maxTraceOn)
}
/** Capture: rendered JPEG + raw frame + camera info -> MDT -> MediaStore. */
fun capturePhoto(context: android.content.Context) {
val frame = latestFrame ?: return
@@ -116,7 +141,49 @@ class LiveViewModel(app: Application) : AndroidViewModel(app) {
_state.value = _state.value.copy(status = if (saved != null) "saved" else "save_fail")
}
private var recorder: com.mag160c.thermal.media.Mp4Recorder? = null
/**
* Tap on the live image: add a probe point, or delete an existing one when
* tapping near it. Coordinates are view pixels; converted via the same
* letterbox + zoom math the renderer uses.
*/
fun tapImage(screenX: Float, screenY: Float, viewW: Float, viewH: Float) {
val s = _state.value
if (!s.streaming) return
val ar = 4f / 3f
val viewRatio = viewW / viewH
val dstW: Float
val dstH: Float
if (viewRatio > ar) {
dstH = viewH
dstW = viewH * ar
} else {
dstW = viewW
dstH = viewW / ar
}
val left = (viewW - dstW) / 2f
val top = (viewH - dstH) / 2f
if (screenX < left || screenX > left + dstW || screenY < top || screenY > top + dstH) return
val srcW = 160f / s.zoom
val srcH = 120f / s.zoom
val sx = (screenX - left) / dstW * srcW + (160 - srcW) / 2f
val sy = (screenY - top) / dstH * srcH + (120 - srcH) / 2f
val sensorX = sx.toInt().coerceIn(0, 159)
val sensorY = sy.toInt().coerceIn(0, 119)
// near an existing probe? delete it instead of adding
val thr = dstW * 0.06f
val existing = s.probes.firstOrNull { p ->
kotlin.math.abs(p.x + 0.5f - sx) * (dstW / srcW) < thr &&
kotlin.math.abs(p.y + 0.5f - sy) * (dstH / srcH) < thr
}
if (existing != null) {
_state.value = _state.value.copy(probes = _state.value.probes - existing)
return
}
val label = "Pt${s.probes.size + 1}"
val p = ProbePoint(sensorX, sensorY, label, null)
_state.value = _state.value.copy(probes = _state.value.probes + p)
refreshTemps()
}
/** Toggle MP4 recording of the live stream. */
fun toggleRecording(context: android.content.Context) {
@@ -159,12 +226,17 @@ class LiveViewModel(app: Application) : AndroidViewModel(app) {
mxPos = i
}
}
// probe temperatures
val probes = _state.value.probes.map { p ->
p.copy(tempC = TempMath.countsToTempMc(nuc[p.y * 160 + p.x]) / 1000f)
}
_state.value = _state.value.copy(
centerTempC = center?.let { TempMath.countsToTempMc(it) / 1000f },
maxTempC = if (mx >= 0) TempMath.countsToTempMc(mx) / 1000f else null,
minTempC = if (mn <= Int.MAX_VALUE) TempMath.countsToTempMc(mn) / 1000f else null,
maxPos = mxPos,
minPos = mnPos,
probes = probes,
)
}
@@ -1,3 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FF000000" android:pathData="M19,3H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM9,17H7v-7h2V17zM13,17h-2V7h2V17zM17,17h-2v-4h2V17z" />
<path android:pathData="M5,20 L5,13" android:strokeColor="#FF000000" android:strokeWidth="4" />
<path android:pathData="M12,4 L12,20" android:strokeColor="#FF000000" android:strokeWidth="4" />
<path android:pathData="M19,9 L19,20" android:strokeColor="#FF000000" android:strokeWidth="4" />
</vector>
@@ -1,4 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FF000000" android:pathData="M12,12m-3.2,0a3.2,3.2 0 1,1 6.4,0a3.2,3.2 0 1,1 -6.4,0" />
<path android:fillColor="#FF000000" android:pathData="M9,2L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2H9zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5s5,2.24 5,5S14.76,17 12,17z" />
<path android:pathData="M4,7 L4,19 L20,19 L20,7 Z" android:strokeColor="#FF000000" android:strokeWidth="2" android:fillColor="#00000000" />
<path android:pathData="M9,7 L9,5 L15,5 L15,7" android:strokeColor="#FF000000" android:strokeWidth="2" android:fillColor="#00000000" />
<path android:pathData="M12,13m-3,0a3,3 0 1,1 6,0a3,3 0 1,1 -6,0" android:strokeColor="#FF000000" android:strokeWidth="2" android:fillColor="#00000000" />
</vector>
+3 -1
View File
@@ -1,3 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FF000000" android:pathData="M12,4a8,8 0 1,0 0,16a8,8 0 1,0 0,-16zM12,2a10,10 0 1,1 0,20a10,10 0 1,1 0,-20zM12,8a4,4 0 1,0 0,8a4,4 0 1,0 0,-8zM11,0h2v4h-2zM11,20h2v4h-2zM2,11h4v2h-4zM20,11h4v2h-4z" />
<path android:pathData="M12,3 L12,7 M12,17 L12,21 M3,12 L7,12 M17,12 L21,12" android:strokeColor="#FF000000" android:strokeWidth="2" />
<path android:pathData="M12,5a7,7 0 1,0 0.001,0z" android:strokeColor="#FF000000" android:strokeWidth="2" android:fillColor="#00000000" />
<path android:pathData="M12,12m-2.5,0a2.5,2.5 0 1,1 5,0a2.5,2.5 0 1,1 -5,0" android:fillColor="#FF000000" />
</vector>
@@ -1,3 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FF000000" android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3,-4.5z" />
<path android:pathData="M4,5 L4,19 L20,19 L20,7 Z" android:strokeColor="#FF000000" android:strokeWidth="2" android:fillColor="#00000000" />
<path android:pathData="M7,16 L11,11 L14,14 L17,12 L20,16" android:strokeColor="#FF000000" android:strokeWidth="2" android:fillColor="#00000000" android:strokeLineCap="round" android:strokeLineJoin="round" />
<path android:pathData="M15,10a1.5,1.5 0 1,1 3,0a1.5,1.5 0 1,1 -3,0" android:fillColor="#FF000000" />
</vector>
@@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="108dp" android:height="108dp" android:viewportWidth="108" android:viewportHeight="108">
<path android:fillColor="#FF1B2B33" android:pathData="M0,0h108v108h-108z" />
<path android:fillColor="#FFFFB59B" android:pathData="M54,54m-20,0a20,20 0 1,1 40,0a20,20 0 1,1 -40,0" />
<path android:fillColor="#FF8F4C38" android:pathData="M54,54m-12,0a12,12 0 1,1 24,0a24,24 0 1,1 -24,0" />
<path android:fillColor="#FFFFE0D2" android:pathData="M54,54m-6,0a6,6 0 1,1 12,0a6,6 0 1,1 -12,0" />
<path android:pathData="M0,0h108v108h-108z" android:fillColor="#FF1B2B33" />
<path android:pathData="M54,54m-20,0a20,20 0 1,0 40,0a20,20 0 1,0 -40,0" android:fillColor="#FFFFB59B" />
<path android:pathData="M54,54m-12,0a12,12 0 1,0 24,0a12,12 0 1,0 -24,0" android:fillColor="#FF8F4C38" />
<path android:pathData="M54,54m-6,0a6,6 0 1,0 12,0a6,6 0 1,0 -12,0" android:fillColor="#FFFFE0D2" />
</vector>
@@ -1,5 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FF000000" android:pathData="M12,2C6.49,2 2,6.49 2,8c0,0 0,10 0,10c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8C22,2 17.51,2 12,2zM20,18H4V8h16V18z" />
<path android:fillColor="#FF000000" android:pathData="M6,11h2v6H6zM10,11h2v6h-2zM10,15h2v3h-2z" />
<path android:fillColor="#FF000000" android:pathData="M18,13a2,2 0 1,0 0.001,0z" />
<path android:pathData="M6,4 L6,18" android:strokeColor="#FFB71C1C" android:strokeWidth="3" />
<path android:pathData="M12,8 L12,18" android:strokeColor="#FF2E7D32" android:strokeWidth="3" />
<path android:pathData="M18,4 L18,18" android:strokeColor="#FF1565C0" android:strokeWidth="3" />
<path android:pathData="M3,18 L21,18" android:strokeColor="#FF37474F" android:strokeWidth="2" />
</vector>
@@ -1 +1,4 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#FF000000" android:pathData="M12,4C7.31,4 3.5,7.81 3.5,12.5S7.31,21 12,21s8.5,-3.81 8.5,-8.5S16.69,4 12,4zM12,18c-3.04,0 -5.5,-2.46 -5.5,-5.5S8.96,7 12,7s5.5,2.46 5.5,5.5S15.04,18 12,18zM12,17c2.49,0 4.5,-2.01 4.5,-4.5S14.49,8 12,8s-4.5,2.01 -4.5,4.5S9.51,17 12,17z" /></vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M12,4a8,8 0 1,0 0.001,0z" android:strokeColor="#FFD32F2F" android:strokeWidth="2.5" android:fillColor="#00000000" />
<path android:pathData="M12,12m-4,0a4,4 0 1,1 8,0a4,4 0 1,1 -8,0" android:fillColor="#FFD32F2F" />
</vector>
@@ -1,3 +1,8 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FF000000" android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.7,4.96c-0.22,-0.16 -0.47,-0.06 -0.59,0.22L3.2,8.28c-0.12,0.21 -0.08,0.47 0.12,0.61l2.03,1.58C5.14,9.9 5,10.44 5,11s0.02,0.61 0.07,0.94L2.08,13.52c-0.18,0.14 -0.24,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.03,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.03,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.01L19.14,12.01zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6S10.02,8.4 12,8.4s3.6,1.6 3.6,3.6S13.98,15.6 12,15.6z" />
<path android:pathData="M4,6 L20,6" android:strokeColor="#FF000000" android:strokeWidth="2" />
<path android:pathData="M14,6m-2.5,0a2,2 0 1,1 4,0a2,2 0 1,1 -4,0" android:fillColor="#FF000000" />
<path android:pathData="M4,12 L20,12" android:strokeColor="#FF000000" android:strokeWidth="2" />
<path android:pathData="M8,12m-2,0a2,2 0 1,1 4,0a2,2 0 1,1 -4,0" android:fillColor="#FF000000" />
<path android:pathData="M4,18 L20,18" android:strokeColor="#FF000000" android:strokeWidth="2" />
<path android:pathData="M16,18m-2,0a2,2 0 1,1 4,0a2,2 0 1,1 -4,0" android:fillColor="#FF000000" />
</vector>
@@ -1,4 +1,4 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FF000000" android:pathData="M15,3l2.3,2.3l-2.89,2.87l1.42,1.42L18.7,6.7L21,9V2h-7zM3,9V2h7l-2.3,2.3l2.87,2.89l-1.42,1.42L9.3,6.31L7,8.6l-1.99,-2zM9,15l-2.3,2.3l-1.42,-1.42L6.3,15l-2.87,-2.89l-1.42,1.42l2.89,2.87L3,18l7,7v-7h-7zM21,18l-2.3,-2.3l1.42,-1.42L21.3,17l-2.87,2.87l1.42,1.42L21.3,21l2.7,3V21h-7z" />
<path android:fillColor="#FF000000" android:pathData="M4,4l4,0l0,4l-4,0z M16,4l4,0l0,4l-4,0z M4,16l4,0l0,4l-4,0z M16,16l4,0l0,4l-4,0z" />
<path android:pathData="M10.5,3a7.5,7.5 0 1,0 0.001,0z" android:strokeColor="#FF000000" android:strokeWidth="2.5" android:fillColor="#00000000" />
<path android:pathData="M16,16 L21,21" android:strokeColor="#FF000000" android:strokeWidth="2.5" />
</vector>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- MAG160C thermal module -->
<usb-device vendor-id="33596" />
</resources>
+1
View File
@@ -1,4 +1,5 @@
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
org.gradle.caching=true
kotlin.daemon.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
kotlin.code.style=official