feat(demo): 对齐 iOS demo 布局 + Info+Ver 并发按钮
主要改动: - DemoSecrets 默认值对齐 iOS:owner=xmly, scanNamePrefix=Duooomi- - userId 默认值对齐:Cjgex9kTTTJq9u9RMOnc5hiIZTvrOOq3 - Config 区可在运行时编辑 owner/apiKey/scanPrefix,SharedPreferences 持久化;Reload SDK 按钮重建实例(旧 SDK 自动 disconnect) - PlayMode 行:显示当前模式标签(循环 / 单播)+ Single/Loop 按钮 - Firmware OTA:3 个独立输入(sn/currentVersion/userId) +「回填当前 状态」+「拉取安装包」+「安装」三按钮,对齐 iOS WrapperTestView - bind 完成 / version 更新自动回填 OTA 入参 - UpgradeRecords 4 状态展示:待上报 / ✅ 正确上报 / ❌ 错误上报 (版本不匹配) / 过期放弃 - 保留先前加的 Info+Ver 并发按钮(async 同时发 getDeviceInfo + getVersion,验证不同 commandType 通道并发不冲突) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,9 +13,9 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.duooomi.ble.DuooomiBleSDK
|
||||
import com.duooomi.ble.core.ConnectionState
|
||||
import com.duooomi.ble.models.FirmwareUpgradeCheckResult
|
||||
import com.duooomi.ble.models.PlayMode
|
||||
import com.duooomi.ble.protocol.CommandType
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
// MARK: - CDN Helper
|
||||
@@ -44,7 +44,11 @@ private enum class LogLevel { INFO, SUCCESS, ERROR }
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DemoScreen(sdk: DuooomiBleSDK) {
|
||||
fun DemoScreen(
|
||||
sdk: DuooomiBleSDK,
|
||||
config: DemoConfig,
|
||||
onReload: (DemoConfig) -> Unit
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
// SDK state
|
||||
@@ -56,13 +60,18 @@ fun DemoScreen(sdk: DuooomiBleSDK) {
|
||||
val isActivated by sdk.isActivated.collectAsState()
|
||||
val transferProgress by sdk.transferProgress.collectAsState()
|
||||
val sdkError by sdk.error.collectAsState()
|
||||
|
||||
// Local state
|
||||
var userId by remember { mutableStateOf("test-user-001") }
|
||||
var fileUrl by remember { mutableStateOf("https://cdn.bowong.cc/material/569f48a8e29f47859b3a9808be37f94c.mp4") }
|
||||
val sn by sdk.sn.collectAsState()
|
||||
val firmwareUpgrade by sdk.firmwareUpgrade.collectAsState()
|
||||
val upgradeRecords by sdk.upgradeRecords.collectAsState()
|
||||
|
||||
// Editable config (rekey on sdk change so new SDK 不会带着旧编辑残留)
|
||||
var configOwner by remember(sdk) { mutableStateOf(config.owner) }
|
||||
var configApiKey by remember(sdk) { mutableStateOf(config.apiKey) }
|
||||
var configScanPrefix by remember(sdk) { mutableStateOf(config.scanPrefix) }
|
||||
|
||||
// Local state
|
||||
var userId by remember { mutableStateOf("Cjgex9kTTTJq9u9RMOnc5hiIZTvrOOq3") }
|
||||
var fileUrl by remember { mutableStateOf("https://cdn.bowong.cc/material/569f48a8e29f47859b3a9808be37f94c.mp4") }
|
||||
var firmwareLoading by remember { mutableStateOf(false) }
|
||||
var isBusy by remember { mutableStateOf(false) }
|
||||
var connectingDeviceId by remember { mutableStateOf<String?>(null) }
|
||||
@@ -70,6 +79,23 @@ fun DemoScreen(sdk: DuooomiBleSDK) {
|
||||
var logs by remember { mutableStateOf<List<LogLine>>(emptyList()) }
|
||||
var logCounter by remember { mutableLongStateOf(0L) }
|
||||
|
||||
// OTA fetch 入参(可手动填,bind/getVersion 完成时自动回填)
|
||||
var otaFetchSn by remember(sdk) { mutableStateOf("") }
|
||||
var otaFetchVersion by remember(sdk) { mutableStateOf("") }
|
||||
var otaFetchUserId by remember(sdk) { mutableStateOf("") }
|
||||
|
||||
// bind 完成 → 回填 OTA 入参
|
||||
LaunchedEffect(isActivated, sn) {
|
||||
if (isActivated && sn.isNotEmpty()) {
|
||||
otaFetchSn = sn
|
||||
if (version.isNotEmpty()) otaFetchVersion = version
|
||||
if (otaFetchUserId.isEmpty()) otaFetchUserId = userId
|
||||
}
|
||||
}
|
||||
LaunchedEffect(version) {
|
||||
if (version.isNotEmpty()) otaFetchVersion = version
|
||||
}
|
||||
|
||||
fun log(message: String, level: LogLevel = LogLevel.INFO) {
|
||||
logCounter++
|
||||
logs = listOf(LogLine(logCounter, level, message)) + logs.take(199)
|
||||
@@ -91,6 +117,24 @@ fun DemoScreen(sdk: DuooomiBleSDK) {
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
// === Config Section ===
|
||||
item {
|
||||
SectionHeader("SDK Config(修改后点 Reload 生效)")
|
||||
ConfigField("x-owner", configOwner) { configOwner = it }
|
||||
ConfigField("apiKey", configApiKey) { configApiKey = it }
|
||||
ConfigField("scanPrefix", configScanPrefix) { configScanPrefix = it }
|
||||
Button(
|
||||
onClick = {
|
||||
onReload(DemoConfig(configOwner, configApiKey, configScanPrefix))
|
||||
log(
|
||||
"SDK reloaded with owner=$configOwner, prefix=$configScanPrefix",
|
||||
LogLevel.SUCCESS
|
||||
)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) { Text("Reload SDK") }
|
||||
}
|
||||
|
||||
// === Status Section ===
|
||||
item {
|
||||
SectionHeader("Status")
|
||||
@@ -214,6 +258,27 @@ fun DemoScreen(sdk: DuooomiBleSDK) {
|
||||
} finally { isBusy = false }
|
||||
}
|
||||
}) { Text("getVersion") }
|
||||
|
||||
OutlinedButton(onClick = {
|
||||
scope.launch {
|
||||
isBusy = true
|
||||
log("-> getDeviceInfo + getVersion (并发)")
|
||||
try {
|
||||
coroutineScope {
|
||||
val infoJob = async { sdk.getDeviceInfo() }
|
||||
val verJob = async { sdk.getVersion() }
|
||||
val info = infoJob.await()
|
||||
val ver = verJob.await()
|
||||
log(
|
||||
"info+ver OK: brand=${info.brand} power=${info.powerlevel}% v=${ver.version}",
|
||||
LogLevel.SUCCESS
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log("info+ver FAIL: ${e.message}", LogLevel.ERROR)
|
||||
} finally { isBusy = false }
|
||||
}
|
||||
}) { Text("Info+Ver") }
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(4.dp))
|
||||
@@ -257,7 +322,18 @@ fun DemoScreen(sdk: DuooomiBleSDK) {
|
||||
}
|
||||
|
||||
// Play mode toggle —— gated by isActivated
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text("PlayMode", fontSize = 12.sp)
|
||||
val modeLabel = when (deviceInfo?.loop) {
|
||||
PlayMode.LOOP -> "循环"
|
||||
PlayMode.SINGLE -> "单播"
|
||||
null -> ""
|
||||
}
|
||||
Text(modeLabel, fontSize = 12.sp, color = Color.Gray, modifier = Modifier.weight(1f))
|
||||
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
@@ -319,64 +395,59 @@ fun DemoScreen(sdk: DuooomiBleSDK) {
|
||||
) { Text("Transfer") }
|
||||
}
|
||||
|
||||
// === Firmware Section ===
|
||||
// === Firmware OTA Section ===
|
||||
item {
|
||||
SectionHeader("Firmware Update")
|
||||
StatusRow("SN", sn.ifEmpty { "--" })
|
||||
SectionHeader("Firmware OTA")
|
||||
StatusRow("Current Version", version.ifEmpty { "--" })
|
||||
|
||||
firmwareUpgrade?.targetFirmware?.let { target ->
|
||||
Card(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(modifier = Modifier.padding(12.dp)) {
|
||||
StatusRow("Latest", target.version)
|
||||
target.fileSize?.let { StatusRow("Size", "$it bytes") }
|
||||
target.fileMd5?.let { Text("MD5: $it", fontSize = 10.sp, color = Color.Gray) }
|
||||
target.forceUpgrade?.let { Text("forceUpgrade=$it", fontSize = 11.sp, color = Color.Red) }
|
||||
target.fileUrl?.let { Text(it, fontSize = 10.sp, color = Color.Gray, maxLines = 2) }
|
||||
}
|
||||
}
|
||||
} ?: firmwareUpgrade?.let {
|
||||
Text("Already up-to-date", fontSize = 12.sp, color = Color.Gray)
|
||||
}
|
||||
ConfigField("sn", otaFetchSn) { otaFetchSn = it }
|
||||
ConfigField("currentVersion", otaFetchVersion) { otaFetchVersion = it }
|
||||
ConfigField("userId", otaFetchUserId) { otaFetchUserId = it }
|
||||
|
||||
if (firmwareLoading) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
CircularProgressIndicator(modifier = Modifier.size(16.dp), strokeWidth = 2.dp)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("Fetching...", fontSize = 12.sp, color = Color.Gray)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
OutlinedButton(onClick = {
|
||||
otaFetchSn = sn
|
||||
otaFetchVersion = version
|
||||
otaFetchUserId = userId
|
||||
}) { Text("回填当前状态") }
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
firmwareLoading = true
|
||||
log("-> fetchLatestFirmware(sn=$sn, version=$version, $userId)")
|
||||
log("-> fetchLatestFirmware(sn=$otaFetchSn, version=$otaFetchVersion, userId=$otaFetchUserId) [report→softBind→check]")
|
||||
try {
|
||||
val r = sdk.fetchLatestFirmware(sn = sn, currentVersion = version, userId = userId)
|
||||
val target = r.targetFirmware
|
||||
if (target != null) {
|
||||
log("firmware latest: ${target.version}", LogLevel.SUCCESS)
|
||||
} else {
|
||||
log("already up-to-date")
|
||||
}
|
||||
val r = sdk.fetchLatestFirmware(
|
||||
sn = otaFetchSn,
|
||||
currentVersion = otaFetchVersion,
|
||||
userId = otaFetchUserId
|
||||
)
|
||||
val tv = r.targetFirmware?.version ?: "—"
|
||||
log("fetchLatestFirmware OK target=$tv", LogLevel.SUCCESS)
|
||||
} catch (e: Exception) {
|
||||
log("firmware info FAIL: ${e.message}", LogLevel.ERROR)
|
||||
log("fetchLatestFirmware FAIL: ${e.message}", LogLevel.ERROR)
|
||||
} finally { firmwareLoading = false }
|
||||
}
|
||||
},
|
||||
enabled = !firmwareLoading && sn.isNotEmpty()
|
||||
) { Text("Get Update Info") }
|
||||
enabled = !firmwareLoading && otaFetchSn.isNotEmpty() && otaFetchUserId.isNotEmpty()
|
||||
) { Text("拉取安装包") }
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
val target: FirmwareUpgradeCheckResult.TargetFirmware =
|
||||
firmwareUpgrade?.targetFirmware ?: return@launch
|
||||
val targetFileUrl = target.fileUrl ?: return@launch
|
||||
val target = firmwareUpgrade?.targetFirmware
|
||||
val targetFileUrl = target?.fileUrl
|
||||
if (target == null || targetFileUrl.isNullOrEmpty()) {
|
||||
log("install FAIL: 无可用固件包(请先点拉取安装包)", LogLevel.ERROR)
|
||||
return@launch
|
||||
}
|
||||
isBusy = true
|
||||
log("-> upgradeFirmware ${target.version}")
|
||||
log("-> upgradeFirmware sn=$sn $version→${target.version} (fw=${target.id})")
|
||||
try {
|
||||
sdk.upgradeFirmware(
|
||||
sn = sn,
|
||||
@@ -385,32 +456,68 @@ fun DemoScreen(sdk: DuooomiBleSDK) {
|
||||
fromVersion = version,
|
||||
targetVersion = target.version
|
||||
)
|
||||
log("OTA OK", LogLevel.SUCCESS)
|
||||
log("install OK(已落 UpgradeRecord,下次 fetchLatestFirmware 自动对账上报)", LogLevel.SUCCESS)
|
||||
} catch (e: Exception) {
|
||||
log("OTA FAIL: ${e.message}", LogLevel.ERROR)
|
||||
log("install FAIL: ${e.message}", LogLevel.ERROR)
|
||||
} finally { isBusy = false }
|
||||
}
|
||||
},
|
||||
enabled = firmwareUpgrade?.targetFirmware?.fileUrl?.isNotEmpty() == true
|
||||
&& connectedDevice != null && !isBusy
|
||||
) { Text("Upgrade") }
|
||||
enabled = firmwareUpgrade?.targetFirmware?.fileUrl?.isNotEmpty() == true && !isBusy
|
||||
) { Text("安装") }
|
||||
}
|
||||
|
||||
if (upgradeRecords.isNotEmpty()) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text("Upgrade Records (${upgradeRecords.size})", fontSize = 12.sp, fontWeight = FontWeight.Bold)
|
||||
upgradeRecords.sortedByDescending { it.triggeredAt }.take(5).forEach { r ->
|
||||
val outcome = r.outcome
|
||||
val outcomeText = when {
|
||||
!r.reported -> "pending"
|
||||
outcome == null -> "expired"
|
||||
else -> outcome.raw
|
||||
if (firmwareLoading) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
CircularProgressIndicator(modifier = Modifier.size(14.dp), strokeWidth = 2.dp)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("Fetching...", fontSize = 12.sp, color = Color.Gray)
|
||||
}
|
||||
}
|
||||
|
||||
firmwareUpgrade?.let { upgrade ->
|
||||
val target = upgrade.targetFirmware
|
||||
if (target != null) {
|
||||
StatusRow("target version", target.version)
|
||||
Text("firmwareId: ${target.id}", fontSize = 10.sp, color = Color.Gray)
|
||||
target.forceUpgrade?.let {
|
||||
Text(
|
||||
"forceUpgrade: ${if (it) "是" else "否"}",
|
||||
fontSize = 10.sp,
|
||||
color = if (it) Color.Red else Color.Gray
|
||||
)
|
||||
}
|
||||
Text(
|
||||
"[$outcomeText] fw=${r.firmwareId} ${r.fromVersion}→${r.targetVersion}",
|
||||
fontSize = 10.sp,
|
||||
color = Color.Gray
|
||||
)
|
||||
target.fileSize?.let { Text("fileSize: $it", fontSize = 10.sp, color = Color.Gray) }
|
||||
target.fileMd5?.let { Text("md5: $it", fontSize = 10.sp, color = Color.Gray) }
|
||||
target.fileUrl?.let { Text(it, fontSize = 10.sp, color = Color.Gray, maxLines = 2) }
|
||||
} else {
|
||||
Text("无可用固件(targetFirmware 为空)", fontSize = 12.sp, color = Color.Gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === Upgrade Records ===
|
||||
if (upgradeRecords.isNotEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
"Upgrade Records (${upgradeRecords.size})",
|
||||
fontSize = 12.sp,
|
||||
color = Color.Gray,
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
)
|
||||
}
|
||||
items(upgradeRecords.sortedByDescending { it.triggeredAt }, key = { it.firmwareId + it.sn }) { r ->
|
||||
Column(modifier = Modifier.padding(vertical = 2.dp)) {
|
||||
Text("${r.sn} · ${r.firmwareId}", fontSize = 11.sp)
|
||||
Text("${r.fromVersion} → ${r.targetVersion}", fontSize = 10.sp, color = Color.Gray)
|
||||
val outcome = r.outcome
|
||||
val (statusText, statusColor) = when {
|
||||
!r.reported -> "待上报" to Color(0xFFFF9800)
|
||||
outcome?.raw == "success" -> "✅ 正确上报" to Color(0xFF2E7D32)
|
||||
outcome?.raw == "version_mismatch" -> "❌ 错误上报(版本不匹配)" to Color.Red
|
||||
else -> "过期放弃" to Color.Gray
|
||||
}
|
||||
Text(statusText, fontSize = 10.sp, color = statusColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -486,6 +593,23 @@ private fun SectionHeader(title: String) {
|
||||
Divider()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ConfigField(label: String, value: String, onChange: (String) -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(label, fontSize = 12.sp, modifier = Modifier.width(90.dp))
|
||||
OutlinedTextField(
|
||||
value = value,
|
||||
onValueChange = onChange,
|
||||
modifier = Modifier.weight(1f),
|
||||
singleLine = true,
|
||||
textStyle = androidx.compose.ui.text.TextStyle(fontSize = 12.sp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StatusRow(label: String, value: String) {
|
||||
Row(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.duooomi.ble.demo
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -10,42 +12,76 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.duooomi.ble.DuooomiBleConfig
|
||||
import com.duooomi.ble.DuooomiBleSDK
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
private lateinit var sdk: DuooomiBleSDK
|
||||
|
||||
private val permissionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions()
|
||||
) { /* permissions granted or denied — SDK handles errors */ }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
sdk = DuooomiBleSDK(
|
||||
context = this,
|
||||
config = DuooomiBleConfig(
|
||||
apiKey = DemoSecrets.API_KEY,
|
||||
owner = DemoSecrets.OWNER,
|
||||
scanNamePrefix = DemoSecrets.SCAN_NAME_PREFIX
|
||||
)
|
||||
)
|
||||
|
||||
requestBlePermissions()
|
||||
|
||||
val prefs = getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
val initialConfig = DemoConfig(
|
||||
owner = prefs.getString(KEY_OWNER, DemoSecrets.OWNER) ?: DemoSecrets.OWNER,
|
||||
apiKey = prefs.getString(KEY_API_KEY, DemoSecrets.API_KEY) ?: DemoSecrets.API_KEY,
|
||||
scanPrefix = prefs.getString(KEY_SCAN_PREFIX, DemoSecrets.SCAN_NAME_PREFIX)
|
||||
?: DemoSecrets.SCAN_NAME_PREFIX
|
||||
)
|
||||
|
||||
setContent {
|
||||
var config by remember { mutableStateOf(initialConfig) }
|
||||
var sdk by remember { mutableStateOf(buildSdk(config)) }
|
||||
|
||||
MaterialTheme {
|
||||
Surface(modifier = Modifier.fillMaxSize()) {
|
||||
DemoScreen(sdk = sdk)
|
||||
DemoScreen(
|
||||
sdk = sdk,
|
||||
config = config,
|
||||
onReload = { newConfig ->
|
||||
persist(prefs, newConfig)
|
||||
// 旧 SDK:fire-and-forget disconnect 释放 BLE 资源
|
||||
val old = sdk
|
||||
@Suppress("OPT_IN_USAGE")
|
||||
GlobalScope.launch { runCatching { old.disconnect() } }
|
||||
config = newConfig
|
||||
sdk = buildSdk(newConfig)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildSdk(c: DemoConfig) = DuooomiBleSDK(
|
||||
context = this,
|
||||
config = DuooomiBleConfig(
|
||||
apiKey = c.apiKey,
|
||||
owner = c.owner,
|
||||
scanNamePrefix = c.scanPrefix
|
||||
)
|
||||
)
|
||||
|
||||
private fun persist(prefs: SharedPreferences, c: DemoConfig) {
|
||||
prefs.edit()
|
||||
.putString(KEY_OWNER, c.owner)
|
||||
.putString(KEY_API_KEY, c.apiKey)
|
||||
.putString(KEY_SCAN_PREFIX, c.scanPrefix)
|
||||
.apply()
|
||||
}
|
||||
|
||||
private fun requestBlePermissions() {
|
||||
val permissions = mutableListOf<String>()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
@@ -62,4 +98,17 @@ class MainActivity : ComponentActivity() {
|
||||
permissionLauncher.launch(needed.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val PREFS = "demo_config"
|
||||
const val KEY_OWNER = "demo.config.owner"
|
||||
const val KEY_API_KEY = "demo.config.apiKey"
|
||||
const val KEY_SCAN_PREFIX = "demo.config.scanPrefix"
|
||||
}
|
||||
}
|
||||
|
||||
data class DemoConfig(
|
||||
val owner: String,
|
||||
val apiKey: String,
|
||||
val scanPrefix: String
|
||||
)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package com.duooomi.ble.demo
|
||||
|
||||
/**
|
||||
* Copy this file to `DemoSecrets.kt` (gitignored) and fill in real credentials.
|
||||
* Copy this file to `demo/src/main/kotlin/com/duooomi/ble/demo/DemoSecrets.kt`
|
||||
* (already gitignored) and fill in real credentials.
|
||||
*/
|
||||
object DemoSecrets {
|
||||
const val API_KEY: String = "<paste-api-key>"
|
||||
const val OWNER: String = "<paste-owner>"
|
||||
const val OWNER: String = "xmly"
|
||||
const val SCAN_NAME_PREFIX: String = "Duooomi-"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user