commit 2017535f0f8bdfc01d816125d9068f658362e2e7
Author: BBIT-Kai <2911862937@qq.com>
Date: Mon May 25 15:06:08 2026 +0800
初始化项目
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9478034
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+.idea/
+app/release/
+app/debug/
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 0000000..a359457
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,198 @@
+import com.android.build.api.dsl.Packaging
+
+plugins {
+ alias(libs.plugins.android.application)
+ alias(libs.plugins.jetbrains.kotlin.android)
+ alias(libs.plugins.compose.compiler)
+ id("com.google.devtools.ksp")
+ id("com.google.dagger.hilt.android")
+ id("kotlin-kapt")
+}
+
+android {
+ namespace = "com.bbitcn.f8.pad"
+ compileSdk = 35
+
+ defaultConfig {
+ applicationId = "com.bbitcn.f8.pad"
+ minSdk = 30
+ targetSdk = 32
+ versionCode = 44
+ versionName = "1.0.44"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ vectorDrawables {
+ useSupportLibrary = true
+ }
+ ndk {
+ abiFilters.addAll(listOf("armeabi", "armeabi-v7a", "x86", "arm64-v8a"))
+ }
+ }
+
+ signingConfigs {
+// create("config") {
+// keyAlias = "key0"
+// keyPassword = "123456"
+// storeFile = file("../key/key.jks")
+// storePassword = "123456"
+// }
+ getByName("debug") {
+ keyAlias = "key0"
+ keyPassword = "123456"
+ storeFile = file("../key/key.jks")
+ storeType = "jks"
+ storePassword = "123456"
+ }
+ }
+ buildTypes {
+ release {
+// signingConfig = signingConfigs.getByName("config")
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+
+// composeCompiler {
+ // 强力跳过模式
+// enableStrongSkippingMode = true
+// }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_21
+ targetCompatibility = JavaVersion.VERSION_21
+ }
+ kotlinOptions {
+ jvmTarget = "21"
+ }
+ buildFeatures {
+ compose = true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.5.1"
+ }
+ packaging {
+ resources {
+ excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ }
+ }
+ sourceSets {
+ getByName("main") {
+ jniLibs.srcDirs("libs")
+ }
+ }
+ fun Packaging.() {
+ resources {
+ excludes += setOf("META-INF/DEPENDENCIES", "common.properties")
+ }
+ }
+}
+dependencies {
+ // 打印机驱动CPCL_SDK_V1
+ // 身份证驱动zkandroidcore
+ implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar"))))
+ // NFC需要
+// implementation("com.google.guava:guava:14.0")
+ implementation("com.google.guava:guava:30.1-jre")
+
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.lifecycle.runtime.ktx)
+ implementation(libs.androidx.activity.compose)
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.ui)
+ implementation(libs.androidx.ui.graphics)
+ implementation(libs.androidx.ui.tooling.preview)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+ androidTestImplementation(platform(libs.androidx.compose.bom))
+ androidTestImplementation(libs.androidx.ui.test.junit4)
+ debugImplementation(libs.androidx.ui.tooling)
+ debugImplementation(libs.androidx.ui.test.manifest)
+ implementation(libs.androidx.core.splashscreen)
+ //lifecycle-viewmodel-compose
+ implementation(libs.androidx.lifecycle.viewmodel.compose)
+ implementation(libs.androidx.material.icons.extended)
+ //MMKV
+ implementation(libs.mmkv)
+ //util
+ implementation(libs.com.blankj.utilcodex2)
+ //GSON
+ implementation(libs.com.google.code.gson.gson2)
+ //权限库
+ implementation(libs.com.github.getactivity.xxpermissions)
+ //日志库
+ implementation(libs.timber)
+ implementation(libs.androidx.legacy.legacy.support.v43)
+ implementation(libs.androidx.navigation.compose)
+ //日期下拉框
+ implementation(libs.android.pickerview)
+ implementation("io.github.ltttttttttttt:ComposeViews:1.6.0.1")
+ implementation("com.inuker.bluetooth:library:1.4.0")
+ //RxJava
+// implementation("io.reactivex.rxjava2:rxjava:2.2.20")
+// implementation("io.reactivex.rxjava2:rxandroid:2.1.1")
+// implementation("com.jakewharton.rxbinding2:rxbinding:2.2.0")
+// implementation("com.github.xuexiangjys:RxUtil2:1.2.1")
+ // MD3
+ implementation("androidx.compose.material3:material3:1.4.0-alpha12")
+ //
+ implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.5")
+ //gson
+ implementation("com.google.code.gson:gson:2.9.0")
+ implementation("com.squareup.retrofit2:retrofit:2.11.0")
+ implementation("com.squareup.retrofit2:converter-gson:2.11.0")
+ implementation("com.google.dagger:hilt-android:2.52")
+ kapt("com.google.dagger:hilt-compiler:2.52")
+ implementation("androidx.hilt:hilt-navigation-compose:1.0.0")
+ implementation("com.github.devnied.emvnfccard:library:3.0.1")
+ // 日历
+ implementation("com.xhinliang:LunarCalendar:4.0.7")
+ // 网络图片加载
+ implementation("io.coil-kt.coil3:coil-compose:3.0.4")
+ implementation("io.coil-kt.coil3:coil-network-okhttp:3.0.4")
+ // Paging3
+ val paging_version = "3.3.2"
+ implementation("androidx.paging:paging-runtime:$paging_version")
+ // optional - Jetpack Compose integration
+ implementation("androidx.paging:paging-compose:$paging_version")
+ // 阿里云接口
+ implementation("com.aliyun:ocr_api20210707:3.1.2")
+
+ // CameraX core library using the camera2 implementation
+ val camerax_version = "1.5.0-alpha04"
+ // The following line is optional, as the core library is included indirectly by camera-camera2
+ implementation("androidx.camera:camera-core:${camerax_version}")
+ implementation("androidx.camera:camera-compose:${camerax_version}")
+ implementation("androidx.camera:camera-camera2:${camerax_version}")
+ // If you want to additionally use the CameraX Lifecycle library
+ implementation("androidx.camera:camera-lifecycle:${camerax_version}")
+ // If you want to additionally use the CameraX VideoCapture library
+ implementation("androidx.camera:camera-video:${camerax_version}")
+ // If you want to additionally use the CameraX View class
+ implementation("androidx.camera:camera-view:${camerax_version}")
+ // If you want to additionally add CameraX ML Kit Vision Integration
+ implementation("androidx.camera:camera-mlkit-vision:${camerax_version}")
+ // If you want to additionally use the CameraX Extensions library
+ implementation("androidx.camera:camera-extensions:${camerax_version}")
+// implementation("androidx.camera.viewfinder:viewfinder-compose:${camerax_version}")
+// implementation("androidx.camera:camera-viewfinder-compose:1.0.0-alpha02")
+
+ // 串口-超高频读卡器
+ api("com.licheedev:android-serialport:2.1.3")
+ // 条形码
+ implementation("com.google.zxing:core:3.5.3")
+ // 人脸识别 7M
+ implementation("com.google.mlkit:face-detection:16.1.7")
+ // 阿里云OSS
+// implementation("com.aliyun.oss:aliyun-sdk-oss:3.18.1")
+ implementation("com.aliyun.dpa:oss-android-sdk:2.9.21")
+ // USB驱动
+ implementation("com.github.mik3y:usb-serial-for-android:3.8.1")
+ // 日期时间选择器
+ implementation("com.github.commandiron:WheelPickerCompose:1.1.11")
+ // 图表组件
+ implementation("io.github.ehsannarmani:compose-charts:0.1.2")
+
+}
\ No newline at end of file
diff --git a/app/libs/CPCL_SDK_V1.22.05.jar b/app/libs/CPCL_SDK_V1.22.05.jar
new file mode 100644
index 0000000..b6ad8f1
Binary files /dev/null and b/app/libs/CPCL_SDK_V1.22.05.jar differ
diff --git a/app/libs/Msc.jar b/app/libs/Msc.jar
new file mode 100644
index 0000000..d2757d6
Binary files /dev/null and b/app/libs/Msc.jar differ
diff --git a/app/libs/ScaleLib.aar b/app/libs/ScaleLib.aar
new file mode 100644
index 0000000..caaa2a4
Binary files /dev/null and b/app/libs/ScaleLib.aar differ
diff --git a/app/libs/adrcplib-1.1.jar b/app/libs/adrcplib-1.1.jar
new file mode 100644
index 0000000..d7c0584
Binary files /dev/null and b/app/libs/adrcplib-1.1.jar differ
diff --git a/app/libs/arm64-v8a/libLZO.so b/app/libs/arm64-v8a/libLZO.so
new file mode 100644
index 0000000..a8b7a72
Binary files /dev/null and b/app/libs/arm64-v8a/libLZO.so differ
diff --git a/app/libs/arm64-v8a/libmsc.so b/app/libs/arm64-v8a/libmsc.so
new file mode 100644
index 0000000..184d206
Binary files /dev/null and b/app/libs/arm64-v8a/libmsc.so differ
diff --git a/app/libs/arm64-v8a/libwlt2bmp.so b/app/libs/arm64-v8a/libwlt2bmp.so
new file mode 100644
index 0000000..24bf99b
Binary files /dev/null and b/app/libs/arm64-v8a/libwlt2bmp.so differ
diff --git a/app/libs/arm64-v8a/libzkserialport.so b/app/libs/arm64-v8a/libzkserialport.so
new file mode 100644
index 0000000..08f7511
Binary files /dev/null and b/app/libs/arm64-v8a/libzkserialport.so differ
diff --git a/app/libs/arm64-v8a/libzkwltdecode.so b/app/libs/arm64-v8a/libzkwltdecode.so
new file mode 100644
index 0000000..2a6b265
Binary files /dev/null and b/app/libs/arm64-v8a/libzkwltdecode.so differ
diff --git a/app/libs/arm64-v8a/libzyapi_common.so b/app/libs/arm64-v8a/libzyapi_common.so
new file mode 100644
index 0000000..d56aa63
Binary files /dev/null and b/app/libs/arm64-v8a/libzyapi_common.so differ
diff --git a/app/libs/armeabi-v7a/libLZO.so b/app/libs/armeabi-v7a/libLZO.so
new file mode 100644
index 0000000..ffd0aeb
Binary files /dev/null and b/app/libs/armeabi-v7a/libLZO.so differ
diff --git a/app/libs/armeabi-v7a/libmsc.so b/app/libs/armeabi-v7a/libmsc.so
new file mode 100644
index 0000000..84579e0
Binary files /dev/null and b/app/libs/armeabi-v7a/libmsc.so differ
diff --git a/app/libs/armeabi-v7a/libwlt2bmp.so b/app/libs/armeabi-v7a/libwlt2bmp.so
new file mode 100644
index 0000000..d36c600
Binary files /dev/null and b/app/libs/armeabi-v7a/libwlt2bmp.so differ
diff --git a/app/libs/armeabi-v7a/libzkserialport.so b/app/libs/armeabi-v7a/libzkserialport.so
new file mode 100644
index 0000000..a785cfc
Binary files /dev/null and b/app/libs/armeabi-v7a/libzkserialport.so differ
diff --git a/app/libs/armeabi-v7a/libzkwltdecode.so b/app/libs/armeabi-v7a/libzkwltdecode.so
new file mode 100644
index 0000000..a6c6543
Binary files /dev/null and b/app/libs/armeabi-v7a/libzkwltdecode.so differ
diff --git a/app/libs/armeabi-v7a/libzyapi_common.so b/app/libs/armeabi-v7a/libzyapi_common.so
new file mode 100644
index 0000000..5602e49
Binary files /dev/null and b/app/libs/armeabi-v7a/libzyapi_common.so differ
diff --git a/app/libs/armeabi/libLZO.so b/app/libs/armeabi/libLZO.so
new file mode 100644
index 0000000..53cee5a
Binary files /dev/null and b/app/libs/armeabi/libLZO.so differ
diff --git a/app/libs/autoreplyprint.aar b/app/libs/autoreplyprint.aar
new file mode 100644
index 0000000..9c44e32
Binary files /dev/null and b/app/libs/autoreplyprint.aar differ
diff --git a/app/libs/lzo_V1.0.jar b/app/libs/lzo_V1.0.jar
new file mode 100644
index 0000000..f438905
Binary files /dev/null and b/app/libs/lzo_V1.0.jar differ
diff --git a/app/libs/mips/libLZO.so b/app/libs/mips/libLZO.so
new file mode 100644
index 0000000..02c3838
Binary files /dev/null and b/app/libs/mips/libLZO.so differ
diff --git a/app/libs/mips64/libLZO.so b/app/libs/mips64/libLZO.so
new file mode 100644
index 0000000..2896f0b
Binary files /dev/null and b/app/libs/mips64/libLZO.so differ
diff --git a/app/libs/myRfid.jar b/app/libs/myRfid.jar
new file mode 100644
index 0000000..f5a037b
Binary files /dev/null and b/app/libs/myRfid.jar differ
diff --git a/app/libs/my_adsiolib-1.51.jar b/app/libs/my_adsiolib-1.51.jar
new file mode 100644
index 0000000..2059860
Binary files /dev/null and b/app/libs/my_adsiolib-1.51.jar differ
diff --git a/app/libs/qhlog-1.0.2.aar b/app/libs/qhlog-1.0.2.aar
new file mode 100644
index 0000000..29eb9d8
Binary files /dev/null and b/app/libs/qhlog-1.0.2.aar differ
diff --git a/app/libs/rfid18 b/app/libs/rfid18
new file mode 100644
index 0000000..32a1cd4
Binary files /dev/null and b/app/libs/rfid18 differ
diff --git a/app/libs/x86/libLZO.so b/app/libs/x86/libLZO.so
new file mode 100644
index 0000000..4f13511
Binary files /dev/null and b/app/libs/x86/libLZO.so differ
diff --git a/app/libs/x86_64/libLZO.so b/app/libs/x86_64/libLZO.so
new file mode 100644
index 0000000..c8abbd3
Binary files /dev/null and b/app/libs/x86_64/libLZO.so differ
diff --git a/app/libs/zkandroidcore.jar b/app/libs/zkandroidcore.jar
new file mode 100644
index 0000000..df507bf
Binary files /dev/null and b/app/libs/zkandroidcore.jar differ
diff --git a/app/libs/zkandroididcardreader.jar b/app/libs/zkandroididcardreader.jar
new file mode 100644
index 0000000..17bc9de
Binary files /dev/null and b/app/libs/zkandroididcardreader.jar differ
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/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
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/bbitcn/f8/pad/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/bbitcn/f8/pad/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..ebb1e69
--- /dev/null
+++ b/app/src/androidTest/java/com/bbitcn/f8/pad/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.bbitcn.f8.pad
+
+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.bbitcn.f8.pad", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..06299e0
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/assets/RasterImage/barcode.bmp b/app/src/main/assets/RasterImage/barcode.bmp
new file mode 100644
index 0000000..1445d9e
Binary files /dev/null and b/app/src/main/assets/RasterImage/barcode.bmp differ
diff --git a/app/src/main/assets/RasterImage/blackwhite.png b/app/src/main/assets/RasterImage/blackwhite.png
new file mode 100644
index 0000000..c82faca
Binary files /dev/null and b/app/src/main/assets/RasterImage/blackwhite.png differ
diff --git a/app/src/main/assets/RasterImage/iu.jpeg b/app/src/main/assets/RasterImage/iu.jpeg
new file mode 100644
index 0000000..fddb552
Binary files /dev/null and b/app/src/main/assets/RasterImage/iu.jpeg differ
diff --git a/app/src/main/assets/RasterImage/yellowmen.png b/app/src/main/assets/RasterImage/yellowmen.png
new file mode 100644
index 0000000..90601cf
Binary files /dev/null and b/app/src/main/assets/RasterImage/yellowmen.png differ
diff --git a/app/src/main/java/android/zyapi/CommonApi.java b/app/src/main/java/android/zyapi/CommonApi.java
new file mode 100644
index 0000000..1685cd8
--- /dev/null
+++ b/app/src/main/java/android/zyapi/CommonApi.java
@@ -0,0 +1,30 @@
+package android.zyapi;
+
+
+public class CommonApi {
+
+ private static CommonApi mMe = null;
+
+ public CommonApi() {
+ }
+
+ // gpio
+ public native int setGpioMode(int pin, int mode);
+ public native int setGpioDir(int pin, int dir);
+ public native int setGpioPullEnable(int pin, int enable);
+ public native int setGpioPullSelect(int pin, int select);
+ public native int setGpioOut(int pin, int out);
+ public native int getGpioIn(int pin);
+ //serialport
+ public native int openCom(String port, int baudrate, int bits, char event, int stop);
+ public native int openComEx(String port, int baudrate, int bits, char event, int stop, int flags);
+ public native int writeCom(int fd, byte[] buf, int sizes);
+ public native int readCom(int fd, byte[] buf, int sizes);
+ public native int readComEx(int fd, byte[] buf, int sizes, int sec, int usec);
+ public native void closeCom(int fd);
+
+ static {
+ System.loadLibrary("zyapi_common");
+ }
+
+}
diff --git a/app/src/main/java/android_serialport_api/NFC/NdefMessageParser.java b/app/src/main/java/android_serialport_api/NFC/NdefMessageParser.java
new file mode 100644
index 0000000..1137fda
--- /dev/null
+++ b/app/src/main/java/android_serialport_api/NFC/NdefMessageParser.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android_serialport_api.NFC;
+
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+
+/**
+ * Utility class for creating {@link ParsedNdefMessage}s.
+ */
+public class NdefMessageParser {
+
+ // Utility class
+ private NdefMessageParser() {
+
+ }
+
+ /** Parse an NdefMessage */
+ public static List parse(NdefMessage message) {
+ return getRecords(message.getRecords());
+ }
+
+ public static List getRecords(NdefRecord[] records) {
+ List elements = new ArrayList();
+ for (final NdefRecord record : records) {
+ if (UriRecord.isUri(record)) {
+ elements.add(UriRecord.parse(record));
+ } else if (TextRecord.isText(record)) {
+ elements.add(TextRecord.parse(record));
+ } else if (SmartPoster.isPoster(record)) {
+ elements.add(SmartPoster.parse(record));
+ } else {
+// String temp ="";
+// temp = new String(record.getPayload());
+// temp = bytesToHexString(record.getPayload(),true);
+ elements.add(new ParsedNdefRecord() {
+ @Override
+ public String getViewText() {
+ String temp = new String(record.getPayload()) + "\n";
+ return temp;
+ }
+ });
+ }
+ }
+ return elements;
+ }
+
+ private static String hexString="0123456789ABCDEF";
+ public static String decode(String bytes) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(
+ bytes.length() / 2);
+ for (int i = 0; i < bytes.length(); i += 2)
+ baos.write((hexString.indexOf(bytes.charAt(i)) << 4 | hexString
+ .indexOf(bytes.charAt(i + 1))));
+ return new String(baos.toByteArray());
+ }
+
+
+ private static String bytesToHexString(byte[] src, boolean isPrefix) {
+ StringBuilder stringBuilder = new StringBuilder();
+ if (isPrefix == true) {
+ stringBuilder.append("0x");
+ }
+ if (src == null || src.length <= 0) {
+ return null;
+ }
+ char[] buffer = new char[2];
+ for (int i = 0; i < src.length; i++) {
+ buffer[0] = Character.toUpperCase(Character.forDigit(
+ (src[i] >>> 4) & 0x0F, 16));
+ buffer[1] = Character.toUpperCase(Character.forDigit(src[i] & 0x0F,
+ 16));
+ System.out.println(buffer);
+ stringBuilder.append(buffer);
+ }
+ return stringBuilder.toString();
+ }
+}
diff --git a/app/src/main/java/android_serialport_api/NFC/ParsedNdefRecord.java b/app/src/main/java/android_serialport_api/NFC/ParsedNdefRecord.java
new file mode 100644
index 0000000..e72128e
--- /dev/null
+++ b/app/src/main/java/android_serialport_api/NFC/ParsedNdefRecord.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android_serialport_api.NFC;
+
+public interface ParsedNdefRecord {
+
+ /**
+ * Returns a view to display this record.
+ */
+ public String getViewText();
+
+}
diff --git a/app/src/main/java/android_serialport_api/NFC/SmartPoster.java b/app/src/main/java/android_serialport_api/NFC/SmartPoster.java
new file mode 100644
index 0000000..0c98b09
--- /dev/null
+++ b/app/src/main/java/android_serialport_api/NFC/SmartPoster.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android_serialport_api.NFC;
+
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+
+import android.nfc.FormatException;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+/**
+ * A representation of an NFC Forum "Smart Poster".
+ */
+public class SmartPoster implements ParsedNdefRecord {
+
+ /**
+ * NFC Forum Smart Poster Record Type Definition section 3.2.1.
+ *
+ * "The Title record for the service (there can be many of these in
+ * different languages, but a language MUST NOT be repeated). This record is
+ * optional."
+ */
+ private final TextRecord mTitleRecord;
+
+ /**
+ * NFC Forum Smart Poster Record Type Definition section 3.2.1.
+ *
+ * "The URI record. This is the core of the Smart Poster, and all other
+ * records are just metadata about this record. There MUST be one URI record
+ * and there MUST NOT be more than one."
+ */
+ private final UriRecord mUriRecord;
+
+ /**
+ * NFC Forum Smart Poster Record Type Definition section 3.2.1.
+ *
+ * "The Action record. This record describes how the service should be
+ * treated. For example, the action may indicate that the device should save
+ * the URI as a bookmark or open a browser. The Action record is optional.
+ * If it does not exist, the device may decide what to do with the service.
+ * If the action record exists, it should be treated as a strong suggestion;
+ * the UI designer may ignore it, but doing so will induce a different user
+ * experience from device to device."
+ */
+ private final RecommendedAction mAction;
+
+ /**
+ * NFC Forum Smart Poster Record Type Definition section 3.2.1.
+ *
+ * "The Type record. If the URI references an external entity (e.g., via a
+ * URL), the Type record may be used to declare the MIME type of the entity.
+ * This can be used to tell the mobile device what kind of an object it can
+ * expect before it opens the connection. The Type record is optional."
+ */
+ private final String mType;
+
+ private SmartPoster(UriRecord uri, TextRecord title, RecommendedAction action, String type) {
+ mUriRecord = Preconditions.checkNotNull(uri);
+ mTitleRecord = title;
+ mAction = Preconditions.checkNotNull(action);
+ mType = type;
+ }
+
+ public UriRecord getUriRecord() {
+ return mUriRecord;
+ }
+
+ /**
+ * Returns the title of the smart poster. This may be {@code null}.
+ */
+ public TextRecord getTitle() {
+ return mTitleRecord;
+ }
+
+ public static SmartPoster parse(NdefRecord record) {
+ Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN);
+ Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_SMART_POSTER));
+ try {
+ NdefMessage subRecords = new NdefMessage(record.getPayload());
+ return parse(subRecords.getRecords());
+ } catch (FormatException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public static SmartPoster parse(NdefRecord[] recordsRaw) {
+ try {
+ Iterable records = NdefMessageParser.getRecords(recordsRaw);
+ UriRecord uri = Iterables.getOnlyElement(Iterables.filter(records, UriRecord.class));
+ TextRecord title = getFirstIfExists(records, TextRecord.class);
+ RecommendedAction action = parseRecommendedAction(recordsRaw);
+ String type = parseType(recordsRaw);
+ return new SmartPoster(uri, title, action, type);
+ } catch (NoSuchElementException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public static boolean isPoster(NdefRecord record) {
+ try {
+ parse(record);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Returns the first element of {@code elements} which is an instance of
+ * {@code type}, or {@code null} if no such element exists.
+ */
+ private static T getFirstIfExists(Iterable> elements, Class type) {
+ Iterable filtered = Iterables.filter(elements, type);
+ T instance = null;
+ if (!Iterables.isEmpty(filtered)) {
+ instance = Iterables.get(filtered, 0);
+ }
+ return instance;
+ }
+
+ private enum RecommendedAction {
+ UNKNOWN((byte) -1), DO_ACTION((byte) 0), SAVE_FOR_LATER((byte) 1), OPEN_FOR_EDITING(
+ (byte) 2);
+
+ private static final ImmutableMap LOOKUP;
+ static {
+ ImmutableMap.Builder builder = ImmutableMap.builder();
+ for (RecommendedAction action : RecommendedAction.values()) {
+ builder.put(action.getByte(), action);
+ }
+ LOOKUP = builder.build();
+ }
+
+ private final byte mAction;
+
+ private RecommendedAction(byte val) {
+ this.mAction = val;
+ }
+
+ private byte getByte() {
+ return mAction;
+ }
+ }
+
+ private static NdefRecord getByType(byte[] type, NdefRecord[] records) {
+ for (NdefRecord record : records) {
+ if (Arrays.equals(type, record.getType())) {
+ return record;
+ }
+ }
+ return null;
+ }
+
+ private static final byte[] ACTION_RECORD_TYPE = new byte[] {'a', 'c', 't'};
+
+ private static RecommendedAction parseRecommendedAction(NdefRecord[] records) {
+ NdefRecord record = getByType(ACTION_RECORD_TYPE, records);
+ if (record == null) {
+ return RecommendedAction.UNKNOWN;
+ }
+ byte action = record.getPayload()[0];
+ if (RecommendedAction.LOOKUP.containsKey(action)) {
+ return RecommendedAction.LOOKUP.get(action);
+ }
+ return RecommendedAction.UNKNOWN;
+ }
+
+ private static final byte[] TYPE_TYPE = new byte[] {'t'};
+
+ private static String parseType(NdefRecord[] records) {
+ NdefRecord type = getByType(TYPE_TYPE, records);
+ if (type == null) {
+ return null;
+ }
+ return new String(type.getPayload(), Charsets.UTF_8);
+ }
+
+ @Override
+ public String getViewText() {
+ if (mTitleRecord != null) {
+ return mTitleRecord.getText();
+ }
+ return mTitleRecord.getText();
+ }
+}
diff --git a/app/src/main/java/android_serialport_api/NFC/TextRecord.java b/app/src/main/java/android_serialport_api/NFC/TextRecord.java
new file mode 100644
index 0000000..9c84e58
--- /dev/null
+++ b/app/src/main/java/android_serialport_api/NFC/TextRecord.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android_serialport_api.NFC;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+import android.app.Activity;
+import android.nfc.NdefRecord;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * An NFC Text Record
+ */
+public class TextRecord implements ParsedNdefRecord{
+
+ /** ISO/IANA language code */
+ private final String mLanguageCode;
+
+ private final String mText;
+
+ private TextRecord(String languageCode, String text) {
+ mLanguageCode = Preconditions.checkNotNull(languageCode);
+ mText = Preconditions.checkNotNull(text);
+ }
+
+ @Override
+ public String getViewText() {
+ return mText;
+ }
+
+ public String getText() {
+ return mText;
+ }
+
+ /**
+ * Returns the ISO/IANA language code associated with this text element.
+ */
+ public String getLanguageCode() {
+ return mLanguageCode;
+ }
+
+ // TODO: deal with text fields which span multiple NdefRecords
+ public static TextRecord parse(NdefRecord record) {
+ Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN);
+ Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_TEXT));
+ try {
+ byte[] payload = record.getPayload();
+ /*
+ * payload[0] contains the "Status Byte Encodings" field, per the
+ * NFC Forum "Text Record Type Definition" section 3.2.1.
+ *
+ * bit7 is the Text Encoding Field.
+ *
+ * if (Bit_7 == 0): The text is encoded in UTF-8 if (Bit_7 == 1):
+ * The text is encoded in UTF16
+ *
+ * Bit_6 is reserved for future use and must be set to zero.
+ *
+ * Bits 5 to 0 are the length of the IANA language code.
+ */
+
+ if (payload.length <= 0) {
+ payload = record.getType();
+ return null;
+ }
+ String textEncoding = ((payload[0] & 0200) == 0) ? "UTF-8" : "UTF-16";
+ int languageCodeLength = payload[0] & 0077;
+ String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII");
+ String text =
+ new String(payload, languageCodeLength + 1,
+ payload.length - languageCodeLength - 1, textEncoding);
+ return new TextRecord(languageCode, text);
+ } catch (UnsupportedEncodingException e) {
+ // should never happen unless we get a malformed tag.
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public static boolean isText(NdefRecord record) {
+ try {
+ parse(record);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+}
diff --git a/app/src/main/java/android_serialport_api/NFC/UriRecord.java b/app/src/main/java/android_serialport_api/NFC/UriRecord.java
new file mode 100644
index 0000000..1e53dc8
--- /dev/null
+++ b/app/src/main/java/android_serialport_api/NFC/UriRecord.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android_serialport_api.NFC;
+
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import android.net.Uri;
+import android.nfc.NdefRecord;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.primitives.Bytes;
+
+/**
+ * A parsed record containing a Uri.
+ */
+public class UriRecord implements ParsedNdefRecord {
+
+ private static final String TAG = "UriRecord";
+
+ public static final String RECORD_TYPE = "UriRecord";
+
+ /**
+ * NFC Forum "URI Record Type Definition"
+ *
+ * This is a mapping of "URI Identifier Codes" to URI string prefixes,
+ * per section 3.2.2 of the NFC Forum URI Record Type Definition document.
+ */
+ private static final BiMap URI_PREFIX_MAP = ImmutableBiMap.builder()
+ .put((byte) 0x00, "")
+ .put((byte) 0x01, "http://www.")
+ .put((byte) 0x02, "https://www.")
+ .put((byte) 0x03, "http://")
+ .put((byte) 0x04, "https://")
+ .put((byte) 0x05, "tel:")
+ .put((byte) 0x06, "mailto:")
+ .put((byte) 0x07, "ftp://anonymous:anonymous@")
+ .put((byte) 0x08, "ftp://ftp.")
+ .put((byte) 0x09, "ftps://")
+ .put((byte) 0x0A, "sftp://")
+ .put((byte) 0x0B, "smb://")
+ .put((byte) 0x0C, "nfs://")
+ .put((byte) 0x0D, "ftp://")
+ .put((byte) 0x0E, "dav://")
+ .put((byte) 0x0F, "news:")
+ .put((byte) 0x10, "telnet://")
+ .put((byte) 0x11, "imap:")
+ .put((byte) 0x12, "rtsp://")
+ .put((byte) 0x13, "urn:")
+ .put((byte) 0x14, "pop:")
+ .put((byte) 0x15, "sip:")
+ .put((byte) 0x16, "sips:")
+ .put((byte) 0x17, "tftp:")
+ .put((byte) 0x18, "btspp://")
+ .put((byte) 0x19, "btl2cap://")
+ .put((byte) 0x1A, "btgoep://")
+ .put((byte) 0x1B, "tcpobex://")
+ .put((byte) 0x1C, "irdaobex://")
+ .put((byte) 0x1D, "file://")
+ .put((byte) 0x1E, "urn:epc:id:")
+ .put((byte) 0x1F, "urn:epc:tag:")
+ .put((byte) 0x20, "urn:epc:pat:")
+ .put((byte) 0x21, "urn:epc:raw:")
+ .put((byte) 0x22, "urn:epc:")
+ .put((byte) 0x23, "urn:nfc:")
+ .build();
+
+ private final Uri mUri;
+
+ private UriRecord(Uri uri) {
+ this.mUri = Preconditions.checkNotNull(uri);
+ }
+
+ public String getViewText(){
+ return mUri.toString();
+ }
+
+ public Uri getUri() {
+ return mUri;
+ }
+
+ /**
+ * Convert {@link android.nfc.NdefRecord} into a {@link android.net.Uri}.
+ * This will handle both TNF_WELL_KNOWN / RTD_URI and TNF_ABSOLUTE_URI.
+ *
+ * @throws IllegalArgumentException if the NdefRecord is not a record
+ * containing a URI.
+ */
+ public static UriRecord parse(NdefRecord record) {
+ short tnf = record.getTnf();
+ if (tnf == NdefRecord.TNF_WELL_KNOWN) {
+ return parseWellKnown(record);
+ } else if (tnf == NdefRecord.TNF_ABSOLUTE_URI) {
+ return parseAbsolute(record);
+ }
+ throw new IllegalArgumentException("Unknown TNF " + tnf);
+ }
+
+ /** Parse and absolute URI record */
+ private static UriRecord parseAbsolute(NdefRecord record) {
+ byte[] payload = record.getPayload();
+ Uri uri = Uri.parse(new String(payload, Charset.forName("UTF-8")));
+ return new UriRecord(uri);
+ }
+
+ /** Parse an well known URI record */
+ private static UriRecord parseWellKnown(NdefRecord record) {
+ Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_URI));
+ byte[] payload = record.getPayload();
+ /*
+ * payload[0] contains the URI Identifier Code, per the
+ * NFC Forum "URI Record Type Definition" section 3.2.2.
+ *
+ * payload[1]...payload[payload.length - 1] contains the rest of
+ * the URI.
+ */
+ String prefix = URI_PREFIX_MAP.get(payload[0]);
+ byte[] fullUri =
+ Bytes.concat(prefix.getBytes(Charset.forName("UTF-8")), Arrays.copyOfRange(payload, 1,
+ payload.length));
+ Uri uri = Uri.parse(new String(fullUri, Charset.forName("UTF-8")));
+ return new UriRecord(uri);
+ }
+
+ public static boolean isUri(NdefRecord record) {
+ try {
+ parse(record);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ private static final byte[] EMPTY = new byte[0];
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/MyApp.kt b/app/src/main/java/com/bbitcn/f8/pad/MyApp.kt
new file mode 100644
index 0000000..c11aaeb
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/MyApp.kt
@@ -0,0 +1,79 @@
+package com.bbitcn.f8.pad
+
+import android.app.Application
+import android.content.Context
+import android.provider.Settings
+import androidx.compose.animation.animateContentSize
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.ui.Modifier
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.TTSManager
+import com.bbitcn.f8.pad.utils.global.Global
+import com.bbitcn.f8.pad.utils.log.CrashHandlerUtil
+import com.bbitcn.f8.pad.utils.log.MyLog
+import com.blankj.utilcode.util.ActivityUtils
+import com.iflytek.cloud.SpeechConstant
+import com.iflytek.cloud.SpeechUtility
+
+import org.xutils.x
+import timber.log.Timber
+
+
+/**
+ * @Description APPLICATION类
+ * @Author DuanKaiji
+ * @CreateTime 2024年03月27日 13:43
+ */
+val M = Modifier
+ .animateContentSize(
+ animationSpec = spring(
+ dampingRatio = Spring.DampingRatioLowBouncy,
+ stiffness = Spring.StiffnessLow
+ )
+ )
+val MD = Modifier
+
+val IS_DEBUG_DRYCOCOON = false // 是否是调试状态
+
+
+class MyApp : Application() {
+ override fun onCreate() {
+ super.onCreate()
+ // 初始化MMKV
+ MMKVUtil.init(applicationContext)
+ // 初始化崩溃捕捉
+ CrashHandlerUtil.init()
+ // 初始化日志库
+ Timber.plant(MyLog())
+ // 初始化网络请求库
+ x.Ext.init(this)
+ // 初始化全局变量
+ MMKVUtil.put(Global.DEVICE_ID, Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID))
+ // 初始化讯飞语音
+ SpeechUtility.createUtility(applicationContext, SpeechConstant.APPID +"=5d0fed03")
+ // 初始化文本转语音
+ TTSManager.init(applicationContext)
+
+ MyLog.test("设备唯一码:${Global.getDeviceId()}")
+
+ // 在开发阶段启用 StrictMode
+// if (BuildConfig.DEBUG) {
+// val policy = StrictMode.ThreadPolicy.Builder()
+// .detectDiskReads()
+// .detectDiskWrites()
+// .detectNetwork()
+// .penaltyLog() // 记录到 Logcat
+// .build()
+//
+// StrictMode.setThreadPolicy(policy)
+// }
+ }
+
+ companion object {
+ @JvmStatic
+ val appContext: Context
+ get() = ActivityUtils.getTopActivity()
+
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/base/BaseDialogFrame.kt b/app/src/main/java/com/bbitcn/f8/pad/base/BaseDialogFrame.kt
new file mode 100644
index 0000000..b063d2d
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/base/BaseDialogFrame.kt
@@ -0,0 +1,164 @@
+package com.bbitcn.f8.pad.base
+
+import android.app.Activity
+import android.os.Build
+import android.view.WindowInsetsController
+import androidx.annotation.RequiresApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.max
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.zIndex
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowInsetsControllerCompat
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.MyApp
+import com.blankj.utilcode.util.ActivityUtils
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun BaseDialogPreview() {
+ MyDialog(
+ title = "关于我们",
+ showDialog = true,
+ onDismissRequest = {},
+ content = {
+ Column {
+ Text(
+ text = "奥立集团旗下四川主干信息技术有限公司,是一家专业从事搭建互联网云平台," +
+ "服务传统行业的互联网企业,主营业务包括软件产品设计与研发、系统集成与维护" +
+ "、互联网数据挖掘及应用。公司秉承“行业互助,资源共享、助小众企业实现大众信息化" +
+ "、助大众企业实现全球化”的企业宗旨,以“专业、创新、服务、共赢”为核心价值观," +
+ "为客户提供全方位的信息化解决方案。",
+ color = Color.Black
+ )
+ }
+ }, onClickOK = {
+ }
+ )
+}
+
+@Composable
+fun MyDialog(
+ title: String = "",
+ showDialog: Boolean,
+ onDismissRequest: () -> Unit,
+ clickOKStr: String = "",
+ onClickOK: () -> Unit = {},
+ content: @Composable () -> Unit,
+) {
+ MyAnimatedVisibility(showDialog) {
+ Box(
+ modifier = M
+ .fillMaxSize()
+ .background(Color(0x99000000))
+ .noVisualFeedbackClickable {
+// onDismissRequest()
+ },
+ contentAlignment = Alignment.Center
+ ) {
+ BaseDialogFrame(title, {
+ content()
+ }) {
+ Row(modifier = M.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
+ BigButton(
+ "返回", modifier = M
+ .weight(1f)
+ .widthIn(max = 100.dp)
+ ) {
+ onDismissRequest()
+ }
+ // 判断是否有点击事件
+ if (clickOKStr != "") {
+ Spacer(modifier = M.width(30.dp))
+ BigButton(clickOKStr, modifier = M.weight(1f), true) {
+ onClickOK()
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun BaseDialogFrame(
+ title: String = "",
+ content: @Composable () -> Unit = {},
+ bottomContent: @Composable () -> Unit = {}
+) {
+ MyCard(
+ modifier = M
+ .padding(top = 10.dp, bottom = 50.dp, start = 100.dp, end = 100.dp)
+ .noVisualFeedbackClickable { }
+ .fillMaxSize()
+ ) {
+ Column(
+ modifier = M
+ .fillMaxSize()
+ .padding(horizontal = 30.dp)
+ ) {
+ Row(
+ modifier = M
+ .padding(top = 20.dp, bottom = 5.dp)
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ // 一个绿色的竖线
+ Box(
+ modifier = M
+ .width(3.dp)
+ .height(40.dp)
+ .padding(end = 10.dp)
+ .background(Color(0xFF209344))
+ )
+ Text(
+ text = title,
+ fontSize = MaterialTheme.typography.titleLarge.fontSize,
+ )
+ }
+ Box(
+ modifier = M
+ .weight(1f)
+ .fillMaxWidth(),
+ contentAlignment = Alignment.TopCenter
+ ) {
+ content()
+ }
+ Column(
+ modifier = M
+ .fillMaxWidth()
+ .padding(top = 10.dp, bottom = 20.dp), // 调整底部间距
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center
+ ) {
+ bottomContent()
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/base/BaseList.kt b/app/src/main/java/com/bbitcn/f8/pad/base/BaseList.kt
new file mode 100644
index 0000000..8a741dc
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/base/BaseList.kt
@@ -0,0 +1,751 @@
+package com.bbitcn.f8.pad.base
+
+import android.R.attr.onClick
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.animateContentSize
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material3.Text
+import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
+import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
+import androidx.compose.runtime.mutableStateOf
+
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ProgressIndicatorDefaults
+import androidx.compose.material3.VerticalDivider
+import androidx.compose.material3.pulltorefresh.PullToRefreshBox
+import androidx.compose.material3.pulltorefresh.PullToRefreshState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Alignment.Companion.CenterVertically
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.paging.LoadState
+import androidx.paging.compose.LazyPagingItems
+import androidx.paging.compose.itemKey
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.MD
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.cyberecho.ui.screen.JumpToBottomThreshold
+import com.cyberecho.ui.view.JumpToBottom
+import com.cyberecho.ui.view.JumpToTop
+import com.google.common.collect.Multimaps.index
+import kotlinx.coroutines.launch
+import org.slf4j.helpers.Reporter.info
+import kotlin.collections.map
+
+@Composable
+fun TableHeadLine(modifier: Modifier, list: List>) {
+ MyCard(elevation = 0.dp, colors = MyColors.BlueGreen, radius = 5.dp) {
+ Row(
+ modifier = modifier,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ list.forEach {
+ if (it.first == "|") {
+ VerticalDivider(
+ color = MyColors.Gray,
+ thickness = 1.dp,
+ modifier = M
+ .height(25.dp)
+ .padding(horizontal = 5.dp)
+ )
+ } else {
+ Text(
+ text = it.first,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M
+ .weight(it.second.toFloat())
+ .padding(vertical = 7.dp),
+ fontWeight = FontWeight.Bold,
+ color = MyColors.White,
+ textAlign = TextAlign.Center,
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun TableHeadLineCard(modifier: Modifier, list: List>, isSelect: Boolean) {
+ MyCard(
+ elevation = 0.dp,
+ radius = 0.dp,
+ colors = if (isSelect) MyColors.BlueGreen else MyColors.White
+ ) {
+ Row(
+ modifier = modifier,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ list.forEach {
+ Text(
+ text = it.first,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M
+ .weight(it.second.toFloat())
+ .padding(vertical = 5.dp),
+ fontWeight = FontWeight.Bold,
+ color = if (isSelect) MyColors.White else MyColors.Black,
+ textAlign = TextAlign.Center,
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun TableContent(
+ modifier: Modifier,
+ backgroundDeepColor: Boolean,
+ list: List>,
+ verticalPadding: Dp = 5.dp,
+ onLongClick: () -> Unit = {},
+ onClick: () -> Unit = {}
+) {
+ Card(
+ modifier = modifier.pointerInput(Unit) {
+ detectTapGestures(
+ onLongPress = {
+ onLongClick()
+ },
+ onTap = {
+ onClick()
+ }
+ )
+ },
+ colors = CardDefaults.cardColors(
+ containerColor = if (backgroundDeepColor) Color(0xFFF3F7FD) else Color.White
+ )
+ ) {
+ Row(
+ modifier = M.padding(vertical = verticalPadding),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ list.forEach {
+ if (it.first == "|") {
+ VerticalDivider(
+ color = MyColors.Gray,
+ thickness = 1.dp,
+ modifier = M
+ .height(10.dp)
+ .padding(horizontal = 5.dp)
+ )
+ } else {
+ Text(
+ text = it.first,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.weight(it.second.toFloat()),
+ textAlign = TextAlign.Center,
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun MyTable(
+ modifier: Modifier,
+ headerStrings: List,
+ ratio: List,
+ items: List>,
+ verticalPadding: Dp = 5.dp,
+) {
+ Column(modifier = modifier) {
+ TableHeadLine(
+ modifier = M.fillMaxWidth(),
+ list = headerStrings.zip(ratio.map { it.toInt() })
+ )
+ LazyColumn {
+ items(count = items.size) { itIndex ->
+ TableContent(
+ verticalPadding = verticalPadding,
+ modifier = M
+ .fillMaxWidth()
+ .animateItem(),
+ backgroundDeepColor = itIndex % 2 == 0,
+ list = items[itIndex].map { it.toString() }.zip(ratio.map { it.toInt() }),
+ )
+ }
+ }
+ }
+}
+
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MyRefreshTableForCard(
+ modifier: Modifier,
+ isRefreshing: Boolean,
+ info: LazyPagingItems,
+ key: (T) -> Any,
+ onFinishRefresh: () -> Unit,
+
+ columns: Int,// 列数
+ item: @Composable (T, index: Int) -> Unit,
+) {
+ val state = rememberPullToRefreshState()
+ val pagerSate = info.loadState.append
+ LaunchedEffect(pagerSate) {
+ if (pagerSate is LoadState.NotLoading) {
+ onFinishRefresh()
+ }
+ }
+ Column(modifier = modifier) {
+ PullToRefreshBox(
+ modifier = M.fillMaxSize(),
+ state = state,
+ isRefreshing = isRefreshing,
+ onRefresh = {
+ info.refresh()
+ },
+ indicator = {
+ PullToRefreshDefaults.IndicatorBox(
+ state = state,
+ isRefreshing = isRefreshing,
+ modifier = M.align(Alignment.TopCenter),
+ elevation = 0.dp
+ ) {
+ if (isRefreshing) {
+ CircularProgressIndicator(modifier = M.size(30.dp))
+ } else {
+ CircularProgressIndicator(
+ modifier = M.size(30.dp),
+ progress = { state.distanceFraction },
+ trackColor = ProgressIndicatorDefaults.circularIndeterminateTrackColor,
+ )
+ }
+ }
+ }
+ ) {
+ LazyVerticalGrid(
+ modifier = M.fillMaxSize(),
+ columns = GridCells.Fixed(columns),
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
+ verticalArrangement = Arrangement.spacedBy(10.dp)
+ ) {
+ items(count = info.itemCount, key = info.itemKey { key(it) }) { index ->
+ val lineItem = info[index]!!
+ Box(modifier = M.animateItem()) {
+ item(lineItem, index)
+ }
+ }
+ item {
+ when (pagerSate) {
+ is LoadState.NotLoading -> if (pagerSate.endOfPaginationReached)
+ ListNoMore()
+
+ is LoadState.Loading -> ListLoading()
+ is LoadState.Error -> ListError(message = pagerSate.error.message.toString())
+ }
+ }
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MyAnyTable(
+ modifier: Modifier,
+
+ // 每行
+ info: List,
+ onLongClick: (T) -> Unit = {},
+ onClick: (T) -> Unit = {},
+
+ // 每列
+ items: List>,
+ verticalPadding: Dp = 15.dp,
+) {
+ Column(modifier = modifier) {
+ TableHeadLine(
+ modifier = M.fillMaxWidth(), list = items.map { it.header to it.weight }
+ )
+ var selectIndex by rememberSaveable { mutableStateOf(-1) }
+ LazyColumn {
+ items(count = info.size) { index ->
+ // 每行
+ val lineItem = info[index]
+ Card(
+ modifier = M
+ .animateItem()
+ .pointerInput(Unit) {
+ detectTapGestures(
+ onLongPress = {
+ onLongClick(lineItem)
+ },
+ onTap = {
+ onClick(lineItem)
+ selectIndex = if (selectIndex == index) -1 else index
+ }
+ )
+ },
+ colors = CardDefaults.cardColors(
+ containerColor = if (selectIndex == index
+ || (index % 2 == 0)
+ ) {
+ Color(0xFFF3F7FD)
+ } else {
+ Color.White
+ }
+ )
+ ) {
+ Box(
+ modifier = M
+ .fillMaxSize()
+ .padding()
+ .padding(vertical = verticalPadding),
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ items.forEach { item ->
+ val content = item.content(lineItem)
+ if (content == "|") {
+ VerticalDivider(
+ color = MyColors.Gray,
+ thickness = 1.dp,
+ modifier = M
+ .height(10.dp)
+ .padding(horizontal = 5.dp)
+ )
+ } else if (item.isButton) {
+ MyButton(
+ text = content,
+ modifier = M
+ .weight(item.weight.toFloat())
+ .padding(horizontal = 2.5.dp),
+ contentPadding = PaddingValues(5.dp, 2.5.dp)
+ ) {
+ item.onClick(lineItem)
+ }
+ } else if (item.isIndex) {
+ Text(
+ text = (index + 1).toString(),
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.weight(item.weight.toFloat()),
+ textAlign = TextAlign.Center,
+ )
+ } else {
+ Text(
+ text = content,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.weight(item.weight.toFloat()),
+ textAlign = TextAlign.Center,
+ maxLines = 1, // 限制为单行
+ overflow = TextOverflow.Ellipsis // 超出部分显示省略号
+ )
+
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ }
+}
+
+@Composable
+fun MyTable2(
+ modifier: Modifier = Modifier,
+ infos: List,
+ items: List>,
+ onLongClick: (T) -> Unit = {},
+ onClick: (T) -> Unit = {},
+ onExpend: @Composable (T) -> Unit = {},
+ verticalPadding: Dp = 5.dp,
+) {
+ Column(modifier = modifier) {
+ // 表头
+ TableHeadLine(
+ modifier = Modifier.fillMaxWidth(),
+ list = items.map { it.header to it.weight }
+ )
+
+ var selectIndex by rememberSaveable { mutableStateOf(-1) }
+
+ LazyColumn {
+ items(count = infos.size) { index ->
+ val lineItem = infos[index]
+
+ Card(
+ modifier = MD
+ .animateItem()
+ .pointerInput(Unit) {
+ detectTapGestures(
+ onLongPress = {
+ onLongClick(lineItem)
+ },
+ onTap = {
+ selectIndex = if (selectIndex == index) -1 else index
+ onClick(lineItem)
+ }
+ )
+ },
+ // 直角
+ shape = MaterialTheme.shapes.small,
+ colors = CardDefaults.cardColors(
+ containerColor = if (selectIndex == index) {
+ MyColors.LightLightBlueGreen
+ } else if (index % 2 == 0) {
+ Color(0xFFF3F7FD)
+ } else {
+ Color.White
+ }
+ )
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth(),
+ ) {
+ Row(
+ modifier = M
+ .padding(vertical = verticalPadding),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ items.forEach { item ->
+ Text(
+ text = item.content(lineItem),
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = Modifier.weight(item.weight.toFloat()),
+ textAlign = TextAlign.Center,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ }
+ }
+ if (selectIndex == index) {
+ Box(modifier = M.padding(2.5.dp)){
+ Column {
+ onExpend(lineItem)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MyRefreshTable(
+ modifier: Modifier,
+ isRefreshing: Boolean,
+ info: LazyPagingItems,
+ key: (T) -> Any,
+ onFinishRefresh: () -> Unit,
+
+ // 每行
+ onLongClick: (T) -> Unit = {},
+ onClick: (T) -> Unit = {},
+
+ onExpend: @Composable (T) -> Unit = {},
+
+ // 每列
+ items: List>,
+ verticalPadding: Dp = 15.dp,
+ scrollToTopOnRefresh: Boolean = false,
+) {
+ val listState: LazyListState = rememberLazyListState()
+ val state = rememberPullToRefreshState()
+ val pagerSate = info.loadState.append
+ LaunchedEffect(pagerSate) {
+ if (pagerSate is LoadState.NotLoading) {
+ onFinishRefresh()
+ }
+ }
+ LaunchedEffect(info.loadState.refresh) {
+ if (info.loadState.refresh is LoadState.NotLoading && scrollToTopOnRefresh) {
+ listState.animateScrollToItem(0)
+ }
+ }
+
+ Column(modifier = modifier) {
+ TableHeadLine(
+ modifier = M.fillMaxWidth(), list = items.map { it.header to it.weight }
+ )
+ PullToRefreshBox(
+ modifier = M.fillMaxSize(),
+ state = state,
+ isRefreshing = isRefreshing,
+ onRefresh = {
+ info.refresh()
+ },
+ indicator = {
+ PullToRefreshDefaults.IndicatorBox(
+ state = state,
+ isRefreshing = isRefreshing,
+ modifier = M.align(Alignment.TopCenter),
+ elevation = 0.dp
+ ) {
+ if (isRefreshing) {
+ CircularProgressIndicator(modifier = M.size(30.dp))
+ } else {
+ CircularProgressIndicator(
+ modifier = M.size(30.dp),
+ progress = { state.distanceFraction },
+ trackColor = ProgressIndicatorDefaults.circularIndeterminateTrackColor,
+ )
+ }
+ }
+ }
+ ) {
+ var selectIndex by rememberSaveable { mutableStateOf(-1) }
+ LazyColumn(state = listState) {
+ items(count = info.itemCount, key = info.itemKey { key(it) }) { index ->
+// // 每行
+ val lineItem = info[index]!!
+ Card(
+ modifier = MD
+ .animateItem()
+ .pointerInput(Unit) {
+ detectTapGestures(
+ onLongPress = {
+ onLongClick(lineItem)
+ },
+ onTap = {
+ onClick(lineItem)
+ selectIndex = if (selectIndex == index) -1 else index
+ }
+ )
+ },
+ colors = CardDefaults.cardColors(
+ containerColor = if (selectIndex == index) {
+ MyColors.LightLightBlueGreen
+ } else if (index % 2 == 0) {
+ Color(0xFFF3F7FD)
+ } else {
+ Color.White
+ }
+ )
+ ) {
+ Column(
+ modifier = M
+ .fillMaxSize()
+ .padding(vertical = verticalPadding),
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ items.forEach { item ->
+ val content = item.content(lineItem)
+ if (content == "|") {
+ VerticalDivider(
+ color = MyColors.Gray,
+ thickness = 1.dp,
+ modifier = M
+ .height(10.dp)
+ .padding(horizontal = 5.dp)
+ )
+ } else if (item.isButton) {
+ MyButton(
+ text = content,
+ modifier = M
+ .weight(item.weight.toFloat())
+ .padding(horizontal = 2.5.dp),
+ contentPadding = PaddingValues(5.dp, 2.dp)
+ ) {
+ item.onClick(lineItem)
+ }
+ } else if (item.isIndex) {
+ Text(
+ text = (index + 1).toString(),
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.weight(item.weight.toFloat()),
+ textAlign = TextAlign.Center,
+ )
+ } else {
+ Text(
+ text = content,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.weight(item.weight.toFloat()),
+ textAlign = TextAlign.Center,
+ maxLines = 1, // 限制为单行
+ overflow = TextOverflow.Ellipsis // 超出部分显示省略号
+ )
+ }
+ }
+ }
+ if (selectIndex == index) {
+ onExpend(lineItem)
+ }
+ }
+ }
+ }
+ item {
+ when (pagerSate) {
+ is LoadState.NotLoading -> if (pagerSate.endOfPaginationReached)
+ ListNoMore()
+
+ is LoadState.Loading -> ListLoading()
+ is LoadState.Error -> ListError(message = pagerSate.error.message.toString())
+ }
+ }
+ }
+ }
+
+ }
+}
+
+data class MyTableData(
+ val header: String,
+ val weight: Int,
+ val content: (T) -> String,
+ val isButton: Boolean = false,
+ val isIndex: Boolean = false,
+ val onClick: (T) -> Unit = {},
+) {
+ constructor(weight: Int, isIndex: Boolean = false) : this(
+ header = "序号",
+ content = { "" },
+ weight = weight,
+ isIndex = isIndex
+ )
+
+ constructor(
+ header: String, weight: Int, content: (T) -> String
+ ) : this(
+ header = header,
+ content = content,
+ weight = weight,
+ isButton = false,
+ isIndex = false,
+ onClick = { }
+ )
+
+}
+
+
+@Composable
+fun ListNoMore() {
+ Row(
+ modifier = M
+ .fillMaxWidth()
+ .wrapContentHeight()
+ .padding(6.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Text(
+ color = Color.Black,
+ text = "没有更多数据了",
+ fontSize = 16.sp,
+ modifier = M
+ .padding(start = 12.dp)
+ .align(CenterVertically)
+ )
+ }
+}
+
+@Composable
+fun ListLoading() {
+ Row(
+ modifier = M
+ .fillMaxWidth()
+ .wrapContentHeight()
+ .padding(6.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center
+ ) {
+ CircularProgressIndicator(modifier = M.size(24.dp))
+ Text(
+ color = Color.Black,
+ text = "正在加载中...",
+ fontSize = 16.sp,
+ modifier = M
+ .padding(start = 12.dp)
+ .align(CenterVertically)
+ )
+ }
+
+}
+
+@Composable
+fun ListError(message: String) {
+ Card(
+ modifier = M
+ .padding(6.dp)
+ .fillMaxWidth()
+ .wrapContentHeight()
+ ) {
+ Row(
+ modifier = M
+ .fillMaxWidth()
+ .background(Color.Red)
+ .padding(8.dp)
+ ) {
+ Image(
+ modifier = M
+ .clip(CircleShape)
+ .width(42.dp)
+ .height(42.dp),
+ painter = painterResource(id = R.drawable.error),
+ contentDescription = "",
+ colorFilter = ColorFilter.tint(Color.White)
+ )
+ Text(
+ color = Color.White,
+ text = message,
+ fontSize = 16.sp,
+ modifier = M
+ .padding(start = 12.dp)
+ .align(CenterVertically)
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/base/BaseListTempDataBase.kt b/app/src/main/java/com/bbitcn/f8/pad/base/BaseListTempDataBase.kt
new file mode 100644
index 0000000..7fde4cb
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/base/BaseListTempDataBase.kt
@@ -0,0 +1,76 @@
+package com.bbitcn.sericulture.base
+
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.log.MyLog
+import com.google.gson.Gson
+import java.lang.reflect.Type
+
+/**
+ *
+ * @Description 列表临时存储库
+ * @Author DuanKaiji
+ * @CreateTime 2024年08月06日 11:39:31
+ */
+abstract class BaseListTempDataBase {
+ protected val gson: Gson = Gson()
+
+ abstract fun getKey(): String
+
+ abstract fun getType(): Type
+
+ fun getAll(): MutableList {
+ val json = MMKVUtil.get(getKey(), "[]")
+ return Gson().fromJson(json, getType())
+ }
+
+ suspend fun init(data: List) {
+ MMKVUtil.put(getKey(), gson.toJson(data))
+ }
+
+ suspend fun insert(t: T, afterInsert: (T) -> Unit = {}): Boolean {
+// val type: Type = ParameterizedTypeImpl(T::class.java)
+ val temp: MutableList = getAll()
+ if (temp.contains(t)) {
+ return false
+ }
+ temp.add(t)
+ MyLog.test("insert: ${gson.toJson(temp)}")
+ MMKVUtil.put(getKey(), gson.toJson(temp))
+ afterInsert(t)
+ return true
+ }
+
+ /**
+ * 更新
+ */
+ fun update(predicate: (T) -> Boolean, afterUpdate: (T) -> Unit = {}): Boolean {
+ val temp: MutableList = getAll()
+ for (mode in temp) {
+ if (predicate(mode)) {
+ afterUpdate(mode)
+ MMKVUtil.put(getKey(), gson.toJson(temp))
+ return true
+ }
+ }
+ return false
+ }
+
+ fun delete(predicate: (T) -> Boolean,afterDel: (T) -> Boolean = { true }): Boolean {
+ val temp: MutableList = getAll()
+ val iterator = temp.iterator()
+ while (iterator.hasNext()) {
+ val mode = iterator.next()
+ if (predicate(mode)) {
+ iterator.remove()
+ afterDel(mode)
+ MMKVUtil.put(getKey(), gson.toJson(temp))
+ return true
+ }
+ }
+ return false
+ }
+ fun clear(): Boolean {
+ MMKVUtil.put(getKey(), "[]")
+ return true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/base/BasePagingSource.kt b/app/src/main/java/com/bbitcn/f8/pad/base/BasePagingSource.kt
new file mode 100644
index 0000000..5831376
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/base/BasePagingSource.kt
@@ -0,0 +1,41 @@
+package com.bbitcn.f8.pad.base
+
+import androidx.paging.PagingSource
+import androidx.paging.PagingState
+import com.bbitcn.f8.pad.model.net.request.PageInfo
+import com.bbitcn.f8.pad.utils.network.RetrofitClient
+
+abstract class BasePagingSource(
+ private val requestData: R
+) : PagingSource() {
+
+ val apiService = RetrofitClient.apiInterface()
+
+ abstract suspend fun fetchData(pageInfoJsonStr: String, requestData: R): List // 返回 T 类型的列表
+
+ override suspend fun load(params: LoadParams): LoadResult {
+ return try {
+ val page = params.key ?: 1
+ val pageSize = params.loadSize
+ val response = fetchData(PageInfo(page, pageSize).toJson(), requestData)
+ // 计算上一页和下一页的 key
+ val prevKey = if (page > 1) page - 1 else null
+ val nextKey = if (response.size < pageSize) null else page + 1
+
+ LoadResult.Page(
+ data = response, // 直接返回数据列表
+ prevKey = prevKey,
+ nextKey = nextKey
+ )
+ } catch (exception: Exception) {
+ LoadResult.Error(exception)
+ }
+ }
+
+ /**
+ * 这里可以根据当前的分页位置来返回刷新key
+ */
+ override fun getRefreshKey(state: PagingState): Int {
+ return 1 // 默认返回1,具体实现可根据需求调整
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/base/BaseTempDataBase.kt b/app/src/main/java/com/bbitcn/f8/pad/base/BaseTempDataBase.kt
new file mode 100644
index 0000000..50a9e90
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/base/BaseTempDataBase.kt
@@ -0,0 +1,35 @@
+package com.bbitcn.sericulture.base
+
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.blankj.utilcode.util.GsonUtils
+import com.google.gson.Gson
+import java.lang.reflect.Type
+
+/**
+ *
+ * @Description 临时数据库(单实体,列表请用@BaseListTempDataBase)
+ * @Author DuanKaiji
+ * @CreateTime 2024年08月06日 11:39:31
+ */
+abstract class BaseTempDataBase {
+
+ abstract fun getKey(): String
+ abstract fun defaultData(): T
+ abstract fun getType(): Type
+
+ fun init(value :T): Boolean {
+ MMKVUtil.put(getKey(), Gson().toJson(value))
+ return true
+ }
+
+ fun getData(): T {
+ val json = MMKVUtil.get(getKey(), GsonUtils.toJson(defaultData()))
+ return Gson().fromJson(json, getType())
+ }
+
+ fun clear(): Boolean {
+ MMKVUtil.put(getKey(), "")
+ return true
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/base/BaseViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/base/BaseViewModel.kt
new file mode 100644
index 0000000..2a2346e
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/base/BaseViewModel.kt
@@ -0,0 +1,215 @@
+package com.bbitcn.f8.pad.base
+
+import android.media.MediaPlayer
+import androidx.compose.material3.SnackbarHostState
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.bbitcn.f8.pad.MyApp
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.ui.screen.dialog.LoadingDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.TipsDialogData
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.Toasty.hideLoadingDialog
+import com.bbitcn.f8.pad.utils.PollingTask
+import com.bbitcn.f8.pad.utils.log.MyLog
+import com.bbitcn.f8.pad.utils.network.RetrofitClient
+import com.bbitcn.f8.pad.utils.network.RetrofitClientAI
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
+import java.net.SocketException
+import kotlin.coroutines.cancellation.CancellationException
+
+open class BaseViewModel : ViewModel() {
+
+ val apiService = RetrofitClient.apiInterface()
+ val aiApiService = RetrofitClientAI.aiApiInterface()
+
+ protected var pollingTask: PollingTask =
+ PollingTask.getInstance(javaClass.simpleName) // 使用类名作为ID
+
+ protected fun doInUIThread(task: () -> Unit) {
+ viewModelScope.launch {
+ withContext(Dispatchers.Main) {
+ task()
+ }
+ }
+ }
+
+ protected fun doInIoThreadThenUI(
+ loadingTips: String = "正在加载中",
+ showDialog: Boolean = true,
+ onError: (Throwable) -> Unit = { },
+ onIO: suspend () -> T,
+ onFinish: () -> Unit = { },
+ onUI: (T) -> Unit,
+ ) {
+ viewModelScope.launch {
+ val result = kotlin.runCatching {
+ withContext(Dispatchers.IO) {
+ if (showDialog) {
+ Toasty.showLoadingDialog(loadingTips)
+ }
+ onIO()
+ }
+ }
+
+ if (showDialog) {
+ hideLoadingDialog()
+ }
+
+ withContext(Dispatchers.Main) {
+ result.onSuccess { data ->
+ onUI(data)
+ }.onFailure { exception ->
+ // ✅ 如果是协程取消,不处理,只记录日志
+ if (exception is CancellationException || exception.cause is SocketException && exception.cause?.message?.contains(
+ "Socket closed"
+ ) == true
+ ) {
+ MyLog.test("协程被取消:${exception.javaClass.simpleName},message=${exception.message}")
+ return@onFailure
+ }
+ // 其他异常继续处理
+ exception.printStackTrace()
+ onError(exception)
+ exception.message?.let {
+ Toasty.error(it)
+ }
+ }.also {
+ // ✅ 最终执行的操作
+ onFinish()
+ }
+ }
+ }
+ }
+
+
+ fun doInIoThread(
+ loadingTips: String = "正在加载中",
+ showDialog: Boolean = true,
+ onError: (Throwable) -> Unit = { },
+ onFinish: () -> Unit = { },
+ doInIO: suspend () -> T,
+ ) {
+ doInIoThreadThenUI(loadingTips, showDialog, onError, doInIO, onFinish) { }
+ }
+
+ fun doInIoThreadNoDialog(
+ onError: (Throwable) -> Unit = { },
+ task: suspend () -> T,
+ ) {
+ doInIoThread(showDialog = false, doInIO = task, onError = onError)
+ }
+
+ /**
+ * 在IO线程中执行任务,可选择是否显示加载对话框
+ */
+ fun doInIoThreadWith(showLoading: Boolean, loadingTips: String, function: suspend () -> Unit) {
+ if (showLoading) {
+ doInIoThread(loadingTips) { function() }
+ } else {
+ doInIoThreadNoDialog { function() }
+ }
+ }
+
+ /**
+ * 启动一个无限轮询任务
+ *
+ * @param pollingInterval 轮询间隔时间(单位:秒)
+ * @param pollingTask 轮询任务的挂起函数
+ */
+ fun polling(intervalSeconds: Long, task: suspend () -> Unit) {
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ MyLog.test("开始轮询任务,间隔:$intervalSeconds 秒")
+ while (true) {
+ task()
+ delay(intervalSeconds * 1000L) // 转换秒为毫秒
+ }
+ }
+ }
+ }
+
+ /**
+ * 延迟开始轮询任务
+ */
+ suspend fun delayPolling(delaySeconds: Long, intervalSeconds: Long, task: suspend () -> Unit) {
+ delay(delaySeconds * 1000L)
+ polling(intervalSeconds, task)
+ }
+
+ private val taskMap = mutableMapOf()
+
+ /**
+ * 放弃旧任务,执行新任务
+ *
+ * @param key 任务的唯一标识符
+ * @param block 任务的挂起函数,必须使用协程,不能开启新的协程,否则无法取消任务
+ */
+ fun launchTaskNewFirst(
+ key: String,
+ block: suspend () -> Unit
+ ) {
+ taskMap[key]?.cancel() // 取消旧任务
+ taskMap[key] = viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ try {
+ block()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+ }
+
+ /**
+ * 有新任务时,取消。优先执行旧任务直到完成
+ *
+ * @param key 任务的唯一标识符
+ * @param block 任务函数,可以自由新建协程,但一定要在任务完成时调用 onFinished 回调,否则会导致后续任务永远无法执行
+ */
+ fun launchTaskOldFirst(
+ key: String,
+ block: (onFinished: () -> Unit) -> Unit
+ ) {
+ val existing = taskMap[key]
+ if (existing?.isActive == true) {
+ MyLog.test("已有任务在运行,取消新任务")
+ return
+ }
+ val job = viewModelScope.launch {
+ try {
+ suspendCancellableCoroutine { continuation ->
+ block {
+ if (continuation.isActive) {
+ continuation.resume(Unit) { cause, _, _ ->
+ println("resume 后任务被取消: $cause")
+ }
+ }
+ taskMap.remove(key)
+ }
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ taskMap.remove(key)
+ }
+ }
+ taskMap[key] = job
+ }
+
+ override fun onCleared() {
+ super.onCleared()
+ taskMap.values.forEach { it.cancel() }
+ taskMap.clear()
+ }
+
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/base/CustomMaterialComponents.kt b/app/src/main/java/com/bbitcn/f8/pad/base/CustomMaterialComponents.kt
new file mode 100644
index 0000000..fa53111
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/base/CustomMaterialComponents.kt
@@ -0,0 +1,933 @@
+package com.bbitcn.f8.pad.base
+
+import android.bluetooth.BluetoothAdapter
+import android.content.res.Configuration
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.selection.toggleable
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.CalendarMonth
+import androidx.compose.material.icons.filled.Clear
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material.icons.filled.Search
+import androidx.compose.material3.AssistChip
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.Checkbox
+import androidx.compose.material3.CheckboxDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.InputChipDefaults
+import androidx.compose.material3.LocalTextStyle
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.ScrollableTabRow
+import androidx.compose.material3.Tab
+import androidx.compose.material3.TabRow
+import androidx.compose.material3.TabRowDefaults
+import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
+import androidx.compose.material3.Text
+import androidx.compose.material3.VerticalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.produceState
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.VisualTransformation
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.TextUnit
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import coil3.compose.AsyncImage
+import coil3.request.ImageRequest
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.drawer.IconInfo
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.lt.compose_views.text_field.BackgroundComposeWithTextField
+import com.lt.compose_views.text_field.GoodTextField
+import com.lt.compose_views.text_field.HintComposeWithTextField
+import kotlinx.coroutines.delay
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+/**
+ * 监听某个键的变化,附带防抖功能
+ *
+ * @param key 要监听的键
+ * @param debounceMillis 防抖延迟时间,默认100毫秒
+ * @param effect 当键变化时执行的副作用函数
+ */
+@Composable
+fun DebouncedEffect(
+ key: T,
+ debounceMillis: Long = 100L,
+ effect: suspend () -> Unit
+) {
+ val stableKey by produceState(initialValue = key, key) {
+ delay(debounceMillis)
+ value = key
+ }
+
+ LaunchedEffect(stableKey) {
+ effect()
+ }
+}
+
+/**
+ *
+ * @Author DuanKaiji
+ * @CreateTime 2024年05月13日 10:09:12
+ */
+@Composable
+fun WhiteText(
+ text: String,
+ modifier: Modifier = M,
+ fontSize: TextUnit = 16.sp,
+ fontWeight: FontWeight? = null,
+ color: Color = Color.White,
+) {
+ Text(
+ text = text,
+ modifier = modifier,
+ fontSize = fontSize,
+ fontWeight = fontWeight,
+ color = color,
+ style = TextStyle.Default.copy(color = color),
+ )
+}
+
+@Composable
+fun MyButton(
+ modifier: Modifier = M,
+ text: String = "",
+ enabled: Boolean = true,
+ shape: Shape = RoundedCornerShape(4.dp), // 将圆角设为 4.dp
+ border: BorderStroke? = null,
+ colors: Color = MyColors.BlueGreen,
+ contentPadding: PaddingValues = PaddingValues(
+ start = 24.dp, top = 2.dp,
+ end = 24.dp, bottom = 2.dp
+ ),
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+ onClick: () -> Unit,
+) {
+ Button(
+ onClick = onClick,
+ modifier = modifier,
+ enabled = enabled,
+ interactionSource = interactionSource,
+ shape = shape,
+ border = border,
+ colors = ButtonDefaults.buttonColors(containerColor = colors),
+ contentPadding = contentPadding,
+ ) {
+ Text(
+ text = text,
+ fontSize = MaterialTheme.typography.titleMedium.fontSize,
+ textAlign = TextAlign.Center
+ )
+ }
+}
+
+@Composable
+fun BigButton(text: String, modifier: Modifier = M, isLight: Boolean = false, onClick: () -> Unit) {
+ Button(
+ onClick = onClick,
+ shape = RoundedCornerShape(30.dp),
+ modifier = modifier
+ .height(50.dp)
+ .fillMaxWidth(),
+ colors = ButtonDefaults.buttonColors(containerColor = if (text.contains("确") || isLight) MyColors.BlueGreen else MyColors.Gray)
+ ) {
+ WhiteText(text, fontSize = MaterialTheme.typography.headlineMedium.fontSize)
+ }
+}
+
+@Composable
+fun MyCard(
+ modifier: Modifier = M,
+ colors: Color = if (isSystemInDarkTheme()) MyColors.Black else Color.White,
+ radius: Dp = 4.dp,
+ border: BorderStroke? = null,
+ elevation: Dp = 6.dp,
+ content: @Composable ColumnScope.() -> Unit
+) {
+ Card(
+ modifier = modifier,
+ shape = RoundedCornerShape(radius),
+ colors = CardDefaults.cardColors(containerColor = colors),
+ elevation = CardDefaults.cardElevation(defaultElevation = elevation),
+ border = border,
+ content = content
+ )
+}
+
+@Composable
+fun MyInfoCard(
+ modifier: Modifier,
+ content: @Composable ColumnScope.() -> Unit
+) {
+ MyCard(
+ modifier = modifier,
+ elevation = 0.dp
+ ) {
+ Box(modifier = M.fillMaxSize()) {
+ this@MyCard.content()
+ }
+ }
+}
+
+@Composable
+fun MainFuncFrame(
+ content: @Composable ColumnScope.() -> Unit
+) {
+ Box(
+ modifier = M
+ .fillMaxSize()
+ .background(Color.White)
+ ) {
+ AsyncImage(
+ model = ImageRequest.Builder(LocalContext.current)
+ .data(R.drawable.bg)
+ .build(),
+ contentDescription = "Background",
+ contentScale = ContentScale.Crop,
+ modifier = Modifier.fillMaxSize()
+ )
+
+ // 设置前景内容,透明背景方便显示背景图片
+ Column(
+ modifier = M
+ .fillMaxSize()
+ .padding(15.dp)
+ .background(Color.Transparent) // 确保内容背景透明
+ ) {
+ content()
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun MyTextFieldPreview() {
+ MyTextField(
+ value = "Hello",
+ onValueChange = {})
+}
+
+@Composable
+fun MyTextField(
+ modifier: Modifier = M,
+ value: String,
+ hint: String? = null,
+ maxLines: Int = 1,
+ fontSize: TextUnit = 16.sp,
+ fontColor: Color = Color(0xff333333),
+ maxLength: Int = Int.MAX_VALUE,
+ contentAlignment: Alignment.Vertical = Alignment.CenterVertically,
+ leading: (@Composable RowScope.() -> Unit)? = null,
+ trailing: (@Composable RowScope.() -> Unit)? = null,
+ background: BackgroundComposeWithTextField? = BackgroundComposeWithTextField.DEFAULT,
+ horizontalPadding: Dp = 10.dp,
+ enabled: Boolean = true,
+ readOnly: Boolean = false,
+ textStyle: TextStyle = LocalTextStyle.current,
+ isNumberInputType: Boolean = false,
+ keyboardActions: KeyboardActions = KeyboardActions.Default,
+ visualTransformation: VisualTransformation = VisualTransformation.None,
+ onTextLayout: (TextLayoutResult) -> Unit = {},
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+ cursorBrush: Brush = SolidColor(Color.Black),
+ dateSelectTitle: String = "选择日期",
+ isSelectDate: Boolean = false,
+ onValueChange: (String) -> Unit = {},
+) {
+ GoodTextField(
+ value = value,
+ onValueChange = onValueChange,
+ modifier = modifier
+ .height(35.dp)
+ .background(color = MyColors.White)
+ .noVisualFeedbackClickable {
+ if (isSelectDate) {
+ Toasty.openDatePickerDrawer(dateSelectTitle) {
+ onValueChange(it.toString().replace("T", " "))
+ }
+ }
+ },
+ hint = hint?.let { HintComposeWithTextField.createTextHintCompose(it) },
+ maxLines = maxLines,
+ fontSize = fontSize,
+ fontColor = if (readOnly) MyColors.Gray else fontColor,
+ maxLength = maxLength,
+ contentAlignment = contentAlignment,
+ leading = leading,
+ trailing = trailing,
+ background = background,
+ horizontalPadding = horizontalPadding,
+ enabled = if (isSelectDate) false else enabled,
+ readOnly = readOnly,
+ textStyle = textStyle,
+ keyboardOptions = KeyboardOptions(keyboardType = if (isNumberInputType) KeyboardType.Number else KeyboardType.Text),
+ keyboardActions = keyboardActions,
+ visualTransformation = visualTransformation,
+ onTextLayout = onTextLayout,
+ interactionSource = interactionSource,
+ cursorBrush = cursorBrush
+ )
+}
+
+@Preview(showBackground = true)
+@Composable
+fun VipBadgePreview() {
+ VipBadge {
+ MyButton(text = "Hello") {
+
+ }
+ }
+}
+
+@Composable
+fun VipBadge(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
+ Box(modifier = modifier) {
+ // 内容显示区域
+ content()
+
+ // 显示 Badge
+ Box(
+ modifier = M
+ .align(Alignment.TopEnd) // 控制Badge的位置
+ .padding(2.5.dp) // 适当的内边距,避免和内容重叠
+ ) {
+// Badge(containerColor = MyColors.Transparent) {
+ Image(
+ painter = painterResource(id = R.drawable.vip),
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier.size(20.dp)
+ )
+// }
+ }
+ }
+}
+
+@Composable
+fun RedPointBadge(content: @Composable () -> Unit) {
+ Box {
+ // 内容显示区域
+ content()
+ Box(
+ modifier = M
+ .align(Alignment.TopEnd) // 控制Badge的位置
+ .padding(5.dp) // 适当的内边距,避免和内容重叠
+ ) {
+ Box(
+ modifier = M
+ .size(6.dp) // 调整位置到右上角
+ .background(MyColors.Red, shape = MaterialTheme.shapes.small)
+ .align(Alignment.TopEnd),
+ )
+ }
+ }
+}
+
+@Composable
+fun MyAnimatedVisibility(
+ visible: Boolean,
+ modifier: Modifier = M,
+ enter: EnterTransition = fadeIn(),
+ exit: ExitTransition = fadeOut(),
+ label: String = "AnimatedVisibility",
+ content: @Composable AnimatedVisibilityScope.() -> Unit
+) {
+ AnimatedVisibility(
+ visible = visible,
+ modifier = modifier,
+ enter = enter,
+ exit = exit,
+ label = label,
+ content = content
+ )
+}
+
+@Composable
+fun MyAnimatedVisibilityFromDownToUp(
+ visible: Boolean,
+ modifier: Modifier = M,
+ enter: EnterTransition = expandVertically(expandFrom = Alignment.Bottom),
+ exit: ExitTransition = fadeOut(),
+ label: String = "AnimatedVisibility",
+ content: @Composable AnimatedVisibilityScope.() -> Unit
+) {
+ AnimatedVisibility(
+ visible = visible,
+ modifier = modifier,
+ enter = enter,
+ exit = exit,
+ label = label,
+ content = content
+ )
+}
+
+@Composable
+fun MyOutlineButton(
+ modifier: Modifier = M,
+ text: String,
+ isImportance: Boolean = false,
+ onClick: () -> Unit
+) {
+ OutlinedButton(
+ onClick = onClick,
+ border = BorderStroke(1.dp, if (isImportance) MyColors.Red else MyColors.BlueGreen),
+ contentPadding = PaddingValues(0.dp),
+ modifier = modifier.height(20.dp)
+ ) {
+ Text(
+ modifier = M.padding(horizontal = 5.dp),
+ text = text,
+ color = if (isImportance) MyColors.Red else MyColors.BlueGreen
+ )
+ }
+}
+
+@Composable
+fun QueryTextField(
+ modifier: Modifier = M,
+ text: String,
+ hint: String = "搜索信息",
+ onValueChange: (String) -> Unit
+) {
+ MyTextField(
+ modifier = modifier.height(35.dp),
+ value = text,
+ hint = hint,
+ horizontalPadding = 5.dp,
+ leading = {
+ Icon(
+ imageVector = Icons.Default.Search,
+ contentDescription = "Search Icon",
+ tint = Color.Gray
+ )
+ },
+ trailing = {
+ if (text.isNotEmpty()) {
+ IconButton(onClick = {
+ // 清空输入框
+ onValueChange("")
+ }) {
+ Image(
+// painter = painterResource(id = R.drawable.check_more),
+ Icons.Filled.Clear,
+ contentDescription = "Check More",
+ modifier = M.size(24.dp)
+ )
+ }
+ }
+ }, onValueChange = onValueChange
+ )
+}
+
+@Composable
+fun DateRangePickTextFiled(
+ modifier: Modifier = M,
+ dateRange: Pair,
+ onClick: () -> Unit
+) {
+ GoodTextField(
+ modifier = modifier
+ .background(color = MyColors.White)
+ .clickable(onClick = {
+ onClick()
+ })
+ .width(200.dp)
+ .height(35.dp),
+ value = SimpleDateFormat("yyyy-M-d", Locale.getDefault()).format(
+ dateRange.first
+ ) + " ~ " + SimpleDateFormat("yyyy-M-d", Locale.getDefault()).format(
+ dateRange.second
+ ),
+ onValueChange = {},
+ horizontalPadding = 5.dp,
+ enabled = false,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ leading = {
+ Icon(
+ imageVector = Icons.Default.CalendarMonth,
+ contentDescription = "Calendar Icon",
+ tint = Color.Gray
+ )
+ }
+ )
+// DateRangePickerModal(
+// showDialog = datePickerDialog,
+// onDateRangeSelected = { dateRange ->
+// if (dateRange.first != null && dateRange.second != null) {
+// queryDateRange = Pair(Date(dateRange.first!!), Date(dateRange.second!!))
+// onDateChanged(queryDateRange)
+// }
+// },
+// onDismiss = { datePickerDialog = false }
+// )
+}
+
+
+@Composable
+fun InfoText(title: String, content: String, modifier: Modifier = M, forceShow: Boolean = false) {
+ if (content.isNotEmpty() || forceShow) {
+ Row(
+ modifier = modifier.padding(vertical = 3.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = title,
+ color = MyColors.Gray,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize
+ )
+ Text(
+ modifier = M
+ .weight(1f)
+ .padding(horizontal = 5.dp),
+ text = content,
+ maxLines = 10,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize
+ )
+ }
+ }
+}
+
+@Composable
+fun VerticalTabs(
+ modifier: Modifier = M,
+ textModifier: Modifier = M,
+ tabs: List,
+ selectedTab: Int,
+ onTabSelected: (Int) -> Unit
+) {
+ LazyColumn(modifier = modifier) {
+ item {
+ tabs.forEachIndexed() { index, tab ->
+ Row(
+ modifier = M
+ .animateItem()
+ .clickable {
+ onTabSelected(index)
+ }
+ .background(color = if (index == selectedTab) MyColors.LightGray else MyColors.Transparent),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ VerticalDivider(
+ modifier = M
+ .width(10.dp)
+ .height(30.dp),
+ color = if (index == selectedTab) MyColors.BlueGreen else MyColors.Transparent
+ )
+ Text(
+ modifier = textModifier
+ .padding(
+ start = 15.dp, top = 15.dp, bottom = 15.dp
+ )
+ .width(100.dp),
+ text = tab,
+ fontWeight = if (index == selectedTab) FontWeight.Bold else FontWeight.Normal,
+ color = if (index == selectedTab) MyColors.BlueGreen else MyColors.Black,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun VerticalTabPages(
+ modifier: Modifier = M,
+ textModifier: Modifier = M,
+ tabs: List,
+ onTabSelected: @Composable (Int) -> Unit
+) {
+ var tabSelected by rememberSaveable { mutableStateOf(0) }
+ Row {
+ VerticalTabs(
+ modifier = modifier,
+ textModifier = textModifier,
+ tabs = tabs,
+ selectedTab = tabSelected,
+ onTabSelected = {
+ tabSelected = it
+ }
+ )
+ VerticalDivider()
+ Box(
+ modifier = M
+ .padding(horizontal = 10.dp)
+ .fillMaxWidth()
+ ) {
+ onTabSelected(tabSelected)
+ }
+ }
+}
+
+@Composable
+fun UserBaseInfo(
+ name: String,
+ phone: String,
+ address: String,
+ idCard: String,
+ payCard: String,
+ payCardAddress: String = "",
+ modifier: Modifier = M,
+) {
+ Column(modifier = modifier) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Image(
+ painter = painterResource(R.drawable.icon_user),
+ contentDescription = null,
+ contentScale = ContentScale.FillBounds,
+ modifier = M
+ .size(35.dp)
+ .padding(5.dp)
+ )
+ Text(
+ text = name,
+ fontSize = MaterialTheme.typography.bodyLarge.fontSize,
+ fontWeight = FontWeight.Bold
+ )
+ Spacer(modifier = M.weight(1f))
+ Image(
+ painter = painterResource(R.drawable.icon_tel),
+ contentDescription = null,
+ contentScale = ContentScale.FillBounds,
+ modifier = M.size(20.dp)
+ )
+ Text(
+ text = phone,
+ fontSize = MaterialTheme.typography.bodyLarge.fontSize,
+ fontWeight = FontWeight.Bold,
+ color = MyColors.BlueGreen
+ )
+ }
+ IconInfo(R.drawable.icon_address, address)
+ IconInfo(R.drawable.icon_idcard, idCard)
+ IconInfo(R.drawable.icon_bankcard, payCard)
+ if (payCardAddress != "") {
+ InfoText("所属银行", payCardAddress)
+ }
+ }
+}
+
+@Composable
+fun EasySelect(
+ modifier: Modifier,
+ items: List,
+ onlyNumber: Boolean = false,
+ canInput: Boolean = true,
+ onValueChanged: (String) -> Unit
+) {
+ var value by rememberSaveable { mutableStateOf("") }
+ LazyRow(
+ modifier = modifier,
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ item {
+ if (items.isEmpty()) {
+ Text(
+ text = "暂无数据",
+ color = MyColors.Black,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize
+ )
+ }
+ }
+ items(items) {
+ Box(
+ modifier = M
+ .clickable {
+ value = it
+ onValueChanged(it)
+ }
+ .background(color = if (value == it) MyColors.BlueGreen else MyColors.White)
+ .border(1.dp, MyColors.Gray)
+ ) {
+ Text(
+ text = it,
+ modifier = M
+ .padding(horizontal = 10.dp, vertical = 5.dp),
+ color = if (value == it) MyColors.White else MyColors.Black,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize
+ )
+ }
+ }
+ item {
+ if (canInput) {
+ MyTextField(
+ modifier = M
+ .width(100.dp)
+ .padding(horizontal = 10.dp),
+ value = value,
+ isNumberInputType = true,
+ onValueChange = {
+ value = it
+ onValueChanged(it)
+ }
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun MyCheckBox(
+ title: String,
+ value: Boolean = true,
+ modifier: Modifier = M,
+ onValueChange: (Boolean) -> Unit = {}
+) {
+ Row(
+ modifier
+ .wrapContentWidth()
+ .toggleable(
+ value = value,
+ onValueChange = {
+ onValueChange(!value)
+ },
+ role = Role.Checkbox
+ ),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Checkbox(
+ colors = CheckboxDefaults.colors(
+ checkedColor = MyColors.BlueGreen
+ ),
+ checked = value,
+ onCheckedChange = {
+ onValueChange(!value)
+ }
+ )
+ Text(
+ text = title,
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = M.padding(start = 10.dp)
+ )
+ }
+}
+
+@Composable
+fun isLandscape(): Boolean {
+ val configuration = LocalConfiguration.current
+ return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+}
+
+fun isBluetoothEnabled(): Boolean {
+ val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
+ return bluetoothAdapter?.isEnabled == true
+}
+
+/**
+ * 自定义点击事件
+ * 不使用clickable的点击效果
+ */
+fun Modifier.noVisualFeedbackClickable(
+ onClick: () -> Unit
+): Modifier {
+ return this.pointerInput(Unit) {
+ detectTapGestures(
+ onTap = {
+ onClick()
+ }
+ )
+ }
+}
+
+@Composable
+fun MyTabRowHorizontal(
+ tabs: List,
+ onTabSelected: @Composable (Int) -> Unit
+) {
+ var curPage by rememberSaveable { mutableStateOf(0) }
+ Column {
+ TabRow(
+ selectedTabIndex = curPage,
+ indicator = { tabPositions ->
+ TabRowDefaults.Indicator(
+ M.tabIndicatorOffset(tabPositions[curPage]),
+ color = MyColors.BlueGreen
+ )
+ }
+ ) {
+ tabs.forEachIndexed { index, title ->
+ Tab(
+ text = {
+ Text(
+ title,
+ color = if (curPage == index) MyColors.BlueGreen else MyColors.Black,
+ fontWeight = if (curPage == index) FontWeight.Bold else FontWeight.Normal,
+ fontSize = 18.sp
+ )
+ },
+ selected = curPage == index,
+ onClick = {
+ curPage = index
+ }
+ )
+ }
+ }
+// HorizontalPager(
+// state = pagerState,
+// beyondViewportPageCount = tabs.size,
+// modifier = M
+// .fillMaxSize()
+// .weight(1f)
+// ) { page ->
+// Column(
+// modifier = M.fillMaxWidth(),
+// horizontalAlignment = Alignment.CenterHorizontally
+// ) {
+// onTabSelected(page)
+// }
+// }
+
+ Column(
+ modifier = M.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ onTabSelected(curPage)
+ }
+// HorizontalPager(
+// state = pagerState,
+// beyondViewportPageCount = tabs.size,
+// modifier = Modifier
+// .fillMaxSize()
+// .weight(1f)
+// ) { page ->
+// Column(
+// modifier = M.fillMaxWidth(),
+// horizontalAlignment = Alignment.CenterHorizontally
+// ) {
+// onTabSelected(page)
+// }
+// }
+ }
+}
+
+@Composable
+fun MyScrollableTabRow(
+ moidifer: Modifier = Modifier,
+ position: Int,
+ tabs: List,
+ onValueChange: (Int) -> Unit = {}
+) {
+ ScrollableTabRow(
+ selectedTabIndex = position,
+ containerColor = MyColors.Transparent,
+ edgePadding = 5.dp,
+ modifier = moidifer,
+ indicator = { tabPositions ->
+ TabRowDefaults.SecondaryIndicator(
+ M.tabIndicatorOffset(tabPositions[position]),
+ color = MyColors.BlueGreen,
+ )
+ }
+ ) {
+ tabs.forEachIndexed { index, title ->
+ Tab(
+ text = {
+ Text(
+ title,
+ color = if (position == index) MyColors.BlueGreen else MyColors.Black,
+ fontWeight = if (position == index) FontWeight.Bold else FontWeight.Normal,
+ fontSize = MaterialTheme.typography.bodyLarge.fontSize
+ )
+ },
+ selected = position == index,
+ onClick = {
+ onValueChange(index)
+ }
+ )
+ }
+ }
+}
+
+@Composable
+fun AssistChipFilter(
+ title: String,
+ content: String,
+ onClick: (String) -> Unit = {},
+ deleteEnable: Boolean = true,
+) {
+ if (content.isNotEmpty()) {
+ AssistChip(
+ modifier = M.padding(horizontal = 10.dp),
+ onClick = { onClick("") },
+ label = { Text(title + content) },
+ trailingIcon = {
+ if (deleteEnable) {
+ Icon(
+ Icons.Default.Close,
+ contentDescription = null,
+ M.size(InputChipDefaults.AvatarSize)
+ )
+ }
+ },
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/ParameterizedTypeImpl.kt b/app/src/main/java/com/bbitcn/f8/pad/model/ParameterizedTypeImpl.kt
new file mode 100644
index 0000000..83a91ee
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/ParameterizedTypeImpl.kt
@@ -0,0 +1,18 @@
+package com.bbitcn.f8.pad.model
+
+import java.lang.reflect.ParameterizedType
+import java.lang.reflect.Type
+
+class ParameterizedTypeImpl(private var clazz: Class<*>) : ParameterizedType {
+ override fun getActualTypeArguments(): Array {
+ return arrayOf(clazz)
+ }
+
+ override fun getRawType(): Type {
+ return MutableList::class.java
+ }
+
+ override fun getOwnerType(): Type? {
+ return null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/AddDryAirRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/AddDryAirRequest.kt
new file mode 100644
index 0000000..d3f7c2f
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/AddDryAirRequest.kt
@@ -0,0 +1,26 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.bbitcn.f8.pad.utils.TimeUtils
+import com.google.gson.annotations.SerializedName
+
+data class AddDryAirRequest(
+ @SerializedName("cjsysid")
+ var cjsysid: String = "",
+ @SerializedName("gjckcode")
+ var gjckcode: String = "",
+ @SerializedName("gjcksysid")
+ var gjcksysid: String = "",
+ @SerializedName("jiantype")
+ var jiantype: String = "",
+ @SerializedName("jiantypesysid")
+ var jiantypesysid: String = "",
+ @SerializedName("plantime")
+ var plantime: String = TimeUtils.getStringTime(),
+ @SerializedName("tanliangren")
+ var tanliangren: String = "",
+ @SerializedName("canpinzhong")
+ var canpinzhong: String = "",
+ @SerializedName("xiangzhen")
+ var xiangzhen: String = "",
+)
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/AddDryInRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/AddDryInRequest.kt
new file mode 100644
index 0000000..232e6cc
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/AddDryInRequest.kt
@@ -0,0 +1,38 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.bbitcn.f8.pad.utils.MyUtil
+import com.bbitcn.f8.pad.utils.TimeUtils
+import com.google.gson.annotations.SerializedName
+
+data class AddDryInRequest(
+ @SerializedName("bagtype")
+ var bagtype: String = "",
+ @SerializedName("bagzhongliang")
+ var bagzhongliang: Double = 0.0,
+ @SerializedName("canpinzhong")
+ var canpinzhong: String = "",
+ @SerializedName("cjsysid")
+ var cjsysid: String = "",
+ @SerializedName("codegeneralmodel")
+ var codegeneralmodel: Int = 0,
+ @SerializedName("gjckcode")
+ var gjckcode: String = "",
+ @SerializedName("gjcksysid")
+ var gjcksysid: String = "",
+ @SerializedName("hongjianren")
+ var hongjianren: String = "",
+ @SerializedName("jiantype")
+ var jiantype: String = "",
+ @SerializedName("jiantypesysid")
+ var jiantypesysid: String = "",
+ @SerializedName("rkdatetime")
+ var rkdatetime: String = TimeUtils.getStringTime(),
+ @SerializedName("rukuren")
+ var rukuren: String = "",
+ @SerializedName("xiangzhen")
+ var xiangzhen: String = "",
+ @SerializedName("rukutype")
+ var rukutype: Int = -1, //入库类型 0:烘茧入库 1:翻包摊晾 2:出库盈余
+ @SerializedName("standardtype")
+ var standardtype: Int = 0
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/AddDryOutRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/AddDryOutRequest.kt
new file mode 100644
index 0000000..e37534c
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/AddDryOutRequest.kt
@@ -0,0 +1,39 @@
+package com.bbitcn.f8.pad.model.net.request
+import com.bbitcn.f8.pad.utils.MyUtil
+import com.bbitcn.f8.pad.utils.TimeUtils
+import com.google.gson.annotations.SerializedName
+
+data class AddDryOutRequest(
+ @SerializedName("bagtype")
+ var bagtype: String = "",
+ @SerializedName("bagzhongliang")
+ var bagzhongliang: Double = 0.0,
+ @SerializedName("canpinzhong")
+ var canpinzhong: String = "",
+ @SerializedName("carpaihao")
+ var carpaihao: String = "",
+ @SerializedName("chukuren")
+ var chukuren: String = "",
+ @SerializedName("cjsysid")
+ var cjsysid: String = "",
+ @SerializedName("ckdatetime")
+ var ckdatetime: String = TimeUtils.getStringTime(),
+ @SerializedName("codegeneratemodel")
+ var codegeneratemodel: Int = 0,
+ @SerializedName("gjckcode")
+ var gjckcode: String = "",
+ @SerializedName("gjcksysid")
+ var gjcksysid: String = "",
+ @SerializedName("jiantype")
+ var jiantype: String = "",
+ @SerializedName("jiantypesysid")
+ var jiantypesysid: String = "",
+ @SerializedName("memo")
+ var memo: String = "",
+ @SerializedName("tihuoren")
+ var tihuoren: String = "",
+ @SerializedName("xiangzhen")
+ var xiangzhen: String = "",
+ @SerializedName("wldwsysid")
+ var wldwsysid: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/AddFarmerRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/AddFarmerRequest.kt
new file mode 100644
index 0000000..79e05fa
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/AddFarmerRequest.kt
@@ -0,0 +1,52 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class AddFarmerRequest(
+ @SerializedName("BankCode")
+ val bankCode: String = "",
+ @SerializedName("BankCode2")
+ val bankCode2: String = "",
+ @SerializedName("BankName")
+ val bankName: String = "",
+ @SerializedName("BankName2")
+ val bankName2: String = "",
+ @SerializedName("BankShortName")
+ val bankShortName: String = "",
+ @SerializedName("BankShortName2")
+ val bankShortName2: String = "",
+ @SerializedName("Cun")
+ val cun: String = "",
+ @SerializedName("DepartmentSysid")
+ val departmentSysid: String = "",
+ @SerializedName("IcCardCode")
+ val icCardCode: Long = 0,
+ @SerializedName("IdCard")
+ val idCard: String = "",
+ @SerializedName("IdCardAddress")
+ val idCardAddress: String = "",
+ @SerializedName("NhName")
+ val nhName: String = "",
+ @SerializedName("NhTips")
+ val nhTips: String = "",
+ @SerializedName("Phone")
+ val phone: String = "",
+ @SerializedName("PropertyName")
+ val propertyName: String = "",
+ @SerializedName("PropertySysid")
+ val propertySysid: String = "",
+ @SerializedName("RecBankCode")
+ val recBankCode: String = "",
+ @SerializedName("RecBankCode2")
+ val recBankCode2: String = "",
+ @SerializedName("Sex")
+ val sex: String = "",
+ @SerializedName("Sysid")
+ val sysid: String = "",
+ @SerializedName("Xian")
+ val xian: String = "",
+ @SerializedName("Xiang")
+ val xiang: String = "",
+ @SerializedName("Zu")
+ val zu: String = ""
+)
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/AddUserRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/AddUserRequest.kt
new file mode 100644
index 0000000..99928f8
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/AddUserRequest.kt
@@ -0,0 +1,37 @@
+package com.bbitcn.f8.pad.model.net.request
+import com.google.gson.annotations.SerializedName
+
+data class AddUserRequest(
+ @SerializedName("DepartmentSysid")
+ val departmentSysid: String = "",
+ @SerializedName("ICCardId")
+ val iCCardId: Int = 0,
+ @SerializedName("UserNew")
+ val userNew: UserNew = UserNew(),
+ @SerializedName("UserRole")
+ val userRole: List = listOf()
+) {
+ data class UserNew(
+ @SerializedName("Cun")
+ val cun: String = "",
+ @SerializedName("ICCardId")
+ val iCCardId: Int = 0,
+ @SerializedName("IdCard")
+ val idCard: String = "",
+ @SerializedName("LoginName")
+ val loginName: String = "",
+ @SerializedName("Memo")
+ val memo: String = "",
+ @SerializedName("Name")
+ val name: String = "",
+ @SerializedName("Sex")
+ val sex: Boolean = false,
+ @SerializedName("Sort")
+ val sort: Int = 0,
+ @SerializedName("Tel")
+ val tel: String = "",
+ @SerializedName("Xiang")
+ val xiang: String = ""
+ )
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/AuthDevice.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/AuthDevice.kt
new file mode 100644
index 0000000..532f42c
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/AuthDevice.kt
@@ -0,0 +1,20 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class AuthDevice(
+ @SerializedName("Applicant")
+ val applicant: String = "",
+ @SerializedName("HardwareId")
+ val hardwareId: String = "",
+ @SerializedName("Job")
+ val job: String = "",
+ @SerializedName("Memo")
+ val memo: String = "",
+ @SerializedName("Phone")
+ val phone: String = "",
+ @SerializedName("Pwd")
+ val pwd: String = "",
+ @SerializedName("TenantCode")
+ val tenantCode: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/ChatMessageRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/ChatMessageRequest.kt
new file mode 100644
index 0000000..d65104c
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/ChatMessageRequest.kt
@@ -0,0 +1,13 @@
+package com.bbitcn.f8.pad.model.net.request
+import com.google.gson.annotations.SerializedName
+
+data class ChatMessageRequest(
+ @SerializedName("question")
+ val question: String = "",
+ @SerializedName("session_id")
+ val sessionId: String = "",
+ @SerializedName("stream")
+ val stream: Boolean = false
+)
+
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/CocoonTypeTranslateRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/CocoonTypeTranslateRequest.kt
new file mode 100644
index 0000000..89589aa
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/CocoonTypeTranslateRequest.kt
@@ -0,0 +1,15 @@
+package com.bbitcn.f8.pad.model.net.request
+import com.google.gson.annotations.SerializedName
+data class CocoonTypeTranslateRequest(
+ @SerializedName("czsysid")
+ val czsysid: String = "",
+ @SerializedName("newname")
+ val newname: String = "",
+ @SerializedName("newsysid")
+ val newsysid: String = "",
+ @SerializedName("oldname")
+ val oldname: String = "",
+ @SerializedName("oldsysid")
+ val oldsysid: String = ""
+)
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DateRangeRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DateRangeRequest.kt
new file mode 100644
index 0000000..59e80b5
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DateRangeRequest.kt
@@ -0,0 +1,10 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class DateRangeRequest(
+ @SerializedName("startdate")
+ val startdate: String = "",
+ @SerializedName("endate")
+ val endate: String = "",
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DeleteUserRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DeleteUserRequest.kt
new file mode 100644
index 0000000..9bf1df1
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DeleteUserRequest.kt
@@ -0,0 +1,8 @@
+package com.bbitcn.f8.pad.model.net.request
+import com.google.gson.annotations.SerializedName
+
+
+data class DeleteUserRequest(
+ @SerializedName("Id")
+ val id: Long = 0
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonAirDetailListRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonAirDetailListRequest.kt
new file mode 100644
index 0000000..6e8fe4d
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonAirDetailListRequest.kt
@@ -0,0 +1,7 @@
+package com.bbitcn.f8.pad.model.net.request
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonAirDetailListRequest(
+ @SerializedName("tlsysid")
+ val tlsysid: String = ""
+)
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonAirListRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonAirListRequest.kt
new file mode 100644
index 0000000..da3bfef
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonAirListRequest.kt
@@ -0,0 +1,16 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonAirListRequest(
+ @SerializedName("cjsysid")
+ val cjsysid: String = "",
+ @SerializedName("endtime")
+ val endtime: String = "",
+ @SerializedName("gjcksysid")
+ val gjcksysid: String = "",
+ @SerializedName("like")
+ val like: String = "",
+ @SerializedName("starttime")
+ val starttime: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonOutListRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonOutListRequest.kt
new file mode 100644
index 0000000..19a3181
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonOutListRequest.kt
@@ -0,0 +1,16 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonOutListRequest(
+ @SerializedName("cjsysid")
+ val cjsysid: String = "",
+ @SerializedName("endtime")
+ val endtime: String = "",
+ @SerializedName("gjcksysid")
+ val gjcksysid: String = "",
+ @SerializedName("like")
+ val like: String = "",
+ @SerializedName("starttime")
+ val starttime: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonPackageForOutLossRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonPackageForOutLossRequest.kt
new file mode 100644
index 0000000..fdc51a9
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonPackageForOutLossRequest.kt
@@ -0,0 +1,16 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.bbitcn.f8.pad.utils.MyUtil
+import com.bbitcn.f8.pad.utils.TimeUtils
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonPackageForOutLossRequest(
+ @SerializedName("cjsysid")
+ val cjsysid: String = "",
+ @SerializedName("ckdsysid")
+ val ckdsysid: String = "",
+ @SerializedName("rfid")
+ val rfid: String = "",
+ @SerializedName("time")
+ val time: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonPackageLossRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonPackageLossRequest.kt
new file mode 100644
index 0000000..1452ca0
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonPackageLossRequest.kt
@@ -0,0 +1,13 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.bbitcn.f8.pad.utils.MyUtil
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonPackageLossRequest(
+ @SerializedName("rfid")
+ val rfid: String = "",
+ @SerializedName("time")
+ val time: String = "",
+ @SerializedName("tlsysid")
+ val tlsysid: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonQueryListRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonQueryListRequest.kt
new file mode 100644
index 0000000..70ec30b
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonQueryListRequest.kt
@@ -0,0 +1,16 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonQueryListRequest(
+ @SerializedName("cjsysid")
+ val cjsysid: String = "",
+ @SerializedName("endtime")
+ val endtime: String = "",
+ @SerializedName("gjcksysid")
+ val gjcksysid: String = "",
+ @SerializedName("like")
+ val like: String = "",
+ @SerializedName("starttime")
+ val starttime: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonRefreshStartRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonRefreshStartRequest.kt
new file mode 100644
index 0000000..4a64d73
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonRefreshStartRequest.kt
@@ -0,0 +1,20 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.bbitcn.f8.pad.utils.MyUtil
+import com.bbitcn.f8.pad.utils.TimeUtils
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonRefreshStartRequest(
+ @SerializedName("code")
+ val code: String = "",
+ @SerializedName("kcmaozhong")
+ val kcmaozhong: Double = 0.0,
+ @SerializedName("maozhong")
+ val maozhong: String = "",
+ @SerializedName("rfid")
+ val rfid: String = "",
+ @SerializedName("rkitemsysid")
+ val rkitemsysid: String = "",
+ @SerializedName("time")
+ val time: String = TimeUtils.getStringTime(),
+)
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonRefreshStopRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonRefreshStopRequest.kt
new file mode 100644
index 0000000..1ba1e9d
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonRefreshStopRequest.kt
@@ -0,0 +1,14 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.bbitcn.f8.pad.utils.MyUtil
+import com.bbitcn.f8.pad.utils.TimeUtils
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonRefreshStopRequest(
+ @SerializedName("maozhong")
+ val maozhong: String = "",
+ @SerializedName("rfid")
+ val rfid: String = "",
+ @SerializedName("time")
+ val time: String = TimeUtils.getStringTime(),
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonSaveInDetailRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonSaveInDetailRequest.kt
new file mode 100644
index 0000000..278ecc5
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonSaveInDetailRequest.kt
@@ -0,0 +1,22 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonSaveInDetailRequest(
+ @SerializedName("baoshu")
+ val baoshu: Int = 0,
+ @SerializedName("cjsysid")
+ val cjsysid: String = "",
+ @SerializedName("codegeneratemodel")
+ val codegeneratemodel: Int = 0,
+ @SerializedName("jingzhong")
+ val jingzhong: Double = 0.0,
+ @SerializedName("maozhong")
+ val maozhong: Double = 0.0,
+ @SerializedName("pizhong")
+ val pizhong: Double = 0.0,
+ @SerializedName("rfid")
+ val rfid: String = "",
+ @SerializedName("rksysid")
+ val rksysid: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonSaveNewOutDetail.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonSaveNewOutDetail.kt
new file mode 100644
index 0000000..0e962a2
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonSaveNewOutDetail.kt
@@ -0,0 +1,18 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonSaveNewOutDetail(
+ @SerializedName("baoshu")
+ val baoshu: Int = 1,
+ @SerializedName("cksysid")
+ val cksysid: String = "",
+ @SerializedName("jingzhong")
+ val jingzhong: Double = 0.0,
+ @SerializedName("maozhong")
+ val maozhong: Double = 0.0,
+ @SerializedName("pizhong")
+ val pizhong: Double = 0.0,
+ @SerializedName("rfid")
+ val rfid: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonSaveOutDetail.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonSaveOutDetail.kt
new file mode 100644
index 0000000..68d152b
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonSaveOutDetail.kt
@@ -0,0 +1,20 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonSaveOutDetail(
+ @SerializedName("baoshu")
+ val baoshu: Int = 1,
+ @SerializedName("cksysid")
+ val cksysid: String = "",
+ @SerializedName("code")
+ val code: String = "",
+ @SerializedName("jingzhong")
+ val jingzhong: Double = 0.0,
+ @SerializedName("maozhong")
+ val maozhong: Double = 0.0,
+ @SerializedName("rfid")
+ val rfid: String = "",
+ @SerializedName("pizhong")
+ val pizhong: Double = 0.0
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonStoreDetailListRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonStoreDetailListRequest.kt
new file mode 100644
index 0000000..5399b5a
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/DryCocoonStoreDetailListRequest.kt
@@ -0,0 +1,9 @@
+package com.bbitcn.f8.pad.model.net.request
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonStoreDetailListRequest(
+ @SerializedName("tlsysid")
+ val tlsysid: String = "",
+ @SerializedName("tlsysid")
+ val like: String = ""
+)
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/EditPasswordRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/EditPasswordRequest.kt
new file mode 100644
index 0000000..3da1dfd
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/EditPasswordRequest.kt
@@ -0,0 +1,14 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class EditPasswordRequest(
+ @SerializedName("newpwd")
+ val newpwd: String = "",
+ @SerializedName("phone")
+ val phone: String = "",
+ @SerializedName("smsbucket")
+ val smsbucket: String = "",
+ @SerializedName("smscode")
+ val smscode: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/FaceRecognizeRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/FaceRecognizeRequest.kt
new file mode 100644
index 0000000..13ac0b7
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/FaceRecognizeRequest.kt
@@ -0,0 +1,13 @@
+package com.bbitcn.f8.pad.model.net.request
+import com.google.gson.annotations.SerializedName
+
+data class FaceRecognizeRequest(
+ @SerializedName("group_id_list")
+ val group_id_list: String = "",
+ @SerializedName("image")
+ val image: String = "",
+ @SerializedName("image_type")
+ val image_type: String = "BASE64",
+)
+
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/FaceRegisterF8Request.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/FaceRegisterF8Request.kt
new file mode 100644
index 0000000..745e8e7
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/FaceRegisterF8Request.kt
@@ -0,0 +1,17 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class FaceRegisterF8Request(
+ @SerializedName("baidu_face_token")
+ val baiduFaceToken: String = "",
+ @SerializedName("oss_bucketname")
+ val ossBucketname: String = "",
+ @SerializedName("oss_objectname")
+ val ossObjectname: String = "",
+ @SerializedName("userid")
+ val userid: String = "",
+ @SerializedName("usertype")
+ val usertype: Int = 0
+)
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/FaceRegisterRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/FaceRegisterRequest.kt
new file mode 100644
index 0000000..bdbcaea
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/FaceRegisterRequest.kt
@@ -0,0 +1,17 @@
+package com.bbitcn.f8.pad.model.net.request
+import com.google.gson.annotations.SerializedName
+
+data class FaceRegisterRequest(
+ @SerializedName("group_id")
+ val group_id: String = "",
+ @SerializedName("image")
+ val image: String = "",
+ @SerializedName("image_type")
+ val image_type: String = "BASE64",
+ @SerializedName("user_id")
+ val user_id: String = "",
+ @SerializedName("quality_control")
+ val quality_control: String = "NORMAL",
+)
+
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/ForgetPasswordRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/ForgetPasswordRequest.kt
new file mode 100644
index 0000000..0ad5680
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/ForgetPasswordRequest.kt
@@ -0,0 +1,18 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class ForgetPasswordRequest(
+ @SerializedName("hardwareid")
+ val hardwareid: String = "",
+ @SerializedName("newpwd")
+ val newpwd: String = "",
+ @SerializedName("phone")
+ val phone: String = "",
+ @SerializedName("smsbucket")
+ val smsbucket: String = "",
+ @SerializedName("smscode")
+ val smscode: String = "",
+ @SerializedName("tenantcode")
+ val tenantcode: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/FundsRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/FundsRequest.kt
new file mode 100644
index 0000000..5ba7fc0
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/FundsRequest.kt
@@ -0,0 +1,16 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class FundsRequest(
+ @SerializedName("BegDate")
+ val begDate: String = "",
+ @SerializedName("DepSysid")
+ val depSysid: String = "",
+ @SerializedName("EndData")
+ val endData: String = "",
+ @SerializedName("Like")
+ val like: String = "",
+ @SerializedName("SearchType")
+ val searchType: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/FundsTotalListRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/FundsTotalListRequest.kt
new file mode 100644
index 0000000..73e2e80
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/FundsTotalListRequest.kt
@@ -0,0 +1,16 @@
+package com.bbitcn.f8.pad.model.net.request
+import com.google.gson.annotations.SerializedName
+
+
+data class FundsTotalListRequest(
+ @SerializedName("BegDate")
+ val begDate: String = "",
+ @SerializedName("DepSysid")
+ val depSysid: String = "",
+ @SerializedName("EndData")
+ val endData: String = "",
+ @SerializedName("Like")
+ val like: String = "",
+ @SerializedName("SearchType")
+ val searchType: String = "0"
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/LoginByFaceRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/LoginByFaceRequest.kt
new file mode 100644
index 0000000..d8412cd
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/LoginByFaceRequest.kt
@@ -0,0 +1,14 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class LoginByFaceRequest(
+ @SerializedName("hardwareid")
+ val hardwareid: String = "",
+ @SerializedName("tenantcode")
+ val tenantcode: String = "",
+ @SerializedName("facetoken")
+ val facetoken: String = "",
+ @SerializedName("userid")
+ val userid: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/LoginPhoneRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/LoginPhoneRequest.kt
new file mode 100644
index 0000000..6b20be3
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/LoginPhoneRequest.kt
@@ -0,0 +1,16 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class LoginPhoneRequest(
+ @SerializedName("hardwareid")
+ val hardwareid: String = "",
+ @SerializedName("phone")
+ val phone: String = "",
+ @SerializedName("smsbucket")
+ val smsbucket: String = "",
+ @SerializedName("smscode")
+ val smscode: String = "",
+ @SerializedName("tenantcode")
+ val tenantcode: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/LoginRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/LoginRequest.kt
new file mode 100644
index 0000000..4dd2616
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/LoginRequest.kt
@@ -0,0 +1,16 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class LoginRequest(
+ @SerializedName("account")
+ val account: String = "",
+ @SerializedName("hardwareid")
+ val hardwareid: String = "",
+ @SerializedName("password")
+ val password: String = "",
+ @SerializedName("tenantcode")
+ val tenantcode: String = ""
+)
+
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/PageInfo.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/PageInfo.kt
new file mode 100644
index 0000000..595c635
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/PageInfo.kt
@@ -0,0 +1,11 @@
+package com.bbitcn.f8.pad.model.net.request
+
+data class PageInfo(
+ val page: Int = 1,
+ val limit: Int = 30,
+ val orderby: String = ""
+) {
+ fun toJson(): String {
+ return "{\"page\":$page,\"limit\":$limit,\"orderby\":\"$orderby\"}"
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/PurchaseDataRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/PurchaseDataRequest.kt
new file mode 100644
index 0000000..fe10006
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/PurchaseDataRequest.kt
@@ -0,0 +1,18 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.global.Global
+import com.google.gson.annotations.SerializedName
+
+data class PurchaseDataRequest(
+ @SerializedName("BegDate")
+ val begDate: String = "",
+ @SerializedName("DepSysid")
+ val depSysid: String = MMKVUtil.get(Global.DEP_SYS_ID),
+ @SerializedName("EndData")
+ val endData: String = "",
+ @SerializedName("Like")
+ val like: String = "",
+ @SerializedName("sgSearchState")
+ val sgSearchState: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/RefreshTokenRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/RefreshTokenRequest.kt
new file mode 100644
index 0000000..082f4ed
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/RefreshTokenRequest.kt
@@ -0,0 +1,12 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class RefreshTokenRequest(
+ @SerializedName("hardwareid")
+ val hardwareid: String = "",
+ @SerializedName("ref_token")
+ val refToken: String = "",
+ @SerializedName("token")
+ val token: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SaveExtendInfoRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SaveExtendInfoRequest.kt
new file mode 100644
index 0000000..110ecb8
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SaveExtendInfoRequest.kt
@@ -0,0 +1,23 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+
+class SaveExtendInfoRequest(items: List) :
+ ArrayList(items) {
+
+ data class SaveExtendInfoRequestItem(
+ @SerializedName("colname")
+ val colname: String = "",
+ @SerializedName("colsysid")
+ val colsysid: String = "",
+ @SerializedName("coltitle")
+ val coltitle: String = "",
+ @SerializedName("colvalue")
+ val colvalue: String = "",
+ @SerializedName("extendsysid")
+ val extendsysid: String = "",
+ @SerializedName("nhsysid")
+ val nhsysid: String = ""
+ )
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SaveWeightDetailRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SaveWeightDetailRequest.kt
new file mode 100644
index 0000000..d9fc1b3
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SaveWeightDetailRequest.kt
@@ -0,0 +1,47 @@
+package com.bbitcn.f8.pad.model.net.request
+import com.google.gson.annotations.SerializedName
+
+data class SaveWeightDetailRequest(
+ @SerializedName("BoxCount")
+ val boxCount: Int = 0,
+ @SerializedName("BoxName")
+ val boxName: String = "",
+ @SerializedName("BoxSysid")
+ val boxSysid: String = "",
+ @SerializedName("CarCode")
+ val carCode: Int = 0,
+ @SerializedName("CarWeight")
+ val carWeight: Double = 0.0,
+ @SerializedName("ChengIndex")
+ val chengIndex: Int = 0,
+ @SerializedName("CzItemSysid")
+ val czItemSysid: String = "",
+ @SerializedName("CzSysid")
+ val czSysid: String = "",
+ @SerializedName("HsRatio")
+ val hsRatio: Int = 0,
+ @SerializedName("HsValue")
+ val hsValue: Int = 0,
+ @SerializedName("ItemMoney")
+ val itemMoney: Int = 0,
+ @SerializedName("Jweight")
+ val jweight: Double = 0.0,
+ @SerializedName("Kweight")
+ val kweight: Double = 0.0,
+ @SerializedName("Mweight")
+ val mweight: Double = 0.0,
+ @SerializedName("NhSysid")
+ val nhSysid: String = "",
+ @SerializedName("Price")
+ val price: Double = 0.0,
+ @SerializedName("Pweight")
+ val pweight: Double = 0.0,
+ @SerializedName("SgTypeId")
+ val sgTypeId: Int = 0,
+ @SerializedName("SgTypeName")
+ val sgTypeName: String = "",
+ @SerializedName("SgTypeSysid")
+ val sgTypeSysid: String = "",
+ @SerializedName("WeiShuValue")
+ val weiShuValue: Int = 0
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SaveWeightTicketRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SaveWeightTicketRequest.kt
new file mode 100644
index 0000000..1abd964
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SaveWeightTicketRequest.kt
@@ -0,0 +1,43 @@
+package com.bbitcn.f8.pad.model.net.request
+import com.google.gson.annotations.SerializedName
+
+data class SaveWeightTicketRequest(
+ @SerializedName("BatchCjSysid")
+ val batchCjSysid: String = "",
+ @SerializedName("CzSysid")
+ val czSysid: String = "",
+ @SerializedName("DepCode")
+ val depCode: String = "",
+ @SerializedName("DepSysid")
+ val depSysid: String = "",
+ @SerializedName("Extention")
+ val extention: Extention = Extention(),
+ @SerializedName("Gsysid")
+ val gsysid: String = "",
+ @SerializedName("GyhSysid")
+ val gyhSysid: String = "",
+ @SerializedName("InoputDataList")
+ val inoputDataList: List = listOf(),
+ @SerializedName("NhSysid")
+ val nhSysid: String = "",
+ @SerializedName("YpState")
+ val ypState: String = ""
+) {
+ data class Extention(
+ @SerializedName("czsysid")
+ val czsysid: String = "",
+ @SerializedName("validhanshuicishu")
+ val validhanshuicishu: Int = 0,
+ @SerializedName("validhanshuilv")
+ val validhanshuilv: String = ""
+ )
+
+ data class InoputData(
+ @SerializedName("InputName")
+ val inputName: String = "",
+ @SerializedName("InputSysid")
+ val inputSysid: String = "",
+ @SerializedName("InputValue")
+ val inputValue: Int = 0 // 这里虽然理论上是Any 但实际上各个值都可以用int表示 例如:【是否方格簇-1表示没有选择,1是,0否】,【色泽1上、0中、-1下】 【含水率 也是数字】
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SearchOutDetailByRFIDRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SearchOutDetailByRFIDRequest.kt
new file mode 100644
index 0000000..0465217
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SearchOutDetailByRFIDRequest.kt
@@ -0,0 +1,16 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class SearchOutDetailByRFIDRequest(
+ @SerializedName("cjsysid")
+ val cjsysid: String = "",
+ @SerializedName("code")
+ val code: String = "",
+ @SerializedName("gjcksysid")
+ val gjcksysid: String = "",
+ @SerializedName("jiantypesysid")
+ val jiantypesysid: String = "",
+ @SerializedName("rfid")
+ val rfid: String = ""
+)
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SeedInfoRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SeedInfoRequest.kt
new file mode 100644
index 0000000..6494fce
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SeedInfoRequest.kt
@@ -0,0 +1,9 @@
+package com.bbitcn.f8.pad.model.net.request
+import com.google.gson.annotations.SerializedName
+
+data class SeedInfoRequest(
+ @SerializedName("CjSysid")
+ val cjSysid: String = "",
+ @SerializedName("NhSysid")
+ val nhSysid: String = ""
+)
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SendCodeRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SendCodeRequest.kt
new file mode 100644
index 0000000..40c85f3
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SendCodeRequest.kt
@@ -0,0 +1,14 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class SendCodeRequest(
+ @SerializedName("expired")
+ val expired: Int = 10,
+ @SerializedName("smsbucket")
+ val smsbucket: String = "",
+ @SerializedName("tel")
+ val tel: String = ""
+)
+
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SetUserListRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SetUserListRequest.kt
new file mode 100644
index 0000000..078444c
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/SetUserListRequest.kt
@@ -0,0 +1,18 @@
+package com.bbitcn.f8.pad.model.net.request
+import com.google.gson.annotations.SerializedName
+
+
+data class SetUserListRequest(
+ @SerializedName("DepSysid")
+ val depSysid: String = "",
+ @SerializedName("Like")
+ val like: String = "",
+ /**
+ * 查询用户类型
+ * 全部用户=0,
+ * 有效用户=1,
+ * 冻结用户=2,
+ */
+ @SerializedName("SearchUserType")
+ val searchUserType: String = "0"//
+)
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/StatisticsRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/StatisticsRequest.kt
new file mode 100644
index 0000000..230f3f1
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/StatisticsRequest.kt
@@ -0,0 +1,14 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class StatisticsRequest(
+ @SerializedName("endate")
+ val endate: String = "",
+ @SerializedName("like")
+ val like: String = "",
+ @SerializedName("sgtypesysid")
+ val sgtypesysid: String = "",
+ @SerializedName("startdate")
+ val startdate: String = ""
+)
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/TareRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/TareRequest.kt
new file mode 100644
index 0000000..03291ba
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/TareRequest.kt
@@ -0,0 +1,22 @@
+package com.bbitcn.f8.pad.model.net.request
+import com.google.gson.annotations.SerializedName
+
+data class TareRequest(
+ @SerializedName("CzSysid")
+ val czSysid: String = "",
+ @SerializedName("UpdateItemList")
+ val updateItemList: List = listOf()
+) {
+ data class UpdateItem(
+ @SerializedName("Jweight")
+ var jweight: Double = 0.0,
+ @SerializedName("Kweight")
+ var kweight: Double = 0.0,
+ @SerializedName("Pweight")
+ var pweight: Double = 0.0,
+ @SerializedName("SgTypName")
+ val sgTypName: String = "",
+ @SerializedName("Sysid")
+ val sysid: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/UpdateTicketPriceRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/UpdateTicketPriceRequest.kt
new file mode 100644
index 0000000..f7d37e8
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/UpdateTicketPriceRequest.kt
@@ -0,0 +1,26 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class UpdateTicketPriceRequest(
+ @SerializedName("CzSysid")
+ val czSysid: String = "",
+ @SerializedName("UpdateList")
+ val updateList: List = listOf()
+) {
+ data class Update(
+ @SerializedName("NewJweight")
+ val newJweight: Double = 0.0,
+ @SerializedName("NewKweight")
+ val newKweight: Double = 0.0,
+ @SerializedName("NewMoney")
+ var newMoney: Double = 0.0,
+ @SerializedName("NewPrice")
+ var newPrice: Double = 0.0,
+ @SerializedName("NewPweight")
+ val newPweight: Double = 0.0,
+ @SerializedName("Sysid")
+ val sysid: String = ""
+ )
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/UpdateUserRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/UpdateUserRequest.kt
new file mode 100644
index 0000000..298b92a
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/UpdateUserRequest.kt
@@ -0,0 +1,36 @@
+package com.bbitcn.f8.pad.model.net.request
+import com.google.gson.annotations.SerializedName
+
+data class UpdateUserRequest(
+ @SerializedName("DepartmentSysid")
+ val departmentSysid: String = "",
+ @SerializedName("UserNew")
+ val userNew: UserNew = UserNew(),
+ @SerializedName("UserRole")
+ val userRole: List = listOf()
+) {
+ data class UserNew(
+ @SerializedName("Cun")
+ val cun: String = "",
+ @SerializedName("ICCardId")
+ val iCCardId: Int = 0,
+ @SerializedName("Id")
+ val id: Long = 0,
+ @SerializedName("IdCard")
+ val idCard: String = "",
+ @SerializedName("LoginName")
+ val loginName: String = "",
+ @SerializedName("Memo")
+ val memo: String = "",
+ @SerializedName("Name")
+ val name: String = "",
+ @SerializedName("Sex")
+ val sex: Boolean = false,
+ @SerializedName("Sort")
+ val sort: Int = 0,
+ @SerializedName("Tel")
+ val tel: String = "",
+ @SerializedName("Xiang")
+ val xiang: String = ""
+ )
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/UserListDataRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/UserListDataRequest.kt
new file mode 100644
index 0000000..06726b3
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/UserListDataRequest.kt
@@ -0,0 +1,16 @@
+package com.bbitcn.f8.pad.model.net.request
+
+import com.google.gson.annotations.SerializedName
+
+data class UserListDataRequest(
+ @SerializedName("Xian")
+ val xian: String = "",
+ @SerializedName("Xiang")
+ val xiang: String = "",
+ @SerializedName("Cun")
+ val cun: String = "",
+ @SerializedName("Zu")
+ val zu: String = "",
+ @SerializedName("Like")
+ val like: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/startDryCocoonAirDetailRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/startDryCocoonAirDetailRequest.kt
new file mode 100644
index 0000000..c7c11f9
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/startDryCocoonAirDetailRequest.kt
@@ -0,0 +1,21 @@
+package com.bbitcn.f8.pad.model.net.request
+import com.google.gson.annotations.SerializedName
+
+data class StartDryCocoonAirDetailRequest(
+ @SerializedName("cjsysid")
+ val cjsysid: String = "",
+ @SerializedName("code")
+ val code: String = "",
+ @SerializedName("kcmaozhong")
+ val kcmaozhong: Double = 0.0,
+ @SerializedName("maozhong")
+ val maozhong: Double = 0.0,
+ @SerializedName("rfid")
+ val rfid: String = "",
+ @SerializedName("rkitemsysid")
+ val rkitemsysid: String = "",
+ @SerializedName("time")
+ val time: String = "",
+ @SerializedName("tlsysid")
+ val tlsysid: String = ""
+)
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/request/stopDryCocoonAirDetailRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/stopDryCocoonAirDetailRequest.kt
new file mode 100644
index 0000000..6dcf4d3
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/request/stopDryCocoonAirDetailRequest.kt
@@ -0,0 +1,13 @@
+package com.bbitcn.f8.pad.model.net.request
+import com.google.gson.annotations.SerializedName
+
+data class StopDryCocoonAirDetailRequest(
+ @SerializedName("maozhong")
+ val maozhong: Double = 0.0,
+ @SerializedName("rfid")
+ val rfid: String = "",
+ @SerializedName("time")
+ val time: String = "",
+ @SerializedName("tlsysid")
+ val tlsysid: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/AboutResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/AboutResponse.kt
new file mode 100644
index 0000000..19f970a
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/AboutResponse.kt
@@ -0,0 +1,20 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class AboutResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("describe")
+ val describe: String = "",
+ @SerializedName("tenantname")
+ val tenantname: String = ""
+ )
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/AllExtendInfoResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/AllExtendInfoResponse.kt
new file mode 100644
index 0000000..ddbb28e
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/AllExtendInfoResponse.kt
@@ -0,0 +1,35 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class AllExtendInfoResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("coldroplist")
+ val coldroplist: String = "",
+ @SerializedName("colname")
+ val colname: String = "",
+ @SerializedName("colsysid")
+ val colsysid: String = "",
+ @SerializedName("coltitle")
+ val coltitle: String = "",
+ @SerializedName("coltype")
+ val coltype: Int = 0,
+ @SerializedName("colvalue")
+ val colvalue: String = "",
+ @SerializedName("extendsysid")
+ val extendsysid: String = "",
+ @SerializedName("isnotnull")
+ val isnotnull: Boolean = false,
+ @SerializedName("nhsysid")
+ val nhsysid: String = "",
+ @SerializedName("sort")
+ val sort: Int = 0
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/AppVersion.java b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/AppVersion.java
new file mode 100644
index 0000000..cf783e3
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/AppVersion.java
@@ -0,0 +1,84 @@
+package com.bbitcn.f8.pad.model.net.response;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * @Description 应用版本-网络请求
+ * @Author DuanKaiji
+ * @CreateTime 2023年10月19日 16:06:36
+ */
+public class AppVersion {
+ @SerializedName("Code")
+ private int code;
+ @SerializedName("Message")
+ private String message;
+ @SerializedName("Data")
+ private DataDTO data;
+
+ public int getCode() {
+ return code;
+ }
+
+ public void setCode(int code) {
+ this.code = code;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public DataDTO getData() {
+ return data;
+ }
+
+ public void setData(DataDTO data) {
+ this.data = data;
+ }
+
+ public static class DataDTO {
+ @SerializedName("VersionNumber")
+ private int versionNumber;
+ @SerializedName("VersionName")
+ private String versionName;
+ @SerializedName("VersionDescription")
+ private String versionDescription;
+ @SerializedName("Url")
+ private String url;
+
+ public int getVersionNumber() {
+ return versionNumber;
+ }
+
+ public void setVersionNumber(int versionNumber) {
+ this.versionNumber = versionNumber;
+ }
+
+ public String getVersionName() {
+ return versionName;
+ }
+
+ public void setVersionName(String versionName) {
+ this.versionName = versionName;
+ }
+
+ public String getVersionDescription() {
+ return versionDescription;
+ }
+
+ public void setVersionDescription(String versionDescription) {
+ this.versionDescription = versionDescription;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/BankInfoResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/BankInfoResponse.kt
new file mode 100644
index 0000000..acd8969
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/BankInfoResponse.kt
@@ -0,0 +1,25 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class BankInfoResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("BankName")
+ val bankName: String = "",
+ @SerializedName("BankShortName")
+ val bankShortName: String = "",
+ @SerializedName("BankType")
+ val bankType: Int = 0,
+ @SerializedName("RecBankCode")
+ val recBankCode: String = "",
+ @SerializedName("Validated")
+ val validated: Boolean = false
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/BoxInfoResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/BoxInfoResponse.kt
new file mode 100644
index 0000000..17a37e0
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/BoxInfoResponse.kt
@@ -0,0 +1,29 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+
+
+data class BoxInfoResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("IsVisible")
+ val isVisible: Boolean = false,
+ @SerializedName("MaxCount")
+ val maxCount: Int = 0,
+ @SerializedName("Memo")
+ val memo: String = "",
+ @SerializedName("Name")
+ val name: String = "",
+ @SerializedName("Sort")
+ val sort: Int = 0,
+ @SerializedName("Sysid")
+ val sysid: String = "",
+ @SerializedName("Weight")
+ val weight: Double = 0.0
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/CarInfoResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/CarInfoResponse.kt
new file mode 100644
index 0000000..016eb24
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/CarInfoResponse.kt
@@ -0,0 +1,29 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+
+
+data class CarInfoResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("CarCode")
+ val carCode: Int = -1,
+ @SerializedName("CarWeight")
+ val carWeight: Double = 0.0,
+ @SerializedName("DepCode")
+ val depCode: String = "",
+ @SerializedName("DepSysid")
+ val depSysid: String = "",
+ @SerializedName("Memo")
+ val memo: String = "",
+ @SerializedName("Sort")
+ val sort: Int = 0,
+ @SerializedName("Sysid")
+ val sysid: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/ChatMessageResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/ChatMessageResponse.kt
new file mode 100644
index 0000000..cf3f0cb
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/ChatMessageResponse.kt
@@ -0,0 +1,68 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class ChatMessageResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data()
+) {
+ data class Data(
+ @SerializedName("answer")
+ val answer: String = "",
+ @SerializedName("audio_binary")
+ val audioBinary: Any? = null,
+ @SerializedName("id")
+ val id: String = "",
+ @SerializedName("prompt")
+ val prompt: String = "",
+ @SerializedName("reference")
+ val reference: Reference = Reference(),
+ @SerializedName("session_id")
+ val sessionId: String = ""
+ ) {
+ data class Reference(
+ @SerializedName("chunks")
+ val chunks: List = listOf(),
+ @SerializedName("doc_aggs")
+ val docAggs: List = listOf(),
+ @SerializedName("total")
+ val total: Int = 0
+ ) {
+ data class Chunk(
+ @SerializedName("content")
+ val content: String = "",
+ @SerializedName("dataset_id")
+ val datasetId: String = "",
+ @SerializedName("document_id")
+ val documentId: String = "",
+ @SerializedName("document_name")
+ val documentName: String = "",
+ @SerializedName("id")
+ val id: String = "",
+ @SerializedName("image_id")
+ val imageId: String = "",
+ @SerializedName("positions")
+ val positions: List> = listOf(),
+ @SerializedName("similarity")
+ val similarity: Double = 0.0,
+ @SerializedName("term_similarity")
+ val termSimilarity: Double = 0.0,
+ @SerializedName("vector_similarity")
+ val vectorSimilarity: Double = 0.0
+ )
+
+ data class DocAgg(
+ @SerializedName("count")
+ val count: Int = 0,
+ @SerializedName("doc_id")
+ val docId: String = "",
+ @SerializedName("doc_name")
+ val docName: String = ""
+ )
+ }
+ }
+}
+
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/ChatMessageStreamResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/ChatMessageStreamResponse.kt
new file mode 100644
index 0000000..e48b221
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/ChatMessageStreamResponse.kt
@@ -0,0 +1,66 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class ChatMessageStreamResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("message")
+ val message: String = "",
+ @SerializedName("data")
+ val `data`: Data = Data()
+) {
+ data class Data(
+ @SerializedName("answer")
+ val answer: String = "",
+ @SerializedName("id")
+ val id: String = "",
+ @SerializedName("prompt")
+ val prompt: String = "",
+ @SerializedName("reference")
+ val reference: Reference = Reference(),
+ @SerializedName("session_id")
+ val sessionId: String = ""
+ ) {
+ data class Reference(
+ @SerializedName("chunks")
+ val chunks: List = listOf(),
+ @SerializedName("doc_aggs")
+ val docAggs: List = listOf(),
+ @SerializedName("total")
+ val total: Int = 0
+ ) {
+ data class Chunk(
+ @SerializedName("content")
+ val content: String = "",
+ @SerializedName("dataset_id")
+ val datasetId: String = "",
+ @SerializedName("document_id")
+ val documentId: String = "",
+ @SerializedName("document_name")
+ val documentName: String = "",
+ @SerializedName("id")
+ val id: String = "",
+ @SerializedName("image_id")
+ val imageId: String = "",
+ @SerializedName("positions")
+ val positions: List> = listOf(),
+ @SerializedName("similarity")
+ val similarity: Double = 0.0,
+ @SerializedName("term_similarity")
+ val termSimilarity: Double = 0.0,
+ @SerializedName("vector_similarity")
+ val vectorSimilarity: Double = 0.0
+ )
+
+ data class DocAgg(
+ @SerializedName("count")
+ val count: Int = 0,
+ @SerializedName("doc_id")
+ val docId: String = "",
+ @SerializedName("doc_name")
+ val docName: String = ""
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/CheckUpdateResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/CheckUpdateResponse.kt
new file mode 100644
index 0000000..abf02c6
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/CheckUpdateResponse.kt
@@ -0,0 +1,29 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class CheckUpdateResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("describe")
+ val describe: String = "",
+ @SerializedName("forceupdate")
+ val forceupdate: Int = 0,
+ @SerializedName("issuer")
+ val issuer: String = "",
+ @SerializedName("size")
+ val size: Double = 0.0,
+ @SerializedName("url")
+ val url: String = "",
+ @SerializedName("versionname")
+ val versionname: String = "",
+ @SerializedName("versionnumber")
+ val versionnumber: Int = 0
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/CocoonInDetailResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/CocoonInDetailResponse.kt
new file mode 100644
index 0000000..28b5437
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/CocoonInDetailResponse.kt
@@ -0,0 +1,58 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+
+data class CocoonInDetailResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("bagtype")
+ val bagtype: String = "",
+ @SerializedName("bagzhongliang")
+ val bagzhongliang: Double = 0.0,
+ @SerializedName("baoshu")
+ val baoshu: Int = 0,
+ @SerializedName("cjname")
+ val cjname: String = "",
+ @SerializedName("cjsysid")
+ val cjsysid: String = "",
+ @SerializedName("ckname")
+ val ckname: String = "",
+ @SerializedName("code")
+ val code: String = "",
+ @SerializedName("cpzname")
+ val cpzname: String = "",
+ @SerializedName("datetime")
+ val datetime: String = "",
+ @SerializedName("depname")
+ val depname: String = "",
+ @SerializedName("gjcksysid")
+ val gjcksysid: String = "",
+ @SerializedName("hongjianren")
+ val hongjianren: String = "",
+ @SerializedName("jiantype")
+ val jiantype: String = "",
+ @SerializedName("jiantypesysid")
+ val jiantypesysid: String = "",
+ @SerializedName("jingzhong")
+ val jingzhong: Double = 0.0,
+ @SerializedName("maozhong")
+ val maozhong: Double = 0.0,
+ @SerializedName("memo")
+ val memo: String = "",
+ @SerializedName("pizhong")
+ val pizhong: Double = 0.0,
+ @SerializedName("rukuren")
+ val rukuren: String = "",
+ @SerializedName("standardtype")
+ val standardtype: String = "",
+ @SerializedName("sysid")
+ val sysid: String = "",
+ @SerializedName("xiangzhen")
+ val xiangzhen: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/CocoonOutDetailResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/CocoonOutDetailResponse.kt
new file mode 100644
index 0000000..0fa30dc
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/CocoonOutDetailResponse.kt
@@ -0,0 +1,62 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+
+data class CocoonOutDetailResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("bagtype")
+ val bagtype: String = "",
+ @SerializedName("bagzhongliang")
+ val bagzhongliang: Double = 0.0,
+ @SerializedName("baoshu")
+ val baoshu: Int = 0,
+ @SerializedName("canpinzhong")
+ val canpinzhong: String = "",
+ @SerializedName("releasebaoshu")
+ val releasebaoshu: Int = 0,
+ @SerializedName("carpaihao")
+ val carpaihao: String = "",
+ @SerializedName("chukuren")
+ val chukuren: String = "",
+ @SerializedName("cjname")
+ val cjname: String = "",
+ @SerializedName("cjsysid")
+ val cjsysid: String = "",
+ @SerializedName("ckdatetime")
+ val ckdatetime: String = "",
+ @SerializedName("ckname")
+ val ckname: String = "",
+ @SerializedName("code")
+ val code: String = "",
+ @SerializedName("gjcksysid")
+ val gjcksysid: String = "",
+ @SerializedName("jiantype")
+ val jiantype: String = "",
+ @SerializedName("jiantypesysid")
+ val jiantypesysid: String = "",
+ @SerializedName("jingzhong")
+ val jingzhong: Double = 0.0,
+ @SerializedName("maozhong")
+ val maozhong: Double = 0.0,
+ @SerializedName("memo")
+ val memo: String = "",
+ @SerializedName("pizhong")
+ val pizhong: Double = 0.0,
+ @SerializedName("sysid")
+ val sysid: String = "",
+ @SerializedName("tihuoren")
+ val tihuoren: String = "",
+ @SerializedName("wldwname")
+ val wldwname: String = "",
+ @SerializedName("wldwsysid")
+ val wldwsysid: String = "",
+ @SerializedName("xiangzhen")
+ val xiangzhen: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/CommonResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/CommonResponse.kt
new file mode 100644
index 0000000..0f0e815
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/CommonResponse.kt
@@ -0,0 +1,16 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+/**
+ * 通用网络请求
+ * Data值为唯一值的
+ */
+data class CommonResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Any = Any(),
+ @SerializedName("msg")
+ val msg: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DeviceResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DeviceResponse.kt
new file mode 100644
index 0000000..369b44a
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DeviceResponse.kt
@@ -0,0 +1,33 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DeviceResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("Applicant")
+ val applicant: String = "",
+ @SerializedName("ClientType")
+ val clientType: String = "",
+ @SerializedName("Flag")
+ val flag: Int = 0,
+ @SerializedName("HardwareId")
+ val hardwareId: String = "",
+ @SerializedName("Job")
+ val job: String = "",
+ @SerializedName("Memo")
+ val memo: String = "",
+ @SerializedName("Phone")
+ val phone: String = "",
+ @SerializedName("Sysid")
+ val sysid: String = "",
+ @SerializedName("TenantCode")
+ val tenantCode: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonAirDetailByRFIDResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonAirDetailByRFIDResponse.kt
new file mode 100644
index 0000000..94a4d8c
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonAirDetailByRFIDResponse.kt
@@ -0,0 +1,33 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonAirDetailByRFIDResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("msg")
+ val msg: String = "",
+ @SerializedName("data")
+ val `data`: Data = Data()
+) {
+ data class Data(
+ @SerializedName("bagtype")
+ val bagtype: String = "",
+ @SerializedName("bagzhongliang")
+ val bagzhongliang: Double = 0.0,
+ @SerializedName("cjname")
+ val cjname: String = "",
+ @SerializedName("ckname")
+ val ckname: String = "",
+ @SerializedName("code")
+ val code: String = "",
+ @SerializedName("istanlianging")
+ val istanlianging: Int = 0,
+ @SerializedName("jiantypename")
+ val jiantypename: String = "",
+ @SerializedName("kcmaozhong")
+ val kcmaozhong: Double = 0.0,
+ @SerializedName("sysid")
+ val sysid: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonAirDetailListResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonAirDetailListResponse.kt
new file mode 100644
index 0000000..fb3c668
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonAirDetailListResponse.kt
@@ -0,0 +1,41 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonAirDetailListResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("bagrelease")
+ val bagrelease: String = "",
+ @SerializedName("chayizhongliang")
+ val chayizhongliang: Double = 0.0,
+ @SerializedName("cjname")
+ val cjname: String = "",
+ @SerializedName("ckname")
+ val ckname: String = "",
+ @SerializedName("code")
+ val code: String = "",
+ @SerializedName("endtime")
+ val endtime: String = "",
+ @SerializedName("fbendmaozhong")
+ val fbendmaozhong: Double = 0.0,
+ @SerializedName("fbstartmaozhong")
+ val fbstartmaozhong: Double = 0.0,
+ @SerializedName("jiantypename")
+ val jiantypename: String = "",
+ @SerializedName("kcmaozhong")
+ val kcmaozhong: Double = 0.0,
+ @SerializedName("starttime")
+ val starttime: String = "",
+ @SerializedName("status")
+ val status: String = "",
+ @SerializedName("sysid")
+ val sysid: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonAirDetailResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonAirDetailResponse.kt
new file mode 100644
index 0000000..4d0f852
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonAirDetailResponse.kt
@@ -0,0 +1,49 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonAirDetailResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("baoshu")
+ val baoshu: Int = 0,
+ @SerializedName("cjname")
+ val cjname: String = "",
+ @SerializedName("cjsysid")
+ val cjsysid: String = "",
+ @SerializedName("ckname")
+ val ckname: String = "",
+ @SerializedName("endtime")
+ val endtime: String = "",
+ @SerializedName("gjcksysid")
+ val gjcksysid: String = "",
+ @SerializedName("jiantype")
+ val jiantype: String = "",
+ @SerializedName("canpinzhong")
+ val canpinzhong: String = "",
+ @SerializedName("xiangzhen")
+ val xiangzhen: String = "",
+ @SerializedName("jiantypesysid")
+ val jiantypesysid: String = "",
+ @SerializedName("memo")
+ val memo: String = "",
+ @SerializedName("plantime")
+ val plantime: String = "",
+ @SerializedName("releaseBaoshu")
+ val releaseBaoshu: Int = 0,
+ @SerializedName("startime")
+ val startime: String = "",
+ @SerializedName("sysid")
+ val sysid: String = "",
+ @SerializedName("tanliangingbaoshu")
+ val tanliangingbaoshu: Int = 0,
+ @SerializedName("tanliangren")
+ val tanliangren: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonAirListResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonAirListResponse.kt
new file mode 100644
index 0000000..1074e4f
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonAirListResponse.kt
@@ -0,0 +1,43 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonAirListResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("baoshu")
+ val baoshu: Int = 0,
+ @SerializedName("cjname")
+ val cjname: String = "",
+ @SerializedName("cjsysid")
+ val cjsysid: String = "",
+ @SerializedName("ckname")
+ val ckname: String = "",
+ @SerializedName("endtime")
+ val endtime: String = "",
+ @SerializedName("gjcksysid")
+ val gjcksysid: String = "",
+ @SerializedName("jiantype")
+ val jiantype: String = "",
+ @SerializedName("xiangzhen")
+ val xiangzhen: String = "",
+ @SerializedName("jiantypesysid")
+ val jiantypesysid: String = "",
+ @SerializedName("plantime")
+ val plantime: String = "",
+ @SerializedName("startime")
+ val startime: String = "",
+ @SerializedName("sysid")
+ val sysid: String = "",
+ @SerializedName("tanliangingbaoshu")
+ val tanliangingbaoshu: Int = 0,
+ @SerializedName("tanliangren")
+ val tanliangren: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonAreaResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonAreaResponse.kt
new file mode 100644
index 0000000..ba11516
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonAreaResponse.kt
@@ -0,0 +1,11 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonAreaResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = "",
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonDealObjectResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonDealObjectResponse.kt
new file mode 100644
index 0000000..b717e2b
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonDealObjectResponse.kt
@@ -0,0 +1,29 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonDealObjectResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("address")
+ val address: String = "",
+ @SerializedName("code")
+ val code: String = "",
+ @SerializedName("contact")
+ val contact: String = "",
+ @SerializedName("name")
+ val name: String = "",
+ @SerializedName("phone")
+ val phone: String = "",
+ @SerializedName("sort")
+ val sort: Int = 0,
+ @SerializedName("sysid")
+ val sysid: String = ""
+ )
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInDetailResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInDetailResponse.kt
new file mode 100644
index 0000000..9da350a
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInDetailResponse.kt
@@ -0,0 +1,29 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonInDetailResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("code")
+ val code: String = "",
+ @SerializedName("jingzhong")
+ val jingzhong: Double = 0.0,
+ @SerializedName("maozhong")
+ val maozhong: Double = 0.0,
+ @SerializedName("pizhong")
+ val pizhong: Double = 0.0,
+ @SerializedName("rfid")
+ val rfid: String = "",
+ @SerializedName("rksysid")
+ val rksysid: String = "",
+ @SerializedName("sysid")
+ val sysid: String = ""
+ )
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInLevel.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInLevel.kt
new file mode 100644
index 0000000..6af7fe0
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInLevel.kt
@@ -0,0 +1,23 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonInLevel(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("msg")
+ val msg: String = "",
+ @SerializedName("data")
+ val `data`: List = listOf()
+) {
+ data class Data(
+ @SerializedName("name")
+ val name: String = "",
+ @SerializedName("sort")
+ val sort: Int = 0,
+ @SerializedName("type")
+ val type: Int = 0,//0:其他,1:正茧,2:下足茧
+ @SerializedName("sysid")
+ val sysid: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInListResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInListResponse.kt
new file mode 100644
index 0000000..89a983e
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInListResponse.kt
@@ -0,0 +1,57 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonInListResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("bagtype")
+ val bagtype: String = "",
+ @SerializedName("bagzhongliang")
+ val bagzhongliang: Double = 0.0,
+ @SerializedName("baoshu")
+ val baoshu: Int = 0,
+ @SerializedName("cjname")
+ val cjname: String = "",
+ @SerializedName("cjsysid")
+ val cjsysid: String = "",
+ @SerializedName("ckname")
+ val ckname: String = "",
+ @SerializedName("code")
+ val code: String = "",
+ @SerializedName("cpzname")
+ val cpzname: String = "",
+ @SerializedName("datetime")
+ val datetime: String = "",
+ @SerializedName("depname")
+ val depname: String = "",
+ @SerializedName("gjcksysid")
+ val gjcksysid: String = "",
+ @SerializedName("hongjianren")
+ val hongjianren: String = "",
+ @SerializedName("jiantype")
+ val jiantype: String = "",
+ @SerializedName("jiantypesysid")
+ val jiantypesysid: String = "",
+ @SerializedName("jingzhong")
+ val jingzhong: Double = 0.0,
+ @SerializedName("maozhong")
+ val maozhong: Double = 0.0,
+ @SerializedName("pizhong")
+ val pizhong: Double = 0.0,
+ @SerializedName("rukuren")
+ val rukuren: String = "",
+ @SerializedName("standardtype")
+ val standardtype: String = "",
+ @SerializedName("sysid")
+ val sysid: String = "",
+ @SerializedName("xiangzhen")
+ val xiangzhen: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInPackageType.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInPackageType.kt
new file mode 100644
index 0000000..9d9fff4
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInPackageType.kt
@@ -0,0 +1,23 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonInPackageType(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("msg")
+ val msg: String = "",
+ @SerializedName("data")
+ val `data`: List = listOf()
+) {
+ data class Data(
+ @SerializedName("name")
+ val name: String = "",
+ @SerializedName("sort")
+ val sort: Int = 0,
+ @SerializedName("sysid")
+ val sysid: String = "",
+ @SerializedName("weight")
+ val weight: Double = 0.0
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInStatisticsResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInStatisticsResponse.kt
new file mode 100644
index 0000000..faaa474
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInStatisticsResponse.kt
@@ -0,0 +1,24 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonInStatisticsResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("total_baoshu")
+ val totalBaoshu: Int = 0,
+ @SerializedName("total_jingzhong")
+ val totalJingzhong: Double = 0.0,
+ @SerializedName("total_maozhong")
+ val totalMaozhong: Double = 0.0,
+ @SerializedName("total_num")
+ val totalNum: Int = 0,
+ @SerializedName("total_pizhong")
+ val totalPizhong: Double = 0.0
+ )
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInStore.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInStore.kt
new file mode 100644
index 0000000..7e6b734
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInStore.kt
@@ -0,0 +1,27 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonInStore(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("cangkucode")
+ val cangkucode: String = "",
+ @SerializedName("cangkuname")
+ val cangkuname: String = "",
+ @SerializedName("depname")
+ val depname: String = "",
+ @SerializedName("depsysid")
+ val depsysid: String = "",
+ @SerializedName("sort")
+ val sort: Int = 0,
+ @SerializedName("sysid")
+ val sysid: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInType.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInType.kt
new file mode 100644
index 0000000..3a9e289
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonInType.kt
@@ -0,0 +1,21 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonInType(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("msg")
+ val msg: String = "",
+ @SerializedName("data")
+ val `data`: List = listOf()
+) {
+ data class Data(
+ @SerializedName("name")
+ val name: String = "",
+ @SerializedName("sort")
+ val sort: Int = 0,
+ @SerializedName("sysid")
+ val sysid: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonOutDetailResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonOutDetailResponse.kt
new file mode 100644
index 0000000..25cac4b
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonOutDetailResponse.kt
@@ -0,0 +1,29 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonOutDetailResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("code")
+ val code: String = "",
+ @SerializedName("jingzhong")
+ val jingzhong: Double = 0.0,
+ @SerializedName("maozhong")
+ val maozhong: Double = 0.0,
+ @SerializedName("pizhong")
+ val pizhong: Double = 0.0,
+ @SerializedName("rfid")
+ val rfid: String = "",
+ @SerializedName("rksysid")
+ val rksysid: String = "",
+ @SerializedName("sysid")
+ val sysid: String = ""
+ )
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonOutListResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonOutListResponse.kt
new file mode 100644
index 0000000..1def79f
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonOutListResponse.kt
@@ -0,0 +1,59 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonOutListResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("bagtype")
+ val bagtype: String = "",
+ @SerializedName("bagzhongliang")
+ val bagzhongliang: Double = 0.0,
+ @SerializedName("baoshu")
+ val baoshu: Int = 0,
+ @SerializedName("canpinzhong")
+ val canpinzhong: String = "",
+ @SerializedName("carpaihao")
+ val carpaihao: String = "",
+ @SerializedName("chukuren")
+ val chukuren: String = "",
+ @SerializedName("cjname")
+ val cjname: String = "",
+ @SerializedName("cjsysid")
+ val cjsysid: String = "",
+ @SerializedName("ckdatetime")
+ val ckdatetime: String = "",
+ @SerializedName("ckname")
+ val ckname: String = "",
+ @SerializedName("code")
+ val code: String = "",
+ @SerializedName("gjcksysid")
+ val gjcksysid: String = "",
+ @SerializedName("jiantype")
+ val jiantype: String = "",
+ @SerializedName("jiantypesysid")
+ val jiantypesysid: String = "",
+ @SerializedName("jingzhong")
+ val jingzhong: Double = 0.0,
+ @SerializedName("maozhong")
+ val maozhong: Double = 0.0,
+ @SerializedName("pizhong")
+ val pizhong: Double = 0.0,
+ @SerializedName("sysid")
+ val sysid: String = "",
+ @SerializedName("tihuoren")
+ val tihuoren: String = "",
+ @SerializedName("wldwname")
+ val wldwname: String = "",
+ @SerializedName("wldwsysid")
+ val wldwsysid: String = "",
+ @SerializedName("xiangzhen")
+ val xiangzhen: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonOutStatisticsResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonOutStatisticsResponse.kt
new file mode 100644
index 0000000..723df5a
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonOutStatisticsResponse.kt
@@ -0,0 +1,24 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonOutStatisticsResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("total_baoshu")
+ val totalBaoshu: Int = 0,
+ @SerializedName("total_jingzhong")
+ val totalJingzhong: Double = 0.0,
+ @SerializedName("total_maozhong")
+ val totalMaozhong: Double = 0.0,
+ @SerializedName("total_num")
+ val totalNum: Int = 0,
+ @SerializedName("total_pizhong")
+ val totalPizhong: Double = 0.0
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonPackageInfoResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonPackageInfoResponse.kt
new file mode 100644
index 0000000..bdafc50
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonPackageInfoResponse.kt
@@ -0,0 +1,33 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonPackageInfoResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("bagtype")
+ val bagtype: String = "",
+ @SerializedName("bagzhongliang")
+ val bagzhongliang: Double = 0.0,
+ @SerializedName("cjname")
+ val cjname: String = "",
+ @SerializedName("ckname")
+ val ckname: String = "",
+ @SerializedName("code")
+ val code: String = "",
+ @SerializedName("fanbaotanlianging")
+ val fanbaotanlianging: Int = 0,
+ @SerializedName("jiantypename")
+ val jiantypename: String = "",
+ @SerializedName("kcmaozhong")
+ val kcmaozhong: Double = 0.0,
+ @SerializedName("sysid")
+ val sysid: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonSeason.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonSeason.kt
new file mode 100644
index 0000000..f0962c1
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonSeason.kt
@@ -0,0 +1,25 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonSeason(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("batchname")
+ val batchname: String = "",
+ @SerializedName("bathcyears")
+ val bathcyears: Int = 0,
+ @SerializedName("def")
+ val def: Int = 0,
+ @SerializedName("sort")
+ val sort: Int = 0,
+ @SerializedName("sysid")
+ val sysid: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonStoreDetailListResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonStoreDetailListResponse.kt
new file mode 100644
index 0000000..732d3bc
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonStoreDetailListResponse.kt
@@ -0,0 +1,33 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonStoreDetailListResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("itemcode")
+ val itemcode: String = "",
+ @SerializedName("jingzhong")
+ val jingzhong: Double = 0.0,
+ @SerializedName("maozhong")
+ val maozhong: Double = 0.0,
+ @SerializedName("pizhong")
+ val pizhong: Double = 0.0,
+ @SerializedName("rfid")
+ val rfid: String = "",
+ @SerializedName("rkdcode")
+ val rkdcode: String = "",
+ @SerializedName("status")
+ val status: String = "",
+ @SerializedName("sysid")
+ val sysid: String = "",
+ @SerializedName("xiangzhen")
+ val xiangzhen: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonStoreForceOutDetailListResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonStoreForceOutDetailListResponse.kt
new file mode 100644
index 0000000..4bf0a6c
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonStoreForceOutDetailListResponse.kt
@@ -0,0 +1,25 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonStoreForceOutDetailListResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("cktime")
+ val cktime: String = "",
+ @SerializedName("jingzhong")
+ val jingzhong: Double = 0.0,
+ @SerializedName("maozhong")
+ val maozhong: Double = 0.0,
+ @SerializedName("pizhong")
+ val pizhong: Double = 0.0,
+ @SerializedName("sysid")
+ val sysid: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonStoreProcessDetailListResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonStoreProcessDetailListResponse.kt
new file mode 100644
index 0000000..3aca743
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonStoreProcessDetailListResponse.kt
@@ -0,0 +1,23 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonStoreProcessDetailListResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("code")
+ val code: String = "",
+ @SerializedName("memo")
+ val memo: String = "",
+ @SerializedName("time")
+ val time: String = "",
+ @SerializedName("type")
+ val type: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonTicketInfoResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonTicketInfoResponse.kt
new file mode 100644
index 0000000..fe46389
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryCocoonTicketInfoResponse.kt
@@ -0,0 +1,28 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryCocoonTicketInfoResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("canpinzhong")
+ val canpinzhong: String = "",
+ @SerializedName("cjname")
+ val cjname: String = "",
+ @SerializedName("code")
+ val code: String = "1234567890",
+ @SerializedName("depname")
+ val depname: String = "",
+ @SerializedName("jiantype")
+ val jiantype: String = "",
+ @SerializedName("maozhong")
+ val maozhong: Double = 0.0
+ )
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryStoreListRequest.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryStoreListRequest.kt
new file mode 100644
index 0000000..d22391f
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryStoreListRequest.kt
@@ -0,0 +1,16 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryStoreListRequest(
+ @SerializedName("cjsysid")
+ val cjsysid: String = "",
+ @SerializedName("gjcksysid")
+ val gjcksysid: String = "",
+ @SerializedName("isempty")
+ val isempty: Int = 0, // 是否标记全部出库(0:全部,1:未标记≈实时库存,2:已标记)
+ @SerializedName("jiantypesysid")
+ val jiantypesysid: String = "",
+ @SerializedName("like")
+ val like: String = ""
+)
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryStoreListResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryStoreListResponse.kt
new file mode 100644
index 0000000..d741121
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/DryStoreListResponse.kt
@@ -0,0 +1,37 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DryStoreListResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("baoshu")
+ val baoshu: Int = 0,
+ @SerializedName("cjname")
+ val cjname: String = "",
+ @SerializedName("cjsysid")
+ val cjsysid: String = "",
+ @SerializedName("gjckcode")
+ val gjckcode: String = "",
+ @SerializedName("gjckname")
+ val gjckname: String = "",
+ @SerializedName("gjcksysid")
+ val gjcksysid: String = "",
+ @SerializedName("isempty")
+ val isempty: Int = 0,
+ @SerializedName("jiantypename")
+ val jiantypename: String = "",
+ @SerializedName("jiantypesysid")
+ val jiantypesysid: String = "",
+ @SerializedName("jingzhong")
+ val jingzhong: Double = 0.0,
+ @SerializedName("sysid")
+ val sysid: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/EditPasswordResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/EditPasswordResponse.kt
new file mode 100644
index 0000000..aa989de
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/EditPasswordResponse.kt
@@ -0,0 +1,10 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class EditPasswordResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("msg")
+ val msg: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/ExtendInfoResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/ExtendInfoResponse.kt
new file mode 100644
index 0000000..89b9634
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/ExtendInfoResponse.kt
@@ -0,0 +1,35 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class ExtendInfoResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("addyear")
+ val addyear: String = "",
+ @SerializedName("colname")
+ val colname: String = "",
+ @SerializedName("colsysid")
+ val colsysid: String = "",
+ @SerializedName("coltitle")
+ val coltitle: String = "",
+ @SerializedName("coltype")
+ val coltype: Int = 0,
+ @SerializedName("colvalue")
+ val colvalue: String? = null,
+ @SerializedName("extendsysid")
+ val extendsysid: String = "",
+ @SerializedName("isnotnull")
+ val isnotnull: Boolean = false,
+ @SerializedName("nhsysid")
+ val nhsysid: String = "",
+ @SerializedName("sort")
+ val sort: Int = 0
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FaceListResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FaceListResponse.kt
new file mode 100644
index 0000000..e7e7a67
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FaceListResponse.kt
@@ -0,0 +1,21 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class FaceListResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("bucketname")
+ val bucketname: String = "",
+ @SerializedName("facetoken")
+ val facetoken: String = "",
+ @SerializedName("objectname")
+ val objectname: String = ""
+ )
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FaceRecognizeResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FaceRecognizeResponse.kt
new file mode 100644
index 0000000..b04edbd
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FaceRecognizeResponse.kt
@@ -0,0 +1,36 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class FaceRecognizeResponse(
+ @SerializedName("cached")
+ val cached: Int = 0,
+ @SerializedName("error_code")
+ val errorCode: Int = 0,
+ @SerializedName("error_msg")
+ val errorMsg: String = "",
+ @SerializedName("log_id")
+ val logId: Long = 0,
+ @SerializedName("result")
+ val result: Result = Result(),
+ @SerializedName("timestamp")
+ val timestamp: Long = 0
+) {
+ data class Result(
+ @SerializedName("face_token")
+ val faceToken: String = "",
+ @SerializedName("user_list")
+ val userList: List = listOf()
+ ) {
+ data class User(
+ @SerializedName("group_id")
+ val groupId: String = "",
+ @SerializedName("score")
+ val score: Double = 0.0,
+ @SerializedName("user_id")
+ val userId: String = "",
+ @SerializedName("user_info")
+ val userInfo: String = ""
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FaceRegisterResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FaceRegisterResponse.kt
new file mode 100644
index 0000000..5003ea5
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FaceRegisterResponse.kt
@@ -0,0 +1,38 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class FaceRegisterResponse(
+ @SerializedName("cached")
+ val cached: Int = 0,
+ @SerializedName("error_code")
+ val errorCode: Int = 0,
+ @SerializedName("error_msg")
+ val errorMsg: String = "",
+ @SerializedName("log_id")
+ val logId: Long = 0,
+ @SerializedName("result")
+ val result: Result = Result(),
+ @SerializedName("timestamp")
+ val timestamp: Long = 0
+) {
+ data class Result(
+ @SerializedName("face_token")
+ val faceToken: String = "",
+ @SerializedName("location")
+ val location: Location = Location()
+ ) {
+ data class Location(
+ @SerializedName("height")
+ val height: Long = 0,
+ @SerializedName("left")
+ val left: Double = 0.0,
+ @SerializedName("rotation")
+ val rotation: Long = 0,
+ @SerializedName("top")
+ val top: Double = 0.0,
+ @SerializedName("width")
+ val width: Long = 0
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FarmerAreaForCun.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FarmerAreaForCun.kt
new file mode 100644
index 0000000..7ea097a
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FarmerAreaForCun.kt
@@ -0,0 +1,18 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+data class FarmerAreaForCun(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("Count")
+ val count: Int = 0,
+ @SerializedName("Cun")
+ val cun: String = ""
+ )
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FarmerAreaForXian.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FarmerAreaForXian.kt
new file mode 100644
index 0000000..4239435
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FarmerAreaForXian.kt
@@ -0,0 +1,19 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+
+
+data class FarmerAreaForXian(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("Count")
+ val count: Int = 0,
+ @SerializedName("Xian")
+ val xian: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FarmerAreaForXiang.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FarmerAreaForXiang.kt
new file mode 100644
index 0000000..fe9c1a9
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FarmerAreaForXiang.kt
@@ -0,0 +1,19 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+
+data class FarmerAreaForXiang(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("Count")
+ val count: Int = 0,
+ @SerializedName("Xiang")
+ val xiang: String = ""
+ )
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FarmerAreaForZu.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FarmerAreaForZu.kt
new file mode 100644
index 0000000..654d077
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FarmerAreaForZu.kt
@@ -0,0 +1,18 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+
+data class FarmerAreaForZu(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("Count")
+ val count: Int = 0,
+ @SerializedName("Zu")
+ val zu: String = ""
+ )
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FarmerDetailResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FarmerDetailResponse.kt
new file mode 100644
index 0000000..6ea584b
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FarmerDetailResponse.kt
@@ -0,0 +1,61 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class FarmerDetailResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("BankCode")
+ val bankCode: String = "",
+ @SerializedName("BankCode2")
+ val bankCode2: String = "",
+ @SerializedName("BankName")
+ val bankName: String = "",
+ @SerializedName("BankName2")
+ val bankName2: String = "",
+ @SerializedName("BankShortName")
+ val bankShortName: String = "",
+ @SerializedName("BankShortName2")
+ val bankShortName2: String = "",
+ @SerializedName("Cun")
+ val cun: String = "",
+ @SerializedName("DepartmentSysid")
+ val departmentSysid: String = "",
+ @SerializedName("IcCardCode")
+ val icCardCode: Long = 0,
+ @SerializedName("IdCard")
+ val idCard: String = "",
+ @SerializedName("IdCardAddress")
+ val idCardAddress: String = "",
+ @SerializedName("NhName")
+ val nhName: String = "",
+ @SerializedName("NhTips")
+ val nhTips: String = "",
+ @SerializedName("Phone")
+ val phone: String = "",
+ @SerializedName("PropertyName")
+ val propertyName: String = "",
+ @SerializedName("PropertySysid")
+ val propertySysid: String = "",
+ @SerializedName("RecBankCode")
+ val recBankCode: String = "",
+ @SerializedName("RecBankCode2")
+ val recBankCode2: String = "",
+ @SerializedName("Sex")
+ val sex: String = "",
+ @SerializedName("Sysid")
+ val sysid: String = "",
+ @SerializedName("Xian")
+ val xian: String = "",
+ @SerializedName("Xiang")
+ val xiang: String = "",
+ @SerializedName("Zu")
+ val zu: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FarmerFileListResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FarmerFileListResponse.kt
new file mode 100644
index 0000000..ef68380
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FarmerFileListResponse.kt
@@ -0,0 +1,28 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class FarmerFileListResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("attname")
+ val attname: String = "",
+ @SerializedName("bucketname")
+ val bucketname: String = "",
+ @SerializedName("objectname")
+ val objectname: String = "",
+ @SerializedName("size")
+ val size: Int = 0,
+ @SerializedName("suffix")
+ val suffix: String = "",
+ @SerializedName("sysid")
+ val sysid: String = ""
+ )
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FrpConfigResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FrpConfigResponse.kt
new file mode 100644
index 0000000..c8221ac
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FrpConfigResponse.kt
@@ -0,0 +1,28 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+
+
+/**
+ *
+ * @Author DuanKaiji
+ * @CreateTime 2025年4月7日
+ */
+data class FrpConfigResponse(
+ @SerializedName("Code")
+ val code: String = "",
+ @SerializedName("Data")
+ val `data`: Data = Data()
+) {
+ data class Data(
+ @SerializedName("Code")
+ val code: String = "",
+ @SerializedName("LocalPort")
+ val localPort: String = "",
+ @SerializedName("Name")
+ val name: String = "",
+ @SerializedName("ServicePort")
+ val servicePort: String = "",
+ @SerializedName("ServiceUrl")
+ val serviceUrl: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FrpNewVersionResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FrpNewVersionResponse.kt
new file mode 100644
index 0000000..2c23153
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FrpNewVersionResponse.kt
@@ -0,0 +1,23 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class FrpNewVersionResponse(
+ @SerializedName("Code")
+ val code: String = "",
+ @SerializedName("Data")
+ val `data`: Data = Data(),
+ @SerializedName("Message")
+ val message: String = ""
+) {
+ data class Data(
+ @SerializedName("Url")
+ val url: String = "",
+ @SerializedName("VersionDescription")
+ val versionDescription: String = "",
+ @SerializedName("VersionName")
+ val versionName: String = "",
+ @SerializedName("VersionNumber")
+ val versionNumber: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FundsListResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FundsListResponse.kt
new file mode 100644
index 0000000..6f91a35
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FundsListResponse.kt
@@ -0,0 +1,37 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class FundsListResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("ActualpayMoney")
+ val actualpayMoney: Double = 0.0,
+ @SerializedName("BillCode")
+ val billCode: Long = 0,
+ @SerializedName("BillMoney")
+ val billMoney: Double = 0.0,
+ @SerializedName("CzSysid")
+ val czSysid: String = "",
+ @SerializedName("NhBankCode")
+ val nhBankCode: String = "",
+ @SerializedName("NhName")
+ val nhName: String = "",
+ @SerializedName("NhSysid")
+ val nhSysid: String = "",
+ @SerializedName("PayDateTime")
+ val payDateTime: String = "",
+ @SerializedName("PayState")
+ val payState: String = "",
+ @SerializedName("PayStateValue")
+ val payStateValue: Int = 0,
+ @SerializedName("SendWinDate")
+ val sendWinDate: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FundsTotalListResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FundsTotalListResponse.kt
new file mode 100644
index 0000000..6a97147
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/FundsTotalListResponse.kt
@@ -0,0 +1,23 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+
+
+data class FundsTotalListResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("Paystate")
+ val paystate: String = "",
+ @SerializedName("Summoney")
+ val summoney: Double = 0.0,
+ @SerializedName("Total")
+ val total: Int = 0,
+ @SerializedName("PaystateValue")
+ val payStateValue: Int = 0
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/IdCardAddress.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/IdCardAddress.kt
new file mode 100644
index 0000000..50d0e8a
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/IdCardAddress.kt
@@ -0,0 +1,24 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class IdCardAddress(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("cun")
+ val cun: String = "",
+ @SerializedName("xian")
+ val xian: String = "",
+ @SerializedName("xiang")
+ val xiang: String = "",
+ @SerializedName("zu")
+ val zu: String = ""
+ )
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/IdentityBankCardResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/IdentityBankCardResponse.kt
new file mode 100644
index 0000000..d786907
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/IdentityBankCardResponse.kt
@@ -0,0 +1,74 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class IdentityBankCardResponse(
+ @SerializedName("algo_version")
+ val algoVersion: String = "",
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("ftype")
+ val ftype: Int = 0,
+ @SerializedName("height")
+ val height: Int = 0,
+ @SerializedName("orgHeight")
+ val orgHeight: Int = 0,
+ @SerializedName("orgWidth")
+ val orgWidth: Int = 0,
+ @SerializedName("prism_keyValueInfo")
+ val prismKeyValueInfo: List = listOf(),
+ @SerializedName("sliceRect")
+ val sliceRect: SliceRect = SliceRect(),
+ @SerializedName("width")
+ val width: Int = 0
+) {
+ data class Data(
+ @SerializedName("bankName")
+ val bankName: String = "",
+ @SerializedName("cardNumber")
+ val cardNumber: String = "",
+ @SerializedName("cardType")
+ val cardType: String = "",
+ @SerializedName("validToDate")
+ val validToDate: String = ""
+ )
+
+ data class PrismKeyValueInfo(
+ @SerializedName("key")
+ val key: String = "",
+ @SerializedName("keyProb")
+ val keyProb: Int = 0,
+ @SerializedName("value")
+ val value: String = "",
+ @SerializedName("valuePos")
+ val valuePos: List = listOf(),
+ @SerializedName("valueProb")
+ val valueProb: Int = 0
+ ) {
+ data class ValuePo(
+ @SerializedName("x")
+ val x: Int = 0,
+ @SerializedName("y")
+ val y: Int = 0
+ )
+ }
+
+ data class SliceRect(
+ @SerializedName("x0")
+ val x0: Int = 0,
+ @SerializedName("x1")
+ val x1: Int = 0,
+ @SerializedName("x2")
+ val x2: Int = 0,
+ @SerializedName("x3")
+ val x3: Int = 0,
+ @SerializedName("y0")
+ val y0: Int = 0,
+ @SerializedName("y1")
+ val y1: Int = 0,
+ @SerializedName("y2")
+ val y2: Int = 0,
+ @SerializedName("y3")
+ val y3: Int = 0
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/IdentityIDCardResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/IdentityIDCardResponse.kt
new file mode 100644
index 0000000..5241c69
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/IdentityIDCardResponse.kt
@@ -0,0 +1,100 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class IdentityIDCardResponse(
+ @SerializedName("algo_version")
+ val algoVersion: String = "",
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("height")
+ val height: Int = 0,
+ @SerializedName("orgHeight")
+ val orgHeight: Int = 0,
+ @SerializedName("orgWidth")
+ val orgWidth: Int = 0,
+ @SerializedName("width")
+ val width: Int = 0
+) {
+ data class Data(
+ @SerializedName("face")
+ val face: Face = Face()
+ ) {
+ data class Face(
+ @SerializedName("algo_version")
+ val algoVersion: String = "",
+ @SerializedName("angle")
+ val angle: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("ftype")
+ val ftype: Int = 0,
+ @SerializedName("height")
+ val height: Int = 0,
+ @SerializedName("orgHeight")
+ val orgHeight: Int = 0,
+ @SerializedName("orgWidth")
+ val orgWidth: Int = 0,
+ @SerializedName("prism_keyValueInfo")
+ val prismKeyValueInfo: List = listOf(),
+ @SerializedName("sliceRect")
+ val sliceRect: SliceRect = SliceRect(),
+ @SerializedName("width")
+ val width: Int = 0
+ ) {
+ data class Data(
+ @SerializedName("address")
+ val address: String = "",
+ @SerializedName("birthDate")
+ val birthDate: String = "",
+ @SerializedName("ethnicity")
+ val ethnicity: String = "",
+ @SerializedName("idNumber")
+ val idNumber: String = "",
+ @SerializedName("name")
+ val name: String = "",
+ @SerializedName("sex")
+ val sex: String = ""
+ )
+
+ data class PrismKeyValueInfo(
+ @SerializedName("key")
+ val key: String = "",
+ @SerializedName("keyProb")
+ val keyProb: Int = 0,
+ @SerializedName("value")
+ val value: String = "",
+ @SerializedName("valuePos")
+ val valuePos: List = listOf(),
+ @SerializedName("valueProb")
+ val valueProb: Int = 0
+ ) {
+ data class ValuePo(
+ @SerializedName("x")
+ val x: Int = 0,
+ @SerializedName("y")
+ val y: Int = 0
+ )
+ }
+
+ data class SliceRect(
+ @SerializedName("x0")
+ val x0: Int = 0,
+ @SerializedName("x1")
+ val x1: Int = 0,
+ @SerializedName("x2")
+ val x2: Int = 0,
+ @SerializedName("x3")
+ val x3: Int = 0,
+ @SerializedName("y0")
+ val y0: Int = 0,
+ @SerializedName("y1")
+ val y1: Int = 0,
+ @SerializedName("y2")
+ val y2: Int = 0,
+ @SerializedName("y3")
+ val y3: Int = 0
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/LoginResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/LoginResponse.kt
new file mode 100644
index 0000000..8139393
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/LoginResponse.kt
@@ -0,0 +1,25 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class LoginResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("AccessToken")
+ val accessToken: String = "",
+ @SerializedName("ExpiresIn")
+ val expiresIn: Int = 0,
+ @SerializedName("RefreshToken")
+ val refreshToken: String = "",
+ @SerializedName("RefreshTokenExpiresIn")
+ val refreshTokenExpiresIn: Int = 0
+ )
+}
+
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/MenuPermissionResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/MenuPermissionResponse.kt
new file mode 100644
index 0000000..2dffbc7
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/MenuPermissionResponse.kt
@@ -0,0 +1,25 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+data class MenuPermissionResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("Level")
+ val level: Int = 0,
+ @SerializedName("Nmae")
+ val nmae: String = "",
+ @SerializedName("ParentSysid")
+ val parentSysid: String = "",
+ @SerializedName("Sort")
+ val sort: Int = 0,
+ @SerializedName("Sysid")
+ val sysid: String = "",
+ @SerializedName("Uniquecode")
+ val uniquecode: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/OSSConfigResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/OSSConfigResponse.kt
new file mode 100644
index 0000000..bc5bcf0
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/OSSConfigResponse.kt
@@ -0,0 +1,31 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class OSSConfigResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("AccessKeyId")
+ val accessKeyId: String = "",
+ @SerializedName("AccessKeySecret")
+ val accessKeySecret: String = "",
+ @SerializedName("BucketName")
+ val bucketName: String = "",
+ @SerializedName("EndPoint")
+ val endPoint: String = "",
+ @SerializedName("Expiration")
+ val expiration: String = "",
+ @SerializedName("OssPath")
+ val ossPath: String = "",
+ @SerializedName("Region")
+ val region: String = "",
+ @SerializedName("SecurityToken")
+ val securityToken: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/PurchaseDataDetailResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/PurchaseDataDetailResponse.kt
new file mode 100644
index 0000000..5849d64
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/PurchaseDataDetailResponse.kt
@@ -0,0 +1,12 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class PurchaseDataDetailResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: PurchaseDataResponse.Data= PurchaseDataResponse.Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/PurchaseDataResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/PurchaseDataResponse.kt
new file mode 100644
index 0000000..671f678
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/PurchaseDataResponse.kt
@@ -0,0 +1,102 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class PurchaseDataResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("ActualPayMoney")
+ val actualPayMoney: Double = 0.0,
+ @SerializedName("BatchCjSysid")
+ val batchCjSysid: String = "",
+ @SerializedName("BillCode")
+ val billCode: Long = 0,
+ @SerializedName("BillMoneySum")
+ val billMoneySum: Double = 0.0,
+ @SerializedName("BillState")
+ val billState: String = "",//单据状态:-1弃售、0标记异常、1正常(默认)、2确认销售
+ @SerializedName("BillStateValue")
+ val billStateValue: Int = 0,//单据状态:-1弃售、0标记异常、1正常(默认)、2确认销售
+ @SerializedName("CashMoney")
+ val cashMoney: Double = 0.0,
+ @SerializedName("ChengZhongItemSumList")
+ val chengZhongItemSumList: List = listOf(),
+ @SerializedName("CzSysid")
+ val czSysid: String = "",
+ @SerializedName("CzrName")
+ val czrName: String = "",
+ @SerializedName("DepCode")
+ val depCode: String = "",
+ @SerializedName("DepName")
+ val depName: String = "",
+ @SerializedName("IdCardAddress")
+ val idCardAddress: String = "",
+ @SerializedName("IsKouPiing")
+ val isKouPiing: Boolean = false,
+ @SerializedName("IspPicing")
+ val ispPicing: Boolean = false,
+ @SerializedName("JweightSum")
+ val jweightSum: Double = 0.0,
+ @SerializedName("NhBankCode")
+ val nhBankCode: String = "",
+ @SerializedName("BankName")
+ val bankName: String = "",
+ @SerializedName("NhIdCard")
+ val nhIdCard: String = "",
+ @SerializedName("NhName")
+ val nhName: String = "",
+ @SerializedName("NhPhone")
+ val nhPhone: String = "",
+ @SerializedName("NhSysid")
+ val nhSysid: String = "",
+ @SerializedName("PayMoney")
+ val payMoney: Double = 0.0,
+ @SerializedName("PayState")
+ val payState: String = "",//支付状态:-1签名成功、0待支付、1支付异常、2转账中、3已电子支付、4电子支付失败、5特殊异常、6已现金支付、7已混合支付、
+ @SerializedName("PayStateValue")
+ val payStateValue: Int = 0,//支付状态:-1签名成功、0待支付、1支付异常、2转账中、3已电子支付、4电子支付失败、5特殊异常、6已现金支付、7已混合支付、
+ @SerializedName("PayType")
+ val payType: String = "",
+ @SerializedName("PayUserName")
+ val payUserName: String = "",
+ @SerializedName("PjdjName")
+ val pjdjName: String = "",
+ @SerializedName("SgDateTime")
+ val sgDateTime: String = ""
+ ) {
+ data class ChengZhongItemSum(
+ @SerializedName("BoxCount")
+ val boxCount: Int = 0,
+ @SerializedName("BoxName")
+ val boxName: String = "",
+ @SerializedName("CzSysid")
+ val czSysid: String = "",
+ @SerializedName("JweightSum")
+ val jweightSum: Double = 0.0,
+ @SerializedName("KweightSum")
+ val kweightSum: Double = 0.0,
+ @SerializedName("MoneySum")
+ val moneySum: Double = 0.0,
+ @SerializedName("MweightSum")
+ val mweightSum: Double = 0.0,
+ @SerializedName("Price")
+ val price: Double = 0.0,
+ @SerializedName("PweightSum")
+ val pweightSum: Double = 0.0,
+ @SerializedName("SgTypeName")
+ val sgTypeName: String = "",
+ @SerializedName("SgTypeSysid")
+ val sgTypeSysid: String = "",
+ @SerializedName("Sysid")
+ val sysid: String = "",
+ @SerializedName("WeightCount")
+ val weightCount: Int = 0
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/PurchaseDetailListResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/PurchaseDetailListResponse.kt
new file mode 100644
index 0000000..2d3007b
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/PurchaseDetailListResponse.kt
@@ -0,0 +1,45 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+
+
+data class PurchaseDetailListResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("CzSysid")
+ val czSysid: String = "",
+ @SerializedName("Sysid")
+ val sysid: String = "",
+ @SerializedName("净重")
+ val netWeight: Double = 0.0,
+ @SerializedName("包装")
+ val packaging: String = "",
+ @SerializedName("单价")
+ val unitPrice: Double = 0.0,
+ @SerializedName("小计")
+ val subtotal: Double = 0.0,
+ @SerializedName("扣重")
+ val deductedWeight: Double = 0.0,
+ @SerializedName("时间")
+ val time: String = "",
+ @SerializedName("毛重")
+ val grossWeight: Double = 0.0,
+ @SerializedName("皮重")
+ val tareWeight: Double = 0.0,
+ @SerializedName("磅次")
+ val weighingTimes: Int = 0,
+ @SerializedName("筐数")
+ val basketCount: Int = 0,
+ @SerializedName("类别名称")
+ val categoryName: String = "",
+ @SerializedName("车号")
+ val vehicleNumber: Int = 0,
+ @SerializedName("车重")
+ val vehicleWeight: Double = 0.0
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/PurchaseDetailResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/PurchaseDetailResponse.kt
new file mode 100644
index 0000000..1f8faa4
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/PurchaseDetailResponse.kt
@@ -0,0 +1,39 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class PurchaseDetailResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("depname")
+ val depname: String = "",
+ @SerializedName("depsysid")
+ val depsysid: String = "",
+ @SerializedName("hjjweightsum")
+ val hjjweightsum: Double = 0.0,
+ @SerializedName("hjmoneysum")
+ val hjmoneysum: Double = 0.0,
+ @SerializedName("hjprice")
+ val hjprice: Double = 0.0,
+ @SerializedName("scjweightsum")
+ val scjweightsum: Double = 0.0,
+ @SerializedName("scmoneysum")
+ val scmoneysum: Double = 0.0,
+ @SerializedName("scprice")
+ val scprice: Double = 0.0,
+ @SerializedName("xzjweightsum")
+ val xzjweightsum: Double = 0.0,
+ @SerializedName("xzmoneysum")
+ val xzmoneysum: Double = 0.0,
+ @SerializedName("xzprice")
+ val xzprice: Double = 0.0,
+ @SerializedName("zhidan")
+ val zhidan: Int = 0
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/PurchaseIndexInfoResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/PurchaseIndexInfoResponse.kt
new file mode 100644
index 0000000..cd1d3b1
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/PurchaseIndexInfoResponse.kt
@@ -0,0 +1,31 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+
+
+data class PurchaseIndexInfoResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("Describe")
+ val describe: String = "",
+ @SerializedName("IsAllowNull")
+ val isAllowNull: Boolean = false,
+ @SerializedName("MaxValue")
+ val maxValue: Int = 0,
+ @SerializedName("MinValue")
+ val minValue: Int = 0,
+ @SerializedName("Name")
+ val name: String = "",
+ @SerializedName("Sort")
+ val sort: Int = 0,
+ @SerializedName("Sysid")
+ val sysid: String = "",
+ @SerializedName("ValueType")
+ val valueType: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/QueryAllStoreInfoResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/QueryAllStoreInfoResponse.kt
new file mode 100644
index 0000000..740fb7f
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/QueryAllStoreInfoResponse.kt
@@ -0,0 +1,39 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class QueryAllStoreInfoResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("baoshu")
+ val baoshu: Int = 0,
+ @SerializedName("cjname")
+ val cjname: String = "",
+ @SerializedName("cjsort")
+ val cjsort: Int = 0,
+ @SerializedName("cjsysid")
+ val cjsysid: String = "",
+ @SerializedName("ckcode")
+ val ckcode: String = "",
+ @SerializedName("ckname")
+ val ckname: String = "",
+ @SerializedName("cksort")
+ val cksort: Int = 0,
+ @SerializedName("gjcksysid")
+ val gjcksysid: String = "",
+ @SerializedName("jiantypename")
+ val jiantypename: String = "",
+ @SerializedName("jiantypesort")
+ val jiantypesort: Int = 0,
+ @SerializedName("jiantypesysid")
+ val jiantypesysid: String = "",
+ @SerializedName("jingzhong")
+ val jingzhong: Double = 0.0
+ )
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/RefreshTokenResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/RefreshTokenResponse.kt
new file mode 100644
index 0000000..fcef6e6
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/RefreshTokenResponse.kt
@@ -0,0 +1,23 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class RefreshTokenResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("AccessToken")
+ val accessToken: String = "",
+ @SerializedName("ExpiresIn")
+ val expiresIn: Int = 0,
+ @SerializedName("RefreshToken")
+ val refreshToken: String = "",
+ @SerializedName("RefreshTokenExpiresIn")
+ val refreshTokenExpiresIn: Int = 0
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/RegResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/RegResponse.kt
new file mode 100644
index 0000000..c5a0348
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/RegResponse.kt
@@ -0,0 +1,79 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class RegResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("GroupSysid")
+ val groupSysid: String = "",
+ @SerializedName("RegSysid")
+ val regSysid: String = "",
+ @SerializedName("RegValueType")
+ val regValueType: String = "",
+ @SerializedName("TimeLevel")
+ val timeLevel: String = "", @SerializedName("主键")
+ val primaryKey: String = "",
+
+ @SerializedName("主键值")
+ val primaryKeyValue: String = "",
+
+ @SerializedName("主键描述")
+ val primaryKeyDescription: String = "",
+
+ @SerializedName("主键标题")
+ val primaryKeyTitle: String = "",
+
+ @SerializedName("值最大值")
+ val maxValue: Int = 0,
+
+ @SerializedName("值最小值")
+ val minValue: Int = 0,
+
+ @SerializedName("值类型")
+ val valueType: String = "",
+
+ @SerializedName("密码")
+ val password: String = "",
+
+ @SerializedName("所属分组")
+ val belongingGroup: String = "",
+
+ @SerializedName("排序")
+ val sortingOrder: Int = 0,
+
+ @SerializedName("数组数据字符串")
+ val arrayDataString: String = "",
+
+ @SerializedName("数组数据对象JSON")
+ val arrayDataJson: String = "",
+
+ @SerializedName("数组数据接口获取")
+ val arrayDataApiFetch: String = "",
+
+ @SerializedName("时效级别")
+ val timeEffectLevel: String = "",
+
+ @SerializedName("是否只读")
+ val isReadOnly: String = "",
+
+ @SerializedName("是否可用")
+ val isAvailable: String = "",
+
+ @SerializedName("是否启用密码修改")
+ val isPasswordModificationEnabled: String = "",
+
+ @SerializedName("是否显示")
+ val isDisplayed: String = "",
+
+ @SerializedName("默认值")
+ val defaultValue: String = ""
+
+ )
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/SearchOutDetailByRFIDResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/SearchOutDetailByRFIDResponse.kt
new file mode 100644
index 0000000..83e7748
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/SearchOutDetailByRFIDResponse.kt
@@ -0,0 +1,29 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class SearchOutDetailByRFIDResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("code")
+ val code: String = "",
+ @SerializedName("ischuku")
+ val ischuku: Int = 0,
+ @SerializedName("jingzhong")
+ val jingzhong: Double = 0.0,
+ @SerializedName("maozhong")
+ val maozhong: Double = 0.0,
+ @SerializedName("pizhong")
+ val pizhong: Double = 0.0,
+ @SerializedName("rksysid")
+ val rksysid: String = "",
+ @SerializedName("sysid")
+ val sysid: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/SeedInfoResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/SeedInfoResponse.kt
new file mode 100644
index 0000000..90e1aa9
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/SeedInfoResponse.kt
@@ -0,0 +1,24 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+
+data class SeedInfoResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("CzName")
+ val czName: String = "",
+ @SerializedName("DzNum")
+ val dzNum: Double = 0.0,
+ @SerializedName("Gsysid")
+ val gsysid: String = "",
+ @SerializedName("GyhName")
+ val gyhName: String = "",
+ @SerializedName("GyhSysid")
+ val gyhSysid: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/SetUserListResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/SetUserListResponse.kt
new file mode 100644
index 0000000..cbee3de
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/SetUserListResponse.kt
@@ -0,0 +1,50 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+
+data class SetUserListResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("Cun")
+ val cun: String? = null,
+ @SerializedName("DepartmentSysid")
+ val departmentSysid: String = "",
+ @SerializedName("Depname")
+ val depname: String = "",
+ @SerializedName("Frozen")
+ val frozen: String = "",
+ @SerializedName("ICCardId")
+ val iCCardId: Long = 0,
+ @SerializedName("IcCardId")
+ val icCardId: String = "",
+ @SerializedName("Id")
+ val id: Long = -1,
+ @SerializedName("IdCard")
+ val idCard: String = "",
+ @SerializedName("IsSuperManager")
+ val isSuperManager: Boolean = false,
+ @SerializedName("LoginName")
+ val loginName: String = "",
+ @SerializedName("Memo")
+ val memo: String = "",
+ @SerializedName("Name")
+ val name: String = "",
+ @SerializedName("role")
+ val role: String = "",
+ @SerializedName("Sex")
+ val sex: String = "男",
+ @SerializedName("Sort")
+ val sort: Int = 0,
+ @SerializedName("Tel")
+ val tel: String = "",
+ @SerializedName("TenantId")
+ val tenantId: Long = 0,
+ @SerializedName("Xiang")
+ val xiang: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/StatisticsDataResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/StatisticsDataResponse.kt
new file mode 100644
index 0000000..d1b1928
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/StatisticsDataResponse.kt
@@ -0,0 +1,17 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class StatisticsDataResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("date")
+ val date: String = ""
+ )
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/StatisticsListResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/StatisticsListResponse.kt
new file mode 100644
index 0000000..d39cbb9
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/StatisticsListResponse.kt
@@ -0,0 +1,33 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class StatisticsListResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("billcode")
+ val billcode: Long = 0,
+ @SerializedName("billstate")
+ val billstate: String = "",
+ @SerializedName("czsysid")
+ val czsysid: String = "",
+ @SerializedName("jweightsum")
+ val jweightsum: Double = 0.0,
+ @SerializedName("kweightsum")
+ val kweightsum: Double = 0.0,
+ @SerializedName("mweightsum")
+ val mweightsum: Double = 0.0,
+ @SerializedName("nhname")
+ val nhname: String = "",
+ @SerializedName("paystate")
+ val paystate: String = "",
+ @SerializedName("pweightsum")
+ val pweightsum: Double = 0.0
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/StatisticsResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/StatisticsResponse.kt
new file mode 100644
index 0000000..9e60e70
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/StatisticsResponse.kt
@@ -0,0 +1,25 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class StatisticsResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("sgtypesysid")
+ val sgtypesysid: String = "",
+ @SerializedName("avgprice")
+ val avgprice: Double = 0.0,
+ @SerializedName("name")
+ val name: String = "",
+ @SerializedName("summoney")
+ val summoney: Double = 0.0,
+ @SerializedName("sumweight")
+ val sumweight: Double = 0.0
+ )
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/TodayPriceResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/TodayPriceResponse.kt
new file mode 100644
index 0000000..10b144b
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/TodayPriceResponse.kt
@@ -0,0 +1,25 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class TodayPriceResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("depprice")
+ val depprice: Double = 0.0,
+ @SerializedName("gsprice")
+ val gsprice: Double = 0.0,
+ @SerializedName("maxprice")
+ val maxprice: Double = 0.0,
+ @SerializedName("minprice")
+ val minprice: Double = 0.0,
+ @SerializedName("name")
+ val name: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/UserDataResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/UserDataResponse.kt
new file mode 100644
index 0000000..3f4d0e7
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/UserDataResponse.kt
@@ -0,0 +1,43 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class UserDataResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("bankcode")
+ val bankcode: String = "",
+ @SerializedName("bankname")
+ val bankname: String = "",
+ @SerializedName("bankshortname")
+ val bankshortname: String = "",
+ @SerializedName("createtime")
+ val createtime: String = "",
+ @SerializedName("cun")
+ val cun: String = "",
+ @SerializedName("flag")
+ val flag: Int = 0,
+ @SerializedName("idcard")
+ val idcard: String = "",
+ @SerializedName("nhname")
+ val nhname: String = "",
+ @SerializedName("phone")
+ val phone: String = "",
+ @SerializedName("recbankcode")
+ val recbankcode: String = "",
+ @SerializedName("sysid")
+ val sysid: String = "",
+ @SerializedName("xian")
+ val xian: String = "",
+ @SerializedName("xiang")
+ val xiang: String = "",
+ @SerializedName("zu")
+ val zu: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/UserInfoResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/UserInfoResponse.kt
new file mode 100644
index 0000000..c12a194
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/UserInfoResponse.kt
@@ -0,0 +1,35 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class UserInfoResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("depname")
+ val depname: String = "",
+ @SerializedName("depsysid")
+ val depsysid: String = "",
+ @SerializedName("depcode")
+ val depcode: String = "",
+ @SerializedName("id")
+ val id: String = "",
+ @SerializedName("name")
+ val name: String = "",
+ @SerializedName("role")
+ val role: String = "",
+ @SerializedName("sgcjname")
+ val sgcjname: String = "",
+ @SerializedName("sgcjsysid")
+ val sgcjsysid: String = "",
+ @SerializedName("sgdate")
+ val sgdate: String = "",
+ @SerializedName("tenantname")
+ val tenantname: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/UserLabelResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/UserLabelResponse.kt
new file mode 100644
index 0000000..d143a38
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/UserLabelResponse.kt
@@ -0,0 +1,12 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class UserLabelResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+)
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/UserRoleInfoResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/UserRoleInfoResponse.kt
new file mode 100644
index 0000000..681a4f3
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/UserRoleInfoResponse.kt
@@ -0,0 +1,30 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+
+data class UserRoleInfoResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("GroupName")
+ val groupName: String = "",
+ @SerializedName("GroupSysid")
+ val groupSysid: String = "",
+ @SerializedName("LimitGroup")
+ val limitGroup: String = "",
+ @SerializedName("Memo")
+ val memo: String = "",
+ @SerializedName("ParentSysid")
+ val parentSysid: String = "",
+ @SerializedName("RoleName")
+ val roleName: String = "",
+ @SerializedName("Sort")
+ val sort: Int = 0,
+ @SerializedName("Sysid")
+ val sysid: String = ""
+ )
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/UserTypeResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/UserTypeResponse.kt
new file mode 100644
index 0000000..28d1e0d
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/UserTypeResponse.kt
@@ -0,0 +1,19 @@
+package com.bbitcn.f8.pad.model.net.response
+import com.google.gson.annotations.SerializedName
+
+
+data class UserTypeResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("name")
+ val name: String = "",
+ @SerializedName("sysid")
+ val sysid: String = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/UsersAreaResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/UsersAreaResponse.kt
new file mode 100644
index 0000000..c9a441e
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/UsersAreaResponse.kt
@@ -0,0 +1,24 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class UsersAreaResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("Cun")
+ val cun: String = "",
+ @SerializedName("Xian")
+ val xian: String = "",
+ @SerializedName("Xiang")
+ val xiang: String = "",
+ @SerializedName("Zu")
+ val zu: String = "",
+ val count: Int = -1,
+ )
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/WeatherForTodayResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/WeatherForTodayResponse.kt
new file mode 100644
index 0000000..b37bff3
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/WeatherForTodayResponse.kt
@@ -0,0 +1,52 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class WeatherForTodayResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("adcode")
+ val adcode: String = "",
+ @SerializedName("Alarms")
+ val alarms: List = listOf(),
+ @SerializedName("city")
+ val city: String = "",
+ @SerializedName("humidity")
+ val humidity: String = "",
+ @SerializedName("province")
+ val province: String = "",
+ @SerializedName("reporttime")
+ val reporttime: String = "",
+ @SerializedName("temperature")
+ val temperature: String = "",
+ @SerializedName("weather")
+ val weather: String = "",
+ @SerializedName("weatherpic")
+ val weatherpic: String = "",
+ @SerializedName("winddirection")
+ val winddirection: String = "",
+ @SerializedName("windpower")
+ val windpower: String = ""
+ ) {
+ data class Alarm(
+ @SerializedName("city")
+ val city: String = "",
+ @SerializedName("content")
+ val content: String = "",
+ @SerializedName("level")
+ val level: String = "",
+ @SerializedName("province")
+ val province: String = "",
+ @SerializedName("time")
+ val time: String = "",
+ @SerializedName("type")
+ val type: String = ""
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/WeatherResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/WeatherResponse.kt
new file mode 100644
index 0000000..a87af08
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/WeatherResponse.kt
@@ -0,0 +1,54 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class WeatherResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: Data = Data(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("adcode")
+ val adcode: String = "",
+ @SerializedName("casts")
+ val casts: List = listOf(),
+ @SerializedName("city")
+ val city: String = "",
+ @SerializedName("province")
+ val province: String = "",
+ @SerializedName("reporttime")
+ val reporttime: String = ""
+ ) {
+ data class Cast(
+ @SerializedName("date")
+ val date: String = "",
+ @SerializedName("daypower")
+ val daypower: String = "",
+ @SerializedName("daytemp")
+ val daytemp: String = "",
+ @SerializedName("dayweather")
+ val dayweather: String = "",
+ @SerializedName("dayweatherpic")
+ val dayweatherpic: String = "",
+ @SerializedName("daywind")
+ val daywind: String = "",
+ @SerializedName("nightpower")
+ val nightpower: String = "",
+ @SerializedName("nighttemp")
+ val nighttemp: String = "",
+ @SerializedName("nightweather")
+ val nightweather: String = "",
+ @SerializedName("nightweatherpic")
+ val nightweatherpic: String = "",
+ @SerializedName("nightwind")
+ val nightwind: String = "",
+ @SerializedName("week")
+ val week: String = "",
+ @SerializedName("weekstr")
+ val weekstr: String = ""
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/net/response/WeightKindsResponse.kt b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/WeightKindsResponse.kt
new file mode 100644
index 0000000..b0238dd
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/net/response/WeightKindsResponse.kt
@@ -0,0 +1,35 @@
+package com.bbitcn.f8.pad.model.net.response
+
+import com.google.gson.annotations.SerializedName
+
+data class WeightKindsResponse(
+ @SerializedName("code")
+ val code: Int = 0,
+ @SerializedName("data")
+ val `data`: List = listOf(),
+ @SerializedName("msg")
+ val msg: String = ""
+) {
+ data class Data(
+ @SerializedName("GroupType")
+ val groupType: String = "",
+ @SerializedName("IdCode")
+ val idCode: Int = 0,
+ @SerializedName("MaxPrice")
+ val maxPrice: Double = 0.0,
+ @SerializedName("MaxWarningPrice")
+ val maxWarningPrice: Double = 0.0,
+ @SerializedName("MinPrice")
+ val minPrice: Double = 0.0,
+ @SerializedName("MinWarningPrice")
+ val minWarningPrice: Double = 0.0,
+ @SerializedName("Name")
+ val name: String = "",
+ @SerializedName("PriceType")
+ val priceType: Int = 0,
+ @SerializedName("Sort")
+ val sort: Int = 0,
+ @SerializedName("Sysid")
+ val sysid: String = ""
+ )
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/ui/BaseDialogData.kt b/app/src/main/java/com/bbitcn/f8/pad/model/ui/BaseDialogData.kt
new file mode 100644
index 0000000..ede49c3
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/ui/BaseDialogData.kt
@@ -0,0 +1,9 @@
+package com.bbitcn.f8.pad.model.ui
+
+import androidx.compose.runtime.Immutable
+
+@Immutable
+data class BaseDialogData(
+ var showDialog: Boolean = false,
+ val onDismiss: () -> Unit = {}
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/model/ui/Message.kt b/app/src/main/java/com/bbitcn/f8/pad/model/ui/Message.kt
new file mode 100644
index 0000000..f397331
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/model/ui/Message.kt
@@ -0,0 +1,11 @@
+package com.bbitcn.f8.pad.model.ui
+
+import androidx.compose.runtime.Immutable
+
+
+@Immutable
+data class Message(
+ val content: String,
+ val timestamp: Long,
+ val isFromUser: Boolean = false,
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/receiver/BootReceiver.kt b/app/src/main/java/com/bbitcn/f8/pad/receiver/BootReceiver.kt
new file mode 100644
index 0000000..cbe94ed
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/receiver/BootReceiver.kt
@@ -0,0 +1,20 @@
+package com.bbitcn.f8.pad.receiver
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+
+/**
+ * 开机广播监听
+ */
+class BootReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action != null && Intent.ACTION_BOOT_COMPLETED == intent.action) {
+ val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
+ if (launchIntent != null) {
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(launchIntent)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/receiver/FrpStartReceiver.kt b/app/src/main/java/com/bbitcn/f8/pad/receiver/FrpStartReceiver.kt
new file mode 100644
index 0000000..f7cc5fe
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/receiver/FrpStartReceiver.kt
@@ -0,0 +1,28 @@
+package com.bbitcn.f8.pad.receiver
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.MyUtil.relaunchFrp
+import com.bbitcn.f8.pad.utils.global.Global
+import com.bbitcn.f8.pad.utils.log.MyLog
+
+class FrpStartReceiver : BroadcastReceiver() {
+ override fun onReceive(mContext: Context, intent: Intent) {
+ if (intent.action == "receiver_frp_version") {
+ val frpVersion = intent.getIntExtra("version", 0)
+ MyLog.frp("收到FRP版本:$frpVersion")
+ MMKVUtil.put(Global.FRP_VERSION, frpVersion)
+ } else if (intent.action == "receiver_start_frp") {
+ if (intent.getBooleanExtra("start_frp", false)) {
+ MyLog.frp("收到启动FRP指令")
+ relaunchFrp()
+ }
+ } else if (intent.action == "receiver_frp_info") {
+ val info = intent.getStringExtra("info")
+ MyLog.frp(info)
+ // RxBusUtils.get().post(RxTag.UPDATE_FRP, info);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/receiver/SystemInfoReceiver.kt b/app/src/main/java/com/bbitcn/f8/pad/receiver/SystemInfoReceiver.kt
new file mode 100644
index 0000000..e73bb65
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/receiver/SystemInfoReceiver.kt
@@ -0,0 +1,144 @@
+package com.bbitcn.f8.pad.receiver
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.ConnectivityManager
+import android.net.NetworkCapabilities
+import android.net.wifi.WifiManager
+import android.os.BatteryManager
+import android.os.Build
+import android.telephony.PhoneStateListener
+import android.telephony.SignalStrength
+import android.telephony.TelephonyManager
+import androidx.compose.runtime.mutableStateOf
+import com.bbitcn.f8.pad.utils.PollingTask
+import com.bbitcn.f8.pad.utils.log.MyLog
+import com.bbitcn.f8.pad.utils.registerReceiverCompat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class SystemInfoReceiver(private val context: Context) {
+
+ private val _batteryLevel = MutableStateFlow(0)
+ val batteryLevel = _batteryLevel.asStateFlow()
+
+ private val _networkType = MutableStateFlow("WIFI")
+ val networkType = _networkType.asStateFlow()
+
+ private val _signalStrength = MutableStateFlow(0)
+ val signalStrength = _signalStrength.asStateFlow()
+ /**
+ * 信号强度监听器
+ */
+ private val networkStateListener = object : PhoneStateListener() {
+ override fun onSignalStrengthsChanged(signalStrengths: SignalStrength) {
+ super.onSignalStrengthsChanged(signalStrengths)
+ _signalStrength.value = signalStrengths.level
+ }
+ }
+
+ private val batteryReceiver = object : BroadcastReceiver() {
+ override fun onReceive(ctx: Context?, intent: Intent?) {
+ val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
+ _batteryLevel.value = level
+ }
+ }
+
+ private val connectivityReceiver = object : BroadcastReceiver() {
+ override fun onReceive(ctx: Context?, intent: Intent?) {
+ val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ val activeNetwork = connectivityManager.activeNetwork
+ val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork)
+
+ networkCapabilities?.let {
+ when {
+ it.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> {
+ _networkType.value = "WIFI"
+ val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
+ val wifiInfo = wifiManager.connectionInfo
+ _signalStrength.value = WifiManager.calculateSignalLevel(wifiInfo.rssi, 5)
+ }
+ it.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> {
+ _networkType.value = "Cellular"
+ val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
+ telephonyManager.listen(networkStateListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS)
+ }
+ it.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> {
+ _networkType.value = "Ethernet"
+ _signalStrength.value = 4 // Ethernet typically has a strong connection
+ }
+ else -> {
+ _networkType.value = "Unknown"
+ _signalStrength.value = 0
+ }
+ }
+ } ?: run {
+ _networkType.value = "No Connection"
+ _signalStrength.value = 0
+ }
+ }
+ }
+
+ fun register() {
+ val batteryIntentFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
+ val connectivityIntentFilter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
+
+ context.registerReceiverCompat(
+ batteryReceiver,
+ batteryIntentFilter
+ )
+
+ context.registerReceiverCompat(
+ connectivityReceiver,
+ connectivityIntentFilter
+ )
+ // 开始轮询网络状态 防止状态假死
+ PollingTask.getInstance("SystemInfoReceiver").startPollingTaskOnIOThread("NetworkStatusPolling",30_000) {
+ refreshNetworkStatus()
+ }
+ }
+
+ fun unregister() {
+ context.unregisterReceiver(batteryReceiver)
+ context.unregisterReceiver(connectivityReceiver)
+ PollingTask.getInstance("SystemInfoReceiver").stopTask("NetworkStatusPolling")
+ }
+
+ private fun refreshNetworkStatus() {
+ val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ val activeNetwork = connectivityManager.activeNetwork
+ val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork)
+
+ networkCapabilities?.let {
+ when {
+ it.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> {
+ _networkType.value = "WIFI"
+ val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
+ val wifiInfo = wifiManager.connectionInfo
+ val rssi = wifiInfo.rssi
+ if (rssi != -127) { // -127 通常是无效值
+ _signalStrength.value = WifiManager.calculateSignalLevel(rssi, 5)
+ }
+ }
+ it.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> {
+ _networkType.value = "Cellular"
+ // 通常需要 TelephonyManager.getSignalStrength,但这里只能靠原来 listener 补充
+ }
+ it.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> {
+ _networkType.value = "Ethernet"
+ _signalStrength.value = 4
+ }
+ else -> {
+ _networkType.value = "Unknown"
+ _signalStrength.value = 0
+ }
+ }
+ } ?: run {
+ _networkType.value = "No Connection"
+ _signalStrength.value = 0
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/MainActivity.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/MainActivity.kt
new file mode 100644
index 0000000..5834958
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/MainActivity.kt
@@ -0,0 +1,316 @@
+package com.bbitcn.f8.pad.ui
+
+import android.os.Bundle
+import android.view.View
+import android.view.WindowManager
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.animation.AnimatedContentTransitionScope
+import androidx.compose.animation.core.EaseIn
+import androidx.compose.animation.core.EaseOut
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.slideInHorizontally
+import androidx.compose.animation.slideOutHorizontally
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.DrawerValue
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.ModalNavigationDrawer
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Snackbar
+import androidx.compose.material3.SnackbarHost
+import androidx.compose.material3.Surface
+import androidx.compose.material3.rememberDrawerState
+import androidx.compose.material3.rememberModalBottomSheetState
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.ui.screen.TopInfoViewModel
+import com.bbitcn.f8.pad.ui.screen.LoginScreen
+import com.bbitcn.f8.pad.ui.screen.MainScreen
+import com.bbitcn.f8.pad.ui.screen.MyTopBar
+import com.bbitcn.f8.pad.ui.screen.dialog.ConfirmDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.InputDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.LoadingDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.TipsDialog
+import com.bbitcn.f8.pad.ui.screen.view.drawer.DrawerViewModel
+import com.bbitcn.f8.pad.ui.screen.secondFunc.AddUserScreen
+import com.bbitcn.f8.pad.ui.screen.secondFunc.DryCocoonAirScreen
+import com.bbitcn.f8.pad.ui.screen.secondFunc.DryCocoonInScreen
+import com.bbitcn.f8.pad.ui.screen.secondFunc.DryCocoonOutScreen
+import com.bbitcn.f8.pad.ui.screen.secondFunc.PayScreen
+import com.bbitcn.f8.pad.ui.screen.secondFunc.WeightScreen
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.Toasty.ToastType
+import com.bbitcn.f8.pad.ui.screen.view.Toasty.ToastySnackbarVisuals
+import com.bbitcn.f8.pad.ui.screen.view.common.MyBottomSheet
+import com.bbitcn.f8.pad.ui.screen.view.deviceManager.printer.PrintTest
+import com.bbitcn.f8.pad.ui.theme.AppTheme
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.ui.viewmodel.UpdateViewModel
+import com.bbitcn.f8.pad.utils.externalModules.devices.scale.ScaleBT
+import com.bbitcn.f8.pad.utils.externalModules.devices.printer.PrinterBT
+import com.bbitcn.f8.pad.utils.externalModules.devices.water.WaterCutMeterBT
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.nfc.NFCUtils
+
+/**
+ *
+ * @Author DuanKaiji
+ * @CreateTime 2024年04月30日 10:29:21
+ */
+class MainActivity : ComponentActivity() {
+
+ private lateinit var viewModel: MainActivityViewModel
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ installSplashScreen()
+ super.onCreate(savedInstanceState)
+ setFullScreen(true)
+ // 初始化NfcAdapter
+ NFCUtils.initActivity(this)
+ PrinterBT.init()
+ WaterCutMeterBT.init()
+ ScaleBT.init()
+ viewModel = ViewModelProvider(this)[MainActivityViewModel::class.java]
+ setContent {
+ AppTheme {
+ Surface(
+ modifier = M.fillMaxSize()
+ ) {
+ // 创建导航控制器
+ val navController = rememberNavController()
+ // 获取 ViewModel 中的 Drawer 状态
+ val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
+ val drawerViewModel = viewModel()
+ val updateViewModel = viewModel()
+
+ val drawerContent by drawerViewModel.drawerContent.collectAsState()
+ val isDrawerOpen by drawerViewModel.isDrawerOpen.collectAsState()
+ var curPageName by rememberSaveable { mutableStateOf("main") }
+ val topInfoViewmodel = viewModel()
+
+ val showBottomSheet by Toasty.isDrawerOpen.collectAsState()
+ LaunchedEffect(isDrawerOpen) {
+ if (isDrawerOpen) {
+ drawerState.open()
+ } else {
+ drawerState.close()
+ }
+ }
+ LaunchedEffect(drawerState.isClosed) {
+ if (drawerState.isClosed && isDrawerOpen) {
+ drawerViewModel.closeDrawer()
+ }
+ }
+ Scaffold(
+ snackbarHost = {
+ SnackbarHost(hostState = Toasty.snackbarHostState) { data ->
+ val visuals = data.visuals as? ToastySnackbarVisuals
+ val backgroundColor = when (visuals?.type) {
+ ToastType.Success -> MyColors.Green // 绿色
+ ToastType.Error -> MyColors.Red // 红色
+ else -> MyColors.Gray // 默认深灰
+ }
+ Snackbar(
+ snackbarData = data,
+ containerColor = backgroundColor,
+ contentColor = MyColors.White
+ )
+ }
+ },
+ topBar = {
+ MyTopBar(
+ navController,
+ topInfoViewmodel,
+ drawerViewModel,
+ curPageName
+ )
+ }
+ ) { paddingValues ->
+ ModalNavigationDrawer(
+ modifier = M
+ .fillMaxSize()
+ .padding(paddingValues),
+ gesturesEnabled = drawerState.isOpen,
+ drawerState = drawerState,
+ drawerContent = drawerContent,
+ ) {
+ Box(
+ modifier = M
+ .fillMaxSize(),
+ ) {
+ // 优化没登录时先到主界面再到登录界面的冗余逻辑
+ val loginExpiredToLogin by Toasty.loginExpiredToLogin.collectAsState()
+ var startDestination = if (loginExpiredToLogin) "login" else "main"
+ // 测试
+// startDestination = "test"
+ NavHost(
+ navController = navController,
+ startDestination = startDestination, // 设置起始页面
+ enterTransition = {
+ slideInHorizontally(initialOffsetX = { it }) + fadeIn(
+ initialAlpha = 0f,
+ animationSpec = tween(
+ durationMillis = 400,
+ easing = LinearEasing
+ )
+ )
+ },
+ exitTransition = {
+ slideOutHorizontally(targetOffsetX = { -it }) + fadeOut(
+ targetAlpha = 0f,
+ animationSpec = tween(
+ durationMillis = 400,
+ easing = LinearEasing
+ )
+ )
+ }
+ ) {
+ composable("test") {
+ // 测试打印
+ PrintTest()
+ // 称重界面
+// WeightScreen("e3c97898-59e1-4419-a2ac-297566c00bf5",bottomDrawerViewModel ) // duan
+// WeightScreen("b23a7fab-443b-4e1f-824f-46dbd8e8931c",bottomDrawerViewModel ) // test
+ // 入库
+// DryCocoonInScreen("3d51438a-c835-401d-bb6c-1c045363298f")
+ // 拍照测试
+// topInfoViewmodel.toScreen("camera")
+// MyCameraScreen()
+ }
+ composable("login") {
+ curPageName = "login"
+ LoginScreen(navController, updateViewModel)
+ }
+ composable("main") {
+ curPageName = "main"
+ topInfoViewmodel.toScreen()
+ MainScreen(
+ navController,
+ drawerViewModel,
+ updateViewModel,
+ topInfoViewmodel
+ )
+ }
+ composable("addUser") {
+ topInfoViewmodel.toScreen("addUser")
+ AddUserScreen()
+ }
+ composable("editUser/{sysId}") { backStackEntry ->
+ val sysId =
+ backStackEntry.arguments?.getString("sysId") ?: ""
+ topInfoViewmodel.toScreen("editUser")
+ AddUserScreen(sysId, navController)
+ }
+ composable("weight/{sysId}") { backStackEntry ->
+ val sysId =
+ backStackEntry.arguments?.getString("sysId") ?: ""
+ topInfoViewmodel.toScreen("weight")
+ WeightScreen(navController, sysId)
+ }
+ composable("pay/{czSysId}") { backStackEntry ->
+ topInfoViewmodel.toScreen("pay")
+ val czSysId =
+ backStackEntry.arguments?.getString("czSysId") ?: ""
+ PayScreen(czSysId)
+ }
+ composable("dryCoonOperateIn/{sysId}") { backStackEntry ->
+ topInfoViewmodel.toScreen("dryCoonOperateIn")
+ var sysId =
+ backStackEntry.arguments?.getString("sysId") ?: ""
+// sysId = "3d51438a-c835-401d-bb6c-1c045363298f"
+ DryCocoonInScreen(sysId)
+ }
+ composable("dryCoonOperateAir/{sysId}") { backStackEntry ->
+ topInfoViewmodel.toScreen("dryCoonOperateAir")
+ var sysId =
+ backStackEntry.arguments?.getString("sysId") ?: ""
+ DryCocoonAirScreen(sysId)
+ }
+ composable("dryCoonOperateOut/{sysId}") { backStackEntry ->
+ topInfoViewmodel.toScreen("dryCoonOperateOut")
+ var sysId =
+ backStackEntry.arguments?.getString("sysId") ?: ""
+// sysId = "3d51438a-c835-401d-bb6c-1c045363298f"
+ DryCocoonOutScreen(sysId)
+ }
+ }
+ }
+ MyBottomSheet(
+ showBottomSheet = showBottomSheet,
+ onDismissRequest = {
+ Toasty.closeDrawer()
+ }
+ ) {
+ Box(
+ modifier = M.fillMaxSize()
+ ) {
+ // 底部抽屉
+ Toasty.drawerContent.value()
+ }
+ }
+ }
+ }
+ val tipsDialog by Toasty.tipsDialog.collectAsState()
+ val loadingDialog by Toasty.loadingDialog.collectAsState()
+ val confirmDialog by Toasty.confirmDialog.collectAsState()
+ val inputDialog by Toasty.inputDialog.collectAsState()
+ TipsDialog(
+ showDialog = tipsDialog.showDialog,
+ onDismiss = { Toasty.hideTipsDialog() },
+ content = tipsDialog.content
+ )
+ LoadingDialog(loadingDialog)
+ ConfirmDialog(confirmDialog)
+ InputDialog(inputDialog)
+ }
+ }
+ }
+ }
+
+ fun setFullScreen(isFullScreen: Boolean) {
+ if (isFullScreen) {
+ window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
+ window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN)
+ val decorView = window.decorView
+ val uiOptions = (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ or View.SYSTEM_UI_FLAG_FULLSCREEN)
+ decorView.systemUiVisibility = uiOptions
+
+ } else {
+ window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN)
+ }
+ }
+
+ private var lastBackPressedTime: Long = 0
+
+ override fun onBackPressed() {
+ val currentTime = System.currentTimeMillis()
+ if (currentTime - lastBackPressedTime < 2000) { // 如果两次点击时间间隔小于2秒
+ super.onBackPressed() // 调用系统默认的退出行为
+ } else {
+ Toasty.showToast("再按一次退出")
+ lastBackPressedTime = currentTime
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/MainActivityViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/MainActivityViewModel.kt
new file mode 100644
index 0000000..2de5342
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/MainActivityViewModel.kt
@@ -0,0 +1,64 @@
+package com.bbitcn.f8.pad.ui
+
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.utils.log.MyLog
+import com.blankj.utilcode.util.ActivityUtils
+import com.hjq.permissions.OnPermissionCallback
+import com.hjq.permissions.Permission
+import com.hjq.permissions.XXPermissions
+
+class MainActivityViewModel :BaseViewModel() {
+
+ init {
+ doInIoThreadNoDialog {
+ checkPermission()
+ // 初始化NfcAdapter
+// Printer.init()
+// WaterCutMeter.init()
+// Balance.init()
+ }
+ }
+
+ /**
+ * 检查与请求权限
+ */
+ fun checkPermission() {
+ XXPermissions.with(ActivityUtils.getTopActivity())
+ .permission(Permission.WRITE_EXTERNAL_STORAGE)
+ .permission(Permission.READ_PHONE_STATE)
+ // 定位
+ .permission(Permission.ACCESS_FINE_LOCATION)
+ .permission(Permission.ACCESS_COARSE_LOCATION)
+ // 蓝牙
+ .permission(Permission.BLUETOOTH_SCAN)
+ .permission(Permission.BLUETOOTH_CONNECT)
+ .permission(Permission.BLUETOOTH_ADVERTISE)
+ // 录音
+ .permission(Permission.RECORD_AUDIO)
+ // 安装应用
+ .permission(Permission.REQUEST_INSTALL_PACKAGES)
+ // 相机
+ .permission(Permission.CAMERA)
+ .request(object : OnPermissionCallback {
+ override fun onGranted(permissions: List, allGranted: Boolean) {
+ if (!allGranted) {
+ MyLog.appError("获取部分权限成功,以下权限未获得")
+ for (permission in permissions) {
+ MyLog.appError(permission)
+ }
+// XXPermissions.startPermissionActivity(context, permissions) //todo
+ }
+ }
+
+ override fun onDenied(permissions: List, doNotAskAgain: Boolean) {
+ if (doNotAskAgain) {
+ MyLog.app("被永久拒绝授权,需手动授予权限")
+ // 如果是被永久拒绝就跳转到应用权限系统设置页面
+// XXPermissions.startPermissionActivity(context, permissions)
+ } else {
+ MyLog.appError("权限请求失败")
+ }
+ }
+ })
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/LoginScreen.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/LoginScreen.kt
new file mode 100644
index 0000000..81c424e
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/LoginScreen.kt
@@ -0,0 +1,434 @@
+package com.bbitcn.f8.pad.ui.screen
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Clear
+import androidx.compose.material.icons.filled.Visibility
+import androidx.compose.material.icons.filled.VisibilityOff
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Tab
+import androidx.compose.material3.TabRow
+import androidx.compose.material3.TabRowDefaults
+import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavController
+import androidx.navigation.compose.rememberNavController
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.MyTextField
+import androidx.compose.runtime.*
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.text.input.VisualTransformation
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MainFuncFrame
+import com.bbitcn.f8.pad.ui.screen.dialog.AboutDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.AuthDialog
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.QueryTextField
+import com.bbitcn.f8.pad.base.VipBadge
+import com.bbitcn.f8.pad.ui.screen.dialog.FaceDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.ForgetPasswordDialog
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.viewmodel.UpdateViewModel
+import kotlinx.coroutines.launch
+import kotlin.math.log
+
+/**
+ *
+ * @Author DuanKaiji
+ * @CreateTime 2024年06月12日 09:04:34
+ */
+@Preview(showBackground = true, widthDp = 1280)
+@Composable
+fun ListScreenPreview() {
+ LoginScreen(rememberNavController(), updateViewModel = viewModel())
+}
+
+@Composable
+fun LoginScreen(
+ navController: NavController,
+ updateViewModel: UpdateViewModel,
+ loginViewModel: LoginViewModel = viewModel()
+) {
+ val authDialogState by loginViewModel.authDialogState.collectAsState()
+ val aboutDialogState by loginViewModel.aboutDialogState.collectAsState()
+ val forgetPasswordDialogData by loginViewModel.editPasswordDialogData.collectAsState()
+ val faceDialogData by loginViewModel.faceDialogData.collectAsState()
+ val navToMain by loginViewModel.navToMain.collectAsState()
+
+ LaunchedEffect(navToMain) {
+ if (navToMain) {
+ navController.navigate("main") {
+ popUpTo(navController.graph.startDestinationId) { inclusive = true }
+ launchSingleTop = true
+ }
+ Toasty.loginSuccess()
+ }
+ }
+ MainFuncFrame {
+ Box(modifier = M.fillMaxSize(), contentAlignment = Alignment.CenterEnd) {
+ MyCard(
+ modifier = M
+ .padding(end = 100.dp)
+ ) {
+ Box(
+ modifier = M
+ .height(400.dp)
+ .width(320.dp)
+ ) {
+ TabPagerScreen(loginViewModel,updateViewModel)
+ }
+ }
+ Box(modifier = M.fillMaxSize(), contentAlignment = Alignment.BottomCenter) {
+ Text(
+ text = "Copyright @ 2023 智慧蚕桑收购系统",
+ modifier = M
+ .fillMaxWidth()
+ .padding(bottom = 10.dp),
+ color = MyColors.White,
+ textAlign = TextAlign.Center
+ )
+ }
+ }
+ }
+ AuthDialog(authDialogState)
+ AboutDialog(aboutDialogState)
+ ForgetPasswordDialog(forgetPasswordDialogData)
+ FaceDialog(faceDialogData)
+}
+
+@Composable
+fun TabPagerScreen(
+ loginViewModel: LoginViewModel,
+ updateViewModel: UpdateViewModel
+) {
+ val tabs = listOf("账号登录", "手机登录", "人脸识别")
+ val pagerState = rememberPagerState(pageCount = { tabs.size })
+ val scope = rememberCoroutineScope()
+ Column {
+ TabRow(
+ selectedTabIndex = pagerState.currentPage,
+ indicator = { tabPositions ->
+ TabRowDefaults.Indicator(
+ M.tabIndicatorOffset(tabPositions[pagerState.currentPage]),
+ color = MyColors.BlueGreen,
+ )
+ }
+ ) {
+ tabs.forEachIndexed { index, title ->
+ Tab(
+ text = {
+ if (index == 2) {
+ // 人脸识别高级功能
+ VipBadge {
+ Text(
+ title,
+ color = if (pagerState.currentPage == index) MyColors.BlueGreen else MyColors.Black,
+ fontWeight = if (pagerState.currentPage == index) FontWeight.Bold else FontWeight.Normal,
+ fontSize = 18.sp
+ )
+ }
+ } else {
+ Text(
+ title,
+ color = if (pagerState.currentPage == index) MyColors.BlueGreen else MyColors.Black,
+ fontWeight = if (pagerState.currentPage == index) FontWeight.Bold else FontWeight.Normal,
+ fontSize = 18.sp
+ )
+ }
+ },
+ selected = pagerState.currentPage == index,
+ onClick = {
+ scope.launch {
+ pagerState.scrollToPage(index)
+ }
+ }
+ )
+ }
+ }
+ HorizontalPager(
+ state = pagerState,
+ beyondViewportPageCount = tabs.size,
+ modifier = M
+ .fillMaxWidth()
+ .weight(1f)
+ ) { page ->
+ when (page) {
+ 0 -> AccountLogin(loginViewModel)
+ 1 -> PhoneLogin(loginViewModel)
+ 2 -> FaceLogin(loginViewModel)
+ }
+ }
+ Row(
+ modifier = M
+ .background(color = MyColors.Black)
+ ) {
+ Row(modifier = M.padding(horizontal = 5.dp, vertical = 10.dp)) {
+ FuncButton(modifier = M.weight(1f), "设备授权", R.drawable.auth, onClick = {
+ loginViewModel.showAuthDialog()
+ })
+ FuncButton(modifier = M.weight(1f), "关于我们", R.drawable.about, onClick = {
+ loginViewModel.showAboutDialog()
+ })
+ FuncButton(
+ modifier = M.weight(1f),
+ "检查更新",
+ R.drawable.update,
+ onClick = {
+ updateViewModel.checkUpdate()
+ }
+ )
+ FuncButton(
+ modifier = M.weight(1f),
+ "忘记密码",
+ R.drawable.password,
+ onClick = {
+ loginViewModel.showForgetPasswordDialog()
+ }
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun AccountLogin(loginViewModel: LoginViewModel) {
+ val companyCode by loginViewModel.companyCode.collectAsState()
+ val username by loginViewModel.defaultUserName.collectAsState()
+ var password by rememberSaveable { mutableStateOf("") }
+ val keyboardController = LocalSoftwareKeyboardController.current
+ Column(
+ modifier = M.padding(20.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ MyTextField(
+ value = companyCode,
+ onValueChange = { },
+ hint = "公司代码",
+ fontColor = MyColors.Gray,
+ enabled = false,
+ modifier = M.padding(top = 20.dp)
+ )
+ MyTextField(
+ value = username,
+ onValueChange = {
+ loginViewModel.updateUserName(it)
+ },
+ hint = "用户名",
+ isNumberInputType = true,
+ modifier = M.padding(top = 20.dp),
+ trailing = {
+ if (username.isNotEmpty()) {
+ IconButton(onClick = {
+ loginViewModel.clearUsername()
+ }) {
+ Image(
+ Icons.Filled.Clear,
+ contentDescription = "Check More",
+ modifier = M.size(24.dp)
+ )
+ }
+ }
+ }
+ )
+ // 登录按钮点击事件
+ val onLoginClick = {
+ if (companyCode.isEmpty() || username.isEmpty() || password.isEmpty()) {
+ Toasty.showTipsDialog("公司代码、用户名、密码不能为空")
+ } else {
+ keyboardController?.hide()
+ loginViewModel.login(companyCode, username, password)
+ }
+ }
+ var passwordVisible by rememberSaveable { mutableStateOf(false) }
+ MyTextField(
+ value = password,
+ onValueChange = { password = it },
+ hint = "密码",
+ visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
+ keyboardActions = KeyboardActions(onDone = {
+ onLoginClick()
+ }),
+ trailing = {
+ // 显示密码
+ IconButton(onClick = {
+ passwordVisible = !passwordVisible
+ }) {
+ Icon(
+ imageVector = if (passwordVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff,
+ contentDescription = "显示密码",
+ tint = MyColors.Gray
+ )
+ }
+ },
+ modifier = M.padding(top = 20.dp)
+ )
+ MyButton(
+ text = "登录",
+ onClick = onLoginClick,
+ modifier = M
+ .padding(top = 20.dp)
+ .fillMaxWidth()
+ )
+ }
+}
+
+/**
+ * 手机号登录
+ */
+@Composable
+fun PhoneLogin(loginViewModel: LoginViewModel) {
+ val companyCode by loginViewModel.companyCode.collectAsState()
+ var phone by rememberSaveable { mutableStateOf("") }
+ var code by rememberSaveable { mutableStateOf("") }
+ val codeSendTime by loginViewModel.codeSendTime.collectAsState()
+ val keyboardController = LocalSoftwareKeyboardController.current
+ Column(
+ modifier = M.padding(20.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ MyTextField(
+ value = companyCode,
+ onValueChange = { },
+ hint = "公司代码",
+ fontColor = MyColors.Gray,
+ enabled = false,
+ modifier = M.padding(top = 20.dp)
+ )
+ MyTextField(
+ value = phone,
+ onValueChange = { phone = it },
+ hint = "手机号",
+ isNumberInputType = true,
+ modifier = M.padding(top = 20.dp)
+ )
+ Row(
+ modifier = M.padding(top = 20.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ MyTextField(
+ value = code,
+ onValueChange = { code = it },
+ hint = "验证码",
+ isNumberInputType = true,
+ modifier = M.weight(1f)
+ )
+ MyButton(
+ enabled = codeSendTime == 0,
+ text = if (codeSendTime == 0) "发送验证码" else "${codeSendTime}秒后重发",
+ modifier = Modifier
+ .padding(start = 10.dp)
+ .height(30.dp),
+ contentPadding = PaddingValues(vertical = 5.dp, horizontal = 10.dp)
+ ) {
+ if (phone.isEmpty() || !phone.matches(Regex("^1[3-9]\\d{9}\$"))) {
+ Toasty.showTipsDialog("手机号格式错误")
+ } else {
+ loginViewModel.sendCode(phone)
+ }
+
+ }
+ }
+ MyButton(
+ text = "登录",
+ onClick = {
+ if (companyCode.isEmpty() || phone.isEmpty() || code.isEmpty()) {
+ Toasty.showTipsDialog("公司代码、手机号、验证码不能为空")
+ return@MyButton
+ }
+ keyboardController?.hide()
+ loginViewModel.loginByPhone(companyCode, phone, code)
+ },
+ modifier = Modifier
+ .padding(top = 20.dp)
+ .fillMaxWidth()
+ )
+ }
+}
+
+/**
+ * 人脸识别
+ */
+@Composable
+fun FaceLogin(loginViewModel: LoginViewModel) {
+ Column(
+ modifier = M.padding(20.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.face),
+ contentDescription = null,
+ modifier = M.size(100.dp)
+ )
+ Text(text = "人脸识别登录", modifier = M.padding(vertical = 10.dp))
+ Text(
+ text = "请保持光线充足,人脸无遮挡",
+ color = MyColors.Gray,
+ modifier = M.padding(vertical = 10.dp)
+ )
+ MyButton(
+ text = "开始识别登录",
+ onClick = {
+ loginViewModel.faceRecognize()
+ },
+ modifier = Modifier
+ .padding(top = 5.dp)
+ .fillMaxWidth()
+ )
+ }
+}
+
+@Composable
+fun FuncButton(modifier: Modifier, text: String, icon: Int, onClick: () -> Unit) {
+ Column(
+ modifier = modifier
+ .clickable { onClick() },
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ painter = painterResource(id = icon),
+ contentDescription = null,
+ modifier = M.size(50.dp)
+ )
+ Text(
+ text = text,
+ modifier = M.padding(top = 7.dp),
+ fontSize = MaterialTheme.typography.labelSmall.fontSize,
+ color = MyColors.White
+ )
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/LoginViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/LoginViewModel.kt
new file mode 100644
index 0000000..80171d6
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/LoginViewModel.kt
@@ -0,0 +1,287 @@
+package com.bbitcn.f8.pad.ui.screen;
+
+import com.bbitcn.f8.pad.ui.screen.dialog.AboutDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.AuthDialogData
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.model.net.request.AuthDevice
+import com.bbitcn.f8.pad.model.net.request.LoginByFaceRequest
+import com.bbitcn.f8.pad.model.net.request.LoginPhoneRequest
+import com.bbitcn.f8.pad.model.net.request.LoginRequest
+import com.bbitcn.f8.pad.model.net.request.SendCodeRequest
+import com.bbitcn.f8.pad.model.ui.BaseDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.FaceDialogData
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.Toasty.showTipsDialog
+import com.bbitcn.f8.pad.ui.screen.view.Toasty.showToast
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.MyUtil.encryptPassword
+import com.bbitcn.f8.pad.utils.global.Global
+import com.bbitcn.f8.pad.utils.global.RxTag
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+
+class LoginViewModel : BaseViewModel() {
+
+ private val _defaultCompanyCode = MutableStateFlow("")
+ val companyCode = _defaultCompanyCode.asStateFlow()
+
+ private val _defaultUserName = MutableStateFlow("")
+ val defaultUserName = _defaultUserName.asStateFlow()
+
+ private val _navToMain = MutableStateFlow(false)
+ val navToMain = _navToMain.asStateFlow()
+
+ private val _authDialog = MutableStateFlow(AuthDialogData())
+ val authDialogState: StateFlow = _authDialog.asStateFlow()
+
+ private val _aboutDialog = MutableStateFlow(AboutDialogData())
+ val aboutDialogState: StateFlow = _aboutDialog.asStateFlow()
+
+ private val _editPasswordDialogData = MutableStateFlow(BaseDialogData())
+ val editPasswordDialogData = _editPasswordDialogData.asStateFlow()
+
+ private val _faceDialogData = MutableStateFlow(FaceDialogData())
+ val faceDialogData = _faceDialogData.asStateFlow()
+
+ /**
+ * 验证码发送倒计时
+ */
+ private val _codeSendTime = MutableStateFlow(0)
+ val codeSendTime: StateFlow = _codeSendTime.asStateFlow()
+
+ init {
+ doInIoThreadNoDialog {
+ _defaultUserName.value = MMKVUtil.get(RxTag.USER_ACCOUNT)
+ _defaultCompanyCode.value = MMKVUtil.get(RxTag.TENANT_CODE)
+ }
+ }
+
+ fun showAboutDialog() {
+ doInIoThread("正在加载关于信息") {
+ val tenantCode = MMKVUtil.get(RxTag.TENANT_CODE)
+ if (tenantCode.isEmpty()) {
+ showTipsDialog("此设备未授权,请联系管理员")
+ } else {
+ val result = apiService.getAboutInfo(tenantCode)
+ if (result.code == 1) {
+ _aboutDialog.value = AboutDialogData(
+ showDialog = true,
+ text = result.data.describe
+ ) {
+ _aboutDialog.update { it.copy(showDialog = false) }
+ }
+ }else{
+ showTipsDialog(result.msg)
+ }
+ }
+ }
+ }
+
+ fun showAuthDialog() {
+ doInIoThread("正在查询授权信息") {
+ var tenantCode = MMKVUtil.get(RxTag.TENANT_CODE)
+ var name = MMKVUtil.get(RxTag.AUTH_USER_NAME)
+ var phone = MMKVUtil.get(RxTag.USER_PHONE)
+ val authType : Int
+ if (tenantCode.isEmpty()) {
+ authType = 0
+ } else {
+ val result = apiService.getDeviceState(tenantCode, Global.getDeviceId())
+ if (result.code == 0) {
+ // 未授权
+ authType = 0
+ } else {
+ tenantCode = result.data.tenantCode
+ name = result.data.applicant
+ phone = result.data.phone
+ authType = when (result.data.flag) {
+ 0 -> 1 // 已申请,正在审核中
+ 1 -> 2 // 已授权 显示授权信息
+ else -> 3 // 其他情况 (已拒绝)
+ }
+ }
+ }
+ _defaultCompanyCode.value = tenantCode
+ _authDialog.value = AuthDialogData(
+ showDialog = true,
+ authType = authType,
+ companyId = tenantCode,
+ name = name,
+ phone = phone,
+ onReAuth = {
+ Toasty.showConfirmDialog("确认重新申请授权吗?") {
+ doInIoThreadNoDialog {
+ MMKVUtil.remove(RxTag.TENANT_CODE)
+ MMKVUtil.remove(RxTag.AUTH_USER_NAME)
+ MMKVUtil.remove(RxTag.USER_PHONE)
+ _defaultCompanyCode.value = ""
+ }
+ }
+ },
+ onAuth = { companyCode, name, phone, pwd ->
+ doInIoThread("正在提交授权信息") {
+ val result = apiService.authDevice(
+ AuthDevice(
+ hardwareId = Global.getDeviceId(),
+ tenantCode = companyCode,
+ applicant = name,
+ phone = phone,
+ job = "Empty",
+ pwd = encryptPassword(pwd)//授权密码
+ )
+ )
+ if (result.code == 1) {
+ showTipsDialog(result.msg)
+ MMKVUtil.put(RxTag.TENANT_CODE, companyCode)
+ MMKVUtil.put(RxTag.AUTH_USER_NAME, name)
+ MMKVUtil.put(RxTag.USER_PHONE, phone)
+ _authDialog.value = AuthDialogData(showDialog = false)
+ _defaultCompanyCode.value = companyCode
+ } else {
+ showTipsDialog(result.msg)
+ }
+ }
+ }) {
+ _authDialog.update { it.copy(showDialog = false) }
+ }
+ }
+
+ }
+
+ /**
+ * 登录
+ *
+ * @param account 账号
+ * @param password 密码
+ */
+ fun login(companyCode: String, username: String, password: String) {
+ doInIoThread("正在登录中...") {
+ val result = apiService.login(
+ LoginRequest(
+ account = username,
+ password = encryptPassword(password),
+ tenantcode = companyCode,
+ hardwareid = Global.getDeviceId()
+ )
+ )
+ if (result.code == 1) {
+ MMKVUtil.put(RxTag.ACCESS_TOKEN, result.data.accessToken)
+ MMKVUtil.put(RxTag.REFRESH_TOKEN, result.data.refreshToken)
+ MMKVUtil.put(RxTag.USER_ACCOUNT, username)
+ _navToMain.value = true
+ } else {
+ showTipsDialog(result.msg)
+ }
+ }
+ }
+
+ /**
+ * 登录
+ *
+ * @param account 账号
+ * @param code 密码
+ */
+ fun loginByPhone(companyCode: String, phone: String, code: String) {
+ doInIoThread("正在登录中...") {
+ val result = apiService.loginByPhone(
+ LoginPhoneRequest(
+ phone = phone,
+ smsbucket = "login",
+ smscode = code,
+ tenantcode = companyCode,
+ hardwareid = Global.getDeviceId()
+ )
+ )
+ if (result.code == 1) {
+ _navToMain.value = true
+ MMKVUtil.put(RxTag.ACCESS_TOKEN, result.data.accessToken)
+ MMKVUtil.put(RxTag.REFRESH_TOKEN, result.data.refreshToken)
+ } else {
+ showTipsDialog(result.msg)
+ }
+ }
+ }
+ /**
+ * 登录
+ *
+ * @param account 账号
+ * @param code 密码
+ */
+ fun loginByFace(userId: String, faceToken: String) {
+ doInIoThread("正在登录中...") {
+ delay(300)
+ val result = apiService.loginByFace(
+ LoginByFaceRequest(
+ userid = userId,
+ facetoken = faceToken,
+ tenantcode = MMKVUtil.get(RxTag.TENANT_CODE, ""),
+ hardwareid = Global.getDeviceId()
+ )
+ )
+ if (result.code == 1) {
+ _navToMain.value = true
+ MMKVUtil.put(RxTag.ACCESS_TOKEN, result.data.accessToken)
+ MMKVUtil.put(RxTag.REFRESH_TOKEN, result.data.refreshToken)
+ } else {
+ showTipsDialog(result.msg)
+ }
+ }
+ }
+ fun sendCode(tel: String) {
+ doInIoThread("正在发送验证码...") {
+ val result = apiService.sendCode(SendCodeRequest(5, "login", tel))
+ if (result.code == 1) {
+ Toasty.success("验证码发送成功")
+ _codeSendTime.value = 60
+ while (_codeSendTime.value > 0) {
+ _codeSendTime.update { it - 1 }
+ delay(1000)
+ }
+ } else {
+ showTipsDialog(result.msg)
+ }
+ }
+ }
+
+ fun showForgetPasswordDialog() {
+ doInIoThreadNoDialog {
+ _editPasswordDialogData.value = BaseDialogData(showDialog = true){
+ _editPasswordDialogData.update { it.copy(showDialog = false) }
+ }
+ }
+ }
+
+ fun updateUserName(name: String) {
+ doInIoThreadNoDialog {
+ _defaultUserName.value = name
+ }
+ }
+
+ fun clearUsername() {
+ doInIoThreadNoDialog {
+ MMKVUtil.remove(RxTag.USER_ACCOUNT)
+ _defaultUserName.value = ""
+ }
+ }
+
+ fun faceRecognize() {
+ doInIoThread("正在启动人脸识别...") {
+ _faceDialogData.value = FaceDialogData(
+ showDialog = true,
+ onDismiss = {
+ _faceDialogData.update { it.copy(showDialog = false) }
+ },
+ isRegister = false,
+ isSystemUser = true,
+ onRecognizeFace = { userId,faceToken ->
+ loginByFace(userId, faceToken)
+ }
+ )
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/MainScreen.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/MainScreen.kt
new file mode 100644
index 0000000..6daa5b2
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/MainScreen.kt
@@ -0,0 +1,210 @@
+package com.bbitcn.f8.pad.ui.screen;
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.NavigationRail
+import androidx.compose.material3.NavigationRailItem
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.navigation.compose.rememberNavController
+import androidx.compose.runtime.*
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavHostController
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.MyAnimatedVisibility
+import com.bbitcn.f8.pad.base.MyAnimatedVisibilityFromDownToUp
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.WhiteText
+import com.bbitcn.f8.pad.ui.screen.dialog.AIListenerDialog
+import com.bbitcn.f8.pad.ui.screen.mainFunc.DryCoonScreen
+import com.bbitcn.f8.pad.ui.screen.view.drawer.DrawerViewModel
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.ui.screen.mainFunc.FundsScreen
+import com.bbitcn.f8.pad.ui.screen.mainFunc.HomeScreen
+import com.bbitcn.f8.pad.ui.screen.mainFunc.PurchaseScreen
+import com.bbitcn.f8.pad.ui.screen.mainFunc.setting.SettingScreen
+import com.bbitcn.f8.pad.ui.screen.mainFunc.StatisticsScreen
+import com.bbitcn.f8.pad.ui.screen.mainFunc.UserScreen
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.viewmodel.UpdateViewModel
+import com.cyberecho.ui.screen.ChatDialog
+import kotlinx.coroutines.launch
+import org.xutils.db.annotation.Column
+
+@Preview(showBackground = true, widthDp = 1280)
+@Composable
+fun MainPreview() {
+ MainScreen(navController = rememberNavController(), viewModel(), viewModel(), viewModel(), viewModel())
+}
+
+@Composable
+fun MainScreen(
+ navController: NavHostController,
+ drawerViewModel: DrawerViewModel,
+ updateViewModel: UpdateViewModel,
+ topInfoViewmodel: TopInfoViewModel,
+ mainViewModel: MainViewModel = viewModel()
+) {
+ var isAIDialogVisible by remember { mutableStateOf(false) }
+ val loginExpiredToLogin by Toasty.loginExpiredToLogin.collectAsState()
+ LaunchedEffect(loginExpiredToLogin) {
+ if (loginExpiredToLogin) {
+ Toasty.showToast("登录信息已过期,请重新登录")
+ navController.navigate("login")
+ }
+ }
+ Box(modifier = M.fillMaxWidth(), contentAlignment = Alignment.BottomStart) {
+ var selectedPage by rememberSaveable { mutableStateOf(0) }
+ val coroutineScope = rememberCoroutineScope()
+
+ val menu by mainViewModel.menu.collectAsState()
+ Row(modifier = M.fillMaxWidth()) {
+ NavigationRail(
+ containerColor = MyColors.Black,
+ modifier = M.width(70.dp)
+ ) {
+ menu.forEachIndexed { index, item ->
+ NavigationRailItem(
+ modifier = M.padding(vertical = 2.5.dp),
+ icon = {
+ Image(
+ painter = painterResource(id = item.icon),
+ contentDescription = null,
+ modifier = M.size(30.dp)
+ )
+ },
+ label = {
+ WhiteText(
+ item.title,
+ fontSize = MaterialTheme.typography.bodyLarge.fontSize,
+ )
+ },
+ selected = selectedPage == index,
+ onClick = { selectedPage = index }
+ )
+ }
+ }
+ Box {
+ var index = -1
+ if (menu.isNotEmpty()) {
+ index = menu[selectedPage].index
+ }
+ when (index) {
+ -1 -> {
+ Box(modifier = M.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Text("正在加载菜单,如长时间未响应请点击重新加载")
+ MyButton(text = "刷新菜单") {
+ mainViewModel.refreshMenuPermission()
+ }
+ }
+ }
+ }
+
+ 0 -> HomeScreen(navController)
+ 1 -> UserScreen(navController)
+ 2 -> PurchaseScreen(navController, drawerViewModel = drawerViewModel)
+ 3 -> FundsScreen(navController, drawerViewModel = drawerViewModel)
+ 4 -> StatisticsScreen(drawerViewModel = drawerViewModel)
+ 5 -> DryCoonScreen(navController, drawerViewModel)
+ 6 -> SettingScreen(navController, updateViewModel = updateViewModel, topInfoViewmodel = topInfoViewmodel){
+ mainViewModel.refreshMenuPermission()
+ }
+ }
+ }
+ }
+ Column(
+ modifier = M
+ .width(70.dp)
+ .pointerInput(Unit) {
+ detectTapGestures(
+ onPress = { offset ->
+ coroutineScope.launch {
+ isAIDialogVisible = true
+ mainViewModel.startListening()
+ tryAwaitRelease()
+ coroutineScope.launch {
+ // 隐藏对话框
+ isAIDialogVisible = false
+ mainViewModel.stopListening()
+ }
+ }
+ },
+ onTap = { offset ->
+ coroutineScope.launch {// 在松手时调用onTap并传入松手位置的offset
+ val releasePosition = offset // 获取松手位置
+ val topButtonPosition = Rect(
+ left = 0f,
+ top = 0f,
+ right = 70.dp.toPx(),
+ bottom = 70.dp.toPx()
+ )
+ val bottomButtonPosition = Rect(
+ left = 0f,
+ top = 70.dp.toPx(),
+ right = 70.dp.toPx(),
+ bottom = 140.dp.toPx()
+ )
+ if (topButtonPosition.contains(releasePosition)) {
+ coroutineScope.launch {
+ // 执行第一个按钮点击后的逻辑
+ mainViewModel.showChatDialog(true)
+ }
+ } else if (bottomButtonPosition.contains(releasePosition)) {
+ coroutineScope.launch {
+ // 执行第二个按钮点击后的逻辑
+ mainViewModel.showChatDialog(false)
+ }
+ }
+ }
+ }
+ )
+ }
+ ) {
+ // 第一个Image
+ MyAnimatedVisibilityFromDownToUp(isAIDialogVisible) {
+ Image(
+ modifier = M
+ .fillMaxWidth()
+ .padding(15.dp),
+ painter = painterResource(id = R.drawable.icon_read_card),
+ contentDescription = null,
+ contentScale = ContentScale.Inside
+ )
+ }
+ Image(
+ modifier = M
+ .fillMaxWidth()
+ .padding(start = 15.dp, end = 15.dp, bottom = 15.dp),
+ painter = painterResource(id = R.drawable.icon_ai),
+ contentDescription = null,
+ contentScale = ContentScale.Inside
+ )
+ }
+ }
+ val question by mainViewModel.question.collectAsState()
+ MyAnimatedVisibility(isAIDialogVisible) {
+ AIListenerDialog(question)
+ }
+ val chatDialogData by mainViewModel.chatDialogData.collectAsState()
+ ChatDialog(chatDialogData)
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/MainViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/MainViewModel.kt
new file mode 100644
index 0000000..b74a3d5
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/MainViewModel.kt
@@ -0,0 +1,284 @@
+package com.bbitcn.f8.pad.ui.screen;
+
+import android.os.Bundle
+import com.bbitcn.f8.pad.MyApp
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.PollingTask
+import com.bbitcn.f8.pad.utils.externalModules.devices.light.Light_
+import com.bbitcn.f8.pad.utils.externalModules.devices.printer.PrinterBT
+import com.bbitcn.f8.pad.utils.externalModules.devices.printer.JTPrinterUSB
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.uhf.UHFReaderG06M_G25M
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.uhf.UHFReaderG20R
+import com.bbitcn.f8.pad.utils.externalModules.devices.scale.ScaleBT
+import com.bbitcn.f8.pad.utils.externalModules.devices.scale.ScaleSerial
+import com.bbitcn.f8.pad.utils.externalModules.manager.bluetooth.MyBlueTooth
+import com.bbitcn.f8.pad.utils.externalModules.manager.serial.SerialDeviceConnector
+import com.bbitcn.f8.pad.utils.externalModules.manager.serial.SerialDeviceConnector2
+import com.bbitcn.f8.pad.utils.externalModules.manager.usb.UsbDeviceConnector
+import com.bbitcn.f8.pad.utils.global.Global
+import com.bbitcn.f8.pad.utils.log.MyLog
+import com.bbitcn.sericulture.utils.database.dynamicRoom.MenuPermissionListTempDatabase
+import com.cyberecho.ui.screen.ChatDialogData
+import com.google.gson.Gson
+import com.google.gson.JsonObject
+import com.google.gson.JsonSyntaxException
+import com.iflytek.cloud.RecognizerListener
+import com.iflytek.cloud.RecognizerResult
+import com.iflytek.cloud.SpeechConstant
+import com.iflytek.cloud.SpeechError
+import com.iflytek.cloud.SpeechRecognizer
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import org.json.JSONObject
+import org.json.JSONTokener
+
+
+class MainViewModel : BaseViewModel(), RecognizerListener {
+
+ // 输入弹窗
+ private val _chatDialogData = MutableStateFlow(ChatDialogData())
+ val chatDialogData = _chatDialogData.asStateFlow()
+
+ private val _menu = MutableStateFlow>(emptyList())
+ val menu = _menu.asStateFlow()
+
+ lateinit var mIat: SpeechRecognizer
+
+ val menuToKey = listOf(
+ "index" to MenuInfo(0, "首页", R.drawable.index),
+ "nonghu" to MenuInfo(1, "农户", R.drawable.user),
+ "shougou" to MenuInfo(2, "收购", R.drawable.purchase),
+ "kuanxiang" to MenuInfo(3, "款项", R.drawable.funds),
+ "tongji" to MenuInfo(4, "统计", R.drawable.statistics),
+ "ganjian" to MenuInfo(5, "干茧", R.drawable.purchase),
+ "shebei" to MenuInfo(6, "更多", R.drawable.setting),
+ )
+
+ init {
+ //初始化识别无UI识别对象
+ initVoiceRecognizer()
+ // 初始化菜单权限
+ refreshMenuPermission()
+ // 初始化注册表相关设置
+ initRegistry()
+ // 自动连接硬件
+ initDeviceAutoConnect()
+ }
+
+ private fun initRegistry() {
+ doInIoThreadNoDialog {
+ // 称重模式
+ val purchaseWeightMode = apiService.getPurchaseWeightMode("ACQUISITION_MOLT_TYPE")
+ if (purchaseWeightMode.code == 1) {
+ MMKVUtil.put(
+ Global.WEIGHT_MODE,
+ purchaseWeightMode.data.primaryKeyValue.toIntOrNull() ?: 0
+ )
+ }
+ }
+ }
+
+ private fun initDeviceAutoConnect() {
+ PollingTask.getInstance().startDelayedTask("AutoConnect", delaySeconds = 5) {
+ doInIoThreadNoDialog {
+ val devices = listOf(
+ // 串口 称
+ ScaleSerial,
+ // 串口 灯具
+ Light_,
+ // 串口 读卡器
+ UHFReaderG06M_G25M,
+ UHFReaderG20R,
+ // USB 打印机
+ JTPrinterUSB,
+ // 蓝牙 打印机
+ PrinterBT,
+ // 蓝牙 称
+ ScaleBT,
+ )
+ devices.forEach {
+ if (it.getAutoConnectOnStartUp()) {
+ if (it is SerialDeviceConnector) {
+ // 串口称 串口读卡器
+ it.connect(it.getPort(), it.getBaudRate())
+ } else if (it is SerialDeviceConnector2) {
+ // 串口 灯具
+ it.connect(it.getRDeviceName(), it.getBaudRate())
+ } else if (it is UsbDeviceConnector) {
+ // USB 打印机
+ it.connect(it.getVId(), it.getPId())
+ } else if (it is MyBlueTooth) {
+ // 蓝牙设备
+ it.startScan()
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fun refreshMenuPermission() {
+ doInIoThread("正在加载菜单") {
+ val menuPermission = apiService.getMenuPermission()
+ MenuPermissionListTempDatabase.init(menuPermission.data)
+ val menuList = mutableListOf()
+ if (menuPermission.code != 1) {
+ Toasty.showTipsDialog(menuPermission.msg)
+ } else {
+ val menuPermissionList = menuPermission.data.map { it.uniquecode }
+ for (menu in menuToKey) {
+ if (menuPermissionList.contains(menu.first)) {
+ menuList.add(menu.second)
+ }
+ }
+ _menu.value = menuList
+ }
+ menuList.add(menuToKey[6].second)
+ _menu.value = menuList
+ }
+ }
+
+ private fun initVoiceRecognizer() {
+ doInIoThreadNoDialog {
+ //使用SpeechRecognizer对象,可根据回调消息自定义界面;
+ mIat = SpeechRecognizer.createRecognizer(MyApp.appContext, {
+
+ });
+ //设置语法ID和 SUBJECT 为空,以免因之前有语法调用而设置了此参数;或直接清空所有参数,具体可参考 DEMO 的示例。
+ mIat.setParameter(SpeechConstant.CLOUD_GRAMMAR, null);
+ mIat.setParameter(SpeechConstant.SUBJECT, null);
+ //设置返回结果格式,目前支持json,xml以及plain 三种格式,其中plain为纯听写文本内容
+ mIat.setParameter(SpeechConstant.RESULT_TYPE, "json");
+ mIat.setParameter(SpeechConstant.ENGINE_TYPE, "cloud");
+ //设置语音输入语言,zh_cn为简体中文
+ mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
+ //设置结果返回语言
+ mIat.setParameter(SpeechConstant.ACCENT, "mandarin");
+ // 设置语音前端点:静音超时时间,单位ms,即用户多长时间不说话则当做超时处理
+ //取值范围{1000~10000}
+ mIat.setParameter(SpeechConstant.VAD_BOS, "4000");
+ //设置语音后端点:后端点静音检测时间,单位ms,即用户停止说话多长时间内即认为不再输入,
+ //自动停止录音,范围{0~10000}
+ mIat.setParameter(SpeechConstant.VAD_EOS, "1000");
+ //设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点
+ mIat.setParameter(SpeechConstant.ASR_PTT, "1");
+ }
+ }
+
+ fun startListening() {
+ //开始识别,并设置监听器
+ _question.value = ""
+ mIat.startListening(this);
+ }
+
+ fun stopListening() {
+ mIat.stopListening()
+ }
+
+ fun showChatDialog(useEmpty: Boolean) {
+ _chatDialogData.update {
+ it.copy(
+ showDialog = true,
+ question = if (useEmpty) "" else _question.value,
+ onDismiss = {
+ _chatDialogData.value = _chatDialogData.value.copy(showDialog = false)
+ }
+ )
+ }
+ }
+
+ override fun onVolumeChanged(volume: Int, data: ByteArray) {
+ MyLog.test("当前正在说话,音量大小 = " + volume + " 返回音频数据 = " + data.size);
+ }
+
+ override fun onBeginOfSpeech() {
+ MyLog.test("开始说话")
+ }
+
+ override fun onEndOfSpeech() {
+ MyLog.test("结束说话")
+ }
+
+ override fun onResult(results: RecognizerResult, isLast: Boolean) {
+ if (isLast) {
+ MyLog.test("onResult 结束")
+ }
+ printResult(results)
+ }
+
+ override fun onError(p0: SpeechError?) {
+ MyLog.test("onError: ${p0?.errorCode} ${p0?.errorDescription}")
+ }
+
+ override fun onEvent(eventType: Int, p1: Int, p2: Int, p3: Bundle?) {
+ // 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
+ // 若使用本地能力,会话id为null
+ // if (SpeechEvent.EVENT_SESSION_ID == eventType) {
+ // String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
+ // Log.d(TAG, "session id =" + sid);
+ // }
+ }
+
+ // 用HashMap存储听写结果
+ private val mIatResults: HashMap = LinkedHashMap()
+ private var _question = MutableStateFlow("")
+ val question = _question.asStateFlow()
+
+ /**
+ * 显示结果
+ */
+ private fun printResult(results: RecognizerResult) {
+ val text: String = parseIatResult(results.resultString)
+ var sn: String? = null
+ // 读取json结果中的sn字段
+ try {
+ val resultJson = Gson().fromJson(results.resultString, JsonObject::class.java)
+ sn = resultJson.get("sn")?.asString
+ } catch (e: JsonSyntaxException) {
+ e.printStackTrace()
+ }
+
+ mIatResults.put(sn, text)
+ val resultBuffer = StringBuffer()
+ for (key in mIatResults.keys) {
+ resultBuffer.append(mIatResults.get(key))
+ }
+ _question.value = resultBuffer.toString()
+ }
+
+ fun parseIatResult(json: String?): String {
+ val ret = StringBuffer()
+ try {
+ val tokener = JSONTokener(json)
+ val joResult = JSONObject(tokener)
+
+ val words = joResult.getJSONArray("ws")
+ for (i in 0 until words.length()) {
+ // 转写结果词,默认使用第一个结果
+ val items = words.getJSONObject(i).getJSONArray("cw")
+ val obj = items.getJSONObject(0)
+ ret.append(obj.getString("w"))
+ // 如果需要多候选结果,解析数组其他字段
+// for(int j = 0; j < items.length(); j++)
+// {
+// JSONObject obj = items.getJSONObject(j);
+// ret.append(obj.getString("w"));
+// }
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ return ret.toString()
+ }
+
+
+ data class MenuInfo(
+ val index: Int,
+ val title: String,
+ val icon: Int
+ )
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/TopBar.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/TopBar.kt
new file mode 100644
index 0000000..8b541d2
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/TopBar.kt
@@ -0,0 +1,336 @@
+package com.bbitcn.f8.pad.ui.screen
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.SizeTransform
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
+import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.VerticalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavController
+import androidx.navigation.compose.rememberNavController
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.WhiteText
+import com.bbitcn.f8.pad.base.isBluetoothEnabled
+import com.bbitcn.f8.pad.receiver.SystemInfoReceiver
+import com.bbitcn.f8.pad.ui.screen.view.drawer.DrawerViewModel
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.utils.externalModules.devices.scale.ScaleBT
+import com.bbitcn.f8.pad.utils.externalModules.devices.printer.PrinterBT
+import com.bbitcn.f8.pad.utils.externalModules.devices.printer.JTPrinterUSB
+import com.bbitcn.f8.pad.utils.externalModules.devices.water.WaterCutMeterBT
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.nfc.NFCUtils
+import com.bbitcn.f8.pad.utils.externalModules.devices.scale.ScaleSerial
+
+/**
+ *
+ * @Description TODO
+ * @Author DuanKanji
+ * @CreateTime 2024年06月03日 09:28:17
+ */
+
+@Preview(showBackground = true, widthDp = 400)
+@Composable
+fun SystemInfoPreview() {
+ MyTopBar(
+ navController = rememberNavController(),
+ topInfoViewModel = viewModel(),
+ drawerViewModel = DrawerViewModel(),
+ "main"
+ )
+}
+
+@Composable
+fun MyTopBar(
+ navController: NavController,
+ topInfoViewModel: TopInfoViewModel,
+ drawerViewModel: DrawerViewModel,
+ curPageName: String
+) {
+ val context = LocalContext.current
+ val systemInfoReceiver = remember { SystemInfoReceiver(context) }
+ val date by topInfoViewModel.date.collectAsState()
+ val time by topInfoViewModel.time.collectAsState()
+ val logoState by topInfoViewModel.logoState.collectAsState()
+
+ DisposableEffect(context) {
+ systemInfoReceiver.register()
+ onDispose {
+ systemInfoReceiver.unregister()
+ }
+ }
+ Row(
+ modifier = M
+ .fillMaxWidth()
+ .height(45.dp)
+ .background(color = MyColors.Black),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ AnimatedContent(
+ logoState,
+ transitionSpec = {
+ fadeIn(
+ animationSpec = tween(1000)
+ ) togetherWith fadeOut(animationSpec = tween(1000))
+ },
+ modifier = M.clickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null
+ ) {
+ if (logoState.first.first != R.drawable.logo_white) {
+ // 当前页面
+// val curPage = navController.currentDestination?.route ?: "main"
+// Toasty.setCurPage(curPage)
+ topInfoViewModel.toScreen()
+ navController.popBackStack()
+ }
+ },
+ ) {
+ Image(
+ modifier = M
+ .padding(5.dp)
+ .size(60.dp),
+ painter = painterResource(id = it.first.first),
+ contentDescription = "logo"
+ )
+ }
+ AnimatedContent(
+ targetState = logoState,
+ transitionSpec = {
+ // Compare the incoming number with the previous number.
+ if (targetState.second > initialState.second) {
+ // If the target number is larger, it slides up and fades in
+ // while the initial (smaller) number slides up and fades out.
+ slideInVertically { height -> height } + fadeIn() togetherWith
+ slideOutVertically { height -> -height } + fadeOut()
+ } else {
+ // If the target number is smaller, it slides down and fades in
+ // while the initial number slides down and fades out.
+ slideInVertically { height -> -height } + fadeIn() togetherWith
+ slideOutVertically { height -> height } + fadeOut()
+ }.using(
+ // Disable clipping since the faded slide-in/out should
+ // be displayed out of bounds.
+ SizeTransform(clip = false)
+ )
+ }, label = "animated content"
+ ) { value ->
+ Text(
+ text = value.first.second,
+ M.padding(horizontal = 8.dp),
+ color = MyColors.White,
+ fontWeight = FontWeight.Bold,
+ fontSize = MaterialTheme.typography.headlineMedium.fontSize
+ )
+
+ }
+ Spacer(modifier = M.weight(1f))
+ if (curPageName != "login") {
+ Row(
+ horizontalArrangement = Arrangement.End,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ val padding = M.padding(horizontal = 5.dp)
+// Image(
+// modifier = M
+// .clickable{
+// topInfoViewModel.getFrpNewVersion()
+// }
+// .padding(end = 5.dp)
+// .size(25.dp),
+// painter = painterResource(id = R.drawable.icon_sos),
+// contentDescription = "frp",
+// )
+ Image(
+ modifier = M
+ .padding(end = 5.dp)
+ .size(25.dp),
+ painter = painterResource(id = R.drawable.icon_lock),
+ contentDescription = "screen_lock",
+ )
+ Row(
+ modifier = M.clickable { drawerViewModel.openSetDrawer() },
+ horizontalArrangement = Arrangement.End,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ if (NFCUtils.isEnable()) {
+ Image(
+ modifier = M
+ .padding(end = 5.dp)
+ .size(25.dp),
+ painter = painterResource(id = R.drawable.nfc),
+ contentDescription = "NFC",
+ )
+ }
+ if (isBluetoothEnabled()) {
+ Image(
+ modifier = M
+ .padding(end = 5.dp)
+ .size(25.dp),
+ painter = painterResource(id = R.drawable.bluetooth),
+ contentDescription = "蓝牙",
+ )
+ }
+ val printer1 by PrinterBT.state.collectAsState()
+ val printer2 by JTPrinterUSB.state.collectAsState()
+ val printerState = if (printer1 == 1 || printer2 == 1) 1 else 0
+ Image(
+ modifier = M
+ .padding(end = 5.dp)
+ .size(25.dp),
+ painter = painterResource(id = if (printerState == 1) R.drawable.print_on else R.drawable.print_off),
+ contentDescription = "打印机",
+ )
+ val water by WaterCutMeterBT.state.collectAsState()
+ Image(
+ modifier = M
+ .padding(end = 5.dp)
+ .size(25.dp),
+ painter = painterResource(id = if (water == 1) R.drawable.water_on else R.drawable.water_off),
+ contentDescription = "含水仪",
+ )
+ val scale1 by ScaleBT.state.collectAsState()
+ val scale2 by ScaleSerial.state.collectAsState()
+ val scaleState = if (scale1 == 1 || scale2 == 1) 1 else 0
+ Image(
+ modifier = M
+ .padding(end = 5.dp)
+ .size(25.dp),
+ painter = painterResource(id = if (scaleState == 1) R.drawable.scale_on else R.drawable.scale_off),
+ contentDescription = "电子秤",
+ )
+ //分隔符
+ VerticalDivider(
+ modifier = padding
+ .width(1.dp),
+ color = MyColors.White
+ )
+ // 电量显示
+ val batteryVisible by topInfoViewModel.batteryVisible.collectAsState()
+ if (batteryVisible) {
+ val batteryLevel by systemInfoReceiver.batteryLevel.collectAsState()
+ BatteryLevelBoxWithImage(batteryLevel)
+ }
+ val signalStrength by systemInfoReceiver.signalStrength.collectAsState()
+ val networkType by systemInfoReceiver.networkType.collectAsState()
+ Image(
+ modifier = M
+ .padding(end = 10.dp)
+ .size(25.dp),
+ painter = painterResource(
+ id =
+ if (networkType == "WIFI") {
+ when (signalStrength) {
+ 0 -> R.drawable.server_0
+ 1 -> R.drawable.wifi_2
+ 2 -> R.drawable.wifi_2
+ 3 -> R.drawable.wifi_3
+ 4 -> R.drawable.wifi_4
+ else -> R.drawable.server_0
+ }
+ } else if (networkType == "Cellular") {
+ when (signalStrength) {
+ 0 -> R.drawable.server_0
+ 1 -> R.drawable._4g_1
+ 2 -> R.drawable._4g_2
+ 3 -> R.drawable._4g_3
+ 4 -> R.drawable._4g_4
+ else -> R.drawable._4g_0
+ }
+ } else if (networkType == "Ethernet") {
+ R.drawable.rj45
+ } else {
+ R.drawable.server_0
+ }
+ ),
+ contentDescription = "信号",
+ )
+ }
+ WhiteText(
+ text = date,
+ fontSize = 15.sp,
+ modifier = M.padding(end = 5.dp)
+ )
+ WhiteText(
+ text = time,
+ modifier = M.padding(end = 15.dp),
+ fontSize = 30.sp
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun BatteryLevelBoxWithImage(batteryLevel: Int) {
+ Box(
+ contentAlignment = Alignment.Center, // 文本居中显示
+ modifier = M
+ .padding(horizontal = 10.dp)
+ .width(40.dp) // 电池框宽度
+ .height(20.dp) // 电池框高度
+ ) {
+ // 使用图片作为电池边框
+ Image(
+ painter = painterResource(id = R.drawable.battery),
+ contentDescription = "电池边框",
+ modifier = M.matchParentSize(),
+ contentScale = ContentScale.FillBounds
+ )
+ // 绘制电量填充
+ Canvas(
+ modifier = M
+ .matchParentSize()
+ .padding(top = 3.2.dp, start = 3.2.dp, end = 5.9.dp, bottom = 3.45.dp)
+ ) {
+ val fillWidth = size.width * batteryLevel / 100
+ drawRect(
+ color = if (batteryLevel > 0.2f) MyColors.Green else MyColors.Red, // 低电量红色
+ topLeft = Offset(0f, 0f), // 从左上角开始填充
+ size = Size(fillWidth, size.height) // 宽度根据电量动态变化
+ )
+ }
+ // 居中显示电量文本
+ Text(
+ text = "${batteryLevel}%", // 电量百分比
+ color = MyColors.White, // 文本颜色
+ fontSize = MaterialTheme.typography.bodySmall.fontSize // 字体大小适配小框
+ )
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/TopInfoViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/TopInfoViewModel.kt
new file mode 100644
index 0000000..21e43bc
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/TopInfoViewModel.kt
@@ -0,0 +1,94 @@
+package com.bbitcn.f8.pad.ui.screen
+
+import com.bbitcn.f8.pad.MyApp
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.MyUtil
+import com.bbitcn.f8.pad.utils.global.Global
+import com.bbitcn.f8.pad.utils.global.RxTag
+import com.bbitcn.f8.pad.utils.network.RetrofitClientIOT
+import com.blankj.utilcode.util.StringUtils
+import com.tencent.mmkv.MMKV
+import com.xhinliang.lunarcalendar.LunarCalendar
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import java.text.SimpleDateFormat
+import java.util.Calendar
+import java.util.Date
+
+class TopInfoViewModel : BaseViewModel() {
+
+ // 全局蓝牙设备
+ private val _date = MutableStateFlow("")
+ val date: StateFlow = _date.asStateFlow()
+
+ private val _time = MutableStateFlow("")
+ val time: StateFlow = _time.asStateFlow()
+
+ private val _logoState = MutableStateFlow(Pair(R.drawable.logo_white, "智慧蚕桑收购系统") to 0)
+ val logoState: StateFlow, Int>> = _logoState.asStateFlow()
+
+ init {
+ doInIoThreadNoDialog {
+ refreshBatteryInfo()
+ refreshTitle()
+ //循环更新TopBar UI
+ pollingTask.startPollingTaskOnIOThread(RxTag.BAR_UI, 5) {
+ val calendar = Calendar.getInstance()
+ val lunarCalender: LunarCalendar = LunarCalendar.obtainCalendar(
+ calendar[Calendar.YEAR],
+ calendar[Calendar.MONTH] + 1,
+ calendar[Calendar.DAY_OF_MONTH]
+ )
+ _date.update {
+ SimpleDateFormat("yyyy/MM/dd \n").format(Date()) +
+ (lunarCalender.getLunarMonth() + "月" + lunarCalender.getLunarDay())
+ }
+ _time.update {
+ SimpleDateFormat("HH:mm").format(Date())
+ }
+ }
+ }
+ }
+
+ fun toScreen(target: String = "main", otherInfo: String = "") {
+ fun title(base: String): String = if (otherInfo.isNotEmpty()) "$base $otherInfo" else base
+
+ _logoState.value = when (target) {
+ "addUser" -> Pair(R.drawable.back, title("新增农户")) to 1
+ "editUser" -> Pair(R.drawable.back, title("修改农户")) to 1
+ "weight" -> Pair(R.drawable.back, title("称重")) to 1
+ "pay" -> Pair(R.drawable.back, title("支付")) to 1
+ "camera" -> Pair(R.drawable.back, title("相机预览")) to 1
+ "dryCoonOperateIn" -> Pair(R.drawable.back, title("干茧入库")) to 1
+ "dryCoonOperateOut" -> Pair(R.drawable.back, title("干茧出库")) to 1
+ "dryCoonOperateAir" -> Pair(R.drawable.back, title("摊晾计划")) to 1
+ else -> Pair(R.drawable.logo_white, _title.value) to 0
+ }
+ }
+
+
+ private val _batteryVisible = MutableStateFlow(true)
+ val batteryVisible = _batteryVisible.asStateFlow()
+
+ private val _title = MutableStateFlow("智慧蚕桑收购系统")
+ val title = _title.asStateFlow()
+
+ fun refreshTitle() {
+ doInIoThreadNoDialog {
+ _title.value = MMKVUtil.get(Global.TITLE, "智慧蚕桑收购系统")
+ }
+ }
+
+ fun refreshBatteryInfo() {
+ doInIoThreadNoDialog {
+ _batteryVisible.value = MMKVUtil.get(Global.BATTERY_VISIBLE, true)
+ toScreen()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/AIListenerDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/AIListenerDialog.kt
new file mode 100644
index 0000000..47da3b9
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/AIListenerDialog.kt
@@ -0,0 +1,93 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.animation.animateColor
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.rememberInfiniteTransition
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MyCard
+
+
+@Preview(showBackground = true, widthDp = 1280)
+@Composable
+fun AIDialogPreview() {
+ AIListenerDialog()
+}
+
+
+@Composable
+fun AIListenerDialog(userSaid:String = "") {
+ val infiniteTransition = rememberInfiniteTransition(label = "infinite transition")
+ // 动态颜色动画
+ val animatedColor by infiniteTransition.animateColor(
+ initialValue = Color(0xFF60DDAD),
+ targetValue = Color(0xFF4285F4),
+ animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse),
+ label = "background color"
+ )
+
+ // 动态边框颜色动画
+ val borderColor by infiniteTransition.animateColor(
+ initialValue = Color(0x80FF4081),
+ targetValue = Color(0x803F51B5),
+ animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse),
+ label = "border color"
+ )
+ // 实现包含动态边框的外框
+ Box(modifier = M.fillMaxSize()
+ .border(10.dp, borderColor) , contentAlignment = Alignment.Center) {
+ MyCard(elevation = 20.dp, radius = 15.dp) {
+ Box(
+ modifier = M
+ .background(color = borderColor)
+// .border(10.dp, borderColor) // 动态边框颜色
+ .size(width = 500.dp, height = 200.dp)
+ .padding(10.dp), // 内容内边距
+ ) {
+ MyCard(M.fillMaxSize(), elevation = 0.dp, radius = 10.dp) {
+ Column (modifier = M.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) {
+ Text(
+ modifier = M.fillMaxWidth(),
+ text = "您好,我是F8助手,有什么可以帮助您的吗?",
+ color = animatedColor,
+ textAlign = TextAlign.Center,
+ fontSize = MaterialTheme.typography.titleLarge.fontSize
+ )
+ Text(
+ modifier = M.fillMaxWidth(),
+ text = "正在聆听中,请讲",
+ color = animatedColor,
+ textAlign = TextAlign.Center,
+ fontSize = MaterialTheme.typography.titleMedium.fontSize
+ )
+ Text(
+ modifier = M.fillMaxWidth(),
+ text = userSaid,
+ textAlign = TextAlign.Center,
+ fontSize = MaterialTheme.typography.titleLarge.fontSize
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/AboutDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/AboutDialog.kt
new file mode 100644
index 0000000..c5d3acb
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/AboutDialog.kt
@@ -0,0 +1,113 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.BaseDialogFrame
+import com.bbitcn.f8.pad.base.BigButton
+import com.bbitcn.f8.pad.base.MyAnimatedVisibility
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.ui.theme.MyColors
+
+data class AboutDialogData(
+ var showDialog: Boolean = false,
+ val text: String = "关于我们",
+ val tel: String = "000-000-000",
+ val onDismiss: () -> Unit = {}
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun AboutPreview() {
+ AboutDialog(
+ AboutDialogData(
+ showDialog = true,
+ text = "奥立集团旗下四川主干信息技术有限公司,是一家专业从事搭建互联网云平台," +
+ "服务传统行业的互联网企业,主营业务包括软件产品设计与研发、系统集成与维护" +
+ "、互联网数据挖掘及应用。公司秉承“行业互助,资源共享、助小众企业实现大众信息化" +
+ "”的企业使命让客户了解自身所在行业的大数据环境,从而帮助传统行业客户建立全新的" +
+ "互联网形象。奥立集团旗下四川主干信息技术有限公司,是一家专业从事搭建互联网云平台" +
+ ",服务传统行业的互联网企业,主营业务包括软件产品设计与研发、系统集成与维护、互联" +
+ "网数据挖掘及应用。公司秉承“行业互助,资源共享、助小众企业实现大众信息化”的企业使" +
+ "命,旨在帮助客户梳理内部精细化管理流程,让客户了解自身所在行业的大数据环境,从而" +
+ "帮助传统行业客户建立全新的互联网形象。"
+ ) {}
+ )
+}
+
+@Composable
+fun AboutDialog(info: AboutDialogData) {
+ MyDialog(
+ title = "关于我们",
+ showDialog = info.showDialog,
+ onDismissRequest = info.onDismiss
+ ) {
+ Column(
+ modifier = M
+ .fillMaxSize()
+ .padding(horizontal = 30.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ MyCard(elevation = 0.5.dp, modifier = M.height(350.dp)) {
+ Column(
+ modifier = M
+ .fillMaxSize()
+ .background(color = MyColors.LightGray)
+ .padding(30.dp)
+ .verticalScroll(rememberScrollState()) // 添加滚动支持
+ ) {
+ Text(
+ text = info.text,
+ fontSize = MaterialTheme.typography.headlineMedium.fontSize,
+ fontWeight = FontWeight.Normal
+ )
+ }
+ }
+ Row(
+ modifier = M.padding(vertical = 20.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.logo_green),
+ contentDescription = "电话",
+ modifier = M
+ .size(70.dp)
+ .padding(15.dp)
+ )
+ Text("智慧蚕桑收购系统")
+ Spacer(modifier = M.weight(1f))
+ Text("联系方式:${info.tel}")
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/AddEditUserDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/AddEditUserDialog.kt
new file mode 100644
index 0000000..12eba74
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/AddEditUserDialog.kt
@@ -0,0 +1,189 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.toMutableStateList
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.base.MyTextField
+import com.bbitcn.f8.pad.model.net.response.SetUserListResponse
+import com.bbitcn.f8.pad.model.net.response.UserRoleInfoResponse
+import com.bbitcn.f8.pad.ui.screen.secondFunc.InputFrame
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.common.CombinedDropdownMenu
+import com.bbitcn.f8.pad.ui.screen.view.common.SelectableChipGroup
+
+data class AddEditUserDialogData(
+ var showDialog: Boolean = false,
+ var onDismiss: () -> Unit = {},
+
+ val userRoles: List = emptyList(),
+
+ val depName: String = "",
+ val onInsert: (
+ username: String, phone: String,
+ name: String, sex: Boolean, idCard: String, sort: Int, roles: List
+ ) -> Unit = { _, _, _, _, _, _, _ -> },
+
+ val data: SetUserListResponse.Data = SetUserListResponse.Data(),
+ val onUpdate: (
+ username: String, phone: String,
+ name: String, sex: Boolean, idCard: String, sort: Int, roles: List
+ ) -> Unit = { _, _, _, _, _, _, _ -> }
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun AddEditUserDialogPreview() {
+ AddEditUserDialog(
+ AddEditUserDialogData(showDialog = true)
+ )
+}
+
+@Composable
+fun AddEditUserDialog(info: AddEditUserDialogData) {
+ val data = info.data
+ var username by rememberSaveable { mutableStateOf(data.loginName) }
+ var phone by rememberSaveable { mutableStateOf(data.tel) }
+ var name by rememberSaveable { mutableStateOf(data.name) }
+ var sex by rememberSaveable { mutableStateOf(data.sex == "男") }
+ var idCard by rememberSaveable { mutableStateOf(data.idCard) }
+ var sort by rememberSaveable { mutableStateOf(data.sort.toString()) }
+ var userRoles by rememberSaveable { mutableStateOf>>(emptyList()) }
+ MyDialog("${if (info.data.id == -1L) "新增" else "编辑"}用户-${if (info.depName.isEmpty()) data.depname else info.depName}",
+ info.showDialog,
+ onDismissRequest = { info.onDismiss() },
+ clickOKStr = "确定",
+ onClickOK = {
+ // 姓名、用户名、角色 为必填项
+ if (username.isEmpty() || name.isEmpty() || userRoles.none { it.second }) {
+ Toasty.showToast("姓名、用户名、角色为必填项")
+ return@MyDialog
+ }
+ if (info.data.id == -1L) {
+ info.onInsert(
+ username, phone, name, sex, idCard, sort.toIntOrNull() ?: 0,
+ info.userRoles
+ .filter { main -> userRoles.any { it.first == main.roleName && it.second } }
+ .map { it.sysid }
+ )
+ } else {
+ info.onUpdate(
+ username, phone, name, sex, idCard, sort.toIntOrNull() ?: 0,
+ info.userRoles
+ .filter { main -> userRoles.any { it.first == main.roleName && it.second } }
+ .map { it.sysid }
+ )
+ }
+ }
+ ) {
+ LaunchedEffect(info.showDialog) {
+ if (info.showDialog) {
+ username = data.loginName
+ phone = data.tel
+ name = data.name
+ sex = data.sex == "男"
+ idCard = data.idCard
+ sort = data.sort.toString()
+ userRoles = info.userRoles.map { it.roleName to data.role.contains(it.roleName) }
+ }
+ }
+
+ LazyColumn(modifier = M.fillMaxSize()) {
+ item {
+ Row(
+ modifier = M.fillMaxWidth().animateItem(),
+ horizontalArrangement = Arrangement.spacedBy(10.dp)
+ ) {
+ Column(modifier = M.weight(1f)) {
+ InputFrame("用户名*") {
+ MyTextField(
+ modifier = M.weight(1f),
+ hint = "用户名",
+ value = username
+ ) {
+ username = it
+ }
+ }
+ InputFrame("姓名*") {
+ MyTextField(
+ modifier = M.weight(1f),
+ hint = "姓名",
+ value = name
+ ) {
+ name = it
+ }
+ }
+ InputFrame("手机号") {
+ MyTextField(
+ modifier = M.weight(1f),
+ hint = "手机号",
+ value = phone
+ ) {
+ phone = it
+ }
+ }
+ }
+ Column(modifier = M.weight(1f)) {
+ InputFrame("性别") {
+ CombinedDropdownMenu(
+ M.weight(1f),
+ listOf("男", "女"),
+ "性别",
+ value = if (sex) "男" else "女"
+ ) {
+ sex = if (it == "男") true else false
+ }
+ }
+ InputFrame("身份证号") {
+ MyTextField(
+ modifier = M.weight(1f),
+ hint = "身份证号",
+ value = idCard
+ ) {
+ idCard = it
+ }
+ }
+ InputFrame("排序") {
+ MyTextField(
+ modifier = M.weight(1f),
+ hint = "排序",
+ isNumberInputType = true,
+ value = sort
+ ) {
+ sort = it
+ }
+ }
+ }
+ }
+ InputFrame("角色*") {
+ SelectableChipGroup(M.fillMaxWidth(), userRoles) { sel ->
+ userRoles = userRoles.map {
+ if (it.first == sel.first) {
+ sel.copy(second = !sel.second)
+ } else {
+ it
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/AddTicketDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/AddTicketDialog.kt
new file mode 100644
index 0000000..58a0a5e
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/AddTicketDialog.kt
@@ -0,0 +1,239 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.paging.compose.collectAsLazyPagingItems
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.BaseDialogFrame
+import com.bbitcn.f8.pad.base.BigButton
+import com.bbitcn.f8.pad.base.MyAnimatedVisibility
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.base.MyRefreshTable
+import com.bbitcn.f8.pad.base.MyTable
+import com.bbitcn.f8.pad.base.MyTableData
+import com.bbitcn.f8.pad.base.MyTextField
+import com.bbitcn.f8.pad.base.TableHeadLine
+import com.bbitcn.f8.pad.base.VerticalTabPages
+import com.bbitcn.f8.pad.ui.screen.mainFunc.UserViewModel
+import com.bbitcn.f8.pad.ui.screen.secondFunc.AddUserViewModel
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.theme.MyColors
+
+data class AddTicketDialogData(
+ val showDialog: Boolean = false,
+ val onDismiss: () -> Unit = {},
+
+ val navToWeight: (String) -> Unit = {},
+
+ val cardReaderForUserCard: () -> Unit = {},
+ val cardReaderForIdCard: () -> Unit = {},
+ val cardReaderForBankCard: () -> Unit = {},
+
+ val ocrForIdCard: () -> Unit = {},
+ val ocrForBankCard: () -> Unit = {},
+
+ val faceRecognition: () -> Unit = {},
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun AddTicketDialogPreview() {
+ AddTicketDialog(AddTicketDialogData(showDialog = true) {
+
+ })
+}
+
+@Composable
+fun AddTicketDialog(
+ info: AddTicketDialogData
+) {
+ var successBtnName by remember { mutableStateOf("确定") }
+ var currentTab by remember { mutableStateOf(0) }
+ MyDialog(
+ "选择查询方式",
+ info.showDialog,
+ info.onDismiss,
+ successBtnName,
+ onClickOK = {
+ when (currentTab) {
+ 1 -> info.cardReaderForUserCard()
+ 2 -> info.cardReaderForIdCard()
+ 3 -> info.ocrForIdCard()
+ 4 -> info.cardReaderForBankCard()
+ 5 -> info.ocrForBankCard()
+ 6 -> info.faceRecognition()
+ }
+ info.onDismiss()
+ },
+ ) {
+ VerticalTabPages(
+ tabs = listOf(
+ "直接搜索", "刷农户卡",
+ "刷身份证", "拍身份证",
+ "刷银行卡", "拍银行卡",
+ "人脸识别",
+ /** "随意拍"**/
+ ),
+ ) {
+ currentTab = it
+ when (it) {
+ 0 -> {
+ successBtnName = ""
+ UserSearch { sysId ->
+ info.onDismiss()
+ info.navToWeight(sysId)
+ }
+ }
+
+ 1 -> {
+ successBtnName = "确定"
+ ReadCard("农户卡")
+ }
+
+ 2 -> {
+ successBtnName = "确定"
+ ReadCard("身份证")
+ }
+
+ 3 -> {
+ successBtnName = "确定"
+ OCRCard("身份证")
+ }
+
+ 4 -> {
+ successBtnName = "确定"
+ ReadCard("银行卡")
+ }
+
+ 5 -> {
+ successBtnName = "确定"
+ OCRCard("银行卡")
+ }
+
+ 6 -> {
+ successBtnName = "确定"
+ OCRCard("人脸识别")
+ }
+
+ 7 -> {
+ // 随意拍
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun UserSearch(
+ onSuccess: (String) -> Unit
+) {
+ Column {
+ var input by rememberSaveable { mutableStateOf("") }
+ MyTextField(modifier = M.padding(vertical = 10.dp), value = input, hint = "用户信息") {
+ input = it
+ }
+ val userViewModel: UserViewModel = viewModel()
+ val myPager = userViewModel.usersInfoMyPager
+ val isRefreshing by myPager.listIsRefreshing.collectAsState()
+ val userData = userViewModel.usersInfoPager.collectAsLazyPagingItems()
+ MyRefreshTable(
+ modifier = M.fillMaxWidth(),
+ isRefreshing = isRefreshing,
+ info = userData,
+ key = { it.sysid },
+ onFinishRefresh = {
+ myPager.setListIsRefreshClose()
+ },
+ items = listOf(
+ MyTableData(1, isIndex = true),
+ MyTableData("姓名", 1, { it.nhname }),
+ MyTableData("手机号", 2, { it.phone }),
+ MyTableData("身份证", 1, { it.idcard }),
+ MyTableData("银行卡号", 1, { it.bankcode }),
+ MyTableData("所属地址", 4, { "${it.xian}${it.xiang}${it.cun}${it.zu}" }),
+ ),
+ onClick = {
+ Toasty.showConfirmDialog("确定选择农户<${it.nhname}>吗?") {
+ onSuccess(it.sysid)
+ }
+ }
+ )
+ }
+}
+
+@Composable
+fun ReadCard(cardName: String) {
+ MyCard(elevation = 0.dp) {
+ Box(modifier = M.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Column(
+ modifier = M.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.icon_read_card),
+ contentDescription = "card",
+ modifier = M.size(200.dp)
+ )
+ Text(
+ modifier = M.padding(top = 20.dp),
+ text = "使用读卡器识别${cardName}",
+ fontWeight = FontWeight.Bold,
+ fontSize = MaterialTheme.typography.bodyLarge.fontSize
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun OCRCard(cardName: String) {
+ MyCard(elevation = 0.dp) {
+ Box(modifier = M.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Column(
+ modifier = M.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.camera),
+ contentDescription = "card",
+ modifier = M.size(200.dp)
+ )
+ Text(
+ modifier = M.padding(top = 20.dp),
+ text = "拍照识别${cardName}",
+ fontWeight = FontWeight.Bold,
+ fontSize = MaterialTheme.typography.bodyLarge.fontSize
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/AddWeightDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/AddWeightDialog.kt
new file mode 100644
index 0000000..48ae01d
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/AddWeightDialog.kt
@@ -0,0 +1,334 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.EasySelect
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.base.MyTextField
+import com.bbitcn.f8.pad.model.net.response.BoxInfoResponse
+import com.bbitcn.f8.pad.model.net.response.CarInfoResponse
+import com.bbitcn.f8.pad.model.net.response.WeightKindsResponse
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.utils.MyUtil
+
+data class AddWeightDialogData(
+ val showDialog: Boolean = false,
+ val onDismiss: () -> Unit = {},
+
+ val addType: Int = 0,//0只称重 1扣皮重-标准框 2扣皮重-标准框与茧车
+
+ val weight: Double = 0.0,
+ val kindInfo: WeightKindsResponse.Data = WeightKindsResponse.Data(),
+ val boxInfo: List = listOf(),
+ val carInfo: List = listOf(),
+
+ val saveDetail: (
+ price: Double, carInfo: CarInfoResponse.Data,
+ boxInfo: BoxInfoResponse.Data, boxCount: Int
+ ) -> Unit = { _, _, _, _ -> }
+
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun AddWeightDialogPreview() {
+ AddWeightDialog(
+ AddWeightDialogData(showDialog = true, addType = 0)
+ )
+}
+
+@Composable
+fun AddWeightDialog(info: AddWeightDialogData) {
+ var carInfo by remember { mutableStateOf(CarInfoResponse.Data()) }
+ var boxInfo by remember { mutableStateOf(BoxInfoResponse.Data()) }
+ var boxCount by rememberSaveable { mutableStateOf(0) }
+ LaunchedEffect(key1 = info.showDialog) {
+ if (info.showDialog) {
+ carInfo = CarInfoResponse.Data()
+ boxCount = 0
+ boxInfo = BoxInfoResponse.Data()
+ }
+ }
+ val addType = info.addType
+ MyDialog("添加磅次【" + when (addType) {
+ 0 -> "不扣皮重"
+ 1 -> "标准框扣皮"
+ 2 -> "茧车与标准框扣皮"
+ else -> ""
+ } + "】",
+ info.showDialog,
+ onDismissRequest = { info.onDismiss() },
+ clickOKStr = "添加",
+ onClickOK = {
+ if (info.addType != 0 && (boxInfo.name.isEmpty() || boxCount == 0)) {
+ Toasty.showTipsDialog("请选择标准框类型及数量")
+ } else if (info.addType == 2 && carInfo.carCode == -1) {
+ Toasty.showTipsDialog("请选择车号")
+ } else {
+ val price = if (info.kindInfo.priceType == 1) {
+ info.kindInfo.minPrice
+ } else {
+ 0.0
+ }
+ info.saveDetail(price, carInfo, boxInfo, boxCount)
+ }
+ }
+ ) {
+ LazyColumn {
+ item {
+ Column(
+ modifier = M.fillMaxSize().animateItem(),
+ horizontalAlignment = Alignment.Start,
+ verticalArrangement = Arrangement.spacedBy(10.dp)
+ ) {
+ InputInfo("重量") {
+ MyTextField(
+ modifier = M.width(300.dp),
+ value = info.weight.toString(),
+ readOnly = true
+ )
+ Text(modifier = M.padding(start = 10.dp), text = "公斤")
+ }
+ InputInfo("茧别") {
+ MyTextField(
+ modifier = M.width(300.dp),
+ value = info.kindInfo.name,
+ readOnly = true
+ )
+ }
+ when (addType) {
+ 0 -> AddWeightByOnlyWeight()
+ 1 -> AddWeightByStandardBox(
+ info,
+ boxInfo,
+ boxCount
+ ) { finalBoxInfo, finalBoxCount ->
+ boxInfo = finalBoxInfo
+ boxCount = finalBoxCount
+ }
+
+ 2 -> AddWeightByStandardBoxAndCocoonCar(
+ info,
+ boxInfo,
+ boxCount,
+ carInfo,
+ ) { finalCarInfo, finalBoxInfo, finalBoxCount ->
+ boxInfo = finalBoxInfo
+ boxCount = finalBoxCount
+ carInfo = finalCarInfo
+ }
+ }
+ if (info.kindInfo.priceType == 1) {
+ InputInfo("单价") {
+ MyTextField(
+ modifier = M.width(300.dp),
+ value = info.kindInfo.minPrice.toString(),
+ readOnly = true
+ )
+ Text(modifier = M.padding(horizontal = 10.dp), text = "元/公斤")
+ Image(
+ painter = painterResource(R.drawable.icon_tips),
+ modifier = M.size(20.dp),
+ contentDescription = "tips"
+ )
+ Text(modifier = M.padding(start = 10.dp), text = "固定价格")
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun AddWeightByOnlyWeight() {
+ InputInfo("件数") {
+ EasySelect(modifier = M.fillMaxWidth(), items = listOf("1", "2", "3", "4", "5")) {
+
+ }
+ }
+}
+
+
+@Composable
+fun AddWeightByStandardBox(
+ info: AddWeightDialogData,
+ boxInfo: BoxInfoResponse.Data,
+ boxCount: Int,
+ onConditionsChanged: (BoxInfoResponse.Data, Int) -> Unit
+) {
+ InputInfo("标准框类型") {
+ EasySelect(
+ modifier = M.fillMaxWidth(),
+ items = info.boxInfo.filter { it.isVisible }.map { it.name },
+ canInput = false
+ ) { sel ->
+ onConditionsChanged(
+ info.boxInfo[info.boxInfo.indexOfFirst { it.name == sel }],
+ boxCount
+ )
+ }
+ }
+ InputInfo("标准框重量") {
+ MyTextField(
+ modifier = M.width(300.dp),
+ value = boxInfo.weight.toString(),
+ readOnly = true,
+ hint = "请先选择标准框类型"
+ )
+ Text(modifier = M.padding(start = 10.dp), text = "公斤/个")
+ }
+ InputInfo("框数") {
+ EasySelect(
+ modifier = M.fillMaxWidth(),
+ onlyNumber = true,
+ items = listOf("1", "2", "3", "4", "5")
+ ) {
+ onConditionsChanged(boxInfo, it.toInt())
+ }
+ }
+ val tare = MyUtil.formatDouble(boxInfo.weight * boxCount)
+ InputInfo("皮重") {
+ MyTextField(modifier = M.width(300.dp), value = tare.toString(), readOnly = true)
+ Text(modifier = M.padding(horizontal = 10.dp), text = "公斤")
+ Image(
+ painter = painterResource(R.drawable.icon_tips),
+ modifier = M.size(20.dp),
+ contentDescription = "tips"
+ )
+ Text(modifier = M.padding(start = 10.dp), text = "皮重=框重×框数+车重")
+ }
+ val net = MyUtil.formatDouble(info.weight - tare)
+ InputInfo("净重") {
+ MyTextField(
+ modifier = M.width(300.dp),
+ value = net.toString(),
+ readOnly = true
+ )
+ Text(modifier = M.padding(horizontal = 10.dp), text = "公斤")
+ Image(
+ painter = painterResource(R.drawable.icon_tips),
+ modifier = M.size(20.dp),
+ contentDescription = "tips"
+ )
+ Text(modifier = M.padding(start = 10.dp), text = "净重=重量-皮重")
+ }
+}
+
+@Composable
+fun AddWeightByStandardBoxAndCocoonCar(
+ info: AddWeightDialogData,
+ boxInfo: BoxInfoResponse.Data,
+ boxCount: Int,
+ carInfo: CarInfoResponse.Data,
+ onConditionsChanged: (CarInfoResponse.Data, BoxInfoResponse.Data, Int) -> Unit
+) {
+ InputInfo("标准框类型") {
+ EasySelect(
+ modifier = M.fillMaxWidth(),
+ items = info.boxInfo.filter { it.isVisible }.map { it.name },
+ canInput = false
+ ) { sel ->
+ val myBox = info.boxInfo[info.boxInfo.indexOfFirst { it.name == sel }]
+ onConditionsChanged(carInfo, myBox, boxCount)
+ }
+ }
+ InputInfo("标准框重量") {
+ MyTextField(
+ modifier = M.width(300.dp),
+ value = boxInfo.weight.toString(),
+ readOnly = true,
+ hint = "请先选择标准框类型"
+ )
+ Text(modifier = M.padding(start = 10.dp), text = "公斤/个")
+ }
+ InputInfo("框数") {
+ EasySelect(
+ modifier = M.fillMaxWidth(),
+ onlyNumber = true,
+ items = listOf("1", "2", "3", "4", "5")
+ ) {
+ onConditionsChanged(carInfo, boxInfo, it.toIntOrNull() ?: 0)
+ }
+ }
+
+ InputInfo("车号") {
+ EasySelect(
+ modifier = M.fillMaxWidth(),
+ items = info.carInfo.map { it.carCode.toString() },
+ canInput = false
+ ) { selected ->
+ onConditionsChanged(
+ info.carInfo[info.carInfo.indexOfFirst { it.carCode.toString() == selected }],
+ boxInfo,
+ boxCount
+ )
+ }
+ }
+ InputInfo("车重") {
+ MyTextField(
+ modifier = M.width(300.dp),
+ value = carInfo.carWeight.toString(),
+ readOnly = true,
+ hint = "请输入"
+ )
+ Text(modifier = M.padding(start = 10.dp), text = "公斤/辆")
+ }
+
+ val tare = MyUtil.formatDouble(boxInfo.weight * boxCount + carInfo.carWeight)
+ InputInfo("皮重") {
+ MyTextField(modifier = M.width(300.dp), value = tare.toString(), readOnly = true)
+ Text(modifier = M.padding(horizontal = 10.dp), text = "公斤")
+ Image(
+ painter = painterResource(R.drawable.icon_tips),
+ modifier = M.size(20.dp),
+ contentDescription = "tips"
+ )
+ Text(modifier = M.padding(start = 10.dp), text = "皮重=框重×框数")
+ }
+ val net = MyUtil.formatDouble(info.weight - tare)
+ InputInfo("净重") {
+ MyTextField(
+ modifier = M.width(300.dp),
+ value = net.toString(),
+ readOnly = true
+ )
+ Text(modifier = M.padding(horizontal = 10.dp), text = "公斤")
+ Image(
+ painter = painterResource(R.drawable.icon_tips),
+ modifier = M.size(20.dp),
+ contentDescription = "tips"
+ )
+ Text(modifier = M.padding(start = 10.dp), text = "净重=重量-皮重")
+ }
+}
+
+@Composable
+fun InputInfo(title: String, content: @Composable () -> Unit) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text(modifier = M.padding(end = 10.dp), text = title)
+ content()
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/AuthDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/AuthDialog.kt
new file mode 100644
index 0000000..985f154
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/AuthDialog.kt
@@ -0,0 +1,185 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.bbitcn.f8.pad.M
+
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.base.MyTextField
+import com.bbitcn.f8.pad.base.noVisualFeedbackClickable
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.global.RxTag
+import kotlinx.coroutines.launch
+
+data class AuthDialogData(
+ val showDialog: Boolean = false,
+ val authType: Int = 0,//授权类型 0:未授权/未申请 1:待审核 2:已授权 3:已拒绝,
+ val companyId: String = "",
+ val name: String = "",
+ val phone: String = "",
+ val onReAuth: () -> Unit = {},
+ val onAuth: (companyCode: String, name: String, phone: String, pwd: String) -> Unit = { _, _, _, _ -> },
+ val onDismiss: () -> Unit = {}
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun AuthPreview() {
+ AuthDialog(
+ AuthDialogData(showDialog = true, authType = 1)
+ )
+}
+
+@Composable
+fun AuthDialog(info: AuthDialogData) {
+ val authType = info.authType
+ val canInput = authType == 0
+ var companyCode by rememberSaveable { mutableStateOf(info.companyId) }
+ var name by rememberSaveable { mutableStateOf(info.name) }
+ var phone by rememberSaveable { mutableStateOf(info.phone) }
+ var pwd by rememberSaveable { mutableStateOf("") }
+ var showPwd by rememberSaveable { mutableStateOf(false) }
+ MyDialog("授权申请", info.showDialog, onDismissRequest = {
+ info.onDismiss()
+ }, if (authType == 0) "确认" else "重新申请", onClickOK = {
+ if (authType == 0) {
+ info.onAuth(companyCode, name, phone, pwd)
+ } else {
+ info.onReAuth()
+ info.onDismiss()
+ }
+ }) {
+ LaunchedEffect(info) {
+ companyCode = info.companyId
+ name = info.name
+ phone = info.phone
+ }
+ Column(
+ modifier = M
+ .fillMaxSize(0.8f),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ Row(
+ modifier = M.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceEvenly,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column(
+ modifier = M.padding(end = 10.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ painter = painterResource(
+ id = when (authType) {
+ 1 -> R.drawable.auth_1
+ 2 -> R.drawable.auth_2
+ 3 -> R.drawable.auth_3
+ else -> R.drawable.auth_0
+ }
+ ),
+ contentDescription = "auth",
+ modifier = M
+ .size(150.dp)
+ .noVisualFeedbackClickable {
+ showPwd = !showPwd
+ }
+ )
+ Text(
+ text = when (authType) {
+ 1 -> "待审核"
+ 2 -> "已授权"
+ 3 -> "授权拒绝"
+ else -> "请完善信息申请授权"
+ },
+ fontWeight = FontWeight.Bold,
+ fontSize = MaterialTheme.typography.headlineMedium.fontSize
+ )
+ }
+ Column(
+ modifier = M
+ .width(400.dp)
+ .padding(10.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ MyTextField(
+ modifier = M.padding(top = 20.dp),
+ hint = "企业编号",
+ value = companyCode,
+ enabled = canInput
+ ) {
+ // 自动转为大写
+ companyCode = it.uppercase()
+ }
+ MyTextField(
+ modifier = M.padding(top = 20.dp),
+ hint = "申请人姓名",
+ value = name,
+ enabled = canInput
+ ) {
+ name = it
+ }
+ MyTextField(
+ modifier = M.padding(top = 20.dp),
+ hint = "联系电话",
+ value = phone,
+ isNumberInputType = true,
+ enabled = canInput
+ ) {
+ phone = it
+ }
+ if (showPwd) {
+ MyTextField(
+ modifier = M.padding(top = 20.dp),
+ hint = "PWD(可选)",
+ value = pwd,
+ visualTransformation = PasswordVisualTransformation(),
+ enabled = canInput
+ ) {
+ pwd = it
+ }
+ }
+ Text(
+ modifier = M.padding(top = 20.dp),
+ text = if (authType == 0)
+ "提交申请后请耐心等待系统审核,并保持手机畅通"
+ else if (authType == 1)
+ "请耐心等待系统审核,并保持手机畅通"
+ else if (authType == 2)
+ "您的授权申请已通过,感谢您的使用"
+ else
+ "企业信息未通过审核,请负责人联系本公司购买授权后使用",
+ fontSize = MaterialTheme.typography.bodySmall.fontSize,
+ color = if (authType == 3) MyColors.Red else MyColors.Gray
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/ChatDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/ChatDialog.kt
new file mode 100644
index 0000000..e794731
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/ChatDialog.kt
@@ -0,0 +1,383 @@
+@file:OptIn(ExperimentalMaterial3Api::class)
+
+package com.cyberecho.ui.screen
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.paddingFrom
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.ClickableText
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBackIosNew
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.Divider
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarScrollBehavior
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.layout.LastBaseline
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.BaseDialogFrame
+import com.bbitcn.f8.pad.base.BigButton
+import com.bbitcn.f8.pad.base.MyAnimatedVisibility
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.noVisualFeedbackClickable
+import com.bbitcn.f8.pad.model.ui.Message
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.utils.MyUtil
+import com.bbitcn.f8.pad.utils.TimeUtils
+import com.cyberecho.ui.view.JumpToBottom
+import com.cyberecho.ui.view.UserInput
+import kotlinx.coroutines.launch
+import com.cyberecho.utils.SymbolAnnotationType
+import com.cyberecho.utils.messageFormatter
+
+data class ChatDialogData(
+ val showDialog: Boolean = false,
+ val question: String = "",
+ val onDismiss: () -> Unit = { }
+)
+
+@Preview
+@Composable
+fun ConversationPreview() {
+ ChatDialog()
+}
+
+@Composable
+fun ChatDialog(
+ info: ChatDialogData = ChatDialogData()
+) {
+ MyAnimatedVisibility(info.showDialog) {
+ Box(
+ modifier = M
+ .fillMaxSize()
+ .noVisualFeedbackClickable{}
+ .background(Color(0x99000000)),
+ contentAlignment = Alignment.Center
+ ) {
+ val chatViewModel: ChatViewModel = viewModel()
+ val messages by chatViewModel.messages.collectAsState()
+ val inputEnabled by chatViewModel.inputEnabled.collectAsState()
+ val scrollState = rememberLazyListState()
+ val scope = rememberCoroutineScope()
+ LaunchedEffect(info) {
+ if (info.showDialog) {
+ chatViewModel.updateQuestion(info.question)
+ }
+ }
+ MyCard {
+ Column(
+ Modifier
+ .size(800.dp, 550.dp)
+ .padding(10.dp)
+ ) {
+ ChatHeader {
+ info.onDismiss()
+ }
+ Messages(
+ chatRecords = messages,
+ modifier = M.weight(1f),
+ scrollState = scrollState
+ )
+ UserInput(
+ enabled = inputEnabled,
+ onMessageSent = { content ->
+ chatViewModel.sendMessageToWeb(content)
+ },
+ resetScroll = {
+ scope.launch {
+ scrollState.scrollToItem(0)
+ }
+ },
+ modifier = M
+ .navigationBarsPadding()
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun ChatHeader(onDismiss: () -> Unit) {
+ Box(
+ modifier = M.fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ Column {
+ Text(
+ modifier = M.fillMaxWidth(),
+ textAlign = TextAlign.Center,
+ text = "F8助手",
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ Text(
+ modifier = M.fillMaxWidth(),
+ textAlign = TextAlign.Center,
+ text = "服务生成的内容均由人工智能生成,其生成的内容准确性和完整性无法保证,不代表我们的态度或观点。",
+ style = MaterialTheme.typography.titleSmall,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ }
+ Box(
+ modifier = M
+ .size(30.dp)
+ .align(Alignment.CenterEnd)
+ .clickable {
+ onDismiss()
+ },
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ imageVector = Icons.Default.Close,
+ contentDescription = null,
+ tint = Color.Black
+ )
+ }
+ }
+}
+
+@Composable
+fun Messages(
+ chatRecords: List,
+ scrollState: LazyListState,
+ modifier: Modifier = M
+) {
+ val scope = rememberCoroutineScope()
+ Box(modifier = modifier) {
+ LazyColumn(
+ reverseLayout = true,
+ state = scrollState,
+ modifier = M
+ .fillMaxSize()
+ ) {
+ items(chatRecords) {
+ Message(
+ modifier = M.animateItem(),
+ msg = it,
+ isEndMessageByAuthor = true,
+ isNewMessageByAuthor = true
+ )
+ if (chatRecords.indexOf(it) == chatRecords.size - 1) {
+ DateHeader(System.currentTimeMillis())
+ }
+ }
+ }
+ val jumpThreshold = with(LocalDensity.current) {
+ JumpToBottomThreshold.toPx()
+ }
+ val jumpToBottomButtonEnabled by remember {
+ derivedStateOf {
+ scrollState.firstVisibleItemIndex != 0 || scrollState.firstVisibleItemScrollOffset > jumpThreshold
+ }
+ }
+ JumpToBottom(
+ // Only show if the scroller is not at the bottom
+ enabled = jumpToBottomButtonEnabled,
+ onClicked = {
+ scope.launch {
+ scrollState.animateScrollToItem(0)
+ }
+ },
+ modifier = M.align(Alignment.BottomCenter)
+ )
+ }
+}
+
+@Composable
+fun DateHeader(timestamp: Long) {
+ Row(
+ modifier = M
+ .padding(vertical = 8.dp, horizontal = 16.dp)
+ .height(16.dp)
+ ) {
+ DayHeaderLine()
+ Text(
+ text = TimeUtils.formatTimestampToMD(timestamp),
+ modifier = M.padding(horizontal = 16.dp),
+ style = MaterialTheme.typography.labelSmall,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ DayHeaderLine()
+ }
+}
+
+
+@Composable
+fun Message(
+ modifier: Modifier = M,
+ msg: Message,
+ isEndMessageByAuthor: Boolean,
+ isNewMessageByAuthor: Boolean
+) {
+ val spaceBetweenAuthors = if (isNewMessageByAuthor) modifier.padding(top = 8.dp) else Modifier
+ val isUserMe = msg.isFromUser
+ Row(modifier = spaceBetweenAuthors) {
+ if (isNewMessageByAuthor && !isUserMe) {
+ // AI头像
+ MyAvatar(modifier = M.align(Alignment.Top), false)
+ } else {
+ Spacer(modifier = M.width(74.dp))
+ }
+ Column(
+ modifier = M
+ .weight(1f),
+ horizontalAlignment = if (isUserMe) Alignment.End else Alignment.Start
+ ) {
+ AuthorNameTimestamp(msg, isUserMe)
+ ChatItemBubble(msg, isUserMe)
+ Spacer(modifier = M.height(if (isEndMessageByAuthor) 8.dp else 4.dp))
+ }
+ if (isNewMessageByAuthor && isUserMe) {
+ // 用户头像
+ MyAvatar(modifier = M.align(Alignment.Top), true)
+ } else {
+ Spacer(modifier = M.width(74.dp))
+ }
+ }
+}
+
+@Composable
+fun MyAvatar(
+ modifier: Modifier,
+ isFromUser: Boolean,
+) {
+ Image(
+ modifier = modifier
+ .padding(horizontal = 16.dp)
+ .size(42.dp)
+ .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
+ .border(3.dp, MaterialTheme.colorScheme.surface, CircleShape)
+ .clip(CircleShape),
+ contentScale = ContentScale.Crop,
+ contentDescription = null,
+ painter = painterResource(id = if (isFromUser) R.drawable.user_icon else R.drawable.icon_ai)
+ )
+}
+
+@Composable
+private fun AuthorNameTimestamp(msg: Message, isUserMe: Boolean) {
+ Row(modifier = M.semantics(mergeDescendants = true) {}) {
+ Spacer(modifier = M.width(8.dp))
+ Text(
+ text = TimeUtils.formatAuthorNameTimestamp(msg.timestamp),
+ style = MaterialTheme.typography.bodySmall,
+ modifier = M
+ .alignBy(LastBaseline)
+ .let {
+ if (!isUserMe) it.paddingFrom(LastBaseline, after = 8.dp) else it
+ },
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ }
+}
+
+@Composable
+private fun RowScope.DayHeaderLine() {
+ Divider(
+ modifier = M
+ .weight(1f)
+ .align(Alignment.CenterVertically),
+ color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
+ )
+}
+
+private val ChatBubbleShapeInAi = RoundedCornerShape(2.dp, 20.dp, 20.dp, 20.dp)
+private val ChatBubbleShapeInUser = RoundedCornerShape(20.dp, 2.dp, 20.dp, 20.dp)
+
+@Composable
+fun ChatItemBubble(
+ chatRecord: Message,
+ isUserMe: Boolean,
+) {
+ val backgroundBubbleColor = if (isUserMe) {
+ MyColors.BlueGreen
+ } else {
+ MaterialTheme.colorScheme.surfaceVariant
+ }
+ val textColor = if (isUserMe) {
+ Color.White
+ } else {
+ MaterialTheme.colorScheme.onSurface
+ }
+ Column {
+ Surface(
+ color = backgroundBubbleColor,
+ shape = if (isUserMe) ChatBubbleShapeInUser else ChatBubbleShapeInAi
+ ) {
+ val uriHandler = LocalUriHandler.current
+ val styledMessage = messageFormatter(
+ text = chatRecord.content,
+ primary = isUserMe
+ )
+ ClickableText(
+ text = styledMessage,
+ style = MaterialTheme.typography.bodyLarge.copy(color = textColor),
+ modifier = M.padding(16.dp),
+ onClick = {
+ styledMessage
+ .getStringAnnotations(start = it, end = it)
+ .firstOrNull()
+ ?.let { annotation ->
+ when (annotation.tag) {
+ SymbolAnnotationType.LINK.name -> uriHandler.openUri(annotation.item)
+ else -> Unit
+ }
+ }
+ }
+ )
+ }
+ }
+}
+
+val JumpToBottomThreshold = 56.dp
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/ChatViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/ChatViewModel.kt
new file mode 100644
index 0000000..870be26
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/ChatViewModel.kt
@@ -0,0 +1,156 @@
+package com.cyberecho.ui.screen
+
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.model.net.request.ChatMessageRequest
+import com.bbitcn.f8.pad.model.net.response.ChatMessageStreamResponse
+import com.bbitcn.f8.pad.model.ui.Message
+import com.google.gson.Gson
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import java.io.BufferedReader
+import java.io.InputStreamReader
+
+
+class ChatViewModel : BaseViewModel() {
+
+ private val _messages = MutableStateFlow>(mutableListOf())
+ val messages = _messages.asStateFlow()
+ private var _sessionId = ""
+
+ private val _inputEnabled = MutableStateFlow(false)
+ val inputEnabled = _inputEnabled.asStateFlow()
+
+ fun updateQuestion(question: String) {
+ doInIoThreadNoDialog {
+ //清空消息
+ _messages.update {
+ it.apply {
+ clear()
+ }
+ }
+ addMessage(
+ Message(
+ content = "您好,我是F8助手,有什么可以帮助您的吗?",
+ isFromUser = false,
+ timestamp = System.currentTimeMillis()
+ )
+ )
+ _sessionId = ""
+ if (question != "") {
+ sendMessageToWeb(question)
+ } else {
+ _inputEnabled.value = true
+ }
+ }
+ }
+
+ fun sendMessageToWeb(content: String) {
+ doInIoThreadNoDialog {
+ _inputEnabled.value = false
+ addMessage(
+ Message(
+ content = content,
+ isFromUser = true,
+ timestamp = System.currentTimeMillis()
+ )
+ )
+// val response = aiApiService.chat2(
+// "19da9024b78b11ef9cea0242ac120006",
+// ChatMessageRequest(
+// question = content,
+// sessionId = _sessionId,
+// stream = true
+// )
+// )
+// if (response.isSuccessful) {
+// val inputStream = response.body()?.byteStream()
+// val reader = BufferedReader(InputStreamReader(inputStream))
+//
+// var line: String?
+// while (reader.readLine().also { line = it } != null) {
+// try {
+// if (line!!.startsWith("data:")) {
+// line = line!!.substring(5)
+// }
+// if (line == "{\"code\": 0, \"data\": true}") {
+// // 会话结束
+// break
+// }
+// if (line != "") {
+// val info = Gson().fromJson(line, ChatMessageStreamResponse::class.java)
+// _sessionId = info.data.sessionId
+// updateLastMessage(
+// Message(
+// content = info.data.answer,
+// isFromUser = false,
+// timestamp = System.currentTimeMillis()
+// )
+// )
+// }
+// } catch (e: Exception) {
+// e.printStackTrace()
+// }
+// }
+// } else {
+// // 请求失败处理
+// println("请求失败:${response.code()}")
+// }
+ val result = aiApiService.chat(
+ "19da9024b78b11ef9cea0242ac120006",
+ ChatMessageRequest(
+ question = content,
+ sessionId = _sessionId,
+ )
+ )
+ if (result.code == 0) {
+ _sessionId = result.data.sessionId
+ addMessage(
+ Message(
+ content = result.data.answer,
+ isFromUser = false,
+ timestamp = System.currentTimeMillis()
+ )
+ )
+ _inputEnabled.value = true
+ }else if(result.code == 100){
+ addMessage(
+ Message(
+ content = "请输入您想了解的信息",
+ isFromUser = false,
+ timestamp = System.currentTimeMillis()
+ )
+ )
+ }
+ _inputEnabled.value = true
+ }
+ }
+
+ /**
+ * 流式传输用
+ */
+ private fun updateLastMessage(message: Message) {
+ // 如果最新消息是机器人的,更新它 否则添加到消息列表
+ val lastMessage = _messages.value.firstOrNull()
+ if (lastMessage != null && !lastMessage.isFromUser) {
+ _messages.update {
+ it.apply {
+ set(0, message)
+ }
+ }
+ } else {
+ addMessage(message)
+ }
+ }
+
+ fun addMessage(msg: Message) {
+ val newMsg = msg.copy(content = msg.content.replace(Regex("##\\d+\\$\\$"), ""))
+ // 将新消息添加到消息列表第一位
+ _messages.update {
+ it.apply {
+ add(0, newMsg)
+ }
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/CocoonTypeTranslateDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/CocoonTypeTranslateDialog.kt
new file mode 100644
index 0000000..65d6590
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/CocoonTypeTranslateDialog.kt
@@ -0,0 +1,63 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.BaseDialogFrame
+import com.bbitcn.f8.pad.base.BigButton
+import com.bbitcn.f8.pad.base.MyAnimatedVisibility
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.ui.theme.MyColors
+
+data class CocoonTypeTranslateDialogData(
+ var showDialog: Boolean = false,
+ var onDismiss: () -> Unit = {},
+ var onClickOK: () -> Unit = {}
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun CocoonTypeTranslateDialogPreview() {
+ CocoonTypeTranslateDialogDialog(
+ CocoonTypeTranslateDialogData(showDialog = true)
+ )
+}
+
+@Composable
+fun CocoonTypeTranslateDialogDialog(info: CocoonTypeTranslateDialogData) {
+ MyDialog("",
+ info.showDialog,
+ onDismissRequest = { info.onDismiss() },
+ clickOKStr = "确定",
+ onClickOK = {
+
+ }
+ ) {
+
+ }
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/ConfirmDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/ConfirmDialog.kt
new file mode 100644
index 0000000..16a28f7
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/ConfirmDialog.kt
@@ -0,0 +1,116 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.material3.Card
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MyAnimatedVisibility
+import com.bbitcn.f8.pad.base.noVisualFeedbackClickable
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import kotlin.math.max
+
+data class ConfirmDialogData(
+ var title: String = "标题",
+ var content: String = "",
+ var showDialog: Boolean = false,
+ var canManualClose: Boolean = true,
+ var onSuccess: () -> Unit = {},
+ var onDismiss: () -> Unit = {}
+)
+
+/**
+ *
+ * @Description TODO
+ * @Author DuanKaiji
+ * @CreateTime 2024年04月30日 08:54:51
+ */
+@Preview(showBackground = true)
+@Composable
+fun ConfirmDialogPreview() {
+ ConfirmDialog(ConfirmDialogData(showDialog = true))
+}
+
+@Composable
+fun ConfirmDialog(info: ConfirmDialogData) {
+ MyAnimatedVisibility(info.showDialog) {
+ Box(
+ modifier = M
+ .fillMaxSize()
+ .background(Color(0x99000000))
+ .noVisualFeedbackClickable {
+ if (info.canManualClose) {
+ info.onDismiss()
+ }
+ },
+ contentAlignment = Alignment.Center
+ ) {
+ Column(modifier = M.padding(20.dp)) {
+ Card {
+ Column(
+ modifier = M
+ .heightIn(max = 600.dp)
+ .wrapContentWidth()
+ .background(if (isSystemInDarkTheme()) Color.DarkGray else Color.White)
+ .padding(20.dp)
+ ) {
+ Text(
+ text = info.title,
+ // 设置文字为标题样式
+ style = MaterialTheme.typography.headlineMedium
+ )
+ Text(
+ text = info.content,
+ modifier = M
+ .padding(vertical = 20.dp)
+ .widthIn(300.dp)
+ )
+ Row(
+ modifier = M
+ .wrapContentWidth()
+ .align(Alignment.End)
+ ) {
+ TextButton(
+ onClick = {
+ info.onSuccess()
+ info.onDismiss()
+ },
+ modifier = M
+ .padding(end = 10.dp)
+ .wrapContentWidth()
+ ) {
+ Text(color = MyColors.Green, text = "确定")
+ }
+ if (info.canManualClose) {
+ TextButton(
+ onClick = info.onDismiss,
+ modifier = M
+ .wrapContentWidth()
+ ) {
+ Text(color = MyColors.Green, text = "取消")
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/DateRangeSelectDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/DateRangeSelectDialog.kt
new file mode 100644
index 0000000..89c2dc6
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/DateRangeSelectDialog.kt
@@ -0,0 +1,274 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.lazy.grid.items
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.base.RedPointBadge
+import com.bbitcn.f8.pad.base.TableHeadLine
+import com.bbitcn.f8.pad.ui.screen.dialog.DateRangeSelectDialogViewModel.MyDay
+import com.bbitcn.f8.pad.ui.screen.view.common.CalendarDay
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.utils.TimeUtils
+import java.text.SimpleDateFormat
+import java.time.LocalDate
+import java.util.Date
+import java.util.Locale
+
+data class DateRangeSelectDialogData(
+ val showDialog: Boolean = false,
+ val onDismiss: () -> Unit = {},
+ val default: Pair = Pair(null, null),
+ // 点击日期范围
+ val onClickRangeDay: (dateStrStart: Date, dateStrEnd: Date) -> Unit = { _, _ -> {} },
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun DateRangeSelectDialogPreview() {
+ DateRangeSelectDialog(
+ info = DateRangeSelectDialogData(
+ showDialog = true,
+ onDismiss = {},
+ onClickRangeDay = { dateStrStart, dateStrEnd ->
+ }
+ )
+ )
+}
+
+@Composable
+fun DateRangeSelectDialog(
+ info: DateRangeSelectDialogData,
+ viewmodel: DateRangeSelectDialogViewModel = viewModel(),
+) {
+ MyDialog(
+ "日期范围选择",
+ info.showDialog,
+ onDismissRequest = { info.onDismiss() },
+ clickOKStr = "确定",
+ onClickOK = {
+ val date = viewmodel.getFormatDate()
+ info.onClickRangeDay(date.first, date.second)
+ info.onDismiss()
+ }
+ ) {
+ val selectedStartDay by viewmodel.selectedStartDay.collectAsState()
+ val selectedEndDay by viewmodel.selectedEndDay.collectAsState()
+
+ LaunchedEffect(info) {
+ if (info.showDialog && info.default.first != null && info.default.second != null) {
+ viewmodel.initRange(info.default)
+ }
+ }
+ Row(modifier = M.fillMaxSize()) {
+ MyDateRange(modifier = M.weight(5f), viewmodel)
+ Column(modifier = M.weight(4f), horizontalAlignment = Alignment.CenterHorizontally) {
+ Row {
+ Text(
+ "开始时间:",
+ fontSize = MaterialTheme.typography.titleLarge.fontSize,
+ )
+ Text(
+ text = selectedStartDay.toString(),
+ fontSize = MaterialTheme.typography.titleLarge.fontSize,
+ color = MyColors.BlueGreen,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ Row {
+ Text(
+ "结尾时间:",
+ fontSize = MaterialTheme.typography.titleLarge.fontSize,
+ )
+ Text(
+ text = selectedEndDay?.toString() ?: "未选择",
+ fontSize = MaterialTheme.typography.titleLarge.fontSize,
+ color = MyColors.BlueGreen,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun MyDateRange(
+ modifier: Modifier,
+ viewmodel: DateRangeSelectDialogViewModel = viewModel()
+) {
+ val currentDate by viewmodel.currentDate.collectAsState()
+ Column(modifier = modifier) {
+ Row(
+ modifier = M
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Image(
+ painter = painterResource(R.drawable.previous_year),
+ contentDescription = "Previous Year",
+ modifier = M
+ .size(30.dp)
+ .padding(10.dp)
+ .clickable {
+ viewmodel.updateCurrentDate(currentDate.minusYears(1))
+ }
+ )
+ Image(
+ painter = painterResource(R.drawable.previous_month),
+ contentDescription = "Previous Month",
+ modifier = M
+ .size(30.dp)
+ .padding(10.dp)
+ .clickable {
+ viewmodel.updateCurrentDate(currentDate.minusMonths(1))
+ }
+ )
+ Text(
+ text = "%02d-%d".format(currentDate.monthValue, currentDate.year),
+ fontSize = MaterialTheme.typography.bodyLarge.fontSize,
+ color = MyColors.BlueGreen,
+ fontWeight = FontWeight.Bold
+ )
+ Image(
+ painter = painterResource(R.drawable.next_month),
+ contentDescription = "Next Month",
+ modifier = M
+ .size(30.dp)
+ .padding(10.dp)
+ .clickable {
+ viewmodel.updateCurrentDate(currentDate.plusMonths(1))
+ }
+ )
+ Image(
+ painter = painterResource(R.drawable.next_year),
+ contentDescription = "Next Year",
+ modifier = M
+ .size(30.dp)
+ .padding(10.dp)
+ .clickable {
+ viewmodel.updateCurrentDate(currentDate.plusYears(1))
+ }
+ )
+ }
+ TableHeadLine(
+ modifier = M.fillMaxWidth(),
+ list = listOf(
+ Pair("日", 1), Pair("一", 1), Pair("二", 1),
+ Pair("三", 1), Pair("四", 1), Pair("五", 1), Pair("六", 1)
+ )
+ )
+ val dateList by viewmodel.dateList.collectAsState()
+ LazyVerticalGrid(
+ columns = GridCells.Fixed(7),
+ ) {
+ items(dateList) {
+ CalendarDayS(it) {
+ // 处理日期点击事件
+ viewmodel.selectDay(it) { _, _ ->
+
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun CalendarDayS(
+ info: MyDay,
+ onClick: () -> Unit = {}
+) {
+ Box(
+ modifier = M.background(
+ color = if (info.type != -1) MyColors.Gray else MyColors.Transparent,
+ shape = when (info.type) {
+ -1, 3 -> CircleShape // 圆形
+ 0 -> RoundedCornerShape(topStart = 50.dp, bottomStart = 50.dp) // 左半圆右矩形
+ 1 -> RoundedCornerShape(topEnd = 50.dp, bottomEnd = 50.dp) // 右半圆左矩形
+ 2 -> RectangleShape // 全矩形
+ else -> RectangleShape
+ }
+ )
+ ) {
+ if (info.isImportant) {
+ RedPointBadge {
+ CalTextS(
+ info.date.dayOfMonth.toString(),
+ isSelect = info.type != -1,
+ isCurMonth = info.isCurMonth,
+ onClick = onClick
+ )
+ }
+ } else {
+ CalTextS(
+ info.date.dayOfMonth.toString(),
+ isSelect = info.type != -1,
+ isCurMonth = info.isCurMonth,
+ onClick = onClick
+ )
+ }
+ }
+}
+
+@Composable
+fun CalTextS(
+ day: String,
+ isSelect: Boolean = false,
+ isCurMonth: Boolean,
+ onClick: () -> Unit
+) {
+ Box(
+ modifier = Modifier
+ .aspectRatio(1f)
+ .clickable {
+ onClick()
+ },
+ contentAlignment = Alignment.Center
+ ) {
+ val animatedColor by animateColorAsState(
+ if (isSelect) MyColors.White else if (isCurMonth) MyColors.Black else MyColors.Gray,
+ label = "color"
+ )
+ Text(
+ text = day,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ color = animatedColor
+ )
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/DateRangeSelectDialogViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/DateRangeSelectDialogViewModel.kt
new file mode 100644
index 0000000..c31f2fa
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/DateRangeSelectDialogViewModel.kt
@@ -0,0 +1,129 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.utils.log.MyLog
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import java.time.LocalDate
+import java.time.ZoneId
+import java.util.Date
+
+class DateRangeSelectDialogViewModel : BaseViewModel() {
+
+ // 当前显示的日期
+ private val _currentDate = MutableStateFlow(LocalDate.now())
+ val currentDate = _currentDate.asStateFlow()
+
+ // 选择的开始日期
+ private val _selectedStartDay = MutableStateFlow(LocalDate.now())
+ val selectedStartDay = _selectedStartDay.asStateFlow()
+
+ // 选择的结束日期
+ private val _selectedEndDay = MutableStateFlow(null)
+ val selectedEndDay = _selectedEndDay.asStateFlow()
+
+ // 日期 当前天 to 当天天的类型 -1:不在范围内 0:在第一天 1:在最后一天 2:在中间 3:只有一天
+ private val _dateList = MutableStateFlow>(emptyList())
+ val dateList = _dateList.asStateFlow()
+
+ init {
+ // 初始化当前日期
+ updateCurrentDate(LocalDate.now())
+ }
+
+ fun updateCurrentDate(date: LocalDate) {
+ _currentDate.value = date
+ // 本月的第一天和最后一天
+ val firstDayOfMonth = date.withDayOfMonth(1)
+ val lastDayOfMonth = date.withDayOfMonth(date.lengthOfMonth())
+ // 计算本月第一天和最后一天是星期几
+ val firstDayOfWeek = firstDayOfMonth.dayOfWeek.value % 7 // 周日=0,周一=1,...
+ val endDyaOfWeek = lastDayOfMonth.dayOfWeek.value % 7 // 周日=0,周一=1,...
+ // 上个月末尾需要补多少天
+ val daysFromPrevMonth = if (firstDayOfWeek == 0) 0 else firstDayOfWeek
+ // 填充上个月的天数
+ val startFrom = firstDayOfMonth.minusDays(daysFromPrevMonth.toLong())
+ // 下个月开始需要补多少天
+ val daysFromNextMonth = if (endDyaOfWeek == 6) 0 else 6 - endDyaOfWeek
+ // 填充下个月的天数
+ val endTo = lastDayOfMonth.plusDays(daysFromNextMonth.toLong())
+ // 计算总共需要多少天来填充整个日历
+ val totalDays = (endTo.toEpochDay() - startFrom.toEpochDay()).toInt() + 1
+ val dateList = mutableListOf()
+ for (i in 0 until totalDays) {
+ val current = startFrom.plusDays(i.toLong())
+ val isCurMonth = current.month == date.month
+ dateList.add(MyDay(current, isCurMonth, getTypeOfDay(current)))
+ }
+ _dateList.value = dateList
+ }
+
+ fun selectDay(day: MyDay, onDragSelect: (LocalDate?, LocalDate?) -> Unit) {
+ if (_selectedEndDay.value != null // 已经选了结束日期
+ || day.date.isBefore(_selectedStartDay.value) // 选的日期小于开始日期
+ ) {
+ // 选择当前日期为开始日期
+ _selectedStartDay.value = day.date
+ _selectedEndDay.value = null
+ } else {
+ // 设置结束日期
+ _selectedEndDay.value = day.date
+ }
+ onDragSelect(_selectedStartDay.value, _selectedEndDay.value)
+
+ _dateList.update {
+ it.map { day -> day.copy(getTypeOfDay(day.date)) }
+ }
+ }
+
+ fun getTypeOfDay(date: LocalDate): Int {
+ val start = _selectedStartDay.value
+ val end = _selectedEndDay.value
+ return when {
+ date.isAfter(start) && end != null && date.isBefore(end) -> 2 // 在中间
+ date == start && end == null || date == start && date == end -> 3 // 只有一天
+ date == start && end != null -> 0 // 在第一天
+ date == end -> 1 // 在最后一天
+ else -> -1 // 不在范围内
+ }
+ }
+
+ fun getFormatDate(): Pair {
+ val start = _selectedStartDay.value
+ val end = _selectedEndDay.value ?: start
+
+ val startDateTime = start.atStartOfDay() // 0点0分
+ val endDateTime = end.atTime(23, 59) // 23点59分
+
+ val zone = ZoneId.systemDefault()
+ val startDate = Date.from(startDateTime.atZone(zone).toInstant())
+ val endDate = Date.from(endDateTime.atZone(zone).toInstant())
+ return startDate to endDate
+ }
+
+ fun initRange(pair: Pair) {
+ doInIoThreadNoDialog {
+ _selectedStartDay.value = pair.first!!.toInstant()
+ .atZone(ZoneId.systemDefault()).toLocalDate()
+ _selectedEndDay.value = pair.second!!.toInstant()
+ .atZone(ZoneId.systemDefault()).toLocalDate()
+ updateCurrentDate(pair.first!!.toInstant()
+ .atZone(ZoneId.systemDefault()).toLocalDate())
+ }
+ }
+
+
+ class MyDay(
+ val date: LocalDate,
+ val isCurMonth: Boolean,
+ val type: Int,// -1:不在范围内 0:在第一天 1:在最后一天 2:在中间 3:只有一天
+ val isImportant: Boolean = false,
+ ) {
+ fun copy(type: Int): MyDay {
+ return MyDay(date, isCurMonth, type, isImportant)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/EditPasswordDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/EditPasswordDialog.kt
new file mode 100644
index 0000000..a713e26
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/EditPasswordDialog.kt
@@ -0,0 +1,119 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.BigButton
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.base.MyTextField
+import com.bbitcn.f8.pad.model.ui.BaseDialogData
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.viewmodel.PasswordViewModel
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun EditPasswordDialogPreview() {
+ EditPasswordDialog(
+ BaseDialogData(showDialog = true)
+ )
+}
+
+@Composable
+fun EditPasswordDialog(
+ info: BaseDialogData,
+) {
+ val passwordViewModel: PasswordViewModel = viewModel()
+ var phone by rememberSaveable { mutableStateOf("") }
+ var code by rememberSaveable { mutableStateOf("") }
+ val codeSendTime by passwordViewModel.codeSendTime.collectAsState()
+ var pwd by rememberSaveable { mutableStateOf("") }
+ var pwdConfirm by rememberSaveable { mutableStateOf("") }
+ MyDialog("修改密码", info.showDialog, {
+ info.onDismiss()
+ },"确认修改", {
+ if (pwd.isEmpty() || phone.isEmpty() || code.isEmpty()) {
+ Toasty.showTipsDialog("请填写完整信息")
+ } else if (pwd != pwdConfirm) {
+ Toasty.showTipsDialog("两次密码不一致,请重新输入")
+ } else {
+ passwordViewModel.editPassword(phone, code, pwd) {
+ info.onDismiss()
+ }
+ }
+ }) {
+ Column(
+ modifier = M.padding(20.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ MyTextField(
+ value = phone,
+ isNumberInputType = true,
+ onValueChange = { phone = it },
+ hint = "手机号",
+ modifier = M.padding(top = 20.dp)
+ )
+ Row(
+ modifier = M.padding(top = 20.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ MyTextField(
+ value = code,
+ isNumberInputType = true,
+ onValueChange = { code = it },
+ hint = "验证码",
+ modifier = M.weight(1f)
+ )
+ MyButton(
+ enabled = codeSendTime == 0,
+ text = if (codeSendTime == 0) "发送验证码" else "${codeSendTime}秒后重发",
+ modifier = M
+ .padding(start = 10.dp)
+ .height(30.dp),
+ contentPadding = PaddingValues(vertical = 5.dp, horizontal = 10.dp)
+ ) {
+ if (phone.isEmpty() || !phone.matches(Regex("^1[3-9]\\d{9}\$"))) {
+ Toasty.showTipsDialog("手机号格式错误")
+ } else {
+ passwordViewModel.sendCode(
+ phone,
+ PasswordViewModel.SEND_CODE_BUCKET_EDIT_PASSWORD
+ )
+ }
+ }
+ }
+ MyTextField(
+ value = pwd,
+ onValueChange = { pwd = it },
+ hint = "密码",
+ modifier = M.padding(top = 20.dp)
+ )
+ MyTextField(
+ value = pwdConfirm,
+ onValueChange = {
+ pwdConfirm = it
+ },
+ hint = "确认密码",
+ modifier = M.padding(top = 20.dp)
+ )
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/FaceDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/FaceDialog.kt
new file mode 100644
index 0000000..29d9e82
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/FaceDialog.kt
@@ -0,0 +1,118 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.annotation.OptIn
+import androidx.camera.camera2.interop.ExperimentalCamera2Interop
+import androidx.camera.compose.CameraXViewfinder
+import androidx.camera.viewfinder.compose.MutableCoordinateTransformer
+import androidx.camera.viewfinder.core.ImplementationMode
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MyButton
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.bbitcn.f8.pad.base.MyDialog
+
+data class FaceDialogData(
+ val showDialog: Boolean = false,
+ val onDismiss: () -> Unit = {},
+
+ val isRegister: Boolean = false,
+ val isSystemUser: Boolean = false,
+
+ // 注册
+ val userId: String = "",
+
+ // 识别
+ val onRecognizeFace: (userId:String,faceToken:String) -> Unit = {_,_->},
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun FaceScreenPV() {
+ FaceDialog()
+}
+
+@OptIn(ExperimentalCamera2Interop::class)
+@Composable
+fun FaceDialog(
+ info: FaceDialogData = FaceDialogData(),
+ viewModel: FaceDialogViewModel = viewModel()
+) {
+ val tips by viewModel.tips.collectAsState()
+ MyDialog("人脸识别-${tips}",
+ info.showDialog,
+ onDismissRequest = { info.onDismiss() },
+ clickOKStr = if (info.isRegister) "确定注册" else "",
+ onClickOK = {
+ if (info.isRegister) {
+ viewModel.faceRegister(info.userId){
+ info.onDismiss()
+ }
+ }
+ }
+ ) {
+ LaunchedEffect(info.showDialog) {
+ if (info.showDialog){
+ viewModel.initializeCamera(info.isRegister,info.isSystemUser){ userId,faceToken->
+ // 识别成功的方法
+ info.onDismiss()
+ info.onRecognizeFace(userId, faceToken)
+ }
+ }
+ }
+ val currentSurfaceRequest by viewModel.surfaceRequests.collectAsState()
+ val cameraList by viewModel.cameraList.collectAsState()
+ val showPreview by viewModel.showPreview.collectAsState()
+ Box(modifier = M.fillMaxSize()) {
+ if (showPreview) {
+ // 显示摄像头预览
+ Box(modifier = M.fillMaxSize()) {
+ currentSurfaceRequest?.let { surfaceRequest ->
+ val coordinateTransformer = remember { MutableCoordinateTransformer() }
+ CameraXViewfinder(
+ surfaceRequest = surfaceRequest,
+ implementationMode = ImplementationMode.EXTERNAL,
+ modifier = M
+ .fillMaxSize()
+ .pointerInput(Unit) {
+ detectTapGestures {
+ with(coordinateTransformer) {
+ val surfaceCoords = it.transform()
+ viewModel.focusOnPoint(
+ surfaceRequest.resolution,
+ surfaceCoords.x, surfaceCoords.y
+ )
+ }
+ }
+ },
+ coordinateTransformer = coordinateTransformer
+ )
+ }
+ }
+ }
+ Box(
+ modifier = M
+ .align(Alignment.BottomEnd)
+ .padding(16.dp)
+ ) {
+ if (cameraList.isNotEmpty()) {
+ MyButton(text = "切换摄像头") {
+ viewModel.switchCamera()
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/FaceDialogViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/FaceDialogViewModel.kt
new file mode 100644
index 0000000..9901c31
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/FaceDialogViewModel.kt
@@ -0,0 +1,378 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import android.content.Context
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraManager
+import android.net.Uri
+import android.util.Size
+import android.view.WindowManager
+import androidx.annotation.OptIn
+import androidx.camera.camera2.interop.Camera2CameraInfo
+import androidx.camera.camera2.interop.ExperimentalCamera2Interop
+import androidx.camera.core.Camera
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.ImageAnalysis
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.ImageCaptureException
+import androidx.camera.core.Preview
+import androidx.camera.core.SurfaceRequest
+import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.LifecycleOwner
+import com.alibaba.sdk.android.oss.ClientException
+import com.alibaba.sdk.android.oss.ServiceException
+import com.alibaba.sdk.android.oss.model.ObjectMetadata
+import com.alibaba.sdk.android.oss.model.PutObjectRequest
+import com.alibaba.sdk.android.oss.model.PutObjectResult
+import com.bbitcn.f8.pad.MyApp
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.model.net.request.FaceRegisterF8Request
+import com.bbitcn.f8.pad.model.net.response.FarmerDetailResponse
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.Toasty.showTipsDialog
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.face.FaceRecognize
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.face.OssUtils
+import com.bbitcn.f8.pad.utils.global.RxTag
+import com.bbitcn.f8.pad.utils.log.MyLog
+import com.google.mlkit.vision.common.InputImage
+import com.google.mlkit.vision.face.FaceDetection
+import com.google.mlkit.vision.face.FaceDetectorOptions
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import java.io.File
+import java.util.Random
+
+@ExperimentalCamera2Interop
+class FaceDialogViewModel : BaseViewModel() {
+
+ private val _tips = MutableStateFlow("")
+ val tips = _tips.asStateFlow()
+ private val _surfaceRequests = MutableStateFlow(null)
+ val surfaceRequests: StateFlow get() = _surfaceRequests.asStateFlow()
+
+ // 存储可用摄像头的信息
+ private val _cameraList = MutableStateFlow>(emptyList())
+ val cameraList = _cameraList.asStateFlow()
+
+ private var cameraProvider: ProcessCameraProvider? = null
+ private var previewUseCase: Preview? = null
+ private var cameraSelector: CameraSelector? = null
+ val context: Context
+ val lifecycleOwner: LifecycleOwner
+ val imageCapture: ImageCapture
+
+ lateinit var curCameraInfo: CameraInfo
+
+ var myCamera: Camera? = null
+
+
+ init {
+ context = MyApp.appContext
+ lifecycleOwner = context as LifecycleOwner
+ // 获取当前设备的旋转角度
+ val rotation = (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager)
+ .defaultDisplay.rotation
+ imageCapture = ImageCapture.Builder()
+ .setTargetRotation(rotation)
+ .build()
+ }
+
+ private var _isRegister = false
+ private var _isSystemUser = false
+ private var _onRecognizeFace: ((userId: String, faceToken: String) -> Unit) = { _, _ -> }
+
+ fun initializeCamera(
+ isRegister: Boolean,
+ isSystemUser: Boolean,
+ onRecognizeFace: (userId: String, faceToken: String) -> Unit
+ ) {
+ _isSystemUser = isSystemUser
+ _isRegister = isRegister
+ _onRecognizeFace = onRecognizeFace
+ val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+ val cameraIdList = cameraManager.cameraIdList
+
+ var cameraInfoList = mutableListOf()
+ var selectedCameraId = "0"
+ // 获取所有摄像头的信息
+ cameraIdList.forEach { cameraId ->
+ val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId)
+ val lensFacing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING)
+ val cameraNam = if (lensFacing == CameraCharacteristics.LENS_FACING_FRONT) {
+ selectedCameraId = cameraId
+ "前置摄像头"
+ } else if (lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
+ "后置摄像头"
+ } else if (lensFacing == CameraCharacteristics.LENS_FACING_EXTERNAL) {
+ // 分辨率
+ val resolution =
+ cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE)
+ if (resolution == Size(1920, 1080)) {
+ "普通摄像头"
+ } else if (resolution == Size(2592, 1944)) {
+ "顶部摄像头"
+ } else {
+ "未知摄像头"
+ }
+ } else {
+ "未知摄像头"
+ }
+ cameraInfoList.add(CameraInfo(cameraId, lensFacing!!, cameraNam))
+ }
+ // 更新摄像头列表
+ _cameraList.value = cameraInfoList
+ // 选择默认摄像头
+ if (cameraInfoList.isNotEmpty()) {
+ var id = cameraInfoList.indexOfFirst { it.cameraId == selectedCameraId }
+ id = if (id == -1) 0 else id
+ curCameraInfo = cameraInfoList[id]
+ setCameraSelector(curCameraInfo)
+ }
+ }
+
+ @OptIn(androidx.camera.core.ExperimentalGetImage::class)
+ fun setCameraSelector(cameraInfo: CameraInfo) {
+ MyLog.test("setCameraSelector: ${cameraInfo.cameraId}, ${cameraInfo.lensFacing}")
+ // 创建新的 CameraSelector
+ cameraSelector = CameraSelector.Builder()
+ .requireLensFacing(cameraInfo.lensFacing)
+ .addCameraFilter {
+ it.filter { cameraXInfo ->
+ val thisCam = Camera2CameraInfo.from(cameraXInfo)
+ thisCam.cameraId == cameraInfo.cameraId
+ }
+ }
+ .build()
+ // 解绑当前相机
+ cameraProvider?.unbindAll()
+ // 重新绑定新的相机
+ val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
+ cameraProviderFuture.addListener({
+ cameraProvider = cameraProviderFuture.get()
+ // 初始化 Preview 用例
+ previewUseCase = Preview.Builder().build()
+ // 设置 SurfaceProvider
+ previewUseCase?.setSurfaceProvider { surfaceRequest ->
+ _surfaceRequests.value = surfaceRequest
+ }
+ // 解绑所有之前的用例
+ cameraProvider?.unbindAll()
+
+ val imageAnalysis = ImageAnalysis.Builder()
+ // 默认情况下,ImageAnalysis 的输出图像格式是 YUV,适合用于高效的图像处理和计算,但如果你需要以 RGBA 格式输出图像(通常用于处理图像像素颜色、UI 渲染等),就可以启用这一行代码。
+ // .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
+// .setTargetResolution(Size(1280, 720))
+ .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
+ .build()
+
+ // 高精度人脸检测
+ val highAccuracyOpts = FaceDetectorOptions.Builder()
+ .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
+ .setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
+ .setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
+ .build()
+ // 实时人脸检测
+ val realTimeOpts = FaceDetectorOptions.Builder()
+ .setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
+ .build()
+ val detector = FaceDetection.getClient(realTimeOpts)
+ imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(context)) { imageProxy ->
+ val mediaImage = imageProxy.image
+ if (mediaImage != null) {
+ val image =
+ InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
+ val result = detector.process(image)
+ .addOnSuccessListener { faces ->
+// MyLog.face("检测到人脸数量: ${faces.size}")
+ if (faces.size == 1) {
+ _showPreview.value = false
+ if (!_isRegister) {
+ // 识别:自动
+ faceRecognize()
+ // 停止分析
+ imageAnalysis.clearAnalyzer()
+ }
+ } else if (faces.size > 1) {
+ _tips.value = "识别到多个人,请重试"
+ } else {
+ _tips.value = "未识别到人脸"
+ }
+ }
+ .addOnFailureListener { e ->
+ MyLog.face("没有人脸,${e.message}")
+ }
+ .addOnCompleteListener {
+ mediaImage.close()
+ imageProxy.close()
+ }
+ }
+ }
+ // 绑定选择的摄像头和预览用例
+ myCamera = cameraProvider?.bindToLifecycle(
+ lifecycleOwner,
+ cameraSelector!!,
+ imageCapture,
+ imageAnalysis,
+ previewUseCase!!
+ )
+ }, ContextCompat.getMainExecutor(context))
+ }
+
+ fun focusOnPoint(surfaceBounds: Size, x: Float, y: Float) {
+
+ }
+
+
+ fun takePicture(onFinish: (Uri) -> Unit = {}) {
+ val file = File(context.externalMediaDirs.first(), "${System.currentTimeMillis()}.jpg")
+ val outputFileOptions = ImageCapture.OutputFileOptions.Builder(file).build()
+ val cameraExecutor = ContextCompat.getMainExecutor(context)
+ imageCapture.takePicture(outputFileOptions, cameraExecutor,
+ object : ImageCapture.OnImageSavedCallback {
+ override fun onError(error: ImageCaptureException) {
+ MyLog.test("拍照失败: ${error.message}")
+ Toasty.error("拍照失败: ${error.message}")
+ }
+
+ override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
+// Toasty.success("拍照成功")
+ val savedUri = outputFileResults.savedUri
+// _savedUri.value = savedUri
+ onFinish(savedUri!!)
+ }
+ })
+ }
+
+ fun faceRegister(userId: String, onSuccess: () -> Unit = {}) {
+ takePicture {
+ doInIoThreadThenUI("正在注册人脸", onIO = {
+ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++1. 百度人脸注册
+ val accessToken = apiService.getFaceAccessToken()
+ MyLog.test("userId: ${userId.replace("-", "_")}")
+ val success1 = FaceRecognize.faceRegister(
+ accessToken = accessToken.data.toString(),
+ userId = userId.replace("-", "_"),
+ groupId = FaceRecognize.getGroupId(_isSystemUser),
+ imageUri = it
+ )
+ if (success1 == null || success1.errorCode != 0) {
+ // 注册失败
+ showTipsDialog("人脸注册失败${success1?.errorMsg}")
+ } else {
+ // 注册成功
+ MyLog.face("人脸注册成功1")
+ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++2. 上传图片到阿里云
+ var success2 = true
+ val configInfo = apiService.getOSSConfig()
+ if (configInfo.code != 1) {
+ showTipsDialog(configInfo.msg)
+ success2 = false
+ }
+ val config = configInfo.data
+ // 构造上传请求。
+ val file = File(it.path!!)
+ val fileName = file.getName(); // 获取文件名
+ val objectName =
+ config.ossPath + generateUniqueBucketName("f8-pad") + fileName.substring(
+ fileName.lastIndexOf('.') + 1
+ )
+ var put = PutObjectRequest(config.bucketName, objectName, it)
+ // 设置文件元数据为可选操作。
+ val metadata = ObjectMetadata()
+ metadata.setHeader("x-oss-storage-class", "Standard")
+ put.metadata = metadata
+ try {
+ val oss = OssUtils.getOssClient()
+ val putResult: PutObjectResult = oss.putObject(put)
+ MyLog.network("PutObject" + "UploadSuccess")
+ MyLog.network("ETag" + putResult.eTag)
+ MyLog.network("RequestId" + putResult.requestId)
+ } catch (e: ClientException) {
+ // 客户端异常,例如网络异常等。
+ e.printStackTrace()
+ success2 = false
+ } catch (e: ServiceException) {
+ // 服务端异常。
+ e.printStackTrace()
+ success2 = false
+ } finally {
+ if (!success2) {
+ // 上传失败 回滚操作1
+ showTipsDialog("上传图片到阿里云失败")
+ } else {
+ // 上传成功
+ MyLog.face("人脸注册成功2")
+ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++3.上传信息到服务器
+ val result3 = apiService.registerFaceForF8(
+ data = FaceRegisterF8Request(
+ baiduFaceToken = success1!!.result.faceToken,
+ ossBucketname = config.bucketName,
+ ossObjectname = objectName,
+ userid = userId,
+ usertype = 0
+ )
+ )
+ if (result3.code != 1) {
+ // 上传失败 回滚操作1和2
+ } else {
+ // 上传成功
+ MyLog.face("人脸注册成功3")
+ return@doInIoThreadThenUI true
+ }
+ }
+ }
+ }
+ return@doInIoThreadThenUI false
+ }) {
+ if (it) {
+ onSuccess()
+ }
+ }
+ }
+ }
+
+ fun faceRecognize() {
+ takePicture {
+ doInIoThreadThenUI("正在识别人脸", onIO = {
+ val accessToken = apiService.getFaceAccessToken()
+ val result = FaceRecognize.faceRecognize(
+ accessToken = accessToken.data.toString(),
+ groupIdList = FaceRecognize.getGroupId(_isSystemUser),
+ imageUri = it
+ )
+ return@doInIoThreadThenUI result
+ }) { result ->
+ if (result.first == "false") {
+ // 重新启动摄像头
+ setCameraSelector(curCameraInfo)
+ } else {
+ _onRecognizeFace(result.first, result.second)
+ }
+ }
+ }
+ }
+
+ fun switchCamera() {
+ val curIndex = _cameraList.value.indexOfFirst { it == curCameraInfo }
+ val nextIndex = (curIndex + 1) % _cameraList.value.size
+ curCameraInfo = _cameraList.value[nextIndex]
+ setCameraSelector(curCameraInfo)
+ }
+
+ private val _showPreview = MutableStateFlow(true)
+ val showPreview = _showPreview.asStateFlow()
+
+ /** 生成一个唯一的 Bucket 名称 */
+ fun generateUniqueBucketName(prefix: String): String {
+ // 获取当前时间戳
+ val timestamp = System.currentTimeMillis().toString()
+ // 生成一个 0 到 9999 之间的数
+ val random = Random()
+ val randomNum: Int = random.nextInt(10000) // 生成一个 0 到 9999 之间的数
+ // 连接以形成一个唯一的 Bucket 名称
+ return "$prefix-$timestamp-$randomNum."
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/ForgetPasswordDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/ForgetPasswordDialog.kt
new file mode 100644
index 0000000..95aad0d
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/ForgetPasswordDialog.kt
@@ -0,0 +1,130 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.BigButton
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.base.MyTextField
+import com.bbitcn.f8.pad.model.ui.BaseDialogData
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.ui.viewmodel.PasswordViewModel
+
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun ForgetPasswordDialogPreview() {
+ ForgetPasswordDialog(
+ BaseDialogData(showDialog = true) {}
+ )
+}
+
+@Composable
+fun ForgetPasswordDialog(
+ info: BaseDialogData,
+ passwordViewModel: PasswordViewModel = viewModel()
+) {
+ val tenantCode by passwordViewModel.companyCode.collectAsState()
+ var phone by rememberSaveable { mutableStateOf("") }
+ var code by rememberSaveable { mutableStateOf("") }
+ val codeSendTime by passwordViewModel.codeSendTime.collectAsState()
+ var pwd by rememberSaveable { mutableStateOf("") }
+ var pwdConfirm by rememberSaveable { mutableStateOf("") }
+ MyDialog("忘记密码", info.showDialog, {
+ info.onDismiss()
+ }, "确认修改", {
+ if (pwd.isEmpty() || phone.isEmpty() || code.isEmpty() || tenantCode.isEmpty()) {
+ Toasty.showTipsDialog("请填写完整信息")
+ } else if (pwd != pwdConfirm) {
+ Toasty.showTipsDialog("两次密码不一致,请重新输入")
+ } else {
+ passwordViewModel.forgetPassword(tenantCode, phone, code, pwd) {
+ info.onDismiss()
+ }
+ }
+ }) {
+ Column(
+ modifier = M.padding(20.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ MyTextField(
+ value = tenantCode,
+ onValueChange = { },
+ hint = "公司编码",
+ fontColor = MyColors.Gray,
+ enabled = false,
+ modifier = M.padding(top = 20.dp)
+ )
+ MyTextField(
+ value = phone,
+ isNumberInputType = true,
+ onValueChange = { phone = it },
+ hint = "手机号",
+ modifier = M.padding(top = 20.dp)
+ )
+ Row(
+ modifier = M.padding(top = 20.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ MyTextField(
+ value = code,
+ isNumberInputType = true,
+ onValueChange = { code = it },
+ hint = "验证码",
+ modifier = M.weight(1f)
+ )
+ MyButton(
+ enabled = codeSendTime == 0,
+ text = if (codeSendTime == 0) "发送验证码" else "${codeSendTime}秒后重发",
+ modifier = M
+ .padding(start = 10.dp)
+ .height(30.dp),
+ contentPadding = PaddingValues(vertical = 5.dp, horizontal = 10.dp)
+ ) {
+ if (phone.isEmpty() || !phone.matches(Regex("^1[3-9]\\d{9}\$"))) {
+ Toasty.showTipsDialog("手机号格式错误")
+ } else {
+ passwordViewModel.sendCode(
+ phone,
+ PasswordViewModel.SEND_CODE_BUCKET_FORGET_PASSWORD
+ )
+ }
+ }
+ }
+ MyTextField(
+ value = pwd,
+ onValueChange = { pwd = it },
+ hint = "密码",
+ modifier = M.padding(top = 20.dp)
+ )
+ MyTextField(
+ value = pwdConfirm,
+ onValueChange = {
+ pwdConfirm = it
+ },
+ hint = "确认密码",
+ modifier = M.padding(top = 20.dp)
+ )
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/FrameDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/FrameDialog.kt
new file mode 100644
index 0000000..9973f19
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/FrameDialog.kt
@@ -0,0 +1,57 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.BaseDialogFrame
+import com.bbitcn.f8.pad.base.BigButton
+import com.bbitcn.f8.pad.base.MyAnimatedVisibility
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.ui.theme.MyColors
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun ___Preview() {
+ ___Dialog(
+ AuthDialogData(showDialog = true, authType = 1)
+ )
+}
+
+@Composable
+fun ___Dialog(info: AuthDialogData) {
+ MyDialog("",
+ info.showDialog,
+ onDismissRequest = { info.onDismiss() },
+ clickOKStr = "确定",
+ onClickOK = {
+
+ }
+ ) {
+
+ }
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/InputDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/InputDialog.kt
new file mode 100644
index 0000000..453d71b
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/InputDialog.kt
@@ -0,0 +1,176 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Clear
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.Card
+import androidx.compose.material3.Icon
+import androidx.compose.material3.InputChipDefaults
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.text.input.VisualTransformation
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.window.Dialog
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MyAnimatedVisibility
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.base.MyTextField
+import com.bbitcn.f8.pad.base.noVisualFeedbackClickable
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+data class InputDialogData(
+ val showDialog: Boolean = false,
+ val onDismiss: () -> Unit = {},
+
+ val title: String = "标题",
+ val defaultValue: String = "",// 默认显示
+ val isNumber: Boolean = false,
+ val isPassword: Boolean = false,
+ val onSuccess: (String) -> Unit = {}
+)
+
+/**
+ *
+ * @Description 输入弹窗
+ * @Author DuanKaiji
+ * @CreateTime 2024年04月30日 08:54:51
+ */
+@Preview(showBackground = false, widthDp = 1280, heightDp = 800)
+@Composable
+fun InputDialogPreview() {
+ InputDialog(InputDialogData(showDialog = true))
+}
+
+@Composable
+fun InputDialog(info: InputDialogData) {
+ var content by rememberSaveable { mutableStateOf(info.defaultValue) }
+ MyAnimatedVisibility(info.showDialog) {
+ LaunchedEffect(info.showDialog) {
+ if (info.showDialog) {
+ content = info.defaultValue
+ }
+ }
+ Box(
+ modifier = M
+ .fillMaxSize()
+ .background(Color(0x99000000))
+ .noVisualFeedbackClickable { },
+ contentAlignment = Alignment.Center
+ ) {
+ Column(modifier = M.width(600.dp)) {
+ Card {
+ Column(
+ modifier = M
+ .wrapContentWidth()
+ .background(Color.White)
+ .padding(horizontal = 20.dp, vertical = 10.dp),
+ horizontalAlignment = Alignment.End
+ ) {
+ Row(
+ modifier = M
+ .padding(vertical = 10.dp)
+ .fillMaxWidth()
+ .wrapContentHeight()
+ ) {
+ // 一个绿色的竖线
+ Box(
+ modifier = M
+ .width(3.dp)
+ .height(40.dp)
+ .background(Color(0xFF209344))
+ )
+ Text(
+ text = info.title,
+ fontSize = 30.sp,
+ modifier = M
+ .wrapContentHeight()
+ .padding(start = 10.dp)
+ .align(Alignment.CenterVertically)
+ )
+ Spacer(modifier = M.weight(1f))
+ Icon(
+ Icons.Default.Close,
+ contentDescription = null,
+ Modifier
+ .size(InputChipDefaults.AvatarSize)
+ .clickable {
+ info.onDismiss()
+ }
+ )
+ }
+ MyTextField(
+ visualTransformation = if (info.isPassword) {
+ PasswordVisualTransformation()
+ } else {
+ VisualTransformation.None
+ },
+ hint = "请输入" + info.title,
+ value = content,
+ modifier = M
+ .fillMaxWidth()
+ .padding(vertical = 40.dp),
+ isNumberInputType = info.isNumber,
+ keyboardActions = KeyboardActions(
+ onDone = {
+ info.onSuccess(content)
+ info.onDismiss()
+ }
+ ),
+ trailing = {
+ if(content.isNotEmpty()) {
+ Icon(
+ Icons.Filled.Clear,
+ contentDescription = "清空",
+ modifier = M.clickable {
+ content = ""
+ }
+ )
+ }
+ },
+ fontSize = 30.sp,
+ onValueChange = { content = it }
+ )
+ MyButton(
+ text = "确定",
+ onClick = {
+ info.onSuccess(content)
+ info.onDismiss()
+ },
+ modifier = M.padding(10.dp)
+ )
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/LoadingDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/LoadingDialog.kt
new file mode 100644
index 0000000..e819fed
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/LoadingDialog.kt
@@ -0,0 +1,128 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.LinearOutSlowInEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.material3.Card
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.LinearProgressIndicator
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MyAnimatedVisibility
+import com.bbitcn.f8.pad.base.MyCard
+
+/**
+ *
+ * @Description 加载弹窗(包括无限加载与指定加载)
+ * @Author DuanKaiji
+ * @CreateTime 2024年04月29日 09:09:42
+ */
+data class LoadingDialogData(
+ var showProcess: Boolean = false,
+ var process: Int = 0,
+ var showDialog: Boolean = false,
+ var content: String = "",
+)
+
+@Preview(showBackground = true)
+@Composable
+fun LoadingDialogPreview() {
+ LoadingDialog(
+ LoadingDialogData(
+ showDialog = true,
+ content = "正在加载中"
+ )
+ )
+}
+
+@Composable
+fun LoadingDialog(
+ loadingDialogData: LoadingDialogData
+) {
+ MyAnimatedVisibility(
+ loadingDialogData.showDialog,
+ enter = fadeIn(
+ animationSpec = tween(
+ durationMillis = 500,
+ easing = FastOutSlowInEasing
+ )
+ ),
+ exit = fadeOut(
+ animationSpec = tween(
+ durationMillis = 300,
+ easing = LinearOutSlowInEasing
+ )
+ )
+ ) {
+ Box(
+ modifier = M
+ .fillMaxSize()
+ .background(Color(0x99000000))
+ .clickable(
+ indication = null, // 移除点击效果
+ interactionSource = remember { MutableInteractionSource() } // 拦截点击事件
+ ) { /* 拦截点击,不执行任何操作 */ },
+ contentAlignment = Alignment.Center
+ ) {
+ Column {
+ MyCard(modifier = M.padding(bottom = 16.dp), radius = 18.dp) {
+ Column(
+ modifier = M
+ .wrapContentWidth()
+ .background(Color.White)
+ .padding(30.dp)
+ ) {
+ if (loadingDialogData.showProcess) {
+ LinearProgressIndicator(
+ modifier = M
+ .width(200.dp)
+ .align(Alignment.CenterHorizontally),
+ progress = {
+ loadingDialogData.process / 100f
+ },
+ )
+ } else {
+ CircularProgressIndicator(
+ modifier = M
+ .width(40.dp)
+ .align(Alignment.CenterHorizontally),
+ color = MaterialTheme.colorScheme.primary,
+ trackColor = MaterialTheme.colorScheme.surfaceVariant,
+ )
+ }
+ Text(
+ text = loadingDialogData.content,
+ textAlign = TextAlign.Center,
+ modifier = M
+ .padding(top = 20.dp)
+ .widthIn(200.dp)
+ )
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/MessageDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/MessageDialog.kt
new file mode 100644
index 0000000..d7fcb62
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/MessageDialog.kt
@@ -0,0 +1,110 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.BaseDialogFrame
+import com.bbitcn.f8.pad.base.BigButton
+import com.bbitcn.f8.pad.base.MyAnimatedVisibility
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.ui.theme.MyColors
+
+data class MessageDialogData(
+ val showDialog: Boolean = false,
+ val onDismiss: () -> Unit = {},
+
+ val time: String = "",
+ val username: String = "",
+ val content: String = "",
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun MessagePreview() {
+ MessageDialog(
+ MessageDialogData(
+ showDialog = true,
+ time = "2024年8月7日 13:38:42",
+ username = "广西智慧蚕桑收购系统",
+ content = "奥立集团"
+ )
+ )
+}
+
+@Composable
+fun MessageDialog(info: MessageDialogData) {
+ MyDialog("查看消息",
+ info.showDialog,
+ onDismissRequest = { info.onDismiss() }
+ ) {
+ Column(
+ modifier = M
+ .fillMaxSize()
+ .padding(horizontal = 30.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Row(modifier = M.fillMaxWidth().padding(vertical = 10.dp)) {
+ Image(
+ painter = painterResource(id = R.drawable.time),
+ contentDescription = "time",
+ modifier = M
+ .size(20.dp)
+ )
+ Text(modifier = M.padding(horizontal = 5.dp), text = "时间:${info.time}")
+ Spacer(modifier = M.width(70.dp))
+ Image(
+ painter = painterResource(id = R.drawable.user_circle),
+ contentDescription = "user",
+ modifier = M
+ .size(20.dp)
+ )
+ Text(modifier = M.padding(horizontal = 5.dp), text = "发送人:${info.username}")
+ }
+ MyCard(elevation = 0.5.dp, modifier = M.weight(1f)) {
+ Column(
+ modifier = M
+ .fillMaxSize()
+ .background(color = MyColors.LightGray)
+ .padding(30.dp)
+ .verticalScroll(rememberScrollState()) // 添加滚动支持
+ ) {
+ Text(
+ text = info.content,
+ fontSize = MaterialTheme.typography.headlineMedium.fontSize,
+ fontWeight = FontWeight.Normal
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/OCRDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/OCRDialog.kt
new file mode 100644
index 0000000..1f66a86
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/OCRDialog.kt
@@ -0,0 +1,112 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.annotation.OptIn
+import androidx.camera.camera2.interop.ExperimentalCamera2Interop
+import androidx.camera.compose.CameraXViewfinder
+import androidx.camera.viewfinder.compose.MutableCoordinateTransformer
+import androidx.camera.viewfinder.core.ImplementationMode
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Refresh
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MyDialog
+
+data class OCRDialogData(
+ val showDialog: Boolean = false,
+ val identityType: Int = 0, // 0:身份证 1:银行卡 2:人脸识别
+ val onIdentityIdCard: (name: String,gender:String, idCard: String, address: String) -> Unit = { _, _, _, _ -> },
+ val onIdentityBankCard: (bankCode: String) -> Unit = {},
+ val onDismiss: () -> Unit = {}
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun OCRPreview() {
+ OCRDialog(OCRDialogData())
+}
+
+@OptIn(ExperimentalCamera2Interop::class)
+@Composable
+fun OCRDialog(info: OCRDialogData, ocrDialogViewModel: OCRDialogViewModel = viewModel()) {
+ val currentSurfaceRequest by ocrDialogViewModel.surfaceRequests.collectAsState()
+ val identityInfo = info.identityType
+ MyDialog(
+ if (identityInfo == 0) "身份证识别" else "银行卡识别",
+ info.showDialog,
+ onDismissRequest = info.onDismiss,
+ clickOKStr = "拍照识别",
+ onClickOK = {
+ if (identityInfo == 0) {
+ ocrDialogViewModel.recognizeIdCard(info.onIdentityIdCard){
+ info.onDismiss()
+ }
+ } else {
+ ocrDialogViewModel.recognizeBankCard(info.onIdentityBankCard){
+ info.onDismiss()
+ }
+ }
+ }
+ ) {
+ LaunchedEffect(info) {
+ if (info.showDialog) {
+ ocrDialogViewModel.initCamera(identityInfo)
+ }
+ }
+ val coordinateTransformer = remember { MutableCoordinateTransformer() }
+ Box(
+ modifier = M.fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ if (currentSurfaceRequest == null) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ CircularProgressIndicator(modifier = M.size(80.dp))
+ Text(
+ text = "正在初始化相机",
+ modifier = M.padding(16.dp)
+ )
+ }
+ } else {
+ CameraXViewfinder(
+ surfaceRequest = currentSurfaceRequest!!,
+ implementationMode = ImplementationMode.EXTERNAL,
+ modifier = M
+ .fillMaxSize(),
+ coordinateTransformer = coordinateTransformer
+ )
+ Box(
+ modifier = M.fillMaxSize(),
+ contentAlignment = Alignment.CenterEnd
+ ) {
+ IconButton(onClick = {
+ ocrDialogViewModel.switchCamera()
+ }) {
+ // 白色
+ Image(
+ imageVector = Icons.Default.Refresh,
+ contentDescription = "切换摄像头",
+ modifier = M.size(40.dp),
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/OCRDialogViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/OCRDialogViewModel.kt
new file mode 100644
index 0000000..12e877e
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/OCRDialogViewModel.kt
@@ -0,0 +1,216 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import android.content.Context
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraManager
+import android.net.Uri
+import android.util.Size
+import android.view.WindowManager
+import androidx.camera.camera2.interop.Camera2CameraInfo
+import androidx.camera.camera2.interop.ExperimentalCamera2Interop
+import androidx.camera.core.Camera
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.ImageCaptureException
+import androidx.camera.core.Preview
+import androidx.camera.core.SurfaceRequest
+import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.LifecycleOwner
+import com.bbitcn.f8.pad.MyApp
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.ui.screen.secondFunc.CameraInfo
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.utils.log.MyLog
+import com.bbitcn.f8.pad.utils.externalModules.ocr.ALiApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import java.io.File
+
+@ExperimentalCamera2Interop
+class OCRDialogViewModel : BaseViewModel() {
+
+ private val _surfaceRequests = MutableStateFlow(null)
+ val surfaceRequests: StateFlow get() = _surfaceRequests.asStateFlow()
+
+ // 存储可用摄像头的信息
+ private val _cameraList: MutableList = mutableListOf()
+
+ private var cameraProvider: ProcessCameraProvider? = null
+ private var previewUseCase: Preview? = null
+ private var cameraSelector: CameraSelector? = null
+ val context = MyApp.appContext
+ lateinit var lifecycleOwner: LifecycleOwner
+ lateinit var imageCapture: ImageCapture
+
+ /**
+ * 当前使用的摄像头索引
+ */
+ private var _curCameraIndex = 0
+
+ /**
+ * 初始化相机
+ * @param type 0:身份证 1:银行卡 2:人脸识别
+ */
+ fun initCamera(type: Int) {
+ doInIoThreadThenUI("初始化相机", onIO = {
+ lifecycleOwner = context as LifecycleOwner
+ // 获取当前设备的旋转角度
+ val rotation = (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager)
+ .defaultDisplay.rotation
+ imageCapture = ImageCapture.Builder()
+ .setTargetRotation(rotation)
+ .build()
+ // 获取 CameraProvider 实例并初始化摄像头列表
+ val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+ val cameraIdList = cameraManager.cameraIdList
+
+ // 获取所有摄像头的信息
+ cameraIdList.forEach { cameraId ->
+ val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId)
+ val lensFacing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING)
+ val cameraNam = if (lensFacing == CameraCharacteristics.LENS_FACING_FRONT) {
+ "前置摄像头"
+ } else if (lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
+ "后置摄像头"
+ } else if (lensFacing == CameraCharacteristics.LENS_FACING_EXTERNAL) {
+ // 分辨率
+ val resolution =
+ cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE)
+ if (resolution == Size(1920, 1080)) {
+ "普通摄像头"
+ } else if (resolution == Size(2592, 1944)) {
+ "顶部摄像头"
+ } else {
+ "未知摄像头"
+ }
+ } else {
+ "未知摄像头"
+ }
+ _cameraList.add(CameraInfo(cameraId, lensFacing!!, cameraNam))
+ }
+ // 默认选择合适的摄像头
+ _cameraList.forEachIndexed { index, cameraInfo ->
+ if (((type == 0 || type == 1) && cameraInfo.lensFacing == CameraCharacteristics.LENS_FACING_BACK) ||// 卡片识别 默认后置
+ (type == 2 && cameraInfo.lensFacing == CameraCharacteristics.LENS_FACING_FRONT)// 人脸识别 默认前置
+ ) {
+ _curCameraIndex = index
+ }
+ }
+ }) {
+ switchCamera(_curCameraIndex)
+ }
+ }
+
+
+ var myCamera: Camera? = null
+
+ fun setCameraSelector(cameraInfo: CameraInfo) {
+ // 创建新的 CameraSelector
+ cameraSelector = CameraSelector.Builder()
+ .requireLensFacing(cameraInfo.lensFacing)
+ .addCameraFilter {
+ it.filter { cameraXInfo ->
+ val thisCam = Camera2CameraInfo.from(cameraXInfo)
+ thisCam.cameraId == cameraInfo.cameraId
+ }
+ }
+ .build()
+ // 解绑当前相机
+ cameraProvider?.unbindAll()
+ // 重新绑定新的相机
+ val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
+ cameraProviderFuture.addListener({
+ cameraProvider = cameraProviderFuture.get()
+ // 初始化 Preview 用例
+ previewUseCase = Preview.Builder().build()
+ // 设置 SurfaceProvider
+ previewUseCase?.setSurfaceProvider { surfaceRequest ->
+ _surfaceRequests.value = surfaceRequest
+ }
+ // 解绑所有之前的用例
+ cameraProvider?.unbindAll()
+ // 绑定选择的摄像头和预览用例
+ myCamera = cameraProvider?.bindToLifecycle(
+ lifecycleOwner,
+ cameraSelector!!,
+ imageCapture,
+ previewUseCase!!
+ )
+ }, ContextCompat.getMainExecutor(context))
+ }
+
+ fun takePicture(onFinish: (Uri) -> Unit = {}) {
+ val file = File(context.externalMediaDirs.first(), "${System.currentTimeMillis()}.jpg")
+ val outputFileOptions = ImageCapture.OutputFileOptions.Builder(file).build()
+ val cameraExecutor = ContextCompat.getMainExecutor(context)
+ imageCapture.takePicture(outputFileOptions, cameraExecutor,
+ object : ImageCapture.OnImageSavedCallback {
+ override fun onError(error: ImageCaptureException) {
+ MyLog.test("拍照失败: ${error.message}")
+ Toasty.error("拍照失败: ${error.message}")
+ }
+
+ override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
+ val savedUri = outputFileResults.savedUri
+ // 拍照成功后关闭相机
+ cameraProvider?.unbindAll()
+ onFinish(savedUri!!)
+ }
+ })
+ }
+
+ fun recognizeIdCard(
+ onIdentityIdCard: (name: String, gender: String, idCard: String, address: String) -> Unit,
+ onFinish: () -> Unit
+ ) {
+ takePicture {
+ onFinish()
+ doInIoThread("正在识别身份证") {
+ // 识别身份证
+ ALiApi.identityIdCard(it) {
+ val info = it.data.face.data
+ onIdentityIdCard(info.name, info.sex, info.idNumber, info.address)
+ }
+ }
+ }
+ }
+
+ fun recognizeBankCard(onIdentityBankCard: (bankCode: String) -> Unit, onFinish: () -> Unit) {
+ takePicture {
+ onFinish()
+ doInIoThread("正在识别银行卡") {
+ // 识别身份证
+ ALiApi.identityBankCard(it) {
+ val info = it.data
+ onIdentityBankCard(info.cardNumber)
+ }
+ }
+ }
+ }
+
+
+ fun switchCamera(cameraIndex: Int = -1) {
+ val cameraSize = _cameraList.size
+ if (cameraSize > 1) {
+ if (cameraIndex != -1) {
+ _curCameraIndex = cameraIndex
+ } else {
+ val next = (_curCameraIndex + 1) % cameraSize
+ _curCameraIndex = next
+ }
+ setCameraSelector(_cameraList[_curCameraIndex])
+ Toasty.showToast("切换至${_cameraList[_curCameraIndex].cameraName}")
+ } else {
+ Toasty.showTipsDialog("没有可切换的摄像头")
+ }
+ }
+
+}
+
+data class CameraInfo(
+ val cameraId: String,
+ val lensFacing: Int,
+ val cameraName: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/PriceDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/PriceDialog.kt
new file mode 100644
index 0000000..5cb50af
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/PriceDialog.kt
@@ -0,0 +1,244 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.InputChipDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.BaseDialogFrame
+import com.bbitcn.f8.pad.base.BigButton
+import com.bbitcn.f8.pad.base.MyAnimatedVisibility
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.MyCheckBox
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.base.MyTextField
+import com.bbitcn.f8.pad.base.TableHeadLine
+import com.bbitcn.f8.pad.base.VipBadge
+import com.bbitcn.f8.pad.model.net.request.TareRequest
+import com.bbitcn.f8.pad.model.net.request.UpdateTicketPriceRequest
+import com.bbitcn.f8.pad.model.net.response.PurchaseDataResponse
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.utils.MyUtil
+
+data class PriceDialogData(
+ val showDialog: Boolean = false,
+ val onDismiss: () -> Unit = {},
+
+ val onSave: (request: UpdateTicketPriceRequest) -> Unit = {},
+ val data: PurchaseDataResponse.Data = PurchaseDataResponse.Data()
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun PriceDialogPreview() {
+ PriceDialog(
+ PriceDialogData(showDialog = true)
+ )
+}
+
+@Composable
+fun PriceDialog(info: PriceDialogData) {
+ val request = remember { mutableStateListOf() }
+ MyDialog("定价",
+ info.showDialog,
+ onDismissRequest = { info.onDismiss() },
+ clickOKStr = "确定",
+ onClickOK = {
+ info.onSave(UpdateTicketPriceRequest(czSysid = info.data.czSysid, request))
+ }
+ ) {
+ LaunchedEffect(info.showDialog) {
+ if (info.showDialog) {
+ request.clear()
+ info.data.chengZhongItemSumList.forEach {
+ request.add(
+ UpdateTicketPriceRequest.Update(
+ sysid = it.sysid,
+ newJweight = it.jweightSum,
+ newKweight = it.kweightSum,
+ newMoney = it.moneySum,
+ newPrice = it.price,
+ newPweight = it.pweightSum
+ )
+ )
+ }
+ }
+ }
+ Column(
+ horizontalAlignment = Alignment.Start,
+ verticalArrangement = Arrangement.spacedBy(5.dp)
+ ) {
+ Row(modifier = M.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
+ Text(
+ modifier = M
+ .padding(end = 15.dp),
+ text = "茧票:${info.data.billCode}",
+ style = MaterialTheme.typography.headlineMedium
+ )
+ Text(
+ modifier = M
+ .padding(end = 15.dp),
+ text = "农户:${info.data.nhName}",
+ style = MaterialTheme.typography.headlineMedium
+ )
+ }
+ Column(verticalArrangement = Arrangement.spacedBy(5.dp)) {
+ TableHeadLine(
+ list = listOf(
+ "茧别" to 1, "磅数" to 1, "件数" to 1,
+ "净重(公斤)" to 1, "X单价(元/公斤)" to 3, "=金额" to 1
+ ),
+ modifier = M.fillMaxWidth()
+ )
+ val list = info.data.chengZhongItemSumList
+ LazyColumn(modifier = M.weight(1f)) {
+ itemsIndexed(list) { index, item ->
+ var priceT by rememberSaveable { mutableStateOf(item.price.toString()) }
+ val moneySum = ((priceT.toDoubleOrNull() ?: 0.0) * item.jweightSum)
+ PriceTableContent(
+ M.animateItem(),
+ index % 2 == 0,
+ item.sgTypeName,
+ item.weightCount.toString(),
+ item.boxCount.toString(),
+ item.jweightSum.toString(),
+ priceT,
+ moneySum.toString()
+ ) { price ->
+ priceT = price
+ val re = request.first { it.sysid == item.sysid }
+ re.newPrice = price.toDoubleOrNull() ?: 0.0
+ re.newMoney = moneySum
+ }
+ }
+ }
+ SaveTableTotal(
+ listOf(
+ "汇总" to 1f,
+ list.sumOf { it.weightCount }.toString() to 1f,
+ list.sumOf { it.boxCount }.toString() to 1f,
+ MyUtil.formatDouble(list.sumOf { it.jweightSum }).toString() to 1f,
+ MyUtil.formatDouble(request.sumOf { it.newPrice }).toString() to 3f,
+ MyUtil.formatDouble(request.sumOf { it.newMoney }).toString() to 1f,
+ )
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun PriceTableContent(
+ modifier: Modifier,
+ isDeepBg: Boolean,
+ sgTypeName: String = "",
+ weightCount: String = "",
+ boxCount: String = "",
+ jweight: String = "",
+ price: String = "",
+ moneySum: String = "",
+ onValueChange: (price:String) -> Unit = { _ -> }
+) {
+ Card(
+ modifier = modifier,
+ colors = CardDefaults.cardColors(
+ containerColor = if (isDeepBg) MyColors.LightBlue else MyColors.White
+ )
+ ) {
+ Row(
+ modifier = M.padding(vertical = 4.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = sgTypeName,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.weight(1f),
+ textAlign = TextAlign.Center,
+ )
+ Text(
+ text = weightCount,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.weight(1f),
+ textAlign = TextAlign.Center,
+ )
+ Text(
+ text = boxCount,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.weight(1f),
+ textAlign = TextAlign.Center,
+ )
+ Text(
+ text = jweight,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.weight(1f),
+ textAlign = TextAlign.Center,
+ )
+ MyTextField(
+ value = price,
+ onValueChange = {
+ onValueChange(it)
+ },
+ // 末尾添加删除按钮
+ trailing = {
+ if (price.isNotEmpty()) {
+ Icon(
+ Icons.Default.Close,
+ contentDescription = null,
+ Modifier
+ .size(InputChipDefaults.AvatarSize)
+ .clickable {
+ onValueChange("")
+ }
+ )
+ }
+ },
+ modifier = M
+ .weight(3f)
+ .padding(horizontal = 10.dp),
+ )
+ Text(
+ text = moneySum,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.weight(1f),
+ textAlign = TextAlign.Center,
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/QueryBalanceDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/QueryBalanceDialog.kt
new file mode 100644
index 0000000..88bd7e7
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/QueryBalanceDialog.kt
@@ -0,0 +1,57 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.BaseDialogFrame
+import com.bbitcn.f8.pad.base.BigButton
+import com.bbitcn.f8.pad.base.MyAnimatedVisibility
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.ui.theme.MyColors
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun QueryBalanceDialogPreview() {
+ QueryBalanceDialog(
+ AuthDialogData(showDialog = true, authType = 1)
+ )
+}
+
+@Composable
+fun QueryBalanceDialog(info: AuthDialogData) {
+ MyDialog("查询额度",
+ info.showDialog,
+ onDismissRequest = { info.onDismiss() },
+ clickOKStr = "确定",
+ onClickOK = {
+
+ }
+ ) {
+
+ }
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/SaveDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/SaveDialog.kt
new file mode 100644
index 0000000..ac15915
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/SaveDialog.kt
@@ -0,0 +1,160 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.BaseDialogFrame
+import com.bbitcn.f8.pad.base.BigButton
+import com.bbitcn.f8.pad.base.MyAnimatedVisibility
+import com.bbitcn.f8.pad.base.MyCheckBox
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.base.MyTable
+import com.bbitcn.f8.pad.base.MyTextField
+import com.bbitcn.f8.pad.base.TableHeadLine
+import com.bbitcn.f8.pad.base.VipBadge
+import com.bbitcn.f8.pad.model.net.response.PurchaseDetailListResponse
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.utils.MyUtil
+
+data class SaveDialogData(
+ val showDialog: Boolean = false,
+ val onDismiss: () -> Unit = {},
+
+ val name: String = "",
+ val detailList: List = listOf(),
+ val saveTicket:() -> Unit = {}
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun SaveDialogPreview() {
+ SaveDialog(
+ SaveDialogData(showDialog = true)
+ )
+}
+
+@Composable
+fun SaveDialog(info: SaveDialogData) {
+ MyDialog("保存确认",
+ info.showDialog,
+ onDismissRequest = { info.onDismiss() },
+ clickOKStr = "确定",
+ onClickOK = {
+ //todo 判断是否需要打印
+ // todo 判断是否需要发送短信
+ info.saveTicket()
+ }
+ ) {
+ Column(horizontalAlignment = Alignment.Start) {
+ Text(
+ modifier = M
+ .padding(end = 15.dp, bottom = 10.dp),
+ text = "农户:${info.name}",
+ style = MaterialTheme.typography.bodyLarge
+ )
+ Column(verticalArrangement = Arrangement.spacedBy(5.dp)) {
+ MyTable(modifier = M.weight(1f),
+ headerStrings = listOf(
+ "茧别", "磅数", "件数",
+ "毛重", "X单价(元/公斤)", "=金额(元)"
+ ),
+ ratio = listOf(1f, 1f, 1f, 1f, 2f, 1f),
+ items = info.detailList.map { item ->
+ listOf(
+ item.categoryName,
+ item.weighingTimes.toString(),
+ item.basketCount.toString(),
+ item.grossWeight.toString(),
+ item.unitPrice.toString(),
+ item.subtotal.toString()
+ )
+ }
+ )
+ SaveTableTotal(
+ listOf(
+ "汇总" to 1f,
+ info.detailList.size.toString() to 1f,
+ info.detailList.sumOf { it.basketCount }.toString() to 1f,
+ MyUtil.formatDouble(info.detailList.sumOf { it.grossWeight}).toString() to 1f,
+ "" to 2f,
+ MyUtil.formatDouble( info.detailList.sumOf { it.subtotal }).toString() to 1f
+ )
+ )
+ Row(
+ modifier = M.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ MyCheckBox("打印称重凭据")
+ VipBadge {
+ MyCheckBox("发送短信至农户")
+ }
+ Text(modifier = M.padding(start = 15.dp), text = "打印机在线")
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun SaveTableTotal(
+ data: List> = listOf(
+ "汇总" to 1f,
+ "13" to 1f,
+ "13" to 1f,
+ "13" to 1f,
+ "" to 2f,
+ "13" to 1f
+ )
+) {
+ Card(
+ colors = CardDefaults.cardColors(
+ containerColor = MyColors.LightGreen
+ )
+ ) {
+ Row(
+ modifier = M.padding(vertical = 5.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ data.forEach {
+ Text(
+ text = it.first,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.weight(it.second),
+ fontWeight = FontWeight.Bold,
+ textAlign = TextAlign.Center,
+ )
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/ScanDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/ScanDialog.kt
new file mode 100644
index 0000000..f310e59
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/ScanDialog.kt
@@ -0,0 +1,78 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.idcard.IDCardUtils
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.nfc.NFCUtils
+
+data class ScanDialogData(
+ val showDialog: Boolean = false,
+ val isNFC: Boolean = false,
+ val onDismiss: () -> Unit = {}
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun ScanPreview() {
+ ScanDialog(ScanDialogData())
+}
+
+@Composable
+fun ScanDialog(info: ScanDialogData) {
+ val nfcState by NFCUtils.state.collectAsState()
+ val idCardState by IDCardUtils.state.collectAsState()
+ val state = if (info.isNFC) nfcState else idCardState
+ MyDialog(
+ if (info.isNFC) "NFC读卡" else "身份证读卡",
+ info.showDialog,
+ onDismissRequest = info.onDismiss
+ ) {
+ Column(
+ modifier = M.fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ if (state == 0) {
+ CircularProgressIndicator(
+ modifier = M.size(80.dp)
+ )
+ }
+ Image(
+ painter = painterResource(
+ id = when (state) {
+ -1 -> R.drawable.error
+ else -> R.drawable.success
+ }
+ ),
+ contentDescription = null,
+ modifier = M
+ .size(80.dp)
+ .padding(16.dp)
+ )
+ Text(
+ text = if (state == 0) {
+ "正在初始化读卡模块中..."
+ } else if (state == -1) {
+ "读卡模块初始化失败,请检查读卡器是否开启"
+ } else {
+ "读卡器准备就绪\n" + if (info.isNFC) "请将卡片靠近NFC刷卡模块" else "请将身份证靠近身份证刷卡模块"
+ },
+ modifier = M.padding(16.dp)
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/SplitDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/SplitDialog.kt
new file mode 100644
index 0000000..43ff720
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/SplitDialog.kt
@@ -0,0 +1,194 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.BaseDialogFrame
+import com.bbitcn.f8.pad.base.BigButton
+import com.bbitcn.f8.pad.base.MyAnimatedVisibility
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.base.MyTextField
+import com.bbitcn.f8.pad.base.TableHeadLine
+import com.bbitcn.f8.pad.ui.theme.MyColors
+
+data class SplitDialogData(
+ val showDialog: Boolean = false,
+ val onDismiss: () -> Unit = {},
+
+ val byWeight: Boolean = true,
+ val uniqueId: Long = System.currentTimeMillis()
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun SplitDialogPreview() {
+ SplitDialog(
+ SplitDialogData(showDialog = true, byWeight = true)
+ )
+}
+
+@Composable
+fun SplitDialog(info: SplitDialogData) {
+ MyDialog(if(info.byWeight) "按重量拆分茧别" else "按比例拆分茧别",
+ info.showDialog,
+ onDismissRequest = { info.onDismiss() },
+ clickOKStr = "确定",
+ onClickOK = {
+
+ }
+ ) {
+ Column(horizontalAlignment = Alignment.Start) {
+ Row(
+ modifier = M
+ .fillMaxWidth()
+ .padding(end = 15.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ modifier = M
+ .padding(end = 15.dp), text = "重量"
+ )
+ MyTextField(
+ modifier = M
+ .width(150.dp),
+ readOnly = true,
+ value = "0.00"
+ )
+ Text(text = "公斤")
+ }
+ Row(
+ modifier = M
+ .fillMaxWidth()
+ ) {
+ Text(
+ modifier = M
+ .padding(end = 15.dp), text = "拆分"
+ )
+ Column(verticalArrangement = Arrangement.spacedBy(5.dp)) {
+ TableHeadLine(
+ list = listOf("茧别" to 1, "重量(公斤)" to 1, "比例(%)" to 1),
+ modifier = M.fillMaxWidth()
+ )
+ LazyColumn(modifier = M.weight(1f)) {
+ items(13) {
+ InputTableContent(true, it % 2 == 0)
+ }
+ }
+ InputTableContentEnd(info.byWeight, false)
+ InputTableContentEnd(info.byWeight, true)
+ }
+ }
+
+ }
+ }
+}
+
+@Composable
+fun InputTableContent(byWeight: Boolean, isDeepBg: Boolean) {
+ Card(
+ colors = CardDefaults.cardColors(
+ containerColor = if (isDeepBg) MyColors.LightBlue else Color.White
+ )
+ ) {
+ Row(
+ modifier = M.padding(vertical = 5.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = "上茧",
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.weight(1f),
+ textAlign = TextAlign.Center,
+ )
+ MyTextField(
+ value = "80.0",
+ onValueChange = {},
+ modifier = M
+ .weight(1f)
+ .padding(horizontal = 10.dp),
+ readOnly = byWeight
+ )
+ MyTextField(
+ value = "20",
+ onValueChange = {},
+ modifier = M
+ .weight(1f)
+ .padding(horizontal = 10.dp),
+ readOnly = !byWeight
+ )
+ }
+ }
+}
+
+
+/**
+ * @param byWeight 是否按重量
+ * @param isTotal 是否是总计 否则为扣重
+ */
+@Composable
+fun InputTableContentEnd(byWeight: Boolean, isTotal: Boolean) {
+ Card(
+ colors = CardDefaults.cardColors(
+ containerColor = if (isTotal) MyColors.LightGreen else MyColors.LightOrange
+ )
+ ) {
+ Row(
+ modifier = M.padding(vertical = 5.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = if (isTotal) "汇总" else "-扣重",
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.weight(1f),
+ color = if (isTotal) MyColors.Black else MyColors.Orange,
+ fontWeight = FontWeight.Bold,
+ textAlign = TextAlign.Center,
+ )
+ MyTextField(
+ value = "80.0",
+ onValueChange = {},
+ modifier = M
+ .weight(1f)
+ .padding(horizontal = 10.dp),
+ readOnly = byWeight
+ )
+ MyTextField(
+ value = "20",
+ onValueChange = {},
+ modifier = M
+ .weight(1f)
+ .padding(horizontal = 10.dp),
+ readOnly = !byWeight
+ )
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/TareDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/TareDialog.kt
new file mode 100644
index 0000000..c50ace8
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/TareDialog.kt
@@ -0,0 +1,304 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.InputChipDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.VerticalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.BaseDialogFrame
+import com.bbitcn.f8.pad.base.BigButton
+import com.bbitcn.f8.pad.base.MyAnimatedVisibility
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.MyCheckBox
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.base.MyTextField
+import com.bbitcn.f8.pad.base.TableHeadLine
+import com.bbitcn.f8.pad.base.VipBadge
+import com.bbitcn.f8.pad.model.net.request.TareRequest
+import com.bbitcn.f8.pad.model.net.response.PurchaseDataResponse
+import com.bbitcn.f8.pad.ui.screen.view.deviceManager.scale.MyWeightShow
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.utils.MyUtil
+
+data class TareDialogData(
+ val showDialog: Boolean = false,
+ val onDismiss: () -> Unit = {},
+
+ val canTare: Boolean = false,//是否可以扣重
+ val onSave: (request: TareRequest) -> Unit = {},
+ val data: PurchaseDataResponse.Data = PurchaseDataResponse.Data()
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun TareDialogPreview() {
+ TareDialog(
+ TareDialogData(showDialog = true)
+ )
+}
+
+@Composable
+fun TareDialog(info: TareDialogData) {
+ val request = remember { mutableStateListOf() }
+ MyDialog(if(info.canTare) "扣皮扣重" else "扣重",
+ info.showDialog,
+ onDismissRequest = { info.onDismiss() },
+ clickOKStr = "确定",
+ onClickOK = {
+ info.onSave(TareRequest(czSysid = info.data.czSysid, request))
+ }
+ ) {
+ LaunchedEffect(info.showDialog) {
+ if (info.showDialog) {
+ request.clear()
+ info.data.chengZhongItemSumList.forEach {
+ request.add(
+ TareRequest.UpdateItem(
+ sgTypName = it.sgTypeName,
+ sysid = it.sysid,
+ jweight = it.jweightSum,
+ kweight = it.kweightSum,
+ pweight = it.pweightSum
+ )
+ )
+ }
+ }
+ }
+
+ Column(
+ horizontalAlignment = Alignment.Start,
+ verticalArrangement = Arrangement.spacedBy(5.dp)
+ ) {
+ Row(modifier = M.fillMaxWidth()) {
+ Column(
+ modifier = M.weight(1f),
+ verticalArrangement = Arrangement.spacedBy(5.dp)
+ ) {
+ Text(
+ modifier = M
+ .padding(end = 15.dp),
+ text = "茧票:${info.data.billCode}",
+ style = MaterialTheme.typography.headlineMedium
+ )
+ Text(
+ modifier = M
+ .padding(end = 15.dp),
+ text = "农户:${info.data.nhName}",
+ style = MaterialTheme.typography.headlineMedium
+ )
+ }
+ MyWeightShow(modifier = M.weight(1f))
+ }
+ Column(verticalArrangement = Arrangement.spacedBy(5.dp)) {
+ TableHeadLine(
+ list = listOf(
+ "茧别" to 1, "磅数" to 1, "包数" to 1,
+ "毛重" to 1, "-皮重" to 3, "-扣重" to 3, "=净重" to 2
+ ),
+ modifier = M.fillMaxWidth()
+ )
+ val list = info.data.chengZhongItemSumList
+ LazyColumn(modifier = M.weight(1f)) {
+ itemsIndexed(list) { index, item ->
+ var pweightT by rememberSaveable { mutableStateOf(item.pweightSum.toString()) }
+ var kweightT by rememberSaveable { mutableStateOf(item.kweightSum.toString()) }
+ val jweightSum = item.mweightSum - (kweightT.toDoubleOrNull() ?: 0.0) - (pweightT.toDoubleOrNull() ?: 0.0)
+ TareTableContent(
+ index % 2 == 0,
+ info.canTare,
+ item.sgTypeName,
+ item.weightCount.toString(),
+ item.boxCount.toString(),
+ item.mweightSum.toString(),
+ pweightT,
+ kweightT,
+ jweightSum.toString()
+ ){ pweight, kweight ->
+ pweightT = pweight
+ kweightT = kweight
+ val re = request.first { it.sysid == item.sysid }
+ re.pweight = pweight.toDoubleOrNull() ?: 0.0
+ re.kweight = kweight.toDoubleOrNull() ?: 0.0
+ re.jweight = jweightSum
+ }
+ }
+ }
+ SaveTableTotal(
+ listOf(
+ "汇总" to 1f,
+ list.sumOf { it.weightCount }.toString() to 1f,
+ list.sumOf { it.boxCount }.toString() to 1f,
+ MyUtil.formatDouble(list.sumOf { it.mweightSum }).toString() to 1f,
+ MyUtil.formatDouble(request.sumOf { it.pweight }).toString() to 3f,
+ MyUtil.formatDouble(request.sumOf { it.kweight }).toString() to 3f,
+ MyUtil.formatDouble(request.sumOf { it.jweight }).toString() to 1f
+ )
+ )
+ Row(
+ modifier = M.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ MyCheckBox("打印称重凭据")
+ VipBadge {
+ MyCheckBox("发送短信至农户")
+ }
+ Text(modifier = M.padding(start = 15.dp), text = "打印机在线")
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun TareTableContent(
+ isDeepBg: Boolean,
+ canTare: Boolean,
+ sgTypeName: String,
+ weightCount: String,
+ boxCount: String,
+ mweightSum: String,
+ pweightSum: String,
+ kweightSum: String,
+ jweightSum: String,
+ onValueChanged: (pweight: String,kweight: String) -> Unit = { _, _ -> }
+) {
+ Card(
+ colors = CardDefaults.cardColors(
+ containerColor = if (isDeepBg) Color(0xFFF3F7FD) else Color.White
+ )
+ ) {
+ Row(
+ modifier = M.padding(vertical = 4.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = sgTypeName,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.weight(1f),
+ textAlign = TextAlign.Center,
+ )
+ Text(
+ text = weightCount,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.weight(1f),
+ textAlign = TextAlign.Center,
+ )
+ Text(
+ text = boxCount,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.weight(1f),
+ textAlign = TextAlign.Center,
+ )
+ Text(
+ text = mweightSum,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.weight(1f),
+ textAlign = TextAlign.Center,
+ )
+ if (canTare) {
+ MyTextField(
+ value = pweightSum,
+ onValueChange = {
+ onValueChanged(it, kweightSum)
+ },
+ isNumberInputType = true,
+ modifier = M
+ .weight(3f)
+ .padding(horizontal = 10.dp),
+ trailing = {
+ if (pweightSum.isNotEmpty()) {
+ Icon(
+ Icons.Default.Close,
+ contentDescription = null,
+ Modifier
+ .size(InputChipDefaults.AvatarSize)
+ .clickable {
+ onValueChanged("", kweightSum)
+ }
+ )
+ }
+ },
+ )
+ } else {
+ Text(
+ text = pweightSum,
+ modifier = M.weight(3f),
+ textAlign = TextAlign.Center,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ )
+ }
+ MyTextField(
+ value = kweightSum,
+ onValueChange = {
+ onValueChanged(pweightSum, it)
+ },
+ isNumberInputType = true,
+ modifier = M
+ .weight(3f)
+ .padding(horizontal = 10.dp),
+ // 末尾添加删除按钮
+ trailing = {
+ if (kweightSum.isNotEmpty()) {
+ Icon(
+ Icons.Default.Close,
+ contentDescription = null,
+ Modifier
+ .size(InputChipDefaults.AvatarSize)
+ .clickable {
+ onValueChanged(pweightSum, "")
+ }
+ )
+ }
+ },
+ )
+ Text(
+ text = jweightSum,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.weight(2f),
+ textAlign = TextAlign.Center,
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/TicketMoreDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/TicketMoreDialog.kt
new file mode 100644
index 0000000..be4f003
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/TicketMoreDialog.kt
@@ -0,0 +1,271 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import android.content.res.Configuration
+import androidx.compose.animation.slideInHorizontally
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutHorizontally
+import androidx.compose.animation.slideOutVertically
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.MyAnimatedVisibility
+import com.bbitcn.f8.pad.base.MyAnyTable
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.base.MyRefreshTable
+import com.bbitcn.f8.pad.base.MyTableData
+import com.bbitcn.f8.pad.base.TableContent
+import com.bbitcn.f8.pad.base.TableHeadLine
+import com.bbitcn.f8.pad.base.VipBadge
+import com.bbitcn.f8.pad.model.net.request.CocoonTypeTranslateRequest
+import com.bbitcn.f8.pad.model.net.response.PurchaseDataResponse
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.utils.MyUtil
+import kotlin.collections.map
+
+data class TicketMoreDialogData(
+ val showDialog: Boolean = false,
+ val onDismiss: () -> Unit = {},
+
+ // 收购、款项、统计(不需要)
+ val ticketId: String = "",
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun TicketMorePreview() {
+ TicketMoreDialog(
+ TicketMoreDialogData(showDialog = true)
+ )
+}
+
+@Composable
+fun TicketMoreDialog(
+ baseInfo: TicketMoreDialogData
+) {
+ var isTranslated by rememberSaveable { mutableStateOf(false) }
+ // 横竖屏
+ val configuration = LocalConfiguration.current
+ val columns =
+ if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
+ 2 // 竖屏显示两列
+ } else {
+ 4 // 横屏显示四列
+ }
+ MyDialog(
+ "更多操作",
+ baseInfo.showDialog,
+ onDismissRequest = {
+ if (isTranslated) {
+ isTranslated = !isTranslated
+ } else {
+ baseInfo.onDismiss()
+ }
+ }
+ ) {
+ val viewModel: TicketMoreDialogViewModel = viewModel()
+
+ LaunchedEffect(baseInfo.showDialog) {
+ if (baseInfo.showDialog) {
+ viewModel.getDetail(baseInfo.ticketId)
+ }
+ }
+ val info by viewModel.info.collectAsState()
+ Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
+ Row(
+ modifier = M
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Text(
+ text = "农户:${info.nhName}",
+ fontSize = MaterialTheme.typography.headlineMedium.fontSize
+ )
+ Text(
+ text = "茧票:${info.billCode}",
+ fontSize = MaterialTheme.typography.headlineMedium.fontSize
+ )
+ }
+ MyAnimatedVisibility(
+ isTranslated,
+ enter = slideInVertically(
+ initialOffsetY = { fullHeight -> fullHeight }),
+ exit = slideOutVertically(
+ targetOffsetY = { fullHeight -> fullHeight })
+ ) {
+ CocoonTypeTranslate(info, viewModel = viewModel)
+ }
+ MyAnimatedVisibility(
+ !isTranslated,
+ enter = slideInVertically(
+ initialOffsetY = { fullHeight -> -fullHeight }),
+ exit = slideOutVertically(
+ targetOffsetY = { fullHeight -> -fullHeight })
+ ) {
+ LazyVerticalGrid(modifier = M.fillMaxSize(), columns = GridCells.Fixed(columns)) {
+ item {
+ MyImageButton("弃售", R.drawable.ic_ticket_delete) {
+ Toasty.showConfirmDialog("确定弃售茧票吗?") {
+ viewModel.abandonTicket(info.czSysid)
+ }
+ }
+ }
+ item {
+ MyImageButton("恢复状态至\n未扣皮未定价", R.drawable.ic_ticket_price) {
+ Toasty.showConfirmDialog("确定恢复单据状态至未扣皮未定价吗?") {
+ viewModel.recoverTicketToUnPricing(info.czSysid) {
+ baseInfo.onDismiss()
+ }
+ }
+ }
+ }
+ item {
+ MyImageButton("茧票作废", R.drawable.ic_ticket_delete) {
+ Toasty.showConfirmDialog("确定作废茧票吗?") {
+ viewModel.deleteTicket(info.czSysid) {
+ baseInfo.onDismiss()
+ }
+ }
+ }
+ }
+ item {
+ MyImageButton("茧别转换", R.drawable.ic_ticket_convert) {
+ isTranslated = !isTranslated
+ }
+ }
+ item {
+ MyImageButton("茧票过户", R.drawable.ic_ticket_transfer, true) {
+
+ }
+ }
+ item {
+ MyImageButton("撤销支付", R.drawable.ic_ticket_cancel, true) {
+ Toasty.showConfirmDialog("确定撤销支付吗?") {
+ viewModel.unPay(info.czSysid) {
+ baseInfo.onDismiss()
+ }
+ }
+ }
+ }
+ item {
+ MyImageButton("上传图片", R.drawable.ic_upload_pic, true) {
+
+ }
+ }
+ item {
+ MyImageButton("隔日作废", R.drawable.ic_ticket_delete_next_day, true) {
+
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * 茧别转换
+ */
+@Composable
+fun CocoonTypeTranslate(
+ info: PurchaseDataResponse.Data,
+ viewModel: TicketMoreDialogViewModel
+) {
+ val kindsInfo by viewModel.kindsInfo.collectAsState()
+ val items: List = info.chengZhongItemSumList
+ MyAnyTable(
+ modifier = M.fillMaxSize(),
+ info = items,
+ items = listOf(
+ MyTableData(1, isIndex = true),
+ MyTableData("茧别", 2, { it.sgTypeName }),
+ MyTableData("称重数", 2, { it.weightCount.toString() }),
+ MyTableData("包装", 2, { it.boxName }),
+ MyTableData("包数", 2, { it.boxCount.toString() }),
+ MyTableData("毛重", 2, { it.mweightSum.toString() }),
+ MyTableData("皮重", 2, { it.pweightSum.toString() }),
+ MyTableData("扣重", 2, { it.kweightSum.toString() }),
+ MyTableData("净重", 2, { it.jweightSum.toString() }),
+ MyTableData("单价", 2, { it.price.toString() }),
+ MyTableData("总额", 2, { it.moneySum.toString() }),
+ MyTableData("操作", 2, { "转换" }, true) {
+ Toasty.showOptionDrawer(
+ title = "将${it.sgTypeName}转换为",
+ options = kindsInfo.map { it.name }
+ ) { newTypeName ->
+ val new = kindsInfo.find { it.name == newTypeName } ?: return@showOptionDrawer
+ viewModel.translateCocoonType(
+ CocoonTypeTranslateRequest(
+ czsysid = info.czSysid,
+ oldname = it.sgTypeName,
+ oldsysid = it.sgTypeSysid,
+ newname = new.name,
+ newsysid = new.sysid
+ )
+ )
+ }
+ }
+ )
+ )
+}
+
+@Composable
+fun MyImageButton(text: String, resId: Int, vip: Boolean = false, onClick: () -> Unit) {
+ Column(
+ modifier = M
+ .fillMaxWidth()
+ .padding(10.dp)
+ .clickable { onClick() },
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ if (vip) {
+ VipBadge {
+ Image(
+ painter = painterResource(id = resId),
+ contentDescription = text,
+ modifier = M.size(90.dp)
+ )
+ }
+ } else {
+ Image(
+ painter = painterResource(id = resId),
+ contentDescription = text,
+ modifier = M.size(90.dp)
+ )
+ }
+ Text(
+ text = text,
+ fontSize = MaterialTheme.typography.headlineMedium.fontSize,
+ textAlign = TextAlign.Center
+ )
+ }
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/TicketMoreDialogViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/TicketMoreDialogViewModel.kt
new file mode 100644
index 0000000..40b3cf4
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/TicketMoreDialogViewModel.kt
@@ -0,0 +1,112 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.model.net.request.CocoonTypeTranslateRequest
+import com.bbitcn.f8.pad.model.net.response.PurchaseDataResponse
+import com.bbitcn.f8.pad.model.net.response.PurchaseDetailListResponse
+import com.bbitcn.f8.pad.model.net.response.WeightKindsResponse
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class TicketMoreDialogViewModel : BaseViewModel() {
+
+ private val _info = MutableStateFlow(PurchaseDataResponse.Data())
+ val info = _info.asStateFlow()
+
+ private val _kindsInfo = MutableStateFlow>(emptyList())
+ val kindsInfo = _kindsInfo.asStateFlow()
+
+ init {
+ doInIoThread {
+ getCocoonKinds()
+ }
+ }
+
+ fun getDetail(czSysId: String) {
+ doInIoThread {
+ val res = apiService.getPurchaseDetail(czSysId)
+ if (res.code == 0) {
+ Toasty.showTipsDialog(res.msg)
+ } else {
+ _info.value = res.data
+ }
+ }
+ }
+
+ /**
+ * 茧别信息
+ */
+ suspend fun getCocoonKinds() {
+ val kindsInfo = apiService.getCocoonKinds()
+ if (kindsInfo.code != 1) {
+ Toasty.showTipsDialog(kindsInfo.msg)
+ } else {
+ // 茧别信息
+ _kindsInfo.value = kindsInfo.data
+ }
+ }
+
+ /**
+ * 删除茧票
+ */
+ fun deleteTicket(ticketId: String, onSuccess: () -> Unit) {
+ doInIoThread {
+ val result = apiService.deleteTicket(ticketId, "更多操作", false) // todo 传入参数需要修改
+ if (result.code == 1) {
+ Toasty.success("删除成功")
+ onSuccess()
+ } else {
+ Toasty.showTipsDialog(result.msg)
+ }
+ }
+ }
+
+ fun abandonTicket(czSysId: String) {
+ doInIoThread {
+ val result = apiService.abandonTicket(czSysId)
+ if (result.code == 1) {
+ Toasty.success("此单弃售成功,请刷新单据查看")
+ } else {
+ Toasty.showTipsDialog(result.msg)
+ }
+ }
+ }
+
+ fun recoverTicketToUnPricing(ticketId: String, onSuccess: () -> Unit) {
+ doInIoThread {
+ val result = apiService.recoverTicketToUnPricing(ticketId)
+ if (result.code == 1) {
+ Toasty.success("恢复成功")
+ onSuccess()
+ } else {
+ Toasty.showTipsDialog(result.msg)
+ }
+ }
+ }
+
+ fun translateCocoonType(info: CocoonTypeTranslateRequest) {
+ doInIoThread("正在转换中") {
+ val result = apiService.cocoonTypeTranslate(info)
+ if (result.code == 1) {
+ Toasty.success("转换成功")
+ // 成功后刷新详情
+ getDetail(info.czsysid)
+ } else {
+ Toasty.showTipsDialog(result.msg)
+ }
+ }
+ }
+
+ fun unPay(sysId: String,onSuccess: () -> Unit) {
+ doInIoThread {
+ val response = apiService.getUnPay(sysid = sysId, remark = "Pad端操作")
+ if (response.code == 1) {
+ Toasty.success("操作成功,请刷新后查看")
+ onSuccess()
+ } else {
+ Toasty.showTipsDialog(response.msg)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/TipsDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/TipsDialog.kt
new file mode 100644
index 0000000..7b8a84a
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/TipsDialog.kt
@@ -0,0 +1,103 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.material3.Card
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.R
+
+/**
+ *
+ * @Description TODO
+ * @Author DuanKaiji
+ * @CreateTime 2024年04月29日 09:09:42
+ */
+data class TipsDialogData(
+ var showDialog: Boolean = false,
+ var content: String = "",
+)
+
+@Preview(showBackground = true)
+@Composable
+fun TipsDialogPreview() {
+ TipsDialog(
+ content = "这是一个提示框",
+ showDialog = true,
+ onDismiss = {}
+ )
+}
+
+@Composable
+fun TipsDialog(
+ content: String,
+ showDialog: Boolean,
+ onDismiss: () -> Unit
+) {
+ if (showDialog) {
+ Box(
+ modifier = M
+ .fillMaxSize()
+ .clickable { onDismiss() }
+ .background(Color(0x99000000)),
+ contentAlignment = Alignment.Center
+ ) {
+// Dialog(onDismissRequest = onDismiss) {
+ // 使用 Column 布局来自定义弹窗内容
+ Column {
+ Card(modifier = M.padding(bottom = 12.dp)) {
+ Column(
+ modifier = M
+ .wrapContentWidth()
+ .background(Color.White)
+ .padding(20.dp)
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.tips),
+ contentDescription = "Tips",
+ modifier = M
+ .padding(bottom = 18.dp)
+ .wrapContentWidth()
+ .align(Alignment.CenterHorizontally)
+ )
+ Text(
+ text = content,
+ textAlign = TextAlign.Center,
+ modifier = M
+ .padding(vertical = 10.dp)
+ .widthIn(200.dp)
+ )
+ }
+ }
+ Image(
+ painter = painterResource(id = R.drawable.dismiss),
+ contentDescription = "Close",
+ contentScale = ContentScale.Crop,
+ modifier = M
+ .size(30.dp)
+ .wrapContentWidth()
+ .clickable { onDismiss() }
+ .align(Alignment.CenterHorizontally)
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/WaterCutRecordDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/WaterCutRecordDialog.kt
new file mode 100644
index 0000000..4676d2a
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/WaterCutRecordDialog.kt
@@ -0,0 +1,132 @@
+package com.bbitcn.f8.pad.ui.screen.dialog
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material3.Switch
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.base.MyTextField
+import com.bbitcn.f8.pad.base.TableContent
+import com.bbitcn.f8.pad.base.TableHeadLine
+import com.bbitcn.f8.pad.ui.screen.secondFunc.WaterCutData
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.utils.externalModules.devices.water.WaterCutMeterBT
+import kotlinx.coroutines.flow.MutableStateFlow
+
+data class WaterCutRecordDialogData(
+ val showDialog: Boolean = false,
+ val onDismiss: () -> Unit = {},
+
+ var list: MutableStateFlow> = MutableStateFlow(emptyList()),
+ // 删除指定记录
+ val deleteRecordById: (Int) -> Unit = {},
+ // 使用手动记录
+ val useManualRecord: (Boolean, Double?) -> Unit = { _, _ -> }
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun WaterCutRecordDialogPreview() {
+ WaterCutRecordDialog(
+ WaterCutRecordDialogData(showDialog = true)
+ )
+}
+
+@Composable
+fun WaterCutRecordDialog(info: WaterCutRecordDialogData) {
+ var inputByBT by rememberSaveable { mutableStateOf(true) }
+ var inputValue by rememberSaveable { mutableStateOf("") }
+ MyDialog("含水仪记录",
+ info.showDialog,
+ onDismissRequest = { info.onDismiss() }
+ ) {
+ val list by info.list.collectAsState()
+ val ratio = listOf(1f, 2f, 2f)
+ Column(modifier = M.fillMaxSize()) {
+ Row(
+ modifier = M.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text("使用手动输入")
+ Switch(
+ modifier = M.padding(horizontal = 5.dp),
+ checked = !inputByBT,
+ onCheckedChange = {
+ inputByBT = !it
+ info.useManualRecord(inputByBT, inputValue.toDoubleOrNull())
+ }
+ )
+ Spacer(modifier = M.weight(1f))
+ if (inputByBT) {
+ val state by WaterCutMeterBT.state.collectAsState()
+ val isConnected = state == 1
+ Text("含水仪状态:")
+ Text(text = if (isConnected) "已连接" else "未连接")
+ } else {
+ Text("平均含水率:")
+ MyTextField(
+ modifier = M.width(100.dp),
+ value = inputValue,
+ isNumberInputType = true
+ ) {
+ val temp = it.toDoubleOrNull()
+ inputValue = if (temp != null) {
+ if (temp < 0) {
+ "0"
+ } else if (temp > 100) {
+ "100"
+ } else {
+ it
+ }
+ } else {
+ it
+ }
+ info.useManualRecord(inputByBT, inputValue.toDoubleOrNull())
+ }
+ Text("%")
+ }
+ }
+ if (inputByBT) {
+ TableHeadLine(
+ modifier = M.fillMaxWidth(),
+ list = listOf("次数" to 1, "含水率" to 2, "时间" to 2)
+ )
+ val items = list.map {
+ listOf(it.id, "" + it.waterCut, it.waterCutTime)
+ }
+ LazyColumn {
+ items(count = items.size) { it ->
+ TableContent(
+ modifier = M.fillMaxWidth().animateItem(),
+ backgroundDeepColor = it % 2 == 0,
+ list = items[it].map { it.toString() }.zip(ratio.map { it.toInt() }),
+ verticalPadding = 15.dp,
+ onLongClick = {
+ Toasty.showConfirmDialog("是否删除该记录?") {
+ info.deleteRecordById(it + 1)
+ }
+ }
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/AddDryCocoonTicketAirDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/AddDryCocoonTicketAirDialog.kt
new file mode 100644
index 0000000..ff41434
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/AddDryCocoonTicketAirDialog.kt
@@ -0,0 +1,188 @@
+package com.bbitcn.f8.pad.ui.screen.dialog.drycocoon
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.VerticalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.base.MyTextField
+import com.bbitcn.f8.pad.model.net.request.AddDryAirRequest
+import com.bbitcn.f8.pad.model.net.response.DryCocoonInType
+import com.bbitcn.f8.pad.model.net.response.QueryAllStoreInfoResponse
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.common.CombinedDropdownMenu
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+data class AddDryCocoonTicketAirDialogData(
+ val showDialog: Boolean = false,
+
+ val SSC: List = listOf(),// 仓库 蚕季 茧别 三级联动数据
+
+ var cocoonTypeList :List = listOf(), // 蚕品种
+ val areaList: List = listOf(),
+ // 入库包装类型
+ val onClickOK: (request: AddDryAirRequest) -> Unit = { _ -> },
+ val onDismiss: () -> Unit = {}
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun AddDryCocoonTicketAirDialogPreview() {
+ AddDryCocoonTicketAirDialog(AddDryCocoonTicketAirDialogData(showDialog = true))
+}
+
+@Composable
+fun AddDryCocoonTicketAirDialog(info: AddDryCocoonTicketAirDialogData) {
+ var request by remember { mutableStateOf(AddDryAirRequest()) }
+ MyDialog("新增摊晾计划",
+ info.showDialog,
+ onDismissRequest = { info.onDismiss() },
+ clickOKStr = "确定",
+ onClickOK = {
+ if (request.gjcksysid.isEmpty()
+ || request.cjsysid.isEmpty()
+ || request.jiantypesysid.isEmpty()
+ || request.plantime.isEmpty()
+ ) {
+ Toasty.showTipsDialog("请填写完整信息")
+ return@MyDialog
+ }
+ info.onClickOK(request)
+ info.onDismiss()
+ }
+ ) {
+ LaunchedEffect(Unit) {
+ request = AddDryAirRequest()
+ }
+ var season by remember { mutableStateOf("") }
+ var store by remember { mutableStateOf("") }
+ var cocoonLevel by remember { mutableStateOf("") }
+ var cocoonType by remember { mutableStateOf("") }
+ var seasonList by remember { mutableStateOf(listOf()) }
+ var cocoonLevelList by remember { mutableStateOf(listOf()) }
+ val scope = rememberCoroutineScope()
+
+ Row(modifier = M.fillMaxSize()) {
+ Column(
+ modifier = M.weight(1f),
+ verticalArrangement = Arrangement.spacedBy(15.dp)
+ ) {
+ CombinedDropdownMenu(
+ hint = "仓库",
+ options = info.SSC.map { it.ckname }.distinct(), // 仓库列表
+ value = store
+ ) { sel -> // 当选择仓库时触发
+ scope.launch {
+ withContext(Dispatchers.IO) {
+ store = sel
+ // 获取选择的仓库的gjcksysid和ckcode
+ val selectedStore = info.SSC.find { it.ckname == sel }
+ // 更新请求数据
+ request.gjcksysid = selectedStore?.gjcksysid ?: ""
+ request.gjckcode = selectedStore?.ckcode ?: ""
+
+ // 更新下级列表
+ season = ""
+ cocoonLevel = ""
+ seasonList = info.SSC.filter { it.gjcksysid == request.gjcksysid }
+ .map { it.cjname }
+ .distinct()
+ cocoonLevelList = emptyList()
+ }
+ }
+ }
+ CombinedDropdownMenu(
+ hint = "蚕季",
+ options = seasonList, // 动态生成的蚕季列表
+ value = season
+ ) { sel -> // 当选择蚕季时触发
+ scope.launch {
+ withContext(Dispatchers.IO) {
+ season = sel
+
+ // 获取选择了仓库和蚕季的数据
+ val selectedSeason =
+ info.SSC.find { it.cjname == sel && it.gjcksysid == request.gjcksysid }
+ request.cjsysid = selectedSeason?.cjsysid ?: ""
+
+ // 更新茧别列表
+ cocoonLevel = ""
+ cocoonLevelList =
+ info.SSC.filter {
+ it.gjcksysid == request.gjcksysid && // 筛选仓库
+ it.cjsysid == selectedSeason?.cjsysid // 筛选蚕季
+ }
+ .map { it.jiantypename }
+ .distinct()
+ }
+ }
+ }
+ CombinedDropdownMenu(
+ hint = "茧别",
+ options = cocoonLevelList, // 动态生成的茧别列表
+ value = cocoonLevel
+ ) { sel -> // 当选择茧别时触发
+ cocoonLevel = sel
+ val selectedCocoonLevel =
+ info.SSC.find { it.jiantypename == sel && it.gjcksysid == request.gjcksysid && it.cjsysid == request.cjsysid }
+ request.jiantypesysid = selectedCocoonLevel?.jiantypesysid ?: ""
+ request.jiantype = selectedCocoonLevel?.jiantypename ?: ""
+ }
+ CombinedDropdownMenu(
+ hint = "蚕品种",
+ options = info.cocoonTypeList.map { it.name },
+ value = cocoonType
+ ) { sel -> // 当选择茧别时触发
+ cocoonType = sel
+ request.canpinzhong = sel
+ }
+ }
+ VerticalDivider(
+ modifier = M
+ .fillMaxHeight()
+ .padding(horizontal = 10.dp),
+ )
+ Column(
+ modifier = M.weight(1f),
+ verticalArrangement = Arrangement.spacedBy(15.dp)
+ ) {
+ var operater by remember { mutableStateOf("") }
+ var time by remember { mutableStateOf("") }
+ var area by remember { mutableStateOf("") }
+ CombinedDropdownMenu(
+ hint = "区域",
+ options = info.areaList.map { it },
+ value = area
+ ) { sel ->
+ area = sel
+ request.xiangzhen = sel
+ }
+ MyTextField(hint = "摊晾人", value = operater) {
+ operater = it
+ request.tanliangren = it
+ }
+ MyTextField(hint = "计划摊晾时间", value = time, isSelectDate = true,) {
+ time = it
+ request.plantime = it
+ }
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/AddDryCocoonTicketInDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/AddDryCocoonTicketInDialog.kt
new file mode 100644
index 0000000..52c4a08
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/AddDryCocoonTicketInDialog.kt
@@ -0,0 +1,206 @@
+package com.bbitcn.f8.pad.ui.screen.dialog.drycocoon
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.VerticalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MyCheckBox
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.base.MyTextField
+import com.bbitcn.f8.pad.model.net.request.AddDryInRequest
+import com.bbitcn.f8.pad.model.net.response.DryCocoonAreaResponse
+import com.bbitcn.f8.pad.model.net.response.DryCocoonInLevel
+import com.bbitcn.f8.pad.model.net.response.DryCocoonInPackageType
+import com.bbitcn.f8.pad.model.net.response.DryCocoonSeason
+import com.bbitcn.f8.pad.model.net.response.DryCocoonInStore
+import com.bbitcn.f8.pad.model.net.response.DryCocoonInType
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.common.CombinedDropdownMenu
+
+
+data class AddDryCocoonTicketInDialogData(
+ val showDialog: Boolean = false,
+
+ val defaultStoreName: String = "",
+
+ val season: List = listOf(),
+ val store: List = listOf(),
+ val cocoonType: List = listOf(),
+ val cocoonLevel: List = listOf(),
+ val packageType: List = listOf(),
+
+ val areaList: List = listOf(),
+
+ // 入库包装类型
+ val onClickOK: (request: AddDryInRequest, cocoonLevel: String, store: String, cocoonType: String) -> Unit = { _, _, _, _ -> },
+ val onDismiss: () -> Unit = {}
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun AddDryCocoonTicketDialogPreview() {
+ AddDryCocoonTicketInDialog(AddDryCocoonTicketInDialogData(showDialog = true))
+}
+
+@Composable
+fun AddDryCocoonTicketInDialog(info: AddDryCocoonTicketInDialogData) {
+ var request by remember { mutableStateOf(AddDryInRequest()) }
+ var cocoonType by remember { mutableStateOf("") }
+ var cocoonLevel by remember { mutableStateOf("") }
+ var store by remember { mutableStateOf("") }
+ var season by remember { mutableStateOf("") }
+ MyDialog(
+ "新增入库单据",
+ info.showDialog,
+ onDismissRequest = { info.onDismiss() },
+ clickOKStr = "确定",
+ onClickOK = {
+ if (cocoonLevel.isEmpty()
+ || store.isEmpty()
+ || request.bagtype.isEmpty()
+ || request.rukutype == -1
+ || request.canpinzhong.isEmpty()
+ ) {
+ Toasty.showTipsDialog("请填写完整信息")
+ return@MyDialog
+ }
+ info.onClickOK(request, cocoonLevel, store, cocoonType)
+ info.onDismiss()
+ }
+ ) {
+ LaunchedEffect(Unit) {
+ request = AddDryInRequest()
+ cocoonLevel = ""
+
+ // 默认仓库
+ val sel = info.store.find { it.cangkuname == info.defaultStoreName }
+ request.gjcksysid = sel?.sysid ?: ""
+ request.gjckcode = sel?.cangkucode ?: ""
+ store = info.defaultStoreName
+
+ // 默认蚕季
+ val defSeason = info.season.firstOrNull { it.def == 1 }
+ season = defSeason?.batchname ?: ""
+ request.cjsysid = defSeason?.sysid ?: ""
+ cocoonType = ""
+ }
+ var packageType by remember { mutableStateOf("") }
+ var inType by remember { mutableStateOf("") }
+ var isStandard by remember { mutableStateOf(true) }
+ var operatorName by remember { mutableStateOf("") }
+ var dryingPersonName by remember { mutableStateOf("") }
+ var area by remember { mutableStateOf("") }
+ Row(modifier = M.fillMaxSize()) {
+ Column(
+ modifier = M.weight(1f),
+ verticalArrangement = Arrangement.spacedBy(15.dp)
+ ) {
+ CombinedDropdownMenu(
+ hint = "仓库",
+ options = info.store.map { it.cangkuname },
+ value = store
+ ) { sel ->
+ store = sel
+ request.gjcksysid = info.store.find { it.cangkuname == sel }?.sysid ?: ""
+ request.gjckcode = info.store.find { it.cangkuname == sel }?.cangkucode ?: ""
+ }
+ CombinedDropdownMenu(
+ hint = "蚕季",
+ options = info.season.map { it.batchname },
+ value = season
+ ) { sel ->
+ season = sel
+ request.cjsysid = info.season.find { it.batchname == sel }?.sysid ?: ""
+ }
+ CombinedDropdownMenu(
+ hint = "茧别",
+ options = info.cocoonLevel.map { it.name },
+ value = cocoonLevel
+ ) { sel ->
+ cocoonLevel = sel
+ val selCocoonLevel = info.cocoonLevel.find { it.name == sel }
+ request.jiantypesysid = selCocoonLevel?.sysid ?: ""
+ request.jiantype = selCocoonLevel?.name ?: ""
+ }
+ CombinedDropdownMenu(
+ hint = "包装",
+ options = info.packageType.map { it.name },
+ value = packageType
+ ) { sel ->
+ packageType = sel
+ request.bagtype = info.packageType.find { it.name == sel }?.name ?: ""
+ request.bagzhongliang = info.packageType.find { it.name == sel }?.weight ?: 0.0
+ }
+ CombinedDropdownMenu(
+ hint = "入库类型",
+ options = listOf("烘茧入库", "翻包摊晾", "出库盈余"),
+ value = inType
+ ) { sel ->
+ inType = sel
+ request.rukutype = when (sel) {
+ "烘茧入库" -> 0
+ "翻包摊晾" -> 1
+ "出库盈余" -> 2
+ else -> 0
+ }
+ }
+ MyCheckBox("采用标准包", isStandard) {
+ isStandard = it
+ request.standardtype = if (isStandard) 1 else -1
+ }
+ }
+ VerticalDivider(
+ modifier = M
+ .fillMaxHeight()
+ .padding(horizontal = 10.dp),
+ )
+ Column(
+ modifier = M.weight(1f),
+ verticalArrangement = Arrangement.spacedBy(15.dp)
+ ) {
+ CombinedDropdownMenu(
+ hint = "蚕品种",
+ options = info.cocoonType.map { it.name },
+ value = cocoonType
+ ) { sel ->
+ cocoonType = sel
+ request.canpinzhong = sel
+ }
+ CombinedDropdownMenu(
+ hint = "区域",
+ options = info.areaList.map { it },
+ value = area
+ ) { sel ->
+ area = sel
+ request.xiangzhen = sel
+ }
+ MyTextField(hint = "烘茧人", value = dryingPersonName) {
+ dryingPersonName = it
+ request.hongjianren = it
+ }
+ CombinedDropdownMenu(
+ hint = "审批人",
+ options = listOf(),
+ isEditable = true,
+ value = operatorName
+ ) { input ->
+ operatorName = input
+ request.rukuren = input
+ }
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/AddDryCocoonTicketOutDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/AddDryCocoonTicketOutDialog.kt
new file mode 100644
index 0000000..7badd2a
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/AddDryCocoonTicketOutDialog.kt
@@ -0,0 +1,228 @@
+package com.bbitcn.f8.pad.ui.screen.dialog.drycocoon
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.VerticalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.base.MyTextField
+import com.bbitcn.f8.pad.model.net.request.AddDryOutRequest
+import com.bbitcn.f8.pad.model.net.response.DryCocoonAreaResponse
+import com.bbitcn.f8.pad.model.net.response.DryCocoonDealObjectResponse
+import com.bbitcn.f8.pad.model.net.response.DryCocoonInPackageType
+import com.bbitcn.f8.pad.model.net.response.DryCocoonInType
+import com.bbitcn.f8.pad.model.net.response.QueryAllStoreInfoResponse
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.common.CombinedDropdownMenu
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+data class AddDryCocoonTicketOutDialogData(
+ val showDialog: Boolean = false,
+
+ val SSC: List = listOf(),// 仓库 蚕季 茧别 三级联动数据
+
+ val packageType: List = listOf(),
+ val dealObject: List = listOf(),
+
+ var cocoonTypeList: List = listOf(), // 蚕品种
+ var areaList: List = listOf(), // 区域
+
+ // 入库包装类型
+ val onClickOK: (request: AddDryOutRequest) -> Unit = { _ -> },
+ val onDismiss: () -> Unit = {}
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun AddDryCocoonTicketOutDialogPreview() {
+ AddDryCocoonTicketOutDialog(AddDryCocoonTicketOutDialogData(showDialog = true))
+}
+
+@Composable
+fun AddDryCocoonTicketOutDialog(info: AddDryCocoonTicketOutDialogData) {
+ var request by remember { mutableStateOf(AddDryOutRequest()) }
+ MyDialog(
+ "新增出库单据",
+ info.showDialog,
+ onDismissRequest = { info.onDismiss() },
+ clickOKStr = "确定",
+ onClickOK = {
+ if (request.gjcksysid.isEmpty()
+ || request.cjsysid.isEmpty()
+ || request.jiantypesysid.isEmpty()
+ || request.wldwsysid.isEmpty()
+ || request.bagtype.isEmpty()
+ ) {
+ Toasty.showTipsDialog("请填写完整信息")
+ return@MyDialog
+ }
+ info.onClickOK(request)
+ info.onDismiss()
+ }
+ ) {
+ LaunchedEffect(Unit) {
+ request = AddDryOutRequest()
+ }
+ var season by remember { mutableStateOf("") }
+ var store by remember { mutableStateOf("") }
+ var cocoonLevel by remember { mutableStateOf("") }
+ var packageType by remember { mutableStateOf("") }
+ var cocoonType by remember { mutableStateOf("") }
+ var area by remember { mutableStateOf("") }
+ var dealObject by remember { mutableStateOf("") }
+
+ var seasonList by remember { mutableStateOf(listOf()) }
+ var cocoonLevelList by remember { mutableStateOf(listOf()) }
+
+ val scope = rememberCoroutineScope()
+
+ Row(modifier = M.fillMaxSize()) {
+ Column(
+ modifier = M.weight(1f),
+ verticalArrangement = Arrangement.spacedBy(15.dp)
+ ) {
+ CombinedDropdownMenu(
+ hint = "仓库",
+ options = info.SSC.map { it.ckname }.distinct(), // 仓库列表
+ value = store
+ ) { sel -> // 当选择仓库时触发
+ scope.launch {
+ withContext(Dispatchers.IO) {
+ store = sel
+ // 获取选择的仓库的gjcksysid和ckcode
+ val selectedStore = info.SSC.find { it.ckname == sel }
+ // 更新请求数据
+ request.gjcksysid = selectedStore?.gjcksysid ?: ""
+ request.gjckcode = selectedStore?.ckcode ?: ""
+
+ // 更新下级列表
+ season = ""
+ cocoonLevel = ""
+ seasonList = info.SSC.filter { it.gjcksysid == request.gjcksysid }
+ .map { it.cjname }
+ .distinct()
+ cocoonLevelList = emptyList()
+ }
+ }
+ }
+ CombinedDropdownMenu(
+ hint = "蚕季",
+ options = seasonList, // 动态生成的蚕季列表
+ value = season
+ ) { sel -> // 当选择蚕季时触发
+ scope.launch {
+ withContext(Dispatchers.IO) {
+ season = sel
+
+ // 获取选择了仓库和蚕季的数据
+ val selectedSeason =
+ info.SSC.find { it.cjname == sel && it.gjcksysid == request.gjcksysid }
+ request.cjsysid = selectedSeason?.cjsysid ?: ""
+
+ // 更新茧别列表
+ cocoonLevel = ""
+ cocoonLevelList =
+ info.SSC.filter {
+ it.gjcksysid == request.gjcksysid && // 筛选仓库
+ it.cjsysid == selectedSeason?.cjsysid // 筛选蚕季
+ }
+ .map { it.jiantypename }
+ .distinct()
+ }
+ }
+ }
+ CombinedDropdownMenu(
+ hint = "茧别",
+ options = cocoonLevelList, // 动态生成的茧别列表
+ value = cocoonLevel
+ ) { sel -> // 当选择茧别时触发
+ cocoonLevel = sel
+ val selectedCocoonLevel =
+ info.SSC.find { it.jiantypename == sel && it.gjcksysid == request.gjcksysid && it.cjsysid == request.cjsysid }
+ request.jiantypesysid = selectedCocoonLevel?.jiantypesysid ?: ""
+ request.jiantype = selectedCocoonLevel?.jiantypename ?: ""
+ }
+ CombinedDropdownMenu(
+ hint = "蚕品种",
+ options = info.cocoonTypeList.map { it.name },
+ value = cocoonType
+ ) { sel ->
+ cocoonType = sel
+ request.canpinzhong = sel
+ }
+ CombinedDropdownMenu(
+ hint = "区域",
+ options = info.areaList.map { it },
+ value = area
+ ) { sel ->
+ area = sel
+ request.xiangzhen = sel
+ }
+ }
+ VerticalDivider(
+ modifier = M
+ .fillMaxHeight()
+ .padding(horizontal = 10.dp),
+ )
+ Column(
+ modifier = M.weight(1f),
+ verticalArrangement = Arrangement.spacedBy(15.dp)
+ ) {
+ CombinedDropdownMenu(
+ hint = "往来单位",
+ options = info.dealObject.map { it.name },
+ value = dealObject
+ ) { sel ->
+ dealObject = sel
+ request.wldwsysid = info.dealObject.find { it.name == sel }?.sysid ?: ""
+ }
+ CombinedDropdownMenu(
+ hint = "包装",
+ options = info.packageType.map { it.name },
+ value = packageType
+ ) { sel ->
+ packageType = sel
+ request.bagtype = info.packageType.find { it.name == sel }?.name ?: ""
+ request.bagzhongliang = info.packageType.find { it.name == sel }?.weight ?: 0.0
+ }
+ var pickUpPerson by remember { mutableStateOf("") }
+ var licensePlate by remember { mutableStateOf("") }
+ var shipper by remember { mutableStateOf("") }
+ var remark by remember { mutableStateOf("") }
+ MyTextField(hint = "车牌号", value = licensePlate) {
+ licensePlate = it
+ request.carpaihao = it
+ }
+ MyTextField(hint = "提货人", value = pickUpPerson) {
+ pickUpPerson = it
+ request.tihuoren = it
+ }
+ MyTextField(hint = "出库人", value = shipper) {
+ shipper = it
+ request.chukuren = it
+ }
+ MyTextField(hint = "备注", value = remark) {
+ remark = it
+ request.memo = it
+ }
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/DryCocoonFilterDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/DryCocoonFilterDialog.kt
new file mode 100644
index 0000000..f8d6ebc
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/DryCocoonFilterDialog.kt
@@ -0,0 +1,131 @@
+package com.bbitcn.f8.pad.ui.screen.dialog.drycocoon
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.VerticalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.ui.screen.secondFunc.AddDryCocoonBaseViewModel
+import com.bbitcn.f8.pad.ui.screen.view.deviceManager.reader.MyCardReaderShowViewModel
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.uhf.UHFReaderG06M_G25M
+import kotlinx.coroutines.flow.flowOf
+
+
+data class DryCocoonFilterDialogData(
+ val showDialog: Boolean = false,
+ val viewModel: AddDryCocoonBaseViewModel,
+ val myCardReaderShowViewModel: MyCardReaderShowViewModel,
+ val onDismiss: () -> Unit = {}
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun DryCocoonFilterDialogPreview() {
+ DryCocoonFilterDialog(DryCocoonFilterDialogData(showDialog = true, viewModel(), viewModel()))
+}
+
+@Composable
+fun DryCocoonFilterDialog(
+ info: DryCocoonFilterDialogData,
+) {
+ val curReader by info.myCardReaderShowViewModel.curDevice.collectAsState()
+ val tagIds by (curReader?.tagList
+ ?: flowOf(emptyList())).collectAsState(initial = emptyList())
+ MyDialog(
+ "茧包过滤",
+ info.showDialog,
+ onDismissRequest = {
+ info.onDismiss()
+ }, "添加到过滤列表", {
+ info.viewModel.confirmTagIds(tagIds) {
+ info.myCardReaderShowViewModel.clearList()
+ }
+ }
+ ) {
+ LaunchedEffect(Unit) {
+ info.myCardReaderShowViewModel.clearList()
+ }
+ Row {
+ LazyColumn(
+ modifier = M
+ .weight(1f)
+ .fillMaxHeight()
+ .padding(start = 10.dp),
+ ) {
+ item {
+ Row(
+ modifier = M
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text("当前茧包")
+ MyButton( text = "重新检测") {
+ info.myCardReaderShowViewModel.clearList()
+ }
+ }
+ HorizontalDivider(modifier = M.padding(vertical = 5.dp))
+ if (curReader == null) {
+ Text(
+ "未检测到读卡器",
+ color = MyColors.Red,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize
+ )
+ }
+ }
+ items(tagIds.toList()) {
+ PackageLossInfo(M.animateItem(), it)
+ }
+ }
+ VerticalDivider(modifier = M.padding(horizontal = 5.dp))
+ val forceItems by info.viewModel.forceFilterTags.collectAsState()
+ LazyColumn(
+ modifier = M
+ .weight(1f)
+ .fillMaxHeight()
+ .padding(end = 10.dp),
+ ) {
+ item {
+ Row(
+ modifier = M
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text("过滤列表")
+ MyButton(
+ text = "删除列表",
+ modifier = M.padding(end = 10.dp)
+ ) {
+ info.viewModel.clearForceHadHandleTagIds()
+ }
+ }
+ HorizontalDivider(modifier = M.padding(vertical = 5.dp))
+ }
+ items(forceItems.toList()) {
+ PackageLossInfo(M.animateItem(), it)
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/DryCocoonInfoQueryDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/DryCocoonInfoQueryDialog.kt
new file mode 100644
index 0000000..c491098
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/DryCocoonInfoQueryDialog.kt
@@ -0,0 +1,142 @@
+package com.bbitcn.f8.pad.ui.screen.dialog.drycocoon
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.InfoText
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.model.net.response.SearchOutDetailByRFIDResponse
+import com.bbitcn.f8.pad.ui.screen.view.deviceManager.reader.MyCardReaderShowViewModel
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import kotlinx.coroutines.flow.flowOf
+
+
+data class DryCocoonInfoQueryDialogData(
+ val showDialog: Boolean = false,
+ val cjsysid: String = "",
+ val jiantypesysid: String = "",
+ val gjcksysid: String = "",
+ val printTicket: (sysId: String) -> Unit = {},
+ val onDismiss: () -> Unit = {}
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun DryCocoonInfoQueryDialogPreview() {
+ DryCocoonInfoQueryDialog(DryCocoonInfoQueryDialogData(showDialog = true))
+}
+
+@Composable
+fun DryCocoonInfoQueryDialog(
+ info: DryCocoonInfoQueryDialogData,
+) {
+ MyDialog(
+ "查询茧包信息",
+ info.showDialog,
+ onDismissRequest = {
+ info.onDismiss()
+ }
+ ) {
+ val myCardReaderShowViewModel: MyCardReaderShowViewModel = viewModel()
+ val viewModel: DryCocoonInfoQueryViewModel = viewModel()
+
+ val curReader by myCardReaderShowViewModel.curDevice.collectAsState()
+ val tagIds by (curReader?.tagList
+ ?: flowOf(emptyList())).collectAsState(initial = emptyList())
+ val items by viewModel.list.collectAsState()
+ LaunchedEffect(Unit) {
+ myCardReaderShowViewModel.clearList()
+ viewModel.clearRfidInfo()
+ }
+ LaunchedEffect(tagIds) {
+ viewModel.refreshRfidInfo(tagIds, info)
+ }
+ LazyColumn(
+ modifier = M
+ .fillMaxSize()
+ .padding(10.dp),
+ ) {
+ item {
+ Row(
+ modifier = M
+ .padding(bottom = 5.dp)
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ if (curReader == null) {
+ Text(
+ "未检测到读卡器",
+ color = MyColors.Red,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize
+ )
+ } else {
+ Text("检测到的茧包芯片:")
+ MyButton(text = "重新检测茧包") {
+ viewModel.clearRfidInfo()
+ myCardReaderShowViewModel.clearList()
+ }
+ }
+ }
+ }
+ items(items) {
+ PackageLossInfoS(it) {
+ info.printTicket(it)
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun PackageLossInfoS(
+ info: Pair,
+ printTicket: (sysId: String) -> Unit,
+) {
+ MyCard(modifier = M.padding(10.dp)) {
+ Row(modifier = M.padding(5.dp), verticalAlignment = Alignment.CenterVertically) {
+ Column(
+ modifier = M
+ .weight(1f)
+ .padding(5.dp)
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ InfoText("茧包芯片", info.first, M.weight(2f), true)
+ InfoText("包码", info.second.code, M.weight(2f), true)
+ }
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ InfoText("毛重", info.second.maozhong.toString(), M.weight(1f), true)
+ InfoText("皮重", info.second.pizhong.toString(), M.weight(1f), true)
+ InfoText("净重", info.second.jingzhong.toString(), M.weight(1f), true)
+ InfoText(
+ "已出库",
+ if (info.second.ischuku == 1) "是" else "否",
+ M.weight(1f),
+ true
+ )
+ }
+ }
+ MyButton(text = "补打茧票") {
+ printTicket(info.second.sysid)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/DryCocoonInfoQueryViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/DryCocoonInfoQueryViewModel.kt
new file mode 100644
index 0000000..2b038a3
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/DryCocoonInfoQueryViewModel.kt
@@ -0,0 +1,48 @@
+package com.bbitcn.f8.pad.ui.screen.dialog.drycocoon
+
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.model.net.request.SearchOutDetailByRFIDRequest
+import com.bbitcn.f8.pad.model.net.response.SearchOutDetailByRFIDResponse
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+class DryCocoonInfoQueryViewModel : BaseViewModel() {
+
+ private val _list = MutableStateFlow>>(emptyList())
+ val list = _list.asStateFlow()
+
+ // 已经请求过的tagId
+ private val fetchedIds = mutableSetOf()
+
+ fun refreshRfidInfo(ids: List, info: DryCocoonInfoQueryDialogData) {
+ doInIoThreadNoDialog {
+ val newIds = ids.filterNot { fetchedIds.contains(it) }
+ newIds.forEach { id ->
+ fetchedIds.add(id)
+ val searchResponse = apiService.searchOutDetailByRFID(
+ SearchOutDetailByRFIDRequest(
+ rfid = id,
+ cjsysid = info.cjsysid,
+ jiantypesysid = info.jiantypesysid,
+ gjcksysid = info.gjcksysid
+ )
+ )
+ if (searchResponse.code != 1) {
+ Toasty.showTipsDialog(searchResponse.msg)
+ } else {
+ _list.update { it + (id to searchResponse.data) }
+ }
+ }
+ }
+ }
+
+ fun clearRfidInfo() {
+ doInIoThreadNoDialog {
+ fetchedIds.clear()
+ _list.value = emptyList()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/DryCocoonLossDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/DryCocoonLossDialog.kt
new file mode 100644
index 0000000..f41d612
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/DryCocoonLossDialog.kt
@@ -0,0 +1,174 @@
+package com.bbitcn.f8.pad.ui.screen.dialog.drycocoon
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.bbitcn.f8.pad.IS_DEBUG_DRYCOCOON
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.model.net.response.DryCocoonSeason
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.common.CombinedDropdownMenu
+import com.bbitcn.f8.pad.ui.screen.view.deviceManager.reader.MyCardReaderShowViewModel
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.uhf.UHFReaderG06M_G25M
+import com.bbitcn.f8.pad.utils.externalModules.manager.serial.uhfSerial.UHFReaderForSerial
+import kotlinx.coroutines.flow.flowOf
+
+
+data class DryCocoonLossDialogData(
+ val showDialog: Boolean = false,
+ val seasonList: List = listOf(),
+ val onLossPackage: (rfids: List, seasonSysId: String, onFinished: () -> Unit) -> Unit = { _, _, _ -> },
+ val onDismiss: () -> Unit = {}
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun DryCocoonLossDialogPreview() {
+ DryCocoonLossDialog(DryCocoonLossDialogData(showDialog = true))
+}
+
+@Composable
+fun DryCocoonLossDialog(
+ info: DryCocoonLossDialogData,
+ myCardReaderShowViewModel: MyCardReaderShowViewModel = viewModel(),
+) {
+ val curReader by myCardReaderShowViewModel.curDevice.collectAsState()
+ val tagIds by (curReader?.tagList
+ ?: flowOf(emptyList())).collectAsState(initial = emptyList())
+ var season by remember { mutableStateOf("") }
+ MyDialog(
+ "空包释放-出库",
+ info.showDialog,
+ onDismissRequest = {
+ info.onDismiss()
+ // 关闭弹窗后关闭超高频读卡器
+ UHFReaderForSerial.stopAllScan()
+ },
+ "全部释放", {
+ if (tagIds.isEmpty()) {
+ Toasty.showTipsDialog("未检测到茧包")
+ } else if (season.isEmpty()) {
+ Toasty.showTipsDialog("请先选择空包所属蚕季")
+ } else {
+ val seasonSysId = info.seasonList.first { it.batchname == season }.sysid
+ info.onLossPackage(tagIds, seasonSysId) {
+ myCardReaderShowViewModel.clearList()
+ }
+ }
+ }
+ ) {
+ LaunchedEffect(Unit) {
+ myCardReaderShowViewModel.clearList()
+ UHFReaderForSerial.startAllScan()
+ }
+ LazyColumn(
+ modifier = M
+ .fillMaxSize()
+ .padding(10.dp),
+ ) {
+ item {
+ Row(
+ modifier = M.padding(bottom = 10.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text("空包所属蚕季:")
+ CombinedDropdownMenu(
+ hint = "蚕季",
+ options = info.seasonList.map { it.batchname }, // 动态生成的蚕季列表
+ value = season
+ ) { sel -> // 当选择蚕季时触发
+ season = sel
+ }
+ }
+ if (IS_DEBUG_DRYCOCOON) {
+ Row(
+ modifier = M.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ MyButton(text = "增加随机芯片") {
+ UHFReaderG06M_G25M.testXP()
+ }
+ MyButton(text = "测试增加芯片1") {
+ UHFReaderG06M_G25M.testXP("521323232")
+ }
+ }
+ }
+ Row(
+ modifier = M
+ .padding(bottom = 5.dp)
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ if (curReader == null) {
+ Text(
+ "未检测到读卡器",
+ color = MyColors.Red,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize
+ )
+ } else {
+ Text("检测到的茧包芯片:")
+ MyButton(text = "重新检测茧包") {
+ myCardReaderShowViewModel.clearList()
+ }
+ }
+ }
+ }
+ items(tagIds, key = { it }) { tag ->
+ PackageLossInfo(M.animateItem(),tag)
+ }
+ }
+ }
+}
+
+@Composable
+fun PackageLossInfo(modifier: Modifier, rfid: String) {
+ MyCard(modifier = modifier.padding(10.dp)) {
+ Column(
+ modifier = M
+ .fillMaxWidth()
+ .padding(5.dp)
+ ) {
+ Row(
+ modifier = M
+ .padding(vertical = 3.dp)
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = "茧包芯片:",
+ color = MyColors.Gray,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize
+ )
+ Text(
+ modifier = M.padding(start = 5.dp),
+ text = rfid,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/DryCocoonLossDialogInOut.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/DryCocoonLossDialogInOut.kt
new file mode 100644
index 0000000..7ac5a45
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/DryCocoonLossDialogInOut.kt
@@ -0,0 +1,127 @@
+package com.bbitcn.f8.pad.ui.screen.dialog.drycocoon
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.bbitcn.f8.pad.IS_DEBUG_DRYCOCOON
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.MyDialog
+import com.bbitcn.f8.pad.model.net.response.DryCocoonSeason
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.common.CombinedDropdownMenu
+import com.bbitcn.f8.pad.ui.screen.view.deviceManager.reader.MyCardReaderShowViewModel
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.uhf.UHFReaderG06M_G25M
+import com.bbitcn.f8.pad.utils.externalModules.manager.serial.uhfSerial.UHFReaderForSerial
+import kotlinx.coroutines.flow.flowOf
+
+
+data class DryCocoonLossDialogInOutData(
+ val showDialog: Boolean = false,
+ val onLossPackage: (rfids: List, onFinished: () -> Unit) -> Unit = { _, _ -> },
+ val onDismiss: () -> Unit = {}
+)
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun DryCocoonLossDialogInOutPreview() {
+ DryCocoonLossDialogInOut(DryCocoonLossDialogInOutData(showDialog = true))
+}
+
+@Composable
+fun DryCocoonLossDialogInOut(
+ info: DryCocoonLossDialogInOutData,
+ myCardReaderShowViewModel: MyCardReaderShowViewModel = viewModel(),
+) {
+ val curReader by myCardReaderShowViewModel.curDevice.collectAsState()
+ val tagIds by (curReader?.tagList
+ ?: flowOf(emptyList())).collectAsState(initial = emptyList())
+ MyDialog(
+ "空包释放-出库",
+ info.showDialog,
+ onDismissRequest = {
+ info.onDismiss()
+ // 关闭弹窗后关闭超高频读卡器
+ UHFReaderForSerial.stopAllScan()
+ },
+ "全部释放", {
+ if (tagIds.isEmpty()) {
+ Toasty.showTipsDialog("未检测到茧包")
+ } else {
+ info.onLossPackage(tagIds) {
+ myCardReaderShowViewModel.clearList()
+ }
+ }
+ }
+ ) {
+ LaunchedEffect(Unit) {
+ myCardReaderShowViewModel.clearList()
+ UHFReaderForSerial.startAllScan()
+ }
+ LazyColumn(
+ modifier = M
+ .fillMaxSize()
+ .padding(10.dp),
+ ) {
+ item {
+ Row(
+ modifier = M
+ .padding(bottom = 5.dp)
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ if (curReader == null) {
+ Text(
+ "未检测到读卡器",
+ color = MyColors.Red,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize
+ )
+ } else {
+ Text("检测到的茧包芯片:")
+ MyButton(text = "重新检测茧包") {
+ myCardReaderShowViewModel.clearList()
+ }
+ }
+ }
+ if (IS_DEBUG_DRYCOCOON) {
+ Row(
+ modifier = M.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ MyButton(text = "增加随机芯片") {
+ UHFReaderG06M_G25M.testXP()
+ }
+ MyButton(text = "测试增加芯片1") {
+ UHFReaderG06M_G25M.testXP("521323232")
+ }
+ }
+ }
+ }
+ items(tagIds, key = { it }) { tag ->
+ PackageLossInfo(M.animateItem(), tag)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/DryCocoonRefreshDialog.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/DryCocoonRefreshDialog.kt
new file mode 100644
index 0000000..63b03be
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/dialog/drycocoon/DryCocoonRefreshDialog.kt
@@ -0,0 +1,186 @@
+//package com.bbitcn.f8.pad.ui.screen.dialog
+//
+//import androidx.compose.foundation.layout.Arrangement
+//import androidx.compose.foundation.layout.Column
+//import androidx.compose.foundation.layout.Row
+//import androidx.compose.foundation.layout.fillMaxSize
+//import androidx.compose.foundation.layout.fillMaxWidth
+//import androidx.compose.foundation.layout.padding
+//import androidx.compose.runtime.Composable
+//import androidx.compose.runtime.LaunchedEffect
+//import androidx.compose.runtime.collectAsState
+//import androidx.compose.runtime.getValue
+//import androidx.compose.runtime.mutableStateOf
+//import androidx.compose.runtime.saveable.rememberSaveable
+//import androidx.compose.runtime.setValue
+//import androidx.compose.ui.Alignment
+//import androidx.compose.ui.tooling.preview.Preview
+//import androidx.compose.ui.unit.dp
+//import androidx.lifecycle.viewmodel.compose.viewModel
+//import com.bbitcn.f8.pad.M
+//import com.bbitcn.f8.pad.base.InfoText
+//import com.bbitcn.f8.pad.base.MyButton
+//import com.bbitcn.f8.pad.base.MyCard
+//import com.bbitcn.f8.pad.base.MyDialog
+//import com.bbitcn.f8.pad.model.net.request.DryCocoonRefreshStartRequest
+//import com.bbitcn.f8.pad.model.net.request.DryCocoonRefreshStopRequest
+//import com.bbitcn.f8.pad.ui.screen.mainFunc.DryCocoonViewModel
+//import com.bbitcn.f8.pad.ui.screen.secondFunc.DryCocoonInfo
+//import com.bbitcn.f8.pad.ui.screen.view.Toasty
+//import com.bbitcn.f8.pad.ui.screen.view.deviceManager.reader.MyCardReaderShowViewModel
+//import com.bbitcn.f8.pad.ui.screen.view.deviceManager.scale.MyWeightShow
+//import com.bbitcn.f8.pad.utils.externalModules.manager.serial.uhfSerial.UHFReaderForSerial
+//import kotlinx.coroutines.flow.flowOf
+//
+//
+//data class DryCocoonRefreshDialogData(
+// val showDialog: Boolean = false,
+// // 包装类型
+//// val packageType: List = listOf(),
+// val onDismiss: () -> Unit = {}
+//)
+//
+//@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+//@Composable
+//fun DryCocoonRefreshDialogPreview() {
+// DryCocoonRefreshDialog(DryCocoonRefreshDialogData(showDialog = true))
+//}
+//
+//@Composable
+//fun DryCocoonRefreshDialog(
+// info: DryCocoonRefreshDialogData,
+// dryCocoonViewModel: DryCocoonViewModel = viewModel(),
+// myCardReaderShowViewModel: MyCardReaderShowViewModel = viewModel(),
+//) {
+// var grossWeight by rememberSaveable { mutableStateOf(0.0) }
+// val curReader by myCardReaderShowViewModel.curDevice.collectAsState()
+//
+// val tagIds by (curReader?.tagList
+// ?: flowOf(emptyList())).collectAsState(initial = emptyList())
+// var weightErrorMsg = ""
+// val tagErrorMsg = if (curReader == null) "未检测到读卡器"
+// else if (tagIds.isEmpty()) "未检测到麻袋"
+// else if (tagIds.size > 1) "检测到多个麻袋"
+// else ""
+// val allErrorMsg = if (tagErrorMsg.isNotEmpty()) tagErrorMsg + "\n" else "" +
+// if (weightErrorMsg.isNotEmpty()) weightErrorMsg + "\n" else ""
+//
+// val tagIdTemp = if (tagIds.isNotEmpty()) tagIds[0] else ""
+// var tagId by rememberSaveable { mutableStateOf("") }
+//
+// val state by dryCocoonViewModel.curPackageState.collectAsState()
+//
+// val ticketInfo by dryCocoonViewModel.ticketInfo.collectAsState()
+// MyDialog("翻包摊晾",
+// info.showDialog,{
+// info.onDismiss()
+// // 关闭弹窗后关闭超高频读卡器
+// UHFReaderForSerial.stopAllScan()
+// },
+// state, {
+// if (allErrorMsg.isEmpty()) {
+// if (state == "开始翻包") {
+// dryCocoonViewModel.onRefreshStart(
+// DryCocoonRefreshStartRequest(
+// code = ticketInfo.code,
+// kcmaozhong = ticketInfo.kcmaozhong,
+// maozhong = grossWeight.toString(),
+// rfid = tagId,
+// rkitemsysid = ticketInfo.sysid
+// )
+// ) {
+// myCardReaderShowViewModel.clearList()
+// }
+// } else {
+// dryCocoonViewModel.onRefreshStop(
+// DryCocoonRefreshStopRequest(
+// maozhong = grossWeight.toString(),
+// rfid = tagId
+// )
+// ) {
+// myCardReaderShowViewModel.clearList()
+// }
+// }
+// } else {
+// Toasty.showTipsDialog(allErrorMsg)
+// }
+// }
+// ) {
+// LaunchedEffect(Unit) {
+// // 修复刚进入页面时,tagIdTemp不为空,但tagId为空进而触发刷新getPackageInfo的问题
+// tagId = tagIdTemp
+// myCardReaderShowViewModel.clearList()
+// UHFReaderForSerial.startAllScan()
+// }
+// // 只有在弹窗打开时才检测
+// LaunchedEffect(tagIdTemp) {
+// if (tagIdTemp.isNotEmpty() && tagIdTemp != tagId) {
+// // 检测到新芯片
+// tagId = tagIdTemp
+// dryCocoonViewModel.getPackageInfo(tagId)
+// } else {
+// // 清空数据
+// tagId = tagIdTemp
+// dryCocoonViewModel.clearPackageInfo()
+// }
+// }
+// Row(
+// modifier = M
+// .fillMaxSize()
+// .padding(10.dp),
+// horizontalArrangement = Arrangement.spacedBy(10.dp)
+// ) {
+// MyCard(
+// modifier = M
+// .weight(1f),
+// elevation = 2.dp
+// ) {
+// Column(
+// modifier = M
+// .fillMaxSize()
+// .padding(10.dp),
+// verticalArrangement = Arrangement.spacedBy(10.dp),
+// horizontalAlignment = Alignment.End
+// ) {
+// MyWeightShow(onErrorMsg = {
+// weightErrorMsg = it
+// }) {
+// grossWeight = it
+// }
+// val showTagId = if (tagId == "") ""
+// else "${tagId.take(2)}...${tagId.takeLast(4)}"
+// DryCocoonInfo("麻袋ID", showTagId, tagErrorMsg)
+// MyButton(modifier = M.fillMaxWidth(), text = "重新检测茧包") {
+// tagId = ""
+// myCardReaderShowViewModel.clearList()
+// }
+// }
+// }
+// MyCard(
+// modifier = M
+// .weight(1f),
+// elevation = 2.dp
+// ) {
+// Column(
+// modifier = M
+// .fillMaxSize()
+// .padding(20.dp),
+// horizontalAlignment = Alignment.Start,
+// verticalArrangement = Arrangement.spacedBy(10.dp)
+// ) {
+// InfoText("包码:", ticketInfo.code)
+// InfoText("蚕季:", ticketInfo.cjname)
+// InfoText("仓库:", ticketInfo.ckname)
+// InfoText("茧别:", ticketInfo.jiantypename)
+// InfoText("包装:", ticketInfo.bagtype)
+// val packageWeight =
+// if (ticketInfo.bagzhongliang == 0.0) "" else ticketInfo.bagzhongliang.toString()
+// InfoText("包装重量(kg):", packageWeight)
+// val storeWeight =
+// if (ticketInfo.kcmaozhong == 0.0) "" else ticketInfo.kcmaozhong.toString()
+// InfoText("库存重量(kg):", storeWeight)
+// }
+// }
+// }
+// }
+//}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/DryCoCoonScreen.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/DryCoCoonScreen.kt
new file mode 100644
index 0000000..4023b9e
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/DryCoCoonScreen.kt
@@ -0,0 +1,459 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavController
+import androidx.navigation.compose.rememberNavController
+import androidx.paging.compose.LazyPagingItems
+import androidx.paging.compose.collectAsLazyPagingItems
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.DateRangePickTextFiled
+import com.bbitcn.f8.pad.base.MainFuncFrame
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.MyRefreshTable
+import com.bbitcn.f8.pad.base.MyScrollableTabRow
+import com.bbitcn.f8.pad.base.MyTableData
+import com.bbitcn.f8.pad.base.QueryTextField
+import com.bbitcn.f8.pad.model.net.request.DryCocoonQueryListRequest
+import com.bbitcn.f8.pad.model.net.request.DryCocoonOutListRequest
+import com.bbitcn.f8.pad.model.net.request.DryCocoonAirListRequest
+import com.bbitcn.f8.pad.model.net.response.DryCocoonInListResponse
+import com.bbitcn.f8.pad.model.net.response.DryCocoonOutListResponse
+import com.bbitcn.f8.pad.model.net.response.DryCocoonAirListResponse
+import com.bbitcn.f8.pad.model.net.response.DryStoreListRequest
+import com.bbitcn.f8.pad.model.net.response.DryStoreListResponse
+import com.bbitcn.f8.pad.ui.screen.dialog.drycocoon.AddDryCocoonTicketAirDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.drycocoon.AddDryCocoonTicketInDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.drycocoon.AddDryCocoonTicketOutDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.DateRangeSelectDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.drycocoon.DryCocoonLossDialog
+import com.bbitcn.f8.pad.ui.screen.view.drawer.DrawerViewModel
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.utils.log.MyLog
+import com.bbitcn.f8.pad.utils.pager.MyPager
+import kotlinx.coroutines.delay
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun DryCoonScreenPV() {
+ DryCoonScreen(rememberNavController(), DrawerViewModel())
+}
+
+@Composable
+fun DryCoonScreen(
+ navController: NavController,
+ drawerViewModel: DrawerViewModel,
+ dryCocoonViewModel: DryCocoonViewModel = viewModel()
+) {
+ val addDryCocoonTicketDialogInData by dryCocoonViewModel.addDryCocoonTicketInDialogData.collectAsState()
+ val addDryCocoonTicketDialogOutData by dryCocoonViewModel.AddDryCocoonTicketOutDialogData.collectAsState()
+ val addDryCocoonTicketAirDialogData by dryCocoonViewModel.addDryCocoonTicketAirDialogData.collectAsState()
+// val dryCocoonLossDialogData by dryCocoonViewModel.dryCocoonLossDialogData.collectAsState()
+ val dateRangeSelectDialogData by dryCocoonViewModel.dateRangeSelectDialogData.collectAsState()
+ val tabs = listOf("入库", "出库", "库存", "摊晾")
+ var curPager by rememberSaveable { mutableStateOf(0) }
+ val queryInput by dryCocoonViewModel.queryInput.collectAsState()
+
+ // 入库
+ val dryIn = dryCocoonViewModel.dryCocoonInPager.collectAsLazyPagingItems()
+ val myInPager = dryCocoonViewModel.dryCocoonInMyPager
+
+ // 出库
+ val dryOut = dryCocoonViewModel.dryCocoonOutPager.collectAsLazyPagingItems()
+ val myOutPager = dryCocoonViewModel.dryCocoonOutMyPager
+
+ // 库存
+ val store = dryCocoonViewModel.storePager.collectAsLazyPagingItems()
+ val myStore = dryCocoonViewModel.storeMyPager
+
+ // 摊晾计划
+ val packagePager = dryCocoonViewModel.packagePager.collectAsLazyPagingItems()
+ val myPackagePager = dryCocoonViewModel.packageMyPager
+
+ val refreshDryIn= {
+ dryCocoonViewModel.refreshInStatistics()
+ dryIn.refresh()
+ }
+ val refreshDryOut = {
+ dryCocoonViewModel.refreshOutStatistics()
+ dryOut.refresh()
+ }
+
+ val onFilterLikeChanged: (String) -> Unit = onFilterLikeChanged@{ query ->
+ dryCocoonViewModel.refreshLike(curPager, queryInput) {
+ when (curPager) {
+ 0 -> refreshDryIn()
+ 1 -> refreshDryOut()
+ 2 -> store.refresh()
+ 3 -> packagePager.refresh()
+ }
+ }
+ }
+ MainFuncFrame {
+ MyCard {
+ Column(modifier = M.padding(15.dp)) {
+ Row(
+ modifier = M
+ .fillMaxWidth()
+ .padding(bottom = 5.dp),
+ horizontalArrangement = Arrangement.End,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ val dateRange by dryCocoonViewModel.dateRange.collectAsState()
+ MyScrollableTabRow(
+ M
+ .weight(1f)
+ .padding(end = 10.dp), curPager, tabs
+ ) {
+ curPager = it
+ dryCocoonViewModel.switchMode(curPager)
+ onFilterLikeChanged("")
+ }
+// AssistChipFilter("筛选:", queryInput, onFilterLikeChanged)
+// Spacer(modifier = M.weight(1f))
+ when (curPager) {
+ 0 -> {
+ MyButton(
+ text = "新增入库单"
+ ) {
+ dryCocoonViewModel.showAddDryCocoonTicketInDialog { sysId ->
+ navController.navigate("dryCoonOperateIn/${sysId}")
+ }
+ }
+ }
+
+ 1 -> {
+ MyButton(
+ text = "新增出库单"
+ ) {
+ dryCocoonViewModel.showAddDryCocoonTicketOutDialog { sysId ->
+ navController.navigate("dryCoonOperateOut/${sysId}")
+ }
+ }
+// MyButton(
+// modifier = M.padding(start = 5.dp),
+// text = "空包出库"
+// ) {
+// dryCocoonViewModel.showLossPackageForOut()
+// }
+ }
+
+ 3 -> {
+ MyButton(text = "新增摊晾计划") {
+ dryCocoonViewModel.showDryCocoonAirDialog { sysId ->
+ navController.navigate("dryCoonOperateAir/${sysId}")
+ }
+ }
+ }
+ }
+ if (curPager != 2) {
+ DateRangePickTextFiled(
+ M
+ .width(180.dp)
+ .padding(horizontal = 10.dp),
+ dateRange = dateRange
+ ) {
+ dryCocoonViewModel.showDateRangeSelectDialog(curPager) {
+ when (curPager) {
+ 0 -> refreshDryIn()
+ 1 -> refreshDryOut()
+ 2 -> store.refresh()
+ 3 -> packagePager.refresh()
+ }
+ }
+ }
+ }
+ QueryTextField(
+ M.width(200.dp),
+ queryInput,
+ when (curPager) {
+ 0 -> "搜索入库"
+ 1 -> "搜索出库"
+ 2 -> "搜索茧别"
+ else -> "搜索计划"
+ }
+ ) {
+ onFilterLikeChanged(it)
+ }
+ }
+ HorizontalDivider(
+ modifier = M.padding(vertical = 5.dp),
+ color = MyColors.Gray
+ )
+ when (curPager) {
+ 0 -> DryCoonInScreen(dryCocoonViewModel, dryIn, myInPager) { info ->
+ drawerViewModel.openDryCocoonInDetailDrawer(navController, info) {
+ dryCocoonViewModel.deleteInTicket(info.sysid) {
+ refreshDryIn()
+ }
+ }
+ }
+
+ 1 -> DryCoonOutScreen(dryCocoonViewModel, dryOut, myOutPager) { info ->
+ drawerViewModel.openDryCocoonOutDetailDrawer(navController, info) {
+ dryCocoonViewModel.deleteOutTicket(info.sysid) {
+ refreshDryOut()
+ }
+ }
+ }
+
+ 2 -> StoreScreen(dryCocoonViewModel, store, myStore){
+ drawerViewModel.openDryCocoonStoreDetailDrawer(it)
+ }
+
+ 3 -> AirScreen(packagePager, myPackagePager) {
+ drawerViewModel.openDryCocoonAirDetailDrawer(navController, it) {
+ dryCocoonViewModel.deleteAirTicket(it.sysid) {
+ packagePager.refresh()
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ AddDryCocoonTicketInDialog(addDryCocoonTicketDialogInData)
+ AddDryCocoonTicketOutDialog(addDryCocoonTicketDialogOutData)
+ AddDryCocoonTicketAirDialog(addDryCocoonTicketAirDialogData)
+// DryCocoonLossDialog(dryCocoonLossDialogData)
+ DateRangeSelectDialog(dateRangeSelectDialogData)
+}
+
+@Composable
+fun DryCoonInScreen(
+ dryCocoonViewModel: DryCocoonViewModel,
+ dryIn: LazyPagingItems,
+ myPager: MyPager,
+ openDryCocoonDetailDrawer: (DryCocoonInListResponse.Data) -> Unit,
+) {
+ LaunchedEffect(Unit) {
+ delay(1500)
+ MyLog.test("刷新入库单列表")
+ dryIn.refresh()
+ dryCocoonViewModel.refreshInStatistics()
+ }
+ Column(horizontalAlignment = Alignment.End) {
+ val isRefreshing by myPager.listIsRefreshing.collectAsState()
+ MyRefreshTable(
+ modifier = M
+ .padding(15.dp)
+ .weight(1f)
+ .fillMaxWidth(),
+ key = { it.sysid },
+ isRefreshing = isRefreshing,
+ info = dryIn,
+ onFinishRefresh = {
+ myPager.setListIsRefreshClose()
+ },
+ items = listOf(
+ MyTableData(1, isIndex = true),
+ MyTableData("仓库", 2, { it.ckname }),
+ MyTableData("蚕季", 2, { it.cjname }),
+ MyTableData("蚕品种", 2, { it.cpzname }),
+ MyTableData("区域", 2, { it.xiangzhen }),
+ MyTableData("茧别", 2, { it.jiantype }),
+ MyTableData("入库单号", 2, { it.code }),
+ MyTableData("入库时间", 3, { it.datetime }),
+ MyTableData("包装", 1, { it.bagtype }),
+ MyTableData("总包", 1, { it.baoshu.toString() }),
+ MyTableData("毛重", 1, { it.maozhong.toString() }),
+ MyTableData("皮重", 1, { it.pizhong.toString() }),
+ MyTableData("净重", 1, { it.jingzhong.toString() }),
+ ),
+ onClick = { info ->
+ openDryCocoonDetailDrawer(info)
+ }
+ )
+ val statisticsInfo by dryCocoonViewModel.inStatisticsInfo.collectAsState()
+ MyCard(modifier = M.padding(end = 15.dp), colors = MyColors.BlueGreen) {
+ Row(
+ modifier = M.padding(horizontal = 20.dp, vertical = 10.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text("统计单数:", color = MyColors.White)
+ Text(statisticsInfo.totalNum.toString(), color = MyColors.White)
+ Text("\t\t\t总包数:", color = MyColors.White)
+ Text(statisticsInfo.totalBaoshu.toString(), color = MyColors.White)
+ Text("\t\t\t总毛重:", color = MyColors.White)
+ Text(statisticsInfo.totalMaozhong.toString(), color = MyColors.White)
+ Text("\t\t\t总皮重:", color = MyColors.White)
+ Text(statisticsInfo.totalPizhong.toString(), color = MyColors.White)
+ Text("\t\t\t总净重:", color = MyColors.White)
+ Text(statisticsInfo.totalJingzhong.toString(), color = MyColors.White)
+ }
+ }
+ }
+}
+
+@Composable
+fun DryCoonOutScreen(
+ dryCocoonViewModel: DryCocoonViewModel,
+ dryOut: LazyPagingItems,
+ myPager: MyPager,
+ openDryCocoonDetailDrawer: (DryCocoonOutListResponse.Data) -> Unit
+) {
+ val isRefreshing by myPager.listIsRefreshing.collectAsState()
+ LaunchedEffect(Unit) {
+ delay(1500)
+ MyLog.test("刷新出库单列表")
+ dryOut.refresh()
+ dryCocoonViewModel.refreshOutStatistics()
+ }
+ Column(horizontalAlignment = Alignment.End) {
+ MyRefreshTable(
+ modifier = M
+ .padding(15.dp)
+ .weight(1f)
+ .fillMaxWidth(),
+ key = { it.sysid },
+ isRefreshing = isRefreshing,
+ info = dryOut,
+ onFinishRefresh = {
+ myPager.setListIsRefreshClose()
+ },
+ items = listOf(
+ MyTableData(1, isIndex = true),
+ MyTableData("仓库", 2, { it.ckname }),
+ MyTableData("蚕季", 2, { it.cjname }),
+ MyTableData("区域", 2, { it.xiangzhen }),
+ MyTableData("往来单位", 2, { it.wldwname }),
+ MyTableData("茧别", 2, { it.jiantype }),
+ MyTableData("出库单号", 2, { it.code }),
+ MyTableData("出库时间", 3, { it.ckdatetime }),
+ MyTableData("包装称", 1, { it.bagtype }),
+ MyTableData("总包", 1, { it.baoshu.toString() }),
+ MyTableData("毛重", 1, { it.maozhong.toString() }),
+ MyTableData("皮重", 1, { it.pizhong.toString() }),
+ MyTableData("净重", 1, { it.jingzhong.toString() }),
+ MyTableData("出库人", 1, { it.chukuren }),
+ ),
+ onClick = { info ->
+ openDryCocoonDetailDrawer(info)
+ }
+ )
+ val statisticsInfo by dryCocoonViewModel.outStatisticsInfo.collectAsState()
+ MyCard(modifier = M.padding(end = 15.dp), colors = MyColors.BlueGreen) {
+ Row(
+ modifier = M.padding(horizontal = 20.dp, vertical = 10.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text("统计单数:", color = MyColors.White)
+ Text(statisticsInfo.totalNum.toString(), color = MyColors.White)
+ Text("\t\t\t总包数:", color = MyColors.White)
+ Text(statisticsInfo.totalBaoshu.toString(), color = MyColors.White)
+ Text("\t\t\t总毛重:", color = MyColors.White)
+ Text(statisticsInfo.totalMaozhong.toString(), color = MyColors.White)
+ Text("\t\t\t总皮重:", color = MyColors.White)
+ Text(statisticsInfo.totalPizhong.toString(), color = MyColors.White)
+ Text("\t\t\t总净重:", color = MyColors.White)
+ Text(statisticsInfo.totalJingzhong.toString(), color = MyColors.White)
+ }
+ }
+ }
+}
+
+@Composable
+fun StoreScreen(
+ dryCocoonViewModel: DryCocoonViewModel,
+ store: LazyPagingItems,
+ myPager: MyPager,
+ openDryCocoonDetailDrawer: (DryStoreListResponse.Data) -> Unit
+) {
+ val isRefreshing by myPager.listIsRefreshing.collectAsState()
+ LaunchedEffect(Unit) {
+ delay(1500)
+ MyLog.test("刷新库存列表")
+ store.refresh()
+ }
+ MyRefreshTable(
+ modifier = M
+ .padding(15.dp)
+ .fillMaxSize(),
+ key = { it.sysid },
+ isRefreshing = isRefreshing,
+ info = store,
+ onFinishRefresh = {
+ myPager.setListIsRefreshClose()
+ },
+ items = listOf>(
+ MyTableData(1, isIndex = true),
+ MyTableData("仓库", 1, { it.gjckname }),
+ MyTableData("蚕季", 1, { it.cjname }),
+ MyTableData("茧别", 1, { it.jiantypename }),
+ MyTableData("净重", 1, { it.jingzhong.toString() }),
+ MyTableData("包数", 1, { it.baoshu.toString() }),
+ MyTableData("状态", 1, { if (it.isempty == 1) "已清空" else "有库存" }),
+ MyTableData(
+ "操作",
+ 1,
+ { if (it.isempty == 1) "取消标记" else "标记" },
+ isButton = true
+ ) {
+ dryCocoonViewModel.switchStoreEmpty(it.sysid, if (it.isempty == 1) 0 else 1) {
+ store.refresh()
+ }
+ }
+ ),
+ onClick = { info ->
+ openDryCocoonDetailDrawer(info)
+ }
+ )
+}
+
+@Composable
+fun AirScreen(
+ store: LazyPagingItems,
+ myPager: MyPager,
+ openDryCocoonDetailDrawer: (DryCocoonAirListResponse.Data) -> Unit
+) {
+ LaunchedEffect(Unit) {
+ delay(1500)
+ MyLog.test("刷新摊晾单")
+ store.refresh()
+ }
+ val isRefreshing by myPager.listIsRefreshing.collectAsState()
+ MyRefreshTable(
+ modifier = M
+ .padding(15.dp)
+ .fillMaxSize(),
+ key = { it.sysid },
+ isRefreshing = isRefreshing,
+ info = store,
+ onFinishRefresh = {
+ myPager.setListIsRefreshClose()
+ },
+ items = listOf(
+ MyTableData(1, isIndex = true),
+ MyTableData("仓库", 1, { it.ckname }),
+ MyTableData("蚕季", 1, { it.cjname }),
+ MyTableData("茧别", 1, { it.jiantype }),
+ MyTableData("计划摊晾时间", 1, { it.plantime }),
+ MyTableData("开始时间", 1, { it.startime }),
+ MyTableData("结束时间", 1, { it.endtime }),
+ MyTableData("摊晾人", 1, { it.tanliangren }),
+ ),
+ onClick = { info ->
+ openDryCocoonDetailDrawer(info)
+ }
+ )
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/DryCocoonViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/DryCocoonViewModel.kt
new file mode 100644
index 0000000..8e0cae8
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/DryCocoonViewModel.kt
@@ -0,0 +1,510 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc
+
+import androidx.lifecycle.viewModelScope
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.model.net.request.DryCocoonQueryListRequest
+import com.bbitcn.f8.pad.model.net.request.DryCocoonOutListRequest
+import com.bbitcn.f8.pad.model.net.request.DryCocoonAirListRequest
+import com.bbitcn.f8.pad.model.net.request.DryCocoonPackageForOutLossRequest
+import com.bbitcn.f8.pad.model.net.response.*
+import com.bbitcn.f8.pad.ui.screen.dialog.*
+import com.bbitcn.f8.pad.ui.screen.dialog.drycocoon.AddDryCocoonTicketAirDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.drycocoon.AddDryCocoonTicketInDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.drycocoon.AddDryCocoonTicketOutDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.drycocoon.DryCocoonLossDialogData
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.TimeUtils
+import com.bbitcn.f8.pad.utils.global.Global
+import com.bbitcn.f8.pad.utils.log.MyLog
+
+import com.bbitcn.f8.pad.utils.pager.DryCocoonInPagingSource
+import com.bbitcn.f8.pad.utils.pager.DryCocoonOutPagingSource
+import com.bbitcn.f8.pad.utils.pager.DryCocoonAirPagingSource
+import com.bbitcn.f8.pad.utils.pager.DryCocoonStorePagingSource
+import com.bbitcn.f8.pad.utils.pager.MyPager
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import java.util.Date
+
+class DryCocoonViewModel : BaseViewModel() {
+
+ // —————————————————————————————————————————————————————————————————————全局—————————————————————————————————————————————————————————————————————————————
+
+
+ val initDateRange = Pair(TimeUtils.getRecentDaysDate(20), TimeUtils.getEndOfDay(Date())) // 初始化时间范围为半个月前到当日晚上
+ private val _queryInput = MutableStateFlow("")
+ val queryInput = _queryInput.asStateFlow()
+
+ private val _dateRange = MutableStateFlow(Pair(Date(), Date()))
+ val dateRange = _dateRange.asStateFlow()
+ private val _dateRangeIn = MutableStateFlow(Pair(Date(), Date()))
+ private val _dateRangeOut = MutableStateFlow(Pair(Date(), Date()))
+ private val _dateRangeAir = MutableStateFlow(Pair(Date(), Date()))
+
+ init {
+ doInIoThreadNoDialog {
+ // 获取半个月前的日期
+ _dateRange.value = initDateRange
+ // 初始化入库|出库|摊晾的时间范围
+ _dateRangeIn.value = initDateRange
+ _dateRangeOut.value = initDateRange
+ _dateRangeAir.value = initDateRange
+ }
+ }
+
+ fun switchMode(curPager: Int) {
+ // 切换模块
+ val date = when (curPager) {
+ 0 -> _dateRangeIn.value
+ 1 -> _dateRangeOut.value
+ 3 -> _dateRangeAir.value
+ else -> Date() to Date()
+ }
+ _queryInput.value = ""
+ _dateRange.value = date
+ }
+
+ fun refreshLike(curPager: Int, query: String, onFinish: () -> Unit) {
+ _queryInput.value = query
+ refreshRequest(curPager, onFinish)
+ }
+
+ /**
+ * 刷新入库|出库|摊晾的时间范围
+ */
+ fun refreshRequest(curPager: Int, onFinish: () -> Unit) {
+ doInIoThreadNoDialog {
+ when (curPager) {
+ 0 -> {
+ dryCocoonInMyPager.updateParams { request ->
+ request.copy(
+ like = _queryInput.value,
+ starttime = TimeUtils.formatDateTime(_dateRangeIn.value.first),
+ endtime = TimeUtils.formatDateTime(_dateRangeIn.value.second)
+ )
+ }
+ refreshInStatisticsApi()
+ }
+
+ 1 -> {
+ dryCocoonOutMyPager.updateParams { request ->
+ request.copy(
+ like = _queryInput.value,
+ starttime = TimeUtils.formatDateTime(_dateRangeOut.value.first),
+ endtime = TimeUtils.formatDateTime(_dateRangeOut.value.second)
+ )
+ }
+ refreshOutStatisticsApi()
+ }
+
+ 3 -> packageMyPager.updateParams { request ->
+ request.copy(
+ like = _queryInput.value,
+ starttime = TimeUtils.formatDateTime(_dateRangeAir.value.first),
+ endtime = TimeUtils.formatDateTime(_dateRangeAir.value.second)
+ )
+ }
+ }
+ onFinish()
+ }
+ }
+
+ /**
+ * 筛选时间范围弹窗
+ */
+ private val _dateRangeSelectDialogData = MutableStateFlow(DateRangeSelectDialogData())
+ val dateRangeSelectDialogData = _dateRangeSelectDialogData.asStateFlow()
+
+ fun showDateRangeSelectDialog(curPager: Int, onFinish: () -> Unit) {
+ doInIoThreadNoDialog {
+ _dateRangeSelectDialogData.value = DateRangeSelectDialogData(
+ showDialog = true,
+ default = _dateRange.value,
+ onDismiss = {
+ _dateRangeSelectDialogData.update { it.copy(showDialog = false) }
+ },
+ onClickRangeDay = { dateStrStart, dateStrEnd ->
+ // 切换时间范围
+ val temp =
+ TimeUtils.getStartOfDay(dateStrStart) to TimeUtils.getEndOfDay(dateStrEnd)
+ _dateRange.value = temp
+ when (curPager) {
+ 0 -> _dateRangeIn.value = temp
+ 1 -> _dateRangeOut.value = temp
+ 3 -> _dateRangeAir.value = temp
+ }
+ refreshRequest(curPager, onFinish)
+ }
+ )
+ }
+ }
+
+ // —————————————————————————————————————————————————————————————————————————入库—————————————————————————————————————————————————————————————————————————
+
+ private val _addDryCocoonTicketInDialogData = MutableStateFlow(AddDryCocoonTicketInDialogData())
+ val addDryCocoonTicketInDialogData = _addDryCocoonTicketInDialogData.asStateFlow()
+
+ private val _inStatisticsInfo = MutableStateFlow(DryCocoonInStatisticsResponse.Data())
+ val inStatisticsInfo = _inStatisticsInfo.asStateFlow()
+
+ // 创建 Pager
+ val dryCocoonInMyPager = MyPager(
+ pagingSourceFactory = { DryCocoonInPagingSource(it) },
+ initialRequestData = DryCocoonQueryListRequest(
+ starttime = TimeUtils.formatDateTime(initDateRange.first),
+ endtime = TimeUtils.formatDateTime(initDateRange.second)
+ ), // 传入初始的请求数据
+ )
+ val dryCocoonInPager = dryCocoonInMyPager.createPager(viewModelScope)
+
+ fun refreshInStatistics() {
+ doInIoThreadNoDialog {
+ refreshInStatisticsApi()
+ }
+ }
+
+ suspend fun refreshInStatisticsApi() {
+ val result = apiService.getDryCocoonInStatistics(
+ DryCocoonQueryListRequest(
+ like = _queryInput.value,
+ starttime = TimeUtils.formatDateTime(_dateRangeIn.value.first),
+ endtime = TimeUtils.formatDateTime(_dateRangeIn.value.second)
+ )
+ )
+ if (result.code == 1) {
+ _inStatisticsInfo.value = result.data
+ } else {
+ Toasty.showTipsDialog(result.msg)
+ }
+ }
+
+ fun showAddDryCocoonTicketInDialog(
+ navToDryCoonOperateIn: (sysId: String) -> Unit
+ ) {
+ doInIoThread("正在加载入库单据新增信息") {
+ //入库相关参数
+ // 入库蚕季
+ val season: DryCocoonSeason = apiService.getDryCoonSeason()
+ if (season.code != 1) {
+ Toasty.showTipsDialog(season.msg)
+ return@doInIoThread
+ }
+ // 入库仓库
+ val store: DryCocoonInStore = apiService.getDryCoonInStore()
+ if (store.code != 1) {
+ Toasty.showTipsDialog(store.msg)
+ return@doInIoThread
+ }
+ // 入库茧别
+ val cocoonLevel: DryCocoonInLevel = apiService.getDryCoonInLevel()
+ if (cocoonLevel.code != 1) {
+ Toasty.showTipsDialog(cocoonLevel.msg)
+ return@doInIoThread
+ }
+ // 入库包装类型
+ val packageType: DryCocoonInPackageType = apiService.getDryCocoonPackageType()
+ if (packageType.code != 1) {
+ Toasty.showTipsDialog(packageType.msg)
+ return@doInIoThread
+ }
+ // 蚕品种
+ val cocoonType = apiService.getDryCocoonType()
+ if (cocoonType.code != 1) {
+ Toasty.showTipsDialog(cocoonType.msg)
+ return@doInIoThread
+ }
+ // 区域
+ val area = apiService.getCocoonArea()
+ if (area.code != 1) {
+ Toasty.showTipsDialog(area.msg)
+ return@doInIoThread
+ }
+ _addDryCocoonTicketInDialogData.value =
+ AddDryCocoonTicketInDialogData(
+ true,
+ store.data.firstOrNull { it.depsysid == MMKVUtil.get(Global.DEP_SYS_ID) }?.cangkuname
+ ?: "",
+ season.data,
+ store.data,
+ cocoonType.data,
+ cocoonLevel.data,
+ packageType.data,
+ area.data,
+ { request, cocoonLevelT, storeT, cocoonTypeT ->
+ doInIoThreadThenUI("正在添加入库单据", onIO = {
+ apiService.addDryCocoonInTicket(request)
+ }) { response ->
+ if (response.code != 1) {
+ Toasty.showTipsDialog(response.msg)
+ } else {
+ navToDryCoonOperateIn(response.data.toString())
+ }
+ }
+ }
+ ) {
+ _addDryCocoonTicketInDialogData.update { it.copy(showDialog = false) }
+ }
+ }
+ }
+
+ fun deleteInTicket(sysid: String, onFinish: () -> Unit) {
+ doInIoThread("正在删除入库单") {
+ // 删除入库单
+ val response = apiService.deleteDryCocoonInTicket(sysid)
+ if (response.code != 1) {
+ Toasty.showTipsDialog(response.msg)
+ } else {
+ Toasty.success("删除成功")
+ onFinish()
+ }
+ }
+ }
+
+ // ————————————————————————————————————————————————————————————————————————出库——————————————————————————————————————————————————————————————————————————
+
+ private val _addDryCocoonTicketOutDialogData: MutableStateFlow =
+ MutableStateFlow(AddDryCocoonTicketOutDialogData())
+ val AddDryCocoonTicketOutDialogData = _addDryCocoonTicketOutDialogData.asStateFlow()
+
+ /**
+ * 出库统计信息
+ */
+ private val _outStatisticsInfo = MutableStateFlow(DryCocoonOutStatisticsResponse.Data())
+ val outStatisticsInfo = _outStatisticsInfo.asStateFlow()
+
+ // 创建 Pager
+ val dryCocoonOutMyPager = MyPager(
+ pagingSourceFactory = { DryCocoonOutPagingSource(it) },
+ initialRequestData = DryCocoonOutListRequest(
+ starttime = TimeUtils.formatDateTime(initDateRange.first),
+ endtime = TimeUtils.formatDateTime(initDateRange.second)
+ ), // 传入初始的请求数据
+ )
+ val dryCocoonOutPager = dryCocoonOutMyPager.createPager(viewModelScope)
+
+ fun refreshOutStatistics() {
+ doInIoThreadNoDialog {
+ refreshOutStatisticsApi()
+ }
+ }
+
+ suspend fun refreshOutStatisticsApi() {
+ // 出库统计
+ val result = apiService.getDryCocoonOutStatistics(
+ DryCocoonQueryListRequest(
+ like = _queryInput.value,
+ starttime = TimeUtils.formatDateTime(_dateRangeIn.value.first),
+ endtime = TimeUtils.formatDateTime(_dateRangeIn.value.second)
+ )
+ )
+ if (result.code == 1) {
+ _outStatisticsInfo.value = result.data
+ } else {
+ Toasty.showTipsDialog(result.msg)
+ }
+ }
+
+ fun showAddDryCocoonTicketOutDialog(
+ navToDryCoonOperateOut: (infoStr: String) -> Unit
+ ) {
+ doInIoThread("正在加载出库单据新增信息") {
+ //入库相关参数
+ // 入库蚕季 仓库 茧别 三级联
+ val info = apiService.getDryCocoonOutAddInfo()
+ if (info.code != 1) {
+ Toasty.showTipsDialog(info.msg)
+ return@doInIoThread
+ }
+ // 出库包装类型
+ val packageType: DryCocoonInPackageType = apiService.getDryCocoonPackageType()
+ if (packageType.code != 1) {
+ Toasty.showTipsDialog(packageType.msg)
+ return@doInIoThread
+ }
+ // 出库对象
+ val dealObject: DryCocoonDealObjectResponse = apiService.getDryCocoonDealObject()
+ if (dealObject.code != 1) {
+ Toasty.showTipsDialog(dealObject.msg)
+ return@doInIoThread
+ }
+ // 蚕品种
+ val cocoonType = apiService.getDryCocoonType()
+ if (cocoonType.code != 1) {
+ Toasty.showTipsDialog(cocoonType.msg)
+ return@doInIoThread
+ }
+ // 区域
+ val area = apiService.getCocoonArea()
+ if (area.code != 1) {
+ Toasty.showTipsDialog(cocoonType.msg)
+ return@doInIoThread
+ }
+ // 显示新增出库弹窗
+ _addDryCocoonTicketOutDialogData.value = AddDryCocoonTicketOutDialogData(
+ true, info.data, packageType.data, dealObject.data, cocoonType.data, area.data,
+ onClickOK = { request ->
+ doInIoThreadThenUI("正在添加出库单据", onIO = {
+ apiService.addDryCocoonOutTicket(request)
+ }) { response ->
+ if (response.code != 1) {
+ Toasty.showTipsDialog(response.msg)
+ } else {
+ navToDryCoonOperateOut(response.data.toString())
+ }
+ }
+ }
+ ) {
+ _addDryCocoonTicketOutDialogData.update { it.copy(showDialog = false) }
+ }
+ }
+ }
+
+ fun deleteOutTicket(sysId: String, onFinish: () -> Unit) {
+ doInIoThread("正在删除出库单") {
+ // 删除出库单
+ val response = apiService.deleteDryCocoonOutTicket(sysId)
+ if (response.code != 1) {
+ Toasty.showTipsDialog(response.msg)
+ } else {
+ Toasty.success("删除成功")
+ onFinish()
+ }
+ }
+ }
+
+// /**
+// * 出库-空包释放
+// */
+// private val _dryCocoonLossDialogData: MutableStateFlow =
+// MutableStateFlow(DryCocoonLossDialogData())
+// val dryCocoonLossDialogData = _dryCocoonLossDialogData.asStateFlow()
+//
+// fun showLossPackageForOut() {
+// doInIoThread {
+// // 查询蚕季
+// val season: DryCocoonSeason = apiService.getDryCoonSeason("10")
+// if (season.code != 1) {
+// Toasty.showTipsDialog(season.msg)
+// return@doInIoThread
+// }
+// _dryCocoonLossDialogData.value =
+// DryCocoonLossDialogData(
+// true,
+// seasonList = season.data,
+// onLossPackage = { rfids, seasonSysId, onFinish ->
+// doInIoThread("正在释放空包") {
+// rfids.forEach {
+// // 释放空包
+// val response = apiService.onLossPackageForOut(
+// DryCocoonPackageForOutLossRequest(
+// it,
+// TimeUtils.getStringTime(),
+// seasonSysId
+// )
+// )
+// if (response.code != 1) {
+// Toasty.showTipsDialog(response.msg)
+// } else {
+// Toasty.showToast("释放成功")
+// onFinish()
+// }
+// }
+// }
+// },
+// onDismiss = {
+// _dryCocoonLossDialogData.update { it.copy(showDialog = false) }
+// }
+// )
+// }
+// }
+
+ // ————————————————————————————————————————————————————————————————————————库存——————————————————————————————————————————————————————————————————————————
+
+ // 创建 Pager
+ val storeMyPager = MyPager(
+ pagingSourceFactory = { DryCocoonStorePagingSource(it) },
+ initialRequestData = DryStoreListRequest(), // 传入初始的请求数据
+ )
+ val storePager = storeMyPager.createPager(viewModelScope)
+
+ fun switchStoreEmpty(sysid: String, isEmpty: Int, onSuccess: () -> Unit) {
+ doInIoThread("正在" + if (isEmpty == 1) "标记" else "取消标记") {
+ val response = apiService.switchStoreEmpty(sysid, isEmpty)
+ if (response.code != 1) {
+ Toasty.showTipsDialog(response.msg)
+ } else {
+ onSuccess()
+ }
+ }
+ }
+
+ // —————————————————————————————————————————————————————————————————————————摊晾—————————————————————————————————————————————————————————————————————————
+
+ private val _addDryCocoonTicketAirDialogData: MutableStateFlow =
+ MutableStateFlow(AddDryCocoonTicketAirDialogData())
+ val addDryCocoonTicketAirDialogData = _addDryCocoonTicketAirDialogData.asStateFlow()
+
+ // 创建 Pager
+ val packageMyPager = MyPager(
+ pagingSourceFactory = { DryCocoonAirPagingSource(it) },
+ initialRequestData = DryCocoonAirListRequest(
+ starttime = TimeUtils.formatDateTime(initDateRange.first),
+ endtime = TimeUtils.formatDateTime(initDateRange.second)
+ ), // 传入初始的请求数据
+ )
+ val packagePager = packageMyPager.createPager(viewModelScope)
+
+ fun showDryCocoonAirDialog(
+ navToDryCoonOperateAir: (infoStr: String) -> Unit
+ ) {
+ doInIoThread("正在加载摊晾计划新增信息") {
+ //蚕季 仓库 茧别 三级联
+ val info = apiService.getDryCocoonOutAddInfo()
+ if (info.code != 1) {
+ Toasty.showTipsDialog(info.msg)
+ return@doInIoThread
+ }
+ // 蚕品种
+ val cocoonType = apiService.getDryCocoonType()
+ if (cocoonType.code != 1) {
+ Toasty.showTipsDialog(cocoonType.msg)
+ return@doInIoThread
+ }
+ // 区域
+ val area = apiService.getCocoonArea()
+ if (area.code != 1) {
+ Toasty.showTipsDialog(cocoonType.msg)
+ return@doInIoThread
+ }
+ _addDryCocoonTicketAirDialogData.value = AddDryCocoonTicketAirDialogData(
+ true, info.data, cocoonType.data, area.data, onClickOK = { request ->
+ doInIoThreadThenUI("正在添加摊晾计划", onIO = {
+ apiService.addDryCocoonAirTicket(request)
+ }) { response ->
+ if (response.code != 1) {
+ Toasty.showTipsDialog(response.msg)
+ } else {
+ navToDryCoonOperateAir(response.data.toString())
+ }
+ }
+ }) {
+ _addDryCocoonTicketAirDialogData.update { it.copy(showDialog = false) }
+ }
+ }
+ }
+
+ fun deleteAirTicket(sysid: String, onSuccess: () -> Unit) {
+ doInIoThread("正在删除摊晾计划") {
+ val response = apiService.deleteDryCocoonAirTicket(sysid)
+ if (response.code != 1) {
+ Toasty.showTipsDialog(response.msg)
+ } else {
+ Toasty.success("删除成功")
+ onSuccess()
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/FundsScreen.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/FundsScreen.kt
new file mode 100644
index 0000000..e362de8
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/FundsScreen.kt
@@ -0,0 +1,298 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Card
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MainFuncFrame
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavController
+import androidx.navigation.compose.rememberNavController
+import androidx.paging.compose.collectAsLazyPagingItems
+import com.bbitcn.f8.pad.base.AssistChipFilter
+import com.bbitcn.f8.pad.base.DateRangePickTextFiled
+import com.bbitcn.f8.pad.base.InfoText
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.MyRefreshTableForCard
+import com.bbitcn.f8.pad.base.QueryTextField
+import com.bbitcn.f8.pad.base.TableContent
+import com.bbitcn.f8.pad.base.TableHeadLine
+import com.bbitcn.f8.pad.base.VipBadge
+import com.bbitcn.f8.pad.base.isLandscape
+import com.bbitcn.f8.pad.model.net.response.FundsListResponse
+import com.bbitcn.f8.pad.model.net.response.FundsTotalListResponse
+import com.bbitcn.f8.pad.model.net.response.PurchaseDataResponse
+import com.bbitcn.f8.pad.ui.screen.dialog.DateRangeSelectDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.EditPasswordDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.QueryBalanceDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.TicketMoreDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.TicketMoreDialogData
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.drawer.DrawerViewModel
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.ui.screen.view.common.DatePickerRange
+import com.bbitcn.f8.pad.ui.screen.view.drawer.StateChartsOnclick
+import com.bbitcn.f8.pad.utils.MyUtil
+import java.text.SimpleDateFormat
+import java.time.LocalDate
+import java.util.Locale
+
+/**
+ *
+ * @Description 主功能-预约售茧
+ * @Author DuanKaiji
+ * @CreateTime 2024年08月02日 11:25:32
+ */
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun FundsScreenPV() {
+ FundsScreen(rememberNavController(), drawerViewModel = viewModel())
+}
+
+@Composable
+fun FundsScreen(
+ navController: NavController,
+ fundsViewModel: FundsViewModel = viewModel(),
+ drawerViewModel: DrawerViewModel
+) {
+ val queryDateRange by fundsViewModel.dateRange.collectAsState()
+ val daysInfo by fundsViewModel.daysInfo.collectAsState()
+ val queryBalanceDialogData by fundsViewModel.queryBalanceDialogData.collectAsState()
+ val totalList by fundsViewModel.totalList.collectAsState()
+ var queryInput by rememberSaveable { mutableStateOf("") }
+ val ticketMoreDialogData by fundsViewModel.ticketMoreDialogData.collectAsState()
+ val myPager = fundsViewModel.fundsMyPager
+ val isRefreshing by myPager.listIsRefreshing.collectAsState()
+ val pager = fundsViewModel.fundsPager.collectAsLazyPagingItems()
+ val dateRangeSelectDialogData by fundsViewModel.dateRangeSelectDialogData.collectAsState()
+ val curSearchState by fundsViewModel.curSearchState.collectAsState()
+ val onFilterLikeChanged = onFilterLikeChanged@{ like: String ->
+ queryInput = like
+ fundsViewModel.updateParams(
+ curSearchState.payStateValue.toString(), queryInput
+ )
+ pager.refresh()
+ }
+ MainFuncFrame {
+ MyCard(colors = MyColors.LightLightBlueGreen) {
+ Row(
+ modifier = M
+ .fillMaxSize()
+ .padding(10.dp)
+ ) {
+ Column(
+ modifier = M
+ .weight(2f)
+ .fillMaxHeight()
+ ) {
+ DatePickerRange(
+ importantDateInfoList = daysInfo,
+ currentDate = fundsViewModel.calDate,
+ onUpdateCurrentDate = {
+ fundsViewModel.calDate = it
+ fundsViewModel.getDaysInfo(it.year.toString(), it.monthValue.toString())
+ }
+ ) { start, end ->
+ val dateStart =
+ SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(start)
+ val dateEnd = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(end)
+ fundsViewModel.updateDateRange(dateStart to dateEnd)
+ onFilterLikeChanged(queryInput)
+ }
+ DateRangePickTextFiled(M.fillMaxWidth(), dateRange = queryDateRange) {
+ fundsViewModel.showDateRangeSelectDialog {
+ onFilterLikeChanged(queryInput)
+ }
+ }
+ TableHeadLine(
+ modifier = M.fillMaxWidth(),
+ list = listOf(
+ Pair("状态", 2), Pair("笔数", 1), Pair("金额(元)", 2)
+ )
+ )
+ totalList.forEachIndexed { index, tab ->
+ TableContent(
+ modifier = M.fillMaxWidth(),
+ backgroundDeepColor = tab.payStateValue == curSearchState.payStateValue,
+ listOf(
+ Pair(tab.paystate, 2),
+ Pair(tab.total.toString(), 1),
+ Pair(tab.summoney.toString(), 2)
+ ),
+ onClick = {
+ fundsViewModel.updateCurSearchState(tab)
+ onFilterLikeChanged(queryInput)
+ },
+ verticalPadding = 10.dp,
+ )
+ }
+ Spacer(modifier = M.weight(1f))
+ MyButton(modifier = M.fillMaxWidth(), text = "查询配额") {
+ fundsViewModel.showQueryBalanceDialog()
+ }
+ VipBadge {
+ MyButton(modifier = M.fillMaxWidth(), text = "查询余额") {
+ fundsViewModel.showQueryBalanceDialog()
+ }
+ }
+ MyButton(modifier = M.fillMaxWidth(), text = "修改支付密码") {
+ Toasty.showToast("正在开发中,敬请期待!")
+ }
+ }
+ Column(
+ modifier = M
+ .weight(if (isLandscape()) 8f else 3.5f)
+ .fillMaxHeight()
+ ) {
+ Column(
+ modifier = M
+ .padding(10.dp)
+ .fillMaxHeight()
+ ) {
+ Row(
+ modifier = M
+ .padding(bottom = 5.dp)
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Text(text = if (curSearchState.paystate.isEmpty()) "暂无分类" else "【${curSearchState.paystate}】单据列表")
+ AssistChipFilter(
+ "日期:",
+ SimpleDateFormat("yyyy-M-d", Locale.getDefault()).format(
+ queryDateRange.first
+ ) + " ~ " + SimpleDateFormat(
+ "yyyy-M-d",
+ Locale.getDefault()
+ ).format(
+ queryDateRange.second
+ ), deleteEnable = false
+ )
+ AssistChipFilter("筛选:", queryInput, onFilterLikeChanged)
+ Spacer(modifier = M.weight(1f))
+ QueryTextField(M.width(200.dp), queryInput) {
+ onFilterLikeChanged(it)
+ }
+ }
+ var selectIndex by rememberSaveable { mutableStateOf(-1) }
+ MyRefreshTableForCard(
+ modifier = M
+ .fillMaxWidth()
+ .padding(top = 5.dp),
+ isRefreshing = isRefreshing,
+ info = pager,
+ key = { it.czSysid },
+ onFinishRefresh = {
+ myPager.setListIsRefreshClose()
+ },
+ columns = 3,
+ item = { data, index ->
+ FundsItem(isSelect = index == selectIndex, info = data) {
+ selectIndex = index
+ fundsViewModel.selectDetail(data.czSysid) { data: PurchaseDataResponse.Data ->
+ drawerViewModel.openFundsDetailDrawer(
+ info = data,
+ onClick = StateChartsOnclick(
+ pay = {
+ // 支付
+ navController.navigate("pay/${data.czSysid}")
+ drawerViewModel.closeDrawer()
+ },
+ unPay = {
+ Toasty.showConfirmDialog("确定要撤销支付吗?") {
+ fundsViewModel.unPay(data.czSysid)
+ }
+ }
+ )) {
+ fundsViewModel.showTicketMoreDialog(
+ id = data.czSysid
+ )
+ }
+ }
+ }
+ }
+ )
+ }
+ }
+ }
+ }
+ }
+ QueryBalanceDialog(queryBalanceDialogData)
+ TicketMoreDialog(ticketMoreDialogData)
+ DateRangeSelectDialog(dateRangeSelectDialogData)
+}
+
+@Composable
+fun FundsItem(isSelect: Boolean, info: FundsListResponse.Data, onClick: () -> Unit) {
+ MyCard(elevation = 0.dp) {
+ Column(modifier = M.clickable { onClick() }) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = M.background(color = if (isSelect) MyColors.BlueGreen else MyColors.White)
+ ) {
+ Text(
+ text = info.nhName,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M.padding(horizontal = 10.dp),
+ color = if (isSelect) MyColors.White else MyColors.Black,
+ fontWeight = FontWeight.Bold
+ )
+ Spacer(modifier = M.weight(1f))
+ Text(
+ text = info.billCode.toString(),
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ color = if (isSelect) MyColors.White else MyColors.Black,
+ fontWeight = FontWeight.Bold
+ )
+ Card(
+ shape = RoundedCornerShape(topEnd = 7.dp, bottomStart = 7.dp),
+ modifier = M.padding(bottom = 5.dp)
+ ) {
+ Text(
+ text = info.payState,
+ modifier = M
+ .background(color = MyColors.Orange)
+ .padding(5.dp),
+ color = MyColors.White,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ )
+ }
+ }
+ Column(
+ modifier = M
+ .padding(10.dp)
+ .fillMaxWidth()
+ ) {
+ InfoText("茧票金额", info.billMoney.toString())
+ InfoText("实付金额", info.actualpayMoney.toString())
+ InfoText("银行卡号", info.nhBankCode)
+ InfoText("发起时间", info.payDateTime)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/FundsViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/FundsViewModel.kt
new file mode 100644
index 0000000..7f98580
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/FundsViewModel.kt
@@ -0,0 +1,189 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc;
+
+import androidx.lifecycle.viewModelScope
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.model.net.request.FundsRequest
+import com.bbitcn.f8.pad.model.net.request.FundsTotalListRequest
+import com.bbitcn.f8.pad.model.net.request.StatisticsRequest
+import com.bbitcn.f8.pad.model.net.response.FundsTotalListResponse
+import com.bbitcn.f8.pad.model.net.response.PurchaseDataResponse
+import com.bbitcn.f8.pad.model.ui.BaseDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.AuthDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.DateRangeSelectDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.TicketMoreDialogData
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.MyUtil
+import com.bbitcn.f8.pad.utils.TimeUtils
+import com.bbitcn.f8.pad.utils.global.Global
+import com.bbitcn.f8.pad.utils.pager.FundsListPagingSource
+import com.bbitcn.f8.pad.utils.pager.MyPager
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.update
+import java.time.LocalDate
+import java.time.format.DateTimeFormatter
+import java.util.Date
+
+class FundsViewModel : BaseViewModel() {
+ private val _ticketMoreDialog = MutableStateFlow(TicketMoreDialogData())
+ val ticketMoreDialogData = _ticketMoreDialog.asStateFlow()
+
+ private val _dateRange = MutableStateFlow(Pair(Date(), Date()))
+ val dateRange = _dateRange.asStateFlow()
+
+ private val _queryBalanceDialogData = MutableStateFlow(AuthDialogData())
+ val queryBalanceDialogData = _queryBalanceDialogData.asStateFlow()
+
+ private val _totalList = MutableStateFlow>(emptyList())
+ val totalList = _totalList.asStateFlow()
+
+ /**
+ * 当前选中的单据列表类型
+ */
+ private val _curSearchState = MutableStateFlow(FundsTotalListResponse.Data())
+ val curSearchState = _curSearchState.asStateFlow()
+
+ private val _daysInfo = MutableStateFlow(listOf())
+ val daysInfo = _daysInfo.asStateFlow()
+
+ val fundsMyPager = MyPager(
+ pagingSourceFactory = { FundsListPagingSource(it) },
+ initialRequestData = FundsRequest(), // 传入初始的请求数据
+ )
+ val fundsPager = fundsMyPager.createPager(viewModelScope)
+
+ // 小日历管理
+ var calDate = LocalDate.now() // 保存当前日期
+
+ init {
+ doInIoThreadNoDialog {
+ fundsMyPager.updateParams { it.copy(depSysid = MMKVUtil.get(Global.DEP_SYS_ID)) }
+ val firstDate = TimeUtils.getRecentMonthsDate(1)
+ _dateRange.value = Pair(firstDate, Date())
+ val localDate = LocalDate.now()
+ getDaysInfo(localDate.year.toString(), localDate.monthValue.toString(), false)
+ // 获取汇总数据
+ getTotalList(TimeUtils.formatDate(firstDate), TimeUtils.formatDate(Date()))
+ // 获取完统计信息默认选中第一个
+ if (_totalList.value.isNotEmpty()) {
+ _curSearchState.value = _totalList.value[0]
+ updateParams(_totalList.value[0].payStateValue.toString(), "")
+ }
+ }
+ }
+
+ fun updateCurSearchState(data: FundsTotalListResponse.Data) {
+ _curSearchState.value = data
+ }
+
+ /**
+ * 获取汇总数据
+ */
+ suspend fun getTotalList(begDate: String, endDate: String) {
+ val response = apiService.getFundsTotalList(
+ FundsTotalListRequest(
+ depSysid = MMKVUtil.get(Global.DEP_SYS_ID),
+ begDate = begDate,
+ endData = endDate,
+ )
+ )
+ if (response.code == 1) {
+ _totalList.value = response.data
+ } else {
+ Toasty.showTipsDialog(response.msg)
+ }
+ }
+
+ fun updateParams(searchType: String, like: String) {
+ fundsMyPager.updateParams {
+ it.copy(
+ begDate = TimeUtils.formatDate(_dateRange.value.first),
+ endData = TimeUtils.formatDate(_dateRange.value.second),
+ like = like,
+ searchType = searchType,
+ )
+ }
+ // 如果更改了右侧列表的参数,则同步的,左侧统计列表也要更改
+ doInIoThreadNoDialog {
+ getTotalList(TimeUtils.formatDate(_dateRange.value.first),TimeUtils.formatDate(_dateRange.value.second))
+ }
+ }
+
+ fun showQueryBalanceDialog() {
+ _queryBalanceDialogData.value = AuthDialogData(true, onDismiss = {
+ _queryBalanceDialogData.update { it.copy(showDialog = false) }
+ })
+ }
+
+ fun getDaysInfo(year: String, month: String, showDialog: Boolean = true) {
+ doInIoThreadWith(showDialog, "正在加载日期信息...") {
+ val response = apiService.getDaysInfo(year, month)
+ val tempList = mutableListOf()
+ response.data.forEach {
+ tempList.add(it.date.split("-")[2].toInt())
+ }
+ _daysInfo.value = tempList
+ }
+ }
+
+ fun updateDateRange(dateRange: Pair) {
+ _dateRange.value = dateRange
+ }
+
+ fun showTicketMoreDialog(id: String) {
+ doInIoThread {
+ _ticketMoreDialog.value = TicketMoreDialogData(
+ showDialog = true,
+ ticketId = id,
+ onDismiss = {
+ _ticketMoreDialog.update { it.copy(showDialog = false) }
+ })
+ }
+ }
+
+ fun selectDetail(czSysId: String, function: (PurchaseDataResponse.Data) -> Unit) {
+ doInIoThreadThenUI(loadingTips = "正在加载单据详情...", onIO = {
+ apiService.getPurchaseDetail(czSysId)
+ }) { result ->
+ if (result.code == 1) {
+ function(result.data)
+ } else {
+ Toasty.showTipsDialog(result.msg)
+ }
+ }
+ }
+
+ fun unPay(sysId: String) {
+ doInIoThread {
+ val response = apiService.getUnPay(sysid = sysId, remark = "Pad端操作")
+ if (response.code == 1) {
+ Toasty.success("操作成功")
+ } else {
+ Toasty.showTipsDialog(response.msg)
+ }
+ }
+ }
+
+ private val _dateRangeSelectDialogData = MutableStateFlow(DateRangeSelectDialogData())
+ val dateRangeSelectDialogData = _dateRangeSelectDialogData.asStateFlow()
+
+ fun showDateRangeSelectDialog(onFinish: () -> Unit) {
+ doInIoThreadNoDialog {
+ _dateRangeSelectDialogData.value = DateRangeSelectDialogData(
+ showDialog = true,
+ default = _dateRange.value,
+ onDismiss = {
+ _dateRangeSelectDialogData.update { it.copy(showDialog = false) }
+ },
+ onClickRangeDay = { dateStrStart, dateStrEnd ->
+ // 切换时间范围
+ updateDateRange( Pair(dateStrStart, dateStrEnd))
+ onFinish()
+ }
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/HomeScreen.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/HomeScreen.kt
new file mode 100644
index 0000000..2d1f705
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/HomeScreen.kt
@@ -0,0 +1,643 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.SegmentedButton
+import androidx.compose.material3.SegmentedButtonDefaults
+import androidx.compose.material3.SingleChoiceSegmentedButtonRow
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MainFuncFrame
+import com.bbitcn.f8.pad.base.MyInfoCard
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavController
+import androidx.navigation.compose.rememberNavController
+import coil3.compose.AsyncImage
+import com.bbitcn.f8.pad.ui.screen.dialog.MessageDialog
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.DateRangePickTextFiled
+import com.bbitcn.f8.pad.base.InfoText
+import com.bbitcn.f8.pad.base.MyAnimatedVisibility
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.MyOutlineButton
+import com.bbitcn.f8.pad.base.MyTable
+import com.bbitcn.f8.pad.base.RedPointBadge
+import com.bbitcn.f8.pad.base.isLandscape
+import com.bbitcn.f8.pad.ui.screen.dialog.DateRangeSelectDialog
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.utils.TTSManager
+
+/**
+ *
+ * @Description 主功能-预约售茧
+ * @Author DuanKaiji
+ * @CreateTime 2024年08月02日 11:25:32
+ */
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun HomeScreenPV() {
+ HomeScreen(rememberNavController())
+}
+
+@Composable
+fun HomeScreen(
+ navController: NavController,
+ homeViewModel: HomeViewModel = viewModel()
+) {
+ val dateRangeSelectDialogData by homeViewModel.dateRangeSelectDialogData.collectAsState()
+ val messageDialogData by homeViewModel.messageDialogData.collectAsState()
+ MainFuncFrame {
+ if (isLandscape()) {
+ HomeScreenInLandscape(navController, homeViewModel)
+ } else {
+ HomeScreenInPortrait(navController, homeViewModel)
+ }
+ }
+ MessageDialog(messageDialogData)
+ DateRangeSelectDialog(dateRangeSelectDialogData)
+}
+
+@Composable
+fun HomeScreenInLandscape(
+ navController: NavController,
+ homeViewModel: HomeViewModel = viewModel()
+) {
+ Row(modifier = M.fillMaxSize()) {
+ Column(
+ modifier = M
+ .fillMaxHeight()
+ .weight(0.75f)
+ .padding(end = 15.dp)
+ ) {
+ Row(modifier = M.weight(2f)) {
+ MyInfoCard(
+ modifier = M
+ .weight(1f)
+ .fillMaxHeight()
+ .padding(end = 15.dp, bottom = 15.dp)
+ ) {
+ TodaySilkPrice(homeViewModel)
+ }
+ MyInfoCard(
+ modifier = M
+ .weight(1f)
+ .fillMaxHeight()
+ .padding(bottom = 15.dp)
+ ) {
+ WeatherInfo(homeViewModel)
+ }
+ }
+ MyInfoCard(
+ modifier = M
+ .weight(3f)
+ .fillMaxWidth()
+ .padding(bottom = 15.dp)
+ ) {
+ AcquireDynamic(homeViewModel)
+ }
+ MyInfoCard(
+ modifier = M
+ .weight(1f)
+ .fillMaxWidth()
+ ) {
+ ShortCut(homeViewModel)
+ }
+ }
+ Column(
+ modifier = M
+ .fillMaxHeight()
+ .weight(0.25f)
+ ) {
+ MyInfoCard(
+ modifier = M
+ .weight(2f)
+ .fillMaxWidth()
+ .padding(bottom = 15.dp)
+ ) {
+ LoginInfo(navController, homeViewModel)
+ }
+ MyInfoCard(
+ modifier = M
+ .weight(4f)
+ .fillMaxWidth()
+ ) {
+ MessageInfo(homeViewModel)
+ }
+ }
+ }
+}
+
+@Composable
+fun HomeScreenInPortrait(
+ navController: NavController,
+ homeViewModel: HomeViewModel = viewModel()
+) {
+ Column(modifier = M.fillMaxSize()) {
+ Row(
+ modifier = M
+ .weight(1f)
+ .padding(bottom = 15.dp)
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(15.dp)
+ ) {
+ MyInfoCard(
+ modifier = M
+ .weight(1f)
+ .fillMaxWidth()
+ ) {
+ WeatherInfo(homeViewModel)
+ }
+ MyInfoCard(
+ modifier = M
+ .weight(1f)
+ .fillMaxWidth()
+ ) {
+ LoginInfo(navController, homeViewModel)
+ }
+ }
+ Row(
+ modifier = M
+ .weight(1.5f)
+ .fillMaxWidth()
+ .padding(bottom = 15.dp),
+ horizontalArrangement = Arrangement.spacedBy(15.dp)
+ ) {
+ MyInfoCard(
+ modifier = M
+ .weight(1f)
+ .fillMaxWidth()
+ ) {
+ TodaySilkPrice(homeViewModel)
+ }
+ MyInfoCard(
+ modifier = M
+ .weight(1f)
+ .fillMaxWidth()
+ ) {
+ MessageInfo(homeViewModel)
+ }
+ }
+ MyInfoCard(
+ modifier = M
+ .weight(2f)
+ .fillMaxWidth()
+ .padding(bottom = 15.dp)
+ ) {
+ AcquireDynamic(homeViewModel)
+ }
+ MyInfoCard(
+ modifier = M
+ .weight(0.5f)
+ .fillMaxWidth()
+ ) {
+ ShortCut(homeViewModel)
+ }
+ }
+}
+
+@Composable
+fun FuncTitle(title: String) {
+ Text(
+ title,
+ fontSize = MaterialTheme.typography.titleLarge.fontSize,
+ modifier = M.padding(10.dp)
+ )
+}
+
+
+@Composable
+fun TodaySilkPrice(homeViewModel: HomeViewModel) {
+ val priceInfo by homeViewModel.todayPrice.collectAsState()
+ Column(modifier = M.padding(5.dp)) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ FuncTitle("今日茧价")
+ Text(
+ text = "(元/公斤)"
+ /**+ if (isLandscape()) "" else "\n" + "${}发布"**/
+ ,
+ color = MyColors.Gray,
+ fontSize = MaterialTheme.typography.bodySmall.fontSize,
+ )
+ Spacer(modifier = M.weight(1f))
+ MyOutlineButton(M.padding(end = 10.dp), "刷新", onClick = {
+ homeViewModel.refreshTodayPrice()
+ })
+ }
+ MyTable(
+ Modifier
+ .fillMaxWidth()
+ .padding(end = 5.dp),
+ listOf("茧别", "指导价", "公司均价", "本站均价"),
+ listOf(1f, 1f, 1f, 1f),
+ priceInfo.map {
+ listOf(it.name, "" + it.minprice + "~" + it.maxprice, it.gsprice, it.depprice)
+ }
+ )
+ }
+}
+
+@Composable
+fun LoginInfo(navController: NavController, homeViewModel: HomeViewModel) {
+ val userInfo by homeViewModel.userInfo.collectAsState()
+ Column(modifier = M.padding(5.dp)) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ FuncTitle("登录信息")
+ Spacer(modifier = M.weight(1f))
+ }
+ Row(
+ modifier = M.fillMaxWidth()
+ ) {
+ Column(
+ modifier = M
+ .fillMaxHeight()
+ .padding(10.dp),
+ verticalArrangement = Arrangement.SpaceBetween,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.user_icon),
+ contentDescription = null,
+ modifier = M
+ .size(50.dp)
+ )
+ MyButton(
+ M.wrapContentWidth().padding(top = 10.dp),
+ text = "登出",
+ contentPadding = PaddingValues(
+ start = 15.dp,
+ top = 0.dp,
+ end = 15.dp,
+ bottom = 0.dp
+ ),
+ colors = MyColors.Red,
+ onClick = {
+ Toasty.showConfirmDialog("确定退出登录吗?") {
+ homeViewModel.logout {
+ navController.navigate("login") {
+ popUpTo(navController.graph.startDestinationId) {
+ inclusive = true
+ }
+ launchSingleTop = true
+ }
+ }
+ }
+ }
+ )
+ }
+ LazyColumn(modifier = M.weight(1f)) {
+ item {
+ InfoText("姓名:", userInfo.name)
+ InfoText("企业:", userInfo.tenantname)
+ InfoText("部门:", userInfo.depname)
+ InfoText("批次:", userInfo.sgcjname)
+ InfoText("时间:", userInfo.sgdate)
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun WeatherInfo(homeViewModel: HomeViewModel) {
+ val weatherInfo by homeViewModel.weatherInfo.collectAsState()
+ Box(modifier = M.padding(5.dp)) {
+ Image(
+ painter = painterResource(id = R.drawable.bg_weather),
+ contentDescription = null,
+ alignment = Alignment.BottomCenter,
+ modifier = M.fillMaxSize()
+ )
+ Column {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ FuncTitle("天气预报")
+ Text(
+ text = if (weatherInfo.city.isNotEmpty()) "(${weatherInfo.province}${weatherInfo.city})" else "",
+ color = MyColors.Gray,
+ fontSize = MaterialTheme.typography.bodySmall.fontSize,
+ )
+ Spacer(modifier = M.weight(1f))
+ MyOutlineButton(M.padding(end = 10.dp), "刷新", onClick = {
+ homeViewModel.refreshWeatherInfo()
+ })
+ }
+ LazyRow {
+ items(weatherInfo.casts) {
+ val index = weatherInfo.casts.indexOf(it)
+ WeatherItem(
+ it.weekstr,
+ it.date,
+ it.dayweatherpic,
+ it.nighttemp + "~" + it.daytemp + "℃",
+ index
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun WeatherItem(
+ week: String,
+ date: String,
+ imageUrl: String,
+ temperature: String,
+ position: Int
+) {
+ val isImportant = position == 0 || position == 1
+ Column(
+ modifier = M.padding(top = 10.dp, start = 10.dp, end = 10.dp),
+ verticalArrangement = Arrangement.spacedBy(5.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = if (position == 0) "今天" else if (position == 1) "明天" else week,
+ fontSize = if (isImportant) MaterialTheme.typography.bodyLarge.fontSize
+ else MaterialTheme.typography.bodyMedium.fontSize,
+ )
+ Text(
+ text = date,
+ fontSize = if (isImportant) MaterialTheme.typography.bodyLarge.fontSize
+ else MaterialTheme.typography.bodyMedium.fontSize,
+ )
+ AsyncImage(
+ model = imageUrl,
+ modifier = M.size(30.dp),
+ contentDescription = null,
+ )
+ Text(
+ text = temperature,
+ color = MyColors.BlueGreen,
+ fontSize = if (isImportant) MaterialTheme.typography.bodyLarge.fontSize
+ else MaterialTheme.typography.bodyMedium.fontSize,
+ )
+ }
+}
+
+@Composable
+fun MessageInfo(homeViewModel: HomeViewModel) {
+ Box(modifier = M.padding(5.dp)) {
+ Image(
+ painter = painterResource(id = R.drawable.bg_weather),
+ contentDescription = null,
+ alignment = Alignment.BottomCenter,
+ modifier = M.fillMaxSize()
+ )
+ Column {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ FuncTitle("消息通知")
+ Spacer(modifier = M.weight(1f))
+ MyOutlineButton(M.padding(end = 10.dp), "刷新", onClick = {
+// homeViewModel.refreshTodayPrice()
+ })
+ }
+ LazyColumn {
+ items(count = 0) { index ->
+ MessageItem(
+ homeViewModel,
+ true,
+ true,
+ "管理员",
+ "2024-08-02 11:25:32",
+ "正在开发中"
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun MessageItem(
+ homeViewModel: HomeViewModel,
+ isFromSystem: Boolean,
+ unRead: Boolean,
+ name: String,
+ time: String,
+ message: String
+) {
+ Row(
+ modifier = M
+ .clickable {
+ homeViewModel.showMsgDialog(name, time, message)
+ }
+ .padding(bottom = 5.dp),
+ verticalAlignment = Alignment.CenterVertically) {
+ if (unRead) {
+ RedPointBadge {
+ Image(
+ painter = painterResource(id = if (isFromSystem) R.drawable.msg_system else R.drawable.msg_user),
+ contentDescription = null,
+ modifier = M.size(50.dp)
+ )
+ }
+ } else {
+ Image(
+ painter = painterResource(id = if (isFromSystem) R.drawable.msg_system else R.drawable.msg_user),
+ contentDescription = null,
+ modifier = M.size(40.dp)
+ )
+ }
+ Column(modifier = M.padding(horizontal = 10.dp)) {
+ Row {
+ Text(
+ text = name,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize
+ )
+ Spacer(modifier = M.weight(1f))
+ Text(
+ text = time,
+ fontSize = MaterialTheme.typography.bodySmall.fontSize
+ )
+ }
+ Text(
+ text = message,
+ maxLines = 1,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ fontWeight = if (unRead) FontWeight.Bold else FontWeight.Thin,
+ overflow = TextOverflow.Ellipsis,
+ )
+ }
+ }
+}
+
+@Composable
+fun ShortCut(homeViewModel: HomeViewModel) {
+ Row(modifier = M.fillMaxHeight(), verticalAlignment = Alignment.CenterVertically) {
+ Box(
+ modifier = M
+ .fillMaxHeight()
+ .background(
+ brush = Brush.linearGradient(
+ colors = listOf(Color(0xFFC0EAE3), Color(0xFFFFFFFF)), // 渐变颜色从左到右
+ start = Offset(0f, 0f),
+ end = Offset.Infinite.copy(x = Float.POSITIVE_INFINITY, y = 0f) // 渐变终点(右侧)
+ )
+ )
+ .padding(horizontal = 16.dp, vertical = 8.dp),
+ contentAlignment = Alignment.Center
+ ) { // 添加内边距)
+ Text(
+ text = "快\n捷\n方\n式",
+ color = MyColors.BlueGreen,
+ fontWeight = FontWeight.Bold,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ textAlign = TextAlign.Center
+ )
+ }
+ LazyRow(verticalAlignment = Alignment.CenterVertically) {
+ item {
+// ShowCutItem(R.drawable.user_add, "新增用户", onClick = {})
+// ShowCutItem(R.drawable.user_query, "用户查询", onClick = {})
+// ShowCutItem(R.drawable.silk_add, "新增收茧", onClick = {})
+// ShowCutItem(R.drawable.silk_trans, "茧转移", onClick = {})
+// ShowCutItem(R.drawable.device_link, "设备连接", onClick = {})
+ }
+ }
+ }
+}
+
+@Composable
+fun ShowCutItem(iconId: Int, title: String, onClick: () -> Unit) {
+ Column(
+ modifier = M
+ .padding(10.dp)
+ .clickable { onClick() },
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ painter = painterResource(id = iconId),
+ contentDescription = null,
+ modifier = M.size(50.dp)
+ )
+ Text(
+ text = title,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize
+ )
+ }
+}
+
+
+@Composable
+fun AcquireDynamic(homeViewModel: HomeViewModel) {
+ val acquireData by homeViewModel.acquireData.collectAsState()
+ Column(modifier = M.padding(5.dp)) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ FuncTitle("收购动态")
+ var selectedIndex by remember { mutableIntStateOf(0) }
+ val options = listOf("今日", "昨日", "近7天", "本月", "自定义")
+ SingleChoiceSegmentedButtonRow(
+ modifier = M
+ .height(35.dp)
+ .padding(start = 5.dp),
+ space = 2.dp
+ ) {
+ options.forEachIndexed { index, label ->
+ SegmentedButton(
+ colors = SegmentedButtonDefaults.colors(activeContainerColor = MyColors.BlueGreen),
+ icon = {
+ if (selectedIndex == index) {
+ Icon(
+ imageVector = Icons.Default.Check,
+ contentDescription = null,
+ tint = MyColors.White
+ )
+ }
+ },
+ shape = SegmentedButtonDefaults.itemShape(
+ index = index,
+ count = options.size
+ ),
+ onClick = {
+ selectedIndex = index
+ homeViewModel.refreshAcquireDataByType(index)
+ },
+ selected = index == selectedIndex,
+ label = {
+ Text(
+ text = label,
+ color = if (selectedIndex == index) MyColors.White else MyColors.Black,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize
+ )
+ }
+ )
+ }
+ }
+ MyAnimatedVisibility(selectedIndex == 4) {
+ val dateRange by homeViewModel.dateRange.collectAsState()
+ DateRangePickTextFiled(
+ M
+ .padding(start = 10.dp)
+ .width(150.dp),
+ dateRange
+ ) {
+ homeViewModel.showDateRangeSelectDialog()
+ }
+ }
+// Text(
+// text = "${acquireData.refreshTime}发布",
+// color = MyColors.Gray,
+// fontSize = MaterialTheme.typography.bodySmall.fontSize,
+// )
+ Spacer(modifier = M.weight(1f))
+ MyOutlineButton(M.padding(end = 10.dp), "刷新", onClick = {
+ homeViewModel.refreshAcquireData()
+ })
+ }
+ MyTable(
+ modifier = M.fillMaxWidth(),
+ headerStrings = listOf(
+ "茧站",
+ "上车茧重", "上车茧金额", "上车价格",
+ "下足茧重", "下足茧金额", "下车价格",
+ "合计重量", "合计金额", "价格", "制单数"
+ ),
+ ratio = listOf(1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f),
+ acquireData.map {
+ listOf(
+ it.depname, it.scjweightsum, it.scmoneysum, it.scprice,
+ it.xzjweightsum, it.xzmoneysum, it.xzprice,
+ it.hjjweightsum, it.hjmoneysum, it.hjprice, it.zhidan
+ )
+ },
+ verticalPadding = 7.5.dp
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/HomeViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/HomeViewModel.kt
new file mode 100644
index 0000000..a1ca3b3
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/HomeViewModel.kt
@@ -0,0 +1,158 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc;
+
+import com.bbitcn.f8.pad.ui.screen.dialog.MessageDialogData
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.model.net.response.PurchaseDetailResponse
+import com.bbitcn.f8.pad.model.net.response.TodayPriceResponse
+import com.bbitcn.f8.pad.model.net.response.UserInfoResponse
+import com.bbitcn.f8.pad.model.net.response.WeatherResponse
+import com.bbitcn.f8.pad.ui.screen.dialog.DateRangeSelectDialogData
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.MyUtil
+import com.bbitcn.f8.pad.utils.TimeUtils
+import com.bbitcn.f8.pad.utils.TimeUtils.getRecentMonthsDate
+import com.bbitcn.f8.pad.utils.global.Global
+import com.bbitcn.f8.pad.utils.global.RxTag
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import java.util.Date
+
+
+class HomeViewModel : BaseViewModel() {
+
+ private val _todayPrice = MutableStateFlow>(emptyList())
+ val todayPrice = _todayPrice.asStateFlow()
+
+ private val _acquireData = MutableStateFlow>(emptyList())
+ val acquireData = _acquireData.asStateFlow()
+
+ private val _userInfo = MutableStateFlow(UserInfoResponse.Data())
+ val userInfo = _userInfo.asStateFlow()
+
+ private val _weatherInfo = MutableStateFlow(WeatherResponse.Data())
+ val weatherInfo = _weatherInfo.asStateFlow()
+
+ private val _messageDialogData = MutableStateFlow(MessageDialogData())
+ val messageDialogData: StateFlow = _messageDialogData.asStateFlow()
+
+ init {
+ refreshWeatherInfo(false)
+ refreshTodayPrice(false)
+ doInIoThreadNoDialog {
+ val firstDate = getRecentMonthsDate(1)
+ _dateRange.value = Pair(firstDate, Date())
+ // 收购动态 默认今日
+ refreshAcquireData(TimeUtils.getRecentDaysDate(0), TimeUtils.getRecentDaysDate(0), false)
+ // 用户信息
+ val userInfo = apiService.getUserInfo()
+ if (userInfo.code == 1) {
+ _userInfo.value = userInfo.data
+ MMKVUtil.put(Global.USER_NAME, userInfo.data.name)
+ MMKVUtil.put(Global.USER_ID, userInfo.data.id)
+ MMKVUtil.put(Global.DEP_SYS_ID, userInfo.data.depsysid)
+ MMKVUtil.put(Global.DEP_CODE, userInfo.data.depcode)
+ MMKVUtil.put(Global.DEP_NAME, userInfo.data.depname)
+ }
+ }
+ }
+
+ fun refreshAcquireData(
+ startDate: Date = _dateRange2.value.first,
+ endDate: Date = _dateRange2.value.second,
+ showLoadingDialog: Boolean = true,
+ ) {
+ doInIoThreadWith(showLoadingDialog, "正在刷新收购动态") {
+ _dateRange2.value = startDate to endDate
+ val result = apiService.getAcquireData(
+ startdate = TimeUtils.formatDate(startDate),
+ endate = TimeUtils.formatDate(endDate)
+ )
+ if (result.code == 1) {
+ _acquireData.value = result.data
+ }
+ }
+ }
+
+ fun refreshTodayPrice(showLoadingDialog: Boolean = true) {
+ doInIoThreadWith(showLoadingDialog, loadingTips = "正在刷新今日茧价") {
+ val result = apiService.getTodayPrice()
+ if (result.code == 1) {
+ _todayPrice.value = result.data
+ }
+ }
+ }
+
+ fun refreshWeatherInfo(showLoadingDialog: Boolean = true) {
+ doInIoThreadWith(showLoadingDialog, loadingTips = "正在刷新天气信息") {
+ val addr = MMKVUtil.get(Global.WEATHER_ADDR, "")
+ val result = apiService.getWeather(addr)
+ if (result.code == 1) {
+ _weatherInfo.value = result.data
+ }
+ }
+ }
+
+ fun showMsgDialog(name: String, time: String, message: String) {
+ _messageDialogData.value = MessageDialogData(
+ showDialog = true,
+ username = name,
+ time = time,
+ content = message,
+ onDismiss = {
+ _messageDialogData.update { it.copy(showDialog = false) }
+ }
+ )
+ }
+
+ fun logout(onFinished: () -> Unit) {
+ // 退出登录
+ doInIoThreadThenUI(loadingTips = "正在退出登录", onIO = {
+ MMKVUtil.remove(RxTag.AUTH_USER_NAME)
+ MMKVUtil.remove(RxTag.ACCESS_TOKEN)
+ MMKVUtil.remove(RxTag.REFRESH_TOKEN)
+ }) {
+ onFinished()
+ }
+ }
+
+ // 日期范围-自定义用
+ private val _dateRange = MutableStateFlow(Pair(Date(), Date()))
+ val dateRange = _dateRange.asStateFlow()
+
+ // 日期范围-刷新按钮用
+ private val _dateRange2 = MutableStateFlow(Pair(Date(), Date()))
+
+ fun refreshAcquireDataByType(type: Int) {
+ // "今日", "昨日", "近7天", "本月", "自定义"
+ when (type) {
+ 0 -> refreshAcquireData(TimeUtils.getRecentDaysDate(0), TimeUtils.getRecentDaysDate(0))
+ 1 -> refreshAcquireData(TimeUtils.getRecentDaysDate(1), TimeUtils.getRecentDaysDate(1))
+ 2 -> refreshAcquireData(TimeUtils.getRecentDaysDate(7), TimeUtils.getRecentDaysDate(0))
+ 3 -> refreshAcquireData(TimeUtils.getCurMonthStartDate(), TimeUtils.getCurMonthEndDate())
+ 4 -> refreshAcquireData(_dateRange.value.first, _dateRange.value.second)
+ }
+ }
+ private val _dateRangeSelectDialogData = MutableStateFlow(DateRangeSelectDialogData())
+ val dateRangeSelectDialogData = _dateRangeSelectDialogData.asStateFlow()
+
+ fun showDateRangeSelectDialog() {
+ doInIoThreadNoDialog {
+ _dateRangeSelectDialogData.value = DateRangeSelectDialogData(
+ showDialog = true,
+ onDismiss = {
+ _dateRangeSelectDialogData.update { it.copy(showDialog = false) }
+ },
+ default = _dateRange.value,
+ onClickRangeDay = { dateStrStart, dateStrEnd ->
+ // 切换时间范围
+ _dateRange.value = Pair(dateStrStart, dateStrEnd)
+ refreshAcquireData(
+ dateStrStart, dateStrEnd
+ )
+ }
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/PurchaseScreen.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/PurchaseScreen.kt
new file mode 100644
index 0000000..760ffbf
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/PurchaseScreen.kt
@@ -0,0 +1,464 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc
+
+import android.content.res.Configuration
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Card
+import androidx.compose.material3.DatePickerDefaults
+import androidx.compose.material3.DatePickerDialog
+import androidx.compose.material3.DateRangePicker
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ScrollableTabRow
+import androidx.compose.material3.Tab
+import androidx.compose.material3.TabRowDefaults
+import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.rememberDateRangePickerState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MainFuncFrame
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavController
+import androidx.navigation.compose.rememberNavController
+import androidx.paging.compose.collectAsLazyPagingItems
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.DateRangePickTextFiled
+import com.bbitcn.f8.pad.base.MyAnimatedVisibility
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.MyRefreshTableForCard
+import com.bbitcn.f8.pad.base.QueryTextField
+import com.bbitcn.f8.pad.base.TableContent
+import com.bbitcn.f8.pad.base.TableHeadLineCard
+import com.bbitcn.f8.pad.base.isLandscape
+import com.bbitcn.f8.pad.model.net.response.PurchaseDataResponse
+import com.bbitcn.f8.pad.ui.screen.dialog.AddTicketDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.DateRangeSelectDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.FaceDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.OCRDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.PriceDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.ScanDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.TareDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.TicketMoreDialog
+import com.bbitcn.f8.pad.ui.screen.view.drawer.DrawerViewModel
+import com.bbitcn.f8.pad.ui.screen.view.drawer.StateChartsOnclick
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.utils.TimeUtils
+import java.util.Date
+
+/**
+ *
+ * @Description 主功能-预约售茧
+ * @Author DuanKaiji
+ * @CreateTime 2024年08月02日 11:25:32
+ */
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun PurchaseScreenPV() {
+ PurchaseScreen(
+ rememberNavController(),
+ purchaseViewModel = viewModel(),
+ drawerViewModel = viewModel()
+ )
+}
+
+@Composable
+fun PurchaseScreen(
+ navController: NavController,
+ purchaseViewModel: PurchaseViewModel = viewModel(),
+ drawerViewModel: DrawerViewModel
+) {
+ val tabs = listOf(
+ "全部单据", "待定价扣皮", "待确认销售", "待支付", "已电子支付",
+ "已现金支付", "已弃售", "已混合支付", "已仪评", "不仪评"
+ )
+ val addTicketDialog by purchaseViewModel.addTicketDialog.collectAsState()
+ val ticketMoreDialogData by purchaseViewModel.ticketMoreDialogData.collectAsState()
+ val priceDialogData by purchaseViewModel.priceDialogData.collectAsState()
+ val tareDialogData by purchaseViewModel.tareDialogData.collectAsState()
+ val dateRangeSelectDialogData by purchaseViewModel.dateRangeSelectDialogData.collectAsState()
+
+ // 筛选条件1:时间范围
+ val queryDateRange by purchaseViewModel.dateRange.collectAsState()
+ // 筛选条件2:输入框
+ var queryInput by rememberSaveable { mutableStateOf("") }
+ // 筛选条件3:查询类型
+ var queryType by rememberSaveable { mutableStateOf(0) }
+
+ val info = purchaseViewModel.infoPager.collectAsLazyPagingItems()
+ // 查询方法
+ val updateParams = {
+ purchaseViewModel.updateParams(
+ queryDateRange.first,
+ queryDateRange.second,
+ queryInput,
+ queryType
+ )
+ info.refresh()
+ }
+ MainFuncFrame {
+ MyCard {
+ Column(modifier = M.padding(15.dp)) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ MyButton(modifier = M.padding(end = 10.dp), text = "新增", onClick = {
+ purchaseViewModel.openAddTicketDialog(navController)
+ })
+ if (isLandscape()) {
+ ScrollableTabRow(
+ modifier = M
+ .weight(1f),
+ selectedTabIndex = queryType,
+ containerColor = MyColors.Transparent,
+ edgePadding = 10.dp,
+ indicator = { tabPositions ->
+ TabRowDefaults.SecondaryIndicator(
+ M.tabIndicatorOffset(tabPositions[queryType]),
+ color = MyColors.BlueGreen,
+ )
+ }
+ ) {
+ tabs.forEachIndexed { index, title ->
+ Tab(
+ text = {
+ Text(
+ title,
+ color = if (queryType == index) MyColors.BlueGreen else MyColors.Black,
+ fontWeight = if (queryType == index) FontWeight.Bold else FontWeight.Normal,
+ fontSize = MaterialTheme.typography.bodyLarge.fontSize
+ )
+ },
+ selected = queryType == index,
+ onClick = {
+ queryType = index
+ updateParams()
+ }
+ )
+ }
+ }
+ } else {
+ Spacer(modifier = M.weight(1f))
+ }
+ DateRangePickTextFiled(dateRange = queryDateRange) {
+ purchaseViewModel.showDateRangeSelectDialog{
+ updateParams()
+ }
+ }
+ QueryTextField(
+ modifier = M
+ .padding(horizontal = 10.dp)
+ .width(170.dp),
+ text = queryInput
+ ) {
+ queryInput = it
+ updateParams()
+ }
+ }
+ if (!isLandscape()) {
+ ScrollableTabRow(
+ selectedTabIndex = queryType,
+ containerColor = MyColors.Transparent,
+ edgePadding = 0.dp,
+ modifier = M,
+ indicator = { tabPositions ->
+ TabRowDefaults.SecondaryIndicator(
+ M.tabIndicatorOffset(tabPositions[queryType]),
+ color = MyColors.BlueGreen,
+ )
+ }
+ ) {
+ tabs.forEachIndexed { index, title ->
+ Tab(
+ text = {
+ Text(
+ title,
+ color = if (queryType == index) MyColors.BlueGreen else MyColors.Black,
+ fontWeight = if (queryType == index) FontWeight.Bold else FontWeight.Normal,
+ fontSize = MaterialTheme.typography.bodyLarge.fontSize
+ )
+ },
+ selected = queryType == index,
+ onClick = {
+ queryType = index
+ updateParams()
+ }
+ )
+ }
+ }
+ }
+ InfoList(purchaseViewModel) {
+ drawerViewModel.openPurchaseDetailDrawer(
+ info = it,
+ StateChartsOnclick(pricing = {
+ // 定价
+ purchaseViewModel.showPriceDialog(it) {
+ updateParams()
+ }
+ drawerViewModel.closeDrawer()
+ },deduction ={ canTare ->
+ // 扣皮
+ purchaseViewModel.showTareDialog(it, canTare) {
+ updateParams()
+ }
+ drawerViewModel.closeDrawer()
+ }, confirmSale = {
+ // 确认售
+ purchaseViewModel.showConfirmDialog(it.czSysid) {
+ updateParams()
+ }
+ drawerViewModel.closeDrawer()
+ }, pay = {
+ // 支付
+ navController.navigate("pay/${it.czSysid}")
+ drawerViewModel.closeDrawer()
+ })
+ ) {
+ purchaseViewModel.showTicketMoreDialog(it.czSysid)
+ }
+ }
+ }
+ }
+ }
+ AddTicketDialog(addTicketDialog)
+ PriceDialog(priceDialogData)
+ TareDialog(tareDialogData)
+ TicketMoreDialog(ticketMoreDialogData)
+
+ val scanDialogData by purchaseViewModel.scanDialogData.collectAsState()
+ val ocrDialogData by purchaseViewModel.ocrDialogData.collectAsState()
+ val faceDialogData by purchaseViewModel.faceDialogData.collectAsState()
+ ScanDialog(scanDialogData)
+ OCRDialog(ocrDialogData)
+ FaceDialog(faceDialogData)
+ DateRangeSelectDialog(dateRangeSelectDialogData)
+}
+
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun DateRangePickerModal(
+ showDialog: Boolean = false,
+ onDateRangeSelected: (Pair) -> Unit,
+ onDismiss: () -> Unit
+) {
+ val dateRangePickerState = rememberDateRangePickerState()
+ MyAnimatedVisibility(showDialog) {
+ DatePickerDialog(
+ onDismissRequest = onDismiss,
+ confirmButton = {
+ TextButton(
+ onClick = {
+ onDateRangeSelected(
+ Pair(
+ dateRangePickerState.selectedStartDateMillis,
+ dateRangePickerState.selectedEndDateMillis
+ )
+ )
+ onDismiss()
+ }
+ ) { Text("确定") }
+ },
+ dismissButton = {
+ TextButton(onClick = onDismiss) { Text("取消") }
+ }
+ ) {
+ DateRangePicker(
+ state = dateRangePickerState,
+ title = {
+ Text("选择时间范围", fontSize = MaterialTheme.typography.headlineLarge.fontSize)
+ },
+ showModeToggle = false,
+ colors = DatePickerDefaults.colors(dayInSelectionRangeContainerColor = MyColors.BlueGreen),
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(500.dp)
+ .padding(16.dp)
+ )
+ }
+ }
+}
+
+@Composable
+fun InfoList(
+ purchaseViewModel: PurchaseViewModel,
+ onClick: (info: PurchaseDataResponse.Data) -> Unit
+) {
+ // 获取当前设备的屏幕配置,比如方向
+ val configuration = LocalConfiguration.current
+ // 判断屏幕方向,竖屏为两列,横屏为四列
+ val columns =
+ if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
+ 2 // 竖屏显示两列
+ } else {
+ 4 // 横屏显示四列
+ }
+ val info = purchaseViewModel.infoPager.collectAsLazyPagingItems()
+ val myPager = purchaseViewModel.infoMyPager
+ val isRefreshing by myPager.listIsRefreshing.collectAsState()
+ var selectIndex by rememberSaveable { mutableStateOf(-1) }
+ MyRefreshTableForCard(
+ modifier = M
+ .fillMaxWidth()
+ .padding(top = 5.dp),
+ isRefreshing = isRefreshing,
+ info = info,
+ key = { it.czSysid },
+ onFinishRefresh = {
+ myPager.setListIsRefreshClose()
+ },
+ columns = columns,
+ item = { data, index ->
+ InfoItem(isSelect = index == selectIndex, data = data) {
+ onClick(data)
+ selectIndex = index
+ }
+ }
+ )
+}
+
+@Composable
+fun InfoItem(
+ isSelect: Boolean,
+ data: PurchaseDataResponse.Data,
+ onClick: () -> Unit
+) {
+ MyCard(
+ radius = 7.dp,
+ elevation = 0.dp,
+ border = BorderStroke(width = 1.dp, color = MyColors.LightGray)
+ ) {
+ Column(modifier = M.clickable(onClick = onClick)) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = M.background(color = if (isSelect) MyColors.BlueGreen else MyColors.White)
+ ) {
+ Text(
+ text = data.nhName,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ modifier = M
+ .padding(horizontal = 10.dp)
+ .widthIn(max = 50.dp),
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis, // 超出部分显示省略号
+ color = if (isSelect) MyColors.White else MyColors.Black,
+ fontWeight = FontWeight.Bold
+ )
+ Spacer(modifier = M.weight(1f))
+ Text(
+ text = data.billCode.toString(),
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ color = if (isSelect) MyColors.White else MyColors.Black,
+ fontWeight = FontWeight.Bold
+ )
+ Card(
+ shape = RoundedCornerShape(topEnd = 7.dp, bottomStart = 7.dp),
+ modifier = M.padding(bottom = 5.dp)
+ ) {
+ Text(
+ text = data.billState,
+ modifier = M
+ .background(color = MyColors.Orange)
+ .padding(5.dp),
+ color = MyColors.White,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ )
+ }
+ }
+ TableHeadLineCard(
+ modifier = M.fillMaxWidth(),
+ listOf(
+ Pair("茧别", 1), Pair("毛重", 1), Pair("皮重", 1), Pair("扣重", 1),
+ Pair("净重", 1), Pair("单价", 1)
+ ), isSelect
+ )
+ LazyColumn(modifier = M.height(60.dp)) {
+ items(count = data.chengZhongItemSumList.size) { index ->
+ val item = data.chengZhongItemSumList[index]
+ TableContent(
+ M.fillMaxWidth().animateItem(), isSelect,
+ listOf(
+ Pair(item.sgTypeName, 1),
+ Pair(item.mweightSum.toString(), 1),
+ Pair(item.pweightSum.toString(), 1),
+ Pair(item.kweightSum.toString(), 1),
+ Pair(item.jweightSum.toString(), 1),
+ Pair(item.price.toString(), 1)
+ ), 3.5.dp
+ )
+ }
+ }
+ StateList(
+ data.ispPicing, data.isKouPiing,
+ data.billStateValue == 2, data.payStateValue == 3
+ )
+ }
+ }
+}
+
+@Composable
+fun StateList(hasPrice: Boolean, hasTare: Boolean, hasConfirm: Boolean, hasPay: Boolean) {
+ Row(modifier = M.fillMaxSize()) {
+ StateItem("定价", hasPrice, M.weight(1f))
+ StateItem("扣皮", hasTare, M.weight(1f))
+ StateItem(if (hasConfirm) "确认" else "确认售", hasConfirm, M.weight(1f))
+ StateItem("支付", hasPay, M.weight(1f))
+ }
+}
+
+@Composable
+fun StateItem(text: String, isSelect: Boolean, modifier: Modifier) {
+ Box(modifier = modifier, contentAlignment = Alignment.Center) {
+ Image(
+ painter = painterResource(if (isSelect) R.drawable.bg_state_sel else R.drawable.bg_state),
+ contentDescription = null,
+ contentScale = ContentScale.Fit,
+ modifier = M.fillMaxWidth()
+ )
+ Row(verticalAlignment = Alignment.CenterVertically, modifier = M.padding(start = 7.dp)) {
+ if (isSelect) {
+ Image(
+ painter = painterResource(R.drawable.state_sel),
+ contentDescription = null,
+ modifier = M.size(10.dp)
+ )
+ }
+ Text(
+ text = text,
+ color = if (isSelect) MyColors.BlueGreen else MyColors.Gray,
+ modifier = M.padding(horizontal = 5.dp),
+ fontSize = MaterialTheme.typography.labelSmall.fontSize,
+ )
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/PurchaseViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/PurchaseViewModel.kt
new file mode 100644
index 0000000..55c8c26
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/PurchaseViewModel.kt
@@ -0,0 +1,339 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc;
+
+import androidx.lifecycle.viewModelScope
+import androidx.navigation.NavController
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.model.net.request.PurchaseDataRequest
+import com.bbitcn.f8.pad.model.net.request.TareRequest
+import com.bbitcn.f8.pad.model.net.request.UpdateTicketPriceRequest
+import com.bbitcn.f8.pad.model.net.response.PurchaseDataResponse
+import com.bbitcn.f8.pad.ui.screen.dialog.AddTicketDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.DateRangeSelectDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.FaceDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.OCRDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.PriceDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.ScanDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.TareDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.TicketMoreDialogData
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.MyUtil
+import com.bbitcn.f8.pad.utils.TimeUtils
+import com.bbitcn.f8.pad.utils.TimeUtils.getRecentMonthsDate
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.idcard.IDCardUtils
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.nfc.NFCUtils
+import com.bbitcn.f8.pad.utils.global.Global
+import com.bbitcn.f8.pad.utils.pager.MyPager
+import com.bbitcn.f8.pad.utils.pager.PurchaseInfoPagingSource
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import java.util.Date
+
+
+class PurchaseViewModel : BaseViewModel() {
+
+ private val _addTicketDialog = MutableStateFlow(AddTicketDialogData())
+ val addTicketDialog: StateFlow = _addTicketDialog.asStateFlow()
+
+ private val _scanDialogData = MutableStateFlow(ScanDialogData())
+ val scanDialogData = _scanDialogData.asStateFlow()
+
+ private val _ocrDialogData = MutableStateFlow(OCRDialogData())
+ val ocrDialogData = _ocrDialogData.asStateFlow()
+
+ private val _faceDialogData = MutableStateFlow(FaceDialogData())
+ val faceDialogData = _faceDialogData.asStateFlow()
+
+ private val _ticketMoreDialog = MutableStateFlow(TicketMoreDialogData())
+ val ticketMoreDialogData = _ticketMoreDialog.asStateFlow()
+
+ private val _priceDialogData = MutableStateFlow(PriceDialogData())
+ val priceDialogData = _priceDialogData.asStateFlow()
+
+ private val _tareDialogData = MutableStateFlow(TareDialogData())
+ val tareDialogData = _tareDialogData.asStateFlow()
+
+ val infoMyPager = MyPager(
+ pagingSourceFactory = { PurchaseInfoPagingSource(it) },
+ initialRequestData = PurchaseDataRequest(), // 传入初始的请求数据
+ )
+
+ val infoPager = infoMyPager.createPager(viewModelScope)
+
+ init {
+ doInIoThreadNoDialog {
+ // 获取一个月前的日期
+ updateParams(getRecentMonthsDate(1), Date(), "", 0)
+ }
+ }
+
+ fun updateParams(first: Date, second: Date, queryInput: String, queryType: Int) {
+ infoMyPager.updateParams {
+ it.copy(
+ begDate = TimeUtils.formatDate(first),
+ endData = TimeUtils.formatDate(second),
+ like = queryInput,
+ sgSearchState = queryType.toString()
+ )
+ }
+ }
+
+ fun openAddTicketDialog(navController: NavController) {
+ _addTicketDialog.value = AddTicketDialogData(showDialog = true,
+ onDismiss = {
+ _addTicketDialog.update { it.copy(showDialog = false) }
+ },
+ navToWeight = { sysId ->
+ navController.navigate("weight/${sysId}")
+ },
+ cardReaderForUserCard = {
+ doInIoThread {
+ _scanDialogData.value = ScanDialogData(showDialog = true, isNFC = true,
+ onDismiss = {
+ closeScanDialog()
+ }
+ )
+ NFCUtils.init(isPayCard = false) {
+ getFarmerInfoByICCardAndToWeight(navController, it) {
+ closeScanDialog()
+ }
+ }
+ }
+ },
+ cardReaderForIdCard = {
+ doInIoThread {
+ _scanDialogData.value = ScanDialogData(showDialog = true, isNFC = false,
+ onDismiss = {
+ closeScanDialog()
+ }
+ )
+ // 初始化身份证读卡模块
+ IDCardUtils.openGPIO()
+ IDCardUtils.openDevice {
+ getFarmerInfoByIdCardAndToWeight(navController, it.id) {
+ closeScanDialog()
+ }
+ }
+ }
+ },
+ cardReaderForBankCard = {
+ doInIoThread {
+ _scanDialogData.value = ScanDialogData(showDialog = true, isNFC = true,
+ onDismiss = {
+ closeScanDialog()
+ }
+ )
+ NFCUtils.init(isPayCard = true) {
+ getFarmerInfoByBankCardAndToWeight(navController, it) {
+ closeScanDialog()
+ }
+ }
+ }
+ },
+ ocrForIdCard = {
+ doInIoThread {
+ _ocrDialogData.value = OCRDialogData(
+ showDialog = true,
+ identityType = 0,
+ onIdentityIdCard = { name, gender, idCard, address ->
+ getFarmerInfoByIdCardAndToWeight(navController, idCard) {
+ _ocrDialogData.value = _ocrDialogData.value.copy(showDialog = false)
+ }
+ },
+ onDismiss = {
+ _ocrDialogData.value = _ocrDialogData.value.copy(showDialog = false)
+ }
+ )
+ }
+ },
+ ocrForBankCard = {
+ doInIoThread {
+ _ocrDialogData.value = OCRDialogData(
+ showDialog = true,
+ identityType = 1,
+ onIdentityBankCard = { bankCode ->
+ getFarmerInfoByBankCardAndToWeight(navController, bankCode) {
+ _ocrDialogData.value = _ocrDialogData.value.copy(showDialog = false)
+ }
+ },
+ onDismiss = {
+ _ocrDialogData.value = _ocrDialogData.value.copy(showDialog = false)
+ }
+ )
+ }
+ },
+ faceRecognition = {
+ doInIoThread() {
+ _faceDialogData.value = FaceDialogData(showDialog = true, isRegister = false,
+ isSystemUser = false, onDismiss = {
+ _faceDialogData.update { it.copy(showDialog = false) }
+ }, onRecognizeFace = { userId, faceToken ->
+ navController.navigate("weight/$userId")
+ }
+ )
+ }
+ }
+ )
+ }
+
+ fun getFarmerInfoByIdCardAndToWeight(
+ navController: NavController,
+ idCard: String,
+ onSuccess: () -> Unit
+ ) {
+ doInIoThreadThenUI("正在根据身份证获取农户信息", onIO = {
+ apiService.getFarmersInfoByIdCard(idCard)
+ }) { userInfo ->
+ if (userInfo.code == 1) {
+ onSuccess()
+ navController.navigate("weight/${userInfo.data.sysid}")
+ } else {
+ Toasty.error(userInfo.msg)
+ }
+ }
+ }
+
+ fun getFarmerInfoByBankCardAndToWeight(
+ navController: NavController,
+ bankCard: String,
+ onSuccess: () -> Unit
+ ) {
+ doInIoThreadThenUI("正在根据银行卡获取农户信息", onIO = {
+ apiService.getFarmersInfoByBankCard(bankCard)
+ }) { userInfo ->
+ if (userInfo.code == 1) {
+ onSuccess()
+ navController.navigate("weight/${userInfo.data.sysid}")
+ } else {
+ Toasty.error(userInfo.msg)
+ }
+ }
+ }
+
+ fun getFarmerInfoByICCardAndToWeight(
+ navController: NavController,
+ icCard: String,
+ onSuccess: () -> Unit
+ ) {
+ doInIoThreadThenUI("正在根据农户卡获取农户信息", onIO = {
+ apiService.getFarmersInfoByUserCard(icCard)
+ }) { userInfo ->
+ if (userInfo.code == 1) {
+ onSuccess()
+ _scanDialogData.update { it.copy(showDialog = false) }
+ navController.navigate("weight/${userInfo.data.sysid}")
+ } else {
+ _scanDialogData.update { it.copy(showDialog = false) }
+ Toasty.error(userInfo.msg)
+ }
+ }
+ }
+
+ fun closeScanDialog() {
+ doInIoThreadNoDialog {
+ _scanDialogData.update { it.copy(showDialog = false) }
+ if (_scanDialogData.value.isNFC) {
+ // 关闭NFC Reader Mode
+ NFCUtils.disableReaderMode()
+ } else {
+ // 关闭身份证读卡模块
+ IDCardUtils.closeGPIO()
+ }
+ }
+ }
+
+ fun showPriceDialog(data: PurchaseDataResponse.Data,onSuccess: () -> Unit) {
+ doInIoThread {
+ _priceDialogData.value = PriceDialogData(showDialog = true,data = data,
+ onSave = { request: UpdateTicketPriceRequest ->
+ doInIoThread("正在保存中") {
+ val result = apiService.updateTicketPrice(request)
+ if (result.code == 1) {
+ Toasty.success("保存成功")
+ // 退出弹窗
+ _priceDialogData.update { it.copy(showDialog = false) }
+ onSuccess()
+ } else {
+ Toasty.error(result.msg)
+ }
+ }
+ },
+ onDismiss = {
+ _priceDialogData.update { it.copy(showDialog = false) }
+ }
+ )
+ }
+ }
+
+ fun showTareDialog(data: PurchaseDataResponse.Data, canTare: Boolean,onSuccess: () -> Unit) {
+ doInIoThread {
+ _tareDialogData.value =
+ TareDialogData(showDialog = true, data = data, canTare = canTare,
+ onSave = { request: TareRequest ->
+ doInIoThread("正在保存中") {
+ val result = apiService.updateTicketWeight(request)
+ if (result.code == 1) {
+ Toasty.success("保存成功")
+ // 退出弹窗
+ _tareDialogData.update { it.copy(showDialog = false) }
+ onSuccess()
+ } else {
+ Toasty.error(result.msg)
+ }
+ }
+ },
+ onDismiss = {
+ _tareDialogData.update { it.copy(showDialog = false) }
+ }
+ )
+ }
+ }
+
+ fun showConfirmDialog(czsysid: String,onSuccess: () -> Unit) {
+ Toasty.showConfirmDialog("是否确认销售?") {
+ doInIoThread("正在确认销售") {
+ val result = apiService.confirmTicket(czsysid)
+ if (result.code == 1) {
+ Toasty.success("操作成功")
+ onSuccess()
+ } else {
+ Toasty.error(result.msg)
+ }
+ }
+ }
+ }
+
+ fun showTicketMoreDialog(ticketId: String) {
+ doInIoThread {
+ _ticketMoreDialog.value = TicketMoreDialogData(showDialog = true, ticketId = ticketId,
+ onDismiss = {
+ _ticketMoreDialog.update { it.copy(showDialog = false) }
+ }
+ )
+ }
+ }
+
+ private val _dateRange = MutableStateFlow(Pair(Date(), Date()))
+ val dateRange = _dateRange.asStateFlow()
+
+ private val _dateRangeSelectDialogData = MutableStateFlow(DateRangeSelectDialogData())
+ val dateRangeSelectDialogData = _dateRangeSelectDialogData.asStateFlow()
+
+ fun showDateRangeSelectDialog(onFinished: (Pair) -> Unit) {
+ doInIoThreadNoDialog {
+ _dateRangeSelectDialogData.value = DateRangeSelectDialogData(
+ showDialog = true,
+ default = _dateRange.value,
+ onDismiss = {
+ _dateRangeSelectDialogData.update { it.copy(showDialog = false) }
+ },
+ onClickRangeDay = { dateStrStart, dateStrEnd ->
+ _dateRange.value =Pair(dateStrStart, dateStrEnd)
+ onFinished(Pair(dateStrStart, dateStrEnd))
+ }
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/StatisticsScreen.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/StatisticsScreen.kt
new file mode 100644
index 0000000..4cbcaf9
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/StatisticsScreen.kt
@@ -0,0 +1,249 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.paging.compose.LazyPagingItems
+import androidx.paging.compose.collectAsLazyPagingItems
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.AssistChipFilter
+import com.bbitcn.f8.pad.base.DateRangePickTextFiled
+import com.bbitcn.f8.pad.base.MainFuncFrame
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.MyRefreshTable
+import com.bbitcn.f8.pad.base.MyTableData
+import com.bbitcn.f8.pad.base.QueryTextField
+import com.bbitcn.f8.pad.base.TableContent
+import com.bbitcn.f8.pad.base.TableHeadLine
+import com.bbitcn.f8.pad.base.VipBadge
+import com.bbitcn.f8.pad.model.net.response.StatisticsListResponse
+import com.bbitcn.f8.pad.model.net.response.StatisticsResponse
+import com.bbitcn.f8.pad.ui.screen.dialog.DateRangeSelectDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.DateRangeSelectDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.TicketMoreDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.TicketMoreDialogData
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.common.DatePickerRange
+import com.bbitcn.f8.pad.ui.screen.view.drawer.DrawerViewModel
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import java.text.SimpleDateFormat
+import java.time.LocalDate
+import java.util.Locale
+
+/**
+ *
+ * @Description 主功能-预约售茧
+ * @Author DuanKaiji
+ * @CreateTime 2024年08月02日 11:25:32
+ */
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun StatisticsScreenPV() {
+ StatisticsScreen(drawerViewModel = DrawerViewModel())
+}
+
+@Composable
+fun StatisticsScreen(
+ statisticsViewModel: StatisticsViewModel = viewModel(),
+ drawerViewModel: DrawerViewModel
+) {
+ val ticketMoreDialogData by remember { mutableStateOf(TicketMoreDialogData()) }
+ var curType by remember { mutableStateOf(StatisticsResponse.Data()) }
+ var queryInput by rememberSaveable { mutableStateOf("") }
+ val pager = statisticsViewModel.statisticsPager.collectAsLazyPagingItems()
+ val dateRangeSelectDialogData by statisticsViewModel.dateRangeSelectDialogData.collectAsState()
+ MainFuncFrame {
+ MyCard(colors = MyColors.LightLightBlueGreen) {
+ Row(
+ modifier = M
+ .fillMaxSize()
+ .padding(10.dp)
+ ) {
+ StatisticsLeft(modifier = M.weight(2f), statisticsViewModel, curType) {
+ curType = it
+ statisticsViewModel.updateParamsCocoonType(it.sgtypesysid)
+ pager.refresh()
+ }
+ StatisticsMain(
+ modifier = M.weight(8f),
+ statisticsViewModel,
+ drawerViewModel,
+ queryInput,
+ {
+ queryInput = it
+ statisticsViewModel.updateParamsLike(queryInput)
+ pager.refresh()
+ },
+ curType.name,
+ {
+ // 这里it会为空
+ curType = StatisticsResponse.Data()
+ statisticsViewModel.updateParamsCocoonType("")
+ pager.refresh()
+ },
+ pager
+ )
+ }
+ }
+ }
+ TicketMoreDialog(ticketMoreDialogData)
+ DateRangeSelectDialog(dateRangeSelectDialogData)
+}
+
+@Composable
+fun StatisticsLeft(
+ modifier: Modifier,
+ statisticsViewModel: StatisticsViewModel,
+ selectedTabIndex: StatisticsResponse.Data,
+ onFilterSelectedTabChanged: (StatisticsResponse.Data) -> Unit
+) {
+ val daysInfo by statisticsViewModel.daysInfo.collectAsState()
+ val statistics by statisticsViewModel.statistics.collectAsState()
+ val pager = statisticsViewModel.statisticsPager.collectAsLazyPagingItems()
+
+ Column(
+ modifier = modifier
+ .fillMaxHeight()
+ ) {
+ DatePickerRange(
+ importantDateInfoList = daysInfo,
+ currentDate = statisticsViewModel.calDate,
+ onUpdateCurrentDate = {
+ statisticsViewModel.calDate = it
+ statisticsViewModel.getDaysInfo(it.year.toString(), it.monthValue.toString())
+ }) { start, end ->
+ val dateStart = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(start)
+ val dateEnd = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(end)
+ statisticsViewModel.refreshDateRange(dateStart to dateEnd)
+ pager.refresh()
+ }
+ val dateRange by statisticsViewModel.dateRange.collectAsState()
+ DateRangePickTextFiled(M.fillMaxWidth(), dateRange = dateRange) {
+ statisticsViewModel.showDateRangeSelectDialog {
+ pager.refresh()
+ }
+ }
+ TableHeadLine(
+ modifier = M.fillMaxWidth(),
+ list = listOf(
+ "茧别\n类型" to 1, "重量\n(kg)" to 1, "均价\n(元)" to 1, "金额\n(元)" to 1
+ )
+ )
+ statistics.forEach { tab ->
+ TableContent(
+ modifier = M
+ .fillMaxWidth()
+ .clickable {
+ onFilterSelectedTabChanged(tab)
+ },
+ backgroundDeepColor = tab == selectedTabIndex,
+ listOf(
+ Pair(tab.name, 1),
+ Pair(tab.sumweight.toString(), 1),
+ Pair(tab.avgprice.toString(), 1),
+ Pair(tab.summoney.toString(), 1)
+ ),
+ verticalPadding = 10.dp,
+ )
+ }
+ Spacer(modifier = M.weight(1f))
+ VipBadge {
+ MyButton(modifier = M.fillMaxWidth(), text = "发送短信") {
+ Toasty.showToast("正在开发中,敬请期待")
+ }
+ }
+ }
+}
+
+@Composable
+fun StatisticsMain(
+ modifier: Modifier,
+ statisticsViewModel: StatisticsViewModel,
+ drawerViewModel: DrawerViewModel,
+
+ queryInput: String = "",
+ onFilterLikeChanged: (String) -> Unit,
+
+ curType: String = "",
+ onFilterCocoonTypeChanged: (String) -> Unit,
+ pager: LazyPagingItems
+) {
+ val ticketMoreDialogData = remember { mutableStateOf(TicketMoreDialogData()) }
+ val myPager = statisticsViewModel.statisticsMyPager
+ val isRefreshing by myPager.listIsRefreshing.collectAsState()
+ val dateRange by statisticsViewModel.dateRange.collectAsState()
+ Column(
+ modifier = modifier
+ .padding(10.dp)
+ .fillMaxHeight()
+ ) {
+ Row(
+ modifier = M
+ .padding(bottom = 5.dp)
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ AssistChipFilter(
+ "日期:",
+ SimpleDateFormat("yyyy-M-d", Locale.getDefault()).format(
+ dateRange.first
+ ) + " ~ " + SimpleDateFormat("yyyy-M-d", Locale.getDefault()).format(
+ dateRange.second
+ ), deleteEnable = false
+ )
+ AssistChipFilter("筛选:", queryInput, onFilterLikeChanged)
+ AssistChipFilter("茧别:", curType, onFilterCocoonTypeChanged)
+ Spacer(modifier = M.weight(1f))
+ QueryTextField(M.width(200.dp), queryInput) {
+ onFilterLikeChanged(it)
+ }
+ }
+ MyRefreshTable(
+ modifier = M.fillMaxWidth(),
+ isRefreshing = isRefreshing,
+ info = pager,
+ key = { it.czsysid },
+ onFinishRefresh = {
+ myPager.setListIsRefreshClose()
+ },
+ items = listOf(
+ MyTableData(1, true),
+ MyTableData("茧票编号", 3, { it.billcode.toString() }),
+ MyTableData("收购状态", 2, { it.billstate }),
+ MyTableData("支付状态", 2, { it.billstate }),
+ MyTableData("姓名", 1, { it.nhname }),
+ MyTableData("毛重", 1, { it.mweightsum.toString() }),
+ MyTableData("皮重", 1, { it.pweightsum.toString() }),
+ MyTableData("扣重", 1, { it.kweightsum.toString() }),
+ MyTableData("净重", 1, { it.jweightsum.toString() }),
+ ),
+ onClick = {
+ statisticsViewModel.openStatisticsDetailDrawer(it.czsysid) {
+ drawerViewModel.openStatisticsDetailDrawer(info = it) {
+ ticketMoreDialogData.value =
+ TicketMoreDialogData(true)
+ }
+ }
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/StatisticsViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/StatisticsViewModel.kt
new file mode 100644
index 0000000..24be0e7
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/StatisticsViewModel.kt
@@ -0,0 +1,136 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc;
+
+import androidx.lifecycle.viewModelScope
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.model.net.request.DateRangeRequest
+import com.bbitcn.f8.pad.model.net.request.StatisticsRequest
+import com.bbitcn.f8.pad.model.net.response.PurchaseDataDetailResponse
+import com.bbitcn.f8.pad.model.net.response.PurchaseDataResponse
+import com.bbitcn.f8.pad.model.net.response.StatisticsResponse
+import com.bbitcn.f8.pad.ui.screen.dialog.DateRangeSelectDialogData
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.utils.MyUtil
+import com.bbitcn.f8.pad.utils.TimeUtils
+import com.bbitcn.f8.pad.utils.TimeUtils.getRecentMonthsDate
+import com.bbitcn.f8.pad.utils.pager.MyPager
+import com.bbitcn.f8.pad.utils.pager.StatisticsListPagingSource
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import java.time.LocalDate
+import java.util.Date
+
+
+class StatisticsViewModel : BaseViewModel() {
+
+ private val _daysInfo = MutableStateFlow(listOf())
+ val daysInfo = _daysInfo.asStateFlow()
+
+ private val _dateRange = MutableStateFlow(Pair(Date(), Date()))
+ val dateRange = _dateRange.asStateFlow()
+
+ private val _statistics = MutableStateFlow>(emptyList())
+ val statistics = _statistics.asStateFlow()
+
+ var calDate = LocalDate.now() // 保存当前日期
+
+ val statisticsMyPager = MyPager(
+ pagingSourceFactory = { StatisticsListPagingSource(it) },
+ initialRequestData = StatisticsRequest(), // 传入初始的请求数据
+ )
+ val statisticsPager = statisticsMyPager.createPager(viewModelScope)
+
+ init {
+ doInIoThreadNoDialog {
+ // 获取一个月前的日期
+ refreshDateRange(Pair(getRecentMonthsDate(1), Date()), false)
+ val localDate = LocalDate.now()
+ getDaysInfo(localDate.year.toString(), localDate.monthValue.toString(), false)
+ }
+ }
+
+ fun getDaysInfo(year: String, month: String, showDialog: Boolean = true) {
+ doInIoThreadWith(showDialog, "正在加载日期信息...") {
+ val response = apiService.getDaysInfo(year, month)
+ val tempList = mutableListOf()
+ response.data.forEach {
+ tempList.add(it.date.split("-")[2].toInt())
+ }
+ _daysInfo.value = tempList
+ }
+ }
+
+ /**
+ * 当日期范围改变时,刷新数据
+ */
+ fun refreshDateRange(dataRange: Pair, showDialog: Boolean = true) {
+ _dateRange.value = dataRange
+ // 刷新茧别统计
+ refreshCocoonLevelStatistics(showDialog)
+ // 刷新统计列表
+ statisticsMyPager.updateParams {
+ it.copy(
+ startdate = TimeUtils.formatDate(dataRange.first),
+ endate = TimeUtils.formatDate(dataRange.second)
+ )
+ }
+ }
+
+ fun refreshCocoonLevelStatistics(showDialog: Boolean) {
+ // 刷新茧别统计
+ doInIoThreadWith(showDialog, "正在加载茧别统计...") {
+ val response = apiService.getCocoonLevelStatistics(
+ DateRangeRequest(
+ TimeUtils.formatDate(_dateRange.value.first),
+ TimeUtils.formatDate(_dateRange.value.second)
+ )
+ )
+ if (response.code == 1) {
+ _statistics.value = response.data
+ } else {
+ Toasty.showToast(response.msg)
+ }
+ }
+ }
+
+ fun updateParamsLike(like: String) {
+ statisticsMyPager.updateParams { it.copy(like = like) }
+ }
+
+ fun updateParamsCocoonType(type: String) {
+ statisticsMyPager.updateParams { it.copy(sgtypesysid = type) }
+ }
+
+ fun openStatisticsDetailDrawer(czSysId: String, onClick: (PurchaseDataResponse.Data) -> Unit) {
+ doInIoThread {
+ val response = apiService.getPurchaseDetail(czSysId)
+ if (response.code == 1) {
+ onClick(response.data)
+ } else {
+ Toasty.showToast(response.msg)
+ }
+ }
+ }
+
+ private val _dateRangeSelectDialogData = MutableStateFlow(DateRangeSelectDialogData())
+ val dateRangeSelectDialogData = _dateRangeSelectDialogData.asStateFlow()
+
+ fun showDateRangeSelectDialog(onFinish: () -> Unit) {
+ doInIoThreadNoDialog {
+ _dateRangeSelectDialogData.value = DateRangeSelectDialogData(
+ showDialog = true,
+ default = _dateRange.value,
+ onDismiss = {
+ _dateRangeSelectDialogData.update { it.copy(showDialog = false) }
+ },
+ onClickRangeDay = { dateStrStart, dateStrEnd ->
+ // 切换时间范围
+ refreshDateRange(Pair(dateStrStart, dateStrEnd), false)
+ onFinish()
+ }
+ )
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/UserScreen.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/UserScreen.kt
new file mode 100644
index 0000000..c55d3e7
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/UserScreen.kt
@@ -0,0 +1,497 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.gestures.detectDragGestures
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ExpandLess
+import androidx.compose.material.icons.filled.ExpandMore
+import androidx.compose.material.icons.filled.Refresh
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.rememberSaveable
+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.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MainFuncFrame
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavController
+import androidx.navigation.compose.rememberNavController
+import androidx.paging.compose.collectAsLazyPagingItems
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.AssistChipFilter
+import com.bbitcn.f8.pad.base.InfoText
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.MyInfoCard
+import com.bbitcn.f8.pad.base.MyRefreshTable
+import com.bbitcn.f8.pad.base.MyTableData
+import com.bbitcn.f8.pad.base.QueryTextField
+import com.bbitcn.f8.pad.base.isLandscape
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.utils.MyUtil
+import com.bbitcn.f8.pad.utils.TimeUtils
+import com.blankj.utilcode.util.StringUtils
+import kotlinx.coroutines.launch
+
+/**
+ *
+ * @Description 主功能-预约售茧
+ * @Author DuanKaiji
+ * @CreateTime 2024年08月02日 11:25:32
+ */
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun UserScreenPV() {
+ UserScreen(rememberNavController())
+}
+
+@Composable
+fun UserScreen(
+ navController: NavController,
+ userViewModel: UserViewModel = viewModel()
+) {
+ val treeData by userViewModel.treeData.collectAsState()
+ MainFuncFrame {
+ if (isLandscape()) {
+ UserScreenInLandscape(navController, userViewModel, treeData)
+ } else {
+ UserScreenInPortrait(navController, userViewModel, treeData)
+ }
+ }
+}
+
+@Composable
+fun UserScreenInPortrait(
+ navController: NavController,
+ userViewModel: UserViewModel,
+ treeData: List, Any>>
+) {
+ Column(
+ modifier = M.fillMaxSize()
+ ) {
+ MyInfoCard(
+ modifier = M
+ .weight(2f)
+ .padding(bottom = 15.dp)
+ ) {
+ CollapsibleList(userViewModel, treeData)
+ }
+ MyInfoCard(
+ modifier = M.weight(8f)
+ ) {
+ UserManageList(navController, userViewModel)
+ }
+ }
+}
+
+@Composable
+fun UserScreenInLandscape(
+ navController: NavController,
+ userViewModel: UserViewModel,
+ treeData: List, Any>>
+) {
+ var leftWeight by rememberSaveable { mutableStateOf(2.5f) }
+ var rightWeight by rememberSaveable { mutableStateOf(7.5f) }
+ Row(
+ modifier = M.fillMaxSize()
+ ) {
+ MyInfoCard(
+ modifier = M
+ .weight(leftWeight) // 使用动态权重
+ .fillMaxHeight()
+ ) {
+ CollapsibleList(userViewModel, treeData)
+ }
+ Box(
+ modifier = M
+ .fillMaxHeight()
+ .pointerInput(Unit) {
+ detectDragGestures { change, dragAmount ->
+ change.consume() // 消费掉手势事件
+ val dragDelta = dragAmount.x // 获取横向拖动的距离
+ leftWeight = (leftWeight + dragDelta * 0.01f).coerceIn(2.5f, 4.5f)
+ rightWeight = 10f - leftWeight
+// rightWeight = (rightWeight - dragDelta * 0.01f).coerceIn(5f, 8f)
+ }
+ },
+ contentAlignment = Alignment.Center
+ ) {
+ MyInfoCard(
+ modifier = M
+ .width(15.dp)
+ .height(50.dp)
+ .padding(horizontal = 5.dp)
+ ) {
+ }
+ }
+ MyInfoCard(
+ modifier = M
+ .weight(rightWeight) // 使用动态权重
+ .fillMaxHeight()
+ ) {
+ UserManageList(navController, userViewModel)
+ }
+ }
+}
+
+@Composable
+fun CollapsibleList(userViewModel: UserViewModel, listData: List, Any>>) {
+ val queryInput by userViewModel.areaLike.collectAsState()
+ val pager = userViewModel.usersInfoPager.collectAsLazyPagingItems()
+ Column {
+ Row(
+ modifier = M
+ .fillMaxWidth()
+ .padding(15.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ QueryTextField(M.weight(1f), queryInput) {
+ userViewModel.updateAreaLike(it)
+ userViewModel.getUsersArea(false)
+ }
+ Image(
+ imageVector = Icons.Default.Refresh,
+ contentDescription = "Refresh",
+ modifier = M.clickable {
+ userViewModel.updateParams()
+ userViewModel.getUsersArea(showLoading = true)
+ pager.refresh()
+ }
+ )
+ }
+ LazyColumn {
+ items(listData) { item ->
+ CollapsibleItem(
+ userViewModel,
+ item = item,
+ currentLevel = 1,
+ needExpand = queryInput != ""
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun CollapsibleItem(
+ userViewModel: UserViewModel,
+ item: Pair, Any>,
+ currentLevel: Int,
+ xian: String = "",
+ xiang: String = "",
+ cun: String = "",
+ needExpand: Boolean = false
+) {
+ val titleAndCount = item.first
+ if (StringUtils.isEmpty(titleAndCount.first)) {
+ return
+ }
+ val subItems = item.second
+ // 当前项的展开状态 0表示展开,-1表示折叠 1表示加载中
+ var expandedIndex by rememberSaveable { mutableStateOf(if (needExpand) 0 else -1) }
+ LaunchedEffect(needExpand) {
+ if (needExpand != (expandedIndex == 0)) {
+ expandedIndex = if (needExpand) 0 else -1
+ }
+ }
+ val pager = userViewModel.usersInfoPager.collectAsLazyPagingItems()
+ // 被选中的项
+ val xianTmp = if (currentLevel == 1) titleAndCount.first else xian
+ val xiangTmp = if (currentLevel == 2) titleAndCount.first else xiang
+ val cunTmp = if (currentLevel == 3) titleAndCount.first else cun
+
+ val expendListener = {
+ if (expandedIndex == -1) {
+ // 加载子项数据
+ expandedIndex = 1
+ userViewModel.loadArea(
+ currentLevel,
+ xian = xianTmp,
+ xiang = xiangTmp,
+ cun = cunTmp,
+ ) {
+ expandedIndex = 0
+ }
+ } else {
+ expandedIndex = -1
+ }
+ }
+
+ Column {
+ val titleIsSelect =
+ userViewModel.areaFilter.collectAsState().value.contains(item.first.first)
+ // 顶层项的显示和点击逻辑
+ MyCard(radius = 20.dp, elevation = 0.dp, modifier = M.padding(1.5.dp)) {
+ Row(
+ modifier = M
+ .fillMaxWidth()
+ .background(if (titleIsSelect) MyColors.BlueGreen else MyColors.White)
+ .pointerInput(Unit) {
+ detectTapGestures(
+ onDoubleTap = {
+ expendListener()
+ },
+ onTap = {
+ userViewModel.updateParams(xianTmp, xiangTmp, cunTmp)
+ pager.refresh()
+ }
+ )
+ }
+ .padding(horizontal = 15.dp, vertical = 5.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Row(
+ modifier = M.weight(1f),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Text(
+ text = titleAndCount.first,
+ color = if (titleIsSelect) MyColors.White else MyColors.Black,
+ fontSize = if (currentLevel == 1) MaterialTheme.typography.titleLarge.fontSize
+ else if (currentLevel == 2) MaterialTheme.typography.bodyLarge.fontSize
+ else MaterialTheme.typography.bodyMedium.fontSize,
+ )
+ if (titleAndCount.second != -1) {
+ Text(
+ modifier = M.padding(end = 5.dp),
+ text = "${titleAndCount.second}",
+ color = if (titleIsSelect) MyColors.LightGray else MyColors.Gray,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ style = MaterialTheme.typography.bodyMedium
+ )
+ }
+
+ }
+ if (expandedIndex == 1) {
+ CircularProgressIndicator(
+ strokeWidth = 3.dp,
+ modifier = M.size(20.dp),
+ color = MaterialTheme.colorScheme.primary,
+ trackColor = MaterialTheme.colorScheme.surfaceVariant,
+ )
+ } else {
+ Icon(
+ modifier = M
+ .clickable {
+ expendListener()
+ },
+ tint = if (titleIsSelect) MyColors.White else MyColors.Black,
+ imageVector = if (expandedIndex == 0)
+ Icons.Default.ExpandLess
+ else
+ Icons.Default.ExpandMore,
+ contentDescription = if (expandedIndex != -1) "Collapse" else "Expand"
+ )
+ }
+ }
+ }
+
+ AnimatedVisibility(visible = expandedIndex != -1) {
+ Column(modifier = M.padding(start = 10.dp)) {
+ when (subItems) {
+ // 如果子项是 Map 类型,表示还有嵌套项,需要递归渲染
+ is Map<*, *> -> {
+ subItems.forEach { subItem ->
+ if (subItem is Map.Entry<*, *>) {
+ val subPair = subItem.key to subItem.value
+ CollapsibleItem(
+ userViewModel,
+ subPair as Pair, Any>,
+ currentLevel + 1,
+ xianTmp,
+ xiangTmp,
+ cunTmp,
+ needExpand
+ )
+ }
+ }
+ }
+ // 如果子项是 List 类型,表示这是最底层的组数据
+ is List<*> -> {
+ subItems.forEach { subItem ->
+ if (subItem is Pair<*, *>) {
+ if (StringUtils.isEmpty(subItem.first.toString())) {
+ return@forEach
+ }
+ val isSelect = titleIsSelect
+ && userViewModel.areaFilter.collectAsState().value.contains(
+ subItem.first.toString()
+ )
+ // 最底层的组
+ MyCard(
+ radius = 20.dp,
+ elevation = 0.dp,
+ modifier = M.padding(2.5.dp)
+ ) {
+ Row(
+ modifier = M
+ .fillMaxWidth()
+ .background(
+ if (isSelect) MyColors.BlueGreen else MyColors.White
+ )
+ .clickable {
+ userViewModel.updateParams(
+ xianTmp,
+ xiangTmp,
+ cunTmp,
+ subItem.first.toString()
+ )
+ pager.refresh()
+ }
+ .padding(10.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Text(
+ text = subItem.first.toString(),
+ color = if (isSelect) MyColors.White else MyColors.Black,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ style = MaterialTheme.typography.bodyMedium
+ )
+ if (subItem.second != -1) {
+ Text(
+ modifier = M.padding(end = 33.dp),
+ text = "${subItem.second}",
+ color = if (isSelect) MyColors.White else MyColors.Gray,
+ fontSize = MaterialTheme.typography.bodyMedium.fontSize,
+ style = MaterialTheme.typography.bodyMedium
+ )
+ }
+ }
+ }
+ }
+ // 未来处理更多层级时,可以继续添加逻辑
+// else if (subItem is Pair<*, *>) {
+// // 递归处理更深层的结构
+// (subItem as? Pair)?.let { subItemPair ->
+// CollapsibleItem(userViewModel,subItemPair,
+// currentLevel + 1)
+// }
+// }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun UserManageList(navController: NavController, userViewModel: UserViewModel) {
+ var queryInput by rememberSaveable { mutableStateOf("") }
+ val areaFilter by userViewModel.areaFilter.collectAsState()
+ val userData = userViewModel.usersInfoPager.collectAsLazyPagingItems()
+ val onFilterLikeChanged: (String) -> Unit = {
+ queryInput = it
+ userViewModel.updateParamsLike(it)
+ userData.refresh()
+ }
+ Column(
+ modifier = M
+ .padding(15.dp)
+ .fillMaxSize()
+ ) {
+ Row(
+ modifier = M
+ .fillMaxWidth()
+ .padding(bottom = 5.dp),
+ horizontalArrangement = Arrangement.End,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ MyButton(text = "新增", onClick = {
+ navController.navigate("addUser")
+ })
+ AssistChipFilter("区域:", areaFilter, onClick = {
+ userViewModel.updateParams()
+ userData.refresh()
+ })
+ AssistChipFilter("筛选:", queryInput, onFilterLikeChanged)
+ Spacer(modifier = M.weight(1f))
+ QueryTextField(M.width(200.dp), queryInput, onValueChange = onFilterLikeChanged)
+// MyButton(text = "更多", modifier = M.padding(horizontal = 10.dp), onClick = {})
+ }
+ val myPager = userViewModel.usersInfoMyPager
+ val isRefreshing by myPager.listIsRefreshing.collectAsState()
+ MyRefreshTable(
+ modifier = M.fillMaxWidth(),
+ isRefreshing = isRefreshing,
+ info = userData,
+ key = { it.sysid },
+ onFinishRefresh = {
+ myPager.setListIsRefreshClose()
+ },
+ items = listOf(
+ MyTableData(1, isIndex = true),
+ MyTableData("姓名", 1, { it.nhname }),
+ MyTableData("手机号", 2, { it.phone }),
+ MyTableData("身份证", 1, { if (it.idcard != "") "✔️" else "" }),
+ MyTableData("银行卡", 1, { if (it.bankcode != "") "✔️" else "" }),
+ MyTableData("所属地址", 3, { "${it.xian}${it.xiang}${it.cun}${it.zu}" }),
+ MyTableData("建档时间", 2, { TimeUtils.formatDateTimeStrToDateStr(it.createtime) }),
+ MyTableData("", 1, { "修改" }, true) {
+ navController.navigate("editUser/${it.sysid}")
+ },
+ MyTableData("", 1, { "收购" }, true) {
+ navController.navigate("weight/${it.sysid}")
+ },
+ ),
+ onExpend = {
+ Column(modifier = M.fillMaxWidth().border(1.dp, MyColors.Gray).padding(15.dp)) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ InfoText("姓名", it.nhname,M.weight(2f),true)
+ InfoText("电话", it.phone,M.weight(2f),true)
+ }
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ InfoText("银行", it.bankname,M.weight(2f),true)
+ InfoText("卡号", it.bankcode,M.weight(2f),true)
+ }
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ InfoText("建档时间", it.createtime,M.weight(2f),true)
+ InfoText("身份证", it.idcard,M.weight(2f),true)
+ }
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ InfoText("县", it.xian,M.weight(1f),true)
+ InfoText("乡", it.xiang,M.weight(1f),true)
+ InfoText("村", it.cun,M.weight(1f),true)
+ InfoText("组", it.zu,M.weight(1f),true)
+ }
+ }
+ },
+ onLongClick = {
+ userViewModel.deleteUser(it.nhname, it.sysid) {
+ userData.refresh()
+ }
+ }
+ )
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/UserViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/UserViewModel.kt
new file mode 100644
index 0000000..f01c3b0
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/UserViewModel.kt
@@ -0,0 +1,226 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc;
+
+import androidx.lifecycle.viewModelScope
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.model.net.request.UserListDataRequest
+import com.bbitcn.f8.pad.model.net.response.UsersAreaResponse
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.utils.pager.MyPager
+import com.bbitcn.f8.pad.utils.pager.UsersInfoPagingSource
+import com.blankj.utilcode.util.StringUtils
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class UserViewModel : BaseViewModel() {
+
+ private val _areaLike = MutableStateFlow("")
+ val areaLike = _areaLike.asStateFlow()
+
+ private val _treeData = MutableStateFlow, Any>>>(emptyList())
+ val treeData: StateFlow, Any>>> = _treeData.asStateFlow()
+
+ private val _tempData: MutableList = mutableListOf()
+
+ private val _areaFilter = MutableStateFlow("")
+ val areaFilter = _areaFilter.asStateFlow()
+
+ val usersInfoMyPager = MyPager(
+ pagingSourceFactory = { UsersInfoPagingSource(it) },
+ initialRequestData = UserListDataRequest(), // 传入初始的请求数据
+ )
+
+ val usersInfoPager = usersInfoMyPager.createPager(viewModelScope)
+
+ init {
+ getUsersArea(false)
+ }
+
+ fun updateParamsLike(like: String) {
+ usersInfoMyPager.updateParams { it.copy(like = like) }
+ }
+
+ fun updateParams(
+ xian: String = "",
+ xiang: String = "",
+ cun: String = "",
+ zu: String = "",
+ ) {
+ usersInfoMyPager.updateParams { it.copy(xian = xian, xiang = xiang, cun = cun, zu = zu) }
+ // 使用构建函数生成拼接字符串
+ _areaFilter.value = buildAreaFilter(xian, xiang, cun, zu)
+ }
+
+ fun buildAreaFilter(xian: String, xiang: String, cun: String, zu: String): String {
+ return buildString {
+ append(xian)
+ if (xiang.isNotEmpty()) append(" > $xiang")
+ if (cun.isNotEmpty()) append(" > $cun")
+ if (zu.isNotEmpty()) append(" > $zu")
+ }
+ }
+
+ fun updateAreaLike(like: String) {
+ _areaLike.value = like
+ }
+
+ fun getUsersArea(showLoading: Boolean) {
+ doInIoThreadWith(showLoading, "正在加载区域数据...") {
+ _treeData.value = emptyList()
+ val like = _areaLike.value
+ if (StringUtils.isEmpty(like)) {
+ val response = apiService.getUsersAreaForXian()
+ if (response.code == 1) {
+ response.data.forEach {
+ _tempData.add(UsersAreaResponse.Data(xian = it.xian, count = it.count))
+ }
+ _treeData.value = convertToNestedStructure(_tempData)
+ }
+ } else {
+ val response = apiService.getUsersArea(like)
+ if (response.code == 1) {
+ _treeData.value = convertToNestedStructure(response.data)
+ } else {
+ Toasty.showTipsDialog(response.msg)
+ }
+ }
+ }
+ }
+
+ fun loadArea(
+ currentLevel: Int,
+ xian: String,
+ xiang: String,
+ cun: String,
+ onFinish: () -> Unit
+ ) {
+ // 只有在搜索信息为空的时候才逐级加载
+ if (StringUtils.isEmpty(_areaLike.value)) {
+ doInIoThreadNoDialog {
+ if (currentLevel == 1) {
+ // 级别为1时,加载乡
+ val list = apiService.getUsersAreaForXiang(xian = xian)
+ if (list.code == 1) {
+ list.data.forEach {
+ _tempData.add(
+ UsersAreaResponse.Data(
+ xian = xian,
+ xiang = it.xiang,
+ count = it.count
+ )
+ )
+ }
+ _treeData.value = convertToNestedStructure(_tempData)
+ } else {
+ Toasty.showTipsDialog(list.msg)
+ }
+ } else if (currentLevel == 2) {
+ // 级别为2时,加载村
+ val list = apiService.getUsersAreaForCun(xian = xian, xiang = xiang)
+ if (list.code == 1) {
+ list.data.forEach {
+ _tempData.add(
+ UsersAreaResponse.Data(
+ xian = xian,
+ xiang = xiang,
+ cun = it.cun,
+ count = it.count
+ )
+ )
+ }
+ _treeData.value = convertToNestedStructure(_tempData)
+ } else {
+ Toasty.showTipsDialog(list.msg)
+ }
+ } else if (currentLevel == 3) {
+ // 级别为3时,加载组
+ val list = apiService.getUsersAreaForZu(xian = xian, xiang = xiang, cun = cun)
+ if (list.code == 1) {
+ list.data.forEach {
+ _tempData.add(
+ UsersAreaResponse.Data(
+ xian = xian,
+ xiang = xiang,
+ cun = cun,
+ zu = it.zu,
+ count = it.count
+ )
+ )
+ }
+ _treeData.value = convertToNestedStructure(_tempData)
+ } else {
+ Toasty.showTipsDialog(list.msg)
+ }
+ }
+ onFinish()
+ }
+ }
+ }
+
+ private suspend fun convertToNestedStructure(data: List): List, Any>> {
+ val result = mutableListOf, Any>>()
+// val result = mutableListOf, Pair, Pair, Pair, String>>>>>()
+ // 按县(Xian)分组
+ data.groupBy { it.xian }.forEach { (xian, xiangList) ->
+ // 按乡(Xiang)分组
+ val xiangGroups = xiangList.groupBy { it.xiang }.map { (xiang, cunList) ->
+ // 按村(Cun)分组
+ val cunGroups = cunList.groupBy { it.cun }.map { (cun, zuList) ->
+ // 按组(Zu)分组
+ val zuListWithCount = zuList.map { it.zu to it.count }
+ Pair(cun to getCount(data, xian, xiang, cun), zuListWithCount)
+ }.toMap()
+ // 返回乡及其村的分组
+ Pair(xiang to getCount(data, xian, xiang), cunGroups)
+ }.toMap()
+ result.add(Pair(xian to getCount(data, xian), xiangGroups))
+ }
+ return result
+ }
+
+ fun getCount(
+ list: List,
+ xian: String, xiang: String? = null, cun: String? = null, zu: String? = null
+ ): Int {
+ for (item in list) {
+ // 找县的数量
+ if (xiang == null) {
+ if (item.xian == xian && item.xiang == "" && item.cun == "" && item.zu == "") {
+ return item.count
+ }
+ } else if (cun == null) {
+ // 找乡的数量
+ if (item.xian == xian && item.xiang == xiang && item.cun == "" && item.zu == "") {
+ return item.count
+ }
+ } else if (zu == null) {
+ // 找村的数量
+ if (item.xian == xian && item.xiang == xiang && item.cun == cun && item.zu == "") {
+ return item.count
+ }
+ } else {
+ // 找组的数量
+ if (item.xian == xian && item.xiang == xiang && item.cun == cun && item.zu == zu) {
+ return item.count
+ }
+ }
+ }
+ return -1
+ }
+
+ fun deleteUser(name: String, sysid: String, onSuccess: () -> Unit) {
+ Toasty.showConfirmDialog("确定删除农户" + name + "吗?") {
+ doInIoThread("正在删除农户...") {
+ val response = apiService.deleteFarmer(sysid)
+ if (response.code == 1) {
+ Toasty.success("删除成功")
+ onSuccess()
+ } else {
+ Toasty.showTipsDialog(response.msg)
+ }
+ }
+ }
+ }
+
+
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingBase.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingBase.kt
new file mode 100644
index 0000000..6b997d6
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingBase.kt
@@ -0,0 +1,96 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc.setting
+
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavController
+import com.bbitcn.f8.pad.M
+
+import com.bbitcn.f8.pad.base.InfoText
+import com.bbitcn.f8.pad.ui.screen.TopInfoViewModel
+import com.bbitcn.f8.pad.ui.screen.dialog.EditPasswordDialog
+import com.bbitcn.f8.pad.ui.screen.secondFunc.InputFrame
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.global.Global
+
+@Composable
+fun SettingBase(
+ navController: NavController,
+ refreshMenuList: () -> Unit = {},
+ topInfoViewModel: TopInfoViewModel,
+ settingViewModel: SettingBaseViewModel = viewModel(),
+) {
+ val editPasswordDialogData by settingViewModel.editPasswordDialogData.collectAsState()
+ LazyColumn(verticalArrangement = Arrangement.spacedBy(5.dp)) {
+ item {
+ val userInfo by settingViewModel.userInfo.collectAsState()
+ InputFrame("登录信息") {
+ Column(modifier = M.padding(10.dp)) {
+ InfoText("姓名:", userInfo.name)
+ InfoText("企业:", userInfo.tenantname)
+ InfoText("部门:", userInfo.depname)
+ InfoText("批次:", userInfo.sgcjname)
+ InfoText("时间:", userInfo.sgdate)
+ }
+ }
+ val batteryVisible by settingViewModel.batteryVisible.collectAsState()
+ val title by settingViewModel.title.collectAsState()
+ InputFrame("状态栏") {
+ Column {
+ SetText("应用标题", title) {
+ Toasty.showInputDialog("请输入应用标题", title) {
+ if (it.isNotEmpty()) {
+ MMKVUtil.put(Global.TITLE, it)
+ topInfoViewModel.refreshTitle()
+ settingViewModel.refreshTitle()
+ }
+ }
+ }
+ SetText("电量显示", if (batteryVisible) "显示" else "隐藏") {
+ Toasty.showOptionDrawer("电量显示", listOf("显示", "隐藏")) {
+ MMKVUtil.put(Global.BATTERY_VISIBLE, it == "显示")
+ settingViewModel.refreshBatteryInfo()
+ topInfoViewModel.refreshBatteryInfo()
+ }
+ }
+ }
+ }
+ val aboutInfo by settingViewModel.aboutInfo.collectAsState()
+ InputFrame("关于") {
+ Text(modifier = M.padding(10.dp), text = aboutInfo)
+ }
+ InputFrame("其他操作") {
+ Column {
+ SetText("修改登录密码") {
+ settingViewModel.showEditPasswordDialog()
+ }
+ SetText("刷新菜单") {
+ refreshMenuList()
+ }
+ SetText("退出登录") {
+ Toasty.showConfirmDialog("确定要退出登录吗?") {
+ settingViewModel.logout {
+ navController.navigate("login") {
+ popUpTo(navController.graph.startDestinationId) {
+ inclusive = true
+ }
+ launchSingleTop = true
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ EditPasswordDialog(editPasswordDialogData)
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingBaseViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingBaseViewModel.kt
new file mode 100644
index 0000000..e74ecea
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingBaseViewModel.kt
@@ -0,0 +1,100 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc.setting
+
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.model.net.response.UserInfoResponse
+import com.bbitcn.f8.pad.model.ui.BaseDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.AboutDialogData
+import com.bbitcn.f8.pad.ui.screen.view.Toasty.showTipsDialog
+import com.bbitcn.f8.pad.ui.screen.view.Toasty.showToast
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.global.Global
+import com.bbitcn.f8.pad.utils.global.RxTag
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+class SettingBaseViewModel : BaseViewModel() {
+
+ private val _batteryVisible = MutableStateFlow(false)
+ val batteryVisible = _batteryVisible.asStateFlow()
+
+ private val _title = MutableStateFlow("智慧蚕桑收购系统")
+ val title = _title.asStateFlow()
+
+ private val _userInfo = MutableStateFlow(UserInfoResponse.Data())
+ val userInfo = _userInfo.asStateFlow()
+
+ private val _aboutInfo = MutableStateFlow("")
+ val aboutInfo = _aboutInfo.asStateFlow()
+
+ private val _editPasswordDialogData = MutableStateFlow(BaseDialogData())
+ val editPasswordDialogData = _editPasswordDialogData.asStateFlow()
+
+ init {
+ doInIoThreadNoDialog {
+ // 用户信息
+ getUserInfo()
+ }
+ // 电量信息
+ refreshBatteryInfo()
+ // 应用标题
+ refreshTitle()
+ // 关于信息
+ refreshAboutInfo()
+ }
+
+ fun logout(onFinished: () -> Unit) {
+ // 退出登录
+ doInIoThreadThenUI(loadingTips = "正在退出登录", onIO = {
+ MMKVUtil.remove(RxTag.AUTH_USER_NAME)
+ MMKVUtil.remove(RxTag.ACCESS_TOKEN)
+ MMKVUtil.remove(RxTag.REFRESH_TOKEN)
+ }) {
+ onFinished()
+ }
+ }
+
+ suspend fun getUserInfo() {
+ val userInfo = apiService.getUserInfo()
+ if (userInfo.code == 1) {
+ _userInfo.value = userInfo.data
+ MMKVUtil.put(Global.USER_NAME, userInfo.data.name)
+ MMKVUtil.put(Global.USER_ID, userInfo.data.id)
+ MMKVUtil.put(Global.DEP_SYS_ID, userInfo.data.depsysid)
+ MMKVUtil.put(Global.DEP_CODE, userInfo.data.depcode)
+ MMKVUtil.put(Global.DEP_NAME, userInfo.data.depname)
+ }
+ }
+
+ fun refreshBatteryInfo() {
+ doInIoThreadNoDialog {
+ _batteryVisible.update {
+ MMKVUtil.get(Global.BATTERY_VISIBLE, true)
+ }
+ }
+ }
+
+ fun refreshTitle() {
+ doInIoThreadNoDialog {
+ _title.update { MMKVUtil.get(Global.TITLE, "智慧蚕桑收购系统") }
+ }
+ }
+
+ fun showEditPasswordDialog() {
+ _editPasswordDialogData.value = BaseDialogData(true, onDismiss = {
+ _editPasswordDialogData.update { it.copy(showDialog = false) }
+ })
+ }
+
+ private fun refreshAboutInfo() {
+ doInIoThreadNoDialog {
+ val result = apiService.getAboutInfo(MMKVUtil.get(RxTag.TENANT_CODE))
+ if (result.code == 1) {
+ _aboutInfo.value = result.data.describe
+ } else {
+ _aboutInfo.value = result.msg
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingPurseScreen.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingPurseScreen.kt
new file mode 100644
index 0000000..0c13a16
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingPurseScreen.kt
@@ -0,0 +1,76 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc.setting
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.unit.dp
+import com.bbitcn.f8.pad.ui.screen.secondFunc.InputFrame
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.global.Global
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.platform.LocalContext
+import com.bbitcn.f8.pad.MyApp
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.utils.AudioPlayer
+import com.bbitcn.f8.pad.utils.TTSManager
+
+@Composable
+fun DryCocoonSettingScreen(settingViewModel: SettingViewModel) {
+ val context = LocalContext.current
+ Column {
+ val standardPackageWeight by settingViewModel.standardPackageWeight.collectAsState()
+ val weightStableTime by settingViewModel.weightAutoWaitTime.collectAsState()
+ val isTtsAvailable by TTSManager.isTtsAvailable.collectAsState()
+ InputFrame("干茧-自动出入库") {
+ Column(verticalArrangement = Arrangement.spacedBy(5.dp)) {
+ SetText("标准包重量", "${standardPackageWeight}kg") {
+ Toasty.showInputDialog(
+ "标准包重量",
+ "$standardPackageWeight",
+ isNumber = true
+ ) {
+ if (it.toDoubleOrNull() == null) {
+ Toasty.showToast("请输入数字")
+ return@showInputDialog
+ } else {
+ MMKVUtil.put(Global.STANDARD_PACKAGE_WEIGHT, it.toDouble())
+ settingViewModel.refreshDryCocoonSetting()
+ }
+ }
+ }
+ SetText("称稳定时间", "${weightStableTime}s") {
+ Toasty.showInputDialog("称稳定时间", "$weightStableTime", isNumber = true) {
+ if (it.toIntOrNull() == null) {
+ Toasty.showToast("请输入整数")
+ return@showInputDialog
+ } else {
+ MMKVUtil.put(Global.WEIGHT_AUTO_WAIT_TIME, it.toInt())
+ settingViewModel.refreshDryCocoonSetting()
+ }
+ }
+ }
+// SetText("本机语音支持", "$isTtsAvailable(点击刷新)") {
+// TTSManager.init(MyApp.appContext)
+// }
+// SetText("下载语音", "跳转到语音下载页面") {
+// TTSManager.promptInstallTtsData(context)
+// }
+// SetText("测试语音", "播报") {
+// TTSManager.speak("测试语音播报", true)
+// }
+ SetText("测试语音1(打印机缺纸,请补充打印纸)", "播报") {
+ AudioPlayer.playAudioOnce(R.raw.printer_no_paper)
+ AudioPlayer.playAudioOnce(R.raw.printer_please_add_paper)
+ }
+ SetText("测试语音2(打印机缺纸)", "播报") {
+ AudioPlayer.playAudioOnce(R.raw.printer_no_paper,true)
+ }
+ SetText("测试语音3(请补充打印纸)", "播报") {
+ AudioPlayer.playAudioOnce(R.raw.printer_please_add_paper,true)
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingScreen.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingScreen.kt
new file mode 100644
index 0000000..cd170c1
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingScreen.kt
@@ -0,0 +1,219 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc.setting
+
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MainFuncFrame
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavController
+import androidx.navigation.compose.rememberNavController
+
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.VerticalTabPages
+import com.bbitcn.f8.pad.ui.screen.TopInfoViewModel
+import com.bbitcn.f8.pad.ui.screen.dialog.FaceDialog
+import com.bbitcn.f8.pad.ui.screen.secondFunc.InputFrame
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.drawer.SetScreen
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.ui.viewmodel.UpdateViewModel
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.global.Global
+
+/**
+ *
+ * @Description 主功能-预约售茧
+ * @Author DuanKaiji
+ * @CreateTime 2024年08月02日 11:25:32
+ */
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun SettingScreenPV() {
+ SettingScreen(
+ rememberNavController(),
+ updateViewModel = viewModel(),
+ topInfoViewmodel = viewModel()
+ )
+}
+
+@Composable
+fun SettingScreen(
+ navController: NavController,
+ settingViewModel: SettingViewModel = viewModel(),
+ updateViewModel: UpdateViewModel,
+ topInfoViewmodel: TopInfoViewModel,
+ refreshMenuList: () -> Unit = {}
+) {
+ val faceDialogData by settingViewModel.faceDialogData.collectAsState()
+ MainFuncFrame {
+ MyCard {
+ val userManagementAvailable by settingViewModel.userManagementAvailable.collectAsState()
+ val tabs = mutableListOf("基础设置")
+ if (userManagementAvailable) {
+ tabs.add("用户管理")
+ }
+ tabs.addAll(listOf("称重设置", "设备管理", "天气预警", "版本信息"))
+ VerticalTabPages(
+ modifier = M
+ .fillMaxHeight()
+ .padding(vertical = 15.dp),
+ tabs = tabs
+ ) { index ->
+ when (tabs[index]) {
+ "基础设置" -> SettingBase(navController, refreshMenuList, topInfoViewmodel)
+ "用户管理" -> UserSettingScreen()
+ "称重设置" -> DryCocoonSettingScreen(settingViewModel)
+ "设备管理" -> SetScreen()
+ "版本信息" -> DeviceInfoSettingScreen(updateViewModel)
+ "天气预警" -> SettingWeather()
+
+ "收购设置" -> SettingPurchase(settingViewModel)
+ "会员服务" -> SettingVIP(settingViewModel)
+ "短信平台" -> SettingSms(settingViewModel)
+ "付款设置" -> SettingPay(settingViewModel)
+ }
+ }
+ }
+ }
+ FaceDialog(faceDialogData)
+}
+
+@Composable
+fun DeviceInfoSettingScreen(
+ updateViewModel: UpdateViewModel
+) {
+ val versionName by updateViewModel.versionName.collectAsState()
+ val versionLastName by updateViewModel.versionLastName.collectAsState()
+ val frpVersionName by updateViewModel.frpVersionName.collectAsState()
+ val frpVersionLastName by updateViewModel.frpVersionLastName.collectAsState()
+ val frpConfig by updateViewModel.frpConfig.collectAsState()
+ Column {
+ InputFrame("F8Pad") {
+ Column {
+ SetText("最新版本", versionLastName)
+ SetText("当前版本", versionName)
+ SetText("检查更新") {
+ updateViewModel.checkUpdate()
+ }
+ }
+ }
+ InputFrame("BBIT远程协助") {
+ Column {
+ SetText("设备名", frpConfig.name)
+ SetText("设备Id", frpConfig.code)
+ SetText("分配端口", frpConfig.localPort)
+ SetText("最新版本", frpVersionLastName)
+ SetText(
+ "当前版本",
+ if (frpVersionName == "-1") "未检测到远程协助软件" else frpVersionName
+ )
+ SetText("检查更新并启动") {
+ updateViewModel.checkFrpUpdate()
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun SetText(title: String, value: String = "", onClick: (() -> Unit)? = null) {
+ Row(
+ modifier = M
+ .fillMaxWidth()
+ .padding(horizontal = 10.dp),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Row(
+ modifier = M
+ .fillMaxWidth()
+ .clickable { onClick?.invoke() },
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(text = title)
+ Row {
+ Text(text = value, modifier = M.padding(vertical = 10.dp), color = MyColors.Gray)
+ Text(text = if (onClick != null) ">" else " ", modifier = M.padding(10.dp))
+ }
+ }
+ }
+}
+
+@Composable
+fun SettingFarmer(settingViewModel: SettingViewModel) {
+// Text(
+// text = " 企业(名称 简称 系统名称)\n" +
+// " 授权 (授权信息,变更授权)\n" +
+// " 注册表 (仅PDA功能配置)\n" +
+// "退出登录 检查更新"
+// )
+}
+
+@Composable
+fun SettingPurchase(settingViewModel: SettingViewModel) {
+ Text(
+ text = " 票据预览 (电子票据,微信票据,短信票据)\n" +
+ " 收购设备\n" +
+ " 智能称\n" +
+ " 一体平板\n" +
+ " 三防一体机\n" +
+ " 普通平板"
+ )
+}
+
+@Composable
+fun SettingVIP(settingViewModel: SettingViewModel) {
+ Text(
+ text = " 版本及购买入口\n" +
+ " 单站版 单站会员版 企业版 企业会员版\n" +
+ " 999/年 5999/年 5000/站 8000/站 \n" +
+ "\n" +
+ "集团版\n" +
+ "80000/年\n" +
+ "\n" +
+ "买断:\n" +
+ "单站会员版 6万(含平板端+电脑端)\n" +
+ "企业版15万(6个茧站以内,含平板端和电脑端)\n" +
+ "\n" +
+ "单站版 "
+ )
+}
+
+@Composable
+fun SettingSms(settingViewModel: SettingViewModel) {
+ Text(
+ text = " 账号设置(账号,密码,签名)\n" +
+ " 套餐及购买\n" +
+ " 收购日报\n" +
+ " 财务日报"
+ )
+}
+
+@Composable
+fun SettingPay(settingViewModel: SettingViewModel) {
+ Text(
+ text = " 银企直联账号,银行选择(设置必须收验证码)\n" +
+ " 额度及授权 (单站版无法设置)\n" +
+ " 人脸注册 (人脸注册变更需要验证码)"
+ )
+}
+
+@Composable
+fun SettingUser(settingViewModel: SettingViewModel) {
+ Text(text = " 用户及角色限制,角色限定角色。")
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingViewModel.kt
new file mode 100644
index 0000000..16199fb
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingViewModel.kt
@@ -0,0 +1,95 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc.setting;
+
+import com.alibaba.sdk.android.oss.model.GeneratePresignedUrlRequest
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.ui.screen.dialog.FaceDialogData
+import com.bbitcn.f8.pad.ui.screen.view.Toasty.showTipsDialog
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.TTSManager
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.face.OssUtils
+import com.bbitcn.f8.pad.utils.global.Global
+import com.bbitcn.f8.pad.utils.log.MyLog
+import com.bbitcn.sericulture.utils.database.dynamicRoom.MenuPermissionListTempDatabase
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+
+class SettingViewModel : BaseViewModel() {
+
+ private val _faceList = MutableStateFlow>(emptyList())
+ val faceList = _faceList.asStateFlow()
+
+ private val _userManagementAvailable = MutableStateFlow(false)
+ val userManagementAvailable = _userManagementAvailable.asStateFlow()
+
+ private val _faceDialogData = MutableStateFlow(FaceDialogData())
+ val faceDialogData = _faceDialogData.asStateFlow()
+
+ private val _standardPackageWeight = MutableStateFlow(0.0)
+ val standardPackageWeight = _standardPackageWeight.asStateFlow()
+
+ private val _weightAutoWaitTime = MutableStateFlow(0)
+ val weightAutoWaitTime = _weightAutoWaitTime.asStateFlow()
+
+
+
+ init {
+ doInIoThreadNoDialog {
+ // 用户管理可用性
+ _userManagementAvailable.value =
+ MenuPermissionListTempDatabase.getMenuAvailable("userManagement")
+ // 干茧设置
+ refreshDryCocoonSetting()
+ }
+ }
+
+ fun refreshDryCocoonSetting() {
+ doInIoThreadNoDialog {
+ _standardPackageWeight.value =
+ MMKVUtil.get(Global.STANDARD_PACKAGE_WEIGHT, 0.0)
+ _weightAutoWaitTime.value =
+ MMKVUtil.get(Global.WEIGHT_AUTO_WAIT_TIME, 3)
+ }
+ }
+
+ fun refreshFaceImages() {
+ doInIoThread {
+ val oss = OssUtils.getOssClient()
+// // 请求所有人脸图片
+ if (MMKVUtil.get(Global.USER_ID).isEmpty()) {
+ showTipsDialog("用户ID为空,请重新登录")
+ return@doInIoThread
+ }
+ val serverFaceList = apiService.getFaceList(MMKVUtil.get(Global.USER_ID))
+ if (serverFaceList.code != 1) {
+ showTipsDialog(serverFaceList.msg)
+ } else {
+ val data = serverFaceList.data
+ for (d in data) {
+ // 生成以GET方法访问的签名URL。本示例没有额外请求头,其他人可以直接通过浏览器访问相关内容。
+ val request = GeneratePresignedUrlRequest(d.bucketname, d.objectname);
+ // 设置签名URL的过期时间为30分钟。
+ request.expiration = 30 * 60
+ val imaUrl = oss.presignConstrainedObjectURL(request)
+ MyLog.face("faceUrl: $imaUrl")
+ _faceList.value += imaUrl
+ }
+ }
+
+ }
+ }
+
+ fun showFaceDialog() {
+ doInIoThread {
+ _faceDialogData.value =
+ FaceDialogData(
+ showDialog = true, isRegister = true,
+ isSystemUser = false, userId = MMKVUtil.get(Global.USER_ID), onDismiss = {
+ _faceDialogData.update { it.copy(showDialog = false) }
+ })
+ }
+ }
+
+
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingWeather.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingWeather.kt
new file mode 100644
index 0000000..1ba0c79
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingWeather.kt
@@ -0,0 +1,148 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc.setting
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import coil3.compose.AsyncImage
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.InfoText
+import com.bbitcn.f8.pad.ui.screen.mainFunc.WeatherItem
+import com.bbitcn.f8.pad.ui.screen.secondFunc.InputFrame
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.blankj.utilcode.util.StringUtils
+import ir.ehsannarmani.compose_charts.LineChart
+import ir.ehsannarmani.compose_charts.models.AnimationMode
+import ir.ehsannarmani.compose_charts.models.Line
+
+@Preview
+@Composable
+fun SettingWeatherPV() {
+ SettingWeather()
+}
+
+@Composable
+fun SettingWeather(
+ settingWeatherViewModel: SettingWeatherViewModel = viewModel(),
+) {
+ LazyColumn {
+ item {
+ val weatherInfo by settingWeatherViewModel.weatherInfo.collectAsState()
+ val weatherTodayInfo by settingWeatherViewModel.weatherTodayInfo.collectAsState()
+ val weatherAddr by settingWeatherViewModel.weatherAddr.collectAsState()
+ InputFrame("天气设置") {
+ SetText(
+ "预报地址",
+ if (StringUtils.isEmpty(weatherAddr)) "自动获取" else weatherAddr
+ ) {
+ Toasty.showInputDialog("天气预报地址", weatherAddr) {
+ settingWeatherViewModel.setWeatherAddr(it)
+ }
+ }
+ }
+ InputFrame("实时天气") {
+ Row(modifier = M.padding(10.dp), verticalAlignment = Alignment.CenterVertically) {
+ AsyncImage(
+ model = weatherTodayInfo.weatherpic,
+ modifier = M.size(30.dp),
+ contentDescription = null,
+ )
+ Column(modifier = M
+ .padding(10.dp)
+ .widthIn(max = 200.dp)) {
+ InfoText("地区:", weatherTodayInfo.province + weatherTodayInfo.city)
+ InfoText("天气:", weatherTodayInfo.weather)
+ InfoText(
+ "风向:",
+ weatherTodayInfo.windpower + weatherTodayInfo.winddirection
+ )
+ InfoText("湿度:", weatherTodayInfo.humidity)
+ InfoText("更新时间:", weatherTodayInfo.reporttime)
+ }
+ }
+ }
+ InputFrame("预警信息") {
+ val infos by remember { derivedStateOf { weatherTodayInfo.alarms } }
+ if (infos.isEmpty()) {
+ SetText("暂无", "")
+ } else {
+ Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
+ infos.forEach {
+ Row(modifier = M.fillMaxWidth()) {
+ Column(modifier = M.width(200.dp)) {
+ InfoText("地区:", it.province + it.city)
+ InfoText("类型:", it.level + it.type + "预警")
+ InfoText("发布时间:", it.time)
+ }
+ Text(
+ modifier = M
+ .weight(1f)
+ .padding(start = 10.dp),
+ text = it.content,
+ )
+ }
+ }
+ }
+ }
+ }
+ InputFrame("本周天气预报") {
+ Column {
+ LazyRow {
+ items(weatherInfo.casts) {
+ val index = weatherInfo.casts.indexOf(it)
+ WeatherItem(
+ it.weekstr, it.date, it.dayweatherpic,
+ it.nighttemp + "~" + it.daytemp + "℃", index
+ )
+ }
+ }
+ LineChart(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(400.dp)
+ .width(500.dp)
+ .padding(horizontal = 30.dp, vertical = 20.dp),
+ data = remember {
+ listOf(
+ Line(
+ label = "白天温度",
+ values = settingWeatherViewModel.dayTempList,
+ color = SolidColor(MyColors.Orange),
+ ),
+ Line(
+ label = "夜间温度",
+ values = settingWeatherViewModel.nightTempList,
+ color = SolidColor(MyColors.BlueGreen),
+ )
+ )
+ },
+ animationMode = AnimationMode.Together(delayBuilder = { it * 500L }),
+ )
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingWeatherViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingWeatherViewModel.kt
new file mode 100644
index 0000000..8cd497d
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/SettingWeatherViewModel.kt
@@ -0,0 +1,59 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc.setting
+
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.toMutableStateList
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.model.net.response.WeatherForTodayResponse
+import com.bbitcn.f8.pad.model.net.response.WeatherResponse
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.global.Global
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class SettingWeatherViewModel : BaseViewModel() {
+
+ private val _weatherInfo = MutableStateFlow(WeatherResponse.Data())
+ val weatherInfo = _weatherInfo.asStateFlow()
+
+ private val _weatherAddr = MutableStateFlow("")
+ val weatherAddr = _weatherAddr.asStateFlow()
+
+ private val _weatherTodayInfo = MutableStateFlow(WeatherForTodayResponse.Data())
+ val weatherTodayInfo = _weatherTodayInfo.asStateFlow()
+
+ var dayTempList = mutableStateListOf()
+ var nightTempList = mutableStateListOf()
+
+ init {
+ refreshWeatherInfo()
+ }
+
+ fun refreshWeatherInfo() {
+ doInIoThreadNoDialog {
+ val addr = MMKVUtil.get(Global.WEATHER_ADDR, "")
+ _weatherAddr.value = addr
+ // 实时天气及预警
+ val todayRes = apiService.getWeatherForToday(addr)
+ if (todayRes.code == 1) {
+ _weatherTodayInfo.value = todayRes.data
+ }
+ // 本周天气预报
+ val result = apiService.getWeather(addr)
+ if (result.code == 1) {
+ _weatherInfo.value = result.data
+ _weatherInfo.value.casts.forEach {
+ dayTempList.add(it.daytemp.toDoubleOrNull() ?: 0.0)
+ nightTempList.add(it.nighttemp.toDoubleOrNull() ?: 0.0)
+ }
+ }
+ }
+ }
+
+ fun setWeatherAddr(addr: String) {
+ doInIoThreadNoDialog {
+ MMKVUtil.put(Global.WEATHER_ADDR, addr)
+ refreshWeatherInfo()
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/UserSettingScreen.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/UserSettingScreen.kt
new file mode 100644
index 0000000..146900c
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/UserSettingScreen.kt
@@ -0,0 +1,156 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc.setting
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavController
+import androidx.paging.compose.collectAsLazyPagingItems
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.AssistChipFilter
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.MyInfoCard
+import com.bbitcn.f8.pad.base.MyRefreshTable
+import com.bbitcn.f8.pad.base.MyTableData
+import com.bbitcn.f8.pad.base.QueryTextField
+import com.bbitcn.f8.pad.ui.screen.dialog.AddEditUserDialog
+import com.bbitcn.f8.pad.ui.screen.mainFunc.UserManageList
+import com.bbitcn.f8.pad.ui.screen.mainFunc.UserViewModel
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun UserSettingScreenPV() {
+ UserSettingScreen()
+}
+
+@Composable
+fun UserSettingScreen(
+ userSettingViewModel: UserSettingViewModel = viewModel()
+) {
+ MyInfoCard(
+ modifier = M.fillMaxSize()
+ ) {
+ UserSettingManage(userSettingViewModel)
+ }
+}
+
+@Composable
+fun UserSettingManage(userViewModel: UserSettingViewModel) {
+ val addEditUserDialogData by userViewModel.addEditUserDialogData.collectAsState()
+ var queryInput by rememberSaveable { mutableStateOf("") }
+ val userData = userViewModel.usersInfoPager.collectAsLazyPagingItems()
+ val onFilterLikeChanged: (String) -> Unit = {
+ queryInput = it
+ userViewModel.updateParamsLike(it)
+ userData.refresh()
+ }
+ Column(
+ modifier = M
+ .padding(15.dp)
+ .fillMaxSize()
+ ) {
+ Row(
+ modifier = M
+ .fillMaxWidth()
+ .padding(bottom = 5.dp),
+ horizontalArrangement = Arrangement.End,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ MyButton(text = "新增", onClick = {
+ // 新增用户
+ userViewModel.addUser{
+ onFilterLikeChanged(queryInput)
+ }
+ })
+ AssistChipFilter("筛选:", queryInput, onFilterLikeChanged)
+ Spacer(modifier = M.weight(1f))
+ QueryTextField(M.width(200.dp), queryInput, onValueChange = onFilterLikeChanged)
+ }
+ val myPager = userViewModel.usersInfoMyPager
+ val isRefreshing by myPager.listIsRefreshing.collectAsState()
+ MyRefreshTable(
+ modifier = M.fillMaxWidth(),
+ isRefreshing = isRefreshing,
+ info = userData,
+ key = { it.hashCode() },
+ onFinishRefresh = {
+ myPager.setListIsRefreshClose()
+ },
+ items = listOf(
+ MyTableData(1, isIndex = true),
+ MyTableData("姓名", 2, { it.name }),
+ MyTableData("性别", 1, { it.sex }),
+ MyTableData("登录名", 2, { it.loginName }),
+ MyTableData("手机号", 2, { it.tel }),
+ MyTableData("身份证", 2, { if (it.idCard == "1") "已认证" else "未认证" }),
+ MyTableData("角色", 2, { it.role }),
+ MyTableData("所属部门", 2, { it.depname }),
+ ),
+ onClick = {
+ // 编辑用户
+ userViewModel.updateUser(it) {
+ onFilterLikeChanged(queryInput)
+ }
+ },
+ onLongClick = {
+ // 长按删除用户
+ Toasty.showConfirmDialog("确定要删除用户<${it.name}>吗?") {
+ userViewModel.deleteUser(it.id) {
+ onFilterLikeChanged(queryInput)
+ }
+ }
+ }
+ )
+ }
+ AddEditUserDialog(addEditUserDialogData)
+
+
+// InputFrame("人脸录入") {
+// val faceList by settingViewModel.faceList.collectAsState()
+// Column(modifier = M.fillMaxSize()) {
+// MyButton(text = "刷新人脸数据") {
+// settingViewModel.refreshFaceImages()
+// }
+// LazyRow {
+// items(faceList) {
+// AsyncImage(
+// model = it,
+// modifier = M
+// .size(100.dp)
+// .padding(20.dp),
+// contentDescription = null,
+// )
+// }
+// item {
+// Image(
+// modifier = M
+// .size(100.dp)
+// .padding(20.dp)
+// .clickable {
+// // 人脸录入 注册/农户
+// settingViewModel.showFaceDialog()
+// },
+// painter = painterResource(id = R.drawable.ic_upload),
+// contentDescription = null
+// )
+// }
+// }
+// }
+// }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/UserSettingViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/UserSettingViewModel.kt
new file mode 100644
index 0000000..6d62ca3
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/setting/UserSettingViewModel.kt
@@ -0,0 +1,141 @@
+package com.bbitcn.f8.pad.ui.screen.mainFunc.setting
+
+import androidx.lifecycle.viewModelScope
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.model.net.request.AddUserRequest
+import com.bbitcn.f8.pad.model.net.request.DeleteUserRequest
+import com.bbitcn.f8.pad.model.net.request.SetUserListRequest
+import com.bbitcn.f8.pad.model.net.request.UpdateUserRequest
+import com.bbitcn.f8.pad.model.net.response.SetUserListResponse
+import com.bbitcn.f8.pad.ui.screen.dialog.AddEditUserDialogData
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.global.Global
+import com.bbitcn.f8.pad.utils.pager.MyPager
+import com.bbitcn.f8.pad.utils.pager.SetUserPagingSource
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+class UserSettingViewModel : BaseViewModel() {
+
+ private val _addEditUserDialogData = MutableStateFlow(AddEditUserDialogData())
+ val addEditUserDialogData = _addEditUserDialogData.asStateFlow()
+
+ val usersInfoMyPager = MyPager(
+ pagingSourceFactory = { SetUserPagingSource(it) },
+ initialRequestData = SetUserListRequest(depSysid = MMKVUtil.get(Global.DEP_SYS_ID)), // 传入初始的请求数据
+ )
+
+ val usersInfoPager = usersInfoMyPager.createPager(viewModelScope)
+
+
+ fun updateParamsLike(like: String = "") {
+ usersInfoMyPager.updateParams { it.copy(like = like) }
+ }
+
+ fun deleteUser(id: Long, onSuccess: () -> Unit) {
+ doInIoThread("正在删除用户中") {
+ val res = apiService.deleteUser(DeleteUserRequest(id))
+ if (res.code == 1) {
+ Toasty.success("删除成功")
+ onSuccess()
+ } else {
+ Toasty.showTipsDialog("删除失败:${res.msg}")
+ }
+ }
+ }
+
+ fun addUser(onSuccess: () -> Unit) {
+ doInIoThread {
+ val userRoles = apiService.getUserRoles()
+ if (userRoles.code != 1) {
+ Toasty.showTipsDialog("获取用户角色失败:${userRoles.msg}")
+ return@doInIoThread
+ }
+ _addEditUserDialogData.value =
+ AddEditUserDialogData(showDialog = true, userRoles = userRoles.data,depName = MMKVUtil.get(Global.DEP_NAME),
+ onInsert = { username: String, phone: String,
+ name: String, sex: Boolean, idCard: String,sort:Int, roles: List ->
+ doInIoThread {
+ val res = apiService.addUser(AddUserRequest(
+ departmentSysid = MMKVUtil.get(Global.DEP_SYS_ID),
+ iCCardId = 0,
+ userRole = roles,
+ userNew = AddUserRequest.UserNew(
+ loginName = username,
+ name = name,
+ sex = sex,
+ tel = phone,
+ idCard = idCard,
+ sort = sort,
+ //以下参数暂不考虑
+ cun = "",
+ iCCardId = 0,
+ memo = "",
+ )
+ ))
+ if (res.code == 1) {
+ Toasty.success("添加成功")
+ _addEditUserDialogData.update { it.copy(showDialog = false) }
+ onSuccess()
+ } else {
+ Toasty.showTipsDialog("添加失败:${res.msg}")
+ }
+ }
+ },
+ onDismiss = {
+ _addEditUserDialogData.update { it.copy(showDialog = false) }
+ })
+ }
+ }
+
+ fun updateUser(item: SetUserListResponse.Data, onSuccess: () -> Unit) {
+ doInIoThread {
+ val userRoles = apiService.getUserRoles()
+ if (userRoles.code != 1) {
+ Toasty.showTipsDialog("获取用户角色失败:${userRoles.msg}")
+ return@doInIoThread
+ }
+ _addEditUserDialogData.value =
+ AddEditUserDialogData(
+ showDialog = true,
+ data = item,
+ userRoles = userRoles.data,
+ onUpdate = { username: String, phone: String,
+ name: String, sex: Boolean, idCard: String,sort:Int, roles: List ->
+ doInIoThread {
+ val res = apiService.updateUser(UpdateUserRequest(
+ departmentSysid = MMKVUtil.get(Global.DEP_SYS_ID),
+ userRole = roles,
+ userNew = UpdateUserRequest.UserNew(
+ id = item.id,
+ loginName = username,
+ name = name,
+ sex = sex,
+ tel = phone,
+ idCard = idCard,
+ sort = sort,
+ //以下参数暂不考虑
+ cun = "",
+ iCCardId = 0,
+ memo = ""
+ )
+ ))
+ if (res.code == 1) {
+ Toasty.success("修改成功")
+ onSuccess()
+ _addEditUserDialogData.update { it.copy(showDialog = false) }
+ } else {
+ Toasty.showTipsDialog("修改失败:${res.msg}")
+ }
+ }
+ },
+ onDismiss = {
+ _addEditUserDialogData.update { it.copy(showDialog = false) }
+ })
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonAirScreen.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonAirScreen.kt
new file mode 100644
index 0000000..e261f7b
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonAirScreen.kt
@@ -0,0 +1,303 @@
+package com.bbitcn.f8.pad.ui.screen.secondFunc
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.paging.compose.collectAsLazyPagingItems
+import com.bbitcn.f8.pad.IS_DEBUG_DRYCOCOON
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MainFuncFrame
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.MyCard
+
+import com.bbitcn.f8.pad.base.MyRefreshTable
+import com.bbitcn.f8.pad.base.MyTableData
+import com.bbitcn.f8.pad.model.net.request.DryCocoonPackageLossRequest
+import com.bbitcn.f8.pad.model.net.request.StartDryCocoonAirDetailRequest
+import com.bbitcn.f8.pad.ui.screen.view.deviceManager.scale.MyWeightShow
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.deviceManager.reader.MyCardReaderShowViewModel
+import com.bbitcn.f8.pad.ui.viewmodel.factory.AddDryCocoonAirViewModelFactory
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.uhf.UHFReaderG06M_G25M
+import com.bbitcn.f8.pad.utils.log.MyLog
+import kotlinx.coroutines.flow.flowOf
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun DryCocooAirScreenPreview() {
+ DryCocoonAirScreen("1123")
+}
+
+@Composable
+fun DryCocoonAirScreen(
+ sysId: String,
+ myCardReaderShowViewModel: MyCardReaderShowViewModel = viewModel()
+) {
+ LaunchedEffect(Unit) {
+ MyLog.test("第一次进入,清空tagId缓存")
+ myCardReaderShowViewModel.clearList()
+ }
+ val addDryCocoonAirViewModel = viewModel(
+ factory = AddDryCocoonAirViewModelFactory(sysId)
+ )
+ val info by addDryCocoonAirViewModel.info.collectAsState()
+ val myPager = addDryCocoonAirViewModel.dryCocoonAirDetailMyPager
+ val dryAir =
+ addDryCocoonAirViewModel.dryCocoonAirDetailPager.collectAsLazyPagingItems()
+ val isRefreshing by myPager.listIsRefreshing.collectAsState()
+ MainFuncFrame {
+ Row(
+ modifier = M
+ .fillMaxWidth()
+ ) {
+ MyCard(
+ modifier = M
+ .fillMaxSize()
+ .weight(2f)
+ ) {
+ Column(
+ modifier = M
+ .weight(2f)
+ .padding(10.dp)
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = M
+ .padding(10.dp)
+ .fillMaxWidth()
+ ) {
+ VerticalInfo("仓库", info.ckname)
+ VerticalInfo("蚕季", info.cjname)
+ VerticalInfo("茧别", info.jiantype)
+ VerticalInfo("品种", info.canpinzhong)
+ VerticalInfo("区域", info.xiangzhen)
+ VerticalInfo("总包数", info.baoshu.toString())
+ VerticalInfo("摊晾中", info.tanliangingbaoshu.toString())
+ VerticalInfo("空包释放", info.releaseBaoshu.toString())
+// VerticalInfo("摊晾人", info.tanliangren) // 按需求去掉2025年6月4日
+ }
+ MyRefreshTable(
+ modifier = M
+ .fillMaxWidth()
+ .weight(1f),
+ isRefreshing = isRefreshing,
+ info = dryAir,
+ key = { it.sysid },
+ onFinishRefresh = {
+ myPager.setListIsRefreshClose()
+ },
+ onLongClick = {
+ Toasty.showConfirmDialog("是否删除<${it.code}>摊晾记录") {
+ addDryCocoonAirViewModel.deleteDryCocoonAirDetail(it.sysid) {
+ dryAir.refresh()
+ }
+ }
+ },
+ items = listOf(
+ MyTableData("包码", 2, { ".." + it.code.takeLast(11) }),
+ MyTableData("状态", 2, { it.status }),
+ MyTableData("开始时间", 3, { it.starttime }),
+ MyTableData("结束时间", 3, { it.endtime }),
+// MyTableData("毛重", 1, { it.chayizhongliang.toString() }),
+// MyTableData("库存毛重", 2, { it.kcmaozhong.toString() }),// 按需求去掉2025年6月6日
+ MyTableData("开始重量", 2, { it.fbstartmaozhong.toString() }),
+ MyTableData("结束重量", 2, { it.fbendmaozhong.toString() }),
+ MyTableData("空包释放", 1, { it.bagrelease }),
+ ),
+ scrollToTopOnRefresh = true,
+ )
+ }
+ }
+ var grossWeight by rememberSaveable { mutableStateOf(0.0) }
+ val curReader by myCardReaderShowViewModel.curDevice.collectAsState()
+ val deviceTagIds by (curReader?.tagList
+ ?: flowOf(emptyList())).collectAsState(initial = emptyList())
+ val tagIds by addDryCocoonAirViewModel.tagIds.collectAsState()
+ var weightStableSeconds by rememberSaveable { mutableStateOf(0) }
+ var targetStableWeight by rememberSaveable { mutableStateOf(0.0) }
+ var hasBecomeZero by rememberSaveable { mutableStateOf(true) }
+ var isAutoOperateStart by rememberSaveable { mutableStateOf(false) }
+ var isAutoOperateEnd by rememberSaveable { mutableStateOf(false) }
+ LaunchedEffect(deviceTagIds) {
+ addDryCocoonAirViewModel.filterTagIds(deviceTagIds)
+ }
+ MyCard(
+ modifier = M
+ .weight(1f)
+ .padding(start = 10.dp),
+ ) {
+ Column(
+ modifier = M.padding(10.dp),
+ verticalArrangement = Arrangement.spacedBy(5.dp),
+ horizontalAlignment = Alignment.End
+ ) {
+ var weightErrorMsg by rememberSaveable { mutableStateOf("") }
+ val tagErrorMsg by remember(tagIds, curReader) {
+ derivedStateOf {
+ if (curReader == null) "未检测到读卡器"
+ else if (tagIds.isEmpty()) "未检测到麻袋"
+ else if (tagIds.size > 1) "检测到多个麻袋"
+ else ""
+ }
+ }
+ MyWeightShow(targetStableWeight = targetStableWeight, onErrorMsg = {
+ weightErrorMsg = it
+ }, onStableTimeChanged = {
+ weightStableSeconds = it
+ }) {
+ grossWeight = it
+ if (it == 0.0) {
+ hasBecomeZero = true
+ }
+ }
+ DryCocoonInfo(
+ "麻袋ID",
+ if (tagIds.isNotEmpty()) "${tagIds[0].take(2)}...${tagIds[0].takeLast(4)}" else "",
+ tagErrorMsg
+ )
+ Row(
+ modifier = M.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ MyButton(modifier = M.fillMaxWidth(), text = "重新检测茧包") {
+ myCardReaderShowViewModel.clearList()
+ }
+ }
+ if (IS_DEBUG_DRYCOCOON) {
+ Row(
+ modifier = M.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ MyButton(text = "增加随机芯片") {
+ UHFReaderG06M_G25M.testXP()
+ }
+ MyButton(text = "测试增加芯片1") {
+ UHFReaderG06M_G25M.testXP("521323232")
+ }
+ }
+ }
+ Spacer(modifier = M.weight(1f))
+ val allErrorMsg by remember(tagErrorMsg, weightErrorMsg) {
+ derivedStateOf {
+ if (tagErrorMsg.isNotEmpty()) tagErrorMsg + "\n" else "" +
+ if (weightErrorMsg.isNotEmpty()) weightErrorMsg + "\n" else ""
+ }
+ }
+ val onSaveStart = { onFinish: () -> Unit ->
+ addDryCocoonAirViewModel.startDryCocoonAirDetail(
+ tagIds[0],
+ StartDryCocoonAirDetailRequest(
+ maozhong = grossWeight,
+ ), onSuccess = {
+ dryAir.refresh()
+ myCardReaderShowViewModel.clearList()
+ }, onFinish = onFinish
+ )
+ }
+ val onSaveEnd = { onFinish: () -> Unit ->
+ addDryCocoonAirViewModel.stopDryCocoonAirDetail(
+ tagIds[0], grossWeight, onSuccess = {
+ dryAir.refresh()
+ myCardReaderShowViewModel.clearList()
+ }, onFinish = onFinish
+ )
+ }
+ Column(
+ modifier = M.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(5.dp)
+ ) {
+ AutoOperate(
+ "自动开始摊晾",
+ tagIds,
+ -1.0,
+ weightStableSeconds,
+ addDryCocoonAirViewModel,
+ hasBecomeZero, autoOperate = isAutoOperateStart, onAutoOperate = {
+ targetStableWeight = 0.0
+ isAutoOperateStart = it
+ isAutoOperateEnd = false
+ }
+ ) {
+ hasBecomeZero = false
+ onSaveStart(it)
+ }
+ AutoOperate(
+ "自动结束摊晾",
+ tagIds,
+ grossWeight,
+ weightStableSeconds,
+ addDryCocoonAirViewModel,
+ hasBecomeZero, autoOperate = isAutoOperateEnd, onAutoOperate = {
+ targetStableWeight =
+ addDryCocoonAirViewModel.standardPackageWeight.value
+ isAutoOperateEnd = it
+ isAutoOperateStart = false
+ }
+ ) {
+ hasBecomeZero = false
+ onSaveEnd(it)
+ }
+ MyWeightButton(
+ text = "开始摊晾",
+ isEnable = allErrorMsg.isEmpty(),
+ ) {
+ // 增加本包摊晾
+ if (allErrorMsg.isEmpty()) {
+ onSaveStart { }
+ } else {
+ Toasty.showTipsDialog(allErrorMsg)
+ }
+ }
+ MyWeightButton(
+ text = "结束摊晾",
+ isEnable = allErrorMsg.isEmpty(),
+ ) {
+ // 结束摊晾
+ if (allErrorMsg.isEmpty()) {
+ onSaveEnd {}
+ } else {
+ Toasty.showTipsDialog(allErrorMsg)
+ }
+ }
+ MyWeightButton(
+ text = "空包释放",
+ isEnable = allErrorMsg.isEmpty(),
+ ) {
+ // 空包释放
+ if (allErrorMsg.isEmpty()) {
+ addDryCocoonAirViewModel.lossDryCocoonAir(
+ DryCocoonPackageLossRequest(
+ rfid = tagIds[0],
+ tlsysid = info.sysid,
+ )
+ ) {
+ dryAir.refresh()
+ myCardReaderShowViewModel.clearList()
+ }
+ } else {
+ Toasty.showTipsDialog(allErrorMsg)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonAirViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonAirViewModel.kt
new file mode 100644
index 0000000..f6cc115
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonAirViewModel.kt
@@ -0,0 +1,174 @@
+package com.bbitcn.f8.pad.ui.screen.secondFunc
+
+import androidx.lifecycle.viewModelScope
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.model.net.request.DryCocoonPackageLossRequest
+import com.bbitcn.f8.pad.model.net.request.StartDryCocoonAirDetailRequest
+import com.bbitcn.f8.pad.model.net.request.StopDryCocoonAirDetailRequest
+import com.bbitcn.f8.pad.model.net.response.DryCocoonAirDetailResponse
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.utils.AudioPlayer
+import com.bbitcn.f8.pad.utils.TimeUtils
+import com.bbitcn.f8.pad.utils.pager.DryCocoonAirDetailPagingSource
+import com.bbitcn.f8.pad.utils.pager.MyPager
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+
+class AddDryCocoonAirViewModel(val outSystemId: String) : AddDryCocoonBaseViewModel() {
+
+ private val _info = MutableStateFlow(DryCocoonAirDetailResponse.Data())
+ val info = _info.asStateFlow()
+
+ init {
+ refreshDryCocoonAirDetail(true)
+ }
+
+ /**
+ * 刷新摊晾详情
+ */
+ fun refreshDryCocoonAirDetail(needDialog: Boolean = false) {
+ doInIoThreadWith(needDialog, "正在刷新摊晾详情") {
+ val info = apiService.getCocoonAirDetail(outSystemId)
+ if (info.code != 1) {
+ Toasty.showTipsDialog("请求错误,请尝试退出重新进入:${info.msg}")
+ } else {
+ _info.value = info.data
+ }
+ }
+ }
+
+ /**
+ * 摊晾详情
+ */
+ val dryCocoonAirDetailMyPager = MyPager(
+ pagingSourceFactory = { DryCocoonAirDetailPagingSource(outSystemId) },
+ initialRequestData = "", // 传入初始的请求数据
+ )
+ val dryCocoonAirDetailPager = dryCocoonAirDetailMyPager.createPager(viewModelScope)
+
+ fun stopDryCocoonAirDetail(
+ rfid: String,grossWeight: Double,
+ onSuccess: () -> Unit,
+ onFinish: () -> Unit
+ ) {
+ doInIoThread("正在结束本包摊晾记录", onFinish = onFinish,onError = {
+ AudioPlayer.playAudioOnce(R.raw.network_disconnect)
+// TTSManager.speak("操作失败,请检查网络连接")
+ }) {
+ // 根据RFID查询包码
+ val searchResponse = apiService.searchAirDetailByRFID(_info.value.cjsysid, rfid)
+ if (searchResponse.code != 1) {
+// TTSManager.speak("操作失败," + searchResponse.msg)
+ AudioPlayer.playAudioOnce(R.raw.dry_cocoon_air_start_failed)
+ Toasty.showTipsDialog(searchResponse.msg)
+ } else {
+ val request = StopDryCocoonAirDetailRequest(
+ rfid = rfid,
+ tlsysid = _info.value.sysid,
+ maozhong = grossWeight,
+ time = TimeUtils.getStringTime()
+ )
+ val response =
+ apiService.stopDryCocoonAirDetail(request)
+ if (response.code != 1) {
+// TTSManager.speak("操作失败" + response.msg)
+ AudioPlayer.playAudioOnce(R.raw.dry_cocoon_air_stop_failed)
+ Toasty.showTipsDialog(response.msg)
+ } else {
+ Toasty.success("结束成功")
+ AudioPlayer.playAudioOnce(R.raw.dry_cocoon_air_stop_success)
+// TTSManager.speak("已结束摊晾")
+ addHadHandleTagIds(searchResponse.data.sysid, rfid)
+ onSuccess()
+ refreshDryCocoonAirDetail(false)
+ }
+ }
+ }
+ }
+
+ fun startDryCocoonAirDetail(
+ rfid: String,
+ dryCocoonSaveAirDetail: StartDryCocoonAirDetailRequest,
+ onSuccess: () -> Unit,
+ onFinish: () -> Unit
+ ) {
+ doInIoThread("正在保存本包摊晾记录", onFinish = onFinish,onError = {
+// TTSManager.speak("操作失败,请检查网络连接")
+ AudioPlayer.playAudioOnce(R.raw.network_disconnect)
+ }) {
+ val temp = _info.value
+ // 根据RFID查询包码
+ val searchResponse = apiService.searchAirDetailByRFID(temp.cjsysid, rfid)
+ if (searchResponse.code != 1) {
+// TTSManager.speak("操作失败," + searchResponse.msg)
+ AudioPlayer.playAudioOnce(R.raw.dry_cocoon_air_start_failed)
+ Toasty.showTipsDialog(searchResponse.msg)
+ } else {
+ // 保存摊晾记录
+ val response =
+ apiService.saveDryCocoonAirDetail(
+ dryCocoonSaveAirDetail.copy(
+ tlsysid = temp.sysid,
+ cjsysid = temp.cjsysid,
+ rkitemsysid = searchResponse.data.sysid,
+ code = searchResponse.data.code,
+ rfid = rfid,
+ time = TimeUtils.getStringTime(),
+ kcmaozhong = searchResponse.data.kcmaozhong,
+ )
+ )
+ if (response.code != 1) {
+ AudioPlayer.playAudioOnce(R.raw.dry_cocoon_air_start_failed)
+// TTSManager.speak("操作失败," + response.msg)
+ Toasty.showTipsDialog(response.msg)
+ } else {
+ AudioPlayer.playAudioOnce(R.raw.dry_cocoon_air_start_success)
+// TTSManager.speak("已开始摊晾")
+ Toasty.success("保存成功")
+ addHadHandleTagIds(searchResponse.data.sysid, rfid)
+ onSuccess()
+ refreshDryCocoonAirDetail(false)
+ }
+ }
+ }
+ }
+
+ fun deleteDryCocoonAirDetail(itemsysid: String, onSuccess: () -> Unit) {
+ doInIoThread("正在删除本包摊晾记录") {
+ val response = apiService.deleteDryCocoonAirDetail(itemsysid)
+ if (response.code != 1) {
+ Toasty.showTipsDialog(response.msg)
+ } else {
+ Toasty.success("删除成功")
+ onSuccess()
+ // 删除已处理的RFID 必须在成功后删除
+ deleteHadHandleTagIds(itemsysid)
+ refreshDryCocoonAirDetail(false)
+ }
+ }
+ }
+
+ fun lossDryCocoonAir(
+ request: DryCocoonPackageLossRequest,
+ onSuccess: () -> Unit
+ ) {
+ Toasty.showConfirmDialog("确定要释放该麻袋吗") {
+ doInIoThread("正在释放空包中") {
+ val response =
+ apiService.onLossPackage(request.copy(time = TimeUtils.getStringTime()))
+ if (response.code != 1) {
+ AudioPlayer.playAudioOnce(R.raw.dry_cocoon_air_loss_failed)
+ Toasty.showTipsDialog(response.msg)
+ } else {
+ AudioPlayer.playAudioOnce(R.raw.dry_cocoon_air_loss_success)
+ Toasty.success("释放成功")
+ onSuccess()
+ refreshDryCocoonAirDetail(false)
+ }
+// TTSManager.speak("释放" + if (response.code == 1) "成功" else "失败", true)
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonBase.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonBase.kt
new file mode 100644
index 0000000..1e2c77f
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonBase.kt
@@ -0,0 +1,225 @@
+package com.bbitcn.f8.pad.ui.screen.secondFunc
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.DebouncedEffect
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.MyCheckBox
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.utils.externalModules.devices.light.Light_
+import com.bbitcn.f8.pad.utils.externalModules.devices.light.Light_.closeLight
+import com.bbitcn.f8.pad.utils.externalModules.devices.light.Light_.openGreenLight
+import com.bbitcn.f8.pad.utils.externalModules.devices.light.Light_.openRedLight
+import com.bbitcn.f8.pad.utils.externalModules.devices.light.Light_.openYellowLight
+import com.bbitcn.f8.pad.utils.log.MyLog
+
+
+@Composable
+fun AutoOperate(
+ title: String,
+ tagIds: List,
+ grossWeight: Double,
+ weightStableSeconds: Int,
+ viewModel: AddDryCocoonBaseViewModel,
+ hasBecomeZero: Boolean = false,
+ autoOperate: Boolean = false,
+ onAutoOperate: (Boolean) -> Unit = {},
+ onSucc: (onFinish: () -> Unit) -> Unit = {}
+) {
+ // 标准包重量
+ val standardPackageWeight by viewModel.standardPackageWeight.collectAsState()
+ val weightStableTime by viewModel.weightAutoWaitTime.collectAsState()
+ Column(modifier = M.fillMaxWidth()) {
+ Row(
+ modifier = M.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.End
+ ) {
+ if (autoOperate) {
+ Box(
+ // 黑色外边框
+ modifier = M
+ .border(BorderStroke(1.dp, MyColors.Black))
+ ) {
+ Column(
+ // 黑色外边框
+ modifier = M
+ .padding(5.dp)
+ ) {
+ // 条件1
+ val succ1 = tagIds.size == 1
+ // 条件2类型: 0:小于标准包重量 1:等于标准包重量 2:大于标准包重量
+ val succ2Type = when {
+ grossWeight < standardPackageWeight -> 0
+ grossWeight == standardPackageWeight -> 1
+ else -> 2
+ }
+ // 条件3:称稳定时间
+ val succ3 by remember(grossWeight, weightStableSeconds, weightStableTime) {
+ derivedStateOf { grossWeight != 0.0 && weightStableSeconds >= weightStableTime }
+ }
+ ConditionText(succ1, "检测到新麻袋")
+ ConditionText(
+ hasBecomeZero,
+ "称重前置零"
+ )
+ if (grossWeight != -1.0) {
+ // 不使用标准包重量
+ ConditionText(
+ succ2Type == 1,
+ "符合标准包重量:${standardPackageWeight}kg"
+ )
+ }
+ ConditionText(
+ (grossWeight == -1.0 || succ2Type == 1) && succ3,
+ "称稳定${weightStableTime}秒" + if (weightStableSeconds >= weightStableTime) "符合" else ":当前${weightStableSeconds}s"
+ )
+ LaunchedEffect(succ2Type) {
+ viewModel.launchTaskNewFirst("干茧-灯光切换") {
+ when (succ2Type) {
+ 0 -> openYellowLight()
+ 1 -> openGreenLight()
+ 2 -> openRedLight()
+ else -> closeLight()
+ }
+ }
+ }
+ LaunchedEffect(succ1, succ2Type, succ3, hasBecomeZero) {
+ if (succ1 && (grossWeight == -1.0 || succ2Type == 1) && succ3 && hasBecomeZero) {
+ viewModel.launchTaskOldFirst("干茧-自动操作") { onFinish ->
+ onSucc(onFinish)
+ }
+ }
+ }
+ }
+ }
+ }
+ MyCheckBox(
+ modifier = M.padding(end = 10.dp),
+ title = title,
+ value = autoOperate
+ ) {
+ onAutoOperate(it)
+ if (!it) {
+ Light_.closeLight()
+ }
+ }
+ }
+ }
+}
+
+
+@Composable
+fun MyWeightButton(
+ text: String,
+ isEnable: Boolean,
+ onClick: () -> Unit
+) {
+ MyCard {
+ Box(
+ modifier = M
+ .fillMaxWidth()
+ .heightIn(45.dp)
+ .background(
+ Brush.verticalGradient(
+ colors = listOf(
+ if (isEnable) MyColors.BlueGreen else MyColors.Gray,
+ if (isEnable) MyColors.LightBlueGreen else MyColors.LightGray
+ ),
+ startY = 0f,
+ )
+ )
+ .clickable { onClick() },
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = text,
+ color = if (!isEnable) MyColors.White else MyColors.Black,
+ fontWeight = FontWeight.Bold,
+ fontSize = MaterialTheme.typography.headlineMedium.fontSize
+ )
+ }
+ }
+}
+
+@Composable
+fun DryCocoonInfo(title: String, content: String, errorMsg: String) {
+ MyCard(
+ modifier = M
+ .fillMaxWidth(),
+ border = BorderStroke(1.dp, MyColors.BlueGreen)
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column(
+ modifier = M
+ .weight(1f)
+ .background(color = MyColors.LightBlueGreen)
+ ) {
+ Text(
+ text = title,
+ color = MyColors.Black,
+ textAlign = TextAlign.Center,
+ fontSize = MaterialTheme.typography.titleLarge.fontSize,
+ modifier = M
+ .padding(vertical = 10.dp)
+ .fillMaxWidth(),
+ )
+ }
+ Row(
+ modifier = M
+ .weight(2f)
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text(
+ modifier = M.padding(start = 10.dp),
+ text = errorMsg,
+ color = MyColors.Red,
+ fontWeight = FontWeight.Bold,
+ fontSize = MaterialTheme.typography.titleSmall.fontSize,
+ )
+ Text(
+ modifier = M
+ .padding(end = 10.dp)
+ .widthIn(max = 150.dp),
+ text = content,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis, // 超出部分显示省略号
+ fontSize = MaterialTheme.typography.headlineLarge.fontSize,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonBaseViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonBaseViewModel.kt
new file mode 100644
index 0000000..b79f322
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonBaseViewModel.kt
@@ -0,0 +1,92 @@
+package com.bbitcn.f8.pad.ui.screen.secondFunc
+
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.utils.MMKVUtil
+import com.bbitcn.f8.pad.utils.externalModules.devices.light.Light_
+import com.bbitcn.f8.pad.utils.externalModules.manager.serial.uhfSerial.UHFReaderForSerial
+import com.bbitcn.f8.pad.utils.global.Global
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import kotlin.collections.mutableListOf
+import kotlin.collections.mutableSetOf
+
+
+open class AddDryCocoonBaseViewModel : BaseViewModel() {
+
+ private val _standardPackageWeight = MutableStateFlow(0.0)
+ val standardPackageWeight = _standardPackageWeight.asStateFlow()
+
+ private val _weightAutoWaitTime = MutableStateFlow(0)
+ val weightAutoWaitTime = _weightAutoWaitTime.asStateFlow()
+
+ /**
+ * 已处理的茧包ID 防止重复处理
+ * key: 系统ID
+ * value: 茧包ID
+ */
+ private val _hadHandleTagIds = mutableMapOf()
+
+ // 临时存储需要过滤的茧包ID
+// private val _tempTags = MutableStateFlow>(emptySet())
+// val tempTags = _tempTags.asStateFlow()
+ // 最终需要过滤的茧包ID
+ private val _forceFilterTags = MutableStateFlow>(emptySet())
+ var forceFilterTags = _forceFilterTags.asStateFlow()
+
+ private val _tagIds = MutableStateFlow>(emptyList())
+ val tagIds = _tagIds.asStateFlow()
+
+ init {
+ doInIoThreadNoDialog {
+ _standardPackageWeight.value = MMKVUtil.get(Global.STANDARD_PACKAGE_WEIGHT, 3.0)
+ _weightAutoWaitTime.value = MMKVUtil.get(Global.WEIGHT_AUTO_WAIT_TIME, 3)
+ // 如果已经连接读卡器,则开启
+ UHFReaderForSerial.startAllScan()
+ }
+ }
+
+ override fun onCleared() {
+ // 如果已经连接读卡器,则关闭
+ UHFReaderForSerial.stopAllScan()
+ // 关闭指示灯
+ Light_.closeLight()
+ super.onCleared()
+ }
+
+ fun deleteHadHandleTagIds(sysId: String) {
+ doInIoThreadNoDialog {
+ _hadHandleTagIds.remove(sysId)
+ }
+ }
+
+ fun addHadHandleTagIds(sysId: String, tagId: String) {
+ doInIoThreadNoDialog {
+ // 只保留最新的
+ _hadHandleTagIds.clear()
+ _hadHandleTagIds[sysId] = tagId
+ }
+ }
+
+ fun confirmTagIds(tagIds:List,onFinished: () -> Unit) {
+ _forceFilterTags.update { it + tagIds.toMutableSet() }
+ onFinished()
+ }
+
+ fun clearForceHadHandleTagIds() {
+ _forceFilterTags.value = emptySet()
+ }
+
+ fun filterTagIds(deviceTagIds: List) {
+ doInIoThreadNoDialog {
+ val temp = mutableListOf()
+ deviceTagIds.forEach {
+ if (!_forceFilterTags.value.contains(it) && !_hadHandleTagIds.containsValue(it)) {
+ temp.add(it)
+ }
+ }
+ _tagIds.value = temp
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonInScreen.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonInScreen.kt
new file mode 100644
index 0000000..2e7242a
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonInScreen.kt
@@ -0,0 +1,358 @@
+package com.bbitcn.f8.pad.ui.screen.secondFunc
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Cancel
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.paging.compose.collectAsLazyPagingItems
+import com.bbitcn.f8.pad.IS_DEBUG_DRYCOCOON
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MainFuncFrame
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.MyCheckBox
+import com.bbitcn.f8.pad.base.MyRefreshTable
+import com.bbitcn.f8.pad.base.MyTableData
+import com.bbitcn.f8.pad.model.net.request.DryCocoonSaveInDetailRequest
+import com.bbitcn.f8.pad.ui.screen.dialog.drycocoon.DryCocoonFilterDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.drycocoon.DryCocoonInfoQueryDialog
+import com.bbitcn.f8.pad.ui.screen.view.deviceManager.scale.MyWeightShow
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.deviceManager.reader.MyCardReaderShowViewModel
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.ui.viewmodel.factory.AddDryCocoonInViewModelFactory
+import com.bbitcn.f8.pad.utils.MyUtil
+
+import com.bbitcn.f8.pad.ui.screen.view.deviceManager.printer.Page.MyLabelPrintDialog
+import com.bbitcn.f8.pad.ui.screen.view.deviceManager.printer.PrintState
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.uhf.UHFReaderG06M_G25M
+import kotlinx.coroutines.flow.flowOf
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun DryCocoonScreenPreview() {
+ DryCocoonInScreen("123")
+}
+
+@Composable
+fun DryCocoonInScreen(
+ sysId: String,
+ myCardReaderShowViewModel: MyCardReaderShowViewModel = viewModel(),
+) {
+ val addDryCocoonInViewModel = viewModel(
+ factory = AddDryCocoonInViewModelFactory(sysId)
+ )
+ val info by addDryCocoonInViewModel.info.collectAsState()
+ LaunchedEffect(Unit) {
+ myCardReaderShowViewModel.clearList()
+ }
+ val myPager = addDryCocoonInViewModel.dryCocoonInDetailMyPager
+ val dryIn = addDryCocoonInViewModel.dryCocoonInDetailPager.collectAsLazyPagingItems()
+ val isRefreshing by myPager.listIsRefreshing.collectAsState()
+ var needPrintLabel by rememberSaveable { mutableStateOf(false) }
+ var hasBecomeZero by rememberSaveable { mutableStateOf(true) }
+ val dryCocoonInfoQueryDialogData by addDryCocoonInViewModel.dryCocoonInfoQueryDialogData.collectAsState()
+ val dryCocoonFilterDialog by addDryCocoonInViewModel.dryCocoonFilterDialog.collectAsState()
+ MainFuncFrame {
+ Row(
+ modifier = M
+ .fillMaxWidth()
+ ) {
+ MyCard(
+ modifier = M
+ .fillMaxSize()
+ .weight(2f)
+ ) {
+ Column(
+ modifier = M
+ .fillMaxSize()
+ .padding(10.dp)
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = M
+ .padding(horizontal = 10.dp)
+ .fillMaxWidth()
+ ) {
+ Text(
+ modifier = M.padding(vertical = 5.dp),
+ text = info.code,
+ maxLines = 1,
+ color = MyColors.Orange,
+ fontSize = MaterialTheme.typography.headlineSmall.fontSize,
+ fontWeight = FontWeight.Bold
+ )
+ VerticalInfo("茧站", info.depname)
+ VerticalInfo("仓库", info.ckname)
+ VerticalInfo("品种", info.cpzname)
+ VerticalInfo("茧别", info.jiantype)
+ VerticalInfo("区域", info.xiangzhen)
+ BigVerticalInfo("总包数", info.baoshu.toString())
+ }
+ MyRefreshTable(
+ modifier = M
+ .fillMaxWidth()
+ .padding()
+ .weight(1f),
+ isRefreshing = isRefreshing,
+ info = dryIn,
+ key = { it.sysid },
+ onFinishRefresh = {
+ myPager.setListIsRefreshClose()
+ },
+ onLongClick = {
+ Toasty.showConfirmDialog("是否删除<${it.code}>茧包") {
+ addDryCocoonInViewModel.deleteDryCocoonInDetail(it.sysid) {
+ myCardReaderShowViewModel.clearList()
+ dryIn.refresh()
+ }
+ }
+ },
+ items = listOf(
+ MyTableData("包码", 3, { it.code }),
+ MyTableData("毛重", 2, { it.maozhong.toString() }),
+ MyTableData("皮重", 2, { it.pizhong.toString() }),
+ MyTableData("净重", 2, { it.jingzhong.toString() }),
+ MyTableData("操作", 2, { "补打茧票" }, true) {
+ addDryCocoonInViewModel.printDryCocoonLabel(it.sysid)
+ }),
+ scrollToTopOnRefresh = true,
+ verticalPadding = 5.dp
+ )
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = M
+ .padding(horizontal = 10.dp)
+ .fillMaxWidth()
+ ) {
+ VerticalInfo("烘茧人", info.hongjianren)
+ VerticalInfo("日期", info.datetime)
+ }
+ }
+ }
+ var grossWeight by rememberSaveable { mutableStateOf(0.0) }
+ var netWeight = MyUtil.formatDouble(grossWeight - info.bagzhongliang)
+
+ val curReader by myCardReaderShowViewModel.curDevice.collectAsState()
+
+ val deviceTagIds by (curReader?.tagList
+ ?: flowOf(emptyList())).collectAsState(initial = emptyList())
+ val tagIds by addDryCocoonInViewModel.tagIds.collectAsState()
+ LaunchedEffect(deviceTagIds) {
+ addDryCocoonInViewModel.filterTagIds(deviceTagIds)
+ }
+ MyCard(
+ modifier = M
+ .weight(1f)
+ .padding(start = 10.dp),
+ ) {
+ var weightErrorMsg = rememberSaveable { "" }
+ val tagErrorMsg =
+ if (curReader == null) "未检测到读卡器"
+ else if (tagIds.isEmpty()) "未检测到新麻袋"
+ else if (tagIds.size > 1) "检测到多个麻袋"
+ else ""
+ val netWeightErrorMsg = if (netWeight < 0) "净重小于0" else ""
+ // 称稳定时间
+ var weightStableSeconds by rememberSaveable { mutableStateOf(0) }
+ var autoOperate by rememberSaveable { mutableStateOf(false) }
+ Column(
+ modifier = M
+ .fillMaxWidth()
+ .padding(10.dp)
+ ) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(5.dp),
+ horizontalAlignment = Alignment.End
+ ) {
+ val standardPackageWeight by addDryCocoonInViewModel.standardPackageWeight.collectAsState()
+ MyWeightShow(targetStableWeight = standardPackageWeight, onErrorMsg = {
+ weightErrorMsg = it
+ }, onStableTimeChanged = {
+ weightStableSeconds = it
+ }) {
+ grossWeight = it
+ if (it == 0.0) {
+ hasBecomeZero = true
+ }
+ }
+ DryCocoonInfo("皮重(${info.bagtype})", "${info.bagzhongliang}kg", "")
+ DryCocoonInfo(
+ "净重", "${netWeight}kg", netWeightErrorMsg
+ )
+ DryCocoonInfo(
+ "麻袋ID",
+ if (tagIds.isNotEmpty()) "${tagIds[0].take(2)}...${tagIds[0].takeLast(4)}" else "",
+ tagErrorMsg
+ )
+ Row(
+ modifier = M.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ MyButton(
+ text = "查询茧包信息",
+ contentPadding = PaddingValues(5.dp, 2.5.dp)
+ ) {
+ if (autoOperate) {
+ Toasty.showTipsDialog("请先关闭自动入库,以防误操作")
+ return@MyButton
+ }
+ addDryCocoonInViewModel.showDryCocoonInfoQueryDialog()
+ }
+ MyButton(
+ text = "过滤特殊茧包",
+ contentPadding = PaddingValues(5.dp, 2.5.dp)
+ ) {
+ if (autoOperate) {
+ Toasty.showTipsDialog("请先关闭自动入库,以防误操作")
+ return@MyButton
+ }
+ addDryCocoonInViewModel.showDryCocoonFilterDialog(
+ myCardReaderShowViewModel
+ )
+ }
+ MyButton(
+ text = "重新检测茧包",
+ contentPadding = PaddingValues(5.dp, 2.5.dp)
+ ) {
+ myCardReaderShowViewModel.clearList()
+ }
+ }
+ if (IS_DEBUG_DRYCOCOON) {
+ Row(
+ modifier = M.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ MyButton(text = "增加随机芯片") {
+ UHFReaderG06M_G25M.testXP()
+ }
+ MyButton(text = "测试增加芯片1") {
+ UHFReaderG06M_G25M.testXP("521323232")
+ }
+ }
+ }
+ }
+ Spacer(modifier = M.weight(1f))
+ val onSave = { onFinish: () -> Unit ->
+ addDryCocoonInViewModel.saveDryCocoonInDetail(
+ DryCocoonSaveInDetailRequest(
+ rfid = tagIds[0],
+ rksysid = sysId,
+ jingzhong = netWeight,
+ maozhong = grossWeight,
+ pizhong = info.bagzhongliang
+ ), needPrintLabel, {
+ // 保存成功后清空数据
+ dryIn.refresh()
+ myCardReaderShowViewModel.clearList()
+ }, onFinish = onFinish
+ )
+ }
+ Column(modifier = M.fillMaxWidth()) {
+ AutoOperate(
+ "自动入库",
+ tagIds,
+ grossWeight,
+ weightStableSeconds,
+ addDryCocoonInViewModel,
+ hasBecomeZero, autoOperate = autoOperate, {
+ autoOperate = it
+ }
+ ) { it ->
+ hasBecomeZero = false
+ onSave.invoke { it.invoke() }
+ }
+ Row(
+ modifier = M.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.End
+ ) {
+ var isConnected by rememberSaveable { mutableStateOf(false) }
+ PrintState {
+ isConnected = it
+ }
+ MyCheckBox(
+ modifier = M.padding(end = 10.dp),
+ title = "打印茧票",
+ value = needPrintLabel
+ ) {
+ if (!isConnected && it) {
+ Toasty.error("打印机未连接,无法开启打印")
+ } else {
+ needPrintLabel = it
+ }
+ }
+ }
+ val allErrorMsg = if (tagErrorMsg.isNotEmpty()) tagErrorMsg + "\n" else "" +
+ if (netWeightErrorMsg.isNotEmpty()) netWeightErrorMsg + "\n" else "" +
+ if (weightErrorMsg.isNotEmpty()) weightErrorMsg + "\n" else ""
+ MyWeightButton(
+ text = "保存本包",
+ isEnable = allErrorMsg.isEmpty(),
+ ) {
+ // 增加称重
+ if (allErrorMsg.isEmpty()) {
+ onSave.invoke { }
+ } else {
+ Toasty.showTipsDialog(allErrorMsg)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ val myLabelPrintDialogData by addDryCocoonInViewModel.myLabelPrintDialogData.collectAsState()
+ MyLabelPrintDialog(myLabelPrintDialogData)
+ DryCocoonInfoQueryDialog(dryCocoonInfoQueryDialogData)
+ DryCocoonFilterDialog(dryCocoonFilterDialog)
+}
+
+@Composable
+fun ConditionText(isOK: Boolean, text: String) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Icon(
+ imageVector = if (isOK) Icons.Filled.Check else Icons.Filled.Cancel,
+ contentDescription = null,
+ tint = if (isOK) MyColors.Green else MyColors.Red
+ )
+ Text(
+ modifier = M.padding(start = 5.dp),
+ text = text,
+ color = if (isOK) MyColors.Green else MyColors.Red,
+ fontWeight = FontWeight.Bold,
+ fontSize = MaterialTheme.typography.titleSmall.fontSize
+ )
+ }
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonInViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonInViewModel.kt
new file mode 100644
index 0000000..0c31e21
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonInViewModel.kt
@@ -0,0 +1,175 @@
+package com.bbitcn.f8.pad.ui.screen.secondFunc
+
+import android.media.MediaPlayer
+import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.model.net.request.DryCocoonSaveInDetailRequest
+import com.bbitcn.f8.pad.model.net.response.CocoonInDetailResponse
+import com.bbitcn.f8.pad.ui.screen.dialog.drycocoon.DryCocoonFilterDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.drycocoon.DryCocoonInfoQueryDialogData
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.deviceManager.printer.Page.MyLabelPrintDialogData
+import com.bbitcn.f8.pad.ui.screen.view.deviceManager.reader.MyCardReaderShowViewModel
+import com.bbitcn.f8.pad.utils.AudioPlayer
+import com.bbitcn.f8.pad.utils.TTSManager
+import com.bbitcn.f8.pad.utils.TTSManager.toChineseNumber
+import com.bbitcn.f8.pad.utils.pager.DryCocoonInDetailPagingSource
+import com.bbitcn.f8.pad.utils.pager.MyPager
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+
+class AddDryCocoonInViewModel(val inSystemId: String) : AddDryCocoonBaseViewModel() {
+
+ private var mediaPlayer: MediaPlayer? = null
+
+ override fun onCleared() {
+ super.onCleared()
+ mediaPlayer?.release()
+ }
+
+ private val _myLabelPrintDialogData = MutableStateFlow(MyLabelPrintDialogData())
+ val myLabelPrintDialogData = _myLabelPrintDialogData.asStateFlow()
+
+ private val _info = MutableStateFlow(CocoonInDetailResponse.Data())
+ val info = _info.asStateFlow()
+
+ init {
+ refreshDryCocoonInDetail(true)
+ }
+
+ /**
+ * 刷新入库详情
+ */
+ fun refreshDryCocoonInDetail(needForce: Boolean = false) {
+ doInIoThreadWith(needForce, "正在刷新入库详情") {
+ val info = apiService.getCocoonInDetail(inSystemId)
+ if (info.code != 1) {
+ Toasty.showTipsDialog("请求错误,请尝试退出重新进入:${info.msg}")
+ } else {
+ _info.value = info.data
+ }
+ }
+ }
+
+ /**
+ * 入库详情
+ */
+ val dryCocoonInDetailMyPager = MyPager(
+ pagingSourceFactory = { DryCocoonInDetailPagingSource(inSystemId) },
+ initialRequestData = "", // 传入初始的请求数据
+ )
+ val dryCocoonInDetailPager = dryCocoonInDetailMyPager.createPager(viewModelScope)
+
+ fun saveDryCocoonInDetail(
+ dryCocoonSaveInDetailRequest: DryCocoonSaveInDetailRequest,
+ needPrintLabel: Boolean,
+ onSuccess: () -> Unit,
+ onFinish: () -> Unit,
+ ) {
+ doInIoThread("正在保存本包记录", onFinish = onFinish, onError = {
+ // 当发生错误时
+// TTSManager.speak("入库失败,请检查网络连接", true)
+ AudioPlayer.playAudioOnce(R.raw.dry_cocoon_in_failed_net, true)
+ }) {
+ val response =
+ apiService.saveDryCocoonInDetail(dryCocoonSaveInDetailRequest.copy(cjsysid = _info.value.cjsysid))
+ if (response.code != 1) {
+ Toasty.showTipsDialog(response.msg)
+ AudioPlayer.playAudioOnce(R.raw.dry_cocoon_in_failed)
+ } else {
+ Toasty.success("保存成功")
+ addHadHandleTagIds(response.data.toString(), dryCocoonSaveInDetailRequest.rfid)
+ onSuccess()
+ if (needPrintLabel) {
+ printDryCocoonLabel(response.data.toString())
+ }
+ AudioPlayer.playAudioOnce(R.raw.dry_cocoon_in_success)
+ }
+// TTSManager.speak(
+// if (response.code == 1) "第" + (_info.value.baoshu + 1).toChineseNumber() + "包已入库" else "入库失败,请联系管理员",
+// true
+// )
+ refreshDryCocoonInDetail(false)
+ }
+ }
+
+ fun printDryCocoonLabel(sysId: String) {
+ doInIoThread("正在打印标签") {
+ val ticketInfo = apiService.getDryCocoonInTicketInfo(sysId)
+ if (ticketInfo.code == 1) {
+ _myLabelPrintDialogData.value = MyLabelPrintDialogData(
+ showDialog = true,
+ ticketInfo = ticketInfo.data,
+ onDismissRequest = {
+ _myLabelPrintDialogData.value =
+ _myLabelPrintDialogData.value.copy(showDialog = false)
+ }
+ )
+ } else {
+ Toasty.showTipsDialog(ticketInfo.msg)
+ }
+ }
+ }
+
+ fun deleteDryCocoonInDetail(itemsysid: String, onSuccess: () -> Unit) {
+ doInIoThread("正在删除本包入库记录") {
+ val response = apiService.deleteDryCocoonInDetail(itemsysid)
+ if (response.code != 1) {
+ Toasty.showTipsDialog(response.msg)
+ } else {
+ Toasty.success("删除成功")
+ onSuccess()
+ // 删除已处理的RFID 必须在成功后删除
+ deleteHadHandleTagIds(itemsysid)
+ refreshDryCocoonInDetail(false)
+ }
+ }
+ }
+
+ private val _dryCocoonInfoQueryDialogData = MutableStateFlow(DryCocoonInfoQueryDialogData())
+ val dryCocoonInfoQueryDialogData = _dryCocoonInfoQueryDialogData.asStateFlow()
+
+ fun showDryCocoonInfoQueryDialog() {
+ doInIoThread {
+ _dryCocoonInfoQueryDialogData.value = DryCocoonInfoQueryDialogData(
+ showDialog = true,
+ cjsysid = _info.value.cjsysid,
+ jiantypesysid = _info.value.jiantypesysid,
+ gjcksysid = _info.value.gjcksysid,
+ printTicket = {
+ printDryCocoonLabel(it)
+ },
+ onDismiss = {
+ _dryCocoonInfoQueryDialogData.value =
+ _dryCocoonInfoQueryDialogData.value.copy(showDialog = false)
+ }
+ )
+ }
+ }
+
+ private val _dryCocoonFilterDialog =
+ MutableStateFlow(
+ DryCocoonFilterDialogData(
+ viewModel = this,
+ myCardReaderShowViewModel = MyCardReaderShowViewModel()
+ )
+ )
+ val dryCocoonFilterDialog = _dryCocoonFilterDialog.asStateFlow()
+
+ fun showDryCocoonFilterDialog(myCardReaderShowViewModel: MyCardReaderShowViewModel) {
+ doInIoThread {
+ _dryCocoonFilterDialog.value = DryCocoonFilterDialogData(
+ showDialog = true,
+ viewModel = this,
+ myCardReaderShowViewModel = myCardReaderShowViewModel,
+ onDismiss = {
+ _dryCocoonFilterDialog.update { it.copy(showDialog = false) }
+ }
+ )
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonOutScreen.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonOutScreen.kt
new file mode 100644
index 0000000..1071a54
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonOutScreen.kt
@@ -0,0 +1,303 @@
+package com.bbitcn.f8.pad.ui.screen.secondFunc
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.paging.compose.collectAsLazyPagingItems
+import com.bbitcn.f8.pad.IS_DEBUG_DRYCOCOON
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MainFuncFrame
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.MyCard
+
+import com.bbitcn.f8.pad.base.MyRefreshTable
+import com.bbitcn.f8.pad.base.MyTableData
+import com.bbitcn.f8.pad.ui.screen.dialog.drycocoon.DryCocoonLossDialogInOut
+import com.bbitcn.f8.pad.ui.screen.view.deviceManager.scale.MyWeightShow
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.deviceManager.reader.MyCardReaderShowViewModel
+import com.bbitcn.f8.pad.ui.theme.MyColors
+import com.bbitcn.f8.pad.ui.viewmodel.factory.AddDryCocoonOutViewModelFactory
+import com.bbitcn.f8.pad.utils.MyUtil
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.uhf.UHFReaderG06M_G25M
+import com.bbitcn.f8.pad.utils.log.MyLog
+import kotlinx.coroutines.flow.flowOf
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun DryCocoonOutScreenPreview() {
+ DryCocoonOutScreen("1123")
+}
+
+@Composable
+fun DryCocoonOutScreen(
+ sysId: String,
+ myCardReaderShowViewModel: MyCardReaderShowViewModel = viewModel()
+) {
+ LaunchedEffect(Unit) {
+ MyLog.test("第一次进入,清空tagId缓存")
+ myCardReaderShowViewModel.clearList()
+ }
+ val addDryCocoonOutViewModel = viewModel(
+ factory = AddDryCocoonOutViewModelFactory(sysId)
+ )
+ val info by addDryCocoonOutViewModel.info.collectAsState()
+ val myPager = addDryCocoonOutViewModel.dryCocoonOutDetailMyPager
+ val dryOut =
+ addDryCocoonOutViewModel.dryCocoonOutDetailPager.collectAsLazyPagingItems()
+ val isRefreshing by myPager.listIsRefreshing.collectAsState()
+ val dryCocoonLossDialogData by addDryCocoonOutViewModel.dryCocoonLossDialogData.collectAsState()
+
+ MainFuncFrame {
+ Row(
+ modifier = M
+ .fillMaxWidth()
+ ) {
+ MyCard(
+ modifier = M
+ .fillMaxSize()
+ .weight(2f)
+ ) {
+ Column(
+ modifier = M
+ .weight(2f)
+ .padding(10.dp)
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = M
+ .padding(10.dp)
+ .fillMaxWidth()
+ ) {
+ Text(
+ modifier = M.padding(vertical = 5.dp),
+ text = info.code,
+ maxLines = 1,
+ color = MyColors.Orange,
+ fontSize = MaterialTheme.typography.headlineSmall.fontSize,
+ fontWeight = FontWeight.Bold
+ )
+ VerticalInfo("仓库", info.ckname)
+ VerticalInfo("茧别", info.jiantype)
+ VerticalInfo("蚕品种", info.canpinzhong)
+ VerticalInfo("空包释放", info.releasebaoshu.toString())
+ BigVerticalInfo("总包数", info.baoshu.toString())
+ }
+ MyRefreshTable(
+ modifier = M
+ .fillMaxWidth()
+ .weight(1f),
+ isRefreshing = isRefreshing,
+
+ info = dryOut,
+ key = { it.sysid },
+ onFinishRefresh = {
+ myPager.setListIsRefreshClose()
+ },
+ onLongClick = {
+ Toasty.showConfirmDialog("是否删除<${it.code}>茧包") {
+ addDryCocoonOutViewModel.deleteDryCocoonOutDetail(it.sysid) {
+ dryOut.refresh()
+ }
+ }
+ },
+ items = listOf(
+ MyTableData("包码", 3, { it.code }),
+ MyTableData("毛重", 2, { it.maozhong.toString() }),
+ MyTableData("皮重", 2, { it.pizhong.toString() }),
+ MyTableData("净重", 2, { it.jingzhong.toString() }),
+ ),
+ scrollToTopOnRefresh = true,
+ )
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = M
+ .padding(10.dp)
+ .fillMaxWidth()
+ ) {
+ VerticalInfo("区域", info.xiangzhen)
+ VerticalInfo("往来单位", info.wldwname)
+ VerticalInfo("车牌号", info.carpaihao)
+ VerticalInfo(
+ "日期", info.ckdatetime.replace(" ", "\n")
+ )
+ }
+ }
+ }
+ var grossWeight by rememberSaveable { mutableStateOf(0.0) }
+ var netWeight = MyUtil.formatDouble(grossWeight - info.bagzhongliang)
+ val curReader by myCardReaderShowViewModel.curDevice.collectAsState()
+ val deviceTagIds by (curReader?.tagList
+ ?: flowOf(emptyList())).collectAsState(initial = emptyList())
+// val tagIds by (curReader?.tagList
+// ?: flowOf(emptyList())).collectAsState(initial = emptyList())
+ val tagIds by addDryCocoonOutViewModel.tagIds.collectAsState()
+ // 称稳定时间
+ var weightStableSeconds by rememberSaveable { mutableStateOf(0) }
+ var hasBecomeZero by rememberSaveable { mutableStateOf(true) }
+ var isAutoOperate by rememberSaveable { mutableStateOf(false) }
+ LaunchedEffect(deviceTagIds) {
+ addDryCocoonOutViewModel.filterTagIds(deviceTagIds)
+ }
+ MyCard(
+ modifier = M
+ .weight(1f)
+ .padding(start = 10.dp),
+ ) {
+ Column(
+ modifier = M.padding(10.dp),
+ verticalArrangement = Arrangement.spacedBy(10.dp),
+ horizontalAlignment = Alignment.End
+ ) {
+ var weightErrorMsg by rememberSaveable { mutableStateOf("") }
+ val tagErrorMsg =
+ if (curReader == null) "未检测到读卡器"
+ else if (tagIds.isEmpty()) "未检测到麻袋"
+ else if (tagIds.size > 1) "检测到多个麻袋"
+ else ""
+ val netWeightErrorMsg = if (netWeight < 0) "净重小于0" else ""
+ val standardPackageWeight by addDryCocoonOutViewModel.standardPackageWeight.collectAsState()
+ val onSave = { onFinish: () -> Unit ->
+ addDryCocoonOutViewModel.saveDryCocoonOutDetail(
+ rfid = tagIds[0], netWeight = netWeight, grossWeight = grossWeight, {
+ dryOut.refresh()
+ myCardReaderShowViewModel.clearList()
+ }, onFinish = onFinish
+ )
+ }
+ val allErrorMsg = if (tagErrorMsg.isNotEmpty()) tagErrorMsg + "\n" else "" +
+ if (netWeightErrorMsg.isNotEmpty()) netWeightErrorMsg + "\n" else "" +
+ if (weightErrorMsg.isNotEmpty()) weightErrorMsg + "\n" else ""
+ MyWeightShow(targetStableWeight = standardPackageWeight, onErrorMsg = {
+ weightErrorMsg = it
+ }, onStableTimeChanged = {
+ weightStableSeconds = it
+ }) {
+ grossWeight = it
+ if (it == 0.0) {
+ hasBecomeZero = true
+ }
+ }
+ DryCocoonInfo("皮重(${info.bagtype})", "${info.bagzhongliang}kg", "")
+ DryCocoonInfo(
+ "净重", "${netWeight}kg", netWeightErrorMsg
+ )
+ DryCocoonInfo(
+ "麻袋ID",
+ if (tagIds.isNotEmpty()) "${tagIds[0].take(2)}...${tagIds[0].takeLast(4)}" else "",
+ tagErrorMsg
+ )
+
+ Row(
+ modifier = M.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ MyButton(
+ modifier = M.padding(start = 5.dp),
+ text = "新增茧包出库",
+ enabled = allErrorMsg.isEmpty(),
+ ) {
+ if (isAutoOperate) {
+ Toasty.showTipsDialog("请先关闭自动出库,以防误操作")
+ } else if (!allErrorMsg.isEmpty()) {
+ Toasty.showTipsDialog(allErrorMsg)
+ } else {
+ Toasty.showConfirmDialog(
+ "该茧包未入库,将作为额外茧包出库",
+ "新增茧包出库"
+ ) {
+ addDryCocoonOutViewModel.addNewPackageOut(
+ netWeight = netWeight,
+ grossWeight = grossWeight,
+ rfid = tagIds[0]
+ ){
+ dryOut.refresh()
+ myCardReaderShowViewModel.clearList()
+ }
+ }
+ }
+ }
+ MyButton(text = "重新检测茧包") {
+ myCardReaderShowViewModel.clearList()
+ }
+ }
+ Row(
+ modifier = M.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ MyButton(
+ modifier = M.padding(start = 5.dp),
+ text = "释放空包"
+ ) {
+ if (isAutoOperate) {
+ Toasty.showTipsDialog("请先关闭自动出库,以防误操作")
+ return@MyButton
+ }
+ addDryCocoonOutViewModel.showLossPackageForOut()
+ }
+ }
+ if (IS_DEBUG_DRYCOCOON) {
+ Row(
+ modifier = M.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ MyButton(text = "增加随机芯片") {
+ UHFReaderG06M_G25M.testXP()
+ }
+ MyButton(text = "测试增加芯片1") {
+ UHFReaderG06M_G25M.testXP("521323232")
+ }
+ }
+ }
+ Spacer(modifier = M.weight(1f))
+ Column(modifier = M.fillMaxWidth()) {
+ AutoOperate(
+ "自动出库",
+ tagIds,
+ grossWeight,
+ weightStableSeconds,
+ addDryCocoonOutViewModel,
+ hasBecomeZero, autoOperate = isAutoOperate, onAutoOperate = {
+ isAutoOperate = it
+ }
+ ) {
+ hasBecomeZero = false
+ onSave.invoke { it.invoke() }
+ }
+ MyWeightButton(
+ text = "出库本包",
+ isEnable = allErrorMsg.isEmpty(),
+ ) {
+ // 出库本包
+ if (allErrorMsg.isEmpty()) {
+ onSave.invoke { }
+ } else {
+ Toasty.showTipsDialog(allErrorMsg)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ DryCocoonLossDialogInOut(dryCocoonLossDialogData)
+}
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonOutViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonOutViewModel.kt
new file mode 100644
index 0000000..b3e3df8
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddDryCocoonOutViewModel.kt
@@ -0,0 +1,207 @@
+package com.bbitcn.f8.pad.ui.screen.secondFunc
+
+import androidx.lifecycle.viewModelScope
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.model.net.request.DryCocoonPackageForOutLossRequest
+import com.bbitcn.f8.pad.model.net.request.DryCocoonSaveNewOutDetail
+import com.bbitcn.f8.pad.model.net.request.DryCocoonSaveOutDetail
+import com.bbitcn.f8.pad.model.net.request.SearchOutDetailByRFIDRequest
+import com.bbitcn.f8.pad.model.net.response.CocoonOutDetailResponse
+import com.bbitcn.f8.pad.ui.screen.dialog.drycocoon.DryCocoonLossDialogInOutData
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.utils.AudioPlayer
+import com.bbitcn.f8.pad.utils.TimeUtils
+import com.bbitcn.f8.pad.utils.pager.DryCocoonOutDetailPagingSource
+import com.bbitcn.f8.pad.utils.pager.MyPager
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import kotlin.random.Random
+
+
+class AddDryCocoonOutViewModel(val outSystemId: String) : AddDryCocoonBaseViewModel() {
+
+ private val _info = MutableStateFlow(CocoonOutDetailResponse.Data())
+ val info = _info.asStateFlow()
+
+ init {
+ refreshDryCocoonOutDetail(true)
+ }
+
+ /**
+ * 出库详情统计信息
+ */
+ fun refreshDryCocoonOutDetail(showDialog: Boolean = true) {
+ doInIoThreadWith(showDialog, "正在刷新出库详情") {
+ val info = apiService.getCocoonOutDetail(outSystemId)
+ if (info.code != 1) {
+ Toasty.showTipsDialog("请求错误,请尝试退出重新进入:${info.msg}")
+ } else {
+ _info.value = info.data
+ }
+ }
+ }
+
+ /**
+ * 出库详情
+ */
+ val dryCocoonOutDetailMyPager = MyPager(
+ pagingSourceFactory = { DryCocoonOutDetailPagingSource(outSystemId) },
+ initialRequestData = "", // 传入初始的请求数据
+ )
+ val dryCocoonOutDetailPager = dryCocoonOutDetailMyPager.createPager(viewModelScope)
+
+ fun saveDryCocoonOutDetail(
+ rfid: String,
+ netWeight: Double,
+ grossWeight: Double,
+ onSuccess: () -> Unit,
+ onFinish: () -> Unit = { } // 添加onFinish回调
+ ) {
+ doInIoThread("正在保存本包出库记录", onFinish = onFinish, onError = {
+ // 当发生错误时
+ AudioPlayer.playAudioOnce(R.raw.dry_cocoon_out_failed_net)
+// TTSManager.speak("出库失败,网络异常,请检查网络连接", true)
+ }) {
+ val searchOutDetailByRFIDRequest: SearchOutDetailByRFIDRequest =
+ SearchOutDetailByRFIDRequest(
+ cjsysid = _info.value.cjsysid,
+ jiantypesysid = _info.value.jiantypesysid,
+ gjcksysid = _info.value.gjcksysid,
+ rfid = rfid
+ )
+ val dryCocoonSaveOutDetail =
+ DryCocoonSaveOutDetail(
+ rfid = rfid,
+ cksysid = _info.value.sysid,
+ code = _info.value.code,
+ baoshu = 1,
+ jingzhong = netWeight,
+ maozhong = grossWeight,
+ pizhong = _info.value.bagzhongliang
+ )
+ var res = false
+ // 根据RFID查询包码
+ val searchResponse = apiService.searchOutDetailByRFID(searchOutDetailByRFIDRequest)
+ if (searchResponse.code != 1) {
+ Toasty.showTipsDialog(searchResponse.msg)
+ } else {
+ // 检查是否出库·
+ if (searchResponse.data.ischuku == 1) {
+// TTSManager.speak("该包已出库,无法继续出库")
+ AudioPlayer.playAudioOnce(R.raw.dry_cocoon_out_failed)
+ Toasty.showTipsDialog("该包已出库,无法继续出库")
+ } else {
+ // 保存出库记录
+ val response =
+ apiService.saveDryCocoonOutDetail(dryCocoonSaveOutDetail.copy(code = searchResponse.data.code))
+ if (response.code != 1) {
+ AudioPlayer.playAudioOnce(R.raw.dry_cocoon_out_failed)
+// TTSManager.speak("出库失败," + response.msg)
+ Toasty.showTipsDialog(response.msg)
+ } else {
+ Toasty.success("保存成功")
+ addHadHandleTagIds(searchResponse.data.sysid, searchOutDetailByRFIDRequest.rfid )
+ AudioPlayer.playAudioOnce(R.raw.dry_cocoon_out_success)
+// TTSManager.speak("第" + (_info.value.baoshu + 1).toChineseNumber() + "包已出库")
+ refreshDryCocoonOutDetail(false)
+ onSuccess()
+ res = true
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 新增茧包出库
+ */
+ fun addNewPackageOut(rfid: String, netWeight: Double, grossWeight: Double,onSuccess: () -> Unit) {
+ doInIoThread {
+ val res = apiService.addNewPackageOut(
+ DryCocoonSaveNewOutDetail(
+ jingzhong = netWeight,
+ maozhong = grossWeight,
+ pizhong = _info.value.bagzhongliang,
+ cksysid = _info.value.sysid,
+ rfid = rfid,
+ )
+ )
+ if (res.code != 1) {
+ Toasty.showTipsDialog(res.msg)
+ } else {
+ Toasty.success("新包已直接出库")
+ addHadHandleTagIds(res.data.toString(), rfid )
+ refreshDryCocoonOutDetail(false)
+ // 刷新出库列表
+ onSuccess()
+ }
+ }
+ }
+
+ fun deleteDryCocoonOutDetail(itemsysid: String, onSuccess: () -> Unit) {
+ doInIoThread("正在删除本包出库记录") {
+ val response = apiService.deleteDryCocoonOutDetail(itemsysid)
+ if (response.code != 1) {
+ Toasty.showTipsDialog(response.msg)
+ } else {
+ Toasty.success("删除成功")
+ onSuccess()
+ // 删除已处理的RFID 必须在成功后删除
+ deleteHadHandleTagIds(itemsysid)
+ refreshDryCocoonOutDetail(false)
+ }
+ }
+ }
+
+ /**
+ * 出库-空包释放
+ */
+ private val _dryCocoonLossDialogData: MutableStateFlow =
+ MutableStateFlow(DryCocoonLossDialogInOutData())
+ val dryCocoonLossDialogData = _dryCocoonLossDialogData.asStateFlow()
+
+ fun showLossPackageForOut() {
+ doInIoThread {
+ _dryCocoonLossDialogData.value =
+ DryCocoonLossDialogInOutData(
+ true,
+ onLossPackage = { rfids, onFinish ->
+ doInIoThread("正在释放空包") {
+ rfids.forEach {
+ // 释放空包
+ val response = apiService.onLossPackageForOut(
+ DryCocoonPackageForOutLossRequest(
+ ckdsysid = _info.value.sysid,
+ rfid = it,
+ time = TimeUtils.getStringTime(),
+ cjsysid = _info.value.cjsysid
+ )
+ )
+ if (response.code != 1) {
+ Toasty.showTipsDialog(response.msg)
+ } else {
+ Toasty.success("释放成功")
+ onFinish()
+ }
+ }
+ refreshDryCocoonOutDetail(false)
+ }
+ },
+ onDismiss = {
+ _dryCocoonLossDialogData.update { it.copy(showDialog = false) }
+ }
+ )
+ }
+ }
+
+// fun getPackageWeightByName(name: String): Double {
+// for (i in _packageKinds.value) {
+// if (i.name == name) {
+// return i.weight
+// }
+// }
+// return 0.0
+// }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddUserScreen.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddUserScreen.kt
new file mode 100644
index 0000000..7ce376b
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddUserScreen.kt
@@ -0,0 +1,492 @@
+package com.bbitcn.f8.pad.ui.screen.secondFunc
+
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.bbitcn.f8.pad.M
+import com.bbitcn.f8.pad.base.MyCard
+import com.bbitcn.f8.pad.base.MyTextField
+import com.bbitcn.f8.pad.base.VerticalTabPages
+
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavController
+import androidx.navigation.compose.rememberNavController
+import coil3.compose.AsyncImage
+
+import com.bbitcn.f8.pad.ui.screen.dialog.ScanDialog
+import com.bbitcn.f8.pad.R
+import com.bbitcn.f8.pad.base.BigButton
+import com.bbitcn.f8.pad.base.MainFuncFrame
+import com.bbitcn.f8.pad.base.MyButton
+import com.bbitcn.f8.pad.base.MyCheckBox
+import com.bbitcn.f8.pad.base.VipBadge
+import com.bbitcn.f8.pad.ui.screen.dialog.FaceDialog
+import com.bbitcn.f8.pad.ui.screen.dialog.OCRDialog
+import com.bbitcn.f8.pad.ui.screen.view.common.CombinedDropdownMenu
+import com.bbitcn.f8.pad.ui.screen.view.common.SelectableChipGroup
+
+@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
+@Composable
+fun AddUserScreenPV() {
+ AddUserScreen()
+}
+
+@Composable
+fun AddUserScreen(
+ sysId: String = "",
+ navController: NavController = rememberNavController(),
+ addUserViewModel: AddUserViewModel = viewModel()
+) {
+ LaunchedEffect(sysId) {
+ // 当进入修改页面时,初始化数据
+ if (sysId.isNotEmpty()) {
+ addUserViewModel.sysId = sysId
+ addUserViewModel.refreshUserData(addUserViewModel.sysId)
+ addUserViewModel.initExtendData()
+ }
+ }
+ LaunchedEffect(addUserViewModel.sysId) {
+ // 当新增农户后,初始化拓展信息
+ if (addUserViewModel.sysId.isNotEmpty()) {
+ addUserViewModel.initExtendData()
+ }
+ }
+ val scanDialogData by addUserViewModel.scanDialogData.collectAsState()
+ val ocrDialogData by addUserViewModel.ocrDialogData.collectAsState()
+ val faceDialogData by addUserViewModel.faceDialogData.collectAsState()
+ MainFuncFrame {
+ MyCard(modifier = M.fillMaxSize(), radius = 7.dp, elevation = 1.dp) {
+ VerticalTabPages(
+ modifier = M
+ .fillMaxHeight()
+ .padding(vertical = 15.dp),
+ tabs = listOf("基本信息", "人脸录入", "附件上传", "拓展信息"),
+ ) {
+ when (it) {
+ 0 -> AddUserBaseInfo(addUserViewModel)
+ 1 -> AddUserFaceInfo(addUserViewModel)
+ 2 -> AddUserAttachmentInfo(addUserViewModel)
+ 3 -> AddUSerExtendInfo(addUserViewModel)
+ }
+ }
+ }
+ }
+ ScanDialog(scanDialogData)
+ OCRDialog(ocrDialogData)
+ FaceDialog(faceDialogData)
+}
+
+@Composable
+fun AddUserFaceInfo(addUserViewModel: AddUserViewModel) {
+ InputFrame("人脸录入") {
+ val faceList by addUserViewModel.faceList.collectAsState()
+ Column(modifier = M.fillMaxSize()) {
+ MyButton(text = "刷新人脸数据") {
+ addUserViewModel.refreshFaceImages()
+ }
+ LazyRow {
+ items(faceList) {
+ AsyncImage(
+ model = it,
+ modifier = M
+ .size(100.dp)
+ .padding(20.dp),
+ contentDescription = null,
+ )
+ }
+ item {
+ Image(
+ modifier = M
+ .size(100.dp)
+ .padding(20.dp)
+ .clickable {
+ // 人脸录入 注册/农户
+ addUserViewModel.showFaceDialog()
+ },
+ painter = painterResource(id = R.drawable.ic_upload),
+ contentDescription = null
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun AddUserAttachmentInfo(addUserViewModel: AddUserViewModel) {
+ VipBadge {
+ InputFrame("附件上传") {
+ val fileList by addUserViewModel.fileList.collectAsState()
+ LazyRow {
+ items(fileList) {
+ Column(
+ modifier = M
+ .size(100.dp)
+ .padding(20.dp)
+ ) {
+ Text(text = it.attname + it.suffix)
+ Text(text = "${(it.size / 1024)}MB")
+ }
+ }
+ item {
+ Image(
+ modifier = M
+ .size(100.dp)
+ .padding(20.dp),
+ painter = painterResource(id = R.drawable.ic_upload),
+ contentDescription = null
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun AddUserBaseInfo(
+ addUserViewModel: AddUserViewModel
+) {
+ val configuration = LocalConfiguration.current
+ // 判断设备是否为竖屏
+ if (configuration.orientation == android.content.res.Configuration.ORIENTATION_PORTRAIT) {
+ // 竖屏模式
+ LazyColumn(modifier = M.padding(10.dp)) {
+ item {
+ BigButton(
+ modifier = M.padding(top = 15.dp).animateItem(),
+ text = "保存",
+ isLight = true
+ ) {
+ addUserViewModel.editFarmer()
+ }
+ AddUserLeftColumn(
+ addUserViewModel = addUserViewModel
+ )
+ AddUserRightColumn(addUserViewModel = addUserViewModel)
+ }
+ }
+ } else {
+ // 横屏模式
+ Row(modifier = M.padding(10.dp), horizontalArrangement = Arrangement.spacedBy(10.dp)) {
+ Column(modifier = M.weight(1f)) {
+ MyButton(
+ modifier = M.padding(top = 15.dp),
+ text = "保存",
+ ) {
+ addUserViewModel.editFarmer()
+ }
+ AddUserLeftColumn(
+ modifier = M.weight(1f),
+ addUserViewModel,
+ )
+ }
+ AddUserRightColumn(M.weight(1f), addUserViewModel)
+ }
+ }
+}
+
+@Composable
+fun AddUSerExtendInfo(addUserViewModel: AddUserViewModel) {
+ val userExtendList by addUserViewModel.userExtendList.collectAsState()
+ LazyColumn {
+ item {
+ Column(modifier = M.padding(top = 5.dp).animateItem()) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(5.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ if (addUserViewModel.sysId.isEmpty()) {
+ Text(text = "暂无农户信息,请先保存基本信息", modifier = M.padding(10.dp))
+ } else {
+ MyButton(text = "保存") {
+ addUserViewModel.saveExtendInfo()
+ }
+ }
+ }
+ LazyRow {
+ items(userExtendList) { info ->
+ // bool
+ if (info.coltype == 3) {
+ MyCheckBox(
+ modifier = M.fillMaxWidth(),
+ title = info.coltitle,
+ value = info.colvalue.toBoolean()
+ ) {
+ addUserViewModel.updateExtendInfo(info.colsysid, it.toString())
+ }
+ }
+ }
+ }
+ }
+ }
+ items(userExtendList, key = { it.colsysid }) { info ->
+ if (info.coltype == 3) {
+ return@items
+ }
+ InputFrame(info.coltitle) {
+ // 0: string字符 1: decimal或double 2: int 3: bool 4: 日期(长日期) 5: 下拉选择
+ when (info.coltype) {
+ 0 -> MyTextField(
+ modifier = M.fillMaxWidth(),
+ hint = info.coltitle,
+ value = info.colvalue
+ ) {
+ addUserViewModel.updateExtendInfo(info.colsysid, it)
+ }
+
+ 1 -> MyTextField(
+ modifier = M.fillMaxWidth(),
+ hint = info.coltitle,
+ isNumberInputType = true,
+ value = info.colvalue
+ ) {
+ addUserViewModel.updateExtendInfo(info.colsysid, it)
+ }
+
+ 2 -> MyTextField(
+ modifier = M.fillMaxWidth(),
+ hint = info.coltitle,
+ isNumberInputType = true,
+ value = info.colvalue
+ ) {
+ addUserViewModel.updateExtendInfo(info.colsysid, it)
+ }
+
+ 4 -> MyTextField(
+ modifier = M.fillMaxWidth(),
+ hint = info.coltitle,
+ readOnly = true,
+ isSelectDate = true,
+ value = info.colvalue
+ ) {
+ addUserViewModel.updateExtendInfo(info.colsysid, it)
+ }
+
+ 5 -> CombinedDropdownMenu(
+ M.fillMaxWidth(),
+ info.coldroplist.split(";"),
+ info.coltitle,
+ info.colvalue
+ ) {
+ addUserViewModel.updateExtendInfo(info.colsysid, it)
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun AddUserLeftColumn(
+ modifier: Modifier = M,
+ addUserViewModel: AddUserViewModel,
+) {
+ val userTypeList by addUserViewModel.userTypeList.collectAsState()
+ Column(modifier, verticalArrangement = Arrangement.spacedBy(5.dp)) {
+ InputFrame("基本信息") {
+ MyTextField(modifier = M.weight(1f), hint = "姓名", value = addUserViewModel.idName) {
+ addUserViewModel.idName = it
+ }
+ CombinedDropdownMenu(
+ M.weight(1f),
+ listOf("男", "女"),
+ "性别",
+ addUserViewModel.idGender
+ ) {
+ addUserViewModel.idGender = it
+ }
+ CombinedDropdownMenu(
+ M.weight(1f),
+ userTypeList.map { it.name },
+ "农户类别",
+ addUserViewModel.userType
+ ) {
+ addUserViewModel.userType = it
+ }
+ }
+ InputFrame("手机号码") {
+ MyTextField(
+ modifier = M.weight(1f), hint = "请输入", value = addUserViewModel.userPhone,
+ isNumberInputType = true,
+ ) {
+ addUserViewModel.userPhone = it
+ }
+ }
+ InputFrame("身份证号") {
+ MyTextField(
+ modifier = M.weight(1f), hint = "请输入", value = addUserViewModel.idCardNumber,
+ isNumberInputType = true,
+ ) {
+ addUserViewModel.idCardNumber = it
+ }
+ MyButton(text = "NFC") {
+ addUserViewModel.openIDCardDialog({
+ addUserViewModel.idName = it.name
+ addUserViewModel.idGender = it.sex
+ addUserViewModel.idCardNumber = it.id
+ addUserViewModel.idCardAddress = it.address
+ }) { xian, xiang, cun, zu ->
+ // 地址解析
+ addUserViewModel.userXian = xian
+ addUserViewModel.userXiang = xiang
+ addUserViewModel.userCun = cun
+ addUserViewModel.userZu = zu
+ }
+ }
+ VipBadge {
+ MyButton(text = "拍照识别") {
+ addUserViewModel.recognizeIdCard({ name, gender, idCard, address ->
+ addUserViewModel.idName = name
+ addUserViewModel.idGender = gender
+ addUserViewModel.idCardNumber = idCard
+ addUserViewModel.idCardAddress = address
+ }) { xian, xiang, cun, zu ->
+ addUserViewModel.userXian = xian
+ addUserViewModel.userXiang = xiang
+ addUserViewModel.userCun = cun
+ addUserViewModel.userZu = zu
+ }
+ }
+ }
+ }
+ InputFrame("身份证地址") {
+ MyTextField(
+ modifier = M.weight(1f),
+ hint = "请输入",
+ readOnly = true,
+ value = addUserViewModel.idCardAddress
+ )
+ }
+ val xianList by addUserViewModel.xianList.collectAsState()
+ val xiangList by addUserViewModel.xiangList.collectAsState()
+ val cunList by addUserViewModel.cunList.collectAsState()
+ val zuList by addUserViewModel.zuList.collectAsState()
+ InputFrame("地址") {
+ CombinedDropdownMenu(M.weight(1f), xianList, "县", addUserViewModel.userXian, true) {
+ addUserViewModel.userXian = it
+ // 四级联动
+ addUserViewModel.onAddressSelected()
+ }
+ CombinedDropdownMenu(M.weight(1f), xiangList, "乡", addUserViewModel.userXiang, true) {
+ addUserViewModel.userXiang = it
+ addUserViewModel.onAddressSelected()
+ }
+ CombinedDropdownMenu(M.weight(1f), cunList, "村", addUserViewModel.userCun, true) {
+ addUserViewModel.userCun = it
+ addUserViewModel.onAddressSelected()
+ }
+ CombinedDropdownMenu(M.weight(1f), zuList, "组", addUserViewModel.userZu, true) {
+ addUserViewModel.userZu = it
+ addUserViewModel.onAddressSelected()
+ }
+ }
+ }
+}
+
+@Composable
+fun AddUserRightColumn(
+ modifier: Modifier = M,
+ addUserViewModel: AddUserViewModel
+) {
+ val mainBankCard by addUserViewModel.mainCardInfo.collectAsState()
+ val subBankCard by addUserViewModel.subCardInfo.collectAsState()
+ Column(modifier, verticalArrangement = Arrangement.spacedBy(5.dp)) {
+ val userLabelList by addUserViewModel.userLabelList.collectAsState()
+ InputFrame("农户标签") {
+ SelectableChipGroup(M.weight(1f), userLabelList) {
+ addUserViewModel.switchUserLabel(it)
+ }
+ }
+ InputFrame("主银行卡") {
+ MyTextField(
+ modifier = M.weight(1f), hint = "请输入", value = mainBankCard.first,
+ isNumberInputType = true,
+ ) {
+ addUserViewModel.clearBankCardInfo(true)
+ addUserViewModel.updateBankCardCode(true, it)
+ }
+ MyButton(text = "NFC") {
+ addUserViewModel.openNFCDialog(true)
+ }
+ VipBadge {
+ MyButton(text = "拍照识别") {
+ addUserViewModel.recognizeBankCard(true)
+ }
+ }
+ }
+ InputFrame("主银行卡开户银行") {
+ MyTextField(
+ modifier = M.weight(1f), hint = "请输入", value = mainBankCard.second.bankName,
+ readOnly = true,
+ isNumberInputType = true,
+ )
+ MyButton(text = "查询归属行") {
+ addUserViewModel.analysisBankCard(subBankCard.first, true)
+ }
+ }
+ InputFrame("副银行卡") {
+ MyTextField(
+ modifier = M.weight(1f), hint = "请输入", value = subBankCard.first,
+ isNumberInputType = true,
+ ) {
+ addUserViewModel.clearBankCardInfo(false)
+ addUserViewModel.updateBankCardCode(false, it)
+ }
+ MyButton(text = "NFC") {
+ addUserViewModel.openNFCDialog(false)
+ }
+ VipBadge {
+ MyButton(text = "拍照识别") {
+ addUserViewModel.recognizeBankCard(false)
+ }
+ }
+ }
+ InputFrame("副银行卡开户银行") {
+ MyTextField(
+ modifier = M.weight(1f), hint = "请输入", value = subBankCard.second.bankName,
+ readOnly = true,
+ isNumberInputType = true,
+ )
+ MyButton(text = "查询归属行") {
+ addUserViewModel.analysisBankCard(subBankCard.first, false)
+ }
+ }
+ }
+}
+
+@Composable
+fun InputFrame(title: String, content: @Composable () -> Unit) {
+ Column(modifier = M.padding(top = 5.dp)) {
+ Text(text = title, fontSize = MaterialTheme.typography.headlineMedium.fontSize, fontWeight = FontWeight.Bold, modifier = M.padding(vertical = 7.5.dp))
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(5.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ content()
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddUserViewModel.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddUserViewModel.kt
new file mode 100644
index 0000000..ffc159c
--- /dev/null
+++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/secondFunc/AddUserViewModel.kt
@@ -0,0 +1,503 @@
+package com.bbitcn.f8.pad.ui.screen.secondFunc
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import com.alibaba.sdk.android.oss.OSS
+import com.alibaba.sdk.android.oss.OSSClient
+import com.alibaba.sdk.android.oss.common.auth.OSSCredentialProvider
+import com.alibaba.sdk.android.oss.common.auth.OSSPlainTextAKSKCredentialProvider
+import com.alibaba.sdk.android.oss.model.GeneratePresignedUrlRequest
+import com.bbitcn.f8.pad.MyApp
+import com.bbitcn.f8.pad.base.BaseViewModel
+import com.bbitcn.f8.pad.model.net.request.AddFarmerRequest
+import com.bbitcn.f8.pad.model.net.request.SaveExtendInfoRequest
+import com.bbitcn.f8.pad.model.net.response.AllExtendInfoResponse
+import com.bbitcn.f8.pad.model.net.response.BankInfoResponse
+import com.bbitcn.f8.pad.model.net.response.FarmerFileListResponse
+import com.bbitcn.f8.pad.model.net.response.UserTypeResponse
+import com.bbitcn.f8.pad.ui.screen.dialog.FaceDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.OCRDialogData
+import com.bbitcn.f8.pad.ui.screen.dialog.ScanDialogData
+import com.bbitcn.f8.pad.ui.screen.view.Toasty
+import com.bbitcn.f8.pad.ui.screen.view.Toasty.showTipsDialog
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.face.OssUtils
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.idcard.IDCardUtils
+import com.bbitcn.f8.pad.utils.externalModules.devices.reader.nfc.NFCUtils
+import com.bbitcn.f8.pad.utils.log.MyLog
+import com.zkteco.android.biometric.module.idcard.meta.IDCardInfo
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import java.util.Date
+
+
+class AddUserViewModel : BaseViewModel() {
+
+ private val _userTypeList = MutableStateFlow