android: 修复源码GBK编码导致的中文乱码+重绘图标+USB插入自动授权+媒体权限+多点测温/色标条/追踪开关
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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,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
|
||||
|
||||
Reference in New Issue
Block a user