feat: 初始化项目结构和基本功能

- 创建项目根目录和主要子模块
- 添加基本的 Activity 和布局文件
- 实现简单的导航和电话拨打功能
- 添加相机和图像处理相关代码
- 创建网络请求和数据加密工具类
- 设置 AndroidManifest 文件和权限
This commit is contained in:
songzhiling
2025-04-11 11:52:07 +08:00
parent 74ce8fc526
commit 91305ab9d1
386 changed files with 36517 additions and 34 deletions

1
servicing/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

221
servicing/build.gradle Normal file
View File

@ -0,0 +1,221 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.jetbrains.kotlin.android)
id 'com.google.devtools.ksp'
id 'maven-publish'
}
android {
namespace 'com.za.servicing'
compileSdk 35
defaultConfig {
minSdk 23
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
vectorDrawables {
useSupportLibrary true
}
manifestPlaceholders = [
JPUSH_PKGNAME : "${applicationId}",
JPUSH_APPKEY : "a87e46d05e9f095a2b47a304", //JPush 上注册的包名对应的 Appkey.e6c51448340caba93fd418
JPUSH_CHANNEL : "developer-default", //暂时填写默认值即可.
XIAOMI_APPID : "MI-2882303761518149120",
XIAOMI_APPKEY : "MI-5381814934120",//MI-小米的APPKEY
HUAWEI_APPID : "appid=100923923",//appid=华为的APPID"
OPPO_APPKEY : "OP-c8ce8eafcd3940ceb85c1ccbee8863c7",//OP-oppo的APPKEY
OPPO_APPID : "OP-30136992",//OP-oppo的APPID
OPPO_APPSECRET: "OP-25e2baa85b7946b1b365af515515c42d",//OP-oppo的APPSECRET
VIVO_APPKEY : "cfd443e2a1757cf537361588c988a12a",//vivo的APPKEY
VIVO_APPID : "105470845",//vivo的APPID
]
}
buildTypes {
release {
// minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '11'
}
buildFeatures {
compose true
buildConfig true // 生成 BuildConfig
resValues true // 允许资源值生成
}
composeOptions {
kotlinCompilerExtensionVersion '1.5.15'
}
packaging {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
publishing {
singleVariant("release") {
withSourcesJar()
}
}
}
publishing {
publications {
release(MavenPublication) {
groupId = 'io.github.szl9'
artifactId = 'zd_servicing'
version = "1.0.1.9"
pom {
packaging = "aar"
name.set("zd_servicing")
description.set("zd_servicing: Library for Android Application")
url.set("https://github.com/szl9/4dbki6r4uv.git")
inceptionYear.set("2024")
licenses {
license {
name.set("MIT License")
url.set("https://opensource.org/licenses/MIT")
}
}
developers {
developer {
id.set("zd")
name.set("zd szl")
email.set("17630035658@163.com")
}
}
scm {
connection.set("scm:git@github.com:szl9/4dbki6r4uv.git")
developerConnection.set("scm:git@github.com:szl9/4dbki6r4uv.git")
url.set("https://github.com/szl9/4dbki6r4uv.git")
}
}
// 将 afterEvaluate 移到 publications 块内
afterEvaluate {
from components.release
// 将依赖项处理放在这里
pom.withXml {
def root = asNode()
def dependenciesNode = root.dependencies ? root.dependencies[0] : root.appendNode('dependencies')
configurations.api.allDependencies.each { dep ->
try {
if (dep.group != null && dep.name != null && dep.version != null) {
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', dep.group)
dependencyNode.appendNode('artifactId', dep.name)
dependencyNode.appendNode('version', dep.version)
dependencyNode.appendNode('scope', 'compile')
}
}
catch (Exception e) {
logger.warn("Failed to add dependency ${dep} to POM", e)
}
}
}
}
}
}
repositories {
maven {
name = 'zd_servicing'
url = layout.buildDirectory.dir("zd_servicing")
}
}
}
tasks.register('generateRepo', Zip) {
def publishTask = tasks.named('publishReleasePublicationToZd_servicingRepository')
from publishTask.map { it.getRepository().getUrl() }
into 'zd_servicing'
archiveFileName.set('zd_servicing.zip')
}
dependencies {
api libs.androidx.core.ktx
api libs.androidx.appcompat
api libs.material
api libs.androidx.lifecycle.viewmodel.compose
api libs.androidx.lifecycle.runtime.ktx
api libs.androidx.activity.compose
api platform(libs.androidx.compose.bom)
api libs.androidx.ui
api libs.androidx.ui.graphics
api libs.androidx.ui.tooling.preview
api libs.androidx.material3
api libs.androidx.work.runtime.ktx
api libs.androidx.exifinterface
testApi libs.junit
androidTestApi libs.androidx.junit
androidTestApi libs.androidx.espresso.core
androidTestApi platform(libs.androidx.compose.bom)
androidTestApi libs.androidx.ui.test.junit4
debugApi libs.androidx.ui.tooling
debugApi libs.androidx.ui.test.manifest
api libs.coil.compose
api libs.coil.gif
api libs.permissionx
api libs.utilcodex
api libs.crashreport
// 高德地图
api libs.xdmap
api libs.location
api libs.search
// // JPush
api libs.jpush
api libs.jcore
api libs.gson
// 网络
api libs.retrofit
api libs.converter.gson
api libs.adapter.rxjava3
api libs.rxjava
api libs.rxandroid
api libs.logging.interceptor
api libs.fastjson
// 本地数据
api libs.room.runtime
annotationProcessor libs.room.compiler
ksp libs.room.compiler
api libs.mmkv
// 7z
api libs.xz
api libs.commons.compress
api libs.core
api libs.tbssdk
// CameraX
api libs.androidx.camera.core
api libs.androidx.camera.camera2
api libs.androidx.camera.lifecycle
api libs.androidx.camera.view
api libs.androidx.camera.extensions
api libs.glide
annotationProcessor libs.compiler
api libs.org.eclipse.paho.client.mqttv3
api libs.org.eclipse.paho.android.service
}

View File

203
servicing/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,203 @@
# 保留行号用于调试
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile
# 保留基本属性
-keepattributes Signature
-keepattributes *Annotation*
-keepattributes Exceptions
-keepattributes InnerClasses
# 保留R文件
-keepclassmembers class **.R$* {
public static <fields>;
}
# 保留servicing模块中的所有model类
-keep class com.za.servicing.model.** { *; }
# 保留servicing模块中的所有接口
-keep interface com.za.servicing.** { *; }
# 保留servicing模块中的所有枚举
-keepclassmembers enum com.za.servicing.** { *; }
# 保留Parcelable实现类
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# 保留Serializable实现类
-keepnames class * implements java.io.Serializable
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 保留WebView相关
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String);
}
# 保留native方法
-keepclasseswithmembernames class * {
native <methods>;
}
# 保留View的getter和setter
-keepclassmembers public class * extends android.view.View {
void set*(***); *** get*();
}
# Coil图片加载库
-keep class coil.** { *; }
-dontwarn coil.**
# PermissionX权限库
-keep class com.permissionx.guolindev.** { *; }
# 高德地图相关
-keep class com.amap.api.**{*;}
-keep class com.autonavi.**{*;}
-keep class com.loc.**{*;}
-dontwarn com.amap.api.**
-dontwarn com.autonavi.**
-dontwarn com.loc.**
# JPush相关
-dontoptimize
-dontpreverify
-dontwarn cn.jpush.**
-keep class cn.jpush.** { *; }
-dontwarn cn.jcore.**
-keep class cn.jcore.** { *; }
# Retrofit网络库
-keepattributes Signature
-keepattributes Exceptions
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepclasseswithmembers class * {
@retrofit2.http.* <methods>;
}
# OkHttp
-dontwarn okhttp3.**
-dontwarn okio.**
-keep class okhttp3.** { *; }
-keep class okio.** { *; }
# RxJava
-dontwarn io.reactivex.**
-keep class io.reactivex.** { *; }
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
long producerIndex;
long consumerIndex;
}
#blankJ
-dontwarn com.blankj.utilcode.**
-keepclassmembers class * {
@com.blankj.utilcode.util.BusUtils$Bus <methods>;
}
-keep public class * extends com.blankj.utilcode.util.ApiUtils$BaseApi
-keep,allowobfuscation @interface com.blankj.utilcode.util.ApiUtils$Api
-keep @com.blankj.utilcode.util.ApiUtils$Api class *
#mmkv
-renamesourcefileattribute SourceFile
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,EnclosingMethod
-keep public class * {
public protected *;
}
# Preserve all .class method names.
-keepclassmembernames class * {
java.lang.Class class$(java.lang.String);
java.lang.Class class$(java.lang.String, boolean);
}
# Preserve all native method names and the names of their classes.
-keepclasseswithmembernames class * {
native <methods>;
}
# Preserve the special static methods that are required in all enumeration
# classes.
-keepclassmembers class * extends java.lang.Enum {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# Gson
-keep class com.google.gson.** { *; }
-keepattributes Signature
-keepattributes *Annotation*
-keep class sun.misc.Unsafe { *; }
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
# Kotlin相关
-keep class kotlin.** { *; }
-keep class kotlin.Metadata { *; }
-dontwarn kotlin.**
-keepclassmembers class **$WhenMappings {
<fields>;
}
-keepclassmembers class kotlin.Metadata {
public <methods>;
}
# Kotlin协程
-keepclassmembernames class kotlinx.** {
volatile <fields>;
}
-keepclassmembers class * {
@kotlin.coroutines.jvm.internal.DebugMetadata *;
@kotlin.coroutines.jvm.internal.SuspendLambda *;
}
-keep class kotlinx.coroutines.** { *; }
-dontwarn kotlinx.coroutines.**
# Kotlin反射
-keep class kotlin.reflect.** { *; }
-dontwarn kotlin.reflect.**
# 保留Kotlin数据类
-keepclassmembers class ** {
public ** component*();
public ** copy(...);
}
# 保留Kotlin数据类的toString/hashCode/equals方法
-keepclassmembers class ** {
public java.lang.String toString();
public int hashCode();
public boolean equals(java.lang.Object);
}
-dontwarn java.lang.invoke.StringConcatFactory

View File

@ -0,0 +1,24 @@
package com.za.servicing
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.za.servicing.test", appContext.packageName)
}
}

View File

@ -0,0 +1,249 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-sdk tools:overrideLibrary="androidx.camera.view,androidx.camera:camera-camera2,androidx.camera.camera2,androidx.camera.lifecycle,androidx.camera.core" />
<permission
android:name="${applicationId}.permission.JPUSH_MESSAGE"
android:protectionLevel="signature" /> <!-- 位置相关权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" /> <!-- 存储相关权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <!-- 网络相关权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <!-- 蓝牙相关权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <!-- 电话相关权限 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.WRITE_CALL_LOG" />
<uses-permission
android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
tools:ignore="ProtectedPermissions" /> <!-- 系统相关权限 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.REORDER_TASKS" />
<uses-permission android:name="android.permission.RECEIVE_USER_PRESENT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission
android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <!-- 多媒体相关权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FLASHLIGHT" /> <!-- 硬件特性声明 -->
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false" />
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- Intent queries for Android 11+ -->
<queries package="${applicationId}">
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
<intent>
<action android:name="android.media.action.ACTION_VIDEO_CAPTURE" />
</intent>
</queries>
<queries>
<intent>
<action android:name="android.intent.action.OPEN_DOCUMENT" />
<data android:mimeType="image/*" />
</intent>
</queries>
<application>
<activity
android:name="com.za.ui.main.ServiceLauncherActivity"
android:exported="true"
android:theme="@style/Theme.Dealer">
</activity>
<activity
android:name="com.za.ui.main.ServicingMainActivity"
android:exported="false"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.servicing.departure_photo.DeparturePhotoActivity"
android:exported="false"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.servicing.order_confirm.input_money.InputMoneyActivity"
android:exported="false"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.servicing.order_confirm.modify_money.ModifyMoneyActivity"
android:exported="false"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.new_order.NewOrderActivity"
android:exported="false"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.servicing.order_confirm.real_order_confirm.RealOrderConfirmActivity"
android:exported="false"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.servicing.order_confirm.receive_money.ReceiveMoneyActivity"
android:exported="false"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.map_search.MapSearchActivity"
android:exported="false" />
<service
android:name="com.za.ui.order_report.ReportFloatingManager"
android:enabled="true"
android:exported="false" />
<activity
android:name="com.za.ui.order_report.HistoryReportActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name="com.za.ui.order_report.OrderReportActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name="com.za.ui.h5.CommonH5Activity"
android:exported="false"
android:launchMode="singleInstance"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.servicing.order_give_up.OrderGiveUpActivity"
android:exported="false"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.servicing.in_servicing_setting.OrderRequirementsActivity"
android:exported="false"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.servicing.order_confirm.OrderConfirmActivity"
android:exported="false"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.servicing.destination_photo.DestinationPhotoActivity"
android:exported="false"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.servicing.go_to_destination.GoToDestinationActivity"
android:exported="false"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.signature.GridPaintActivity"
android:exported="false" />
<activity
android:name="com.za.ui.servicing.operation.InOperationActivity"
android:exported="false"
android:label="@string/title_activity_in_operation"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.camera.ZdCameraXActivity"
android:exported="false"
android:screenOrientation="portrait">
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
<activity
android:name="com.za.ui.servicing.check_vehicle.CheckVehicleActivity"
android:exported="false"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.servicing.verify.VerifyOrderActivity"
android:exported="false"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.servicing.go_accident.GoAccidentSiteActivity"
android:exported="false"
android:theme="@style/Theme.Dealer" />
<activity
android:name="com.za.ui.servicing.wait_to_start.WaitToStartActivity"
android:exported="false"
android:theme="@style/Theme.Dealer" />
<service
android:name="cn.jpush.android.service.PushService"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="cn.jpush.android.intent.REGISTER" />
<action android:name="cn.jpush.android.intent.REPORT" />
<action android:name="cn.jpush.android.intent.PushService" />
<action android:name="cn.jpush.android.intent.PUSH_TIME" />
</intent-filter>
</service>
<receiver
android:name="com.za.service.jpush.JPushReceiver"
android:enabled="true"
android:exported="true"
tools:node="replace">
<intent-filter>
<action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED_ACTION" />
<action android:name="cn.jpush.android.intent.NOTIFICATION_OPENED_ACTION" />
<action android:name="cn.jpush.android.intent.CONNECTION" />
<action android:name="android.intent.action.USER_PRESENT" />
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<action android:name="cn.jpush.android.intent.RECEIVE_MESSAGE" />
<category android:name="${applicationId}" />
</intent-filter>
<intent-filter android:priority="1000">
<action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED_PROXY" />
<!-- Required 显示通知栏 -->
<category android:name="${applicationId}" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.USER_PRESENT" />
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
<!-- Optional -->
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package" />
</intent-filter>
</receiver>
<receiver
android:name="com.za.service.jpush.MyJPushMessageReceiver"
android:exported="true">
<intent-filter>
<action android:name="cn.jpush.android.intent.RECEIVE_MESSAGE" />
<action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED_ACTION" />
<action android:name="cn.jpush.android.intent.NOTIFICATION_OPENED_ACTION" />
<action android:name="cn.jpush.android.intent.CONNECTION" />
<action android:name="android.intent.action.USER_PRESENT" />
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<category android:name="${applicationId}" />
</intent-filter>
</receiver>
</application>
</manifest>

Binary file not shown.

View File

@ -0,0 +1,98 @@
package com.za.base
import com.za.common.GlobalData
object AppConfig {
var isRelease = false
// API 相关地址
lateinit var BASE_URL: String // API 主地址
lateinit var IMG_BASE_URL: String // 图片服务器地址
lateinit var Resource_URL: String // 资源服务器地址
// H5 相关地址
lateinit var TRAIN_URL: String // 培训文档地址
lateinit var DOCMENT_URL: String // 中道资料地址
/**
* 正式环境配置
*/
fun release() {
isRelease = true
// API 配置
BASE_URL = "https://api.sinoassist.com"
IMG_BASE_URL = "https://api.sinoassist.com"
Resource_URL = "https://www.sinoassist.com/res"
// H5 配置
TRAIN_URL = "https://www.sinoassist.com/h5/supplier/dispatch/diverTrainDocment"
DOCMENT_URL = "https://www.sinoassist.com/h5/supplier/dispatch/docmentList"
}
/**
* 审核环境配置
*/
fun review() {
isRelease = true
// API 配置
BASE_URL = "http://interface.review.sino-assist.com"
IMG_BASE_URL = "http://interface.review.sino-assist.com"
Resource_URL = "https://www.sinoassist.com/res"
}
/**
* CRM1 环境配置
*/
fun crm1() {
isRelease = false
// API 配置
BASE_URL = "https://api1.sino-assist.com"
IMG_BASE_URL = "https://api1.sino-assist.com"
Resource_URL = "https://crm1.sino-assist.com/res"
// H5 配置
TRAIN_URL = "https://crm1.sino-assist.com/h5/supplier/dispatch/diverTrainDocment"
DOCMENT_URL = "https://crm1.sino-assist.com/h5/supplier/dispatch/docmentList"
}
/**
* CRM2 环境配置
*/
fun crm2() {
isRelease = false
// API 配置
BASE_URL = "https://api2.sino-assist.com"
IMG_BASE_URL = "https://api2.sino-assist.com"
Resource_URL = "https://crm2.sino-assist.com/res"
}
/**
* 获取培训文档完整地址
* @param driverId 司机ID
* @param keyword 关键字
* @return 完整的培训文档URL
*/
fun getTrainUrl(keyWord: String = ""): String {
if (keyWord.isEmpty()) {
return TRAIN_URL + "?token=${GlobalData.token}&driverId=${GlobalData.driverInfo?.userId}"
}
return TRAIN_URL + "?token=${GlobalData.token}&driverId=${GlobalData.driverInfo?.userId}&keyword=$keyWord"
}
/**
* 获取中道资料完整地址
* @param driverId 司机ID
* @param keyword 关键字
* @return 完整的中道资料URL
*/
fun getDocmentUrl(keyWord: String = ""): String {
if (keyWord.isEmpty()) {
return DOCMENT_URL + "?token=${GlobalData.token}&driverId=${GlobalData.driverInfo?.userId}"
}
return DOCMENT_URL + "?token=${GlobalData.token}&driverId=${GlobalData.driverInfo?.userId}&keyword=$keyWord"
}
}

View File

@ -0,0 +1,63 @@
package com.za.base
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.Composable
import com.tencent.smtt.sdk.QbSdk
import com.za.base.BaseVm.Companion.showTipDialog
import com.za.base.theme.DealerTheme
import com.za.base.view.CommonDialog
import com.za.base.view.LoadingManager
import com.za.common.log.LogUtil
abstract class BaseActivity : AppCompatActivity() {
protected val TAG by lazy { javaClass.simpleName }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
DealerTheme {
ContentView()
if (LoadingManager.showLoading.value) {
LoadingManager.LoadingView()
}
if (showTipDialog.value != null) {
CommonDialog(message = showTipDialog.value
?: "",
title = "提示",
confirmText = "我已了解",
confirm = { BaseVm.hideTipDialog() },
cancel = { BaseVm.hideTipDialog() },
dismiss = { BaseVm.hideTipDialog() },
cancelEnable = true)
}
}
}
QbSdk.initX5Environment(this.application, object : QbSdk.PreInitCallback {
override fun onCoreInitFinished() {
// 内核初始化完成,可能为系统内核,也可能为系统内核
LogUtil.print("initX5Environment ", "finish")
}
/**
* 预初始化结束
* 由于X5内核体积较大需要依赖网络动态下发所以当内核不存在的时候默认会回调false此时将会使用系统内核代替
* @param isX5 是否使用X5内核
*/
override fun onViewInitFinished(isX5: Boolean) {
LogUtil.print("onViewInitFinished ", "isX5=$isX5")
}
})
}
@Composable
abstract fun ContentView()
}

View File

@ -0,0 +1,27 @@
package com.za.base
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
abstract class BaseVm<T, U> : ViewModel() {
val tag: String = javaClass.simpleName
abstract fun updateState(uiState: U)
abstract fun dispatch(action: T)
companion object {
val showTipDialog = mutableStateOf<String?>(null)//提示框
fun showTipDialog(msg: String) {
showTipDialog.value = msg
}
fun hideTipDialog() {
showTipDialog.value = null
}
}
}
sealed class LoadState {
data object Init : LoadState()
data object Success : LoadState()
data object Failed : LoadState()
}

View File

@ -0,0 +1,45 @@
package com.za.base
object Const {
const val Image_Max_length = 1024 * 400L
const val faceFileName = "zd_com.dear"
const val NetWorkException = 999
const val PhotoNormalPath =
"http://file-gk.sinoassist.com:38888/group1/M00/98/94/wKgDd2Uib0OAN0glAAAMOeWcU94810.png?date=2023-10-08"
const val DoubleClickTime = 3000
const val driverSighName : String = "driver_sign.jpg"
//通用的照片水印模板 code
const val NormalWaterMarker = "-1"
const val CHILD_COMPANY = 1 //子公司
const val SMALL_REPAIR = 1 // 小修
const val TUO_CHE = 2
object CarState {
const val FREE = 0 //空闲
const val BUSY = 1 //忙碌
const val ZHI_MANG = 2 //置忙
const val REPAIRING = 8 //维修中
}
object PhotoType {
const val InServicing = 1 //服务中照片
const val HistoryOrder = 2 //历史中照片
const val ChangeBattery = 3 //更换电瓶照片
const val NormalImage = 4 //普通的照片处理
}
object InServiceSettingType {
const val ON_SITE_PHOTO = 0 //现场照片
const val ORDER_REQUIREMENTS = 1 //案件要求
const val ORDER_DETAIL = 2 //案件详情
const val ORDER_GIVE_UP = 3 //订单放弃
}
}

View File

@ -0,0 +1,123 @@
package com.za.base
import com.za.bean.db.ele.EleWorkOrderBean
import com.za.bean.db.order.OrderInfo
import com.za.bean.db.order.PhotoTemplateInfo
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.ext.getTaskNode
import com.za.ext.toJson
import com.za.net.CommonMethod
import com.za.offline.OfflineManager
import com.za.offline.OfflineUpdateTaskBean
import com.za.room.RoomHelper
abstract class IServicingVm<T, U> : BaseVm<T, U>() {
fun getCurrentOrder() : OrderInfo? =
GlobalData.currentOrder ?: RoomHelper.db?.orderDao()?.getCurrentOrder()
fun updateOrder(orderInfo : OrderInfo?) {
orderInfo?.let { order ->
if (order.isCurrent == true) {
GlobalData.currentOrder = order
}
RoomHelper.db?.orderDao()?.update(order)
LogUtil.print("$tag updateOrder", order.toJson() ?: "")
} ?: LogUtil.print("$tag updateOrder", "order is null")
}
// 使用 Flow 重写获取照片模板的方法
//当前订单的照片模版
fun getCurrentPhotoTemplate(success : (List<PhotoTemplateInfo>?) -> Unit,
failure : (String?) -> Unit = {}) {
val photoTemplateList = RoomHelper.db?.photoTemplateDao()
?.getOrderPhotoTemplateFromTaskNode(getCurrentOrder()?.getTaskNode() ?: 0,
getCurrentOrder()?.userOrderId ?: 0)
if (photoTemplateList.isNullOrEmpty()) {
CommonMethod.fetchPhotoTemplate(GlobalData.currentOrder, success = {
val data = RoomHelper.db?.photoTemplateDao()?.getOrderPhotoTemplateFromTaskNode(
getCurrentOrder()?.getTaskNode() ?: 0,
getCurrentOrder()?.userOrderId ?: 0)
success(data)
}, failed = {
failure(it)
})
} else {
success(photoTemplateList)
}
}
// 使用 Flow 重写获取照片模板的方法
//获取出发前照片模版
fun getDeparturePhotoTemplate(success : (List<PhotoTemplateInfo>?) -> Unit,
failure : (String?) -> Unit = {}) {
val photoTemplateList = RoomHelper.db?.photoTemplateDao()
?.getOrderPhotoTemplateFromTaskNode(10100, getCurrentOrder()?.userOrderId ?: 0)
if (photoTemplateList.isNullOrEmpty()) {
CommonMethod.fetchPhotoTemplate(GlobalData.currentOrder, success = {
val data = RoomHelper.db?.photoTemplateDao()
?.getOrderPhotoTemplateFromTaskNode(10100, getCurrentOrder()?.userOrderId ?: 0)
success(data)
}, failed = {
failure(it)
})
} else {
success(photoTemplateList)
}
}
fun checkIsGoDeparturePhoto() : Boolean {
val list = RoomHelper.db?.photoTemplateDao()
?.getOrderPhotoTemplateFromTaskNode(10100, getCurrentOrder()?.userOrderId ?: 0)
return ! list.isNullOrEmpty()
}
fun updateCurrentEleWorkOrder(eleWorkOrderBean : EleWorkOrderBean) {
RoomHelper.db?.eleWorkOrderDao()?.update(eleWorkOrderBean)
getCurrentOrder()?.let { order ->
val updatedOrder = order.copy(electronOrderState = eleWorkOrderBean.orderWorkStatus,
taskSuccessStatus = if (eleWorkOrderBean.isSuccess == 1) 0 else 1)
updateOrder(updatedOrder)
}
LogUtil.print("$tag updateCurrentEleWorkOrder", eleWorkOrderBean.toJson() ?: "")
}
fun getCurrentOrderOfflineTask() : List<OfflineUpdateTaskBean>? {
return getCurrentOrder()?.taskId?.let { taskId ->
RoomHelper.db?.offlineTaskDao()?.getOfflineTaskFromTaskId(taskId)
}
}
fun clearCurrentOrderOfflineTask() {
getCurrentOrder()?.taskId?.let { taskId ->
RoomHelper.db?.offlineTaskDao()?.deleteOfflineTaskFromTaskId(taskId)
LogUtil.print("$tag clearCurrentOrderOfflineTask", "taskId==$taskId")
}
}
fun getCurrentOrderEleWorkOrder() : EleWorkOrderBean? {
return RoomHelper.db?.eleWorkOrderDao()?.getEleWorkOrder(getCurrentOrder()?.taskId ?: 0)
}
fun insertOfflineTask(offlineUpdateTaskBean : OfflineUpdateTaskBean) {
RoomHelper.db?.offlineTaskDao()?.let { offlineDao ->
val primaryId = offlineUpdateTaskBean.primaryId ?: 0
val existingTask = offlineDao.getOfflineTaskFromPrimaryId(primaryId)
if (existingTask != null) {
offlineDao.update(offlineUpdateTaskBean)
} else {
offlineDao.insertOfflineTask(offlineUpdateTaskBean)
}
offlineUpdateTaskBean.taskId?.let { taskId ->
OfflineManager.start(taskId)
}
LogUtil.print("$tag insertOfflineTask", offlineUpdateTaskBean.toJson() ?: "")
}
}
}

View File

@ -0,0 +1,37 @@
package com.za.base.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
val bgColor = Color(0xFFF4F5F7)
val MainBottomSelectColor = Color(0xFF3D4B7C)
val MainBottomUnSelectColor = Color(0xFFA9AEBD)
//标题颜色
val headBgColor = Color(0xFF3D4B7C)
//标题颜色
val headTitleColor = Color(0xB3FFFFFF)
//按钮背景颜色
val buttonBgColor = Color(0xFF3D4B7C)
val white5 = Color(0x0DFFFFFF)
val white95 = Color(0xF2FFFFFF)
val white80 = Color(0xCCFFFFFF)
val black5 = Color(0x0D000000)
val black10 = Color(0x1A000000)
val black20 = Color(0x33000000)
val black30 = Color(0x4D000000)
val black50 = Color(0x80000000)
val black65 = Color(0xA6000000)
val black90 = Color(0xE6000000)

View File

@ -0,0 +1,7 @@
package com.za.base.theme
import androidx.compose.ui.unit.dp
import com.blankj.utilcode.util.BarUtils
import com.blankj.utilcode.util.ConvertUtils
val headPadding = ConvertUtils.px2dp(BarUtils.getStatusBarHeight().toFloat()/2).dp

View File

@ -0,0 +1,80 @@
package com.za.base.theme
import android.app.Activity
import android.view.ViewGroup
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.ripple.RippleAlpha
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalRippleConfiguration
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.RippleConfiguration
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalView
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40,
background = bgColor,
onBackground = bgColor,
)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DealerTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = when {
darkTheme -> LightColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = Color.Transparent.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
WindowCompat.setDecorFitsSystemWindows(window, false)
ViewCompat.setOnApplyWindowInsetsListener(window.decorView.findViewById(android.R.id.content)) { view, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view as ViewGroup
view.updateLayoutParams<ViewGroup.MarginLayoutParams> {
leftMargin = insets.left
bottomMargin = insets.bottom
rightMargin = insets.right
topMargin = 0
}
WindowInsetsCompat.CONSUMED
}
}
}
MaterialTheme(colorScheme = colorScheme) {
CompositionLocalProvider(LocalRippleConfiguration provides RippleConfiguration(rippleAlpha = RippleAlpha(0f, 0f, 0f, 0f))) {
ProvideTextStyle(value = MaterialTheme.typography.bodyLarge, content = content)
}
}
}
//object NoRippleTheme : RippleTheme {
//
// @Composable
// override fun defaultColor(): Color {
// return Color.Unspecified
// }
//
// @Composable
// override fun rippleAlpha(): RippleAlpha {
// return RippleAlpha(0f, 0f, 0f, 0f)
// }
//}

View File

@ -0,0 +1,33 @@
package com.za.base.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View File

@ -0,0 +1,33 @@
package com.za.base.view
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.za.base.theme.buttonBgColor
import com.za.base.theme.headBgColor
import com.za.ext.noDoubleClick
@Composable
fun CommonButton(text: String, onClick: () -> Unit) {
Box(modifier = Modifier
.fillMaxWidth()
.noDoubleClick { onClick() }
.padding(horizontal = 60.dp, vertical = 10.dp)
.background(color = buttonBgColor, shape = RoundedCornerShape(4.dp))
.padding(vertical = 12.dp), contentAlignment = Alignment.Center) {
Text(text = text, color = Color.White,
fontSize = 15.sp,
fontWeight = FontWeight.Medium)
}
}

View File

@ -0,0 +1,220 @@
package com.za.base.view
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.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.shape.RoundedCornerShape
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.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import coil.compose.AsyncImage
import com.za.base.theme.black90
import com.za.common.util.MapUtil
import com.za.servicing.R
@Composable
fun CommonDialog(
title: String? = null,
confirmText: String = "确定",
confirm: () -> Unit,
content: @Composable () -> Unit = {},
message: String? = null,
cancelText: String? = "取消",
cancelEnable: Boolean = true,
cancel: () -> Unit = {},
dismiss: () -> Unit
) {
Dialog(onDismissRequest = { dismiss() },
properties = DialogProperties(
dismissOnBackPress = cancelEnable,
dismissOnClickOutside = cancelEnable)) {
Box(modifier = Modifier
.background(color = Color.White, shape = RoundedCornerShape(13.dp))) {
Spacer(modifier = Modifier
.fillMaxWidth()
.height(105.dp)
.background(brush = Brush.verticalGradient(colors = arrayListOf(Color(0xFFDAE8FF),
Color(0x00DAE8FF))),
shape = RoundedCornerShape(topStart = 13.dp, topEnd = 13.dp)))
Column(modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 30.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center) {
Spacer(modifier = Modifier.height(35.dp))
Text(text = "$title", fontSize = 16.sp, fontWeight = FontWeight.Medium, color = Color(0xFF2A4054))
Spacer(modifier = Modifier.height(16.dp))
if (message == null) {
content()
} else {
Text(text = message, fontSize = 14.sp, color = Color(0xFF536475))
}
Spacer(modifier = Modifier.height(24.dp))
Box(modifier = Modifier
.width(202.dp)
.clickable { confirm() }
.background(color = Color(0xFF3A58B1), shape = RoundedCornerShape(23.dp))
.padding(vertical = 12.dp), contentAlignment = Alignment.Center) {
Text(text = confirmText, color = Color.White, fontSize = 15.sp, fontWeight = FontWeight.Medium)
}
Spacer(modifier = Modifier.height(20.dp))
if (!cancelText.isNullOrBlank() && cancelEnable) {
Box(modifier = Modifier
.fillMaxWidth()
.clickable { cancel() }, contentAlignment = Alignment.Center) {
Text(text = cancelText, fontSize = 15.sp, fontWeight = FontWeight.Medium, color = black90)
}
}
Spacer(modifier = Modifier.height(10.dp))
}
}
}
}
@Composable
fun ReTakePhotoDialog(
title: String? = null,
confirm: () -> Unit,
againSubmit: () -> Unit,
path: String? = null,
showAgain: Boolean = false,
cancel: () -> Unit = {},
dismiss: () -> Unit
) {
Dialog(onDismissRequest = { dismiss() },
properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = true)) {
Box(modifier = Modifier
.background(color = Color.White, shape = RoundedCornerShape(13.dp))) {
Spacer(modifier = Modifier
.fillMaxWidth()
.height(105.dp)
.background(brush = Brush.verticalGradient(colors = arrayListOf(Color(0xFFDAE8FF),
Color(0x00DAE8FF))),
shape = RoundedCornerShape(topStart = 13.dp, topEnd = 13.dp)))
Column(modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 30.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center) {
Spacer(modifier = Modifier.height(35.dp))
Text(text = "$title", fontSize = 16.sp, fontWeight = FontWeight.Medium, color = Color(0xFF2A4054))
Spacer(modifier = Modifier.height(16.dp))
Box(modifier = Modifier
.fillMaxWidth()
.height(300.dp), contentAlignment = Alignment.Center) {
AsyncImage(model = path,
contentDescription = "",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.FillWidth)
}
Spacer(modifier = Modifier.height(24.dp))
Box(modifier = Modifier
.width(202.dp)
.clickable { confirm() }
.background(color = Color(0xFF3A58B1), shape = RoundedCornerShape(23.dp))
.padding(vertical = 12.dp), contentAlignment = Alignment.Center) {
Text(text = "重拍", color = Color.White, fontSize = 15.sp, fontWeight = FontWeight.Medium)
}
if (showAgain) {
Spacer(modifier = Modifier.height(20.dp))
Box(modifier = Modifier
.width(202.dp)
.clickable { againSubmit() }
.background(color = Color.Red, shape = RoundedCornerShape(23.dp))
.padding(vertical = 12.dp), contentAlignment = Alignment.Center) {
Text(text = "再次上传", color = Color.White, fontSize = 15.sp, fontWeight = FontWeight.Medium)
}
}
Spacer(modifier = Modifier.height(20.dp))
Box(modifier = Modifier
.fillMaxWidth()
.clickable { cancel() }, contentAlignment = Alignment.Center) {
Text(text = "取消", fontSize = 15.sp, fontWeight = FontWeight.Medium, color = black90)
}
Spacer(modifier = Modifier.height(10.dp))
}
}
}
}
@Composable
fun ChoiceMapDialog(dismiss: () -> Unit,
lat: Double?,
lng: Double?,
address: String?) {
val context = LocalContext.current
Dialog(onDismissRequest = { dismiss() }) {
Row(modifier = Modifier
.fillMaxWidth()
.height(180.dp)
.background(color = Color.White, shape = RoundedCornerShape(8.dp))
.padding(10.dp), verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceAround) {
if (MapUtil.isGdMapInstalled(context)) {
Column(modifier = Modifier.clickable {
MapUtil.startNavigationGd(context, lat = lat, lng = lng, address = address)
dismiss()
}) {
AsyncImage(model = R.drawable.sv_amap_icon, contentDescription = "", modifier = Modifier.size(60.dp))
Spacer(modifier = Modifier.height(5.dp))
Text(text = "高德地图", color = Color.Black, fontWeight = FontWeight.Medium, fontSize = 14.sp)
}
}
if (MapUtil.isBaiduMapInstalled(context)) {
Column(modifier = Modifier.clickable {
MapUtil.startNavigationBd(context, lat = lat, lng = lng, address = address)
dismiss()
}) {
AsyncImage(model = R.drawable.sv_baidu_icon, contentDescription = "", modifier = Modifier.size(60.dp))
Spacer(modifier = Modifier.height(5.dp))
Text(text = "百度地图", color = Color.Black, fontWeight = FontWeight.Medium, fontSize = 14.sp)
}
}
if (MapUtil.isTencentInstalled(context)) {
Column(modifier = Modifier.clickable {
MapUtil.startNavigationTencent(context, lat = lat, lng = lng, address = address)
dismiss()
}) {
AsyncImage(model = R.drawable.sv_tencent_icon, contentDescription = "", modifier = Modifier.size(60.dp))
Spacer(modifier = Modifier.height(5.dp))
Text(text = "腾讯地图", color = Color.Black, fontWeight = FontWeight.Medium, fontSize = 14.sp)
}
}
}
}
}

View File

@ -0,0 +1,90 @@
package com.za.base.view
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
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.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import com.za.servicing.R
@Composable
fun EmptyView(
modifier: Modifier = Modifier,
message: String = "暂无数据",
imageSize: Pair<Int, Int> = Pair(118, 143),
imageRes: Int = R.drawable.sv_emty_data
) {
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
AsyncImage(
model = imageRes,
contentDescription = message,
modifier = Modifier.size(imageSize.first.dp, imageSize.second.dp)
)
Spacer(modifier = Modifier.height(12.dp))
Text(
text = message,
color = Color.Gray,
fontSize = 14.sp,
textAlign = TextAlign.Center
)
}
}
@Composable
fun LoadError(
modifier: Modifier = Modifier,
message: String = "加载出错",
imageSize: Pair<Int, Int> = Pair(118, 143),
onRetry: (() -> Unit)? = null
) {
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
AsyncImage(
model = R.drawable.sv_load_error,
contentDescription = message,
modifier = Modifier.size(imageSize.first.dp, imageSize.second.dp)
)
Spacer(modifier = Modifier.height(12.dp))
Text(
text = message,
color = Color.Gray,
fontSize = 14.sp,
textAlign = TextAlign.Center
)
if (onRetry != null) {
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "点击重试",
color = Color(0xFF1BA8F7),
fontSize = 14.sp,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.clickable { onRetry() }
)
}
}
}

View File

@ -0,0 +1,54 @@
package com.za.base.view
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import com.za.base.theme.headBgColor
import com.za.base.theme.headPadding
import com.za.servicing.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HeadView(title: String, onBack: () -> Unit = {}, isCanBack: Boolean = true, action: @Composable () -> Unit = {}) {
CenterAlignedTopAppBar(modifier = Modifier
.fillMaxWidth()
.background(color = headBgColor)
.systemBarsPadding()
.padding(top = 20.dp),
colors = TopAppBarDefaults.centerAlignedTopAppBarColors().copy(containerColor = headBgColor, titleContentColor = Color.White),
title = { Text(text = title, fontSize = 15.sp, fontWeight = FontWeight.Medium) },
navigationIcon = {
if (isCanBack) {
AsyncImage(model = R.drawable.sv_back, contentDescription = "", modifier = Modifier
.size(40.dp)
.clickable { onBack() }
.padding(10.dp))
}
}, actions = { action() })
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HeadViewNotBack(title: String) {
CenterAlignedTopAppBar(modifier = Modifier
.fillMaxWidth()
.background(color = headBgColor)
.padding(top = headPadding),
colors = TopAppBarDefaults.centerAlignedTopAppBarColors().copy(containerColor = headBgColor, titleContentColor = Color.White),
title = { Text(text = title, fontSize = 15.sp, fontWeight = FontWeight.Medium) },
navigationIcon = {})
}

View File

@ -0,0 +1,46 @@
package com.za.base.view
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import coil.decode.GifDecoder
import coil.request.ImageRequest
import com.za.servicing.R
object LoadingManager {
val showLoading = mutableStateOf(false)
fun showLoading() {
showLoading.value = true
}
fun hideLoading() {
showLoading.value = false
}
@Composable
fun LoadingView() {
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
AsyncImage(model = ImageRequest.Builder(LocalContext.current)
.data(R.drawable.gif_loading)
.decoderFactory(GifDecoder.Factory())
.build(), contentDescription = "加载中", modifier = Modifier
.size(70.dp)
.align(Alignment.Center))
}
}
}
open class LoadingState {
data object Loading : LoadingState()
data object LoadingFailed : LoadingState()
data object LoadingSuccess : LoadingState()
}

