Ver código fonte

添加脉诊设备模块

cc12458 1 mês atrás
pai
commit
f118705f19

+ 2 - 0
.idea/gradle.xml

@@ -14,6 +14,8 @@
             <option value="$PROJECT_DIR$/core" />
             <option value="$PROJECT_DIR$/library" />
             <option value="$PROJECT_DIR$/library/browser" />
+            <option value="$PROJECT_DIR$/library/device" />
+            <option value="$PROJECT_DIR$/library/device/pulse" />
           </set>
         </option>
       </GradleProjectSettings>

+ 1 - 0
app/build.gradle.kts

@@ -59,4 +59,5 @@ dependencies {
 
   implementation(project(":core"))
   implementation(project(":library:browser"))
+  implementation(project(":library:device:pulse"))
 }

+ 5 - 0
app/src/main/java/com/hzliuzhi/applet/container/MainActivity.kt

@@ -2,15 +2,18 @@ package com.hzliuzhi.applet.container
 
 import android.os.Bundle
 import androidx.activity.ComponentActivity
+import androidx.activity.compose.LocalActivity
 import androidx.activity.compose.setContent
 import androidx.activity.enableEdgeToEdge
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
 import androidx.navigation.NavHostController
 import androidx.navigation.compose.rememberNavController
 import com.hzliuzhi.applet.container.navigation.Host
 import com.hzliuzhi.applet.core.theme.SixTheme
+import com.hzliuzhi.applet.device.pulse.PulseEventHandle
 
 class MainActivity : ComponentActivity() {
   private var navController: NavHostController? = null
@@ -28,6 +31,8 @@ class MainActivity : ComponentActivity() {
           }
         }
       }
+
+      LocalActivity.current?.also { PulseEventHandle(it, rememberCoroutineScope()) }
     }
   }
 }

+ 1 - 0
gradle.properties

@@ -15,6 +15,7 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
 # Android operating system, and which are packaged with your app's APK
 # https://developer.android.com/topic/libraries/support-library/androidx-rn
 android.useAndroidX=true
+android.enableJetifier=true
 # Kotlin code style for this project: "official" or "obsolete":
 kotlin.code.style=official
 # Enables namespacing of each library's R class so that its R class includes only the

+ 1 - 0
library/device/pulse/.gitignore

@@ -0,0 +1 @@
+/build

+ 58 - 0
library/device/pulse/build.gradle.kts

@@ -0,0 +1,58 @@
+plugins {
+  alias(libs.plugins.android.library)
+  alias(libs.plugins.kotlin.android)
+  alias(libs.plugins.kotlin.compose)
+}
+
+android {
+  namespace = "com.hzliuzhi.applet.device.pulse"
+  compileSdk = 35
+
+  defaultConfig {
+    minSdk = 26
+
+    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_11
+    targetCompatibility = JavaVersion.VERSION_11
+  }
+  kotlinOptions {
+    jvmTarget = "11"
+  }
+  buildFeatures {
+    compose = true
+  }
+}
+
+dependencies {
+
+  implementation(libs.androidx.core.ktx)
+  implementation(libs.androidx.appcompat)
+  implementation(libs.androidx.activity.compose)
+  implementation(platform(libs.androidx.compose.bom))
+  testImplementation(libs.junit)
+  androidTestImplementation(libs.androidx.junit)
+  androidTestImplementation(libs.androidx.espresso.core)
+
+  implementation(project(":core"))
+  implementation(libs.gson)
+
+  implementation("com.taiyi.sdk.pulse:ble:1.1.0-alpha.02")
+  implementation("com.android.volley:volley:1.2.1")
+  implementation("com.opencsv:opencsv:5.6")
+  implementation("com.aliyun.dpa:oss-android-sdk:2.9.21")
+  implementation("com.jakewharton.rxbinding2:rxbinding:2.0.0")
+  implementation("io.reactivex.rxjava2:rxjava:2.1.7")
+  implementation("io.reactivex.rxjava2:rxandroid:2.0.1")
+  implementation("org.greenrobot:eventbus:3.3.1")
+  implementation("com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.41")
+}

+ 0 - 0
library/device/pulse/consumer-rules.pro


+ 21 - 0
library/device/pulse/proguard-rules.pro

@@ -0,0 +1,21 @@
+# 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

+ 24 - 0
library/device/pulse/src/androidTest/java/com/hzliuzhi/applet/device/pulse/ExampleInstrumentedTest.kt

@@ -0,0 +1,24 @@
+package com.hzliuzhi.applet.device.pulse
+
+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.hzliuzhi.applet.device.pulse.test", appContext.packageName)
+  }
+}

+ 4 - 0
library/device/pulse/src/main/AndroidManifest.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+</manifest>

+ 58 - 0
library/device/pulse/src/main/java/com/hzliuzhi/applet/device/pulse/PulseEventHandler.kt

@@ -0,0 +1,58 @@
+package com.hzliuzhi.applet.device.pulse
+
+import android.app.Activity
+import android.util.Log
+import com.google.gson.Gson
+import com.google.gson.JsonElement
+import com.hzliuzhi.applet.core.shared.Payload
+import com.hzliuzhi.applet.core.shared.SharedFlowHub
+import com.hzliuzhi.applet.core.shared.SharedFlowHub.callbackAs
+import com.hzliuzhi.applet.core.shared.SharedFlowHub.cast
+import com.hzliuzhi.applet.device.pulse.util.TaiYiUtil
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+class PulseEventHandle(private val activity: Activity, scope: CoroutineScope) {
+
+  private data class Pulse(
+    val userId: String?,
+  ) {
+    companion object {
+      fun formJson(element: JsonElement): Pulse {
+        return Gson().fromJson(element, Pulse::class.java)
+      }
+    }
+  }
+
+  init {
+    TaiYiUtil.init(activity.application)
+
+    SharedFlowHub.events
+      .filter { it.type.contains("pulse") }
+      .onEach { _event ->
+        when (_event.type) {
+
+          "${SharedFlowHub.WEBVIEW_BRIDGE_EVENT}:pulse" -> {
+            _event.cast<JsonElement, JsonElement>()?.also { event ->
+              val pulse = event.payload?.let { it -> Pulse.formJson(it) }
+              val callback = event.callback ?: {}
+              handlePulse(pulse) { payload ->
+                payload.copyWith(data = payload.data?.toMap()).toEvent()?.also { callback(it) }
+              }
+            }
+          }
+
+          else -> _event.callbackAs<Payload<Unit>>()?.invoke(Payload.error(message = "[pulse] 未实现 ${_event.type}"))
+        }
+      }
+      .launchIn(scope)
+  }
+
+  private fun handlePulse(pulse: Pulse?, callback: ((Payload<PulseResult?>) -> Unit)) {
+    if (pulse == null || pulse.userId.isNullOrEmpty()) Payload.error<PulseResult?>(message = "[pulse] 参数解析错误").also { callback(it) }
+    else TaiYiUtil.start(activity, "six:${pulse.userId}", callback)
+  }
+}
+

+ 95 - 0
library/device/pulse/src/main/java/com/hzliuzhi/applet/device/pulse/PulseResult.kt

@@ -0,0 +1,95 @@
+package com.hzliuzhi.applet.device.pulse
+
+import com.google.gson.Gson
+import com.google.gson.annotations.SerializedName
+import com.google.gson.stream.JsonReader
+import java.io.StringReader
+
+
+data class PulseResult(
+  @SerializedName("summary_desc")
+  val summaryLabel: SummaryLabel? = null,
+  @SerializedName("summary")
+  val summaryValue: SummaryValue? = null,
+  val time: String? = null,
+
+  val appId: String? = null,
+  val userId: String? = null,
+  val measureId: String? = null,
+  val url: String? = null,
+  val report: String = "",
+) {
+  data class SummaryLabel(
+    @SerializedName("summary")
+    val hands: List<String>? = emptyList(),
+    val left: Detail? = null,
+    val right: Detail? = null,
+  ) {
+    data class Detail(
+      val summary: List<String>? = emptyList(),
+      val guan: String? = null,
+      val chi: String? = null,
+      val cun: String? = null,
+    )
+  }
+
+  data class SummaryValue(
+    /* 弦 */
+    val xian: List<Float>? = emptyList(),
+    /* 软 */
+    val ruan: List<Float>? = emptyList(),
+
+    /* 浮 */
+    val fu: List<Float>? = emptyList(),
+    /* 沉 */
+    val chen: List<Float>? = emptyList(),
+
+    /* 滑 */
+    val hua: List<Float>? = emptyList(),
+    /* 细 */
+    val xi: List<Float>? = emptyList(),
+
+    /* 数 */
+    val shu: List<Float>? = emptyList(),
+    /* 迟 */
+    val chi: List<Float>? = emptyList(),
+
+    val kong: List<Float>? = emptyList(),
+    val shi: List<Float>? = emptyList(),
+  )
+
+
+  companion object {
+    fun fromJson(json: String) = fromJson(JsonReader(StringReader(json)))
+
+    private fun fromJson(reader: JsonReader) = try {
+      Gson().fromJson<PulseResult>(reader, PulseResult::class.java)
+    } catch (_: Throwable) {
+      null
+    }
+
+    fun toJson(result: PulseResult): String = try {
+      toMap(result).let { Gson().toJson(it) }
+    } catch (_: Throwable) {
+      ""
+    }
+
+    fun toMap(result: PulseResult): Map<String, Any?> {
+      val map = mutableMapOf<String, Any?>()
+      map["summaryLabel"] = result.summaryLabel
+      map["summaryValue"] = result.summaryValue
+
+      map["time"] = result.time
+
+      map["appId"] = result.appId
+      map["userId"] = result.userId
+      map["measureId"] = result.measureId
+      map["url"] = result.url
+      map["report"] = result.report
+      return map
+    }
+  }
+
+  fun toMap() = toMap(this)
+  override fun toString(): String = toJson(this)
+}