View File

@ -0,0 +1,24 @@
package com.za.bean
/**
* Created by DoggieX on 2017/7/26.
*/
class BaseResponse<T> {
var code: Int = 0
var msg: String? = null
var message: String? = null
var result: T? = null
val isOk: Boolean
get() = code == 0 || code == 200
override fun toString(): String {
return "BaseResponse{" +
"code=" + code +
", msg='" + msg + '\'' +
", message='" + message + '\'' +
", result=" + result +
'}'
}
}

View File

@ -0,0 +1,216 @@
package com.za.bean
import android.os.Parcel
import android.os.Parcelable
data class DriverInfo(
var userPortrait : String? = "", //用户头像
var token : String? = null, //
var rongyunToken : String? = null,
var logTime : String? = null,
var userId : Int? = null, //用户id
var userName : String? = null, //用户姓名
var userPhone : String? = null, //用户手机号
var callphone : String? = null,
var supplierId : String? = null, //经销商id
var supplierName : String? = null, //经销商名称
var supplierType : Int? = null, //经销商类型 1子公司 2供应商
var loginLogId : Int? = null,
var serviceList : List<String>? = null, //车辆服务列表
var assistUserCode : String? = null,
val authStatus : Int? = null, // 0 未认证 1 已认证
) : Parcelable {
constructor(parcel : Parcel) : this(parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.createStringArrayList(),
parcel.readString(),
parcel.readValue(Int::class.java.classLoader) as? Int) {
}
override fun writeToParcel(parcel : Parcel, flags : Int) {
parcel.writeString(userPortrait)
parcel.writeString(token)
parcel.writeString(rongyunToken)
parcel.writeString(logTime)
parcel.writeValue(userId)
parcel.writeString(userName)
parcel.writeString(userPhone)
parcel.writeString(callphone)
parcel.writeString(supplierId)
parcel.writeString(supplierName)
parcel.writeValue(supplierType)
parcel.writeValue(loginLogId)
parcel.writeStringList(serviceList)
parcel.writeString(assistUserCode)
parcel.writeValue(authStatus)
}
override fun describeContents() : Int {
return 0
}
companion object CREATOR : Parcelable.Creator<DriverInfo> {
override fun createFromParcel(parcel : Parcel) : DriverInfo {
return DriverInfo(parcel)
}
override fun newArray(size : Int) : Array<DriverInfo?> {
return arrayOfNulls(size)
}
}
}
data class GeneralInfo(
var userId : Int? = null, //用户id
val userName : String? = null, //用户名
val userPhone : String? = null,
val userPortrait : String? = null, //大头照
val vehicleId : Int? = null,
val vehicleName : String? = null,
val vehicleState : Int? = null, //车辆状态 0 空闲 1 忙碌
val supplierId : Int? = null,
val supplierName : String? = null,
val supplierType : Int?,
val plateNumber : String? = null, //车牌号
val serviceList : List<String>? = null,
val deviceId : String? = null,
val authStatus : Int? = null, // 0 未认证 1 已认证
)
data class DriverIdentityAuthWebRequest(val vehicleId : Int? = null,
val driverId : Int? = null,
val redirectUrl : String = "https://www.sinoassist.com?esignAppScheme=zd_sinoassist://demo/realBack")
data class DriverIdentityAuthWebBean(val flowId : String? = null,
val shortLink : String? = null,
val url : String? = null)
//public Integer update;//是否强制更新(1:强制更新0非强制更新)
//public String path;//更新路径
//public String newAppVersion;//版本号
//public String description;//版本更新内容
data class UpdateVersionBean(val update : Int? = null,
val path : String? = null,
val newAppVersion : String? = null,
val description : String? = null)
//public String appVersion;//版本号
//public Integer appType = 1;//app类型
data class UpdateVersionRequest(val appVersion : String? = null, val appType : Int? = 1)
// private String jobCode;
// private String phone;
// private String taskCode;
// private String rescueVehicle;
// private String deviceId;
// private String vehicleId;
data class LoginWithTaskRequest(val jobCode : String? = null,
val phone : String? = null,
val taskCode : String? = null,
val rescueVehicle : String? = null,
val deviceId : String? = null,
val vehicleId : String? = null)
//{
// "code": 0,
// "msg": "请求成功",
// "result": {
// "supplierName": "上海安畅",
// "vehicleName": "小小宋车辆(1",
// "supplierId": 1128,
// "userPhone": "17630035658",
// "userName": "宋志领",
// "userId": 4967,
// "token": "c969499d5ac9cbb9da2691ee7533f065",
// "loginLogId": 10267,
// "userPortrait": "http://file.sino-assist.com/group1/M00/04/E1/wKgBzGfZHjCAW8_MAAQsluXYmmc152.jpg?date=2025-03-18",
// "serviceList": [
// "故障--平板拖车",
// "换胎",
// "搭电",
// "抢修",
// "送水",
// "缺汽油",
// "缺机油",
// "事故--平板拖车",
// "困境救援",
// "困境-吊车",
// "困境救援-事故",
// "困境救援-故障",
// "电话技术支持",
// "电话技术解决",
// "派送备用钥匙",
// "待命点间调拨",
// "商品车运输",
// "故障--大型拖车",
// "故障--小型拖车",
// "事故--大型拖车",
// "事故--小型拖车",
// "车辆检测",
// "送防冻液",
// "其他",
// "故障拖车免拖100公里",
// "代验车--取车",
// "代验车--送车",
// "代验车-取件",
// "代验车--送件",
// "应急道路救援",
// "3吨拖车",
// "8吨拖车",
// "25吨拖车",
// "50吨拖车",
// "大力神拖车",
// "代提车-拖车",
// "现场小修--解档",
// "地库救援(不含拖车)",
// "故障拖车免拖50公里",
// "充电拖车-双程",
// "代客充电",
// "充气",
// "移动充电",
// "代验车--现场代办",
// "代验车--快递代办",
// "代验车--取车代办",
// "长途拖车",
// "大板运输",
// "商品车调拨",
// "失车定位",
// "医疗急救",
// "高速救援",
// "代客洗车",
// "代提车-代驾",
// "回程车运输",
// "上门交付",
// "充电拖车-单程"
// ],
// "vehicleId": 327732,
// "supplierType": 1,
// "jobNumber": "宋志领"
// }
//}
data class LoginWithTaskBean(val supplierName : String? = null,
val vehicleName : String? = null,
val supplierId : Int? = null,
val userPhone : String? = null,
val userName : String? = null,
val userId : Int? = null,
val token : String? = null,
val loginLogId : Int? = null,
val userPortrait : String? = null,
val serviceList : List<String>? = null,
val jobNumber : String? = null,
val vehicleId : Int? = null,
val supplierType : Int? = null)

View File

@ -0,0 +1,12 @@
package com.za.bean
data class ImageBean(
var data: String? = null,
var msg: String? = null,
var success: Boolean? = null,
var code: Int? = null) {
fun isOk(): Boolean {
return code == 200
}
}

View File

@ -0,0 +1,329 @@
package com.za.bean
import com.blankj.utilcode.util.EncodeUtils
import com.blankj.utilcode.util.TimeUtils
import com.za.base.AppConfig
import java.io.Serializable
import java.nio.charset.StandardCharsets
data class JpushBean(
val pushType: Int? = null, //0 新任务 1 任务取消 2 任务变更
val taskId: Int? = null,//订单号 "taskId":5313005
val taskCode: String? = null, //订单编码 "taskCode":"ZD20190308009965"
val customerName: String? = null, //客户姓名 "customerName":"越继安"
val customerPhone: String? = null, //客户电话 "customerPhone":"18078815268"
val carBrand: String? = null, //车辆品牌 "carBrand":""
val carModel: String? = null,//车辆型号 "carModel":"秦"
val contract: String? = null, //车辆型号 "carModel":"秦"
val typeDesc: String? = null, //推送的附加消息 revoke 撤回 giveUp放弃 reDispatch改派
val carNo: String? = null, //客户车车牌号 "carNo":"粤AF53918"
val taskState: String? = null, //订单状态 "taskState":"GOTO"
val address: String? = null, //任务地址 "address":"广东省广州市白云区107国道石井凰岗路342号(白云黄石、同德围地区近庆丰兴隆公园)美景大酒店"
val addressProperty: String? = null,//任务地址类型 "addressProperty":"地面"
val hotline: String? = null, //任务地址类型 "addressProperty":"地面"
val schedulingFinalRule: Int? = null, //案件类型 0 传统案件 1 聚合派工
val addressRemark: String? = null,
val distAddress: String? = null, //目的地地址 "distAddress":"广东省广州市白云区雅岗南大道"
val distAddressRemark: String? = null,
val expectArriveTime: String? = null, //预计到达时间 "expectArriveTime":"2019-03-08 05:11:07"
val serviceTypeName: String? = null,//服务类型 "serviceTypeName":"故障--平板拖车"
val dispatchTime: String? = null, //派单时间 "dispatchTime":"2019-03-08 04:26:07"
val lat: Double? = null,
val lng: Double? = null,
val distLat: Double? = null,
val distLng: Double? = null,
val importantTip: String? = null,
val tipContent: String? = null,
val hasReplaceBatteryCapable: Int? = null ,//是否有更换电瓶的能力 1 搭电可以更换电瓶 2搭电不可以更换电瓶 其他的不展示
var voiceType : Int?=null //语音提示类型 1小修单 2拖车单 3困境单
) : Serializable {
fun isNeedCallCustomPhone(): Boolean {
return "210" != customerPhone && "230" != customerPhone
}
}
data class NewOrderRequestBean(val vehicleId: Int? = null)
//历史订单状态
data class HistoryTaskBean(
val taskId: Int? = null, //订单号
val taskCode: String? = null, //订单编码
val userOrderId: Int? = null,
val userOrderCode: String? = null, //只有电子工单历史补传才需要传
val supplierAudit: Int? = null, //审核状态 0待补充 1 待审核 2审核不通过 3 审核通过
val supplierAuditStr: String? = null, //审核状态描述
val missingContent: String? = null, //缺失内容
val auditFailReason: String? = null, //审核失败原因
val customerName: String? = null, //客户姓名
val customerPhone: String? = null, //客户电话
val carBrand: String? = null, //车辆品牌
val carModel: String? = null, //车辆型号
val carNo: String? = null, //客户车车牌号 "
val carVin: String? = null, //客户车Vin码
val taskState: String? = null, //订单状态
val address: String? = null, //任务地址
val addressRemark: String? = null, //事发地地址补充
val addressProperty: String? = null, //任务地址类型
val lat: Double? = null,
val lng: Double? = null,
val distAddress: String? = null, //目的地地址
val distAddressRemark: String? = null, //目的地地址补充
val distLat: Double? = null,
val distLng: Double? = null,
val expectArriveTime: String? = null, //预计到达时间
val serviceTypeName: String? = null, //服务类型
val orderSource: String? = null, //
val flowType: Int? = null, //流程类型 "flowType":2
val settleType: Int? = null, //结算类型 1 月结 2 现金
val supplierId: Int? = null,
val successTime: String? = null, //完成时间 "dispatchTime":"2019-03-08 04:26:07"
val verifyType: Int? = null, //验证类型 "verifyType":1
val verifyValue: String? = null, //验证内容
val holdon: Boolean? = null, //是否被挂起 "holdon":false
val externalCode: String? = null, //流水号
val vehicleName: String? = null,
val traceIdAB: Int? = null,
val mileageAB: Int? = null,
val traceABUrl: String? = null,
val traceIdBC: Int? = null,
val mileageBC: Int? = null,
val mileageCA: Int? = null,
val traceBCUrl: String? = null,
val createTime: String? = null,
val acceptTime: String? = null,
val arriveTime: String? = null,
val arriveDestTime: String? = null,
val giveupTime: String? = null,
val giveupAddress: String? = null,
val giveupLat: Double? = null,
val giveupLng: Double? = null,
val needWaterMarker: Boolean? = null,
val needShowPhoneBrand: Boolean? = null,
val policyNo: String? = null, // 平安拖车责任险 保单号
val electronOrderState: String? = null, //电子工单状态
val hasReplaceBatteryCapable: Int? = null, //是否具有更换电瓶的能力
val hasReplaceBattery: Int = 0 //1已更换过 2有更换能力但没更换过 0无
) : Serializable {
fun getEleOrderH5Url(): String? {
if (electronOrderState == null || electronOrderState != "3") {
return null
}
val paramQuery = (("userOrderId=$userOrderId").toString() + "&userOrderCode=" + taskCode) + "&supplierId=" + userOrderId
return AppConfig.Resource_URL + "/electronicWorkOrder/index.html?" + EncodeUtils.base64Encode2String(paramQuery.toByteArray(StandardCharsets.UTF_8))
}
fun getSettleTypeStr(): String {
return when (settleType) {
1 -> {
"月结"
}
2 -> {
"现金"
}
else -> {
""
}
}
}
}
data class HistoryPhotoTemplates(
/**
* 任务节点
*/
val taskStatus: Int? = null,
/**
* 图片节点名称
*/
val taskStatusString: String? = null,
/**
* 该节点下的所有历史照片信息
*/
val photoList: List<HistoryPhotoTemplateItem>? = null
)
data class HistoryPhotoTemplateItem(
val photoUrl: String? = null,
val tag: String? = null,
val imageTitle: String? = null,
val lon: String? = null,
val lat: String? = null,
val templatePhotoType: Int = 0, //1 照片 2 工单照片
val takePhotoTime: String? = null,
val takeAddress: String? = null,
val uploadStatus: String? = null,
val uploadState: Int? = 0, //0 上传中 1 上传成功 2 上传失败
) {
//获取拍照时间
fun getPhotoTakeTime(taskState: String, historyTaskBean: HistoryTaskBean?): String? {
if (!takePhotoTime.isNullOrBlank()) {
return takePhotoTime
}
return when (taskState) {
"13001", "15001" -> historyTaskBean?.arriveTime
"17001" -> {
if (2 == historyTaskBean?.flowType) {
historyTaskBean.arriveDestTime
} else {
historyTaskBean?.arriveTime
}
}
"18001" -> {
historyTaskBean?.successTime
}
"18100" -> historyTaskBean?.giveupTime
else -> TimeUtils.getNowString()
}
}
fun getPhotoLat(taskState: String, historyTaskBean: HistoryTaskBean?): Double? {
if (!lat.isNullOrBlank()) {
return lat.toDouble()
}
return when (taskState) {
"13001", "15001" -> historyTaskBean?.lat
"17001", "18001" -> {
if (2 == historyTaskBean?.flowType) {
historyTaskBean.distLat
} else {
historyTaskBean?.lat
}
}
"18100" -> historyTaskBean?.giveupLat
else -> null
}
}
fun getPhotoLng(taskState: String, historyTaskBean: HistoryTaskBean?): Double? {
if (!lon.isNullOrBlank()) {
return lon.toDouble()
}
return when (taskState) {
"13001", "15001" -> historyTaskBean?.lng
"17001", "18001" -> {
if (2 == historyTaskBean?.flowType) {
historyTaskBean.distLng
} else {
historyTaskBean?.lng
}
}
"18100" -> historyTaskBean?.giveupLng
else -> null
}
}
fun getPhotoAddress(taskState: String, historyTaskBean: HistoryTaskBean?): String? {
if (!takeAddress?.replace("[", "")?.replace("]", "").isNullOrBlank()) {
return takeAddress
}
return when (taskState) {
"13001", "15001" -> historyTaskBean?.address
"17001", "18001" -> {
if (2 == historyTaskBean?.flowType) {
historyTaskBean.distAddress
} else {
historyTaskBean?.address
}
}
"18100" -> historyTaskBean?.giveupAddress
else -> ""
}
}
}
data class TaskSettlementAndTraceBean(
val settleMap: SettleMapBean? = null,
val traceIdAB: Int? = null,
val mileageAB: Int? = null,
val traceABUrl: String? = null,
val traceIdBC: Int? = null,
val mileageBC: Int? = null,
val mileageCA: Int? = null,
val traceBCUrl: String? = null
)
data class SettleMapBean(
/*————————————子公司——————————*/
val startPrice: Int? = null, //起步价
val unitPrice: Double? = null, //每公里单价
val mileage: Int? = null, //公里数
val basePrice: Double? = null,//基本费用
val roadFee: Int? = null,//路桥费
val assistFee: Int? = null, //辅助总费用
/*———————————————供应商———————————————*/ // public Integer startMileage;//出发段公里数
// public Integer carryMileage;//背车段公里数
// public Integer backMileage;//回程段公里数
val startRoadFee: Int? = null, //出发段过境费
val carryRoadFee: Int? = null, //背车段过境费
val backRoadFee: Int? = null, //回程段过境费
val waitFee: Int? = null,//等候费
val wheelFee: Int? = null,//辅助轮费
val wheelNum: Int? = null, //辅助轮个数
val wheelPrice: Int? = null, //辅助轮单价
val dilemmaFee: Int? = null,//困境费
val basementFee: Int? = null, //其他费用
val totalFee: Double? = null,//费用总计
)
/**
* Created by zhangj on 2019/12/4.
*/
class SettleInfoRequest(
val orderId: Int? = null,
val supplierType: Int? = null,
val startMileage: Int? = null,
val carryMileage: Int? = null,
val backMileage: Int? = null,
val startRoadFee: Int? = null,
val carryRoadFee: Int? = null,
val backRoadFee: Int? = null,
val waitFee: Int? = null,
val settleType: Int? = null,
val wheelFee: Int? = null, //辅助轮费
val wheelNum: Int? = null,
val wheelPrice: Int? = null,
val dilemmaFee: Int? = null, //困境费
val basementFee: Int? = null, //其他费用
val totalFee: Double? = null, //
val startPrice: Int? = null,
val unitPrice: Double? = null,
val mileage: Int? = null,
val basePrice: Double? = null,
val assistFee: Int? = null,
val imgInfo: String? = null,
val imgPath: String? = null,
)
data class AMapTraceBean(
val counts: Int? = null,
val distance: Double? = null,
val time: Long? = null,
val trid: Int? = null,
val points: List<PointsBean>? = null)
data class PointsBean(
val accuracy: Double? = null,
val direction: Double? = null,
val height: Double? = null,
val locatetime: Long? = null,
val location: String? = null,
val speed: Double? = null)

View File

@ -0,0 +1,21 @@
package com.za.bean
data class PaymentInfoBean(
var userOrderId: Int? = null,
val taskOrderId: Int? = null,
val adjustAmount: Int? = null, //调整金额
val startPrice: Int? = null, //起步价
val limitedMileage: Int? = null, //免托公里数
val amount: Float? = null, //收款金额
val unitPrice: Float? = null, //单价 //如果单价为 null 并且payItem 为2 则为超限无单价项目
val mileage: Float? = null, //公里数
val settlementRule: String? = null,//结算规则
val userPhone: String? = null,//客户手机号
val tradeState: Int? = null, //交易状态 交易状态 1 待交易 2交易完成
val isPayment: Boolean? = null, //是否需要进行客户收款
val orderPayDetailId: Int? = null, //订单支付明细ID
val payItem: Int? = null, //支付项目 1.现金项目 2超限项目
val contractSettleRule: Int? = null, //合同结算规则 1系统结算 2一口价结算 其中 一口价的不允许修改金额
val calculateAmount: Float? = null, //计算金额
val askPayment: Boolean? = null, //是否询问是否收款
)

View File

@ -0,0 +1,33 @@
package com.za.bean
data class UploadChangeBatteryRequest(
var userOrderId: Int? = null,
val hasReplaceBattery: Int? = null, //是否更换电瓶 1 更换
val replaceBatteryImgList: List<String>? = null,
val isPayment: Boolean? = null //是否需要付款
)
data class FetchChangeBatteryPhotoRequest(
val userOrderId: Int? = null,
val taskOrderId: Int? = null//是否更换电瓶
)
data class ChangeBatteryResponse(
val userOrderId: Int? = null,
val taskOrderId: Int? = null,
val imageType: Int? = null,//1 开案照片2 医疗救援附件3 开案凭证照片,4议价凭证照片,5电子工单,6换电瓶照片
val imageUrl: String? = null,
val tag: String? = null
)
data class BatteryCostQueryBean(
var isPayment: Boolean? = null,
val userOrderId: Int? = null,
val taskOrderId: Int? = null,
val amount: Float? = null,
val userPhone: String? = null,
val tradeState: Int? = null//交易状态 1 待交易 2 交易完成 6 交易关闭
)
data class BatteryCostQueryRequest(var userOrderId: Int? = null,
val taskOrderId: Int? = null)

View File

@ -0,0 +1,56 @@
package com.za.bean
import android.os.Parcel
import android.os.Parcelable
data class VehicleInfo(
var vehicleId: Int? = null, //车辆id
var vehicleName: String? = null,//车辆名称(车牌号)
var userName: String? = null, //当前用车司机姓名(工号)
var userPhone: String? = null, //当前用车司机电话
var status: Int? = null, //车辆状态 0 不在线 1 在线
val plateNumber: String? = null, //车牌号
var vehicleState: Int? = null, //车辆是否空闲 0 空闲 1 忙碌 2置忙 8维修
var terminalId: Long? = null, //车辆高德tid
var exitMileage: Int? = null, //上次退出公里数
var supplierType: Int? = null, //经销商类型
) : Parcelable {
constructor(parcel : Parcel) : this(parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readString(),
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readValue(Long::class.java.classLoader) as? Long,
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readValue(Int::class.java.classLoader) as? Int) {
}
override fun writeToParcel(parcel : Parcel, flags : Int) {
parcel.writeValue(vehicleId)
parcel.writeString(vehicleName)
parcel.writeString(userName)
parcel.writeString(userPhone)
parcel.writeValue(status)
parcel.writeString(plateNumber)
parcel.writeValue(vehicleState)
parcel.writeValue(terminalId)
parcel.writeValue(exitMileage)
parcel.writeValue(supplierType)
}
override fun describeContents() : Int {
return 0
}
companion object CREATOR : Parcelable.Creator<VehicleInfo> {
override fun createFromParcel(parcel : Parcel) : VehicleInfo {
return VehicleInfo(parcel)
}
override fun newArray(size : Int) : Array<VehicleInfo?> {
return arrayOfNulls(size)
}
}
}

View File

@ -0,0 +1,28 @@
package com.za.bean
//public String reportType;//报备类型名称
// public String reportTemplate;//报备内容模板
data class ReportItem(val reportType: String? = null,
val reportTemplate: String? = null)
//提交报备内容
// public String taskId;//关联的任务Id
// public String reportType;//报备类型名称
// public String reportTemplate;//报备内容模板
data class ReportInfoRequest(val taskId: String? = null,
val reportType: String? = null,
val reportTemplate: String? = null)
//报备历史请求
data class ReportHistoryRequest(val taskId: String? = null)
// public String reportType;//报备类型名称
// public String reportTemplate;//报备内容模板
// public String createTime;//备注时间
// public int state;//1 未处理 2 已处理
data class ReportHistoryBean(val reportType: String? = null,
val reportTemplate: String? = null,
val createTime: Long? = null,
val state: Int? = null)

View File

@ -0,0 +1,90 @@
package com.za.bean.db
import androidx.compose.ui.graphics.Color
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "change_battery_photo")
data class ChangeBatteryPhoto(
//图片id
@PrimaryKey(autoGenerate = true)
var id: Long? = null,
// 流程节点
var taskNode: Int? = null,
//图片描述
var imageDescription: String? = null,
//图片标题
var imageTitle: String? = null,
//标签
var tag: String? = null,
// 是否必拍 0 非必拍 1必拍
var doHaveFilm: Int? = null,
//封面地址
var photoUrl: String? = null,
//图片名称
var photoName: String? = null,
//图片类型
var photoType: Int? = null, //1 走单照片 2 工单照片 3更换电瓶照片
//创建日期
var createTime: String? = null,
var numbering: String? = null, //图片编号
var recognizeType: Int? = null, //ocr识别类型 0 无 1车牌号 2车架号
var photoTip: String? = null, //拍摄提示
var photoPath: String? = null, //照片路径
var photoContract: String? = null, //对比照
var photoNum: Int = 1, //对比照
var mustPhoto: Boolean = false, //是否必拍照片
var photoStatus: Int? = null, // 0 未上传 1 准备上传 2 已上传 3 上传失败 4 上传中 5 照片不符合条件 6 照片缺失地址信息 7 ocr车架号识别出错
var uploadedPath: String? = null,
var photoInfo: String? = null,
var index: Int? = null,
var userOrderId: Int? = null,
var taskId: Int? = null,
var taskCode: String? = null) {
fun convertPhotoStatusStr(status: Int): String {
return when (status) {
2 -> "已上传"
3 -> "上传失败"
4 -> "上传中"
5 -> "照片不符合条件"
6 -> "照片缺失地址信息"
7 -> "车架号识别出错"
else -> ""
}
}
fun getPhotoStatusColor(): Color {
return when (this.photoStatus) {
1 -> Color(0xFF2DBBF9)
2 -> Color(0xFF32CD32)
3 -> Color.Red
4 -> Color(0xFF2DBBF9)
5 -> Color.Red
6 -> Color.Red
else -> Color(0xFF2DBBF9)
}
}
}

View File

@ -0,0 +1,271 @@
package com.za.bean.db;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "new_table_photo_template")
public class NewPhotoTemplateBean {
//图片id
@PrimaryKey(autoGenerate = true)
public long id;
// 流程节点
private int taskNode;
//图片描述
private String imageDescription;
//图片标题
private String imageTitle;
//标签
private String tag;
// 是否必拍 0 非必拍 1必拍
private int doHaveFilm;
//封面地址
private String photoUrl;
//图片名称
private String photoName;
//图片类型
private int photoType;//1 走单照片 2 工单照片 3更换电瓶照片
//创建日期
private String createTime;
private String numbering;//图片编号
private Integer recognizeType;//ocr识别类型 0 无 1车牌号 2车架号
private String photoTip = "";//拍摄提示
private String photoPath = "";//照片路径
private String photoContract = "";//对比照
private int photoNum = 1;//对比照
private Boolean mustPhoto = false;//是否必拍照片
private int photoStatus = 0;// 0 未上传 1 准备上传 2 已上传 3 上传失败 4 上传中 5 照片不符合条件 6 照片缺失地址信息 7 ocr车架号识别出错
private String uploadedPath = "";
private String photoInfo;
private int index;
private int userOrderId;
private int taskId;
private String taskCode;
public NewPhotoTemplateBean() {
this.id = System.currentTimeMillis();
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public int getTaskNode() {
return taskNode;
}
public void setTaskNode(int taskNode) {
this.taskNode = taskNode;
}
public String getImageDescription() {
return imageDescription;
}
public void setImageDescription(String imageDescription) {
this.imageDescription = imageDescription;
}
public String getImageTitle() {
return imageTitle;
}
public void setImageTitle(String imageTitle) {
this.imageTitle = imageTitle;
}
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
public int getDoHaveFilm() {
return doHaveFilm;
}
public void setDoHaveFilm(int doHaveFilm) {
this.doHaveFilm = doHaveFilm;
}
public String getPhotoUrl() {
return photoUrl;
}
public void setPhotoUrl(String photoUrl) {
this.photoUrl = photoUrl;
}
public String getPhotoName() {
return photoName;
}
public void setPhotoName(String photoName) {
this.photoName = photoName;
}
public int getPhotoType() {
return photoType;
}
public void setPhotoType(int photoType) {
this.photoType = photoType;
}
public String getCreateTime() {
return createTime;
}
public void setCreateTime(String createTime) {
this.createTime = createTime;
}
public String getNumbering() {
return numbering;
}
public void setNumbering(String numbering) {
this.numbering = numbering;
}
public Integer getRecognizeType() {
return recognizeType;
}
public void setRecognizeType(Integer recognizeType) {
this.recognizeType = recognizeType;
}
public String getPhotoTip() {
return photoTip;
}
public void setPhotoTip(String photoTip) {
this.photoTip = photoTip;
}
public String getPhotoPath() {
return photoPath;
}
public void setPhotoPath(String photoPath) {
this.photoPath = photoPath;
}
public String getPhotoContract() {
return photoContract;
}
public void setPhotoContract(String photoContract) {
this.photoContract = photoContract;
}
public int getPhotoNum() {
return photoNum;
}
public void setPhotoNum(int photoNum) {
this.photoNum = photoNum;
}
public Boolean getMustPhoto() {
return mustPhoto;
}
public void setMustPhoto(Boolean mustPhoto) {
this.mustPhoto = mustPhoto;
}
public int getPhotoStatus() {
return photoStatus;
}
public void setPhotoStatus(int photoStatus) {
this.photoStatus = photoStatus;
}
public String getUploadedPath() {
return uploadedPath;
}
public void setUploadedPath(String uploadedPath) {
this.uploadedPath = uploadedPath;
}
public String getPhotoInfo() {
return photoInfo;
}
public void setPhotoInfo(String photoInfo) {
this.photoInfo = photoInfo;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public int getUserOrderId() {
return userOrderId;
}
public void setUserOrderId(int userOrderId) {
this.userOrderId = userOrderId;
}
public int getTaskId() {
return taskId;
}
public void setTaskId(int taskId) {
this.taskId = taskId;
}
public String getTaskCode() {
return taskCode;
}
public void setTaskCode(String taskCode) {
this.taskCode = taskCode;
}
@Override
public String toString() {
return "NewPhotoTemplateBean{" +
"id=" + id +
", taskNode=" + taskNode +
", imageDescription='" + imageDescription + '\'' +
", imageTitle='" + imageTitle + '\'' +
", tag='" + tag + '\'' +
", doHaveFilm=" + doHaveFilm +
", photoUrl='" + photoUrl + '\'' +
", photoName='" + photoName + '\'' +
", photoType=" + photoType +
", createTime='" + createTime + '\'' +
", numbering='" + numbering + '\'' +
", recognizeType=" + recognizeType +
", photoTip='" + photoTip + '\'' +
", photoPath='" + photoPath + '\'' +
", photoContract='" + photoContract + '\'' +
", photoNum=" + photoNum +
", mustPhoto=" + mustPhoto +
", photoStatus=" + photoStatus +
", uploadedPath='" + uploadedPath + '\'' +
", photoInfo='" + photoInfo + '\'' +
", index=" + index +
", userOrderId=" + userOrderId +
", taskId=" + taskId +
", taskCode='" + taskCode + '\'' +
'}';
}
}

View File

@ -0,0 +1,28 @@
package com.za.bean.db.ele
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
@Entity(tableName = "ele_car_damage_photo")
data class EleCarDamagePhotoBean(
@PrimaryKey(autoGenerate = true)
val id: Long? = null,
val path: String? = null,
val orderId: Int? = null,
val userOrderId: Int? = null,
val isPhoto: Boolean? = null,
val videoThumbnailPath: String? = null,
val uploadStatus: Int? = null,// 1 上传成功 2 正在上传 3上传失败
val serverPath: String? = null,
val index: Int? = null
) : Serializable {
fun getStatusStr(): String {
return when (uploadStatus) {
1 -> "上传成功"
2 -> "正在上传"
3 -> "上传失败"
else -> ""
}
}
}

View File

@ -0,0 +1,26 @@
package com.za.bean.db.ele
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "ele_work_order")
data class EleWorkOrderBean(
@PrimaryKey(autoGenerate = false) val orderId: Int,
val userOrderId: Int? = null,
val date: String? = null, //日期 2023-10-20
val serviceContent: String? = null, //服务须知内容
val orderWorkStatus: Int? = null, //电子工单状态 1车辆损伤照片 2 客户签名页面 3 服务完成 接车人签名和服务人员签名状态
val hasBad: Boolean? = null, //车辆是否有损伤
val carNO: String? = null,//车牌号
val carVin: String? = null,//车架号
val orderType: String? = null, //救援类型
val isSuccess: Int? = null, //服务是否成功 1 成功 其他失败
val localCustomSignPath: String? = null,
val localAcceptCarSignPath: String? = null,//接车人本地路径
val localServicePeopleSignPath: String? = null, //服务人员签名
val serverCustomSignPath: String? = null,
val serverAcceptCarSignPath: String? = null, //远程地址
val serverServicePeopleSignPath: String? = null,
val hasCreatedEleWorkOrderPhoto: Boolean? = null, //是否已经生成电子工单照片
val changeBattery: Boolean? = null //是否更换电瓶
)

View File

@ -0,0 +1,237 @@
package com.za.bean.db.order
import android.os.Parcel
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "order_info")
data class OrderInfo(
@PrimaryKey(autoGenerate = false) var taskId: Int? = null, //订单号 "taskId":5313005
var userOrderId: Int? = null, //订单id
var taskCode: String? = null, //订单编码 "taskCode":"ZD20190308009965"
var customerName: String? = null, //客户姓名 "customerName":"越继安"
var customerPhone: String? = null,//客户电话 "customerPhone":"18078815268"
var carBrand: String? = null,//车辆品牌 "carBrand":""
var modelVinNo: String? = null,
var carModel: String? = null, //车辆型号 "carModel":"秦"
var carNo: String? = null, //客户车车牌号 "carNo":"粤AF53918"
var carVin: String? = null,//客户车Vin码 "carVin":"LGXC76C30J0132942"
var taskState: String? = null, //订单状态 "taskState":"GOTO"
var nextState: String? = null,//下一步状态
var electronOrderState: Int? = null, //电子工单状态1检查损伤 2生成车辆检查表签字 3作业完成签字
var address: String? = null, //任务地址 "address":"广东省广州市白云区107国道石井凰岗路342号(白云黄石、同德围地区近庆丰兴隆公园)美景大酒店"
var addressProperty: String? = null, //任务地址类型 "addressProperty":"地面"
var addressRemark: String? = null, //事发地地址补充
var distAddress: String? = null, //目的地地址 "distAddress":"广东省广州市白云区雅岗南大道"
var distAddressRemark: String? = null,//目的地地址补充
var expectArriveTime: String? = null, //预计到达时间 "expectArriveTime":"2019-03-08 05:11:07"
var serviceTypeName: String? = null, //服务类型 "serviceTypeName":"故障--平板拖车"
var orderSource: String? = null, // "orderSource":"中道救援-比亚迪道路救援项目"
var flowType: Int? = null, //流程类型 0 综合服务 1小修 2拖车 3取送件 4在线技术解决 5组合服务 6拖车简化流程 7武汉交管服务
var settleType: Int? = null,//结算类型 1 月结 2 现金
var settleTypeStr: String? = null, //结算类型
var supplierId: Int? = null,
var startTime: String? = null,//发车时间
var operationTime: String? = null, //作业完成时间
var dispatchTime: String? = null,//派单时间 "dispatchTime":"2019-03-08 04:26:07"
var verifyType: Int? = null,//验证类型 "verifyType": 0 跳过验证 1和2 车牌和车架号验证 5 新车验证
var verifyValue: String? = null,//验证内容 "verifyValue":"粤AF53918"
var holdon: Boolean? = null,//是否被挂起 "holdon":false
var isCurrent: Boolean? = null, //是否为正在做的任务
var flow: String? = null, //流程状态 "flow":"START,GOTO,VERIFY,EXAMINE,OPERATION,SENDTO,SETTLEMENT,FINISH"
var externalCode: String? = null, //
var plateNumber: String? = null,
var distaddressProperty: String? = null,
var vehiclePointRemark: String? = null,
var destinationRemark: String? = null,
var lat: Double? = null,
var lng: Double? = null,
var distLat: Double? = null,
var distLng: Double? = null,
var hotline: String? = null,
var createTime: String? = null,
var acceptTime: String? = null,
var arriveTime: String? = null,
var arriveDestTime: String? = null,
var needECDevice: Boolean? = null,
var needDestAddress: Boolean? = null,
var linkToDocs: Boolean? = null,
var linkToDaDianH5: Boolean? = null,
var carFactory: Boolean? = null,
var needWaterMarker: Boolean? = null,
var needShowPhoneBrand: Boolean? = null,
var taskNotes: String? = null, //救援要求
var feeStandard: String? = null, //收费标准
var customerNotes: String? = null, //客户提醒
var otherNotes: String? = null, //特殊提醒
var isNewCar: Boolean? = null, //是否新车
var ECDeviceString: String? = null,//搭电设备照片提示
var customerReportImgs: String? = null, //客户上报照片,多张照片用;分隔
var arriveRemind: String? = null,
var arriveRemindLink: String? = null,
var policyNo: String? = null, // 平安拖车责任险 保单号
var advanceTime: Long? = null,
var importantTip: String? = null,
var hasReplaceBatteryCapable: Int? = null, //是否有更换电瓶的能力 1 搭电可以更换电瓶 2搭电不可以更换电瓶 其他的不展示
var taskSuccessStatus: Int? = null, //作业是否完成 0 完成 1未完成 拖车默认完成
var tyreNumber: Int? = null, //辅助费用
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readString(),
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readString(),
parcel.readValue(Boolean::class.java.classLoader) as? Boolean,
parcel.readValue(Boolean::class.java.classLoader) as? Boolean,
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readValue(Double::class.java.classLoader) as? Double,
parcel.readValue(Double::class.java.classLoader) as? Double,
parcel.readValue(Double::class.java.classLoader) as? Double,
parcel.readValue(Double::class.java.classLoader) as? Double,
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readValue(Boolean::class.java.classLoader) as? Boolean,
parcel.readValue(Boolean::class.java.classLoader) as? Boolean,
parcel.readValue(Boolean::class.java.classLoader) as? Boolean,
parcel.readValue(Boolean::class.java.classLoader) as? Boolean,
parcel.readValue(Boolean::class.java.classLoader) as? Boolean,
parcel.readValue(Boolean::class.java.classLoader) as? Boolean,
parcel.readValue(Boolean::class.java.classLoader) as? Boolean,
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readValue(Boolean::class.java.classLoader) as? Boolean,
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readValue(Long::class.java.classLoader) as? Long,
parcel.readString(),
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readValue(Int::class.java.classLoader) as? Int) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeValue(taskId)
parcel.writeValue(userOrderId)
parcel.writeString(taskCode)
parcel.writeString(customerName)
parcel.writeString(customerPhone)
parcel.writeString(carBrand)
parcel.writeString(modelVinNo)
parcel.writeString(carModel)
parcel.writeString(carNo)
parcel.writeString(carVin)
parcel.writeString(taskState)
parcel.writeString(nextState)
parcel.writeValue(electronOrderState)
parcel.writeString(address)
parcel.writeString(addressProperty)
parcel.writeString(addressRemark)
parcel.writeString(distAddress)
parcel.writeString(distAddressRemark)
parcel.writeString(expectArriveTime)
parcel.writeString(serviceTypeName)
parcel.writeString(orderSource)
parcel.writeValue(flowType)
parcel.writeValue(settleType)
parcel.writeString(settleTypeStr)
parcel.writeValue(supplierId)
parcel.writeString(startTime)
parcel.writeString(operationTime)
parcel.writeString(dispatchTime)
parcel.writeValue(verifyType)
parcel.writeString(verifyValue)
parcel.writeValue(holdon)
parcel.writeValue(isCurrent)
parcel.writeString(flow)
parcel.writeString(externalCode)
parcel.writeString(plateNumber)
parcel.writeString(distaddressProperty)
parcel.writeString(vehiclePointRemark)
parcel.writeString(destinationRemark)
parcel.writeValue(lat)
parcel.writeValue(lng)
parcel.writeValue(distLat)
parcel.writeValue(distLng)
parcel.writeString(hotline)
parcel.writeString(createTime)
parcel.writeString(acceptTime)
parcel.writeString(arriveTime)
parcel.writeString(arriveDestTime)
parcel.writeValue(needECDevice)
parcel.writeValue(needDestAddress)
parcel.writeValue(linkToDocs)
parcel.writeValue(linkToDaDianH5)
parcel.writeValue(carFactory)
parcel.writeValue(needWaterMarker)
parcel.writeValue(needShowPhoneBrand)
parcel.writeString(taskNotes)
parcel.writeString(feeStandard)
parcel.writeString(customerNotes)
parcel.writeString(otherNotes)
parcel.writeValue(isNewCar)
parcel.writeString(ECDeviceString)
parcel.writeString(customerReportImgs)
parcel.writeString(arriveRemind)
parcel.writeString(arriveRemindLink)
parcel.writeString(policyNo)
parcel.writeValue(advanceTime)
parcel.writeString(importantTip)
parcel.writeValue(hasReplaceBatteryCapable)
parcel.writeValue(taskSuccessStatus)
parcel.writeValue(tyreNumber)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<OrderInfo> {
override fun createFromParcel(parcel: Parcel): OrderInfo {
return OrderInfo(parcel)
}
override fun newArray(size: Int): Array<OrderInfo?> {
return arrayOfNulls(size)
}
}
}