+ 36 - 0
library/device/pulse/src/main/java/com/hzliuzhi/applet/device/pulse/util/TaiYiResult.kt

@@ -0,0 +1,36 @@
+package com.hzliuzhi.applet.device.pulse.util
+
+import com.google.gson.Gson
+import com.google.gson.annotations.SerializedName
+import com.hzliuzhi.applet.device.pulse.PulseResult
+
+data class TaiYiResult(
+  val measureId: String? = null,
+  @SerializedName("createTime")
+  val time: String? = null,
+  @SerializedName("pulseUrl")
+  val url: String? = null,
+  @SerializedName("handStyle")
+  val hands: String?,
+) {
+  companion object {
+    fun fromJson(json: String) = try {
+      Gson().fromJson(json, TaiYiResult::class.java)
+    } catch (_: Throwable) {
+      null
+    }
+
+    fun toResult(json: String, measureId: String, report: String): PulseResult? {
+      val result = fromJson(json)
+      return PulseResult.fromJson(report)?.let {
+        it.copy(
+          report = report,
+          url = result?.url,
+
+          time = result?.time ?: it.time,
+          measureId = result?.measureId ?: it.measureId ?: measureId
+        )
+      }
+    }
+  }
+}

+ 55 - 0
library/device/pulse/src/main/java/com/hzliuzhi/applet/device/pulse/util/TaiYiUtil.kt

@@ -0,0 +1,55 @@
+package com.hzliuzhi.applet.device.pulse.util
+
+import android.app.Activity
+import android.app.Application
+import com.hzliuzhi.applet.core.shared.Payload
+import com.hzliuzhi.applet.core.util.proxy
+import com.hzliuzhi.applet.device.pulse.PulseResult
+import com.hzliuzhi.applet.device.pulse.R
+import com.taiyi.tyusbsdk.pulse.TaiyiConfig
+import com.taiyi.tyusbsdk.pulse.TaiyiManager
+import com.taiyi.tyusbsdk.pulse.net.HttpImpl
+import com.taiyi.zhimai.ui.activity.MeasureMainActivity
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+
+object TaiYiUtil {
+  fun init(application: Application) = init(application, null)
+  fun init(application: Application, config: TaiyiConfig?) = TaiyiManager.getInstance().init(
+    application,
+    config ?: application.applicationContext.let { context ->
+      TaiyiConfig.getDefault(context).apply {
+        val resources = context.resources
+        skipReport = resources.getBoolean(R.bool.taiyi_skip_report)
+        delayDisconnect = resources.getInteger(R.integer.taiyi_delay_disconnect)
+        this.proxy = resources.proxy(R.array.browser_proxy_pool)
+      }
+    },
+  )
+
+  fun start(activity: Activity, userId: String, callback: ((Payload<PulseResult?>) -> Unit)) {
+    TaiyiManager.getInstance().toMeasure(
+      userId, activity, MeasureMainActivity::class.java,
+      { callback(Payload.error(code = -10, message = "脉诊未完成")) },
+      { data, measureId, report ->
+        TaiYiResult.toResult(data, measureId, report)?.also { result -> callback(Payload.data(result, message = "脉诊已完成 ($measureId)")) }
+      }
+    )
+  }
+
+  fun getReportUrlSync(measureId: String) = TaiyiManager.getInstance().getUrl(measureId)
+  suspend fun getReportUrlUrl(measureId: String) {
+    suspendCancellableCoroutine { cont ->
+      TaiyiManager.getInstance().getAsyncUrl(measureId, object : HttpImpl<String> {
+        override fun showError(message: String?) {
+          cont.resumeWithException(Exception(message))
+        }
+
+        override fun showResponse(url: String) {
+          cont.resume(url)
+        }
+      })
+    }
+  }
+}

+ 16 - 0
library/device/pulse/src/main/res/values/taiyi.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <string name="taiyi_app_id">hJn5D3rr</string>
+  <string name="taiyi_app_secret">0f0d450226f316d668d6eb8bcfbb4acca4ccc47f</string>
+  <bool name="taiyi_skip_report">false</bool>
+  <integer name="taiyi_delay_disconnect">0</integer>
+
+  <string-array name="browser_proxy_pool">
+    <!-- <item>https://hybrid.reborn-tech.com -> </item> -->
+    <!-- <item>https://api.reborn-tech.com -> </item> -->
+    <!-- <item>https://taiyi.oss-accelerate.aliyuncs.com -> </item> -->
+    <!-- <item>https://taiyi.oss-cn-beijing.aliyuncs.com -> </item> -->
+    <!-- <item>https://oss-accelerate.aliyuncs.com/taiyi -> </item> -->
+    <!-- <item>https://oss-cn-beijing.aliyuncs.com/taiyi -> </item> -->
+  </string-array>
+</resources>

+ 17 - 0
library/device/pulse/src/test/java/com/hzliuzhi/applet/device/pulse/ExampleUnitTest.kt

@@ -0,0 +1,17 @@
+package com.hzliuzhi.applet.device.pulse
+
+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)
+  }
+}

BIN
local-repo/com/taiyi/sdk/pulse/ble/1.1.0-alpha.02/ble-1.1.0-alpha.02.aar


+ 30 - 0
local-repo/com/taiyi/sdk/pulse/ble/1.1.0-alpha.02/ble-1.1.0-alpha.02.pom

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>com.taiyi.sdk.pulse</groupId>
+  <artifactId>ble</artifactId>
+  <version>1.1.0-alpha.02</version>
+  <packaging>aar</packaging>
+  <name>ble</name>
+  <description>six.pulse:packag</description>
+  <dependencies>
+    <dependency>
+      <groupId>androidx.appcompat</groupId>
+      <artifactId>appcompat</artifactId>
+      <version>1.4.1</version>
+      <scope>implementation</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.android.material</groupId>
+      <artifactId>material</artifactId>
+      <version>1.5.0</version>
+      <scope>implementation</scope>
+    </dependency>
+    <dependency>
+      <groupId>androidx.constraintlayout</groupId>
+      <artifactId>constraintlayout</artifactId>
+      <version>2.1.3</version>
+      <scope>implementation</scope>
+    </dependency>
+  </dependencies>
+</project>

+ 13 - 0
local-repo/com/taiyi/sdk/pulse/ble/maven-metadata.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>com.taiyi.sdk.pulse</groupId>
+  <artifactId>ble</artifactId>
+  <versioning>
+    <latest>1.1.0-alpha.02</latest>
+    <release>1.1.0-alpha.02</release>
+    <versions>
+      <version>1.1.0-alpha.02</version>
+    </versions>
+    <lastUpdated>20250622165951</lastUpdated>
+  </versioning>
+</metadata>

+ 3 - 0
settings.gradle.kts

@@ -16,6 +16,8 @@ dependencyResolutionManagement {
   repositories {
     google()
     mavenCentral()
+    maven { url = uri("https://jitpack.io") }
+    maven { url = uri("${rootProject.projectDir}/local-repo") }
   }
 }
 
@@ -23,3 +25,4 @@ rootProject.name = "Six-applet.Container"
 include(":app")
 include(":core")
 include(":library:browser")
+include(":library:device:pulse")