View File

@ -0,0 +1,67 @@
package com.za.bean.db.order
import androidx.compose.ui.graphics.Color
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "photo_template_bean")
data class PhotoTemplateInfo(
@PrimaryKey(autoGenerate = true) val primaryId: Int? = null,
val id: Int? = null,
val taskNode: Int? = null,
val imageDescription: String? = null,// 图片描述
val imageTitle: String? = null,// 图片标题
val tag: String? = null,// 标签
val doHaveFilm: Int? = null,//是否必拍 0 非必拍 1 必拍
val photoUrl: String? = null,//封面地址
val photoName: String? = null,// 图片名称
val photoType: Int? = null,// 1 照片 2 电子工单
val createTime: String? = null,//创建时间
val numbering: String? = null,// 图片编号
val recognizeType: Int? = null,//orc 识别类型 0 无 1 车牌号 2 车架号
//以下属性非后台返回属性
val userOrderId: Int? = null,
val taskCode: String? = null,
val taskId: Int? = null,
val photoLocalPath: String? = null,//照片未添加水印之前的位置
val photoUploadPath: String? = null,//照片上传服务器之后的路径
val photoLocalWaterMarkerPath: String? = null,//照片水印位置
val photoUploadStatus: Int? = null,// 0 未上传 1 准备上传 2 已上传 3 上传失败 4 上传中 5 照片不符合条件 6 照片缺失地址信息 7 ocr车架号识别出错
val photoUploadStatusStr: String? = null,
var advanceTime: Long? = null,
val needWaterMarker: Boolean? = null,
val needShowPhoneBrand: Boolean? = null,
val myCustomPhotoType: Int? = null,// 1 默认是服务中照片订单图片 2 历史中照片 3.更换电瓶照片 4.普通的照片
// photo info字段
val realTakePhotoTime: String? = null,//真实拍照时间
val photoSource: Int? = null,//1相机 2 相册 3真实位置
val time: String? = null,// 拍照时间
val lat: Float? = null,
val lng: Float? = null,
val address: String? = null,
) {
fun convertPhotoStatusStr(status: Int): String {
return when (status) {
1 -> "准备上传"
2 -> "已上传"
3 -> "上传失败"
4 -> "上传中"
5 -> "照片不符合条件"
6 -> "照片缺失地址信息"
7 -> "车架号识别出错"
else -> ""
}
}
fun getPhotoStatusColor(): Color {
return when (this.photoUploadStatus) {
1 -> Color(0xFF2DBBF9)
2 -> Color(0xFF32CD32)
3 -> Color.Red
4 -> Color(0xFF2DBBF9)
5 -> Color.Red
6 -> Color.Red
else -> Color(0xFF2DBBF9)
}
}
}

View File

@ -0,0 +1,22 @@
package com.za.bean.db.water_marker
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "water_marker_item")
data class WaterMarkerItemBean(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "water_left") var left: Double = 0.0,
@ColumnInfo(name = "water_right") val right: Double = 0.0,
val top: Double = 0.0,
val bottom: Double = 0.0,
val content: String? = null,
val color: String? = null,
val wideFont: String? = null, //高度大于宽度的系数
val highFont: String? = null, //高度大于宽度的系数
val waterTemplateId: Int? = null,
val taskCode: String? = null,
val taskId: Int? = null,
)

View File

@ -0,0 +1,11 @@
package com.za.bean.db.water_marker
import androidx.room.Embedded
import androidx.room.Relation
data class WaterMarkerTemplateAndItemRef(
@Embedded
val waterMarkerTemplateBean: WaterMarkerTemplateBean? = null,
@Relation(parentColumn = "taskCode", entityColumn = "taskCode")
val waterMarkerItemBeanList: List<WaterMarkerItemBean>? = null
)

View File

@ -0,0 +1,14 @@
package com.za.bean.db.water_marker
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "water_template")
data class WaterMarkerTemplateBean(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val templateId: Int? = null,
val templateName: String? = null,
val taskCode: String? = null,
val taskId: Int? = null,
val updateTime: String? = null)

View File

@ -0,0 +1,17 @@
package com.za.bean.request
data class AcceptOrderRequest(
var userId: Int? = null,
var taskId: Int? = null,
var vehicleId: Int? = null,
var taskCode: String? = null,
var lat: Double? = null,
var lng: Double? = null,
var deviceId: String? = null,
)
data class RefuseOrderRequest(
var userId: Int? = null,
var taskId: Int? = null,
var vehicleId: Int? = null,
var taskCode: String? = null)

View File

@ -0,0 +1,5 @@
package com.za.bean.request
data class DriverFaceCompareRequest(val vehicleId: Int? = null, val driverId: Int? = null, val photoUrl: String? = null)
data class DriverFaceCompareBean(val flowId: Int? = null, val shortLink: String? = null, val url: String? = null)

View File

@ -0,0 +1,34 @@
package com.za.bean.request
data class LoginRequest(
var jobCode: String? = null, //工号
var supplierCode: String? = null, //经销商代码
var phone: String? = null, //密码
var phoneModel: String? = null, //手机机型
var vehicleId: Int? = null, //车辆id
var enterMileage: Int? = null,//上班公里数
var deviceInfo: String? = null, //手机信息
var regId: String? = null, //手机信息
var lat: Double? = null,
var lng: Double? = null,
var deviceId: String? = null,
var dashboardPath: String? = null //里程表照片路径
)
data class PhoneBean(
var versionRelease: String? = null,//系统版本
var model: String? = null,//手机型号
var brand: String? = null,//手机厂商
var appVersion: String? = null,//app版本
)
data class VehicleLogoutRequest(
var vehicleId: Int? = null, //车辆id
var loginLogId: Int? = null ,//用户id
var exitMileage: Int? = null,
)
data class VerifyCodeRequest(val phone : String? = null)
data class VerifyCodeResponse(val verifyCode : String? = null)

View File

@ -0,0 +1,3 @@
package com.za.bean.request
data class UploadPhotoBean(val img: String? = null)

View File

@ -0,0 +1,56 @@
package com.za.bean.request
data class OrderListRequest(
val vehicleId: Int? = null,
val deviceId: String? = null,
)
/**
* 照片模版请求数据
*/
data class PhotoTemplateRequest(val userOrderId: Int? = null)
//照片识别
data class OrderPhotoOcrRecognizeRequest(
val userOrderId: Int? = null,
val recognizeType: Int? = null,
val imgUrl: String? = null,
)
data class TaskFinishRequest(
val userOrderId: Int? = null,
val lat: Double? = null,
val lng: Double? = null,
val operateTime: Long? = null)
data class GiveUpTaskRequest(
val taskId: Int? = null, //任务id
val userId: Int? = null, //用户id
val vehicleId: Int? = null, //车辆id
val lat: Double? = null,
val lng: Double? = null,
val address: String? = null,
val templatePhotoInfoList: List<String?>? = null, //照片数据
val pushGiveUpFlag: Int? = null, //1 是 0否
)
data class UpdatePhotoRequest(
val taskId: Int? = null,
val taskState: String? = null,
val picturePosition: Int? = null, //图片位置
val picturePath: String? = null,//图片路径
val imgInfo: String? = null
)
data class SwitchTaskRequest(val currentTaskId: Int? = null,
val nextTaskId: Int? = null,
val userId: Int? = null,
val vehicleId: Int? = null)
data class HistoryTasksRequest(val userId: Int? = null, val vehicleId: Int? = null)
data class HistoryPhotoTemplateRequest(val taskId: Int? = null)
data class HistoryDetailRequest(val taskId: Int? = null)

View File

@ -0,0 +1,51 @@
package com.za.bean.request
data class PaymentInfoRequest(
var userOrderId: Int? = null,
val taskOrderId: Int? = null)
// private int userOrderId;
// private int taskOrderId;
// private int onSite;//是否在现场 1是 0否
// private Integer orderPayDetailId;//订单支付明细ID
// private String userPhone;//客户号码,客户不在现场时必填
data class CustomerPaymentCreateRequest(val userOrderId: Int? = null,
val taskOrderId: Int? = null,
val onSite: Int? = null,
val orderPayDetailId: Int? = null,
val userPhone: String? = null)
// private int userOrderId;
// private int taskOrderId;
// private Float amount;
// private String payData;//二维码链接
// private String payCode;//付款编号
// private String plateNumber;//车牌号
data class CustomerPaymentCreateBean(val userOrderId: Int? = null,
val taskOrderId: Int? = null,
val amount: Float? = null,
val payData: String? = null,
val payCode: String? = null,
val plateNumber: String? = null)
//private Integer userOrderId;
//private Integer taskOrderId;
//private Float payAmount; //收款金额
//private Float unitPrice;//单价 //如果单价为 null 并且payItem 为2 则为超限无单价项目
//private Float mileage;//公里数
//private Integer orderPayDetailId;//订单支付明细ID
//private String updateRemark;//支付项目 1.现金项目 2超限项目
//private Float calculateAmount;//计算金额
//private Float adjustAmount;//调整金额
//private boolean askPayment;
data class PaymentUpdateRequest(val userOrderId: Int? = null,
val taskOrderId: Int? = null,
val payAmount: Float? = null,
val unitPrice: Float? = null,
val mileage: Float? = null,
val orderPayDetailId: Int? = null,
val updateRemark: String? = null,
val calculateAmount: Float? = null,
val adjustAmount: Float? = null,
val askPayment: Boolean? = null)

View File

@ -0,0 +1,51 @@
package com.za.bean.request
data class SaveEleOrderRequest(
val taskOrderId: Int? = null,
val userOrderId: Int? = null,
val state: Int? = null,
val hasDamage: Int? = null, //是否有算上 1 有
val hasSuccess: Int? = null, // 作业是否完成 1成功 0失败
val customerSignPath: String? = null, //客户签名照片
val recipientSignPath: String? = null, //接车人签名
val waitstaffSignPath: String? = null, //服务人员签名
val damageFileList: List<String?>? = null, //损伤文件列表
val userOrderCode: String? = null, //仅在历史补传电子工单时传
val tyreNumber: Int? = null, //小轮个数
val lng: Double? = null,
val lat: Double? = null,
val isFinish: Boolean = false, //订单是否完成进入历史 true 完成
/**
* 操作时间(时间戳)
*/
val operateTime: String? = null,
/**
* 0 正常上传 1离线补传
*/
val offlineMode: Int? = null)
data class QueryEleOrderRequest(val taskOrderId: Int? = null,
val userOrderId: Int? = null,
val userOrderCode: String? = null)
data class ElectronOrderResponse(val id: Int? = null,
val userOrderId: Int? = null,
val state: Int? = null,
val hasDamage: Int? = null, //1 有损伤 2 无损伤
val hasSuccess: Int? = null,
val customerSignPath: String? = null,
val recipientSignPath: String? = null,
val waitstaffSignPath: String? = null,//
val createTime: String? = null,
val updateTime: String? = null,
val damageFileList: List<String>? = null,
val vinNo: String? = null,
val plateNumberBean: PlateNumberBean? = null,
val serviceId: Int? = null,
val serviceName: String? = null,
val flowId: Int? = null,
val serviceTerm: String? = null)
data class PlateNumberBean(val value: String? = null)

View File

@ -0,0 +1,157 @@
package com.za.bean.request
data class TodayMaintainbean(
val tag: String? = null,//
val tagName: String? = null, //名称
val description: String? = null, //描述
val img: String? = null, //图片地址
)
data class TodayMaintainRequest(val vehicleId: Int? = null)
data class TodayMaintainUploadRequest(
val vehicleId: String? = null,
val driverId: String? = null,
val tag: String? = null,
val tagImage: String? = null,
)
data class RecognizeRefuelOcrRequestBean(
val imageUrl: String? = null,
val address: String? = null,
val longitude: Double? = null,
val latitude: Double? = null,
val vehicleId: Double? = null,
val driverId: Double? = null,
)
data class RecognizeRefuelTicketBean(
var refuelCode: String? = null, //加油卡号
var quantity: Double? = null,//加油量
var amount: Double? = null,//加油金额
var filePath: String? = null,
)
data class RecognizeRefuelTicketRequestBean(
val vehicleId: Int? = null,
val driverId: Int? = null,
val address: String? = null,
val longitude: Double? = null,
val latitude: Double? = null,
val refuelCode: String? = null,
val quantity: Double? = null,
val amount: Double? = null,
val mileage: Double? = null,
val refuelTime: Double? = null,
val receiptPhotoUrls: String? = null,
val anotherPhotoUrls: String? = null,
)
data class RepairHistoryRequestBean(val id: Int? = null,
val driverId: Int? = null,
val vehicleId: Int? = null)
data class VehicleRepairBean(
var id: Int? = null,//维保记录ID
var stage: String? = null, //阶段 //1 维保申请审批中 2,"维保申请不通过" 3,"维保申请通过" 4,"维保完成审批中" 5,"维保完成不通过" 6,"维保完成通过" 9,"维保终止"
var vehicleId: Int? = null,//车辆ID
var vehicleName: String? = null, //车辆名称
var plateNumber: String? = null, //车牌号
var states: Int? = null,//维保状态
var statesStr: String? = null, //维保状态-描述
var address: String? = null, //维修地址
var repairPoint: String? = null, //维修点名称
var vehicleRepairId: Int? = null,//维修点Id
var longitude: Double? = null, //维保地址经度
var latitude: Double? = null, //维保地址纬度
var storePhotoUrls: String? = null, //门店照片
var paymentType: Int? = null,//支付类型 1挂账 2现金
var amount: Double? = null, //维保金额
var mileage: Int? = null,//车辆里程数
var information: String? = null, //维保信息
var maintenancePhotoUrls: String? = null, //维保照片/项目照片
var voucherPhotoUrls: String? = null, //维保清单/凭证照片
var auditRemark: String? = null, //维保审核备注
var createTime: String? = null, //创建时间
var updateTime: String? = null, //修改时间
) {
fun getPayTypeStr(): String {
return when (paymentType) {
1 -> "月结"
2 -> "全包"
3 -> "现金"
4 -> "月结+现金"
else -> ""
}
}
}
//维保提价记录
data class FetchVehicleMaintenanceSubmitHistoryRequestBean(val vehicleId: Int? = null, val driverId: Int? = null, val id: Int? = null)
/**
* 今日保养的请求信息
*/
data class VehicleMaintenanceHistoryBean(
val id: Int? = null,
val stage: String? = null,//阶段 //1 维保申请审批中 2,"维保申请不通过" 3,"维保申请通过" 4,"维保完成审批中" 5,"维保完成不通过" 6,"维保完成通过" 9,"维保终止"
val vehicleId: Int? = null,//车辆ID
val vehicleName: String? = null,//
val plateNumber: String? = null,//车牌号
val states: Int? = null,//维保状态
val statesStr: String? = null,//维保状态-描述
val address: String? = null,//维修地址
val repairPoint: String? = null,//维修点名称
val vehicleRepairId: Int? = null,//维修点Id
val longitude: Double? = null,//维保地址经度
val latitude: Double? = null,//维保地址纬度
val storePhotoUrls: String? = null,//门店照片
val paymentType: Int? = null,//支付类型 1挂账 2现金
val amount: Double? = null,//维保金额
val mileage: Double? = null,//车辆里程数
val information: String? = null,//维保信息
val maintenancePhotoUrls: String? = null,//维保照片/项目照片
val voucherPhotoUrls: String? = null,//维保清单/凭证照片
val auditRemark: String? = null,//维保审核备注
val createTime: String? = null,//创建时间
val updateTime: String? = null//修改时间
)
data class VehicleRepairPointMatcherItem(
val id: Int? = null,
val repairName: String? = null,
val repairLon: Double? = null,
val repairLat: Double? = null,
val repairAddress: String? = null,
val supplierId: Int? = null,
val distance: Double? = null,
val isSelected: Boolean? = false,
)
data class VehicleRepairPointMatcherListRequest(
val vehicleId: Int? = null,
val address: String? = null,
val longitude: Double? = null,
val latitude: Double? = null)
data class VehicleMaintenanceSubmitRequest(
val id: Int? = null,//维保记录ID
val vehicleId: Int? = null,//车辆ID
val driverId: Int? = null,//司机ID
val submitType: Int? = null,//提交类型 1维修开始申请 2维保完成申请
val address: String? = null,//维修地址
val longitude: Double? = null,//维修地址经度
val latitude: Double? = null,//维修地址纬度
val vehicleRepairId: Int? = null,//维修点Id
val storePhotoUrls: String? = null,//门店照片
val paymentType: Int? = null,//支付类型和结算类型
val amount: Double? = null,//维保金额
val mileage: Int? = null,//车辆里程数
val information: String? = null,//维保信息/项目
val maintenancePhotoUrls: String? = null,//维保照片/项目照片
val voucherPhotoUrls: String? = null,//维保清单/凭证照片
val auditRemark: String? = null,//维保审核备注
val remark: String? = null//维保备注
)

View File

@ -0,0 +1,75 @@
package com.za.bean.request
data class UpdateTaskRequest(
val id: Int? = null,
val type: String? = null,
val taskId: Int? = null,
val userId: Int? = null,
val vehicleId: Int? = null,
val currentState: String? = null,
val operateTime: String? = null,
val lat: Double? = null,
val lng: Double? = null,
val address: String? = null,
val offlineMode: Int? = null,
val content: String? = null, //验证输入内容
val newCarCode: String? = null,
val flowType: Int? = null,
val success: Int? = null, //作业是否完成 0 成功 1不成功 拖车默认成功
val templatePhotoInfoList: List<String?>? = null
)
data class UpdateTaskBean(val nextState: String? = null, val address: Long? = null)
data class TaskFinishResponse(
val nextState: String? = null,
val advanceTime: Long? = null,
val electronOrderState: Int? = null,
)
data class UpdateOrderConfirmTaskRequest(
val type: String? = null,
val taskId: Int? = null,
val userId: Int? = null,
val vehicleId: Int? = null,
val currentState: String? = null,
val operateTime: String? = null,
val offlineMode: Int? = null,
val supplierType: Int? = null,
val settleType: Int? = null,
val success: Int? = null,
val lat: Double? = null,
val lng: Double? = null,
val carryMileage:Int?=null,
val startMileage:Int?=null,
//老系统
val wheelFee: Int? = null,
//子公司
val startPrice: Int? = null,
val unitPrice: Double? = null,
val mileage: Int? = null,
val basePrice: Double? = null,
val startRoadFee: Int? = null,
val carryRoadFee: Int? = null,
val waitFee: Int? = null,
val wheelNum: Int? = null,
val wheelPrice: Int? = null,
val dilemmaFee: Int? = null,
val basementFee: Int? = null,
val assistFee: Double? = null,
val totalFee: Double? = null,
val img1: String? = null,
val img2: String? = null,
val img3: String? = null,
val img4: String? = null,
val img1Info: String? = null,
val img2Info: String? = null,
val img3Info: String? = null,
val img4Info: String? = null,
val imgList: List<String>? = null,
val imgInfoList: List<String>? = null,
val templatePhotoInfoList: List<String?>? = null
)

View File

@ -0,0 +1,11 @@
package com.za.bean.request
data class UploadGpsRequest(
var vehicleId: Int? = null,
var userId: Int? = null,
var lat: Double? = null,
var lng: Double? = null,
var speed: Double? = null,
var direction: Double? = null,
var working: Boolean? = false,
)

View File

@ -0,0 +1,19 @@
package com.za.bean.request
//车辆列表请求
data class VehicleListRequest(
val supplierCode: String? = null,
val content: String? = null
)
data class UpdateVehicleStateRequest(
val vehicleId: Int? = null,
val status: Int? = null,
val userId: Int? = null,
)
data class GeneralInfoRequest(
val vehicleId: Int? = null,
val driverId: Int? = null,
val deviceId: String? = null,
)

View File

@ -0,0 +1,159 @@
package com.za.call
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.provider.CallLog
import androidx.core.app.ActivityCompat
import com.blankj.utilcode.util.TimeUtils
import com.tencent.mmkv.MMKV
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.ext.toJson
import com.za.net.BaseObserver
import com.za.net.RetrofitHelper
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
object CallLogManager {
private const val TAG = "CallLogManager"
private var lastUploadTime : Long? = null
set(value) {
MMKV.defaultMMKV().encode("lastUploadCallLogTime", value ?: 0)
field = value
}
get() {
return MMKV.defaultMMKV().decodeLong("lastUploadCallLogTime", 0)
}
//要上传的电话
var phoneCallContactBean : ContactRecordBean? = null
fun uploadCallLog(context : Context) {
if (ActivityCompat.checkSelfPermission(context,
Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED
) {
LogUtil.print(TAG, "没有通话记录读取权限")
return
}
if (phoneCallContactBean == null || phoneCallContactBean?.phone.isNullOrEmpty()) {
if (GlobalData.currentOrder == null) {
return
}
phoneCallContactBean = ContactRecordBean(taskId = GlobalData.currentOrder?.taskId,
taskCode = GlobalData.currentOrder?.taskCode,
phone = GlobalData.currentOrder?.customerPhone)
}
val cursor = context.contentResolver.query(CallLog.Calls.CONTENT_URI,
arrayOf(CallLog.Calls.NUMBER,
CallLog.Calls.DATE,
CallLog.Calls.DURATION,
CallLog.Calls.TYPE),
"${CallLog.Calls.NUMBER} = ${phoneCallContactBean?.phone} ",
null,
"${CallLog.Calls.DATE} DESC")
LogUtil.print("call count", "${cursor?.columnCount}")
cursor?.use { c ->
while (c.moveToNext()) {
val date = c.getLong(c.getColumnIndexOrThrow(CallLog.Calls.DATE))
val duration = c.getLong(c.getColumnIndexOrThrow(CallLog.Calls.DURATION))
val type = c.getInt(c.getColumnIndexOrThrow(CallLog.Calls.TYPE))
val callLong = c.getLong(c.getColumnIndexOrThrow(CallLog.Calls.DATE))
if (System.currentTimeMillis() - callLong > 1000 * 60 * 50 * 24) {
break
}
if (lastUploadTime != null && callLong <= lastUploadTime !!) {
continue
}
uploadCallLogToServer(contactRecordBean = phoneCallContactBean,
date,
duration,
type)
}
}
}
private fun uploadCallLogToServer(contactRecordBean : ContactRecordBean?,
date : Long,
duration : Long,
type : Int) {
if (contactRecordBean == null) {
LogUtil.print("uploadCallLogToServer", "没有需要上传的电话")
return
}
val state = if (duration == 0L) {
if (type == CallLog.Calls.REJECTED_TYPE) { // 被拒接
3
} else {
2
}
} else {
1
}
val contactRecordRequest = ContactRecordRequest(taskId = contactRecordBean.taskId,
state = state,
taskCode = contactRecordBean.taskCode,
callTime = TimeUtils.millis2String(date),
duration = formatDuration(duration),
remarkPath = "")
doUploadCallLog(contactRecordRequest)
LogUtil.print(TAG, "uploadCallLogToServer params=${contactRecordRequest.toJson()}")
}
private fun doUploadCallLog(contactRecordRequest : ContactRecordRequest) {
RetrofitHelper.getDefaultService().uploadContactRecord(contactRecordRequest)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<String>() {
override fun doSuccess(it : String?) {
lastUploadTime = System.currentTimeMillis()
}
override fun doFailure(code : Int, msg : String?) {
LogUtil.print(TAG, "doFailure code=$code, msg=$msg")
}
})
}
// 将秒数转换为友好的时分秒格式
fun formatDuration(duration : Long) : String {
val hours = duration / 3600
val minutes = (duration % 3600) / 60
val seconds = duration % 60
return when {
hours > 0 && minutes == 0L && seconds == 0L -> "${hours}小时" // 只有小时
hours > 0 -> "${hours}小时${minutes}分钟${seconds}" // 包含小时、分钟、秒
minutes > 0 && seconds == 0L -> "${minutes}分钟" // 只有分钟
minutes > 0 -> "${minutes}分钟${seconds}" // 包含分钟、秒
seconds > 0 -> "${seconds}" // 只有秒
else -> "" // 其他情况
}
}
}
//public int taskId;
//public int state;
//public String taskCode;
//public String callTime;
//public String duration;
//
//public String remarkPath;
data class ContactRecordRequest(val taskId : Int? = null,
val state : Int? = null,
val taskCode : String? = null,
val callTime : String? = null,
val duration : String? = null,
val remarkPath : String? = null)
data class ContactRecordBean(val taskId : Int? = null,
val taskCode : String? = null,
val phone : String? = null)

View File

@ -0,0 +1,113 @@
package com.za.common
import android.app.Application
import com.amap.api.location.AMapLocation
import com.tencent.mmkv.MMKV
import com.za.bean.DriverInfo
import com.za.bean.VehicleInfo
import com.za.bean.db.order.OrderInfo
import com.za.room.RoomHelper
object GlobalData {
lateinit var application : Application
var activityCount : Int = 0
var token : String? = null
get() {
return MMKV.defaultMMKV().decodeString("TOKEN", null)
}
set(value) {
MMKV.defaultMMKV().encode("TOKEN", value)
field = value
}
//记录上次登录的手机号
var lastLoginPhone : String? = null
get() {
return MMKV.defaultMMKV().decodeString("lastLoginPhone", null)
}
set(value) {
MMKV.defaultMMKV().encode("lastLoginPhone", value)
field = value
}
var aesKey : String? = null
get() {
return MMKV.defaultMMKV().decodeString("AES_KEY", null)
}
set(value) {
MMKV.defaultMMKV().encode("AES_KEY", value)
field = value
}
var driverInfo : DriverInfo? = null
get() {
return MMKV.defaultMMKV().decodeParcelable("driverInfo", DriverInfo::class.java)
}
set(value) {
MMKV.defaultMMKV().encode("driverInfo", value)
field = value
}
var vehicleInfo : VehicleInfo? = null
get() {
return MMKV.defaultMMKV().decodeParcelable("vehicleInfo", VehicleInfo::class.java)
}
set(value) {
MMKV.defaultMMKV().encode("vehicleInfo", value)
field = value
}
var currentOrder : OrderInfo? = null
get() {
return MMKV.defaultMMKV().decodeParcelable("currentOrder", OrderInfo::class.java)
}
set(value) {
MMKV.defaultMMKV().encode("currentOrder", value)
if (RoomHelper.db?.orderDao()?.getCurrentOrder() == null && value != null) {
RoomHelper.db?.orderDao()?.insertOrder(value)
} else if (value != null) {
RoomHelper.db?.orderDao()?.update(value)
}
field = value
}
var currentLocation : AMapLocation? = null
get() {
return MMKV.defaultMMKV().decodeParcelable("currentLocation", AMapLocation::class.java)
}
set(value) {
value?.time = System.currentTimeMillis()
MMKV.defaultMMKV().encode("currentLocation", value)
field = value
}
var loginTime : Long? = null
get() {
return MMKV.defaultMMKV().decodeLong("loginTime", System.currentTimeMillis())
}
set(value) {
MMKV.defaultMMKV().encode("loginTime", value ?: System.currentTimeMillis())
field = value
}
fun clearUserCache() {
token = null
aesKey = null
driverInfo = null
vehicleInfo = null
currentLocation = null
loginTime = null
}
fun clearAllOrderCache() {
currentOrder = null
RoomHelper.clearAll()
}
fun clearOrderCache(taskId : Int) {
RoomHelper.clearOrderFromTaskCode(taskId = taskId)
}
}

View File

@ -0,0 +1,35 @@
package com.za.common
import android.app.Application
import com.tencent.bugly.Bugly
import com.tencent.mmkv.MMKV
import com.tencent.mmkv.MMKVLogLevel
import com.za.base.AppConfig
import com.za.common.log.LogUtil
import com.za.room.RoomHelper
import com.za.service.location.ZdLocationManager
object ZDManager {
lateinit var application : Application
fun init(application : Application) {
this.application = application
thirdSdkInit()
}
private fun thirdSdkInit() {
GlobalData.application = application
MMKV.initialize(application, MMKVLogLevel.LevelInfo)
Bugly.init(application, "6972a6b56d", true)
AppConfig.crm1()
LogUtil.init(application)
RoomHelper.init(application)
ZdLocationManager.init(application)
// 初始化讯飞SDK
//科大讯飞初始化
// SpeechUtility.createUtility(application,
// SpeechConstant.APPID + "=6fd4aabe," + SpeechConstant.FORCE_LOGIN + "=true")
// SpeechManager.init(application)
}
}

View File

@ -0,0 +1,65 @@
package com.za.common.log
import android.util.Log
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.io.UnsupportedEncodingException
import java.net.URLDecoder
import java.util.concurrent.TimeUnit
object LogRetrofitHelper {
private var retrofit: Retrofit? = null
private var apiService: LogService? = null
private val loggerInterceptor = HttpLoggingInterceptor {
try {
if (it.contains("image/*")) {
return@HttpLoggingInterceptor
}
if (it.contains("name=\"file\"; filename")) {
return@HttpLoggingInterceptor
}
Log.e(
"--network--",
URLDecoder.decode(it.replace(Regex("%(?![0-9a-fA-F]{2})"), ""), "utf-8")
)
} catch (e: UnsupportedEncodingException) {
e.printStackTrace()
}
}.setLevel(HttpLoggingInterceptor.Level.BODY)
fun getDefaultService(): LogService {
return if (apiService == null) {
apiService = getDefaultRetrofit().create(LogService::class.java)
apiService!!
} else {
apiService!!
}
}
private fun getDefaultRetrofit(): Retrofit {
return if (retrofit == null) {
retrofit = Retrofit.Builder().baseUrl("https://api2.sino-assist.com")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.client(getOkHttpClient())
.build()
retrofit!!
} else {
retrofit!!
}
}
private fun getOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.addInterceptor(loggerInterceptor)
.build()
}
}

View File

@ -0,0 +1,27 @@
package com.za.common.log
import io.reactivex.rxjava3.core.Observable
import okhttp3.MultipartBody
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Query
interface LogService {
//日志上传
@Multipart
@POST("/oss/minio/upload")
fun uploadLog(
@Part file: MultipartBody.Part?,
@Query("fileName") fileName: String?,
@Query("bucketName") bucketName: String?
): Observable<LogBaseResponse<String>>
}
data class LogBaseResponse<T : Any>(
val data: T?,
val msg: String?,
val success: Boolean?,
val code: Int?,
val total: Int?,
)

View File

@ -0,0 +1,317 @@
package com.za.common.log
import android.app.Application
import android.content.Context
import android.util.Log
import androidx.work.Configuration
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.blankj.utilcode.constant.MemoryConstants
import com.blankj.utilcode.util.AppUtils
import com.blankj.utilcode.util.DeviceUtils
import com.blankj.utilcode.util.FileUtils
import com.blankj.utilcode.util.TimeUtils
import com.za.common.GlobalData
import com.za.common.util.AppFileManager
import com.za.servicing.BuildConfig
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream
import java.io.BufferedWriter
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.FileWriter
import java.io.IOException
import java.io.PrintWriter
import java.io.StringWriter
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.concurrent.thread
object LogUtil {
private var context: Application? = null
private var logDestinationPath: String? = null
private var orderLogDirPath: String? = null
private var normalLogDirPath: String? = null
private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val logBuffer = StringBuilder()
private val isWriting = AtomicBoolean(false)
fun init(context: Application) {
this.context = context
logDestinationPath = AppFileManager.getLogPath(context).also { path ->
createDirectoryIfNotExists(path)
orderLogDirPath = "$path${File.separator}order_log".also { createDirectoryIfNotExists(it) }
normalLogDirPath = "$path${File.separator}normal_log".also { createDirectoryIfNotExists(it) }
}
initializeWorkManager(context)
}
private fun createDirectoryIfNotExists(path: String) {
File(path).apply { if (!exists()) mkdir() }
}
private fun initializeWorkManager(context: Application) {
if (!WorkManager.isInitialized()) {
WorkManager.initialize(context, Configuration.Builder()
.setMinimumLoggingLevel(Log.INFO)
.build())
}
WorkManager.getInstance(context).apply {
cancelAllWorkByTag("logWorkRequest")
enqueue(PeriodicWorkRequest.Builder(LogTask::class.java, 20, TimeUnit.MINUTES)
.addTag("logWorkRequest")
.build())
}
}
fun print(tag: String, content: String) {
val time = getCurrentTime()
val logEntry = "$time---$tag---$content\n"
if (BuildConfig.DEBUG) {
Log.e("normal", "$tag---$content")
}
synchronized(logBuffer) {
logBuffer.append(logEntry)
}
if (logBuffer.length > 4096) {
coroutineScope.launch {
flushBuffer()
}
}
}
fun print(tag: String, throwable: Throwable) {
val content = StringWriter()
val printWriter = PrintWriter(content)
throwable.printStackTrace(printWriter)
print(tag, content.toString())
}
private suspend fun flushBuffer() = withContext(Dispatchers.IO) {
if (!isWriting.compareAndSet(false, true)) return@withContext
val logContent: String
synchronized(logBuffer) {
if (logBuffer.isEmpty()) {
isWriting.set(false)
return@withContext
}
logContent = logBuffer.toString()
logBuffer.clear()
}
try {
val fileName = "normal_log.txt"
val logFile = File("$normalLogDirPath${File.separator}$fileName")
logFile.parentFile?.mkdirs()
if (!logFile.exists()) {
logFile.createNewFile()
addLogHead(logFile, getCurrentTime())
}
BufferedWriter(FileWriter(logFile, true)).use { writer ->
writer.write(logContent)
writer.flush()
}
if (logFile.length() >= 8 * MemoryConstants.MB) {
rotateLogFile(logFile)
}
} catch (e: IOException) {
Log.e("LogUtil", "Error in flushBuffer: ${e.message}")
} catch (e: Exception) {
Log.e("LogUtil", "Error in flushBuffer: ${e.message}")
} finally {
isWriting.set(false)
}
}
private fun rotateLogFile(file: File) {
if (!file.exists()) return
val newFileName = buildString {
append(AppUtils.getAppVersionCode())
append("_")
append(GlobalData.vehicleInfo?.vehicleName ?: "unknown")
append("_")
append(GlobalData.driverInfo?.userName ?: "unknown")
append("_")
append(TimeUtils.getNowString())
append(".txt")
}
val newFile = File("$normalLogDirPath${File.separator}$newFileName")
try {
if (file.renameTo(newFile)) {
compressAndUploadLog(newFile)
// 创建新的日志文件
file.createNewFile()
addLogHead(file, getCurrentTime())
} else {
print("LogUtil", "Failed to rename log file")
}
} catch (e: Exception) {
print("LogUtil", "Error during log rotation: ${e.message}")
}
}
private fun compressAndUploadLog(logFile: File) = coroutineScope.launch {
try {
val compressedFile = File("${logFile.absolutePath}.7z")
compress(logFile, compressedFile.absolutePath)
upload(logFile, compressedFile)
} catch (e: Exception) {
print("LogUtil", e.toString())
}
}
private fun deleteLog(file: File) {
try {
FileUtils.delete(file.absolutePath)
} catch (e: Exception) {
e.printStackTrace()
}
}
fun updateNormalLog() {
thread {
if (GlobalData.token.isNullOrBlank()) {
return@thread
}
val fileName = "normal_log.txt"
val file = File("$normalLogDirPath${File.separator}$fileName")
val reName = "${AppUtils.getAppVersionCode()}_${GlobalData.vehicleInfo?.vehicleName}_${GlobalData.driverInfo?.userName}_${TimeUtils.getNowString()}.txt"
val reNamePath = "$normalLogDirPath${File.separator}$reName"
file.renameTo(File(reNamePath))
normalLogDirPath?.let { it ->
File(it).listFiles()?.forEach {
if (it.length() / MemoryConstants.MB >= 10) {
deleteLog(it)
return@thread
}
if (it.exists() && !it.name.contains("normal_log")) {
if (it.name.contains("7z")) {
upload(null, desFile = it)
} else {
val zipNamePath = it.absolutePath + ".7z"
val zipFile = File(zipNamePath)
if (!zipFile.exists()) {
try {
zipFile.createNewFile()
} catch (e: IOException) {
e.printStackTrace()
}
}
compress(it, zipNamePath)
}
}
}
}
}
}
private fun compress(srcFile: File, desFilePath: String) {
try {
val out = XZCompressorOutputStream(FileOutputStream(desFilePath))
addToArchiveCompression(out, srcFile, File(desFilePath))
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun addToArchiveCompression(
sevenZOutputFile: XZCompressorOutputStream,
srcFile: File, desFile: File
) {
if (srcFile.isFile) {
var inputStream: FileInputStream? = null
try {
inputStream = FileInputStream(srcFile)
val b = ByteArray(2048)
var count: Int
while (inputStream.read(b).also { count = it } != -1) {
sevenZOutputFile.write(b, 0, count)
}
sevenZOutputFile.close()
inputStream.close()
upload(srcFile, desFile)
} catch (e: Exception) {
e.printStackTrace()
} finally {
try {
sevenZOutputFile.close()
inputStream!!.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
private fun upload(srcFile: File?, desFile: File) {
val requestBody: RequestBody = desFile.asRequestBody("multipart/form-data".toMediaType())
val part = MultipartBody.Part.createFormData("file", desFile.name, requestBody)
val disposable = LogRetrofitHelper.getDefaultService()
.uploadLog(part, desFile.name, "rescue-app")
.subscribeOn(Schedulers.io())
.subscribe({ it ->
if (it.code == 200) {
deleteLog(desFile)
}
srcFile?.let {
deleteLog(it)
}
}, {
}, {})
}
private fun addLogHead(file: File, time: String) {
file.appendBytes("${time}---应用版本---${AppUtils.getAppVersionName()}".toByteArray())
file.appendBytes("\n".toByteArray())
file.appendBytes("$time---系统版本---Android${DeviceUtils.getSDKVersionName()} ${DeviceUtils.getSDKVersionCode()}".toByteArray())
file.appendBytes("\n".toByteArray())
file.appendBytes("$time---ROM---${DeviceUtils.getManufacturer()} ${DeviceUtils.getModel()}".toByteArray())
file.appendBytes("\n".toByteArray())
file.appendBytes("$time---build---${AppUtils.getAppVersionCode()}".toByteArray())
file.appendBytes("\n".toByteArray())
file.appendBytes("$time---APP名称---中道救援-司机端".toByteArray())
file.appendBytes("\n".toByteArray())
file.appendBytes("$time---车辆名称---${GlobalData.vehicleInfo?.vehicleName}".toByteArray())
file.appendBytes("\n".toByteArray())
file.appendBytes("$time---司机名称---${GlobalData.driverInfo?.userName ?: GlobalData.vehicleInfo?.userName}".toByteArray())
file.appendBytes("\n".toByteArray())
}
private fun getCurrentTime(): String {
return TimeUtils.millis2String(System.currentTimeMillis(), "yyyy/MM/dd HH:mm:ss.SSS")
}
class LogTask(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
override fun doWork(): Result {
updateNormalLog()
return Result.success()
}
}
}

View File

@ -0,0 +1,82 @@
package com.za.common.util
import android.content.Context
import android.os.Build
import android.os.Environment
import com.za.base.Const
import java.io.File
object AppFileManager {
//获取人脸大头照存储地址
fun getFaceDir(context: Context): File {
val path = context.cacheDir?.path + File.separator + "faceVerify"
val file = File(path)
if (!file.exists()) {
file.mkdir()
}
return file
}
/**
* 师傅签名位置保存
*/
fun getDriverSignDirPath(context : Context) : String {
val tempPath = context.cacheDir.toString() + File.separator + "driver_sign"
val tempFile = File(tempPath)
if (! tempFile.exists()) {
tempFile.mkdir()
}
return tempFile.absolutePath
}
/**
* 师傅签名位置保存
*/
fun getDriverSignPath(context : Context) : String {
return getDriverSignDirPath(context) + File.separator + Const.driverSighName
}
//人脸大头照存储地址
fun getFaceFilePath(context: Context): String {
return getFaceDir(context).absolutePath + File.separator + "avatar.jpg"
}
/**
* app日志路径
*/
fun getLogPath(context: Context?): String {
val path = "${context?.filesDir?.absolutePath}${File.separator}Log"
if (!File(path).exists()) {
File(path).mkdir()
}
return path
}
fun orderWaterMarkerPath(context: Context): String {
val path = context.cacheDir?.path + File.separator + "water_marker"
if (!File(path).exists()) {
File(path).mkdir()
}
return path
}
/**
* 拍摄的照片的保存位置
*/
fun getTakePictureParentPath(): String {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return Environment.DIRECTORY_PICTURES
} else {
val dstPath =
Environment.getExternalStorageDirectory().absolutePath + File.separator + Environment.DIRECTORY_PICTURES + File.separator + "中道救援"
val file = File(dstPath)
if (!file.exists()) {
file.mkdir()
}
return dstPath
}
}
}

View File

@ -0,0 +1,20 @@
package com.za.common.util;
import android.view.View;
public class ClickProxy implements View.OnClickListener {
private long lastClick = 0;
private final View.OnClickListener onClickListener;
public ClickProxy(View.OnClickListener onClickListener) {
this.onClickListener = onClickListener;
}
@Override
public void onClick(View v) {
if (System.currentTimeMillis() - lastClick >= 1000) {
onClickListener.onClick(v);
lastClick = System.currentTimeMillis();
}
}
}

View File

@ -0,0 +1,76 @@
package com.za.common.util;
import android.annotation.SuppressLint;
import android.content.Context;
import android.media.MediaDrm;
import android.os.Build;
import android.provider.Settings;
import com.blankj.utilcode.util.SPUtils;
import java.security.MessageDigest;
import java.util.UUID;
/**
* 设备唯一标识
* 顺序为Android_ID->DRM_ID->UUID
*/
public class DeviceUtil {
public static String getAndroidId(Context context) {
String deviceId = SPUtils.getInstance().getString("deviceId");
if (deviceId == null || deviceId.isEmpty()) {
@SuppressLint("HardwareIds") String temp = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
if (temp == null || temp.isEmpty() || temp.replace("0", "").isEmpty()) {
temp = getDrmId();
}
SPUtils.getInstance().put("deviceId", temp);
return temp;
}
return deviceId;
}
private static String getDrmId() {
String sRet = "";
UUID WIDEVINE_UUID = new UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L);
MediaDrm mediaDrm = null;
try {
mediaDrm = new MediaDrm(WIDEVINE_UUID);
byte[] widevineId = mediaDrm.getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID);
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(widevineId);
sRet = bytesToHex(md.digest()); //we convert byte[] to hex for our purposes
} catch (Exception e) {
//WIDEVINE is not available
} finally {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (null != mediaDrm) {
mediaDrm.close();
}
} else {
if (null != mediaDrm) {
mediaDrm.release();
}
}
}
if (sRet.isEmpty()) {
sRet = WIDEVINE_UUID.toString().replace("-", "");
}
return sRet;
}
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
private static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars);
}
}

View File

@ -0,0 +1,489 @@
package com.za.common.util
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.Typeface
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.VectorDrawable
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.text.format.DateUtils
import android.view.View
import android.widget.ImageView
import androidx.core.content.ContextCompat
import androidx.core.graphics.createBitmap
import com.amap.api.maps.model.BitmapDescriptor
import com.amap.api.maps.model.BitmapDescriptorFactory
import com.za.common.log.LogUtil
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/**
* 图片工具类
*
* @author
*/
object ImageUtil {
/**
* 设置水印图片在左上角
*
* @param context
* @param src
* @param watermark
* @param paddingLeft
* @param paddingTop
* @return
*/
fun createWaterMaskLeftTop(context : Context,
src : Bitmap?,
watermark : Bitmap,
paddingLeft : Int,
paddingTop : Int) : Bitmap? {
return createWaterMaskBitmap(src,
watermark,
paddingLeft,
dp2px(context, paddingTop.toFloat()))
}
private fun createWaterMaskBitmap(src : Bitmap?,
watermark : Bitmap,
paddingLeft : Int,
paddingTop : Int) : Bitmap? {
if (src == null) {
return null
}
val width = src.width
val height = src.height //创建一个bitmap
val newb = Bitmap.createBitmap(width,
height,
Bitmap.Config.ARGB_8888) // 创建一个新的和SRC长度宽度一样的位图 //将该图片作为画布
val canvas = Canvas(newb) //在画布 00坐标上开始绘制原始图片
canvas.drawBitmap(src, 0f, 0f, null) //在画布上绘制水印图片
canvas.drawBitmap(watermark, paddingLeft.toFloat(), paddingTop.toFloat(), null) // 保存
canvas.save() // 存储
canvas.restore()
return newb
}
/**
* 设置水印图片在右下角
*
* @param context
* @param src
* @param watermark
* @param paddingRight
* @param paddingBottom
* @return
*/
fun createWaterMaskRightBottom(context : Context?,
src : Bitmap,
watermark : Bitmap,
paddingRight : Int,
paddingBottom : Int) : Bitmap? {
return createWaterMaskBitmap(src,
watermark,
src.width - watermark.width - paddingRight,
src.height - watermark.height - paddingBottom)
}
/**
* 设置水印图片到右上角
*
* @param context
* @param src
* @param watermark
* @param paddingRight
* @param paddingTop
* @return
*/
fun createWaterMaskRightTop(context : Context?,
src : Bitmap,
watermark : Bitmap,
paddingRight : Int,
paddingTop : Int) : Bitmap? {
return createWaterMaskBitmap(src,
watermark,
src.width - watermark.width - paddingRight,
paddingTop)
}
/**
* 设置水印图片到左下角
*
* @param context
* @param src
* @param watermark
* @param paddingLeft
* @param paddingBottom
* @return
*/
fun createWaterMaskLeftBottom(context : Context?,
src : Bitmap,
watermark : Bitmap,
paddingLeft : Int,
paddingBottom : Int) : Bitmap? {
return createWaterMaskBitmap(src,
watermark,
paddingLeft,
src.height - watermark.height - paddingBottom)
}
/**
* 设置水印图片到中间
*
* @param src
* @param watermark
* @return
*/
fun createWaterMaskCenter(src : Bitmap, watermark : Bitmap) : Bitmap? {
return createWaterMaskBitmap(src,
watermark,
(src.width - watermark.width) / 2,
(src.height - watermark.height) / 2)
}
/**
* 给图片添加文字到左上角
*
* @param context
* @param bitmap
* @param text
* @return
*/
fun drawTextToLeftTop(context : Context,
bitmap : Bitmap,
text : String,
size : Int,
color : Int,
paddingLeft : Int,
paddingTop : Int) : Bitmap {
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.color = color
paint.textSize = size.toFloat()
paint.setTypeface(Typeface.createFromAsset(context.assets, "fonts/song.ttf"))
paint.isFakeBoldText = true
val bounds = Rect()
paint.getTextBounds(text, 0, text.length, bounds)
return drawTextToBitmap(context,
bitmap,
text,
paint,
bounds,
paddingLeft,
paddingTop + bounds.height())
}
/**
* 绘制文字到右下角
*
* @param context
* @param bitmap
* @param text
* @param size
* @param color
* @return
*/
fun drawTextToRightBottom(context : Context,
bitmap : Bitmap,
text : String,
size : Int,
color : Int,
paddingRight : Int,
paddingBottom : Int) : Bitmap {
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.color = color
paint.textSize = size.toFloat()
paint.setTypeface(Typeface.createFromAsset(context.assets, "fonts/song.ttf"))
paint.isFakeBoldText = true
val bounds = Rect()
paint.getTextBounds(text, 0, text.length, bounds)
return drawTextToBitmap(context,
bitmap,
text,
paint,
bounds,
bitmap.width - bounds.width() - paddingRight,
bitmap.height - paddingBottom)
}
/**
* 绘制文字到右上方
*
* @param context
* @param bitmap
* @param text
* @param size
* @param color
* @param paddingRight
* @param paddingTop
* @return
*/
fun drawTextToRightTop(context : Context,
bitmap : Bitmap,
text : String,
size : Int,
color : Int,
paddingRight : Int,
paddingTop : Int) : Bitmap {
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.color = color
paint.textSize = size.toFloat()
paint.setTypeface(Typeface.createFromAsset(context.assets, "fonts/song.ttf"))
paint.isFakeBoldText = true
val bounds = Rect()
paint.getTextBounds(text, 0, text.length, bounds)
return drawTextToBitmap(context,
bitmap,
text,
paint,
bounds,
bitmap.width - bounds.width() - paddingRight,
paddingTop + bounds.height())
}
/**
* 绘制文字到左下方
*
* @param context
* @param bitmap
* @param text
* @param size
* @param color
* @param paddingLeft
* @param paddingBottom
* @return
*/
fun drawTextToLeftBottom(context : Context,
bitmap : Bitmap,
text : String,
size : Int,
color : Int,
paddingLeft : Int,
paddingBottom : Int) : Bitmap {
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.color = color
paint.textSize = size.toFloat()
paint.setTypeface(Typeface.createFromAsset(context.assets, "fonts/song.ttf"))
paint.isFakeBoldText = true
val bounds = Rect()
paint.getTextBounds(text, 0, text.length, bounds)
return drawTextToBitmap(context,
bitmap,
text,
paint,
bounds,
paddingLeft,
bitmap.height - paddingBottom)
}
/**
* 绘制文字到中间
*
* @param context
* @param bitmap
* @param text
* @param size
* @param color
* @return
*/
fun drawTextToCenter(context : Context,
bitmap : Bitmap,
text : String,
size : Int,
color : Int) : Bitmap {
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.color = color
paint.textSize = size.toFloat()
paint.setTypeface(Typeface.createFromAsset(context.assets, "fonts/song.ttf"))
paint.isFakeBoldText = true
val bounds = Rect()
paint.getTextBounds(text, 0, text.length, bounds)
return drawTextToBitmap(context,
bitmap,
text,
paint,
bounds,
(bitmap.width - bounds.width()) / 2,
(bitmap.height + bounds.height()) / 2)
}
//图片上绘制文字
private fun drawTextToBitmap(context : Context,
myBitmap : Bitmap,
text : String,
paint : Paint,
bounds : Rect,
paddingLeft : Int,
paddingTop : Int) : Bitmap {
val bitmapConfig = myBitmap.config
paint.isDither = true // 获取跟清晰的图像采样
paint.isFilterBitmap = true // 过滤一些
val bitmap = myBitmap.copy(bitmapConfig !!, true)
val canvas = Canvas(bitmap)
canvas.drawText(text, paddingLeft.toFloat(), paddingTop.toFloat(), paint)
return bitmap
}
/**
* 缩放图片
*
* @param src
* @param w
* @param h
* @return
*/
fun scaleWithWH(src : Bitmap?, w : Double, h : Double) : Bitmap? {
if (w == 0.0 || h == 0.0 || src == null) {
return src
} else { // 记录src的宽高
val width = src.width
val height = src.height // 创建一个matrix容器
val matrix = Matrix() // 计算缩放比例
val scaleWidth = (w / width).toFloat()
val scaleHeight = (h / height).toFloat() // 开始缩放
matrix.postScale(scaleWidth, scaleHeight) // 创建缩放后的图片
return Bitmap.createBitmap(src, 0, 0, width, height, matrix, true)
}
}
/**
* dip转pix
*
* @param context
* @param dp
* @return
*/
private fun dp2px(context : Context, dp : Float) : Int {
val scale = context.resources.displayMetrics.density
return (dp * scale + 0.5f).toInt()
}
// view转bitmap
fun createBitmapFromView(view : View) : Bitmap? { //是ImageView直接获取
if (view is ImageView) {
val drawable = view.drawable
if (drawable is BitmapDrawable) {
return drawable.bitmap
}
}
view.clearFocus()
val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
view.draw(canvas)
canvas.setBitmap(null)
return bitmap
}
// 保存图片到相册
fun saveImage(context : Context, bitmap : Bitmap?) {
if (bitmap == null) {
return
}
val isSaveSuccess = if (Build.VERSION.SDK_INT < 29) {
saveImageToGallery(context, bitmap)
} else {
saveImageToGallery1(context, bitmap)
}
if (isSaveSuccess) {
LogUtil.print("照片保存到相册成功", "success")
} else {
LogUtil.print("照片保存到相册失败", "failed")
}
}
/**
* android 10 以下版本
*/
private fun saveImageToGallery(context : Context, image : Bitmap) : Boolean { // 首先保存图片
val storePath =
Environment.getExternalStorageDirectory().absolutePath + File.separator + "中道救援"
val appDir = File(storePath)
if (! appDir.exists()) {
appDir.mkdir()
}
val fileName = System.currentTimeMillis().toString() + ".jpg"
val file = File(appDir, fileName)
try {
val fos = FileOutputStream(file) // 通过io流的方式来压缩保存图片
val isSuccess = image.compress(Bitmap.CompressFormat.JPEG, 60, fos)
fos.flush()
fos.close()
// 保存图片后发送广播通知更新数据库
val uri = Uri.fromFile(file)
context.sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri))
return isSuccess
} catch (e : IOException) {
e.printStackTrace()
}
return false
}
/**
* android 10 以上版本
*/
private fun saveImageToGallery1(context : Context, image : Bitmap) : Boolean {
val mImageTime = System.currentTimeMillis()
val imageDate =
SimpleDateFormat("yyyyMMdd-HHmmss", Locale.getDefault()).format(Date(mImageTime))
val SCREENSHOT_FILE_NAME_TEMPLATE = "zd_%s.png" //图片名称,以"zd"+时间戳命名
val mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate)
val values = ContentValues()
values.put(MediaStore.MediaColumns.RELATIVE_PATH,
Environment.DIRECTORY_PICTURES + File.separator + "中道救援") //Environment.DIRECTORY_SCREENSHOTS:截图,图库中显示的文件夹名
values.put(MediaStore.MediaColumns.DISPLAY_NAME, mImageFileName)
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
values.put(MediaStore.MediaColumns.DATE_ADDED, mImageTime / 1000)
values.put(MediaStore.MediaColumns.DATE_MODIFIED, mImageTime / 1000)
values.put(MediaStore.MediaColumns.DATE_EXPIRES,
(mImageTime + DateUtils.DAY_IN_MILLIS) / 1000)
values.put(MediaStore.MediaColumns.IS_PENDING, 1)
val resolver = context.contentResolver
val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
try { // First, write the actual data for our screenshot
resolver.openOutputStream(uri !!).use { out ->
if (! image.compress(Bitmap.CompressFormat.PNG, 100, out !!)) {
return false
}
} // Everything went well above, publish it!
values.clear()
values.put(MediaStore.MediaColumns.IS_PENDING, 0)
values.putNull(MediaStore.MediaColumns.DATE_EXPIRES)
resolver.update(uri, values, null, null)
} catch (e : IOException) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
resolver.delete(uri !!, null)
}
return false
}
return true
}
fun vectorToBitmap(context : Context, vectorResId : Int) : BitmapDescriptor {
val vectorDrawable = ContextCompat.getDrawable(context, vectorResId) as VectorDrawable
val bitmap = createBitmap(vectorDrawable.intrinsicWidth, vectorDrawable.intrinsicHeight)
val canvas = Canvas(bitmap)
vectorDrawable.setBounds(0, 0, canvas.width, canvas.height)
vectorDrawable.draw(canvas)
return BitmapDescriptorFactory.fromBitmap(bitmap)
}
}

View File

@ -0,0 +1,101 @@
package com.za.common.util
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import com.amap.api.maps.model.LatLng
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.ln
import kotlin.math.sin
import kotlin.math.sqrt
object MapUtil {
const val PN_GAODE_MAP: String = "com.autonavi.minimap" // 高德地图包名
const val PN_BAIDU_MAP: String = "com.baidu.BaiduMap" // 百度地图包名
const val PN_TENCENT_MAP: String = "com.tencent.map" // 百度地图包名
/**
* 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换
* 即谷歌、高德 转 百度
*
* @param latLng
* @returns
*/
fun gcj02ToBD09(latLng: LatLng): LatLng {
val xPI = 3.141592653589793 * 3000.0 / 180.0
val z = sqrt(latLng.longitude * latLng.longitude + latLng.latitude * latLng.latitude) + 0.00002 * sin(latLng.latitude * xPI)
val theta = atan2(latLng.latitude, latLng.longitude) + 0.000003 * cos(latLng.longitude * xPI)
val bdLat = z * sin(theta) + 0.006
val bdLng = z * cos(theta) + 0.0065
return LatLng(bdLat, bdLng)
}
/**
* 检查应用是否安装
*
* @return
*/
fun isGdMapInstalled(context: Context): Boolean {
return isInstallPackage(context, PN_GAODE_MAP)
}
fun isBaiduMapInstalled(context: Context): Boolean {
return isInstallPackage(context, PN_BAIDU_MAP)
}
fun isTencentInstalled(context: Context): Boolean {
return isInstallPackage(context, PN_TENCENT_MAP)
}
private fun isInstallPackage(context: Context, packageName: String): Boolean {
try {
val info = context.packageManager
.getApplicationInfo(packageName,
PackageManager.GET_UNINSTALLED_PACKAGES)
return true
} catch (e: PackageManager.NameNotFoundException) {
return false
}
}
//开启高德导航
fun startNavigationGd(context: Context, lat: Double?, lng: Double?, address: String?) {
val stringBuffer = "androidamap://route?sourceApplication=" + "amap" +
"&dlat=" + lat +
"&dlon=" + lng +
"&dname=" + address +
"&dev=" + 0 +
"&t=" + 0
val intent = Intent("android.intent.action.VIEW", Uri.parse(stringBuffer))
intent.setPackage("com.autonavi.minimap")
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}
//开启高德导航
fun startNavigationBd(context: Context, lat: Double?, lng: Double?, address: String?) {
val i1 = Intent("android.intent.action.VIEW")
i1.setData(Uri.parse("baidumap://map/navi?query=${address}&mode=driving&location=${lat},${lng}&coord_type=gcj02&src=com.za.servicing"))
context.startActivity(i1)
}
//开启高德导航
fun startNavigationTencent(context: Context, lat: Double?, lng: Double?, address: String?) {
val stringBuffer = "androidamap://route?sourceApplication=" + "amap" +
"&dlat=" + lat +
"&dlon=" + lng +
"&dname=" + address +
"&dev=" + 0 +
"&t=" + 0
val intent = Intent("android.intent.action.VIEW", Uri.parse(stringBuffer))
intent.setPackage("com.autonavi.minimap")
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}
}

View File

@ -0,0 +1,5 @@
package com.za.common.util
object NotificationUtil {
const val CHANNEL_ID = "1001"
}

View File

@ -0,0 +1,92 @@
package com.za.common.util
import android.graphics.Bitmap
import android.graphics.Color
import android.text.TextUtils
import androidx.annotation.ColorInt
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.WriterException
import com.google.zxing.qrcode.QRCodeWriter
import java.util.Hashtable
/**
* @ClassName: QRCodeUtil
* @Description: 二维码工具类
*/
object QRCodeUtil {
/**
* 创建二维码位图 (支持自定义配置和自定义样式)
*
* @param content 字符串内容
* @param width 位图宽度,要求>=0(单位:px)
* @param height 位图高度,要求>=0(单位:px)
* @param character_set 字符集/字符转码格式 (支持格式:[CharacterSetECI])。传null时,zxing源码默认使用 "ISO-8859-1"
* @param error_correction 容错级别 (支持级别:[ErrorCorrectionLevel])。传null时,zxing源码默认使用 "L"
* @param margin 空白边距 (可修改,要求:整型且>=0), 传null时,zxing源码默认使用"4"。
* @param color_black 黑色色块的自定义颜色值
* @param color_white 白色色块的自定义颜色值
* @return
*/
/**
* 创建二维码位图
*
* @param content 字符串内容(支持中文)
* @param width 位图宽度(单位:px)
* @param height 位图高度(单位:px)
* @return
*/
@JvmOverloads
fun createQRCodeBitmap(content: String?, width: Int, height: Int,
characterSet: String? = "UTF-8", errorCorrection: String? = "H", margin: String? = "2",
@ColorInt colorBlack: Int = Color.BLACK, @ColorInt colorWhite: Int = Color.WHITE): Bitmap? {
/** 1.参数合法性判断 */
if (TextUtils.isEmpty(content)) { // 字符串内容判空
return null
}
if (width < 0 || height < 0) { // 宽和高都需要>=0
return null
}
try {
/** 2.设置二维码相关配置,生成BitMatrix(位矩阵)对象 */
val hints = Hashtable<EncodeHintType, String?>()
if (!TextUtils.isEmpty(characterSet)) {
hints[EncodeHintType.CHARACTER_SET] = characterSet // 字符转码格式设置
}
if (!TextUtils.isEmpty(errorCorrection)) {
hints[EncodeHintType.ERROR_CORRECTION] = errorCorrection // 容错级别设置
}
if (!TextUtils.isEmpty(margin)) {
hints[EncodeHintType.MARGIN] = margin // 空白边距设置
}
val bitMatrix = QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints)
/** 3.创建像素数组,并根据BitMatrix(位矩阵)对象为数组元素赋颜色值 */
val pixels = IntArray(width * height)
for (y in 0 until height) {
for (x in 0 until width) {
if (bitMatrix[x, y]) {
pixels[y * width + x] = colorBlack // 黑色色块像素设置
} else {
pixels[y * width + x] = colorWhite // 白色色块像素设置
}
}
}
/** 4.创建Bitmap对象,根据像素数组设置Bitmap每个像素点的颜色值,之后返回Bitmap对象 */
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
bitmap.setPixels(pixels, 0, width, 0, 0, width, height)
return bitmap
} catch (e: WriterException) {
e.printStackTrace()
}
return null
}
}

View File

@ -0,0 +1,53 @@
package com.za.common.util
import android.content.Context
import android.media.MediaPlayer
import com.za.servicing.R
object ServicingSpeechManager {
private var mediaPlayer: MediaPlayer? = null
// 提醒客户签字
fun playOrderCustomSign(mContext: Context) {
mediaPlayer = MediaPlayer.create(mContext, R.raw.custom_sign)
mediaPlayer?.start()
}
// 提醒接车人签字
fun playOrderAcceptSign(mContext: Context) {
mediaPlayer = MediaPlayer.create(mContext, R.raw.accept_sign)
mediaPlayer?.start()
}
// 车主签字
fun playCarOwnerSign(mContext: Context) {
mediaPlayer = MediaPlayer.create(mContext, R.raw.car_onwer_sign)
mediaPlayer?.start()
}
// 提醒五星好评
fun playOrderGoodService(mContext: Context) {
mediaPlayer = MediaPlayer.create(mContext, R.raw.good_star)
mediaPlayer?.start()
}
//提醒客户不需要再上传纸质工单
fun playNoUploadEleOrderWork(mContext: Context) {
mediaPlayer = MediaPlayer.create(mContext, R.raw.no_upload_ele_order_work)
mediaPlayer?.start()
}
// 发车前提示
fun playStartTip(mContext: Context) {
mediaPlayer = MediaPlayer.create(mContext, R.raw.start_tip)
mediaPlayer?.start()
}
// 验车前提示
fun playCheckTip(mContext: Context) {
mediaPlayer = MediaPlayer.create(mContext, R.raw.check_tip)
mediaPlayer?.start()
}
}

View File

@ -0,0 +1,33 @@
package com.za.common.util
import android.util.Base64
import java.nio.charset.StandardCharsets
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
/**
* @author DoggieX
* @create 2021/3/22 16:17
* @mail coldpuppy@163.com
*/
object Tools {
/**
* @param text 要签名的文本
* @param secretKey 阿里云MQ secretKey
* @return 加密后的字符串
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
*/
@Throws(InvalidKeyException::class, NoSuchAlgorithmException::class)
fun macSignature(text: String, secretKey: String): String {
val charset = StandardCharsets.UTF_8
val algorithm = "HmacSHA1"
val mac = Mac.getInstance(algorithm)
mac.init(SecretKeySpec(secretKey.toByteArray(charset), algorithm))
val bytes = mac.doFinal(text.toByteArray(charset))
// android的base64编码注意换行符情况, 使用NO_WRAP
return String(Base64.encode(bytes, Base64.NO_WRAP), charset)
}
}

View File

@ -0,0 +1,53 @@
package com.za.ext
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import androidx.activity.ComponentActivity
import androidx.core.app.ActivityCompat
import com.blankj.utilcode.util.ToastUtils
fun ComponentActivity.navigationActivity(destActivity: Class<*>) {
ActivityCompat.startActivity(this, Intent(this, destActivity), null)
}
fun ComponentActivity.navigationActivity(destActivity: Class<*>, requestCode: Int) {
ActivityCompat.startActivityForResult(this, Intent(this, destActivity), requestCode, null)
}
fun ComponentActivity.navigationActivity(intent: Intent, requestCode: Int) {
ActivityCompat.startActivityForResult(this, intent, requestCode, null)
}
fun ComponentActivity.navigationActivity(destActivity: Class<*>, isFinish: Boolean? = false) {
ActivityCompat.startActivity(this, Intent(this, destActivity), null)
if (isFinish == true) {
this.finish()
}
}
fun ComponentActivity.navigationActivity(destActivity: Class<*>, key: String, value: String?) {
val intent = Intent(this, destActivity)
intent.putExtra(key, value)
ActivityCompat.startActivity(this, intent, null)
}
fun ComponentActivity.callPhone(phone: String?) {
if (phone.isNullOrBlank()) {
ToastUtils.showShort("号码错误!")
// LogUtil.print("call phone","号码错误=$phone")
return
}
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_DENIED) {
ToastUtils.showShort("请先允许通话权限!")
// LogUtil.print("call phone","拨打电话权限未允许")
return
}
val intentPhone = Intent(Intent.ACTION_DIAL, Uri.parse("tel:$phone"))
ActivityCompat.startActivity(this, intentPhone, null)
}

View File

@ -0,0 +1,98 @@
package com.za.ext
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import com.blankj.utilcode.util.ToastUtils
import com.za.call.CallLogManager
import com.za.call.ContactRecordBean
import com.za.common.GlobalData
import com.za.common.log.LogUtil
fun Context.navigationActivity(destActivity : Class<*>) {
if (this !is Activity) {
return
}
ActivityCompat.startActivity(this, Intent(this, destActivity), null)
}
fun Context.navigationActivity(destActivity : Class<*>, requestCode : Int) {
if (this !is Activity) {
return
}
ActivityCompat.startActivityForResult(this, Intent(this, destActivity), requestCode, null)
}
fun Context.navigationActivity(intent : Intent, requestCode : Int) {
if (this !is Activity) {
return
}
ActivityCompat.startActivityForResult(this, intent, requestCode, null)
}
fun Context.navigationActivity(destActivity : Class<*>, isFinish : Boolean? = false) {
if (this !is Activity) {
return
}
ActivityCompat.startActivity(this, Intent(this, destActivity), null)
if (isFinish == true) {
this.finish()
}
}
fun Context.navigationActivity(destActivity : Class<*>, key : String, value : String?) {
if (this !is Activity) {
return
}
val intent = Intent(this, destActivity)
intent.putExtra(key, value)
ActivityCompat.startActivity(this, intent, null)
}
fun Context.finish() {
if (this !is Activity) {
return
}
this.finish()
}
fun AppCompatActivity.navigationActivity(destActivity : Class<*>, key : String, value : String?) {
val intent = Intent(this, destActivity)
intent.putExtra(key, value)
ActivityCompat.startActivity(this, intent, null)
}
fun Context.callPhone(phone : String?) {
if (this !is Activity) {
return
}
if (phone.isNullOrBlank()) {
ToastUtils.showShort("号码错误!")
LogUtil.print("call phone", "号码错误=$phone")
return
}
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_DENIED
) {
ToastUtils.showShort("请先允许通话权限!")
LogUtil.print("call phone", "拨打电话权限未允许")
return
}
//如果是联系客户,则记录通话记录
if (GlobalData.currentOrder != null && GlobalData.currentOrder?.customerPhone == phone) {
CallLogManager.phoneCallContactBean =
ContactRecordBean(taskId = GlobalData.currentOrder?.taskId,
taskCode = GlobalData.currentOrder?.taskCode,
phone = phone)
}
val intentPhone = Intent(Intent.ACTION_DIAL, Uri.parse("tel:$phone"))
ActivityCompat.startActivity(this, intentPhone, null)
}

View File

@ -0,0 +1,22 @@
package com.za.ext
import androidx.compose.foundation.clickable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.za.base.Const
@Composable
fun Modifier.noDoubleClick(onClick: () -> Unit): Modifier {
var lastClickTime by remember { mutableLongStateOf(value = 0L) }
return clickable(enabled = true) {
val currentTimeMillis = System.currentTimeMillis()
if (currentTimeMillis - Const.DoubleClickTime >= lastClickTime) {
onClick()
lastClickTime = currentTimeMillis
}
}
}

View File

@ -0,0 +1,139 @@
package com.za.ext
import android.content.Context
import com.alibaba.fastjson.JSON
import com.blankj.utilcode.util.EncodeUtils
import com.za.base.AppConfig
import com.za.bean.db.order.OrderInfo
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.room.RoomHelper
import com.za.ui.servicing.check_vehicle.CheckVehicleActivity
import com.za.ui.servicing.departure_photo.DeparturePhotoActivity
import com.za.ui.servicing.destination_photo.DestinationPhotoActivity
import com.za.ui.servicing.go_accident.GoAccidentSiteActivity
import com.za.ui.servicing.go_to_destination.GoToDestinationActivity
import com.za.ui.servicing.operation.InOperationActivity
import com.za.ui.servicing.order_confirm.OrderConfirmActivity
import com.za.ui.servicing.verify.VerifyOrderActivity
import com.za.ui.servicing.wait_to_start.WaitToStartActivity
import java.nio.charset.StandardCharsets
fun Any?.toJson() : String? {
if (this == null) {
return null
}
return JSON.toJSON(this).toString()
}
fun OrderInfo.goStatusPage(context : Context) {
when (this.taskState) { //等待发车
"START" -> {
if (! RoomHelper.db?.photoTemplateDao()
?.getOrderPhotoTemplateFromTaskNode(10100, userOrderId ?: 0).isNullOrEmpty()
) {
context.navigationActivity(DeparturePhotoActivity::class.java)
return
}
context.navigationActivity(WaitToStartActivity::class.java)
}
"GOTO" -> context.navigationActivity(GoAccidentSiteActivity::class.java) //验证服务资格
"VERIFY" -> context.navigationActivity(VerifyOrderActivity::class.java) //检查车辆
"EXAMINE" -> context.navigationActivity(CheckVehicleActivity::class.java) //作业中
"OPERATION" -> context.navigationActivity(InOperationActivity::class.java) //前往目的地
"SENDTO" -> context.navigationActivity(GoToDestinationActivity::class.java) //目的地照片
"DESTPHOTO" -> context.navigationActivity(DestinationPhotoActivity::class.java) //结算中
"SETTLEMENT" -> context.navigationActivity(OrderConfirmActivity::class.java) //任务结束
"FINISH" -> context.navigationActivity(WaitToStartActivity::class.java)
}
}
fun OrderInfo.getNextStatus() : String? {
val flowStr : String?
val flowArray = flow?.split(",")
if (flowArray.isNullOrEmpty()) {
LogUtil.print("getNextStatus", "订单流程为空")
return null
}
flowStr = flowArray[flowArray.indexOf(this.taskState) + 1]
LogUtil.print("getNextStatus flowStr", "$flowStr")
return flowStr
}
fun goNextPage(status : String?, context : Context) {
when (status) { //等待发车
"START" -> {
if (! RoomHelper.db?.photoTemplateDao()?.getOrderPhotoTemplateFromTaskNode(10100,
GlobalData.currentOrder?.userOrderId ?: 0).isNullOrEmpty()
) {
context.navigationActivity(DeparturePhotoActivity::class.java)
return
}
context.navigationActivity(WaitToStartActivity::class.java)
} //前往事发地
"GOTO" -> context.navigationActivity(GoAccidentSiteActivity::class.java) //验证服务资格
"VERIFY" -> context.navigationActivity(VerifyOrderActivity::class.java) //检查车辆
"EXAMINE" -> context.navigationActivity(CheckVehicleActivity::class.java) //作业中
"OPERATION" -> context.navigationActivity(InOperationActivity::class.java) //前往目的地
"SENDTO" -> context.navigationActivity(GoToDestinationActivity::class.java) //目的地照片
"DESTPHOTO" -> context.navigationActivity(DestinationPhotoActivity::class.java) //结算中
"SETTLEMENT" -> context.navigationActivity(OrderConfirmActivity::class.java) //任务结束
"FINISH" -> context.navigationActivity(WaitToStartActivity::class.java)
}
}
fun OrderInfo.getTaskNode() : Int { //防空的节点为 18100
return when (this.taskState) { //检查车辆
"EXAMINE" -> 13001 //作业中
"OPERATION" -> 15001 //目的地照片
"DESTPHOTO" -> 17001 //结算中
"SETTLEMENT" -> 18001
else -> 0
}
}
fun OrderInfo.convertToFlowName() : String {
return when (this.taskState) {
"START" -> "等待发车"
"GOTO" -> "前往事发地"
"VERIFY" -> "验证服务资格"
"EXAMINE" -> "检查车辆"
"OPERATION" -> "作业中"
"SENDTO", "SIMPLE_CARRYING" -> "前往目的地"
"DESTPHOTO", "SIMPLE_DEST_PHOTO" -> "目的地照片"
"SIMPLE_SETTLEMENT", "SETTLEMENT" -> "结算中"
"FINISH" -> "任务结束"
"SIMPLE_ARRIVE_PHOTO" -> "现场照片"
"SIMPLE_CARRY_PRE" -> "开始拖车"
"SIMPLE_DESP_PHOTO" -> "完成确认"
else -> "继续任务"
}
}
fun OrderInfo.getEleState() : Int {
if (RoomHelper.db?.eleWorkOrderDao()?.getEleWorkOrder(taskId ?: 0) == null) {
LogUtil.print("作业中获取电子表单未空", "电子表单为空")
return electronOrderState ?: 0
}
if (electronOrderState == RoomHelper.db?.eleWorkOrderDao()
?.getEleWorkOrder(taskId ?: 0)?.orderWorkStatus
) {
return electronOrderState ?: 0
}
return (electronOrderState ?: 0).coerceAtLeast(RoomHelper.db?.eleWorkOrderDao()
?.getEleWorkOrder(taskId ?: 0)?.orderWorkStatus ?: 0)
}
fun OrderInfo.getEleOrderH5Url() : String? {
if (GlobalData.currentOrder == null) {
return null
}
val queryParams =
"userOrderId=${GlobalData.currentOrder?.userOrderId}&userOrderCode=${GlobalData.currentOrder?.taskCode}&supplierId=${GlobalData.currentOrder?.userOrderId}"
return AppConfig.Resource_URL + "/electronicWorkOrder/index.html?" + EncodeUtils.base64Encode2String(
queryParams.toByteArray(StandardCharsets.UTF_8))
}

View File

@ -0,0 +1,27 @@
package com.za.ext
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Context.CLIPBOARD_SERVICE
import com.blankj.utilcode.util.ToastUtils
fun String.taskStateToFlow() : String {
return when (this) {
"13001" -> "EXAMINE"
"18001" -> "FINISH"
"15001" -> "OPERATION"
"17001" -> "DESTPHOTO"
"18100" -> "GIVEUP"
else -> ""
}
}
fun String?.copy(context : Context) {
if (this.isNullOrBlank()) {
return
}
val clipboardManager = context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
clipboardManager.setPrimaryClip(ClipData.newPlainText("", this))
ToastUtils.showLong("复制成功")
}

View File

@ -0,0 +1,123 @@
package com.za.net;
import android.util.Base64;
import java.nio.charset.StandardCharsets;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class AESUtils {
private static final String cipherMode = "AES/ECB/PKCS5Padding";//算法/模式/补码方式
/* AES秘钥支持128bit/192bit/256bit三种长度的秘钥一个字节等于8bit
* 因此支持生成的字符串的长度应该是 16/24/32
* */
private static final int keyLength = 16;
// public static void main(String[] args) {
//
// /*构建一个随机密码*/
// String key = getRandomKey(keyLength);
// System.out.println("随机生成的key" + key);
//
// String data = "{'fig':1,'message':'登录成功'}";
//
// try {
// String encriptData = AESUtils.encrypt(data, key);
// System.out.println("加密后的数据:" + encriptData);
//
// String decryptData = decrypt(encriptData, key);
//
// System.out.println("解密后的数据:" + decryptData);
//
// } catch (Exception e) {
// e.printStackTrace();
// }
//
// }
/**
* @param length 需要生成的字符串长度
* @return 随机生成的字符串
*/
public static String getRandomKey(int length) {
if (length != 16 && length != 24 && length != 32) {
System.out.println("长度必须为16/24/32");
return null;
}
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < length; i++) {
int number = random.nextInt(62);
stringBuilder.append(str.charAt(number));
}
return stringBuilder.toString();
}
/**
* @param data 需要加密的数据
* @param key 加密使用的key
* @return 加密后的数据(Base64编码)
* @throws Exception
*/
public static String encrypt(String data, String key) throws Exception {
int length = key.length();
if (length != 16 && length != 24 && length != 32) {
System.out.println("长度必须为16/24/32");
return null;
}
byte[] raw = key.getBytes(StandardCharsets.UTF_8);
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance(cipherMode);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.encodeToString(encrypted, Base64.URL_SAFE | Base64.NO_WRAP);
}
/**
* @param data 需要解密的数据
* @param key 解密用的key
* @return 解密后的数据
* @throws Exception
*/
public static String decrypt(String data, String key) throws Exception {
try {
int length = key.length();
if (length != 16 && length != 24 && length != 32) {
System.out.println("长度必须为16/24/32");
return null;
}
byte[] raw = key.getBytes(StandardCharsets.UTF_8);
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance(cipherMode);
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] encrypted = Base64.decode(data,Base64.URL_SAFE | Base64.NO_WRAP);//先用base64解密
try {
byte[] original = cipher.doFinal(encrypted);
return new String(original, StandardCharsets.UTF_8);
} catch (Exception e) {
System.out.println(e.toString());
return null;
}
} catch (Exception ex) {
System.out.println(ex.toString());
return null;
}
}
}

View File

@ -0,0 +1,265 @@
package com.za.net
import com.za.bean.BaseResponse
import com.za.bean.BatteryCostQueryBean
import com.za.bean.BatteryCostQueryRequest
import com.za.bean.ChangeBatteryResponse
import com.za.bean.DriverIdentityAuthWebBean
import com.za.bean.DriverIdentityAuthWebRequest
import com.za.bean.DriverInfo
import com.za.bean.FetchChangeBatteryPhotoRequest
import com.za.bean.GeneralInfo
import com.za.bean.HistoryPhotoTemplates
import com.za.bean.HistoryTaskBean
import com.za.bean.ImageBean
import com.za.bean.JpushBean
import com.za.bean.LoginWithTaskBean
import com.za.bean.LoginWithTaskRequest
import com.za.bean.NewOrderRequestBean
import com.za.bean.PaymentInfoBean
import com.za.bean.ReportHistoryBean
import com.za.bean.ReportHistoryRequest
import com.za.bean.ReportInfoRequest
import com.za.bean.ReportItem
import com.za.bean.SettleInfoRequest
import com.za.bean.TaskSettlementAndTraceBean
import com.za.bean.UpdateVersionBean
import com.za.bean.UpdateVersionRequest
import com.za.bean.UploadChangeBatteryRequest
import com.za.bean.VehicleInfo
import com.za.bean.db.order.OrderInfo
import com.za.bean.db.order.PhotoTemplateInfo
import com.za.bean.request.AcceptOrderRequest
import com.za.bean.request.CustomerPaymentCreateBean
import com.za.bean.request.CustomerPaymentCreateRequest
import com.za.bean.request.DriverFaceCompareBean
import com.za.bean.request.DriverFaceCompareRequest
import com.za.bean.request.ElectronOrderResponse
import com.za.bean.request.FetchVehicleMaintenanceSubmitHistoryRequestBean
import com.za.bean.request.GeneralInfoRequest
import com.za.bean.request.GiveUpTaskRequest
import com.za.bean.request.HistoryDetailRequest
import com.za.bean.request.HistoryPhotoTemplateRequest
import com.za.bean.request.HistoryTasksRequest
import com.za.bean.request.LoginRequest
import com.za.bean.request.OrderListRequest
import com.za.bean.request.OrderPhotoOcrRecognizeRequest
import com.za.bean.request.PaymentInfoRequest
import com.za.bean.request.PaymentUpdateRequest
import com.za.bean.request.PhotoTemplateRequest
import com.za.bean.request.QueryEleOrderRequest
import com.za.bean.request.RecognizeRefuelOcrRequestBean
import com.za.bean.request.RecognizeRefuelTicketBean
import com.za.bean.request.RecognizeRefuelTicketRequestBean
import com.za.bean.request.RefuseOrderRequest
import com.za.bean.request.SaveEleOrderRequest
import com.za.bean.request.SwitchTaskRequest
import com.za.bean.request.TaskFinishRequest
import com.za.bean.request.TaskFinishResponse
import com.za.bean.request.TodayMaintainRequest
import com.za.bean.request.TodayMaintainUploadRequest
import com.za.bean.request.TodayMaintainbean
import com.za.bean.request.UpdateOrderConfirmTaskRequest
import com.za.bean.request.UpdatePhotoRequest
import com.za.bean.request.UpdateTaskBean
import com.za.bean.request.UpdateTaskRequest
import com.za.bean.request.UpdateVehicleStateRequest
import com.za.bean.request.UploadGpsRequest
import com.za.bean.request.UploadPhotoBean
import com.za.bean.request.VehicleListRequest
import com.za.bean.request.VehicleLogoutRequest
import com.za.bean.request.VehicleMaintenanceHistoryBean
import com.za.bean.request.VehicleMaintenanceSubmitRequest
import com.za.bean.request.VehicleRepairBean
import com.za.bean.request.VehicleRepairPointMatcherItem
import com.za.bean.request.VehicleRepairPointMatcherListRequest
import com.za.bean.request.VerifyCodeRequest
import com.za.bean.request.VerifyCodeResponse
import com.za.call.ContactRecordRequest
import com.za.water_marker.bean.MyWaterMarkerTemplateBean
import com.za.water_marker.bean.WaterMarkerRequestBean
import io.reactivex.rxjava3.core.Observable
import okhttp3.MultipartBody
import retrofit2.http.Body
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
interface ApiService {
//获取车辆列表
@POST("/driverApp/supplier/listVehicleNew")
fun getVehicleList(@Body info : VehicleListRequest) : Observable<BaseResponse<List<VehicleInfo>>>
//更改车辆状态
@POST("/driverApp/task/switchVehicleStatus")
fun newUpdateVehicleState(@Body info : UpdateVehicleStateRequest) : Observable<BaseResponse<String>>
@POST("/driverApp/v2/user/generalInfo")
fun generalInfo(@Body info : GeneralInfoRequest) : Observable<BaseResponse<GeneralInfo>>
@POST("/driverApp/base/appVersion")
fun getUpdate(@Body versionRequest : UpdateVersionRequest) : Observable<BaseResponse<UpdateVersionBean>>
@POST("/driverApp/base/getVerifyCode")
fun getVerifyCode(@Body verifyCodeRequest : VerifyCodeRequest) : Observable<BaseResponse<VerifyCodeResponse>>
@Multipart
@POST("/order/uploadImage")
fun uploadImage(@Part part : MultipartBody.Part) : Observable<ImageBean>
@POST("/driverApp/task/uploadContactRecord")
fun uploadContactRecord(@Body contactRecordRequest : ContactRecordRequest?) : Observable<BaseResponse<String>>
@POST("/driverApp/task/login")
fun login(@Body info : LoginRequest) : Observable<BaseResponse<DriverInfo?>>
@POST("/driverApp/task/loginWithTask")
fun loginWithTask(@Body loginWithTaskRequest : LoginWithTaskRequest) : Observable<BaseResponse<LoginWithTaskBean>>
@POST("/driverApp/supplier/vehicleLogout")
fun vehicleLogout(@Body info : VehicleLogoutRequest) : Observable<BaseResponse<String>>
//当前订单列表
@POST("/driverApp/v2/task/listCurrentTask")
fun queryOrderList(@Body info : OrderListRequest) : Observable<BaseResponse<List<OrderInfo>?>>
@POST("/driverApp/task/getNewOrder")
fun getNewOrder(@Body newOrderRequestBean : NewOrderRequestBean) : Observable<BaseResponse<JpushBean>>
@POST("/driverApp/supplier/uploadGps")
fun uploadGps(@Body info : UploadGpsRequest) : Observable<BaseResponse<String>>
//获取报备内容列表
@POST("/driverApp/base/getReportTemplates")
fun getReportTemplates() : Observable<BaseResponse<List<ReportItem>>>
//提交报备
@POST("/driverApp/task/submitReport")
fun submitReport(@Body reportInfoRequest : ReportInfoRequest) : Observable<BaseResponse<String>>
//历史报备内容
@POST("/driverApp/task/reportHistory")
fun getReportHistory(@Body reportHistoryRequest : ReportHistoryRequest) : Observable<BaseResponse<List<ReportHistoryBean>>>
//拒绝任务
@POST("/driverApp/task/refuseTask")
fun refuseOrder(@Body request : RefuseOrderRequest) : Observable<BaseResponse<String>>
//接受任务
@POST("/driverApp/task/acceptTask")
fun acceptOrder(@Body request : AcceptOrderRequest) : Observable<BaseResponse<String>>
//更新任务
@POST("/driverApp/v4/task/updateTask")
fun updateTask(@Body request : UpdateTaskRequest) : Observable<BaseResponse<UpdateTaskBean>>
//更新任务
@POST("/driverApp/v4/task/updateTask")
fun submitOrderConfirmTask(@Body request : UpdateOrderConfirmTaskRequest) : Observable<BaseResponse<UpdateTaskBean>>
@POST("/driverApp/v2/supplier/photoTemplate")
fun fetchPhotoTemplate(@Body request : PhotoTemplateRequest) : Observable<BaseResponse<List<PhotoTemplateInfo>>>
@POST("/driverApp/supplier/getWatermarkTemplateConfig")
fun getWatermarkTemplateConfig(@Body request : WaterMarkerRequestBean) : Observable<BaseResponse<MyWaterMarkerTemplateBean>>
@POST("/driverApp/supplier/orderPhotoOcrRecognize")
fun orderPhotoOcrRecognize(@Body info : OrderPhotoOcrRecognizeRequest) : Observable<BaseResponse<String>>
@POST("/driverApp/v4/supplier/saveElectronOrder")
fun saveElectronOrder(@Body info : SaveEleOrderRequest?) : Observable<BaseResponse<String>>
@POST("/driverApp/supplier/queryElectronOrder")
fun queryElectronOrder(@Body info : QueryEleOrderRequest?) : Observable<BaseResponse<ElectronOrderResponse>>
//查询支付信息
@POST("/driverApp/payment/paymentInfoQuery")
fun paymentInfoQuery(@Body info : PaymentInfoRequest?) : Observable<BaseResponse<PaymentInfoBean>>
@POST("/driverApp/payment/customerPaymentCreate")
fun customerPaymentCreate(@Body customerPaymentCreateRequest : CustomerPaymentCreateRequest) : Observable<BaseResponse<CustomerPaymentCreateBean>>
@POST("/driverApp/payment/paymentAmountUpdate")
fun paymentAmountUpdate(@Body paymentUpdateRequest : PaymentUpdateRequest) : Observable<BaseResponse<String>>
@POST("/driverApp/battery/saveReplaceBatteryPhoto")
fun saveReplaceBatteryPhoto(@Body info : UploadChangeBatteryRequest) : Observable<BaseResponse<String>>
@POST("/driverApp/supplier/getReplaceBatteryPhoto")
fun getReplaceBatteryPhoto(@Body info : FetchChangeBatteryPhotoRequest) : Observable<BaseResponse<List<ChangeBatteryResponse>>>
//电瓶费用查询
@POST("/driverApp/battery/batteryCostQuery")
fun batteryCostQuery(@Body info : BatteryCostQueryRequest) : Observable<BaseResponse<BatteryCostQueryBean>>
@POST("/driverApp/v4/task/taskFinish")
fun taskFinish(@Body info : TaskFinishRequest) : Observable<BaseResponse<TaskFinishResponse>>
@POST("/driverApp/task/giveUpTask")
fun giveUpTask(@Body info : GiveUpTaskRequest) : Observable<BaseResponse<String>>
//历史订单补传照片
@POST("/driverApp/task/updatePhoto")
fun addPhoto(@Body params : UpdatePhotoRequest) : Observable<BaseResponse<UploadPhotoBean>>
@POST("/driverApp/v2/task/switchTask")
fun switchTask(@Body info : SwitchTaskRequest) : Observable<BaseResponse<String>>
@POST("/driverApp/task/listHistoryTaskNew")
fun getHistory(@Body info : HistoryTasksRequest) : Observable<BaseResponse<List<HistoryTaskBean>>>
@POST("/driverApp/task/getHistoryTaskPhotos")
fun getHistoryTaskPhotos(@Body info : HistoryPhotoTemplateRequest) : Observable<BaseResponse<List<HistoryPhotoTemplates>>>
//历史结算单和轨迹
@POST("/driverApp/task/getTaskSettlementAndTrace")
fun getTaskSettlementAndTrace(@Body info : HistoryDetailRequest) : Observable<BaseResponse<TaskSettlementAndTraceBean>>
@POST("driverApp/task/updateSettleInfo")
fun updateSettleInfo(@Body settleInfoRequest : SettleInfoRequest) : Observable<BaseResponse<String>>
//人脸比对
@POST("driverApp/supplier/driverFaceCompare")
fun driverFaceCompare(@Body driverFaceCompareRequest : DriverFaceCompareRequest) : Observable<BaseResponse<DriverFaceCompareBean>>
@POST("driverApp/supplier/driverIdentityAuthWeb")
fun driverIdentityAuthWeb(@Body request : DriverIdentityAuthWebRequest) : Observable<BaseResponse<DriverIdentityAuthWebBean>>
//今日保养
@POST("driverApp/supplier/getTodayMaintain")
fun getTodayMaintain(@Body info : TodayMaintainRequest) : Observable<BaseResponse<List<TodayMaintainbean>>>
//今日保养
@POST("driverApp/supplier/uploadTodayMaintain")
fun uploadTodayMaintain(@Body params : TodayMaintainUploadRequest) : Observable<BaseResponse<String>>
//加油小票识别
@POST("driverApp/supplier/recognizeRefuelTicket")
fun recognizeRefuelTicket(@Body bean : RecognizeRefuelOcrRequestBean?) : Observable<BaseResponse<RecognizeRefuelTicketBean>>
//提交加油记录
@POST("driverApp/supplier/vehicleRefuelSubmit")
fun vehicleRefuelSubmit(@Body info : RecognizeRefuelTicketRequestBean?) : Observable<BaseResponse<String>>
//获取车辆维保记录
@POST("driverApp/supplier/v2/getVehicleMaintenanceSubmit")
fun getVehicleMaintenanceSubmit(@Body info : FetchVehicleMaintenanceSubmitHistoryRequestBean) : Observable<BaseResponse<VehicleRepairBean>>
//获取车辆维保详细信息
@POST("driverApp/supplier/v2/getVehicleMaintenanceDetail")
fun getVehicleRepairDetail(@Body info : FetchVehicleMaintenanceSubmitHistoryRequestBean) : Observable<BaseResponse<VehicleRepairBean>>
//维修地点匹配
@POST("driverApp/supplier/v2/vehicleRepairPointMatcherList")
fun vehicleRepairPointMatcherList(@Body info : VehicleRepairPointMatcherListRequest) : Observable<BaseResponse<List<VehicleRepairPointMatcherItem>>>
//提交维保记录
@POST("driverApp/supplier/v2/vehicleMaintenanceSubmit")
fun vehicleMaintenanceSubmit(@Body info : VehicleMaintenanceSubmitRequest) : Observable<BaseResponse<String>>
//获取车辆维修历史
@POST("/driverApp/supplier/v2/vehicleMaintenanceList")
fun vehicleMaintenanceList(@Body info : FetchVehicleMaintenanceSubmitHistoryRequestBean) : Observable<BaseResponse<List<VehicleMaintenanceHistoryBean>>>
}

View File

@ -0,0 +1,127 @@
package com.za.net
import android.net.ParseException
import com.blankj.utilcode.util.ActivityUtils
import com.blankj.utilcode.util.NetworkUtils
import com.blankj.utilcode.util.ToastUtils
import com.google.gson.JsonParseException
import com.za.base.Const
import com.za.bean.BaseResponse
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.service.location.ZdLocationManager
import io.reactivex.rxjava3.core.Observer
import io.reactivex.rxjava3.disposables.Disposable
import org.json.JSONException
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import java.util.concurrent.TimeoutException
import javax.net.ssl.SSLHandshakeException
/**
* Created by DoggieX on 2017/7/26.
*/
abstract class BaseObserver<T> : Observer<BaseResponse<T>> {
override fun onSubscribe(d: Disposable) {
// if (!NetworkUtils.isAvailable()) {
// doFailure(999, "网络无法使用")
// d.dispose()
// }
}
override fun onNext(tBaseResponse: BaseResponse<T>) {
if (tBaseResponse.isOk) {
doSuccess(tBaseResponse.result)
} else {
when (tBaseResponse.code) {
3, 401 -> handlerTokenExpired()
// 4 -> RsaRouter.navigate(context, "/page/Standby")
}
if (null != tBaseResponse.msg) {
doFailure(tBaseResponse.code, tBaseResponse.msg)
} else if (null != tBaseResponse.message) {
doFailure(tBaseResponse.code, tBaseResponse.message)
} else {
doFailure(tBaseResponse.code, "error")
}
}
}
override fun onError(e: Throwable) {
LogUtil.print("net error", e)
when (e) {
is JsonParseException -> {
doFailure(1, "数据解析错误")
}
is JSONException -> {
doFailure(1, "数据解析错误")
}
is ParseException -> {
doFailure(1, "数据解析错误")
}
is ConnectException -> {
doFailure(Const.NetWorkException, "与服务器断开连接")
}
is UnknownHostException -> {
doFailure(Const.NetWorkException, "与服务器断开连接")
}
is SSLHandshakeException -> {
doFailure(1, "证书验证失败")
LogUtil.print("SSLHandshakeException", e)
}
is TimeoutException -> {
doFailure(Const.NetWorkException, "网络连接超时1")
LogUtil.print("TimeoutException", e)
}
is SocketTimeoutException -> {
doFailure(Const.NetWorkException, "网络连接超时2")
LogUtil.print("SocketTimeoutException2", e)
}
else -> {
doFailure(1, "error:" + e.message)
LogUtil.print("other error", e)
}
}
}
override fun onComplete() {
}
abstract fun doSuccess(it: T?)
abstract fun doFailure(code: Int, msg: String?)
private fun handlerTokenExpired() {
ToastUtils.showShort("登陆信息已过期,请重新登录")
try {
GlobalData.clearUserCache()
ZdLocationManager.stopContinuousLocation()
ActivityUtils.finishAllActivities()
// val oldLoginInfo: LoginInfo = DbManager.getInstance(context).queryLoginInfo()
// val loginInfo: LoginInfo = LoginInfo()
// loginInfo.setSupplierId(oldLoginInfo.getSupplierId())
// loginInfo.setSupplierCode(oldLoginInfo.getSupplierCode())
// loginInfo.setJobNumber(oldLoginInfo.getJobNumber())
// loginInfo.setPassword(oldLoginInfo.getPassword())
// DbManager.getInstance(context).deleteLoginInfo()
// DbManager.getInstance(context).saveLoginInfo(loginInfo)
// SPKit.saveString(context, "userId", "-1")
// SPKit.saveString(context, "vehicleId", "-1")
//
// LBSManager.getInstance().cancel()
// DaemonService.stopService()
// //跳转
// RsaRouter.navigateNoFlag(context, "/page/login")
} catch (e: Exception) {
LogUtil.print("handlerTokenExpired", e)
}
}
}

View File

@ -0,0 +1,405 @@
package com.za.net
import android.content.Context
import android.graphics.BitmapFactory
import com.blankj.utilcode.util.ActivityUtils
import com.blankj.utilcode.util.ImageUtils
import com.za.base.Const
import com.za.bean.DriverInfo
import com.za.bean.GeneralInfo
import com.za.bean.ImageBean
import com.za.bean.JpushBean
import com.za.bean.NewOrderRequestBean
import com.za.bean.VehicleInfo
import com.za.bean.db.ele.EleCarDamagePhotoBean
import com.za.bean.db.ele.EleWorkOrderBean
import com.za.bean.db.order.OrderInfo
import com.za.bean.db.order.PhotoTemplateInfo
import com.za.bean.request.ElectronOrderResponse
import com.za.bean.request.GeneralInfoRequest
import com.za.bean.request.OrderListRequest
import com.za.bean.request.PhotoTemplateRequest
import com.za.bean.request.QueryEleOrderRequest
import com.za.bean.request.UpdateTaskBean
import com.za.bean.request.UpdateTaskRequest
import com.za.bean.request.UploadGpsRequest
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.common.util.DeviceUtil
import com.za.ext.toJson
import com.za.offline.OfflineManager
import com.za.room.RoomHelper
import com.za.ui.new_order.NewOrderActivity
import com.za.ui.order_report.ReportFloatingManager
import com.za.water_marker.WaterMarkerTemplateManager
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observer
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.schedulers.Schedulers
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
object CommonMethod {
fun uploadGps(uploadGpsRequest : UploadGpsRequest,
success : () -> Unit = {},
failed : (String?) -> Unit = {}) {
RetrofitHelper.getDefaultService().uploadGps(uploadGpsRequest).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(object : BaseObserver<String>() {
override fun doSuccess(it : String?) {
success()
LogUtil.print("uploadGps", uploadGpsRequest.toString())
}
override fun doFailure(code : Int, msg : String?) {
failed(msg)
LogUtil.print("uploadGps", "failed==$msg")
}
})
}
fun uploadImage(file : File,
preview : () -> Unit = {},
success : (String?) -> Unit = {},
failed : (String?) -> Unit = {}) {
file.writeBytes(ImageUtils.compressByQuality(BitmapFactory.decodeStream(file.inputStream()),
Const.Image_Max_length,
true))
val requestFile = file.asRequestBody("image/*".toMediaTypeOrNull())
val body = MultipartBody.Part.createFormData("file", file.name, requestFile)
preview()
RetrofitHelper.getDefaultService().uploadImage(body).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(object : Observer<ImageBean> {
override fun onSubscribe(d : Disposable) {
}
override fun onError(e : Throwable) {
failed(e.message)
LogUtil.print("uploadImage", e)
}
override fun onComplete() {
}
override fun onNext(t : ImageBean) {
if (t.isOk()) {
success(t.data)
return
}
failed(t.msg)
LogUtil.print("uploadImage failed", t.toJson() ?: "")
}
})
}
//更新订单状态
fun updateTask(taskRequest : UpdateTaskRequest,
success : (UpdateTaskBean?) -> Unit,
failed : (String?, Int) -> Unit) {
RetrofitHelper.getDefaultService().updateTask(taskRequest).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<UpdateTaskBean>() {
override fun doSuccess(it : UpdateTaskBean?) {
success(it)
LogUtil.print("updateTask", "success=$it request=${taskRequest.toJson()}")
}
override fun doFailure(code : Int, msg : String?) {
failed(msg, code)
LogUtil.print("updateTask", "doFailure=$msg request=${taskRequest.toJson()}")
}
})
}
fun getGenerateInfo(vehicleId : Int? = null,
userId : Int? = null,
success : (GeneralInfo) -> Unit = {},
failed : (String?) -> Unit = {}) {
val generalInfoRequest =
GeneralInfoRequest(vehicleId = vehicleId ?: GlobalData.vehicleInfo?.vehicleId,
driverId = userId ?: GlobalData.driverInfo?.userId,
deviceId = DeviceUtil.getAndroidId(ActivityUtils.getTopActivity()))
RetrofitHelper.getDefaultService().generalInfo(generalInfoRequest)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<GeneralInfo>() {
override fun doSuccess(it : GeneralInfo?) {
if (it == null) {
failed("获取车辆信息失败")
return
}
if (GlobalData.vehicleInfo == null) {
GlobalData.vehicleInfo = VehicleInfo(vehicleId = it.vehicleId,
vehicleName = it.vehicleName,
userName = it.userName,
userPhone = it.userPhone,
status = it.vehicleState,
plateNumber = it.plateNumber,
vehicleState = it.vehicleState,
supplierType = it.supplierType)
} else {
GlobalData.vehicleInfo =
GlobalData.vehicleInfo?.copy(vehicleId = it.vehicleId,
vehicleName = it.vehicleName,
userName = it.userName,
userPhone = it.userPhone,
status = it.vehicleState,
plateNumber = it.plateNumber,
vehicleState = it.vehicleState,
supplierType = it.supplierType)
}
if (GlobalData.driverInfo == null) {
GlobalData.driverInfo = DriverInfo(userPortrait = it.userPortrait,
userName = it.userName,
userId = it.userId,
userPhone = it.userPhone,
supplierId = it.supplierId.toString(),
supplierName = it.supplierName,
authStatus = it.authStatus,
supplierType = it.supplierType,
serviceList = it.serviceList)
} else {
GlobalData.driverInfo =
GlobalData.driverInfo?.copy(userPortrait = it.userPortrait,
userName = it.userName,
userId = it.userId,
userPhone = it.userPhone,
supplierId = it.supplierId.toString(),
supplierName = it.supplierName,
authStatus = it.authStatus,
supplierType = it.supplierType,
serviceList = it.serviceList)
}
success(it)
}
override fun doFailure(code : Int, msg : String?) {
failed(msg)
LogUtil.print("getGenerateInfo",
"doFailure=$msg request=${generalInfoRequest.toJson()}")
}
})
}
fun getNewOrder(context : Context) {
val newOrderRequestBean = NewOrderRequestBean(GlobalData.vehicleInfo?.vehicleId)
RetrofitHelper.getDefaultService().getNewOrder(newOrderRequestBean)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<JpushBean>() {
override fun doSuccess(order : JpushBean?) {
NewOrderActivity.goNewOrderActivity(context,
jpushBean = JpushBean(taskId = order?.taskId,
taskCode = order?.taskCode,
customerName = order?.customerName,
customerPhone = order?.customerPhone,
carBrand = order?.carBrand,
carModel = order?.carModel,
carNo = order?.carNo,
taskState = order?.taskState,
address = order?.address,
addressProperty = order?.addressProperty,
hotline = order?.hotline,
expectArriveTime = order?.expectArriveTime,
serviceTypeName = order?.serviceTypeName,
dispatchTime = order?.dispatchTime,
lat = order?.lat,
lng = order?.lng,
distLat = order?.distLat,
distLng = order?.distLng,
importantTip = order?.importantTip,
hasReplaceBatteryCapable = order?.hasReplaceBatteryCapable,
distAddress = order?.distAddress,
distAddressRemark = order?.distAddressRemark,
addressRemark = order?.addressRemark,
voiceType = order?.voiceType))
}
override fun doFailure(code : Int, msg : String?) {
}
})
}
fun queryOrderList(context : Context? = null,
success : (OrderInfo?, List<OrderInfo>?) -> Unit,
failed : (String?) -> Unit) {
RetrofitHelper.getDefaultService()
.queryOrderList(OrderListRequest(vehicleId = GlobalData.vehicleInfo?.vehicleId,
deviceId = DeviceUtil.getAndroidId(ActivityUtils.getTopActivity())))
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<List<OrderInfo>?>() {
override fun doSuccess(it : List<OrderInfo>?) {
LogUtil.print("queryOrderList", "${it.toJson()}")
if (it.isNullOrEmpty()) {
GlobalData.clearAllOrderCache()
ReportFloatingManager.stopService()
success(null, null)
return
}
val inServicingOrder = it.find { it.isCurrent == true }
val waitServiceOrders = it.filter { it.isCurrent == false }
if (inServicingOrder == null) {
context?.let { context ->
val order = it[0]
NewOrderActivity.goNewOrderActivity(context,
jpushBean = JpushBean(taskId = order.taskId,
taskCode = order.taskCode,
customerName = order.customerName,
customerPhone = order.customerPhone,
carBrand = order.carBrand,
carModel = order.carModel,
carNo = order.carNo,
taskState = order.taskState,
address = order.address,
addressProperty = order.addressProperty,
hotline = order.hotline,
expectArriveTime = order.expectArriveTime,
serviceTypeName = order.serviceTypeName,
dispatchTime = order.dispatchTime,
lat = order.lat,
lng = order.lng,
distLat = order.distLat,
distLng = order.distLng,
importantTip = order.importantTip,
hasReplaceBatteryCapable = order.hasReplaceBatteryCapable,
distAddress = order.distAddress,
distAddressRemark = order.distAddressRemark,
addressRemark = order.addressRemark))
}
success(null, waitServiceOrders)
return
}
GlobalData.currentOrder = inServicingOrder
it.forEach {
if (RoomHelper.db?.orderDao()
?.getOrderInfoFromTaskId(it.taskId ?: 0) != null
) {
RoomHelper.db?.orderDao()?.update(orderInfo = it)
} else {
RoomHelper.db?.orderDao()?.insertOrder(orderInfo = it)
}
}
fetchPhotoTemplate(inServicingOrder)
queryElectronOrder(orderInfo = inServicingOrder)
WaterMarkerTemplateManager.fetchWaterTemplate(inServicingOrder.taskCode ?: "",
taskId = inServicingOrder.taskId ?: 0,
inServicingOrder.userOrderId ?: 0)
OfflineManager.start(GlobalData.currentOrder?.taskId)
context?.let {
ReportFloatingManager.startService(it)
}
success(inServicingOrder, waitServiceOrders)
}
override fun doFailure(code : Int, msg : String?) {
val list = RoomHelper.db?.orderDao()?.getAllOrder()
val inServicingOrder = list?.find { it.isCurrent == true }
val waitServiceOrders = list?.filter { it.isCurrent == false }
GlobalData.currentOrder = inServicingOrder
success(inServicingOrder, waitServiceOrders)
OfflineManager.start(GlobalData.currentOrder?.taskId)
LogUtil.print("queryOrderList", "queryOrderList failed 是用离线数据==$msg")
}
})
}
fun fetchPhotoTemplate(orderInfo : OrderInfo?,
success : () -> Unit = {},
failed : (String?) -> Unit = {}) {
val photoTemplateRequest = PhotoTemplateRequest(orderInfo?.userOrderId)
RetrofitHelper.getDefaultService().fetchPhotoTemplate(photoTemplateRequest)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<List<PhotoTemplateInfo>>() {
override fun doSuccess(it : List<PhotoTemplateInfo>?) {
if (it.isNullOrEmpty()) {
LogUtil.print("fetchPhotoTemplate",
"模板为null ${photoTemplateRequest.toJson()}")
failed("未找到模版")
return
}
val listData = RoomHelper.db?.photoTemplateDao()
?.queryCurrentOrderHasTaskNode(orderInfo?.userOrderId ?: 0)
if (! listData.isNullOrEmpty()) {
LogUtil.print("queryPhotoTemplate", "模板已经存在==${listData.toJson()}")
success()
return
}
it.forEachIndexed { _, item ->
val photoTemplateInfo = item.copy(userOrderId = orderInfo?.userOrderId,
taskCode = orderInfo?.taskCode,
myCustomPhotoType = Const.PhotoType.InServicing,
advanceTime = orderInfo?.advanceTime,
taskId = orderInfo?.taskId,
needWaterMarker = orderInfo?.needWaterMarker,
needShowPhoneBrand = orderInfo?.needShowPhoneBrand,
photoSource = 0)
RoomHelper.db?.photoTemplateDao()?.insert(photoTemplateInfo)
}
LogUtil.print("fetchPhotoTemplate", " ${it.toJson()}")
success()
}
override fun doFailure(code : Int, msg : String?) {
LogUtil.print("fetchPhotoTemplate", "err==$msg")
failed(msg)
}
})
}
fun queryElectronOrder(orderInfo : OrderInfo?,
success : (EleWorkOrderBean) -> Unit = {},
failed : (String?) -> Unit = {}) {
val ele = RoomHelper.db?.eleWorkOrderDao()?.getEleWorkOrder(taskId = orderInfo?.taskId ?: 0)
if (ele != null) {
success(ele)
return
}
val queryEleOrderRequest = QueryEleOrderRequest(taskOrderId = orderInfo?.taskId,
userOrderId = orderInfo?.userOrderId)
RetrofitHelper.getDefaultService().queryElectronOrder(queryEleOrderRequest)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<ElectronOrderResponse>() {
override fun doSuccess(it : ElectronOrderResponse?) {
val temp = EleWorkOrderBean(orderId = orderInfo?.taskId ?: 0,
userOrderId = it?.userOrderId,
date = it?.createTime,
orderWorkStatus = it?.state,
hasBad = it?.hasDamage == 1,
serverCustomSignPath = it?.customerSignPath,
carNO = it?.plateNumberBean?.value,
carVin = it?.vinNo,
serviceContent = it?.serviceTerm,
serverAcceptCarSignPath = it?.recipientSignPath,
serverServicePeopleSignPath = it?.waitstaffSignPath,
orderType = it?.serviceName)
it?.damageFileList?.forEach { item ->
val damagePhotoBean = EleCarDamagePhotoBean(userOrderId = it.userOrderId,
orderId = orderInfo?.taskId,
isPhoto = true,
uploadStatus = 1,
serverPath = item)
RoomHelper.db?.eleCarDamagePhotoDao()?.insert(damagePhotoBean)
}
RoomHelper.db?.eleWorkOrderDao()?.insertEleWorkOrder(temp)
success(temp)
}
override fun doFailure(code : Int, msg : String?) {
failed(msg)
}
})
}
}

View File

@ -0,0 +1,347 @@
package com.za.net;
import android.util.Base64;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
public class RSAUtils {
/**
* 加密算法RSA
*/
private static final String KEY_ALGORITHM = "RSA";
/**
* 签名算法
*/
// public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
/**
* 获取公钥的key
*/
private static final String PUBLIC_KEY = "RSAPublicKey";
/**
* 获取私钥的key
*/
private static final String PRIVATE_KEY = "RSAPrivateKey";
/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 256;
public static void main(String[] args) {
// try {
// genKeyPair(2048);
// } catch (Exception e) {
// e.printStackTrace();
// }
/*RSA 2048*/
// String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAichGTEP0QFswnvn+ZAQrgGHM8VeDZLJuezGhgxh4d9SyRUfnIW/zefT71rwS4bZUs1MPxJwavOyxABJOHLuckdHXknCsGEWz78gsA6D0+O+9dl1gCZR29nnN/NlzmNbSjFnzvsTJYBlS88qSr35RXFE+6DM7uPsS8Fm2I+65FteJ8p2yMvpSg72QkIX8xvI1F1uwXrciIB+4u7uTozxIplMOo4a6uhAm3W+Kjpz3ni2btjGqHRbqb3ebSZyl+nFfnjQaBe3XyVxAWDSanjgFj/wbqbeug9FBs+nQFVPIZR9z0aE5Ndi5o3eSkV7HFmWpkxaiPZ0BLRK3XHMaBtuSpwIDAQAB";
// String privateKey = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCJyEZMQ/RAWzCe+f5kBCuAYczxV4Nksm57MaGDGHh31LJFR+chb/N59PvWvBLhtlSzUw/EnBq87LEAEk4cu5yR0deScKwYRbPvyCwDoPT47712XWAJlHb2ec382XOY1tKMWfO+xMlgGVLzypKvflFcUT7oMzu4+xLwWbYj7rkW14nynbIy+lKDvZCQhfzG8jUXW7BetyIgH7i7u5OjPEimUw6jhrq6ECbdb4qOnPeeLZu2MaodFupvd5tJnKX6cV+eNBoF7dfJXEBYNJqeOAWP/Bupt66D0UGz6dAVU8hlH3PRoTk12Lmjd5KRXscWZamTFqI9nQEtErdccxoG25KnAgMBAAECggEBAIPz1b88ZTMtIgdejA7lH3Q4Nbn8gc1yRPSet3uBd/3rKT/IeMZBHQBzaqxgOgUIRV3n8nXsun6sf2b+IOjLlErimH2agnZMauL85YokH/g4QU6WZl9GXBf41xmMd3SsZ8AadaEBfYoXNqZcHtcLNogfFwvx5QRnD+A3SoRnH8OLBeVvOEe4AqHLT2xEZ9TeCf3fJe0Rf0fUIbw7I5ioiRZV/ir0L1VM7+1k2JODUkdC2Luj5Tl3nl1Eg6EmkYCmGE1bip1NAatsfjPBLMF7XdPNjLboiffjgKVBOjb7Y9vL18BCoLtWeTT2GkMpi5Sr94T1te1Ox77dF4BP33Xn7eECgYEA1TNUrAQsh14NbbkwFtUHXS8/YXt81p9wbSpFBymIawF2Lkk0913TB4CHSun45LhYXjdZZxK/TgqC5EIq5v2RA0jY3cSxoqVe6RZKB04E8wszeJHiEJPdu2vFnpZh9iAyhswiM5FmuKZKoWsVc2SZrBXAI02smSn3lXYok1VBS3sCgYEApXEZS6gjUu4o7ZL53Ur1HDfi/nxpkxqrPh+D1HVYjzjT+4vTeZwtLXt2VCInPWNXH+f11mzhxIrLkI0jMcSCah81DuU8aFXnqvPuyFvt9uaQBYlVWBtkcGZyeaxHFrbfCyeu0jm7SfwmiIg12hKlIHtPTjEZQUX+kkWr8cdaZ8UCgYEAh0Pl+K09QzVc97yC0jmeTnTnlYWvksvdnKUw3nZvYtSukndH75nLhfr524HOs+5xwnUDd+3hCjaJDSEd7yf5lUfmr+1XdoXNTb0igrfxU/JLWbfU4geuqnaaDyACTxHmfLePC4C413ZJ61fxaCDvjsrN+JgTZanGt0EcRT3WC3kCgYEAgf5/GMJxlw0JXbs515a5R8Xl9358Whj/at3KcRsPTeIiNqnkrc54dR9ol60KViMDZ0+VDDobn5pLXzZ26/jzXD1PLHgU4gp18Q6glhAdx/3cNm11gLhtUCA/XLlwVjm0wggZRpgUQIr/IBKe9c3mr8IUS2Uq6e38nKRf+adhst0CgYAM4tvl+U1MPbbz3YzDv8QPepZ7Pglgdfxqfr5OkXA7jNhqTZjSq10B6oClGvirBo1m6f26F02iUKk1n67AuiLlTP/RRZHi1cfq6P9IaXl23PcxJfUMvIxQDS0U+UTFpNXryTw/qNAkSfufN48YzKdGvc8vHrYJyaeemaVlbdJOCw==";
//
//
// try {
//
// String data = "测试提交数据";
//
// byte[] publicEncryptBytes = RSAUtils.encryptByPublicKey(data.getBytes(), publicKey);
// System.out.println("公钥加密后的数据:" + Base64.encodeToString(publicEncryptBytes, Base64.URL_SAFE | Base64.NO_WRAP));
// byte[] privatDecryptBytes = RSAUtils.decryptByPrivateKey(publicEncryptBytes, privateKey);
// System.out.println("私钥解密后的数据:" + new String(privatDecryptBytes));
//
//
// System.out.println("--------------------");
//
// byte[] privateKeyEncryptBytes = RSAUtils.encryptByPrivateKey(data.getBytes(), privateKey);
// System.out.println("私钥加密后的数据:" + Base64.encodeToString(privateKeyEncryptBytes, Base64.URL_SAFE | Base64.NO_WRAP));
//
// String singnData = RSAUtils.sign(data.getBytes(), privateKey);
// System.out.println("私钥签名后的数据:" + singnData);
//
//
// byte[] publicDecryptBytes = RSAUtils.decryptByPublicKey(privateKeyEncryptBytes, publicKey);
// System.out.println("公钥解密后的数据:" + new String(publicDecryptBytes));
//
// boolean isSign = RSAUtils.verify(data.getBytes(), publicKey, singnData);
// System.out.println("签名是否正确:" + isSign);
//
//
// } catch (Exception e) {
// e.printStackTrace();
// }
}
/**
* @param keySize 生成的秘钥长度 一般为1024或2048
* @return
* @throws Exception
*/
public static Map<String, Object> genKeyPair(int keySize) throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
keyPairGen.initialize(keySize);
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
Map<String, Object> keyMap = new HashMap<String, Object>(2);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
// Log.d("DoggieX","publicKey" + Base64.encodeToString(publicKey.getEncoded(), Base64.URL_SAFE | Base64.NO_WRAP));
// Log.d("DoggieX","privateKey" + Base64.encodeToString(privateKey.getEncoded(), Base64.URL_SAFE | Base64.NO_WRAP));
Log.d("DoggieX","publicKey" + Base64.encodeToString(publicKey.getEncoded(), Base64.NO_WRAP));
Log.d("DoggieX","privateKey" + Base64.encodeToString(privateKey.getEncoded(), Base64.NO_WRAP));
return keyMap;
}
/**
* 对已加密数据进行签名
*
* @param data 已加密的数据
* @param privateKey 私钥
* @return 对已加密数据生成的签名
* @throws Exception
*/
public static String sign(byte[] data, String privateKey) throws Exception {
byte[] keyBytes = Base64.decode(privateKey, Base64.URL_SAFE | Base64.NO_WRAP);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(privateK);
signature.update(data);
return Base64.encodeToString(signature.sign(), Base64.URL_SAFE);
}
/**
* 验签
*
* @param data 签名之前的数据
* @param publicKey 公钥
* @param sign 签名之后的数据
* @return 验签是否成功
* @throws Exception
*/
public static boolean verify(byte[] data, String publicKey, String sign) throws Exception {
byte[] keyBytes = Base64.decode(publicKey, Base64.URL_SAFE | Base64.NO_WRAP);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicK = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(publicK);
signature.update(data);
return signature.verify(Base64.decode(sign, Base64.URL_SAFE));
}
/**
* 用私钥对数据进行解密
*
* @param encryptedData 使用公钥加密过的数据
* @param privateKey 私钥
* @return 解密后的数据
* @throws Exception
*/
public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception {
byte[] keyBytes = Base64.decode(privateKey, Base64.NO_WRAP);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
//Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding","BC");
cipher.init(Cipher.DECRYPT_MODE, privateK);
Log.d("DoggieX","provider:"+cipher.getProvider().getClass().getName());
int inputLen = encryptedData.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
return decryptedData;
}
/**
* 公钥解密
*
* @param encryptedData 使用私钥加密过的数据
* @param publicKey 公钥
* @return 解密后的数据
* @throws Exception
*/
public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey) throws Exception {
byte[] keyBytes = Base64.decode(publicKey, Base64.URL_SAFE | Base64.NO_WRAP);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key publicK = keyFactory.generatePublic(x509KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicK);
int inputLen = encryptedData.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
return decryptedData;
}
/**
* 公钥加密
*
* @param data 需要加密的数据
* @param publicKey 公钥
* @return 使用公钥加密后的数据
* @throws Exception
*/
public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
byte[] keyBytes = Base64.decode(publicKey, Base64.NO_WRAP);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key publicK = keyFactory.generatePublic(x509KeySpec);
// 对数据加密
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding","BC");
cipher.init(Cipher.ENCRYPT_MODE, publicK);
Log.d("DoggieX",cipher.getProvider().getClass().toString());
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
return encryptedData;
}
/**
* 私钥加密
*
* @param data 待加密的数据
* @param privateKey 私钥
* @return 使用私钥加密后的数据
* @throws Exception
*/
public static byte[] encryptByPrivateKey(byte[] data, String privateKey) throws Exception {
byte[] keyBytes = Base64.decode(privateKey, Base64.URL_SAFE | Base64.NO_WRAP);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateK);
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
return encryptedData;
}
/**
* 获取私钥
*
* @param keyMap 生成的秘钥对
* @return
* @throws Exception
*/
public static String getPrivateKey(Map<String, Object> keyMap) throws Exception {
Key key = (Key) keyMap.get(PRIVATE_KEY);
return Base64.encodeToString(key.getEncoded(), Base64.URL_SAFE | Base64.NO_WRAP);
}
/**
* 获取公钥
*
* @param keyMap 生成的秘钥对
* @return
* @throws Exception
*/
public static String getPublicKey(Map<String, Object> keyMap) throws Exception {
Key key = (Key) keyMap.get(PUBLIC_KEY);
return Base64.encodeToString(key.getEncoded(), Base64.URL_SAFE | Base64.NO_WRAP);
}
}

View File

@ -0,0 +1,109 @@
package com.za.net;
import android.util.Base64;
import androidx.annotation.NonNull;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.za.common.GlobalData;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okio.Buffer;
/**
* @author DoggieX
* @create 2021/8/23 15:00
* @mail coldpuppy@163.com
*/
public class RequestEncryptInterceptor implements Interceptor {
static final String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7UM6zdWdBuO0DZZVkxVfJioawUe6qH1p5Uz/qR9zbawl2oWyxxcBfxQyPS+HxOej/ZnyS4bu7qhh99alDqkJzk6g9oGZWs+jEF5GRWt9nChlfUsjvHQwuF2TSQMTdPtDPCByF/QgMFCAfbCqTrNmOETrZ/2GFy1Re0BTlhh6X/XzpzqtK+enikEMlQ5fIM5ljdXgyCnvDou9ptWqzw8Zmsat6LeA0UKz+bgpJAbw6KfK+8lPMqUpNFfkmJuEd5+JQOG9McH7j9pBagohkC6k3Cn92dAf9iD6NSDKSNgt1vxXhaNnfAbYJ5pqeSGy6QMSVO0TXYj4asln5OutD/284QIDAQAB";
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
Request request = chain.request();
String method = request.method().toLowerCase().trim();
HttpUrl url = request.url();
boolean skipEncrypt = url.encodedPath().endsWith("appVersion")
|| url.encodedPath().endsWith("getVerifyCode")
|| url.encodedPath().endsWith("listVehicleNew")
|| url.encodedPath().endsWith("getExitMileage")
|| url.encodedPath().endsWith("uploadImage")
|| url.encodedPath().endsWith("upload")
|| url.encodedPath().endsWith("matchAppUploadTime");
if (method.equals("post") && !skipEncrypt) {
RequestBody body = request.body();
if (null != body) {
Buffer buffer = new Buffer();
body.writeTo(buffer);
String data = URLDecoder.decode(buffer.readString(StandardCharsets.UTF_8), "utf-8");
JSONObject jsonObject = JSON.parseObject(data);
if (null == jsonObject) {
jsonObject = new JSONObject();
}
jsonObject.put("requestTime", System.currentTimeMillis());
data = jsonObject.toJSONString();
String aesKey;
try {
if (url.encodedPath().endsWith("login")
|| url.encodedPath().endsWith("fastLogin")) {
// 登录接口生成新key
aesKey = AESUtils.getRandomKey(16);
GlobalData.INSTANCE.setAesKey(aesKey);
} else {
aesKey = GlobalData.INSTANCE.getAesKey();
if (null == aesKey && url.encodedPath().endsWith("loginWithTask")) {
aesKey = AESUtils.getRandomKey(16);
GlobalData.INSTANCE.setAesKey(aesKey);
} else if (null == aesKey) {
throw new IOException("缺失加密密钥");
} else {
aesKey = GlobalData.INSTANCE.getAesKey();
}
}
if (!url.encodedPath().endsWith("login")
&& !url.encodedPath().endsWith("fastLogin")
&& !url.encodedPath().endsWith("loginWithTask")) {
data = AESUtils.encrypt(data, aesKey);
}
RequestBody requestBody = RequestBody.create(body.contentType(), data);
Request.Builder requestBuilder = request.newBuilder().post(requestBody);
if (url.encodedPath().endsWith("login")
|| url.encodedPath().endsWith("fastLogin")
|| url.encodedPath().endsWith("loginWithTask")) {
byte[] bytes = RSAUtils.encryptByPublicKey(aesKey.getBytes(StandardCharsets.UTF_8), PUBLIC_KEY);
String tokenSecret = Base64.encodeToString(bytes, Base64.NO_WRAP);
requestBuilder.addHeader("secret", tokenSecret);
} else {
String token = GlobalData.INSTANCE.getToken();
if (null == token) {
throw new IOException("缺失token");
} else {
requestBuilder.addHeader("token", token);
}
}
request = requestBuilder.build();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return chain.proceed(request);
}
}

View File

@ -0,0 +1,83 @@
package com.za.net
import android.util.Log
import com.za.base.AppConfig
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.io.UnsupportedEncodingException
import java.net.URLDecoder
import java.util.concurrent.TimeUnit
object RetrofitHelper {
private var retrofit: Retrofit? = null
private var apiService: ApiService? = null
private val loggerInterceptor = HttpLoggingInterceptor {
try {
if (it.contains("image/*") || it.contains("form-data; name=\"file\"")
|| it.startsWith("Content-Type")
|| it.startsWith("Content-Length")
|| it.startsWith("Server")
|| it.startsWith("Date")
|| it.startsWith("Transfer-Encoding")
|| it.startsWith("Connection")
|| it.startsWith("X-")
|| it.startsWith("token")
|| it.startsWith("Cache-Control")
|| it.startsWith("Expires")
|| it.startsWith("Accept")
|| it.startsWith("Vary")
|| it.isEmpty()
|| it.startsWith("Pragma")
) {
return@HttpLoggingInterceptor
}
if (it.contains("name=\"file\"; filename")) {
return@HttpLoggingInterceptor
}
Log.e(
"--network--",
URLDecoder.decode(it.replace(Regex("%(?![0-9a-fA-F]{2})"), ""), "utf-8")
)
} catch (e: UnsupportedEncodingException) {
e.printStackTrace()
}
}.setLevel(HttpLoggingInterceptor.Level.BODY)
fun getDefaultService(): ApiService {
return if (apiService == null) {
apiService = getDefaultRetrofit().create(ApiService::class.java)
apiService!!
} else {
apiService!!
}
}
private fun getDefaultRetrofit(): Retrofit {
return if (retrofit == null) {
retrofit = Retrofit.Builder().baseUrl(AppConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.client(getOkHttpClient())
.build()
retrofit!!
} else {
retrofit!!
}
}
private fun getOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.addInterceptor(RequestEncryptInterceptor())
.addInterceptor(loggerInterceptor)
.build()
}
}

View File

@ -0,0 +1,62 @@
package com.za.offline
import android.view.View
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.lifecycle.setViewTreeViewModelStoreOwner
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryController
import androidx.savedstate.SavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
class MyOfflineServiceViewLifecycleOwner : LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner {
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
private val savedStateRegistryController = SavedStateRegistryController.create(this)
private val store = ViewModelStore()
override val lifecycle: Lifecycle
get() = lifecycleRegistry
override val savedStateRegistry: SavedStateRegistry
get() = savedStateRegistryController.savedStateRegistry
override val viewModelStore: ViewModelStore
get() = store
fun onCreate() {
savedStateRegistryController.performRestore(null)
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
}
fun onStart() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
}
fun onResume() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}
fun onPause() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
}
fun onStop() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
}
fun onDestroy() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
store.clear()
}
fun attachToDecorView(decorView: View?) {
decorView?.let {
it.setViewTreeViewModelStoreOwner(this)
it.setViewTreeLifecycleOwner(this)
it.setViewTreeSavedStateRegistryOwner(this)
} ?: return
}
}

View File

@ -0,0 +1,40 @@
package com.za.offline
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
@Dao
interface OfflineDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOfflineTask(offlineUpdateTaskBean: OfflineUpdateTaskBean)
@Query("select * from offline_update_task_bean where taskId =:taskId")
fun getOfflineTaskFromTaskId(taskId: Int): List<OfflineUpdateTaskBean>?
@Query("select * from offline_update_task_bean where primaryId =:primaryId")
fun getOfflineTaskFromPrimaryId(primaryId: Int): OfflineUpdateTaskBean?
@Query("select * from offline_update_task_bean where taskId =:taskId and offlineType=1")
fun getRecentOfflineTask(taskId: Int): List<OfflineUpdateTaskBean?>?
@Query("select * from offline_update_task_bean where taskId =:taskId and offlineType=3")
fun getRecentOfflineEle(taskId: Int): List<OfflineUpdateTaskBean?>?
@Query("delete from offline_update_task_bean where primaryId =:primaryId")
fun deleteOfflineTaskFromId(primaryId: Int)
@Query("delete from offline_update_task_bean where taskId =:taskId")
fun deleteOfflineTaskFromTaskId(taskId: Int)
@Query("delete from offline_update_task_bean")
fun deleteAllOfflineTask()
@Update(onConflict = OnConflictStrategy.REPLACE)
fun update(offlineUpdateTaskBean: OfflineUpdateTaskBean)
}

View File

@ -0,0 +1,514 @@
package com.za.offline
import android.content.Intent
import android.provider.Settings
import androidx.appcompat.app.AlertDialog
import androidx.core.net.toUri
import com.alibaba.fastjson.JSONObject
import com.amap.api.services.core.LatLonPoint
import com.amap.api.services.geocoder.GeocodeResult
import com.amap.api.services.geocoder.GeocodeSearch
import com.amap.api.services.geocoder.GeocodeSearch.OnGeocodeSearchListener
import com.amap.api.services.geocoder.RegeocodeQuery
import com.amap.api.services.geocoder.RegeocodeResult
import com.blankj.utilcode.util.ActivityUtils
import com.blankj.utilcode.util.ToastUtils
import com.za.bean.request.SaveEleOrderRequest
import com.za.bean.request.TaskFinishRequest
import com.za.bean.request.TaskFinishResponse
import com.za.bean.request.UpdateTaskBean
import com.za.bean.request.UpdateTaskRequest
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.ext.toJson
import com.za.net.BaseObserver
import com.za.net.CommonMethod
import com.za.net.RetrofitHelper
import com.za.room.RoomHelper
import com.za.water_marker.PhotoMarkerManager
import com.za.water_marker.bean.PhotoMarkerInfo
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.schedulers.Schedulers
import java.io.File
interface IOfflineListener {
fun start()
fun stop()
fun uploadSuccess()
fun uploadFailure(msg : String?)
fun currentTask(offlineUpdateTaskBean : OfflineUpdateTaskBean)
}
object OfflineManager {
private var isUploading = false
private var uploadDispose : Disposable? = null
private var offlineListener : IOfflineListener? = null
private var currentTaskId : Int? = null
fun addOfflineListener(offlineListener : IOfflineListener) {
this.offlineListener = offlineListener
}
fun start(taskId : Int?) {
if (checkOfflineIsComplete(taskId)) {
return
}
if (currentTaskId != null && taskId == currentTaskId && isUploading) {
return
}
if (! Settings.canDrawOverlays(GlobalData.application)) {
openSystemAlertPermissionDialog()
return
}
stop()
OfflineService.addListener()
offlineListener?.start()
uploadDispose = Observable.interval(0, 5, java.util.concurrent.TimeUnit.SECONDS)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe {
if (! isUploading) {
val list =
RoomHelper.db?.offlineTaskDao()?.getOfflineTaskFromTaskId(taskId ?: 0)
if (list.isNullOrEmpty()) {
LogUtil.print("离线任务", "离线任务为空!!")
stop()
ToastUtils.showLong("离线任务上传完成")
return@subscribe
}
doUpload(list[0])
}
}
}
private fun stop() {
uploadDispose?.dispose()
uploadDispose = null
isUploading = false
currentTaskId = null
offlineListener?.stop()
offlineListener = null
}
private fun checkOfflineIsComplete(taskId : Int?) : Boolean {
val list = RoomHelper.db?.offlineTaskDao()?.getOfflineTaskFromTaskId(taskId ?: 0)
LogUtil.print("离线任务列表", "${list.toJson()}")
return list.isNullOrEmpty()
}
private fun doUpload(offlineUpdateTaskBean : OfflineUpdateTaskBean) {
offlineListener?.currentTask(offlineUpdateTaskBean)
currentTaskId = offlineUpdateTaskBean.taskId
when (offlineUpdateTaskBean.offlineType) {
1 -> uploadTask(offlineUpdateTaskBean)
2 -> uploadOrderImage(offlineUpdateTaskBean)
3 -> uploadEleWorkOrder(offlineUpdateTaskBean)
4 -> uploadEleDamageImage(offlineUpdateTaskBean)
5 -> uploadCustomerSignImage(offlineUpdateTaskBean)
6 -> uploadAcceptPeopleSignImage(offlineUpdateTaskBean)
7 -> uploadServicePeopleSignImage(offlineUpdateTaskBean)
8 -> taskFinish(offlineUpdateTaskBean)
}
}
private fun uploadTask(offlineUpdateTaskBean : OfflineUpdateTaskBean) {
isUploading = true
val updateTaskRequest = UpdateTaskRequest(type = offlineUpdateTaskBean.type,
taskId = offlineUpdateTaskBean.taskId,
userId = offlineUpdateTaskBean.userId,
vehicleId = offlineUpdateTaskBean.vehicleId,
currentState = offlineUpdateTaskBean.currentState,
operateTime = offlineUpdateTaskBean.operateTime,
lat = offlineUpdateTaskBean.updateTaskLat,
lng = offlineUpdateTaskBean.updateTaskLng,
address = offlineUpdateTaskBean.updateTaskAddress,
offlineMode = offlineUpdateTaskBean.offlineMode,
flowType = offlineUpdateTaskBean.flowType,
success = offlineUpdateTaskBean.success,
templatePhotoInfoList = offlineUpdateTaskBean.templatePhotoInfoList)
RetrofitHelper.getDefaultService().updateTask(updateTaskRequest)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<UpdateTaskBean>() {
override fun doSuccess(it : UpdateTaskBean?) {
RoomHelper.db?.offlineTaskDao()
?.deleteOfflineTaskFromId(offlineUpdateTaskBean.primaryId ?: 0)
isUploading = false
offlineListener?.uploadSuccess()
LogUtil.print("离线任务", "success=$it request=${updateTaskRequest.toJson()}")
}
override fun doFailure(code : Int, msg : String?) {
isUploading = false
offlineListener?.uploadFailure(msg)
LogUtil.print("离线任务",
"uploadTask failed==${offlineUpdateTaskBean.toJson()}")
}
})
}
private fun uploadOrderImage(offlineUpdateTaskBean : OfflineUpdateTaskBean) {
isUploading = true
var file = File(offlineUpdateTaskBean.photoLocalWaterMarkerPath ?: "")
if (! file.exists()) {
offlineListener?.uploadFailure("图片未找到!")
LogUtil.print("离线任务 uploadOrderImage",
"uploadTask failed==${offlineUpdateTaskBean.toJson()}")
RoomHelper.db?.offlineTaskDao()
?.deleteOfflineTaskFromId(offlineUpdateTaskBean.primaryId ?: 0)
isUploading = false
return
}
if (offlineUpdateTaskBean.imageAddress.isNullOrBlank() && (offlineUpdateTaskBean.imageLat != null && offlineUpdateTaskBean.imageLat != 0f && offlineUpdateTaskBean.imageLng != null && offlineUpdateTaskBean.imageLng != 0f)) {
geoCoder(offlineUpdateTaskBean.imageLat.toDouble(),
offlineUpdateTaskBean.imageLng.toDouble(),
success = { it ->
val photoMarkerInfo = PhotoMarkerInfo(offlineUpdateTaskBean.needWater,
needShowPhoneBrand = offlineUpdateTaskBean.needPhoneBrand,
path = offlineUpdateTaskBean.photoLocalWaterMarkerPath,
from = "(离线)",
lat = offlineUpdateTaskBean.imageLat.toDouble(),
lng = offlineUpdateTaskBean.imageLng.toDouble(),
address = it,
time = offlineUpdateTaskBean.time,
driverName = GlobalData.driverInfo?.userName,
taskCode = offlineUpdateTaskBean.taskCode)
val offlineTemp = offlineUpdateTaskBean.copy(imageAddress = it)
file = File(PhotoMarkerManager.addPhotoMarker(ActivityUtils.getTopActivity(),
photoMarkerInfo))
CommonMethod.uploadImage(file = file, success = {
val item = RoomHelper.db?.offlineTaskDao()
?.getRecentOfflineTask(offlineTemp.taskId ?: 0)?.get(0)
if (item == null) {
offlineListener?.uploadFailure(it)
isUploading = false
return@uploadImage
}
LogUtil.print("离线任务 getRecentOfflineTask", "success=${item.toJson()}")
val list = item.templatePhotoInfoList?.toMutableList()
val jsonObject = JSONObject()
jsonObject["realTakePhotoTime"] = offlineTemp.realTakePhotoTime ?: ""
jsonObject["photoSource"] = offlineTemp.photoSource
jsonObject["path"] = it
jsonObject["time"] = offlineTemp.time
jsonObject["lat"] = offlineTemp.imageLat
jsonObject["lng"] = offlineTemp.imageLng
jsonObject["address"] = offlineTemp.imageAddress
list?.set(offlineTemp.imageIndex ?: 0, jsonObject.toJSONString())
val temp = item.copy(templatePhotoInfoList = list?.toList())
RoomHelper.db?.offlineTaskDao()?.update(temp)
RoomHelper.db?.offlineTaskDao()
?.deleteOfflineTaskFromId(offlineTemp.primaryId ?: 0)
isUploading = false
offlineListener?.uploadSuccess()
LogUtil.print("离线任务 逆地理 uploadOrderImage",
"success=$it request=${offlineUpdateTaskBean.toJson()}")
}, failed = {
RoomHelper.db?.offlineTaskDao()?.update(offlineTemp)
offlineListener?.uploadFailure(it)
isUploading = false
})
},
failed = {
offlineListener?.uploadFailure(it)
isUploading = false
})
} else {
CommonMethod.uploadImage(file = file, success = {
val item = RoomHelper.db?.offlineTaskDao()
?.getRecentOfflineTask(offlineUpdateTaskBean.taskId ?: 0)?.get(0)
if (item == null) {
offlineListener?.uploadFailure(it)
isUploading = false
return@uploadImage
}
LogUtil.print("离线任务 getRecentOfflineTask", "success=${item.toJson()}")
val list = item.templatePhotoInfoList?.toMutableList()
val jsonObject = JSONObject()
jsonObject["realTakePhotoTime"] = offlineUpdateTaskBean.realTakePhotoTime ?: ""
jsonObject["photoSource"] = offlineUpdateTaskBean.photoSource
jsonObject["path"] = it
jsonObject["time"] = offlineUpdateTaskBean.time
jsonObject["lat"] = offlineUpdateTaskBean.imageLat
jsonObject["lng"] = offlineUpdateTaskBean.imageLng
jsonObject["address"] = offlineUpdateTaskBean.imageAddress
list?.set(offlineUpdateTaskBean.imageIndex ?: 0, jsonObject.toJSONString())
val temp = item.copy(templatePhotoInfoList = list?.toList())
RoomHelper.db?.offlineTaskDao()?.update(temp)
RoomHelper.db?.offlineTaskDao()
?.deleteOfflineTaskFromId(offlineUpdateTaskBean.primaryId ?: 0)
isUploading = false
offlineListener?.uploadSuccess()
LogUtil.print("离线任务 uploadOrderImage",
"success=$it request=${offlineUpdateTaskBean.toJson()}")
}, failed = {
offlineListener?.uploadFailure(it)
isUploading = false
})
}
}
private fun uploadEleDamageImage(offlineUpdateTaskBean : OfflineUpdateTaskBean) {
isUploading = true
val file = File(offlineUpdateTaskBean.imageLocalPath ?: "")
if (! file.exists()) {
offlineListener?.uploadFailure("图片未找到!")
LogUtil.print("离线任务 uploadEleDamageImage",
"failed==${offlineUpdateTaskBean.toJson()}")
RoomHelper.db?.offlineTaskDao()
?.deleteOfflineTaskFromId(offlineUpdateTaskBean.primaryId ?: 0)
isUploading = false
return
}
CommonMethod.uploadImage(file = file, success = {
val item = RoomHelper.db?.offlineTaskDao()
?.getRecentOfflineEle(offlineUpdateTaskBean.taskId ?: 0)?.get(0)
if (item == null) {
offlineListener?.uploadFailure(it)
isUploading = false
return@uploadImage
}
LogUtil.print("离线任务 getRecentOfflineEle", "success=${item.toJson()}")
val list = item.damageFileList?.toMutableList()
list?.set(offlineUpdateTaskBean.imageIndex ?: 0, it)
val temp = item.copy(templatePhotoInfoList = list?.toList())
RoomHelper.db?.offlineTaskDao()?.update(temp)
RoomHelper.db?.offlineTaskDao()
?.deleteOfflineTaskFromId(offlineUpdateTaskBean.primaryId ?: 0)
isUploading = false
offlineListener?.uploadSuccess()
LogUtil.print("离线任务 uploadEleDamageImage",
"success=$it request=${offlineUpdateTaskBean.toJson()}")
}, failed = {
offlineListener?.uploadFailure(it)
isUploading = false
})
}
private fun uploadEleWorkOrder(offlineUpdateTaskBean : OfflineUpdateTaskBean) {
val saveEleOrderRequest = SaveEleOrderRequest(state = offlineUpdateTaskBean.eleState,
userOrderId = offlineUpdateTaskBean.userOrderId,
taskOrderId = offlineUpdateTaskBean.taskId,
damageFileList = offlineUpdateTaskBean.damageFileList,
hasDamage = offlineUpdateTaskBean.hasDamage,
hasSuccess = offlineUpdateTaskBean.hasSuccess,
customerSignPath = offlineUpdateTaskBean.customerSignPath,
recipientSignPath = offlineUpdateTaskBean.recipientSignPath,
waitstaffSignPath = offlineUpdateTaskBean.waitstaffSignPath,
offlineMode = 1,
userOrderCode = offlineUpdateTaskBean.userOrderCode,
lat = offlineUpdateTaskBean.eleLat,
lng = offlineUpdateTaskBean.eleLng,
tyreNumber = offlineUpdateTaskBean.tyreNumber,
isFinish = offlineUpdateTaskBean.isFinish)
LogUtil.print("离线数据 eleSign_check request", saveEleOrderRequest.toJson() ?: "")
RetrofitHelper.getDefaultService().saveElectronOrder(saveEleOrderRequest)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<String>() {
override fun doSuccess(it : String?) {
RoomHelper.db?.offlineTaskDao()
?.deleteOfflineTaskFromId(offlineUpdateTaskBean.primaryId ?: 0)
isUploading = false
offlineListener?.uploadSuccess()
LogUtil.print("离线任务 uploadEleWorkOrder",
"success=$it request=${offlineUpdateTaskBean.toJson()}")
}
override fun doFailure(code : Int, msg : String?) {
offlineListener?.uploadFailure(msg)
isUploading = false
}
})
}
private fun uploadCustomerSignImage(offlineUpdateTaskBean : OfflineUpdateTaskBean) {
isUploading = true
val file = File(offlineUpdateTaskBean.imageLocalPath ?: "")
if (! file.exists()) {
offlineListener?.uploadFailure("图片未找到!")
LogUtil.print("离线任务 uploadCustomerSignImage",
"failed==${offlineUpdateTaskBean.toJson()}")
RoomHelper.db?.offlineTaskDao()
?.deleteOfflineTaskFromId(offlineUpdateTaskBean.primaryId ?: 0)
isUploading = false
return
}
CommonMethod.uploadImage(file = file, success = { it ->
val item = RoomHelper.db?.offlineTaskDao()
?.getRecentOfflineEle(offlineUpdateTaskBean.taskId ?: 0)?.get(0)
if (item == null) {
offlineListener?.uploadFailure(it)
isUploading = false
return@uploadImage
}
LogUtil.print("离线任务 uploadCustomerSignImage getRecentOfflineEle",
"success=${item.toJson()}")
RoomHelper.db?.offlineTaskDao()?.update(item.copy(customerSignPath = it))
RoomHelper.db?.offlineTaskDao()
?.deleteOfflineTaskFromId(offlineUpdateTaskBean.primaryId ?: 0)
RoomHelper.db?.eleWorkOrderDao()?.getEleWorkOrder(offlineUpdateTaskBean.taskId ?: 0)
?.copy(serverCustomSignPath = it)?.let {
RoomHelper.db?.eleWorkOrderDao()?.update(it)
}
isUploading = false
offlineListener?.uploadSuccess()
LogUtil.print("离线任务 uploadCustomerSignImage",
"success=$it request=${offlineUpdateTaskBean.toJson()}")
}, failed = {
offlineListener?.uploadFailure(it)
isUploading = false
})
}
private fun uploadAcceptPeopleSignImage(offlineUpdateTaskBean : OfflineUpdateTaskBean) {
isUploading = true
val file = File(offlineUpdateTaskBean.imageLocalPath ?: "")
if (! file.exists()) {
offlineListener?.uploadFailure("图片未找到!")
LogUtil.print("离线任务 uploadCustomerSignImage",
"failed==${offlineUpdateTaskBean.toJson()}")
RoomHelper.db?.offlineTaskDao()
?.deleteOfflineTaskFromId(offlineUpdateTaskBean.primaryId ?: 0)
isUploading = false
return
}
CommonMethod.uploadImage(file = file, success = { it ->
val item = RoomHelper.db?.offlineTaskDao()
?.getRecentOfflineEle(offlineUpdateTaskBean.taskId ?: 0)?.get(0)
if (item == null) {
offlineListener?.uploadFailure(it)
isUploading = false
return@uploadImage
}
LogUtil.print("离线任务 uploadAcceptPeopleSignImage getRecentOfflineEle",
"success=${item.toJson()}")
RoomHelper.db?.offlineTaskDao()?.update(item.copy(recipientSignPath = it))
RoomHelper.db?.offlineTaskDao()
?.deleteOfflineTaskFromId(offlineUpdateTaskBean.primaryId ?: 0)
RoomHelper.db?.eleWorkOrderDao()?.getEleWorkOrder(offlineUpdateTaskBean.taskId ?: 0)
?.copy(serverAcceptCarSignPath = it)?.let {
RoomHelper.db?.eleWorkOrderDao()?.update(it)
}
isUploading = false
offlineListener?.uploadSuccess()
LogUtil.print("离线任务 uploadAcceptPeopleSignImage",
"success=$it request=${offlineUpdateTaskBean.toJson()}")
}, failed = {
offlineListener?.uploadFailure(it)
isUploading = false
})
}
private fun uploadServicePeopleSignImage(offlineUpdateTaskBean : OfflineUpdateTaskBean) {
isUploading = true
val file = File(offlineUpdateTaskBean.imageLocalPath ?: "")
if (! file.exists()) {
offlineListener?.uploadFailure("图片未找到!")
LogUtil.print("离线任务 uploadCustomerSignImage",
"failed==${offlineUpdateTaskBean.toJson()}")
RoomHelper.db?.offlineTaskDao()
?.deleteOfflineTaskFromId(offlineUpdateTaskBean.primaryId ?: 0)
isUploading = false
return
}
CommonMethod.uploadImage(file = file, success = { it ->
val item = RoomHelper.db?.offlineTaskDao()
?.getRecentOfflineEle(offlineUpdateTaskBean.taskId ?: 0)?.get(0)
if (item == null) {
offlineListener?.uploadFailure(it)
isUploading = false
return@uploadImage
}
LogUtil.print("离线任务 uploadCustomerSignImage getRecentOfflineEle",
"success=${item.toJson()}")
RoomHelper.db?.offlineTaskDao()?.update(item.copy(waitstaffSignPath = it))
RoomHelper.db?.offlineTaskDao()
?.deleteOfflineTaskFromId(offlineUpdateTaskBean.primaryId ?: 0)
RoomHelper.db?.eleWorkOrderDao()?.getEleWorkOrder(offlineUpdateTaskBean.taskId ?: 0)
?.copy(serverServicePeopleSignPath = it)?.let {
RoomHelper.db?.eleWorkOrderDao()?.update(it)
}
isUploading = false
offlineListener?.uploadSuccess()
LogUtil.print("离线任务 uploadCustomerSignImage",
"success=$it request=${offlineUpdateTaskBean.toJson()}")
}, failed = {
offlineListener?.uploadFailure(it)
isUploading = false
})
}
private fun taskFinish(offlineUpdateTaskBean : OfflineUpdateTaskBean) {
isUploading = true
val taskFinishRequest = TaskFinishRequest(
operateTime = offlineUpdateTaskBean.operateTime?.toLong(),
lat = offlineUpdateTaskBean.updateTaskLat,
userOrderId = offlineUpdateTaskBean.userOrderId,
lng = offlineUpdateTaskBean.updateTaskLng,
)
RetrofitHelper.getDefaultService().taskFinish(taskFinishRequest)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BaseObserver<TaskFinishResponse>() {
override fun doSuccess(it : TaskFinishResponse?) {
RoomHelper.db?.offlineTaskDao()
?.deleteOfflineTaskFromId(offlineUpdateTaskBean.primaryId ?: 0)
isUploading = false
offlineListener?.uploadSuccess()
LogUtil.print("离线任务", "success=$it request=${taskFinishRequest.toJson()}")
}
override fun doFailure(code : Int, msg : String?) {
isUploading = false
offlineListener?.uploadFailure(msg)
LogUtil.print("离线任务",
"uploadTask failed==${offlineUpdateTaskBean.toJson()}")
}
})
}
}
//打开系统悬浮窗权限
private fun openSystemAlertPermissionDialog() {
val context = ActivityUtils.getTopActivity()
val alertdialog = AlertDialog.Builder(ActivityUtils.getTopActivity()).setTitle("提示")
.setMessage("当前应用缺少【悬浮窗】必要权限。\n\n请点击\"设置\"-\"权限\"-打开所需权限。")
.setCancelable(false).setPositiveButton("设置") { _, _ ->
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
"package:${context.packageName}".toUri())
intent.data = ("package:" + ActivityUtils.getTopActivity().packageName).toUri()
ActivityUtils.getTopActivity().startActivity(intent)
}
alertdialog.show()
}
//逆地址编码
private fun geoCoder(lat : Double,
lng : Double,
success : (String) -> Unit,
failed : (String) -> Unit = {}) {
val geocoderSearch = GeocodeSearch(ActivityUtils.getTopActivity().application)
val query = RegeocodeQuery(LatLonPoint(lat, lng), 200f, GeocodeSearch.AMAP)
geocoderSearch.getFromLocationAsyn(query)
geocoderSearch.setOnGeocodeSearchListener(object : OnGeocodeSearchListener {
override fun onRegeocodeSearched(regeocodeResult : RegeocodeResult, i : Int) {
if (i == 1000) {
success(regeocodeResult.regeocodeAddress.formatAddress)
LogUtil.print("离线任务 singleLocation 逆地理编码",
regeocodeResult.regeocodeAddress.formatAddress)
} else {
failed("地址获取失败")
LogUtil.print("singleLocation 逆地理失败 ", regeocodeResult.toString())
}
}
override fun onGeocodeSearched(geocodeResult : GeocodeResult, i : Int) {}
})
}

View File

@ -0,0 +1,270 @@
package com.za.offline
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PixelFormat
import android.os.Build
import android.view.GestureDetector
import android.view.GestureDetector.OnGestureListener
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
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.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.blankj.utilcode.util.ActivityUtils
import com.za.base.theme.headBgColor
import com.za.room.RoomHelper
@SuppressLint("StaticFieldLeak")
object OfflineService : IOfflineListener {
private var viewcompose : View? = null
private var lifecycleOwner : MyOfflineServiceViewLifecycleOwner? = null
private var windowManager : WindowManager? = null
private var layoutParams : WindowManager.LayoutParams? = null
private var currentOfflineUpdateTaskBean = mutableStateOf<OfflineUpdateTaskBean?>(null)
private var state = mutableIntStateOf(0) //0 初始化 1 准备上传 2 结束上传 3上传中 4 上传失败 5 上传完成
private var onGestureListener =
GestureDetector(ActivityUtils.getTopActivity().application, object : OnGestureListener {
var xDown : Int = 0
var yDown : Int = 0
override fun onDown(motionEvent : MotionEvent) : Boolean {
xDown = motionEvent.rawX.toInt()
yDown = motionEvent.rawY.toInt()
return false
}
override fun onShowPress(e : MotionEvent) {
}
override fun onSingleTapUp(e : MotionEvent) : Boolean {
val context = ActivityUtils.getTopActivity()
return true
}
override fun onScroll(e1 : MotionEvent?,
motionEvent : MotionEvent,
distanceX : Float,
distanceY : Float) : Boolean {
val nowX = motionEvent.rawX.toInt()
val nowY = motionEvent.rawY.toInt()
val movedX = nowX - xDown
val movedY = nowY - yDown
xDown = nowX
yDown = nowY
layoutParams?.apply {
x += movedX
y += movedY
} //更新悬浮球控件位置
updateView()
return true
}
override fun onLongPress(e : MotionEvent) {
}
override fun onFling(e1 : MotionEvent?,
e2 : MotionEvent,
velocityX : Float,
velocityY : Float) : Boolean {
return true
}
})
init {
windowManager =
ActivityUtils.getTopActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager
layoutParams = WindowManager.LayoutParams()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams?.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
layoutParams?.type = WindowManager.LayoutParams.TYPE_PHONE
}
layoutParams?.format = PixelFormat.RGBA_8888
layoutParams?.width = ViewGroup.LayoutParams.WRAP_CONTENT
layoutParams?.height = ViewGroup.LayoutParams.WRAP_CONTENT
layoutParams?.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
}
fun addListener() {
OfflineManager.addOfflineListener(this)
}
@SuppressLint("ClickableViewAccessibility")
private fun showWindow() {
destroyView()
viewcompose = ComposeView(ActivityUtils.getTopActivity().application).apply {
setContent {
MyOfflineServiceView(currentOfflineUpdateTaskBean.value, state = state.intValue)
}
}.rootView
viewcompose?.setOnTouchListener { _, event ->
onGestureListener.onTouchEvent(event)
if (event.action == MotionEvent.ACTION_UP) {
}
true
}
lifecycleOwner = MyOfflineServiceViewLifecycleOwner().also {
it.onCreate() // 注意
it.onStart()
it.attachToDecorView(viewcompose)
}
windowManager?.addView(viewcompose, layoutParams)
}
private fun destroyView() {
viewcompose?.let {
windowManager?.removeViewImmediate(viewcompose)
}
viewcompose = null
lifecycleOwner?.onDestroy()
lifecycleOwner = null
}
private fun updateView() {
windowManager?.updateViewLayout(viewcompose, layoutParams)
}
override fun start() {
showWindow()
state.intValue = 1
}
override fun stop() {
destroyView()
}
override fun uploadSuccess() {
state.intValue = 4
}
override fun uploadFailure(msg : String?) {
state.intValue = 3
}
override fun currentTask(offlineUpdateTaskBean : OfflineUpdateTaskBean) {
state.intValue = 2
currentOfflineUpdateTaskBean.value = offlineUpdateTaskBean
}
}
//0 初始化 1 准备上传 2 上传中 3 上传失败 4 上传完成
@Composable
fun MyOfflineServiceView(offlineUpdateTaskBean : OfflineUpdateTaskBean?, state : Int) {
Column(modifier = Modifier
.fillMaxWidth()
.background(color = Color.White, shape = RoundedCornerShape(8.dp))
.border(border = BorderStroke(width = 1.dp, color = Color.Red),
shape = RoundedCornerShape(8.dp))
.padding(10.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text("离线订单:", color = Color.Gray, fontSize = 15.sp)
Spacer(modifier = Modifier.width(5.dp))
Text(offlineUpdateTaskBean?.taskCode ?: "",
color = Color.Black,
fontWeight = FontWeight.Bold)
}
Spacer(modifier = Modifier.height(10.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Text("剩余任务:", color = Color.Gray, fontSize = 15.sp)
Spacer(modifier = Modifier.width(5.dp))
Text("${
RoomHelper.db?.offlineTaskDao()
?.getOfflineTaskFromTaskId(offlineUpdateTaskBean?.taskId ?: 0)?.size
}", color = Color.Black, fontWeight = FontWeight.Medium)
}
Spacer(modifier = Modifier.height(10.dp))
Row(modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start) {
Text("当前节点:", color = Color.Gray, fontSize = 15.sp)
Spacer(Modifier.width(5.dp))
Text(offlineUpdateTaskBean?.offlineTitle ?: "",
color = Color.Black,
fontWeight = FontWeight.Bold,
fontSize = 15.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis)
when (state) {
1 -> {
Spacer(modifier = Modifier.weight(1f))
Text("准备上传",
color = headBgColor,
fontSize = 12.sp,
fontWeight = FontWeight.Bold)
Spacer(modifier = Modifier.width(5.dp))
}
2 -> {
Spacer(modifier = Modifier.weight(1f))
Text("上传中",
color = headBgColor,
fontSize = 12.sp,
fontWeight = FontWeight.Bold)
Spacer(modifier = Modifier.width(3.dp))
CircularProgressIndicator(modifier = Modifier.size(12.dp), color = headBgColor)
Spacer(modifier = Modifier.width(5.dp))
}
3 -> {
Spacer(modifier = Modifier.weight(1f))
Text("上传失败",
color = Color.Red,
fontSize = 12.sp,
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis)
Spacer(modifier = Modifier.width(5.dp))
}
4 -> {
Spacer(modifier = Modifier.weight(1f))
Text("上传完成",
color = headBgColor,
fontSize = 12.sp,
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis)
Spacer(modifier = Modifier.width(5.dp))
}
}
}
}
}

View File

@ -0,0 +1,90 @@
package com.za.offline
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverter
import androidx.room.TypeConverters
import com.alibaba.fastjson.JSON
@TypeConverters(InspectionConverter::class)
@Entity(tableName = "offline_update_task_bean")
data class OfflineUpdateTaskBean(
@PrimaryKey(autoGenerate = true)
val primaryId: Int? = null,
//以下为上传状态的参数
val type: String? = null,
val taskId: Int? = null,
val userId: Int? = null,
val vehicleId: Int? = null,
val currentState: String? = null,
val operateTime: String? = null,
val updateTaskLat: Double? = null,
val updateTaskLng: Double? = null,
val updateTaskAddress: String? = null,
val offlineMode: Int? = null, //0 正常 1 离线
val flowType: Int? = null,
val success: Int? = null, //作业是否完成 0 成功 1不成功 拖车默认成功
val templatePhotoInfoList: List<String?>? = null, //照片路径,使用@分隔符分开
//以下为上传图片的参数
val realTakePhotoTime: String? = null,//真实拍照时间
val photoSource: Int? = null,//1相机 2 相册 3真实位置
val time: String? = null,// 拍照时间
val imageLat: Float? = null,
val imageLng: Float? = null,
val imageAddress: String? = null,
val imageIndex: Int? = null,
val imageLocalPath: String? = null,
val imageUploadPath: String? = null,
val advanceTime: Long? = null,
val needWater:Boolean?=null,
val needPhoneBrand:Boolean?=null,
val photoLocalWaterMarkerPath: String? = null,
//以下为电子工单状态
val eleState: Int? = null,
val hasDamage: Int? = null, //是否有算上 1 有
val hasSuccess: Int? = null, // 作业是否完成 1成功 0失败
val customerSignPath: String? = null, //客户签名照片
val recipientSignPath: String? = null, //接车人签名
val waitstaffSignPath: String? = null, //服务人员签名
val damageFileList: List<String?>? = null, //损伤文件列表 //照片路径,使用,分隔符分开
val userOrderCode: String? = null, //仅在历史补传电子工单时传
val tyreNumber: Int? = null, //小轮个数
val eleLng: Double? = null,
val eleLat: Double? = null,
val isFinish: Boolean = false, //订单是否完成进入历史 true 完成
//以下为自定义字段
val userOrderId: Int? = null,
val taskCode: String? = null,
val isUpload: Int = 0,//是否已经上传 0 未上传 1 已经上传
val offlineTime: Long = System.currentTimeMillis(), //添加离线的时间
val offlineTitle: String? = null,
val offlineTimeStr: String? = com.blankj.utilcode.util.TimeUtils.getNowString(),
val offlineType: Int? = null,// 1 订单状态 2 订单图片 3 电子工单状态 4 电子工单损伤照片 5客户签名照片 6接车人签名照片 7服务人员签名照片 8 任务完成
)
class InspectionConverter {
@TypeConverter
fun stringToObject(value: String?): List<String?>? {
if (value.isNullOrBlank()) {
return null
}
val str = JSON.parseArray(value, String::class.java)
return str
}
@TypeConverter
fun objectToString(list: List<String?>?): String? {
if (list.isNullOrEmpty()) {
return null
}
val str = JSON.toJSONString(list)
return str
}
}

View File

@ -0,0 +1,74 @@
package com.za.room
import android.annotation.SuppressLint
import android.content.Context
import androidx.room.Room.databaseBuilder
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.za.base.Const
import com.za.room.db.GlobalRoom
@SuppressLint("StaticFieldLeak")
object RoomHelper {
const val VERSION: Int = 35
private lateinit var mContext: Context
var db: GlobalRoom? = null
//ele_work_order 表中新增userOrderId字段
//ele_car_damage_photo 表中新增userOrderId
private var MIGRATION_2_3: Migration = object : Migration(2, 3) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE ele_work_order" + " ADD COLUMN userOrderId INTEGER NOT NULL DEFAULT 0")
db.execSQL("ALTER TABLE ele_car_damage_photo" + " ADD COLUMN userOrderId INTEGER NOT NULL DEFAULT 0")
}
}
//增加水印模版
private var MIGRATION_3_4: Migration = object : Migration(3, 4) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("CREATE TABLE water_template(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + "templateName TEXT," + "taskCode TEXT," + "templateId INTEGER NOT NULL DEFAULT 0)")
db.execSQL("CREATE TABLE water_marker_item(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + "waterTemplateId TEXT," + "taskCode TEXT," + "water_left DECIMAL," + "water_right DECIMAL," + "top DECIMAL," + "bottom DECIMAL," + "content TEXT," + "color TEXT," + "wideFont TEXT," + "highFont TEXT)")
}
}
fun init(context: Context) {
this.mContext = context
db = databaseBuilder(mContext, GlobalRoom::class.java, "zd_db")
.allowMainThreadQueries().addMigrations(MIGRATION_2_3, MIGRATION_3_4)
.fallbackToDestructiveMigration() //出现异常问题,重新构建表,同时数据会丢失
.setJournalMode(RoomDatabase.JournalMode.AUTOMATIC).build()
}
fun clearAll() {
db?.orderDao()?.deleteAllOrder()
db?.photoTemplateDao()?.deleteAllPhotoTemplate()
db?.changeBatteryDao()?.deleteAll()
db?.eleWorkOrderDao()?.deleteTable()
db?.offlineTaskDao()?.deleteAllOfflineTask()
val temp = db?.waterMarkerDao()?.queryAll()
temp?.forEach {
if (it.waterMarkerTemplateBean?.taskCode != Const.NormalWaterMarker) {
db?.waterMarkerDao()?.deleteWaterMarkerItem(it.waterMarkerTemplateBean?.taskCode
?: "")
db?.waterMarkerDao()?.deleteWaterTemplate(it.waterMarkerTemplateBean?.taskCode
?: "")
}
}
}
fun clearOrderFromTaskCode(taskId: Int) {
db?.orderDao()?.deleteOrderFromTaskId(taskId)
db?.photoTemplateDao()?.deleteOrderPhotoTemplateFromTaskId(taskId)
db?.changeBatteryDao()?.deleteFromTaskId(taskId)
db?.eleWorkOrderDao()?.deleteOrder(taskId)
val temp = db?.waterMarkerDao()?.queryAll()
temp?.forEach {
if (it.waterMarkerTemplateBean?.taskCode != Const.NormalWaterMarker && it.waterMarkerTemplateBean?.taskId == taskId) {
db?.waterMarkerDao()?.deleteWaterMarkerItem(it.waterMarkerTemplateBean.taskCode
?: "")
db?.waterMarkerDao()?.deleteWaterTemplate(it.waterMarkerTemplateBean.taskCode ?: "")
}
}
}
}

View File

@ -0,0 +1,49 @@
package com.za.room.db
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.za.bean.db.ChangeBatteryPhoto
import com.za.bean.db.NewPhotoTemplateBean
import com.za.bean.db.ele.EleCarDamagePhotoBean
import com.za.bean.db.ele.EleWorkOrderBean
import com.za.bean.db.order.OrderInfo
import com.za.bean.db.order.PhotoTemplateInfo
import com.za.bean.db.water_marker.WaterMarkerItemBean
import com.za.bean.db.water_marker.WaterMarkerTemplateBean
import com.za.offline.InspectionConverter
import com.za.offline.OfflineDao
import com.za.offline.OfflineUpdateTaskBean
import com.za.room.RoomHelper
import com.za.room.db.ele_work.EleCarDamagePhotoDao
import com.za.room.db.ele_work.EleWorkOrderDao
import com.za.room.db.order.ChangeBatteryDao
import com.za.room.db.order.OrderDao
import com.za.room.db.order.PhotoTemplateDao
import com.za.room.db.water_marker.WaterMarkerDao
@Database(entities = [EleWorkOrderBean::class,
EleCarDamagePhotoBean::class,
WaterMarkerTemplateBean::class,
WaterMarkerItemBean::class,
ChangeBatteryPhoto::class,
NewPhotoTemplateBean::class,
OrderInfo::class,
OfflineUpdateTaskBean::class,
PhotoTemplateInfo::class], version = RoomHelper.VERSION, exportSchema = false)
abstract class GlobalRoom : RoomDatabase() {
abstract fun eleWorkOrderDao(): EleWorkOrderDao
abstract fun eleCarDamagePhotoDao(): EleCarDamagePhotoDao
abstract fun waterMarkerDao(): WaterMarkerDao
abstract fun changeBatteryDao(): ChangeBatteryDao
abstract fun orderDao(): OrderDao
abstract fun photoTemplateDao(): PhotoTemplateDao
abstract fun offlineTaskDao(): OfflineDao
}

View File

@ -0,0 +1,34 @@
package com.za.room.db.ele_work
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import com.za.bean.db.ele.EleCarDamagePhotoBean
@Dao
interface EleCarDamagePhotoDao {
@Insert(onConflict = OnConflictStrategy.REPLACE, entity = EleCarDamagePhotoBean::class)
fun insert(eleCarDamagePhotoBean: EleCarDamagePhotoBean)
@Query("DELETE FROM `ele_car_damage_photo` WHERE orderId =:orderId")
fun deleteAll(orderId: Int)
@Query("DELETE FROM `ele_car_damage_photo`")
fun deleteTable()
@Delete(entity = EleCarDamagePhotoBean::class)
fun delete(eleCarDamagePhotoBean: EleCarDamagePhotoBean)
@Query("SELECT * FROM ele_car_damage_photo WHERE orderId =:orderId")
fun getEleCarDamagePhotos(orderId: Int): List<EleCarDamagePhotoBean>?
@Query("SELECT * FROM ele_car_damage_photo WHERE id =:id")
fun getEleCarDamagePhoto(id: Long): EleCarDamagePhotoBean?
//更新
@Update(entity = EleCarDamagePhotoBean::class, onConflict = OnConflictStrategy.REPLACE)
fun update(eleCarDamagePhotoBean: EleCarDamagePhotoBean)
}

View File

@ -0,0 +1,27 @@
package com.za.room.db.ele_work
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import com.za.bean.db.ele.EleWorkOrderBean
@Dao
interface EleWorkOrderDao {
@Insert(onConflict = OnConflictStrategy.REPLACE, entity = EleWorkOrderBean::class)
fun insertEleWorkOrder(eleWorkOrderBean: EleWorkOrderBean)
@Query("DELETE FROM `ele_work_order` WHERE orderId =:taskId")
fun deleteOrder(taskId: Int)
@Query("DELETE FROM `ele_work_order`")
fun deleteTable()
@Query("SELECT * FROM ele_work_order WHERE orderId =:taskId")
fun getEleWorkOrder(taskId: Int): EleWorkOrderBean?
//更新
@Update(onConflict = OnConflictStrategy.REPLACE, entity = EleWorkOrderBean::class)
fun update(eleWorkOrderBean: EleWorkOrderBean)
}

View File

@ -0,0 +1,29 @@
package com.za.room.db.order
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import com.za.base.Const
import com.za.bean.db.order.PhotoTemplateInfo
@Dao
interface ChangeBatteryDao {
@Insert(onConflict = OnConflictStrategy.REPLACE, entity = PhotoTemplateInfo::class)
fun insert(photoTemplateInfo: PhotoTemplateInfo)
//获取当前节点的图片模板
@Query("select * from photo_template_bean where taskCode =:taskCode and myCustomPhotoType=${Const.PhotoType.ChangeBattery}")
fun getAllChangeBatteryPhoto(taskCode: String): List<PhotoTemplateInfo>?
@Update(onConflict = OnConflictStrategy.REPLACE)
fun update(photoTemplateInfo: PhotoTemplateInfo)
@Query("delete from photo_template_bean where taskId=:taskId and myCustomPhotoType=${Const.PhotoType.ChangeBattery}")
fun deleteFromTaskId(taskId: Int)
@Query("delete from photo_template_bean where myCustomPhotoType=${Const.PhotoType.ChangeBattery}")
fun deleteAll()
}

View File

@ -0,0 +1,39 @@
package com.za.room.db.order
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import com.za.bean.db.order.OrderInfo
@Dao
interface OrderDao {
//更换电瓶照片
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrder(orderInfo: OrderInfo)
@Query("select * from order_info where taskId =:taskId")
fun getOrderInfoFromTaskId(taskId: Int): OrderInfo?
//获取当前正在执行的订单
@Query("select * from order_info where isCurrent ==1")
fun getCurrentOrder(): OrderInfo?
//获取不是当前正在执行的订单
@Query("select * from order_info where isCurrent !=1")
fun getAllNotCurrentOrder(): List<OrderInfo>?
//获取所有的订单
@Query("select * from order_info")
fun getAllOrder(): List<OrderInfo>?
@Query("delete from order_info")
fun deleteAllOrder()
@Query("delete from order_info where taskId =:taskId")
fun deleteOrderFromTaskId(taskId: Int)
@Update(onConflict = OnConflictStrategy.REPLACE)
fun update(orderInfo: OrderInfo)
}

View File

@ -0,0 +1,44 @@
package com.za.room.db.order
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import com.za.base.Const
import com.za.bean.db.order.PhotoTemplateInfo
@Dao
interface PhotoTemplateDao {
@Insert(onConflict = OnConflictStrategy.REPLACE, entity = PhotoTemplateInfo::class)
fun insert(photoTemplateInfo: PhotoTemplateInfo)
//获取当前节点的图片模板
@Query("select * from photo_template_bean where myCustomPhotoType=${Const.PhotoType.InServicing}")
fun getAll(): List<PhotoTemplateInfo>?
//获取当前节点的图片模板
@Query("select * from photo_template_bean where taskNode =:taskNode and userOrderId=:userOrderId " +
"and myCustomPhotoType=${Const.PhotoType.InServicing}")
fun getOrderPhotoTemplateFromTaskNode(taskNode: Int, userOrderId: Int): List<PhotoTemplateInfo>?
//获取当前节点的图片模板
@Query("select * from photo_template_bean where taskNode =:taskNode and taskId=:taskId " +
"and myCustomPhotoType=${Const.PhotoType.InServicing}")
fun getOrderPhotoTemplateFromTaskNodeAndTaskId(taskNode: Int, taskId: Int): List<PhotoTemplateInfo>?
//获取当前节点的图片模板
@Query("select * from photo_template_bean where userOrderId =:userOrderId " +
"and myCustomPhotoType=${Const.PhotoType.InServicing}")
fun queryCurrentOrderHasTaskNode(userOrderId: Int): List<PhotoTemplateInfo>?
@Update(onConflict = OnConflictStrategy.REPLACE)
fun update(photoTemplateInfo: PhotoTemplateInfo)
@Query("delete from photo_template_bean where myCustomPhotoType=${Const.PhotoType.InServicing}")
fun deleteAllPhotoTemplate()
@Query("delete from photo_template_bean where taskId =:taskId and myCustomPhotoType=${Const.PhotoType.InServicing}")
fun deleteOrderPhotoTemplateFromTaskId(taskId: Int)
}

View File

@ -0,0 +1,43 @@
package com.za.room.db.water_marker
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import com.za.bean.db.water_marker.WaterMarkerItemBean
import com.za.bean.db.water_marker.WaterMarkerTemplateAndItemRef
import com.za.bean.db.water_marker.WaterMarkerTemplateBean
@Dao
interface WaterMarkerDao {
@Transaction
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertWaterMarkerTemplate(waterMarkerTemplateBean: WaterMarkerTemplateBean)
@Transaction
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertWaterMarkerItem(waterMarkerItemBean: WaterMarkerItemBean)
@Transaction
@Query("delete from water_template where taskCode=:taskCode")
fun deleteWaterTemplate(taskCode: String)
@Transaction
@Query("delete from water_marker_item where taskCode=:taskCode")
fun deleteWaterMarkerItem(taskCode: String)
@Transaction
@Query("select * from water_template where taskCode=:taskCode")
fun query(taskCode: String): WaterMarkerTemplateAndItemRef?
@Transaction
@Query("select * from water_template")
fun queryAll(): List<WaterMarkerTemplateAndItemRef>?
@Query("delete from water_template")
fun deleteWaterTemplate()
@Query("delete from water_marker_item")
fun deleteWaterTemplateItem()
}

View File

@ -0,0 +1,222 @@
package com.za.service
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.media.RingtoneManager
import android.os.Build
import androidx.core.app.NotificationCompat
import cn.jiguang.api.utils.JCollectionAuth
import cn.jpush.android.api.JPushInterface
import com.google.gson.Gson
import com.za.bean.JpushBean
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.common.util.DeviceUtil
import com.za.service.mqtt.MyMqttClient
import com.za.servicing.R
import java.util.concurrent.atomic.AtomicReference
interface PushListener {
fun newOrderMsg(jpushBean : JpushBean)
fun giveUpOrder(jpushBean : JpushBean)
fun revokeOrder(jpushBean : JpushBean)
fun reDispatchOrder(jpushBean : JpushBean)
fun broadcast(string : String)
fun importantTip(jpushBean : JpushBean)
}
data class LastJPushBean(val msg : String, val time : Long = System.currentTimeMillis())
object ServiceManager {
private val pushListener = AtomicReference<PushListener>()
private var lastJPushBean : LastJPushBean? = null
// Initialize SharedPreferences
fun initialize(context : Context) {
LogUtil.print("ServiceManager", "Initializing ServiceManager")
jpushRegister(context)
MyMqttClient.initialize(deviceId = DeviceUtil.getAndroidId(context))
}
// Register JPush
private fun jpushRegister(context : Context) {
try {
JCollectionAuth.setAuth(context, true)
JPushInterface.setDebugMode(false)
JPushInterface.init(context)
LogUtil.print("ServiceManager", "JPush initialized successfully")
} catch (e : Exception) {
LogUtil.print("ServiceManager", "JPush initialization failed: ${e.message}")
}
}
// Register push listener
fun registerPushListener(listener : PushListener?) {
listener?.let {
pushListener.set(it)
LogUtil.print("ServiceManager", "Registered push listener: ${it.javaClass.simpleName}")
}
}
// Handle incoming push messages
fun handlerPushMsg(msg : String) {
LogUtil.print("JpushMessage", "Received push message: $msg")
if (msg.startsWith("broadcast:")) {
handleBroadcast(msg)
return
}
try {
val jpushOrderInfoBean = Gson().fromJson(msg, JpushBean::class.java)
if (lastJPushBean != null && (System.currentTimeMillis() - lastJPushBean !!.time < 3000) && lastJPushBean !!.msg == jpushOrderInfoBean.taskCode) {
LogUtil.print("MessageHandler", "Duplicate message: " + lastJPushBean?.msg)
return
}
jpushOrderInfoBean?.taskCode?.let {
lastJPushBean = LastJPushBean(it)
}
sendSystemNotificationFromMessage(jpushOrderInfoBean)
when (jpushOrderInfoBean.pushType) {
0 -> newOrderMsg(jpushOrderInfoBean)
1 -> handleTypeOneMessage(jpushOrderInfoBean)
3 -> importantTip(jpushOrderInfoBean)
else -> LogUtil.print("JpushMessage",
"Unknown push type: ${jpushOrderInfoBean.pushType}")
}
} catch (e : Exception) {
if (msg.startsWith("broadcast:")) {
handleBroadcast(msg)
}
LogUtil.print("JpushMessage", "Error handling message: ${e.message}")
}
}
// Handle broadcast messages
private fun handleBroadcast(msg : String) {
try {
val content = msg.substring(10)
pushListener.get()?.broadcast(content)
sendNotification(GlobalData.application, content)
LogUtil.print("JpushMessage", "Broadcast content: $content")
} catch (e : Exception) {
LogUtil.print("JpushMessage", "Broadcast failed: ${e.message}")
}
}
// Handle type one messages
private fun handleTypeOneMessage(jpushOrderBean : JpushBean) {
when (jpushOrderBean.typeDesc) {
"giveUp" -> giveUpOrder(jpushOrderBean)
"revoke" -> revokeOrder(jpushOrderBean)
"reDispatch" -> reDispatchOrder(jpushOrderBean)
else -> LogUtil.print("JpushMessage", "Unknown typeDesc: ${jpushOrderBean.typeDesc}")
}
}
// Handle new order messages
private fun newOrderMsg(jpushOrderBean : JpushBean) {
try {
pushListener.get()?.newOrderMsg(jpushOrderBean)
} catch (e : Exception) {
LogUtil.print("JpushMessage", "Failed to handle new order message: ${e.message}")
}
}
// Handle give up order messages
private fun giveUpOrder(jpushOrderBean : JpushBean) {
pushListener.get()?.giveUpOrder(jpushOrderBean)
}
// Handle revoke order messages
private fun revokeOrder(jpushOrderBean : JpushBean) {
pushListener.get()?.revokeOrder(jpushOrderBean)
}
// Handle re-dispatch order messages
private fun reDispatchOrder(jpushOrderBean : JpushBean) {
pushListener.get()?.reDispatchOrder(jpushOrderBean)
}
// Handle important tip messages
private fun importantTip(jpushOrderBean : JpushBean) {
pushListener.get()?.importantTip(jpushOrderBean)
}
// Disconnect from JPush and MQTT
fun disconnect(context : Context) {
try {
JPushInterface.stopPush(context) // Stop JPush
MyMqttClient.disconnect() // Disconnect MQTT
LogUtil.print("ServiceManager", "Disconnected from JPush and MQTT successfully")
} catch (e : Exception) {
LogUtil.print("ServiceManager", "Error during disconnection: ${e.message}")
}
}
private const val CHANNEL_ID = "ImportantMessagesChannel"
private const val NOTIFICATION_ID = 1003
// Initialize notification channel
private fun createNotificationChannel(context : Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(CHANNEL_ID,
"订单通知",
NotificationManager.IMPORTANCE_HIGH).apply {
description = "用于接收重要消息通知"
setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
Notification.AUDIO_ATTRIBUTES_DEFAULT)
enableVibration(true)
}
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
private fun sendSystemNotificationFromMessage(jpushOrderInfoBean : JpushBean) {
when (jpushOrderInfoBean.pushType) {
0 -> sendNotification(GlobalData.application,
"您有新的 ${jpushOrderInfoBean.addressProperty ?: ""} ${jpushOrderInfoBean.serviceTypeName ?: ""}: ${jpushOrderInfoBean.taskCode ?: ""}")
1 -> {
when (jpushOrderInfoBean.typeDesc) {
"giveUp" -> sendNotification(GlobalData.application,
"订单:${jpushOrderInfoBean.taskCode ?: ""}已被放弃!")
"revoke" -> sendNotification(GlobalData.application,
"订单:${jpushOrderInfoBean.taskCode ?: ""}已被撤回!")
"reDispatch" -> sendNotification(GlobalData.application,
"订单:${jpushOrderInfoBean.taskCode ?: ""}被改派!")
else -> {}
}
}
3 -> sendNotification(GlobalData.application,
"重要提醒:${jpushOrderInfoBean.tipContent ?: ""}")
else -> {}
}
}
// Send notification
private fun sendNotification(context : Context, message : String) {
createNotificationChannel(context)
val notification =
NotificationCompat.Builder(context, CHANNEL_ID).setContentTitle("重要通知")
.setContentText(message).setSmallIcon(R.mipmap.ic_launcher) // 替换为你的应用图标
.setPriority(NotificationCompat.PRIORITY_HIGH).setAutoCancel(true) // 点击后自动取消通知
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
.setVibrate(longArrayOf(0, 100, 200, 300)).build()
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(NOTIFICATION_ID, notification)
}
}

View File

@ -0,0 +1,38 @@
package com.za.service.jpush
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import cn.jpush.android.api.JPushInterface
import com.za.common.log.LogUtil
import com.za.service.ServiceManager
/**
* Created by zhangj on 2019/4/4.
*/
class JPushReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val bundle = intent.extras
LogUtil.print("JpushMessage ", "onReceive==" + "action:" + intent.action)
if (intent.action == null || bundle == null) { return }
when (intent.action) {
JPushInterface.ACTION_REGISTRATION_ID -> {
val regId = bundle.getString(JPushInterface.EXTRA_REGISTRATION_ID)
LogUtil.print("JpushMessage ", "Registration successful: $regId")
}
JPushInterface.ACTION_MESSAGE_RECEIVED -> {
val msg = bundle.getString(JPushInterface.EXTRA_MESSAGE)
if (msg.isNullOrBlank()) { return }
ServiceManager.handlerPushMsg(msg)
}
JPushInterface.ACTION_NOTIFICATION_RECEIVED -> {
val title = bundle.getString(JPushInterface.EXTRA_NOTIFICATION_TITLE)
LogUtil.print("JPushReceiver:", "ACTION_NOTIFICATION_RECEIVED title: $title")
}
else -> {}
}
}
}

View File

@ -0,0 +1,71 @@
package com.za.service.jpush
import android.content.Context
import android.util.Log
import cn.jpush.android.api.CmdMessage
import cn.jpush.android.api.JPushMessage
import cn.jpush.android.service.JPushMessageReceiver
import com.za.common.log.LogUtil
/**
* 自定义JPush message 接收器,包括操作tag/alias的结果返回(仅仅包含tag/alias新接口部分)
*/
class MyJPushMessageReceiver : JPushMessageReceiver() {
override fun onRegister(context: Context, s: String) {
super.onRegister(context, s)
LogUtil.print("JPushConnected onRegister", "REGID$s")
}
override fun onConnected(context: Context, b: Boolean) {
super.onConnected(context, b)
LogUtil.print("JPushConnected", "极光连接情况:$b")
// Global.isJpushConnected = b;
// if (b && null != Global.VEHICLE_INFO && !Global.isAliasSuccess)
// JPushInterface.setAlias(context, 5, AppUrlConfig.isRelease ? (Global.VEHICLE_INFO.vehicleId + "") : (Global.JPUSH_PREFIX + Global.VEHICLE_INFO.vehicleId));
}
override fun onTagOperatorResult(context: Context, jPushMessage: JPushMessage) {
// TagAliasOperatorHelper.getInstance().onTagOperatorResult(context, jPushMessage);
super.onTagOperatorResult(context, jPushMessage)
}
override fun onCheckTagOperatorResult(context: Context, jPushMessage: JPushMessage) {
// TagAliasOperatorHelper.getInstance().onCheckTagOperatorResult(context, jPushMessage);
super.onCheckTagOperatorResult(context, jPushMessage)
}
override fun onAliasOperatorResult(context: Context, jPushMessage: JPushMessage) {
// LogUtil.getInstance().print("极光 alias", "0->" + JSON.toJSONString(jPushMessage));
// TagAliasOperatorHelper.getInstance().onAliasOperatorResult(context, jPushMessage);
super.onAliasOperatorResult(context, jPushMessage)
}
override fun onMobileNumberOperatorResult(context: Context, jPushMessage: JPushMessage) {
// TagAliasOperatorHelper.getInstance().onMobileNumberOperatorResult(context, jPushMessage);
super.onMobileNumberOperatorResult(context, jPushMessage)
}
override fun onCommandResult(context: Context, cmdMessage: CmdMessage) {
//注册失败+三方厂商注册回调
LogUtil.print("极光 厂商", "[onCommandResult] $cmdMessage");
//cmd为10000时说明为厂商token回调
if (cmdMessage.cmd == 10000 && cmdMessage.extra != null) {
val token = cmdMessage.extra.getString("token")
val platform = cmdMessage.extra.getInt("platform")
var deviceName = "unkown"
when (platform) {
1 -> deviceName = "小米"
2 -> deviceName = "华为"
3 -> deviceName = "魅族"
4 -> deviceName = "OPPO"
5 -> {
deviceName = "VIVO"
Log.e("厂商", "[onCommandResult] vivo:$token")
}
8 -> deviceName = "FCM"
}
LogUtil.print("极光 厂商", "获取到 $deviceName 的token:$token");
}
}
}

View File

@ -0,0 +1,6 @@
package com.za.service.jpush
import cn.jpush.android.service.JCommonService
class PushService : JCommonService()

View File

@ -0,0 +1,492 @@
package com.za.service.location
import android.annotation.SuppressLint
import android.content.Context
import android.location.Geocoder
import android.location.Location
import android.location.LocationManager
import android.os.Build
import androidx.core.content.ContextCompat
import androidx.core.location.LocationManagerCompat
import com.amap.api.location.AMapLocation
import com.amap.api.location.AMapLocationClient
import com.amap.api.location.AMapLocationClientOption
import com.amap.api.location.AMapLocationListener
import com.amap.api.services.core.LatLonPoint
import com.amap.api.services.geocoder.GeocodeResult
import com.amap.api.services.geocoder.GeocodeSearch
import com.amap.api.services.geocoder.GeocodeSearch.OnGeocodeSearchListener
import com.amap.api.services.geocoder.RegeocodeQuery
import com.amap.api.services.geocoder.RegeocodeResult
import com.blankj.utilcode.util.NetworkUtils
import com.za.bean.request.UploadGpsRequest
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.ext.toJson
import com.za.net.CommonMethod
import com.za.service.mqtt.MyMqttClient
import java.util.Locale
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
@SuppressLint("StaticFieldLeak")
object ZdLocationManager : AMapLocationListener {
private const val TAG = "ZdLocationManager"
private var aMapLocationClient : AMapLocationClient? = null
private var context : Context? = null
private var option : AMapLocationClientOption? = null
private const val NORMA_INTERVAL_TIME = 20000L // 普通的GPS上传时间间隔
private const val LOCATION_CACHE_DURATION = 60 * 1000L // 1分钟的缓存时间
private const val LOCATION_SEARCH_RADIUS = 200f
private const val DEFAULT_TIMEOUT = 30000L
private var globalLocation : AMapLocation? = null
private var lastSingleLocationTime = 0L
fun init(context : Context) {
this.context = context.applicationContext
initAMapLocationClient()
}
private fun initAMapLocationClient() {
context?.let { ctx ->
try {
AMapLocationClient.updatePrivacyShow(ctx, true, true)
AMapLocationClient.updatePrivacyAgree(ctx, true)
option = createLocationOption()
aMapLocationClient = AMapLocationClient(ctx).apply {
setLocationOption(option)
setLocationListener(this@ZdLocationManager)
}
} catch (e : Exception) {
LogUtil.print(TAG, "初始化定位失败: ${e.message}")
}
}
}
private fun createLocationOption(isOnceLocation : Boolean = false,
isGpsFirst : Boolean = false,
isNeedAddress : Boolean = false) : AMapLocationClientOption =
AMapLocationClientOption().apply {
locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy
interval = NORMA_INTERVAL_TIME
isWifiScan = true
isLocationCacheEnable = false
httpTimeOut = DEFAULT_TIMEOUT
isMockEnable = false
this.isNeedAddress = isNeedAddress
this.isOnceLocation = isOnceLocation
this.isGpsFirst = isGpsFirst
}
fun startContinuousLocation(context : Context) {
stopContinuousLocation()
if (aMapLocationClient == null) {
init(context)
}
try {
aMapLocationClient?.startLocation()
LogUtil.print(TAG, "持续定位开启成功")
} catch (e : Exception) {
LogUtil.print(TAG, "持续定位开启失败: ${e.message}")
}
}
fun stopContinuousLocation() {
try {
aMapLocationClient?.stopLocation()
LogUtil.print(TAG, "关闭持续定位成功")
} catch (e : Exception) {
LogUtil.print(TAG, "关闭持续定位失败: ${e.message}")
}
}
override fun onLocationChanged(location : AMapLocation?) {
if (location == null) {
LogUtil.print(TAG, "定位获取失败: location is null")
return
}
if (location.errorCode != 0) {
LogUtil.print(TAG, "定位获取失败: ${location.errorInfo}")
return
}
GlobalData.currentLocation = location
uploadGpsRequest(location)
}
private fun uploadGpsRequest(location : AMapLocation) {
val request = UploadGpsRequest().apply {
lat = location.latitude
lng = location.longitude
userId = GlobalData.driverInfo?.userId
vehicleId = GlobalData.vehicleInfo?.vehicleId
working = GlobalData.currentOrder != null
direction = location.bearing.toDouble()
speed = location.speed.toDouble()
}
uploadGps(request)
}
private fun uploadGps(uploadGpsRequest : UploadGpsRequest) {
CommonMethod.uploadGps(uploadGpsRequest, success = {
LogUtil.print(TAG, "定位上传成功: ${uploadGpsRequest.toJson()}")
MyMqttClient.publishMessage() // if (ActivityUtils.getTopActivity()==null) {
// AppUtils.launchApp(AppUtils.getAppPackageName())
// }
}, failed = { error ->
LogUtil.print(TAG, "定位上传失败: $error $uploadGpsRequest")
MyMqttClient.publishMessage()
})
}
fun getSingleLocation(success : (AMapLocation) -> Unit,
failed : (String) -> Unit,
isNeedAddress : Boolean = false) {
globalLocation?.let { location ->
if (System.currentTimeMillis() - lastSingleLocationTime < LOCATION_CACHE_DURATION && (! isNeedAddress || ! location.address.isNullOrBlank())) {
LogUtil.print(TAG, "返回缓存位置")
success(location)
GlobalData.currentLocation = location
return
}
}
NetworkUtils.isAvailableAsync { isNetworkAvailable ->
LogUtil.print(TAG, "网络状态: $isNetworkAvailable")
if (isNetworkAvailable) {
doGetSingleLocationOnline(isNeedAddress = isNeedAddress,
success = success,
failed = failed)
} else {
doGetSingleLocationGpsFromOriginal(success,
failed) // doGetSingleLocationGps(success, failed)
}
}
}
private fun createSingleLocationClient(isNeedAddress : Boolean,
isGpsFirst : Boolean,
locationCallback : (AMapLocation?) -> Unit) : AMapLocationClient? {
return context?.let { ctx ->
try {
AMapLocationClient(ctx).apply {
setLocationOption(createLocationOption(isOnceLocation = true,
isGpsFirst = isGpsFirst,
isNeedAddress = isNeedAddress))
setLocationListener { location ->
locationCallback(location)
stopLocation()
onDestroy()
}
}
} catch (e : Exception) {
LogUtil.print(TAG, "创建单次定位客户端失败: ${e.message}")
null
}
}
}
private fun handleLocationResult(location : AMapLocation?,
isNeedAddress : Boolean,
success : (AMapLocation) -> Unit,
failed : (String) -> Unit) {
if (location == null) {
failed("Location is null")
LogUtil.print(TAG, "单次定位失败: location is null")
return
}
if (location.errorCode != 0) {
failed("Error code: ${location.errorCode}, ${location.errorInfo}")
LogUtil.print(TAG,
"单次定位失败: errorCode=${location.errorCode}, errorInfo=${location.locationDetail}")
return
}
if (isNeedAddress && location.address.isNullOrBlank()) {
nativeGeoCoder(location = location, success = { success(it) }, failed = { error ->
LogUtil.print(TAG, "逆地理编码失败: $error")
failed("位置获取失败")
})
} else {
updateGlobalLocation(location)
success(location)
}
}
private fun updateGlobalLocation(location : AMapLocation) {
globalLocation = location
lastSingleLocationTime = System.currentTimeMillis()
GlobalData.currentLocation = location
LogUtil.print(TAG, "单次定位结果: $location")
}
private fun doGetSingleLocationOnline(isNeedAddress : Boolean,
success : (AMapLocation) -> Unit,
failed : (String) -> Unit) {
val locationClient = createSingleLocationClient(isNeedAddress = isNeedAddress,
isGpsFirst = false) { location ->
handleLocationResult(location, isNeedAddress, success, failed)
} ?: run {
failed("Failed to create location client")
return
}
try {
locationClient.startLocation()
} catch (e : Exception) {
LogUtil.print(TAG, "启动单次定位失败: ${e.message}")
failed("Failed to start location: ${e.message}")
}
}
private fun doGetSingleLocationGps(success : (AMapLocation) -> Unit,
failed : (String) -> Unit) {
val locationClient =
createSingleLocationClient(isNeedAddress = false, isGpsFirst = true) { location ->
handleLocationResult(location, false, success, failed)
} ?: run {
failed("Failed to create location client")
return
}
try {
locationClient.startLocation()
} catch (e : Exception) {
LogUtil.print(TAG, "启动GPS定位失败: ${e.message}")
failed("Failed to start GPS location: ${e.message}")
}
}
@SuppressLint("MissingPermission")
private fun doGetSingleLocationGpsFromOriginal(success : (AMapLocation) -> Unit,
failed : (String) -> Unit) {
context?.let {
try {
val locationManager =
ContextCompat.getSystemService(it, LocationManager::class.java)
if (locationManager == null || ! LocationManagerCompat.isLocationEnabled(
locationManager)
) {
failed("Location service is disabled")
return@let
}
val lastKnownLocation =
locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
if (lastKnownLocation != null) {
val gcj02Location =
wgs84ToGcj02(lastKnownLocation.latitude, lastKnownLocation.longitude)
val aMapLocation = AMapLocation("").apply {
latitude = gcj02Location.latitude
longitude = gcj02Location.longitude
accuracy = lastKnownLocation.accuracy
bearing = lastKnownLocation.bearing
speed = lastKnownLocation.speed
time = lastKnownLocation.time
errorCode = 0
}
updateGlobalLocation(aMapLocation)
success(aMapLocation)
} else { // 如果没有最后位置,尝试请求单次更新
requestSingleGpsUpdate(locationManager, success, failed)
}
} catch (e : Exception) {
failed("Native GPS location failed: ${e.message}")
}
} ?: failed("获取失败")
}
@SuppressLint("MissingPermission")
private fun requestSingleGpsUpdate(locationManager : LocationManager,
success : (AMapLocation) -> Unit,
failed : (String) -> Unit) {
val locationListener = object : android.location.LocationListener {
override fun onLocationChanged(location : Location) {
locationManager.removeUpdates(this)
val gcj02 = wgs84ToGcj02(location.latitude, location.longitude)
val aMapLocation = AMapLocation("").apply {
latitude = gcj02.latitude
longitude = gcj02.longitude
accuracy = location.accuracy
bearing = location.bearing
speed = location.speed
time = location.time
errorCode = 0
}
updateGlobalLocation(aMapLocation)
success(aMapLocation)
}
override fun onProviderDisabled(provider : String) {
locationManager.removeUpdates(this)
failed("GPS provider disabled")
}
override fun onProviderEnabled(provider : String) {}
}
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
0L,
0f,
ContextCompat.getMainExecutor(context !!),
locationListener)
} else {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
0L,
0f,
locationListener)
}
} catch (e : Exception) {
failed("Failed to request GPS update: ${e.message}")
}
}
private fun geoCoder(aMapLocation : AMapLocation,
success : (AMapLocation) -> Unit,
failed : (String) -> Unit = {}) {
try {
val geocoderSearch = GeocodeSearch(context)
geocoderSearch.setOnGeocodeSearchListener(object : OnGeocodeSearchListener {
override fun onRegeocodeSearched(result : RegeocodeResult?, rCode : Int) {
if (result == null || rCode != 1000) {
failed("Geocode search failed: $rCode")
return
}
val address = result.regeocodeAddress?.formatAddress
if (! address.isNullOrBlank()) {
aMapLocation.address = address
success(aMapLocation)
} else {
failed("Empty address returned")
}
}
override fun onGeocodeSearched(result : GeocodeResult?,
rCode : Int) { // Not used in reverse geocoding
}
})
val point = LatLonPoint(aMapLocation.latitude, aMapLocation.longitude)
val query = RegeocodeQuery(point, LOCATION_SEARCH_RADIUS, GeocodeSearch.AMAP)
geocoderSearch.getFromLocationAsyn(query)
} catch (e : Exception) {
LogUtil.print(TAG, "地理编码异常: ${e.message}")
failed("Geocoding failed: ${e.message}")
}
}
private fun nativeGeoCoder(location : AMapLocation,
success : (AMapLocation) -> Unit,
failed : (String) -> Unit) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context?.let { ctx ->
try {
val geocoder = Geocoder(ctx, Locale.getDefault())
geocoder.getFromLocation(location.latitude,
location.longitude,
1) { addresses ->
if (addresses.isNotEmpty()) {
updateLocationWithNativeAddress(location, addresses[0])
success(location)
} else {
failed("No address found")
}
}
} catch (e : Exception) {
LogUtil.print(TAG, "原生地理编码异常: ${e.message}")
failed("Native geocoding failed: ${e.message}")
}
} ?: failed("Context is null")
} else {
context?.let { ctx ->
try {
val geocoder = Geocoder(ctx, Locale.getDefault())
val addresses =
geocoder.getFromLocation(location.latitude, location.longitude, 1)
if (! addresses.isNullOrEmpty()) {
updateLocationWithNativeAddress(location, addresses[0])
success(location)
} else {
failed("No address found")
}
} catch (e : Exception) {
LogUtil.print(TAG, "原生地理编码异常: ${e.message}")
failed("Native geocoding failed: ${e.message}")
}
} ?: failed("Context is null")
}
}
private fun updateLocationWithNativeAddress(location : AMapLocation,
address : android.location.Address) {
location.address = buildAddressString(address)
location.country = address.countryName
location.province = address.adminArea
location.city = address.locality
location.district = address.subLocality
location.street = address.thoroughfare
}
private fun buildAddressString(address : android.location.Address) : String {
return buildString {
address.countryName?.let { append(it) }
address.adminArea?.let { append(it) }
address.locality?.let { append(it) }
address.subLocality?.let { append(it) }
address.thoroughfare?.let { append(it) }
address.subThoroughfare?.let { append(it) }
}
}
// WGS84转GCJ02(火星坐标)
private fun wgs84ToGcj02(lat : Double, lon : Double) : WGS84Coordinate {
if (isOutOfChina(lat, lon)) {
return WGS84Coordinate(lat, lon)
}
var dLat = transformLat(lon - 105.0, lat - 35.0)
var dLon = transformLon(lon - 105.0, lat - 35.0)
val radLat = lat / 180.0 * Math.PI
var magic = sin(radLat)
magic = 1 - 0.006693421622965943 * magic * magic
val sqrtMagic = sqrt(magic)
dLat =
(dLat * 180.0) / ((6378245.0 * (1 - 0.006693421622965943)) / (magic * sqrtMagic) * Math.PI)
dLon = (dLon * 180.0) / (6378245.0 / sqrtMagic * cos(radLat) * Math.PI)
return WGS84Coordinate(latitude = lat + dLat, longitude = lon + dLon)
}
data class WGS84Coordinate(val latitude : Double, val longitude : Double)
private fun transformLat(x : Double, y : Double) : Double {
var ret = - 100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(abs(x))
ret += (20.0 * sin(6.0 * x * Math.PI) + 20.0 * sin(2.0 * x * Math.PI)) * 2.0 / 3.0
ret += (20.0 * sin(y * Math.PI) + 40.0 * sin(y / 3.0 * Math.PI)) * 2.0 / 3.0
ret += (160.0 * sin(y / 12.0 * Math.PI) + 320 * sin(y * Math.PI / 30.0)) * 2.0 / 3.0
return ret
}
private fun transformLon(x : Double, y : Double) : Double {
var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(abs(x))
ret += (20.0 * sin(6.0 * x * Math.PI) + 20.0 * sin(2.0 * x * Math.PI)) * 2.0 / 3.0
ret += (20.0 * sin(x * Math.PI) + 40.0 * sin(x / 3.0 * Math.PI)) * 2.0 / 3.0
ret += (150.0 * sin(x / 12.0 * Math.PI) + 300.0 * sin(x / 30.0 * Math.PI)) * 2.0 / 3.0
return ret
}
private fun isOutOfChina(lat : Double, lon : Double) : Boolean {
return lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271
}
}

View File

@ -0,0 +1,51 @@
package com.za.service.mqtt
import com.za.common.util.Tools.macSignature
import org.eclipse.paho.client.mqttv3.MqttConnectOptions
/**
* A utility class that encapsulates the initialization parameters for the MQ4IOT client.
*/
class ConnectionOptionWrapper(
instanceId: String,
accessKey: String,
clientId: String,
secretKey: String? = null,
tokenData: Map<String, String>? = null
) {
/**
* Internal connection parameters.
*/
@JvmField
val mqttConnectOptions: MqttConnectOptions = MqttConnectOptions().apply {
isCleanSession = false
keepAliveInterval = 90 // Keep alive interval in seconds
isAutomaticReconnect = true
mqttVersion = MqttConnectOptions.MQTT_VERSION_3_1_1
connectionTimeout = 30 // Connection timeout in seconds
}
init {
if (tokenData != null) {
mqttConnectOptions.userName = "Token|$accessKey|$instanceId"
mqttConnectOptions.password = buildTokenPassword(tokenData)
} else if (secretKey != null) {
mqttConnectOptions.userName = "Signature|$accessKey|$instanceId"
mqttConnectOptions.password = macSignature(clientId, secretKey).toCharArray()
}
}
/**
* Builds the password for token authentication.
*/
private fun buildTokenPassword(tokenData: Map<String, String>): CharArray {
val builder = StringBuilder()
for ((key, value) in tokenData) {
builder.append(key).append("|").append(value).append("|")
}
if (builder.isNotEmpty()) {
builder.setLength(builder.length - 1) // Remove the last '|'
}
return builder.toString().toCharArray()
}
}

View File

@ -0,0 +1,11 @@
package com.za.service.mqtt
object MqttConfig {
const val INSTANCE_ID = "mqtt-cn-oew23jbkb1f"
const val END_POINT = "mqtt-cn-oew23jbkb1f.mqtt.aliyuncs.com"
const val ACCESS_KEY = "LTAI5tKgZ9ACKorXzzWLxgg7"
const val SECRET_KEY = "D04F0UH2GzrDsYaJ9GTfULGPjcsvvz"
const val GROUP_ID = "GID_ZDJY"
const val TOPIC_PREFIX = "pushBaseTopic/p2p"
const val QOS_LEVEL = 0
}

View File

@ -0,0 +1,127 @@
package com.za.service.mqtt
import android.util.Log
import com.za.common.log.LogUtil
import com.za.service.ServiceManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended
import org.eclipse.paho.client.mqttv3.MqttClient
import org.eclipse.paho.client.mqttv3.MqttException
import org.eclipse.paho.client.mqttv3.MqttMessage
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence
object MyMqttClient {
private lateinit var clientId : String
private lateinit var topic : String
private val mqttClient : MqttClient by lazy {
MqttClient("tcp://${MqttConfig.END_POINT}:1883", clientId, MemoryPersistence())
}
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private val _connectionState = MutableStateFlow(false)
private val connectionState : StateFlow<Boolean> = _connectionState
fun initialize(deviceId : String?) {
clientId = "${MqttConfig.GROUP_ID}@@@$deviceId"
topic = "${MqttConfig.TOPIC_PREFIX}/$clientId"
setupMqttCallbacks()
connect()
LogUtil.print("MyMqttClient ", "initialize success")
Log.e("MyMqttClient ", "initialize success")
}
private fun setupMqttCallbacks() {
mqttClient.setCallback(object : MqttCallbackExtended {
override fun connectComplete(reconnect : Boolean, serverURI : String) {
val status = if (reconnect) "Reconnected" else "Connected"
LogUtil.print("MyMqttClient ", "$status to: $serverURI")
_connectionState.value = true
subscribeTopic()
}
override fun connectionLost(throwable : Throwable) {
LogUtil.print("MyMqttClient ", "Connection lost: ${throwable.message}")
_connectionState.value = false
}
override fun messageArrived(topic : String, mqttMessage : MqttMessage) {
val message = String(mqttMessage.payload)
LogUtil.print("MyMqttClient ", "Message arrived: $message")
ServiceManager.handlerPushMsg(message) // Pass the message to ServiceManager for processing
}
override fun deliveryComplete(token : IMqttDeliveryToken) {
LogUtil.print("MyMqttClient ", "Message delivery complete")
}
})
}
private fun connect() {
if (connectionState.value && mqttClient.isConnected) {
LogUtil.print("MyMqttClient ", "Already connected")
return
}
coroutineScope.launch {
try {
val options = ConnectionOptionWrapper(MqttConfig.INSTANCE_ID,
MqttConfig.ACCESS_KEY,
clientId,
MqttConfig.SECRET_KEY).mqttConnectOptions
withContext(Dispatchers.IO) {
mqttClient.connect(options)
}
_connectionState.value = true // Update connection state after successful connection
} catch (e : MqttException) {
LogUtil.print("MyMqttClient ", "Connection failed: ${e.message}")
_connectionState.value = false // Update connection state on failure
}
}
}
//检测mqtt连接状态
fun publishMessage() {
if (mqttClient.isConnected) {
LogUtil.print("MyMqttClient ", "publishMessage success")
return
}
connect()
}
private fun subscribeTopic() {
coroutineScope.launch {
try {
mqttClient.subscribe(topic, MqttConfig.QOS_LEVEL)
LogUtil.print("MyMqttClient ", "Subscribed to topic: $topic")
} catch (e : MqttException) {
LogUtil.print("MyMqttClient ", "Subscribe failed: ${e.message}")
}
}
}
fun disconnect() {
coroutineScope.launch {
try {
if (connectionState.value) {
mqttClient.disconnect()
_connectionState.value = false
LogUtil.print("MyMqttClient ", "Disconnected successfully")
}
} catch (e : MqttException) {
LogUtil.print("MyMqttClient ", "Disconnect failed: ${e.message}")
} finally {
coroutineScope.cancel()
}
}
}
}

View File

@ -0,0 +1,76 @@
package com.za.signature;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.za.servicing.R;
import com.za.signature.util.StatusBarCompat;
/***
* Activity基类
*
* @since 2018-06-25
* @author king
*/
public abstract class BaseActivity extends AppCompatActivity {
protected View actionbar;
protected TextView tvCancel;
protected TextView tvSave;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayout());
initTitleBar();
initView();
initData();
}
/**
* 初始化标题栏
*/
protected void initTitleBar() {
actionbar = findViewById(R.id.actionbar);
tvCancel = findViewById(R.id.tv_cancel);
tvSave = findViewById(R.id.tv_ok);
}
/**
* 获取布局
*/
protected abstract int getLayout();
/**
* 初始化视图
*/
protected abstract void initView();
/**
* 初始化数据
*/
protected abstract void initData();
/**
* 设置主题颜色
*
* @param color 主题颜色
*/
protected void setThemeColor(int color) {
try {
if (actionbar != null) {
actionbar.setBackgroundColor(color);
}
StatusBarCompat.compat(this, color);
} catch (Exception e) {
}
}
}

View File

@ -0,0 +1,441 @@
package com.za.signature;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.Editable;
import android.util.TypedValue;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.za.servicing.R;
import com.za.signature.config.PenConfig;
import com.za.signature.util.BitmapUtil;
import com.za.signature.util.DisplayUtil;
import com.za.signature.util.SystemUtil;
import com.za.signature.view.CircleImageView;
import com.za.signature.view.CircleView;
import com.za.signature.view.GridDrawable;
import com.za.signature.view.GridPaintView;
import com.za.signature.view.HVScrollView;
import com.za.signature.view.HandWriteEditView;
import com.za.signature.view.PaintSettingWindow;
/***
* 名称GridWriteActivity<br>
* 描述:米格输入界面
* 最近修改时间:
* @since 2017/11/14
* @author king
*/
public class GridPaintActivity extends BaseActivity implements View.OnClickListener, Handler.Callback {
private View mCircleContainer;
private HVScrollView mTextContainer;
private HandWriteEditView mEditView;
private CircleImageView mDeleteView;
private CircleImageView mSpaceView;
private CircleImageView mClearView;
private CircleImageView mEnterView;
private CircleView mPenCircleView;
private GridPaintView mPaintView;
private ProgressDialog mSaveProgressDlg;
private static final int MSG_SAVE_SUCCESS = 1;
private static final int MSG_SAVE_FAILED = 2;
private static final int MSG_WRITE_OK = 100;
private Handler mHandler;
private String mSavePath;
private Editable cacheEditable;
private int bgColor;
private boolean isCrop;
private String format;
private int lineSize;
private int fontSize;
private PaintSettingWindow settingWindow;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
bgColor = getIntent().getIntExtra("background", Color.TRANSPARENT);
lineSize = getIntent().getIntExtra("lineLength", 15);
fontSize = getIntent().getIntExtra("fontSize", 50);
isCrop = getIntent().getBooleanExtra("crop", false);
format = getIntent().getStringExtra("format");
PenConfig.PAINT_COLOR = PenConfig.getPaintColor(this);
PenConfig.PAINT_SIZE_LEVEL = PenConfig.getPaintTextLevel(this);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
super.onCreate(savedInstanceState);
SystemUtil.disableShowInput(getApplicationContext(), mEditView);
}
/**
* 横竖屏切换
*/
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setContentView(R.layout.sign_activity_grid_paint);
initTitleBar();
initView();
initData();
SystemUtil.disableShowInput(getApplicationContext(), mEditView);
if (mEditView != null && cacheEditable != null) {
mEditView.setText(cacheEditable);
mEditView.setSelection(cacheEditable.length());
mEditView.requestFocus();
}
mHandler = new Handler(this);
if (settingWindow != null) {
settingWindow.dismiss();
}
}
@Override
protected int getLayout() {
return R.layout.sign_activity_grid_paint;
}
@Override
protected void initData() {
setThemeColor(PenConfig.THEME_COLOR);
mPenCircleView.setOutBorderColor(PenConfig.THEME_COLOR);
mClearView.setEnabled(false);
mClearView.setImage(R.mipmap.sign_ic_clear, Color.LTGRAY);
mEnterView.setImage(R.mipmap.sign_ic_enter, PenConfig.THEME_COLOR);
mSpaceView.setImage(R.mipmap.sign_ic_space, PenConfig.THEME_COLOR);
mDeleteView.setImage(R.mipmap.sign_ic_delete, PenConfig.THEME_COLOR);
mHandler = new Handler(this);
}
/**
* 初始化视图
*/
@Override
protected void initView() {
mPaintView = findViewById(R.id.paint_view);
mDeleteView = findViewById(R.id.delete);
mSpaceView = findViewById(R.id.space);
mPenCircleView = findViewById(R.id.pen_color);
mClearView = findViewById(R.id.clear);
mEnterView = findViewById(R.id.enter);
mEditView = findViewById(R.id.et_view);
mTextContainer = findViewById(R.id.sv_container);
mCircleContainer = findViewById(R.id.circle_container);
mEnterView.setOnClickListener(this);
mDeleteView.setOnClickListener(this);
mSpaceView.setOnClickListener(this);
mPenCircleView.setOnClickListener(this);
tvCancel.setOnClickListener(this);
mClearView.setOnClickListener(this);
tvSave.setOnClickListener(this);
mPenCircleView.setPaintColor(PenConfig.PAINT_COLOR);
mPenCircleView.setRadiusLevel(PenConfig.PAINT_SIZE_LEVEL);
int size = getResources().getDimensionPixelSize(R.dimen.sign_grid_size);
GridDrawable gridDrawable = new GridDrawable(size, size, Color.WHITE);
mPaintView.setBackground(gridDrawable);
mPaintView.setGetTimeListener(new GridPaintView.WriteListener() {
@Override
public void onWriteStart() {
mHandler.removeMessages(MSG_WRITE_OK);
}
@Override
public void onWriteCompleted(long time) {
mHandler.sendEmptyMessageDelayed(MSG_WRITE_OK, 1000);
}
});
int maxWidth = lineSize * DisplayUtil.dip2px(this, fontSize);
if (!isCrop) {
mEditView.setWidth(maxWidth + 2);
mEditView.setMaxWidth(maxWidth);
} else {
mEditView.setWidth(maxWidth * 2 / 3);
mEditView.setMaxWidth(maxWidth * 2 / 3);
}
mEditView.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize);
mEditView.setLineHeight(DisplayUtil.dip2px(this, fontSize));
mEditView.setHorizontallyScrolling(false);
mEditView.requestFocus();
if (bgColor != Color.TRANSPARENT) {
mTextContainer.setBackgroundColor(bgColor);
}
mEditView.addTextWatcher(s -> {
if (s != null && s.length() > 0) {
mClearView.setEnabled(true);
mClearView.setImage(R.mipmap.sign_ic_clear, PenConfig.THEME_COLOR);
} else {
mClearView.setEnabled(false);
mClearView.setImage(R.mipmap.sign_ic_clear, Color.LTGRAY);
}
});
}
@Override
protected void onStop() {
super.onStop();
mHandler.removeMessages(MSG_WRITE_OK);
}
@Override
protected void onDestroy() {
mPaintView.release();
super.onDestroy();
mHandler.removeMessages(MSG_SAVE_FAILED);
mHandler.removeMessages(MSG_SAVE_SUCCESS);
}
private void initSaveProgressDlg() {
mSaveProgressDlg = new ProgressDialog(this);
mSaveProgressDlg.setMessage("正在保存,请稍候...");
mSaveProgressDlg.setCancelable(false);
}
/**
* 弹出清空提示
*/
private void showClearTips() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("提示")
.setMessage("清空文本框内手写内容?")
.setNegativeButton("取消", null)
.setPositiveButton("确定", (dialog, which) -> {
mEditView.setText("");
mEditView.setSelection(0);
cacheEditable = null;
});
builder.show();
}
@Override
public boolean handleMessage(@NonNull Message msg) {
switch (msg.what) {
case MSG_SAVE_FAILED:
if (mSaveProgressDlg != null) {
mSaveProgressDlg.dismiss();
}
Toast.makeText(getApplicationContext(), "保存失败", Toast.LENGTH_SHORT).show();
break;
case MSG_SAVE_SUCCESS:
if (mSaveProgressDlg != null) {
mSaveProgressDlg.dismiss();
}
Intent intent = new Intent();
intent.putExtra(PenConfig.SAVE_PATH, mSavePath);
setResult(RESULT_OK, intent);
finish();
break;
case MSG_WRITE_OK:
if (!mPaintView.isEmpty()) {
Bitmap bitmap = mPaintView.buildBitmap(isCrop, DisplayUtil.dip2px(GridPaintActivity.this, fontSize));
this.cacheEditable = mEditView.addBitmapToText(bitmap);
mPaintView.reset();
}
break;
default:
break;
}
return true;
}
/**
* 保存
*/
private void save() {
if (mEditView.getText() == null || mEditView.getText().length() == 0) {
Toast.makeText(getApplicationContext(), "没有写入任何文字", Toast.LENGTH_SHORT).show();
return;
}
//先检查是否有存储权限
// if (ContextCompat.checkSelfPermission(this,
// Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// Toast.makeText(getApplicationContext(), "没有读写存储的权限", Toast.LENGTH_SHORT).show();
// return;
// }
if (mSaveProgressDlg == null) {
initSaveProgressDlg();
}
mEditView.clearFocus();
mEditView.setCursorVisible(false);
mSaveProgressDlg.show();
new Thread(() -> {
if (PenConfig.FORMAT_JPG.equals(format) && bgColor == Color.TRANSPARENT) {
bgColor = Color.WHITE;
}
Bitmap bm = getWriteBitmap(bgColor);
bm = BitmapUtil.clearBlank(bm, 20, bgColor);
if (bm == null) {
mHandler.obtainMessage(MSG_SAVE_FAILED).sendToTarget();
return;
}
mSavePath = BitmapUtil.saveImage(GridPaintActivity.this, bm, 100, format);
if (mSavePath != null) {
mHandler.obtainMessage(MSG_SAVE_SUCCESS).sendToTarget();
} else {
mHandler.obtainMessage(MSG_SAVE_FAILED).sendToTarget();
}
bm.recycle();
}).start();
}
/**
* 获取EdiText截图
*
* @param bgColor 背景颜色
* @return EdiText截图
*/
private Bitmap getWriteBitmap(int bgColor) {
int w = 0;
int h = 0;
for (int i = 0; i < mTextContainer.getChildCount(); i++) {
h += mTextContainer.getChildAt(i).getHeight();
w += mTextContainer.getChildAt(i).getWidth();
}
Bitmap bitmap = null;
try {
bitmap = Bitmap.createBitmap(w, h,
Bitmap.Config.ARGB_8888);
} catch (OutOfMemoryError e) {
bitmap = Bitmap.createBitmap(w, h,
Bitmap.Config.ARGB_4444);
}
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(bgColor);
mTextContainer.draw(canvas);
return bitmap;
}
@Override
public void onClick(View v) {
int i = v.getId();
if (i == R.id.delete) {
this.cacheEditable = mEditView.deleteBitmapFromText();
} else if (i == R.id.tv_cancel) {
if (mEditView.getText() != null && mEditView.getText().length() > 0) {
showQuitTip();
} else {
setResult(RESULT_CANCELED);
finish();
}
} else if (i == R.id.tv_ok) {
save();
} else if (i == R.id.enter) {
Editable editable = mEditView.getText();
editable.insert(mEditView.getSelectionStart(), "\n");
} else if (i == R.id.space) {
mEditView.addSpace(fontSize);
} else if (i == R.id.clear) {
showClearTips();
} else if (i == R.id.pen_color) {
showSettingWindow();
}
}
/**
* 弹出画笔设置
*/
private void showSettingWindow() {
settingWindow = new PaintSettingWindow(this);
settingWindow.setSettingListener(new PaintSettingWindow.OnSettingListener() {
@Override
public void onColorSetting(int color) {
mPaintView.setPaintColor(color);
mPenCircleView.setPaintColor(PenConfig.PAINT_COLOR);
}
@Override
public void onSizeSetting(int level) {
mPaintView.setPaintWidth(PaintSettingWindow.PEN_SIZES[level]);
mPenCircleView.setRadiusLevel(level);
}
});
int[] location = new int[2];
mCircleContainer.getLocationOnScreen(location);
View contentView = settingWindow.getContentView();
//需要先测量PopupWindow还未弹出时宽高为0
contentView.measure(SystemUtil.makeDropDownMeasureSpec(settingWindow.getWidth()),
SystemUtil.makeDropDownMeasureSpec(settingWindow.getHeight()));
int padding = DisplayUtil.dip2px(this, 10);
int offsetX, offsetY;
Configuration config = getResources().getConfiguration();
int smallestScreenWidth = config.smallestScreenWidthDp;
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE && smallestScreenWidth >= 720) {
//平板上横屏显示
settingWindow.popAtBottomRight();
settingWindow.showAsDropDown(mCircleContainer, mCircleContainer.getWidth() - settingWindow.getContentView().getMeasuredWidth() - padding, 10);
} else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
//横屏显示
offsetX = -settingWindow.getContentView().getMeasuredWidth() - padding;
offsetY = -settingWindow.getContentView().getMeasuredHeight() - mCircleContainer.getHeight() / 2 + padding;
settingWindow.popAtLeft();
settingWindow.showAsDropDown(mCircleContainer, offsetX, offsetY);
} else {
//竖屏显示
offsetX = 0;
offsetY = -(settingWindow.getContentView().getMeasuredHeight() + mPenCircleView.getHeight() + 4 * padding);
settingWindow.popAtTopLeft();
settingWindow.showAsDropDown(mPenCircleView, offsetX, offsetY);
}
}
@Override
public void onBackPressed() {
if (mEditView.getText() != null && mEditView.getText().length() > 0) {
showQuitTip();
} else {
setResult(RESULT_CANCELED);
finish();
}
}
/**
* 弹出退出提示
*/
private void showQuitTip() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("提示")
.setMessage("当前文字未保存,是否退出?")
.setNegativeButton("取消", null)
.setPositiveButton("确定", (dialog, which) -> {
setResult(RESULT_CANCELED);
finish();
});
builder.show();
}
}

View File

@ -0,0 +1,430 @@
package com.za.signature;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.za.servicing.R;
import com.za.signature.config.PenConfig;
import com.za.signature.util.BitmapUtil;
import com.za.signature.util.DisplayUtil;
import com.za.signature.util.StatusBarCompat;
import com.za.signature.util.SystemUtil;
import com.za.signature.view.CircleView;
import com.za.signature.view.PaintSettingWindow;
import com.za.signature.view.PaintView;
/**
* 空白手写画板
*
* @author king
* @since 2018/07/10 14:20
*/
public class PaintActivity extends BaseActivity implements View.OnClickListener, PaintView.StepCallback {
/**
* 画布最大宽度
*/
public static final int CANVAS_MAX_WIDTH = 3000;
/**
* 画布最大高度
*/
public static final int CANVAS_MAX_HEIGHT = 3000;
private ImageView mHandView; //切换 滚动/手写
private ImageView mUndoView;
private ImageView mRedoView;
private ImageView mPenView;
private ImageView mClearView;
private CircleView mSettingView;
private PaintView mPaintView;
private ProgressDialog mSaveProgressDlg;
private static final int MSG_SAVE_SUCCESS = 1;
private static final int MSG_SAVE_FAILED = 2;
private String mSavePath;
private boolean hasSize = false;
private float mWidth;
private float mHeight;
private float widthRate = 1.0f;
private float heightRate = 1.0f;
private int bgColor;
private boolean isCrop;
private String format;
private PaintSettingWindow settingWindow;
@Override
protected int getLayout() {
return R.layout.sign_activity_paint;
}
@Override
protected void initView() {
View mCancelView = findViewById(R.id.tv_cancel);
View mOkView = findViewById(R.id.tv_ok);
mPaintView = findViewById(R.id.paint_view);
mHandView = findViewById(R.id.btn_hand);
mUndoView = findViewById(R.id.btn_undo);
mRedoView = findViewById(R.id.btn_redo);
mPenView = findViewById(R.id.btn_pen);
mClearView = findViewById(R.id.btn_clear);
mSettingView = findViewById(R.id.btn_setting);
mUndoView.setOnClickListener(this);
mRedoView.setOnClickListener(this);
mPenView.setOnClickListener(this);
mClearView.setOnClickListener(this);
mSettingView.setOnClickListener(this);
mHandView.setOnClickListener(this);
mCancelView.setOnClickListener(this);
mOkView.setOnClickListener(this);
mPenView.setSelected(true);
mUndoView.setEnabled(false);
mRedoView.setEnabled(false);
mClearView.setEnabled(!mPaintView.isEmpty());
mPaintView.setBackgroundColor(Color.WHITE);
mPaintView.setStepCallback(this);
PenConfig.PAINT_SIZE_LEVEL = PenConfig.getPaintTextLevel(this);
PenConfig.PAINT_COLOR = PenConfig.getPaintColor(this);
mSettingView.setPaintColor(PenConfig.PAINT_COLOR);
mSettingView.setRadiusLevel(PenConfig.PAINT_SIZE_LEVEL);
setThemeColor(PenConfig.THEME_COLOR);
BitmapUtil.setImage(mClearView, R.mipmap.sign_ic_clear, PenConfig.THEME_COLOR);
BitmapUtil.setImage(mPenView, R.mipmap.sign_ic_pen, PenConfig.THEME_COLOR);
BitmapUtil.setImage(mRedoView, R.mipmap.sign_ic_redo, mPaintView.canRedo() ? PenConfig.THEME_COLOR : Color.LTGRAY);
BitmapUtil.setImage(mUndoView, R.mipmap.sign_ic_undo, mPaintView.canUndo() ? PenConfig.THEME_COLOR : Color.LTGRAY);
BitmapUtil.setImage(mClearView, R.mipmap.sign_ic_clear, !mPaintView.isEmpty() ? PenConfig.THEME_COLOR : Color.LTGRAY);
// mSettingView.setOutBorderColor(PenConfig.THEME_COLOR);
BitmapUtil.setImage(mHandView, R.mipmap.sign_ic_hand, PenConfig.THEME_COLOR);
}
/**
* 获取画布默认宽度
*
*/
private int getResizeWidth() {
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE && dm.widthPixels < dm.heightPixels) {
return (int) (dm.heightPixels * widthRate);
}
return (int) (dm.widthPixels * widthRate);
}
/**
* 获取画布默认高度
*
*/
private int getResizeHeight() {
int toolBarHeight = getResources().getDimensionPixelSize(R.dimen.sign_grid_toolbar_height);
int actionbarHeight = getResources().getDimensionPixelSize(R.dimen.sign_actionbar_height);
int statusBarHeight = StatusBarCompat.getStatusBarHeight(this);
int otherHeight = toolBarHeight + actionbarHeight + statusBarHeight;
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE && dm.widthPixels < dm.heightPixels) {
return (int) ((dm.widthPixels - otherHeight) * heightRate);
}
return (int) ((dm.heightPixels - otherHeight) * heightRate);
}
@Override
protected void initData() {
isCrop = getIntent().getBooleanExtra("crop", false);
format = getIntent().getStringExtra("format");
bgColor = getIntent().getIntExtra("background", Color.TRANSPARENT);
String mInitPath = getIntent().getStringExtra("image");
float bitmapWidth = getIntent().getFloatExtra("width", 1.0f);
float bitmapHeight = getIntent().getFloatExtra("height", 1.0f);
if (bitmapWidth > 0 && bitmapWidth <= 1.0f) {
widthRate = bitmapWidth;
mWidth = getResizeWidth();
} else {
hasSize = true;
mWidth = bitmapWidth;
}
if (bitmapHeight > 0 && bitmapHeight <= 1.0f) {
heightRate = bitmapHeight;
mHeight = getResizeHeight();
} else {
hasSize = true;
mHeight = bitmapHeight;
}
if (mWidth > CANVAS_MAX_WIDTH) {
Toast.makeText(getApplicationContext(), "画板宽度已超过" + CANVAS_MAX_WIDTH, Toast.LENGTH_LONG).show();
finish();
return;
}
if (mHeight > CANVAS_MAX_WIDTH) {
Toast.makeText(getApplicationContext(), "画板高度已超过" + CANVAS_MAX_WIDTH, Toast.LENGTH_LONG).show();
finish();
return;
}
//初始画板设置
if (!hasSize && !TextUtils.isEmpty(mInitPath)) {
Bitmap bitmap = BitmapFactory.decodeFile(mInitPath);
mWidth = bitmap.getWidth();
mHeight = bitmap.getHeight();
hasSize = true;
if (mWidth > CANVAS_MAX_WIDTH || mHeight > CANVAS_MAX_HEIGHT) {
bitmap = BitmapUtil.zoomImg(bitmap, CANVAS_MAX_WIDTH, CANVAS_MAX_WIDTH);
mWidth = bitmap.getWidth();
mHeight = bitmap.getHeight();
}
}
mPaintView.init((int) mWidth, (int) mHeight, mInitPath);
if (bgColor != Color.TRANSPARENT) {
mPaintView.setBackgroundColor(bgColor);
}
}
/**
* 横竖屏切换
*/
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (settingWindow != null) {
settingWindow.dismiss();
}
int resizeWidth = getResizeWidth();
int resizeHeight = getResizeHeight();
if (mPaintView != null && !hasSize) {
mPaintView.resize(mPaintView.getLastBitmap(), resizeWidth, resizeHeight);
}
}
@Override
public void onClick(View v) {
int i = v.getId();
if (i == R.id.btn_setting) {
showPaintSettingWindow();
} else if (i == R.id.btn_hand) {
//切换是否允许写字
mPaintView.setFingerEnable(!mPaintView.isFingerEnable());
if (mPaintView.isFingerEnable()) {
BitmapUtil.setImage(mHandView, R.mipmap.sign_ic_hand, PenConfig.THEME_COLOR);
} else {
BitmapUtil.setImage(mHandView, R.mipmap.sign_ic_drag, PenConfig.THEME_COLOR);
}
} else if (i == R.id.btn_clear) {
mPaintView.reset();
} else if (i == R.id.btn_undo) {
mPaintView.undo();
} else if (i == R.id.btn_redo) {
mPaintView.redo();
} else if (i == R.id.btn_pen) {
if (!mPaintView.isEraser()) {
mPaintView.setPenType(PaintView.TYPE_ERASER);
BitmapUtil.setImage(mPenView, R.mipmap.sign_ic_eraser, PenConfig.THEME_COLOR);
} else {
mPaintView.setPenType(PaintView.TYPE_PEN);
BitmapUtil.setImage(mPenView, R.mipmap.sign_ic_pen, PenConfig.THEME_COLOR);
}
} else if (i == R.id.tv_ok) {
save();
} else if (i == R.id.tv_cancel) {
if (!mPaintView.isEmpty()) {
showQuitTip();
} else {
setResult(RESULT_CANCELED);
finish();
}
}
}
@Override
protected void onDestroy() {
if (mPaintView != null) {
mPaintView.release();
}
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
super.onDestroy();
}
/**
* 弹出画笔设置
*/
private void showPaintSettingWindow() {
settingWindow = new PaintSettingWindow(this);
settingWindow.setSettingListener(new PaintSettingWindow.OnSettingListener() {
@Override
public void onColorSetting(int color) {
mPaintView.setPaintColor(color);
mSettingView.setPaintColor(color);
}
@Override
public void onSizeSetting(int index) {
mSettingView.setRadiusLevel(index);
mPaintView.setPaintWidth(PaintSettingWindow.PEN_SIZES[index]);
}
});
View contentView = settingWindow.getContentView();
//需要先测量PopupWindow还未弹出时宽高为0
contentView.measure(SystemUtil.makeDropDownMeasureSpec(settingWindow.getWidth()),
SystemUtil.makeDropDownMeasureSpec(settingWindow.getHeight()));
int padding = DisplayUtil.dip2px(this, 45);
settingWindow.popAtTopRight();
settingWindow.showAsDropDown(mSettingView, -250, -2 * padding - settingWindow.getContentView().getMeasuredHeight());
}
private void initSaveProgressDlg() {
mSaveProgressDlg = new ProgressDialog(this);
mSaveProgressDlg.setMessage("正在保存,请稍候...");
mSaveProgressDlg.setCancelable(false);
}
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAVE_FAILED:
mSaveProgressDlg.dismiss();
Toast.makeText(getApplicationContext(), "保存失败", Toast.LENGTH_SHORT).show();
break;
case MSG_SAVE_SUCCESS:
mSaveProgressDlg.dismiss();
Intent intent = new Intent();
intent.putExtra(PenConfig.SAVE_PATH, mSavePath);
setResult(RESULT_OK, intent);
finish();
break;
default:
break;
}
}
};
/**
* 保存
*/
private void save() {
if (mPaintView.isEmpty()) {
Toast.makeText(getApplicationContext(), "没有写入任何文字", Toast.LENGTH_SHORT).show();
return;
}
//先检查是否有存储权限
// if (ContextCompat.checkSelfPermission(this,
// Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// Toast.makeText(getApplicationContext(), "没有读写存储的权限", Toast.LENGTH_SHORT).show();
// return;
// }
if (mSaveProgressDlg == null) {
initSaveProgressDlg();
}
mSaveProgressDlg.show();
new Thread(() -> {
try {
Bitmap result = mPaintView.buildAreaBitmap(isCrop);
if (PenConfig.FORMAT_JPG.equals(format) && bgColor == Color.TRANSPARENT) {
bgColor = Color.WHITE;
}
if (bgColor != Color.TRANSPARENT) {
result = BitmapUtil.drawBgToBitmap(result, bgColor);
}
if (result == null) {
mHandler.obtainMessage(MSG_SAVE_FAILED).sendToTarget();
return;
}
mSavePath = BitmapUtil.saveImage(PaintActivity.this, result, 100, format);
if (mSavePath != null) {
mHandler.obtainMessage(MSG_SAVE_SUCCESS).sendToTarget();
} else {
mHandler.obtainMessage(MSG_SAVE_FAILED).sendToTarget();
}
} catch (Exception e) {
}
}).start();
}
/**
* 画布有操作
*/
@Override
public void onOperateStatusChanged() {
mUndoView.setEnabled(mPaintView.canUndo());
mRedoView.setEnabled(mPaintView.canRedo());
mClearView.setEnabled(!mPaintView.isEmpty());
BitmapUtil.setImage(mRedoView, R.mipmap.sign_ic_redo, mPaintView.canRedo() ? PenConfig.THEME_COLOR : Color.LTGRAY);
BitmapUtil.setImage(mUndoView, R.mipmap.sign_ic_undo, mPaintView.canUndo() ? PenConfig.THEME_COLOR : Color.LTGRAY);
BitmapUtil.setImage(mClearView, R.mipmap.sign_ic_clear, !mPaintView.isEmpty() ? PenConfig.THEME_COLOR : Color.LTGRAY);
}
@Override
public void onBackPressed() {
if (!mPaintView.isEmpty()) {
showQuitTip();
} else {
setResult(RESULT_CANCELED);
finish();
}
}
/**
* 弹出退出提示
*/
private void showQuitTip() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("提示")
.setMessage("当前文字未保存,是否退出?")
.setNegativeButton("取消", null)
.setPositiveButton("确定", (dialog, which) -> {
setResult(RESULT_CANCELED);
finish();
});
builder.show();
}
}

Some files were not shown because too many files have changed in this diff Show More