diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt index 467d6e5..7f9077b 100644 --- a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt +++ b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt @@ -10,7 +10,7 @@ //根据不同设备状态栏高度设置"跳过"按钮的Margin值 var statusBarHeight = QMUIDisplayHelper.getStatusBarHeight(this) if (statusBarHeight == 0) { - statusBarHeight = 44f.dp2px(BaseApplication.obtainInstance()) + statusBarHeight = 44f.dp2px(BaseApplication.get()) } rootView.setPadding(0, statusBarHeight, 0, 0) rootView.requestLayout() diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt index 467d6e5..7f9077b 100644 --- a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt +++ b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt @@ -10,7 +10,7 @@ //根据不同设备状态栏高度设置"跳过"按钮的Margin值 var statusBarHeight = QMUIDisplayHelper.getStatusBarHeight(this) if (statusBarHeight == 0) { - statusBarHeight = 44f.dp2px(BaseApplication.obtainInstance()) + statusBarHeight = 44f.dp2px(BaseApplication.get()) } rootView.setPadding(0, statusBarHeight, 0, 0) rootView.requestLayout() diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..533d3a7 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.xz.meterage.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt index 467d6e5..7f9077b 100644 --- a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt +++ b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt @@ -10,7 +10,7 @@ //根据不同设备状态栏高度设置"跳过"按钮的Margin值 var statusBarHeight = QMUIDisplayHelper.getStatusBarHeight(this) if (statusBarHeight == 0) { - statusBarHeight = 44f.dp2px(BaseApplication.obtainInstance()) + statusBarHeight = 44f.dp2px(BaseApplication.get()) } rootView.setPadding(0, statusBarHeight, 0, 0) rootView.requestLayout() diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..533d3a7 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.xz.meterage.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/String.kt b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt new file mode 100644 index 0000000..f4d63e2 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt @@ -0,0 +1,64 @@ +package com.casic.xz.meterage.extensions + +import android.content.Context +import com.casic.xz.meterage.callback.OnImageCompressListener +import com.casic.xz.meterage.model.ErrorMessageModel +import com.casic.xz.meterage.utils.LocaleConstant +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.createCompressImageDir +import com.pengxh.kt.lite.utils.SaveKeyValues +import org.json.JSONObject +import top.zibin.luban.Luban +import top.zibin.luban.OnCompressListener +import java.io.File +import java.util.* + +/** + * String扩展方法 + */ +fun String.compressImage(context: Context, listener: OnImageCompressListener) { + Luban.with(context) + .load(this) + .ignoreBy(100) + .setTargetDir(context.createCompressImageDir().toString()) + .filter { + !(it.isBlank() || it.lowercase(Locale.getDefault()).endsWith(".gif")) + } + .setCompressListener(object : OnCompressListener { + override fun onStart() { + + } + + override fun onSuccess(file: File) { + listener.onSuccess(file) + } + + override fun onError(e: Throwable) { + listener.onError(e) + } + }).launch() +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt index 467d6e5..7f9077b 100644 --- a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt +++ b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt @@ -10,7 +10,7 @@ //根据不同设备状态栏高度设置"跳过"按钮的Margin值 var statusBarHeight = QMUIDisplayHelper.getStatusBarHeight(this) if (statusBarHeight == 0) { - statusBarHeight = 44f.dp2px(BaseApplication.obtainInstance()) + statusBarHeight = 44f.dp2px(BaseApplication.get()) } rootView.setPadding(0, statusBarHeight, 0, 0) rootView.requestLayout() diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..533d3a7 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.xz.meterage.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/String.kt b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt new file mode 100644 index 0000000..f4d63e2 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt @@ -0,0 +1,64 @@ +package com.casic.xz.meterage.extensions + +import android.content.Context +import com.casic.xz.meterage.callback.OnImageCompressListener +import com.casic.xz.meterage.model.ErrorMessageModel +import com.casic.xz.meterage.utils.LocaleConstant +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.createCompressImageDir +import com.pengxh.kt.lite.utils.SaveKeyValues +import org.json.JSONObject +import top.zibin.luban.Luban +import top.zibin.luban.OnCompressListener +import java.io.File +import java.util.* + +/** + * String扩展方法 + */ +fun String.compressImage(context: Context, listener: OnImageCompressListener) { + Luban.with(context) + .load(this) + .ignoreBy(100) + .setTargetDir(context.createCompressImageDir().toString()) + .filter { + !(it.isBlank() || it.lowercase(Locale.getDefault()).endsWith(".gif")) + } + .setCompressListener(object : OnCompressListener { + override fun onStart() { + + } + + override fun onSuccess(file: File) { + listener.onSuccess(file) + } + + override fun onError(e: Throwable) { + listener.onError(e) + } + }).launch() +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java new file mode 100644 index 0000000..afc0b3a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java @@ -0,0 +1,49 @@ +package com.casic.xz.meterage.model; + +public class ErrorMessageModel { + private int code; + private String data; + private String exceptionClazz; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getExceptionClazz() { + return exceptionClazz; + } + + public void setExceptionClazz(String exceptionClazz) { + this.exceptionClazz = exceptionClazz; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt index 467d6e5..7f9077b 100644 --- a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt +++ b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt @@ -10,7 +10,7 @@ //根据不同设备状态栏高度设置"跳过"按钮的Margin值 var statusBarHeight = QMUIDisplayHelper.getStatusBarHeight(this) if (statusBarHeight == 0) { - statusBarHeight = 44f.dp2px(BaseApplication.obtainInstance()) + statusBarHeight = 44f.dp2px(BaseApplication.get()) } rootView.setPadding(0, statusBarHeight, 0, 0) rootView.requestLayout() diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..533d3a7 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.xz.meterage.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/String.kt b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt new file mode 100644 index 0000000..f4d63e2 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt @@ -0,0 +1,64 @@ +package com.casic.xz.meterage.extensions + +import android.content.Context +import com.casic.xz.meterage.callback.OnImageCompressListener +import com.casic.xz.meterage.model.ErrorMessageModel +import com.casic.xz.meterage.utils.LocaleConstant +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.createCompressImageDir +import com.pengxh.kt.lite.utils.SaveKeyValues +import org.json.JSONObject +import top.zibin.luban.Luban +import top.zibin.luban.OnCompressListener +import java.io.File +import java.util.* + +/** + * String扩展方法 + */ +fun String.compressImage(context: Context, listener: OnImageCompressListener) { + Luban.with(context) + .load(this) + .ignoreBy(100) + .setTargetDir(context.createCompressImageDir().toString()) + .filter { + !(it.isBlank() || it.lowercase(Locale.getDefault()).endsWith(".gif")) + } + .setCompressListener(object : OnCompressListener { + override fun onStart() { + + } + + override fun onSuccess(file: File) { + listener.onSuccess(file) + } + + override fun onError(e: Throwable) { + listener.onError(e) + } + }).launch() +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java new file mode 100644 index 0000000..afc0b3a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java @@ -0,0 +1,49 @@ +package com.casic.xz.meterage.model; + +public class ErrorMessageModel { + private int code; + private String data; + private String exceptionClazz; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getExceptionClazz() { + return exceptionClazz; + } + + public void setExceptionClazz(String exceptionClazz) { + this.exceptionClazz = exceptionClazz; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java new file mode 100644 index 0000000..3305db0 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.xz.meterage.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt index 467d6e5..7f9077b 100644 --- a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt +++ b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt @@ -10,7 +10,7 @@ //根据不同设备状态栏高度设置"跳过"按钮的Margin值 var statusBarHeight = QMUIDisplayHelper.getStatusBarHeight(this) if (statusBarHeight == 0) { - statusBarHeight = 44f.dp2px(BaseApplication.obtainInstance()) + statusBarHeight = 44f.dp2px(BaseApplication.get()) } rootView.setPadding(0, statusBarHeight, 0, 0) rootView.requestLayout() diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..533d3a7 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.xz.meterage.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/String.kt b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt new file mode 100644 index 0000000..f4d63e2 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt @@ -0,0 +1,64 @@ +package com.casic.xz.meterage.extensions + +import android.content.Context +import com.casic.xz.meterage.callback.OnImageCompressListener +import com.casic.xz.meterage.model.ErrorMessageModel +import com.casic.xz.meterage.utils.LocaleConstant +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.createCompressImageDir +import com.pengxh.kt.lite.utils.SaveKeyValues +import org.json.JSONObject +import top.zibin.luban.Luban +import top.zibin.luban.OnCompressListener +import java.io.File +import java.util.* + +/** + * String扩展方法 + */ +fun String.compressImage(context: Context, listener: OnImageCompressListener) { + Luban.with(context) + .load(this) + .ignoreBy(100) + .setTargetDir(context.createCompressImageDir().toString()) + .filter { + !(it.isBlank() || it.lowercase(Locale.getDefault()).endsWith(".gif")) + } + .setCompressListener(object : OnCompressListener { + override fun onStart() { + + } + + override fun onSuccess(file: File) { + listener.onSuccess(file) + } + + override fun onError(e: Throwable) { + listener.onError(e) + } + }).launch() +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java new file mode 100644 index 0000000..afc0b3a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java @@ -0,0 +1,49 @@ +package com.casic.xz.meterage.model; + +public class ErrorMessageModel { + private int code; + private String data; + private String exceptionClazz; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getExceptionClazz() { + return exceptionClazz; + } + + public void setExceptionClazz(String exceptionClazz) { + this.exceptionClazz = exceptionClazz; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java new file mode 100644 index 0000000..3305db0 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.xz.meterage.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java new file mode 100644 index 0000000..5dbc99a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.xz.meterage.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt index 467d6e5..7f9077b 100644 --- a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt +++ b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt @@ -10,7 +10,7 @@ //根据不同设备状态栏高度设置"跳过"按钮的Margin值 var statusBarHeight = QMUIDisplayHelper.getStatusBarHeight(this) if (statusBarHeight == 0) { - statusBarHeight = 44f.dp2px(BaseApplication.obtainInstance()) + statusBarHeight = 44f.dp2px(BaseApplication.get()) } rootView.setPadding(0, statusBarHeight, 0, 0) rootView.requestLayout() diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..533d3a7 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.xz.meterage.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/String.kt b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt new file mode 100644 index 0000000..f4d63e2 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt @@ -0,0 +1,64 @@ +package com.casic.xz.meterage.extensions + +import android.content.Context +import com.casic.xz.meterage.callback.OnImageCompressListener +import com.casic.xz.meterage.model.ErrorMessageModel +import com.casic.xz.meterage.utils.LocaleConstant +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.createCompressImageDir +import com.pengxh.kt.lite.utils.SaveKeyValues +import org.json.JSONObject +import top.zibin.luban.Luban +import top.zibin.luban.OnCompressListener +import java.io.File +import java.util.* + +/** + * String扩展方法 + */ +fun String.compressImage(context: Context, listener: OnImageCompressListener) { + Luban.with(context) + .load(this) + .ignoreBy(100) + .setTargetDir(context.createCompressImageDir().toString()) + .filter { + !(it.isBlank() || it.lowercase(Locale.getDefault()).endsWith(".gif")) + } + .setCompressListener(object : OnCompressListener { + override fun onStart() { + + } + + override fun onSuccess(file: File) { + listener.onSuccess(file) + } + + override fun onError(e: Throwable) { + listener.onError(e) + } + }).launch() +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java new file mode 100644 index 0000000..afc0b3a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java @@ -0,0 +1,49 @@ +package com.casic.xz.meterage.model; + +public class ErrorMessageModel { + private int code; + private String data; + private String exceptionClazz; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getExceptionClazz() { + return exceptionClazz; + } + + public void setExceptionClazz(String exceptionClazz) { + this.exceptionClazz = exceptionClazz; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java new file mode 100644 index 0000000..3305db0 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.xz.meterage.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java new file mode 100644 index 0000000..5dbc99a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.xz.meterage.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt new file mode 100644 index 0000000..6ab6d16 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.xz.meterage.utils + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt index 467d6e5..7f9077b 100644 --- a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt +++ b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt @@ -10,7 +10,7 @@ //根据不同设备状态栏高度设置"跳过"按钮的Margin值 var statusBarHeight = QMUIDisplayHelper.getStatusBarHeight(this) if (statusBarHeight == 0) { - statusBarHeight = 44f.dp2px(BaseApplication.obtainInstance()) + statusBarHeight = 44f.dp2px(BaseApplication.get()) } rootView.setPadding(0, statusBarHeight, 0, 0) rootView.requestLayout() diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..533d3a7 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.xz.meterage.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/String.kt b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt new file mode 100644 index 0000000..f4d63e2 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt @@ -0,0 +1,64 @@ +package com.casic.xz.meterage.extensions + +import android.content.Context +import com.casic.xz.meterage.callback.OnImageCompressListener +import com.casic.xz.meterage.model.ErrorMessageModel +import com.casic.xz.meterage.utils.LocaleConstant +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.createCompressImageDir +import com.pengxh.kt.lite.utils.SaveKeyValues +import org.json.JSONObject +import top.zibin.luban.Luban +import top.zibin.luban.OnCompressListener +import java.io.File +import java.util.* + +/** + * String扩展方法 + */ +fun String.compressImage(context: Context, listener: OnImageCompressListener) { + Luban.with(context) + .load(this) + .ignoreBy(100) + .setTargetDir(context.createCompressImageDir().toString()) + .filter { + !(it.isBlank() || it.lowercase(Locale.getDefault()).endsWith(".gif")) + } + .setCompressListener(object : OnCompressListener { + override fun onStart() { + + } + + override fun onSuccess(file: File) { + listener.onSuccess(file) + } + + override fun onError(e: Throwable) { + listener.onError(e) + } + }).launch() +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java new file mode 100644 index 0000000..afc0b3a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java @@ -0,0 +1,49 @@ +package com.casic.xz.meterage.model; + +public class ErrorMessageModel { + private int code; + private String data; + private String exceptionClazz; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getExceptionClazz() { + return exceptionClazz; + } + + public void setExceptionClazz(String exceptionClazz) { + this.exceptionClazz = exceptionClazz; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java new file mode 100644 index 0000000..3305db0 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.xz.meterage.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java new file mode 100644 index 0000000..5dbc99a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.xz.meterage.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt new file mode 100644 index 0000000..6ab6d16 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.xz.meterage.utils + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt new file mode 100644 index 0000000..18ddc8b --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt @@ -0,0 +1,14 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import me.leolin.shortcutbadger.ShortcutBadger + +object BadeHelper { + fun setBadgeNum(context: Context, badgeCount: Int) { + if (badgeCount == 0) { + ShortcutBadger.removeCount(context) + } else { + ShortcutBadger.applyCount(context, badgeCount) + } + } +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt index 467d6e5..7f9077b 100644 --- a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt +++ b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt @@ -10,7 +10,7 @@ //根据不同设备状态栏高度设置"跳过"按钮的Margin值 var statusBarHeight = QMUIDisplayHelper.getStatusBarHeight(this) if (statusBarHeight == 0) { - statusBarHeight = 44f.dp2px(BaseApplication.obtainInstance()) + statusBarHeight = 44f.dp2px(BaseApplication.get()) } rootView.setPadding(0, statusBarHeight, 0, 0) rootView.requestLayout() diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..533d3a7 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.xz.meterage.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/String.kt b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt new file mode 100644 index 0000000..f4d63e2 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt @@ -0,0 +1,64 @@ +package com.casic.xz.meterage.extensions + +import android.content.Context +import com.casic.xz.meterage.callback.OnImageCompressListener +import com.casic.xz.meterage.model.ErrorMessageModel +import com.casic.xz.meterage.utils.LocaleConstant +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.createCompressImageDir +import com.pengxh.kt.lite.utils.SaveKeyValues +import org.json.JSONObject +import top.zibin.luban.Luban +import top.zibin.luban.OnCompressListener +import java.io.File +import java.util.* + +/** + * String扩展方法 + */ +fun String.compressImage(context: Context, listener: OnImageCompressListener) { + Luban.with(context) + .load(this) + .ignoreBy(100) + .setTargetDir(context.createCompressImageDir().toString()) + .filter { + !(it.isBlank() || it.lowercase(Locale.getDefault()).endsWith(".gif")) + } + .setCompressListener(object : OnCompressListener { + override fun onStart() { + + } + + override fun onSuccess(file: File) { + listener.onSuccess(file) + } + + override fun onError(e: Throwable) { + listener.onError(e) + } + }).launch() +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java new file mode 100644 index 0000000..afc0b3a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java @@ -0,0 +1,49 @@ +package com.casic.xz.meterage.model; + +public class ErrorMessageModel { + private int code; + private String data; + private String exceptionClazz; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getExceptionClazz() { + return exceptionClazz; + } + + public void setExceptionClazz(String exceptionClazz) { + this.exceptionClazz = exceptionClazz; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java new file mode 100644 index 0000000..3305db0 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.xz.meterage.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java new file mode 100644 index 0000000..5dbc99a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.xz.meterage.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt new file mode 100644 index 0000000..6ab6d16 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.xz.meterage.utils + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt new file mode 100644 index 0000000..18ddc8b --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt @@ -0,0 +1,14 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import me.leolin.shortcutbadger.ShortcutBadger + +object BadeHelper { + fun setBadgeNum(context: Context, badgeCount: Int) { + if (badgeCount == 0) { + ShortcutBadger.removeCount(context) + } else { + ShortcutBadger.applyCount(context, badgeCount) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..9feef94 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt @@ -0,0 +1,92 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.widget.ImageView +import androidx.annotation.Nullable +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.casic.xz.meterage.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.interfaces.OnCallbackListener +import com.luck.picture.lib.utils.ActivityCompatHelper + + +class GlideLoadEngine private constructor() : ImageEngine { + + companion object { + val instance: GlideLoadEngine by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + GlideLoadEngine() + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context).load(url).into(imageView); + } + + override fun loadImageBitmap( + context: Context, + url: String, + maxWidth: Int, + maxHeight: Int, + call: OnCallbackListener? + ) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .override(maxWidth, maxHeight) + .load(url) + .into(object : CustomTarget() { + override fun onResourceReady( + resource: Bitmap, @Nullable transition: Transition? + ) { + call?.onCall(resource) + } + + override fun onLoadFailed(@Nullable errorDrawable: Drawable?) { + call?.onCall(null) + } + + override fun onLoadCleared(@Nullable placeholder: Drawable?) {} + }) + } + + override fun loadAlbumCover(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .load(url) + .override(180, 180) + .sizeMultiplier(0.5f) + .transform(CenterCrop(), RoundedCorners(8)) + .placeholder(R.mipmap.load_image_error) + .into(imageView) + } + + override fun pauseRequests(context: Context?) { + context?.let { Glide.with(it).pauseRequests() } + } + + override fun resumeRequests(context: Context?) { + context?.let { Glide.with(it).resumeRequests() } + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.mipmap.load_image_error)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt index 467d6e5..7f9077b 100644 --- a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt +++ b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt @@ -10,7 +10,7 @@ //根据不同设备状态栏高度设置"跳过"按钮的Margin值 var statusBarHeight = QMUIDisplayHelper.getStatusBarHeight(this) if (statusBarHeight == 0) { - statusBarHeight = 44f.dp2px(BaseApplication.obtainInstance()) + statusBarHeight = 44f.dp2px(BaseApplication.get()) } rootView.setPadding(0, statusBarHeight, 0, 0) rootView.requestLayout() diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..533d3a7 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.xz.meterage.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/String.kt b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt new file mode 100644 index 0000000..f4d63e2 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt @@ -0,0 +1,64 @@ +package com.casic.xz.meterage.extensions + +import android.content.Context +import com.casic.xz.meterage.callback.OnImageCompressListener +import com.casic.xz.meterage.model.ErrorMessageModel +import com.casic.xz.meterage.utils.LocaleConstant +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.createCompressImageDir +import com.pengxh.kt.lite.utils.SaveKeyValues +import org.json.JSONObject +import top.zibin.luban.Luban +import top.zibin.luban.OnCompressListener +import java.io.File +import java.util.* + +/** + * String扩展方法 + */ +fun String.compressImage(context: Context, listener: OnImageCompressListener) { + Luban.with(context) + .load(this) + .ignoreBy(100) + .setTargetDir(context.createCompressImageDir().toString()) + .filter { + !(it.isBlank() || it.lowercase(Locale.getDefault()).endsWith(".gif")) + } + .setCompressListener(object : OnCompressListener { + override fun onStart() { + + } + + override fun onSuccess(file: File) { + listener.onSuccess(file) + } + + override fun onError(e: Throwable) { + listener.onError(e) + } + }).launch() +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java new file mode 100644 index 0000000..afc0b3a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java @@ -0,0 +1,49 @@ +package com.casic.xz.meterage.model; + +public class ErrorMessageModel { + private int code; + private String data; + private String exceptionClazz; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getExceptionClazz() { + return exceptionClazz; + } + + public void setExceptionClazz(String exceptionClazz) { + this.exceptionClazz = exceptionClazz; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java new file mode 100644 index 0000000..3305db0 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.xz.meterage.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java new file mode 100644 index 0000000..5dbc99a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.xz.meterage.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt new file mode 100644 index 0000000..6ab6d16 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.xz.meterage.utils + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt new file mode 100644 index 0000000..18ddc8b --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt @@ -0,0 +1,14 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import me.leolin.shortcutbadger.ShortcutBadger + +object BadeHelper { + fun setBadgeNum(context: Context, badgeCount: Int) { + if (badgeCount == 0) { + ShortcutBadger.removeCount(context) + } else { + ShortcutBadger.applyCount(context, badgeCount) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..9feef94 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt @@ -0,0 +1,92 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.widget.ImageView +import androidx.annotation.Nullable +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.casic.xz.meterage.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.interfaces.OnCallbackListener +import com.luck.picture.lib.utils.ActivityCompatHelper + + +class GlideLoadEngine private constructor() : ImageEngine { + + companion object { + val instance: GlideLoadEngine by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + GlideLoadEngine() + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context).load(url).into(imageView); + } + + override fun loadImageBitmap( + context: Context, + url: String, + maxWidth: Int, + maxHeight: Int, + call: OnCallbackListener? + ) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .override(maxWidth, maxHeight) + .load(url) + .into(object : CustomTarget() { + override fun onResourceReady( + resource: Bitmap, @Nullable transition: Transition? + ) { + call?.onCall(resource) + } + + override fun onLoadFailed(@Nullable errorDrawable: Drawable?) { + call?.onCall(null) + } + + override fun onLoadCleared(@Nullable placeholder: Drawable?) {} + }) + } + + override fun loadAlbumCover(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .load(url) + .override(180, 180) + .sizeMultiplier(0.5f) + .transform(CenterCrop(), RoundedCorners(8)) + .placeholder(R.mipmap.load_image_error) + .into(imageView) + } + + override fun pauseRequests(context: Context?) { + context?.let { Glide.with(it).pauseRequests() } + } + + override fun resumeRequests(context: Context?) { + context?.let { Glide.with(it).resumeRequests() } + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.mipmap.load_image_error)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt new file mode 100644 index 0000000..f5a3e6c --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt @@ -0,0 +1,31 @@ +package com.casic.xz.meterage.utils + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object LoadingDialogHub { + + private lateinit var loadingDialog: QMUITipDialog + + fun show(activity: Activity, message: String) { + loadingDialog = QMUITipDialog + .Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismiss() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt index 467d6e5..7f9077b 100644 --- a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt +++ b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt @@ -10,7 +10,7 @@ //根据不同设备状态栏高度设置"跳过"按钮的Margin值 var statusBarHeight = QMUIDisplayHelper.getStatusBarHeight(this) if (statusBarHeight == 0) { - statusBarHeight = 44f.dp2px(BaseApplication.obtainInstance()) + statusBarHeight = 44f.dp2px(BaseApplication.get()) } rootView.setPadding(0, statusBarHeight, 0, 0) rootView.requestLayout() diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..533d3a7 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.xz.meterage.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/String.kt b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt new file mode 100644 index 0000000..f4d63e2 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt @@ -0,0 +1,64 @@ +package com.casic.xz.meterage.extensions + +import android.content.Context +import com.casic.xz.meterage.callback.OnImageCompressListener +import com.casic.xz.meterage.model.ErrorMessageModel +import com.casic.xz.meterage.utils.LocaleConstant +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.createCompressImageDir +import com.pengxh.kt.lite.utils.SaveKeyValues +import org.json.JSONObject +import top.zibin.luban.Luban +import top.zibin.luban.OnCompressListener +import java.io.File +import java.util.* + +/** + * String扩展方法 + */ +fun String.compressImage(context: Context, listener: OnImageCompressListener) { + Luban.with(context) + .load(this) + .ignoreBy(100) + .setTargetDir(context.createCompressImageDir().toString()) + .filter { + !(it.isBlank() || it.lowercase(Locale.getDefault()).endsWith(".gif")) + } + .setCompressListener(object : OnCompressListener { + override fun onStart() { + + } + + override fun onSuccess(file: File) { + listener.onSuccess(file) + } + + override fun onError(e: Throwable) { + listener.onError(e) + } + }).launch() +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java new file mode 100644 index 0000000..afc0b3a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java @@ -0,0 +1,49 @@ +package com.casic.xz.meterage.model; + +public class ErrorMessageModel { + private int code; + private String data; + private String exceptionClazz; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getExceptionClazz() { + return exceptionClazz; + } + + public void setExceptionClazz(String exceptionClazz) { + this.exceptionClazz = exceptionClazz; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java new file mode 100644 index 0000000..3305db0 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.xz.meterage.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java new file mode 100644 index 0000000..5dbc99a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.xz.meterage.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt new file mode 100644 index 0000000..6ab6d16 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.xz.meterage.utils + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt new file mode 100644 index 0000000..18ddc8b --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt @@ -0,0 +1,14 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import me.leolin.shortcutbadger.ShortcutBadger + +object BadeHelper { + fun setBadgeNum(context: Context, badgeCount: Int) { + if (badgeCount == 0) { + ShortcutBadger.removeCount(context) + } else { + ShortcutBadger.applyCount(context, badgeCount) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..9feef94 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt @@ -0,0 +1,92 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.widget.ImageView +import androidx.annotation.Nullable +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.casic.xz.meterage.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.interfaces.OnCallbackListener +import com.luck.picture.lib.utils.ActivityCompatHelper + + +class GlideLoadEngine private constructor() : ImageEngine { + + companion object { + val instance: GlideLoadEngine by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + GlideLoadEngine() + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context).load(url).into(imageView); + } + + override fun loadImageBitmap( + context: Context, + url: String, + maxWidth: Int, + maxHeight: Int, + call: OnCallbackListener? + ) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .override(maxWidth, maxHeight) + .load(url) + .into(object : CustomTarget() { + override fun onResourceReady( + resource: Bitmap, @Nullable transition: Transition? + ) { + call?.onCall(resource) + } + + override fun onLoadFailed(@Nullable errorDrawable: Drawable?) { + call?.onCall(null) + } + + override fun onLoadCleared(@Nullable placeholder: Drawable?) {} + }) + } + + override fun loadAlbumCover(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .load(url) + .override(180, 180) + .sizeMultiplier(0.5f) + .transform(CenterCrop(), RoundedCorners(8)) + .placeholder(R.mipmap.load_image_error) + .into(imageView) + } + + override fun pauseRequests(context: Context?) { + context?.let { Glide.with(it).pauseRequests() } + } + + override fun resumeRequests(context: Context?) { + context?.let { Glide.with(it).resumeRequests() } + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.mipmap.load_image_error)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt new file mode 100644 index 0000000..f5a3e6c --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt @@ -0,0 +1,31 @@ +package com.casic.xz.meterage.utils + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object LoadingDialogHub { + + private lateinit var loadingDialog: QMUITipDialog + + fun show(activity: Activity, message: String) { + loadingDialog = QMUITipDialog + .Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismiss() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt index 8b7bef4..7bc5704 100644 --- a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt @@ -3,11 +3,40 @@ import android.Manifest object LocaleConstant { + + /** + * ============================================================================================= + * Array + * ============================================================================================= + * */ val USER_PERMISSIONS = arrayOf( Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ) + /** + * ============================================================================================= + * Int + * ============================================================================================= + * */ const val PERMISSIONS_CODE = 999 + const val PAGE_LIMIT = 20 + + /** + * ============================================================================================= + * Long + * ============================================================================================= + * */ + + + /** + * ============================================================================================= + * String + * ============================================================================================= + * */ + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val SERVER_BASE_URL = "http://111.198.10.15:21609" + const val ACCOUNT = "account" + const val PASSWORD = "password" } \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt index 467d6e5..7f9077b 100644 --- a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt +++ b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt @@ -10,7 +10,7 @@ //根据不同设备状态栏高度设置"跳过"按钮的Margin值 var statusBarHeight = QMUIDisplayHelper.getStatusBarHeight(this) if (statusBarHeight == 0) { - statusBarHeight = 44f.dp2px(BaseApplication.obtainInstance()) + statusBarHeight = 44f.dp2px(BaseApplication.get()) } rootView.setPadding(0, statusBarHeight, 0, 0) rootView.requestLayout() diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..533d3a7 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.xz.meterage.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/String.kt b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt new file mode 100644 index 0000000..f4d63e2 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt @@ -0,0 +1,64 @@ +package com.casic.xz.meterage.extensions + +import android.content.Context +import com.casic.xz.meterage.callback.OnImageCompressListener +import com.casic.xz.meterage.model.ErrorMessageModel +import com.casic.xz.meterage.utils.LocaleConstant +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.createCompressImageDir +import com.pengxh.kt.lite.utils.SaveKeyValues +import org.json.JSONObject +import top.zibin.luban.Luban +import top.zibin.luban.OnCompressListener +import java.io.File +import java.util.* + +/** + * String扩展方法 + */ +fun String.compressImage(context: Context, listener: OnImageCompressListener) { + Luban.with(context) + .load(this) + .ignoreBy(100) + .setTargetDir(context.createCompressImageDir().toString()) + .filter { + !(it.isBlank() || it.lowercase(Locale.getDefault()).endsWith(".gif")) + } + .setCompressListener(object : OnCompressListener { + override fun onStart() { + + } + + override fun onSuccess(file: File) { + listener.onSuccess(file) + } + + override fun onError(e: Throwable) { + listener.onError(e) + } + }).launch() +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java new file mode 100644 index 0000000..afc0b3a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java @@ -0,0 +1,49 @@ +package com.casic.xz.meterage.model; + +public class ErrorMessageModel { + private int code; + private String data; + private String exceptionClazz; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getExceptionClazz() { + return exceptionClazz; + } + + public void setExceptionClazz(String exceptionClazz) { + this.exceptionClazz = exceptionClazz; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java new file mode 100644 index 0000000..3305db0 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.xz.meterage.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java new file mode 100644 index 0000000..5dbc99a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.xz.meterage.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt new file mode 100644 index 0000000..6ab6d16 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.xz.meterage.utils + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt new file mode 100644 index 0000000..18ddc8b --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt @@ -0,0 +1,14 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import me.leolin.shortcutbadger.ShortcutBadger + +object BadeHelper { + fun setBadgeNum(context: Context, badgeCount: Int) { + if (badgeCount == 0) { + ShortcutBadger.removeCount(context) + } else { + ShortcutBadger.applyCount(context, badgeCount) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..9feef94 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt @@ -0,0 +1,92 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.widget.ImageView +import androidx.annotation.Nullable +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.casic.xz.meterage.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.interfaces.OnCallbackListener +import com.luck.picture.lib.utils.ActivityCompatHelper + + +class GlideLoadEngine private constructor() : ImageEngine { + + companion object { + val instance: GlideLoadEngine by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + GlideLoadEngine() + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context).load(url).into(imageView); + } + + override fun loadImageBitmap( + context: Context, + url: String, + maxWidth: Int, + maxHeight: Int, + call: OnCallbackListener? + ) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .override(maxWidth, maxHeight) + .load(url) + .into(object : CustomTarget() { + override fun onResourceReady( + resource: Bitmap, @Nullable transition: Transition? + ) { + call?.onCall(resource) + } + + override fun onLoadFailed(@Nullable errorDrawable: Drawable?) { + call?.onCall(null) + } + + override fun onLoadCleared(@Nullable placeholder: Drawable?) {} + }) + } + + override fun loadAlbumCover(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .load(url) + .override(180, 180) + .sizeMultiplier(0.5f) + .transform(CenterCrop(), RoundedCorners(8)) + .placeholder(R.mipmap.load_image_error) + .into(imageView) + } + + override fun pauseRequests(context: Context?) { + context?.let { Glide.with(it).pauseRequests() } + } + + override fun resumeRequests(context: Context?) { + context?.let { Glide.with(it).resumeRequests() } + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.mipmap.load_image_error)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt new file mode 100644 index 0000000..f5a3e6c --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt @@ -0,0 +1,31 @@ +package com.casic.xz.meterage.utils + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object LoadingDialogHub { + + private lateinit var loadingDialog: QMUITipDialog + + fun show(activity: Activity, message: String) { + loadingDialog = QMUITipDialog + .Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismiss() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt index 8b7bef4..7bc5704 100644 --- a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt @@ -3,11 +3,40 @@ import android.Manifest object LocaleConstant { + + /** + * ============================================================================================= + * Array + * ============================================================================================= + * */ val USER_PERMISSIONS = arrayOf( Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ) + /** + * ============================================================================================= + * Int + * ============================================================================================= + * */ const val PERMISSIONS_CODE = 999 + const val PAGE_LIMIT = 20 + + /** + * ============================================================================================= + * Long + * ============================================================================================= + * */ + + + /** + * ============================================================================================= + * String + * ============================================================================================= + * */ + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val SERVER_BASE_URL = "http://111.198.10.15:21609" + const val ACCOUNT = "account" + const val PASSWORD = "password" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt new file mode 100644 index 0000000..5a440bd --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.xz.meterage.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt index 467d6e5..7f9077b 100644 --- a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt +++ b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt @@ -10,7 +10,7 @@ //根据不同设备状态栏高度设置"跳过"按钮的Margin值 var statusBarHeight = QMUIDisplayHelper.getStatusBarHeight(this) if (statusBarHeight == 0) { - statusBarHeight = 44f.dp2px(BaseApplication.obtainInstance()) + statusBarHeight = 44f.dp2px(BaseApplication.get()) } rootView.setPadding(0, statusBarHeight, 0, 0) rootView.requestLayout() diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..533d3a7 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.xz.meterage.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/String.kt b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt new file mode 100644 index 0000000..f4d63e2 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt @@ -0,0 +1,64 @@ +package com.casic.xz.meterage.extensions + +import android.content.Context +import com.casic.xz.meterage.callback.OnImageCompressListener +import com.casic.xz.meterage.model.ErrorMessageModel +import com.casic.xz.meterage.utils.LocaleConstant +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.createCompressImageDir +import com.pengxh.kt.lite.utils.SaveKeyValues +import org.json.JSONObject +import top.zibin.luban.Luban +import top.zibin.luban.OnCompressListener +import java.io.File +import java.util.* + +/** + * String扩展方法 + */ +fun String.compressImage(context: Context, listener: OnImageCompressListener) { + Luban.with(context) + .load(this) + .ignoreBy(100) + .setTargetDir(context.createCompressImageDir().toString()) + .filter { + !(it.isBlank() || it.lowercase(Locale.getDefault()).endsWith(".gif")) + } + .setCompressListener(object : OnCompressListener { + override fun onStart() { + + } + + override fun onSuccess(file: File) { + listener.onSuccess(file) + } + + override fun onError(e: Throwable) { + listener.onError(e) + } + }).launch() +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java new file mode 100644 index 0000000..afc0b3a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java @@ -0,0 +1,49 @@ +package com.casic.xz.meterage.model; + +public class ErrorMessageModel { + private int code; + private String data; + private String exceptionClazz; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getExceptionClazz() { + return exceptionClazz; + } + + public void setExceptionClazz(String exceptionClazz) { + this.exceptionClazz = exceptionClazz; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java new file mode 100644 index 0000000..3305db0 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.xz.meterage.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java new file mode 100644 index 0000000..5dbc99a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.xz.meterage.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt new file mode 100644 index 0000000..6ab6d16 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.xz.meterage.utils + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt new file mode 100644 index 0000000..18ddc8b --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt @@ -0,0 +1,14 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import me.leolin.shortcutbadger.ShortcutBadger + +object BadeHelper { + fun setBadgeNum(context: Context, badgeCount: Int) { + if (badgeCount == 0) { + ShortcutBadger.removeCount(context) + } else { + ShortcutBadger.applyCount(context, badgeCount) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..9feef94 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt @@ -0,0 +1,92 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.widget.ImageView +import androidx.annotation.Nullable +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.casic.xz.meterage.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.interfaces.OnCallbackListener +import com.luck.picture.lib.utils.ActivityCompatHelper + + +class GlideLoadEngine private constructor() : ImageEngine { + + companion object { + val instance: GlideLoadEngine by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + GlideLoadEngine() + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context).load(url).into(imageView); + } + + override fun loadImageBitmap( + context: Context, + url: String, + maxWidth: Int, + maxHeight: Int, + call: OnCallbackListener? + ) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .override(maxWidth, maxHeight) + .load(url) + .into(object : CustomTarget() { + override fun onResourceReady( + resource: Bitmap, @Nullable transition: Transition? + ) { + call?.onCall(resource) + } + + override fun onLoadFailed(@Nullable errorDrawable: Drawable?) { + call?.onCall(null) + } + + override fun onLoadCleared(@Nullable placeholder: Drawable?) {} + }) + } + + override fun loadAlbumCover(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .load(url) + .override(180, 180) + .sizeMultiplier(0.5f) + .transform(CenterCrop(), RoundedCorners(8)) + .placeholder(R.mipmap.load_image_error) + .into(imageView) + } + + override fun pauseRequests(context: Context?) { + context?.let { Glide.with(it).pauseRequests() } + } + + override fun resumeRequests(context: Context?) { + context?.let { Glide.with(it).resumeRequests() } + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.mipmap.load_image_error)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt new file mode 100644 index 0000000..f5a3e6c --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt @@ -0,0 +1,31 @@ +package com.casic.xz.meterage.utils + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object LoadingDialogHub { + + private lateinit var loadingDialog: QMUITipDialog + + fun show(activity: Activity, message: String) { + loadingDialog = QMUITipDialog + .Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismiss() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt index 8b7bef4..7bc5704 100644 --- a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt @@ -3,11 +3,40 @@ import android.Manifest object LocaleConstant { + + /** + * ============================================================================================= + * Array + * ============================================================================================= + * */ val USER_PERMISSIONS = arrayOf( Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ) + /** + * ============================================================================================= + * Int + * ============================================================================================= + * */ const val PERMISSIONS_CODE = 999 + const val PAGE_LIMIT = 20 + + /** + * ============================================================================================= + * Long + * ============================================================================================= + * */ + + + /** + * ============================================================================================= + * String + * ============================================================================================= + * */ + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val SERVER_BASE_URL = "http://111.198.10.15:21609" + const val ACCOUNT = "account" + const val PASSWORD = "password" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt new file mode 100644 index 0000000..5a440bd --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.xz.meterage.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt new file mode 100644 index 0000000..f40401a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt @@ -0,0 +1,20 @@ +package com.casic.xz.meterage.utils.retrofit + +import okhttp3.RequestBody +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST + +interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/sys/config/baseConfig") + suspend fun getPublicKey(): String + + /** + * 登录并获取Token + */ + @POST("/sys/user/login") + suspend fun login(@Body requestBody: RequestBody): String +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt index 467d6e5..7f9077b 100644 --- a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt +++ b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt @@ -10,7 +10,7 @@ //根据不同设备状态栏高度设置"跳过"按钮的Margin值 var statusBarHeight = QMUIDisplayHelper.getStatusBarHeight(this) if (statusBarHeight == 0) { - statusBarHeight = 44f.dp2px(BaseApplication.obtainInstance()) + statusBarHeight = 44f.dp2px(BaseApplication.get()) } rootView.setPadding(0, statusBarHeight, 0, 0) rootView.requestLayout() diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..533d3a7 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.xz.meterage.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/String.kt b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt new file mode 100644 index 0000000..f4d63e2 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt @@ -0,0 +1,64 @@ +package com.casic.xz.meterage.extensions + +import android.content.Context +import com.casic.xz.meterage.callback.OnImageCompressListener +import com.casic.xz.meterage.model.ErrorMessageModel +import com.casic.xz.meterage.utils.LocaleConstant +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.createCompressImageDir +import com.pengxh.kt.lite.utils.SaveKeyValues +import org.json.JSONObject +import top.zibin.luban.Luban +import top.zibin.luban.OnCompressListener +import java.io.File +import java.util.* + +/** + * String扩展方法 + */ +fun String.compressImage(context: Context, listener: OnImageCompressListener) { + Luban.with(context) + .load(this) + .ignoreBy(100) + .setTargetDir(context.createCompressImageDir().toString()) + .filter { + !(it.isBlank() || it.lowercase(Locale.getDefault()).endsWith(".gif")) + } + .setCompressListener(object : OnCompressListener { + override fun onStart() { + + } + + override fun onSuccess(file: File) { + listener.onSuccess(file) + } + + override fun onError(e: Throwable) { + listener.onError(e) + } + }).launch() +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java new file mode 100644 index 0000000..afc0b3a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java @@ -0,0 +1,49 @@ +package com.casic.xz.meterage.model; + +public class ErrorMessageModel { + private int code; + private String data; + private String exceptionClazz; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getExceptionClazz() { + return exceptionClazz; + } + + public void setExceptionClazz(String exceptionClazz) { + this.exceptionClazz = exceptionClazz; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java new file mode 100644 index 0000000..3305db0 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.xz.meterage.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java new file mode 100644 index 0000000..5dbc99a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.xz.meterage.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt new file mode 100644 index 0000000..6ab6d16 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.xz.meterage.utils + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt new file mode 100644 index 0000000..18ddc8b --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt @@ -0,0 +1,14 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import me.leolin.shortcutbadger.ShortcutBadger + +object BadeHelper { + fun setBadgeNum(context: Context, badgeCount: Int) { + if (badgeCount == 0) { + ShortcutBadger.removeCount(context) + } else { + ShortcutBadger.applyCount(context, badgeCount) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..9feef94 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt @@ -0,0 +1,92 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.widget.ImageView +import androidx.annotation.Nullable +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.casic.xz.meterage.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.interfaces.OnCallbackListener +import com.luck.picture.lib.utils.ActivityCompatHelper + + +class GlideLoadEngine private constructor() : ImageEngine { + + companion object { + val instance: GlideLoadEngine by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + GlideLoadEngine() + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context).load(url).into(imageView); + } + + override fun loadImageBitmap( + context: Context, + url: String, + maxWidth: Int, + maxHeight: Int, + call: OnCallbackListener? + ) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .override(maxWidth, maxHeight) + .load(url) + .into(object : CustomTarget() { + override fun onResourceReady( + resource: Bitmap, @Nullable transition: Transition? + ) { + call?.onCall(resource) + } + + override fun onLoadFailed(@Nullable errorDrawable: Drawable?) { + call?.onCall(null) + } + + override fun onLoadCleared(@Nullable placeholder: Drawable?) {} + }) + } + + override fun loadAlbumCover(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .load(url) + .override(180, 180) + .sizeMultiplier(0.5f) + .transform(CenterCrop(), RoundedCorners(8)) + .placeholder(R.mipmap.load_image_error) + .into(imageView) + } + + override fun pauseRequests(context: Context?) { + context?.let { Glide.with(it).pauseRequests() } + } + + override fun resumeRequests(context: Context?) { + context?.let { Glide.with(it).resumeRequests() } + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.mipmap.load_image_error)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt new file mode 100644 index 0000000..f5a3e6c --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt @@ -0,0 +1,31 @@ +package com.casic.xz.meterage.utils + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object LoadingDialogHub { + + private lateinit var loadingDialog: QMUITipDialog + + fun show(activity: Activity, message: String) { + loadingDialog = QMUITipDialog + .Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismiss() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt index 8b7bef4..7bc5704 100644 --- a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt @@ -3,11 +3,40 @@ import android.Manifest object LocaleConstant { + + /** + * ============================================================================================= + * Array + * ============================================================================================= + * */ val USER_PERMISSIONS = arrayOf( Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ) + /** + * ============================================================================================= + * Int + * ============================================================================================= + * */ const val PERMISSIONS_CODE = 999 + const val PAGE_LIMIT = 20 + + /** + * ============================================================================================= + * Long + * ============================================================================================= + * */ + + + /** + * ============================================================================================= + * String + * ============================================================================================= + * */ + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val SERVER_BASE_URL = "http://111.198.10.15:21609" + const val ACCOUNT = "account" + const val PASSWORD = "password" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt new file mode 100644 index 0000000..5a440bd --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.xz.meterage.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt new file mode 100644 index 0000000..f40401a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt @@ -0,0 +1,20 @@ +package com.casic.xz.meterage.utils.retrofit + +import okhttp3.RequestBody +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST + +interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/sys/config/baseConfig") + suspend fun getPublicKey(): String + + /** + * 登录并获取Token + */ + @POST("/sys/user/login") + suspend fun login(@Body requestBody: RequestBody): String +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt new file mode 100644 index 0000000..c6e74be --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt @@ -0,0 +1,39 @@ +package com.casic.xz.meterage.utils.retrofit + +import com.casic.xz.meterage.utils.LocaleConstant +import com.pengxh.kt.lite.utils.RetrofitFactory +import com.pengxh.kt.lite.utils.SaveKeyValues +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject + +object RetrofitServiceManager { + + private val api by lazy { + val httpConfig = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + RetrofitFactory.createRetrofit(httpConfig) + } + + /** + * 验证PublicKey + */ + suspend fun getPublicKey(): String { + return api.getPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + val paramObject = JSONObject() + paramObject.put("sid", sid) + paramObject.put("username", account) + paramObject.put("password", secretKey) + val requestBody = paramObject.toString().toRequestBody( + "application/json;charset=UTF-8".toMediaType() + ) + return api.login(requestBody) + } +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt index 467d6e5..7f9077b 100644 --- a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt +++ b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt @@ -10,7 +10,7 @@ //根据不同设备状态栏高度设置"跳过"按钮的Margin值 var statusBarHeight = QMUIDisplayHelper.getStatusBarHeight(this) if (statusBarHeight == 0) { - statusBarHeight = 44f.dp2px(BaseApplication.obtainInstance()) + statusBarHeight = 44f.dp2px(BaseApplication.get()) } rootView.setPadding(0, statusBarHeight, 0, 0) rootView.requestLayout() diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..533d3a7 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.xz.meterage.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/String.kt b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt new file mode 100644 index 0000000..f4d63e2 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt @@ -0,0 +1,64 @@ +package com.casic.xz.meterage.extensions + +import android.content.Context +import com.casic.xz.meterage.callback.OnImageCompressListener +import com.casic.xz.meterage.model.ErrorMessageModel +import com.casic.xz.meterage.utils.LocaleConstant +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.createCompressImageDir +import com.pengxh.kt.lite.utils.SaveKeyValues +import org.json.JSONObject +import top.zibin.luban.Luban +import top.zibin.luban.OnCompressListener +import java.io.File +import java.util.* + +/** + * String扩展方法 + */ +fun String.compressImage(context: Context, listener: OnImageCompressListener) { + Luban.with(context) + .load(this) + .ignoreBy(100) + .setTargetDir(context.createCompressImageDir().toString()) + .filter { + !(it.isBlank() || it.lowercase(Locale.getDefault()).endsWith(".gif")) + } + .setCompressListener(object : OnCompressListener { + override fun onStart() { + + } + + override fun onSuccess(file: File) { + listener.onSuccess(file) + } + + override fun onError(e: Throwable) { + listener.onError(e) + } + }).launch() +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java new file mode 100644 index 0000000..afc0b3a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java @@ -0,0 +1,49 @@ +package com.casic.xz.meterage.model; + +public class ErrorMessageModel { + private int code; + private String data; + private String exceptionClazz; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getExceptionClazz() { + return exceptionClazz; + } + + public void setExceptionClazz(String exceptionClazz) { + this.exceptionClazz = exceptionClazz; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java new file mode 100644 index 0000000..3305db0 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.xz.meterage.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java new file mode 100644 index 0000000..5dbc99a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.xz.meterage.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt new file mode 100644 index 0000000..6ab6d16 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.xz.meterage.utils + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt new file mode 100644 index 0000000..18ddc8b --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt @@ -0,0 +1,14 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import me.leolin.shortcutbadger.ShortcutBadger + +object BadeHelper { + fun setBadgeNum(context: Context, badgeCount: Int) { + if (badgeCount == 0) { + ShortcutBadger.removeCount(context) + } else { + ShortcutBadger.applyCount(context, badgeCount) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..9feef94 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt @@ -0,0 +1,92 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.widget.ImageView +import androidx.annotation.Nullable +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.casic.xz.meterage.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.interfaces.OnCallbackListener +import com.luck.picture.lib.utils.ActivityCompatHelper + + +class GlideLoadEngine private constructor() : ImageEngine { + + companion object { + val instance: GlideLoadEngine by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + GlideLoadEngine() + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context).load(url).into(imageView); + } + + override fun loadImageBitmap( + context: Context, + url: String, + maxWidth: Int, + maxHeight: Int, + call: OnCallbackListener? + ) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .override(maxWidth, maxHeight) + .load(url) + .into(object : CustomTarget() { + override fun onResourceReady( + resource: Bitmap, @Nullable transition: Transition? + ) { + call?.onCall(resource) + } + + override fun onLoadFailed(@Nullable errorDrawable: Drawable?) { + call?.onCall(null) + } + + override fun onLoadCleared(@Nullable placeholder: Drawable?) {} + }) + } + + override fun loadAlbumCover(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .load(url) + .override(180, 180) + .sizeMultiplier(0.5f) + .transform(CenterCrop(), RoundedCorners(8)) + .placeholder(R.mipmap.load_image_error) + .into(imageView) + } + + override fun pauseRequests(context: Context?) { + context?.let { Glide.with(it).pauseRequests() } + } + + override fun resumeRequests(context: Context?) { + context?.let { Glide.with(it).resumeRequests() } + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.mipmap.load_image_error)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt new file mode 100644 index 0000000..f5a3e6c --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt @@ -0,0 +1,31 @@ +package com.casic.xz.meterage.utils + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object LoadingDialogHub { + + private lateinit var loadingDialog: QMUITipDialog + + fun show(activity: Activity, message: String) { + loadingDialog = QMUITipDialog + .Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismiss() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt index 8b7bef4..7bc5704 100644 --- a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt @@ -3,11 +3,40 @@ import android.Manifest object LocaleConstant { + + /** + * ============================================================================================= + * Array + * ============================================================================================= + * */ val USER_PERMISSIONS = arrayOf( Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ) + /** + * ============================================================================================= + * Int + * ============================================================================================= + * */ const val PERMISSIONS_CODE = 999 + const val PAGE_LIMIT = 20 + + /** + * ============================================================================================= + * Long + * ============================================================================================= + * */ + + + /** + * ============================================================================================= + * String + * ============================================================================================= + * */ + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val SERVER_BASE_URL = "http://111.198.10.15:21609" + const val ACCOUNT = "account" + const val PASSWORD = "password" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt new file mode 100644 index 0000000..5a440bd --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.xz.meterage.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt new file mode 100644 index 0000000..f40401a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt @@ -0,0 +1,20 @@ +package com.casic.xz.meterage.utils.retrofit + +import okhttp3.RequestBody +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST + +interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/sys/config/baseConfig") + suspend fun getPublicKey(): String + + /** + * 登录并获取Token + */ + @POST("/sys/user/login") + suspend fun login(@Body requestBody: RequestBody): String +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt new file mode 100644 index 0000000..c6e74be --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt @@ -0,0 +1,39 @@ +package com.casic.xz.meterage.utils.retrofit + +import com.casic.xz.meterage.utils.LocaleConstant +import com.pengxh.kt.lite.utils.RetrofitFactory +import com.pengxh.kt.lite.utils.SaveKeyValues +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject + +object RetrofitServiceManager { + + private val api by lazy { + val httpConfig = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + RetrofitFactory.createRetrofit(httpConfig) + } + + /** + * 验证PublicKey + */ + suspend fun getPublicKey(): String { + return api.getPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + val paramObject = JSONObject() + paramObject.put("sid", sid) + paramObject.put("username", account) + paramObject.put("password", secretKey) + val requestBody = paramObject.toString().toRequestBody( + "application/json;charset=UTF-8".toMediaType() + ) + return api.login(requestBody) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt b/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt index 3579db3..df0855c 100644 --- a/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt +++ b/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt @@ -1,25 +1,94 @@ package com.casic.xz.meterage.view +import androidx.lifecycle.ViewModelProvider import com.casic.xz.meterage.R import com.casic.xz.meterage.extensions.initLayoutImmersionBar +import com.casic.xz.meterage.utils.AuthenticationHelper +import com.casic.xz.meterage.utils.LoadingDialogHub +import com.casic.xz.meterage.utils.LocaleConstant +import com.casic.xz.meterage.utils.RSAUtils +import com.casic.xz.meterage.vm.AuthenticateViewModel +import com.casic.xz.meterage.vm.UserViewModel import com.gyf.immersionbar.ImmersionBar import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState import kotlinx.android.synthetic.main.activity_login.* class LoginActivity : KotlinBaseActivity() { - override fun initData() { + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var userViewModel: UserViewModel + override fun initData() { + userPhoneView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + passwordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] } override fun initEvent() { +// forgetPasswordView.setOnClickListener { +// navigatePageTo() +// } +// registerView.setOnClickListener { +// navigatePageTo() +// } + + loginButton.setOnClickListener { + val userPhone = userPhoneView.text.toString() + if (userPhone.isBlank()) { + "请输入手机号".show(this) + return@setOnClickListener + } + val userPassword = passwordView.text.toString() + if (userPassword.isBlank()) { + "请输入密码".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, userPhone) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + + authenticateViewModel.obtainPublicKey() + authenticateViewModel.keyModel.observe(this, { keyModel -> + if (keyModel.code == 200) { + val keyString = keyModel.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val dataByPublicKey = RSAUtils.encryptDataByPublicKey( + userPassword.toByteArray(), publicKey!! + ) + userViewModel.enter(keyModel.data!!.sid!!, userPhone, dataByPublicKey) + userViewModel.loginResult.observe(this, { + if (it.code == 200) { + AuthenticationHelper.saveToken(it.data!!.token!!) + PageNavigationManager.finishAllActivity() + navigatePageTo() + } + }) + } + }) + } } override fun initLayoutView(): Int = R.layout.activity_login override fun observeRequestState() { - + userViewModel.loadState.observe(this, { + when (it) { + LoadState.Loading -> LoadingDialogHub.show(this, "登录中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) } override fun setupTopBarLayout() { diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt index 467d6e5..7f9077b 100644 --- a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt +++ b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt @@ -10,7 +10,7 @@ //根据不同设备状态栏高度设置"跳过"按钮的Margin值 var statusBarHeight = QMUIDisplayHelper.getStatusBarHeight(this) if (statusBarHeight == 0) { - statusBarHeight = 44f.dp2px(BaseApplication.obtainInstance()) + statusBarHeight = 44f.dp2px(BaseApplication.get()) } rootView.setPadding(0, statusBarHeight, 0, 0) rootView.requestLayout() diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..533d3a7 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.xz.meterage.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/String.kt b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt new file mode 100644 index 0000000..f4d63e2 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt @@ -0,0 +1,64 @@ +package com.casic.xz.meterage.extensions + +import android.content.Context +import com.casic.xz.meterage.callback.OnImageCompressListener +import com.casic.xz.meterage.model.ErrorMessageModel +import com.casic.xz.meterage.utils.LocaleConstant +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.createCompressImageDir +import com.pengxh.kt.lite.utils.SaveKeyValues +import org.json.JSONObject +import top.zibin.luban.Luban +import top.zibin.luban.OnCompressListener +import java.io.File +import java.util.* + +/** + * String扩展方法 + */ +fun String.compressImage(context: Context, listener: OnImageCompressListener) { + Luban.with(context) + .load(this) + .ignoreBy(100) + .setTargetDir(context.createCompressImageDir().toString()) + .filter { + !(it.isBlank() || it.lowercase(Locale.getDefault()).endsWith(".gif")) + } + .setCompressListener(object : OnCompressListener { + override fun onStart() { + + } + + override fun onSuccess(file: File) { + listener.onSuccess(file) + } + + override fun onError(e: Throwable) { + listener.onError(e) + } + }).launch() +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java new file mode 100644 index 0000000..afc0b3a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java @@ -0,0 +1,49 @@ +package com.casic.xz.meterage.model; + +public class ErrorMessageModel { + private int code; + private String data; + private String exceptionClazz; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getExceptionClazz() { + return exceptionClazz; + } + + public void setExceptionClazz(String exceptionClazz) { + this.exceptionClazz = exceptionClazz; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java new file mode 100644 index 0000000..3305db0 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.xz.meterage.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java new file mode 100644 index 0000000..5dbc99a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.xz.meterage.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt new file mode 100644 index 0000000..6ab6d16 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.xz.meterage.utils + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt new file mode 100644 index 0000000..18ddc8b --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt @@ -0,0 +1,14 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import me.leolin.shortcutbadger.ShortcutBadger + +object BadeHelper { + fun setBadgeNum(context: Context, badgeCount: Int) { + if (badgeCount == 0) { + ShortcutBadger.removeCount(context) + } else { + ShortcutBadger.applyCount(context, badgeCount) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..9feef94 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt @@ -0,0 +1,92 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.widget.ImageView +import androidx.annotation.Nullable +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.casic.xz.meterage.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.interfaces.OnCallbackListener +import com.luck.picture.lib.utils.ActivityCompatHelper + + +class GlideLoadEngine private constructor() : ImageEngine { + + companion object { + val instance: GlideLoadEngine by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + GlideLoadEngine() + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context).load(url).into(imageView); + } + + override fun loadImageBitmap( + context: Context, + url: String, + maxWidth: Int, + maxHeight: Int, + call: OnCallbackListener? + ) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .override(maxWidth, maxHeight) + .load(url) + .into(object : CustomTarget() { + override fun onResourceReady( + resource: Bitmap, @Nullable transition: Transition? + ) { + call?.onCall(resource) + } + + override fun onLoadFailed(@Nullable errorDrawable: Drawable?) { + call?.onCall(null) + } + + override fun onLoadCleared(@Nullable placeholder: Drawable?) {} + }) + } + + override fun loadAlbumCover(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .load(url) + .override(180, 180) + .sizeMultiplier(0.5f) + .transform(CenterCrop(), RoundedCorners(8)) + .placeholder(R.mipmap.load_image_error) + .into(imageView) + } + + override fun pauseRequests(context: Context?) { + context?.let { Glide.with(it).pauseRequests() } + } + + override fun resumeRequests(context: Context?) { + context?.let { Glide.with(it).resumeRequests() } + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.mipmap.load_image_error)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt new file mode 100644 index 0000000..f5a3e6c --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt @@ -0,0 +1,31 @@ +package com.casic.xz.meterage.utils + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object LoadingDialogHub { + + private lateinit var loadingDialog: QMUITipDialog + + fun show(activity: Activity, message: String) { + loadingDialog = QMUITipDialog + .Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismiss() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt index 8b7bef4..7bc5704 100644 --- a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt @@ -3,11 +3,40 @@ import android.Manifest object LocaleConstant { + + /** + * ============================================================================================= + * Array + * ============================================================================================= + * */ val USER_PERMISSIONS = arrayOf( Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ) + /** + * ============================================================================================= + * Int + * ============================================================================================= + * */ const val PERMISSIONS_CODE = 999 + const val PAGE_LIMIT = 20 + + /** + * ============================================================================================= + * Long + * ============================================================================================= + * */ + + + /** + * ============================================================================================= + * String + * ============================================================================================= + * */ + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val SERVER_BASE_URL = "http://111.198.10.15:21609" + const val ACCOUNT = "account" + const val PASSWORD = "password" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt new file mode 100644 index 0000000..5a440bd --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.xz.meterage.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt new file mode 100644 index 0000000..f40401a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt @@ -0,0 +1,20 @@ +package com.casic.xz.meterage.utils.retrofit + +import okhttp3.RequestBody +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST + +interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/sys/config/baseConfig") + suspend fun getPublicKey(): String + + /** + * 登录并获取Token + */ + @POST("/sys/user/login") + suspend fun login(@Body requestBody: RequestBody): String +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt new file mode 100644 index 0000000..c6e74be --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt @@ -0,0 +1,39 @@ +package com.casic.xz.meterage.utils.retrofit + +import com.casic.xz.meterage.utils.LocaleConstant +import com.pengxh.kt.lite.utils.RetrofitFactory +import com.pengxh.kt.lite.utils.SaveKeyValues +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject + +object RetrofitServiceManager { + + private val api by lazy { + val httpConfig = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + RetrofitFactory.createRetrofit(httpConfig) + } + + /** + * 验证PublicKey + */ + suspend fun getPublicKey(): String { + return api.getPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + val paramObject = JSONObject() + paramObject.put("sid", sid) + paramObject.put("username", account) + paramObject.put("password", secretKey) + val requestBody = paramObject.toString().toRequestBody( + "application/json;charset=UTF-8".toMediaType() + ) + return api.login(requestBody) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt b/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt index 3579db3..df0855c 100644 --- a/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt +++ b/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt @@ -1,25 +1,94 @@ package com.casic.xz.meterage.view +import androidx.lifecycle.ViewModelProvider import com.casic.xz.meterage.R import com.casic.xz.meterage.extensions.initLayoutImmersionBar +import com.casic.xz.meterage.utils.AuthenticationHelper +import com.casic.xz.meterage.utils.LoadingDialogHub +import com.casic.xz.meterage.utils.LocaleConstant +import com.casic.xz.meterage.utils.RSAUtils +import com.casic.xz.meterage.vm.AuthenticateViewModel +import com.casic.xz.meterage.vm.UserViewModel import com.gyf.immersionbar.ImmersionBar import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState import kotlinx.android.synthetic.main.activity_login.* class LoginActivity : KotlinBaseActivity() { - override fun initData() { + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var userViewModel: UserViewModel + override fun initData() { + userPhoneView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + passwordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] } override fun initEvent() { +// forgetPasswordView.setOnClickListener { +// navigatePageTo() +// } +// registerView.setOnClickListener { +// navigatePageTo() +// } + + loginButton.setOnClickListener { + val userPhone = userPhoneView.text.toString() + if (userPhone.isBlank()) { + "请输入手机号".show(this) + return@setOnClickListener + } + val userPassword = passwordView.text.toString() + if (userPassword.isBlank()) { + "请输入密码".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, userPhone) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + + authenticateViewModel.obtainPublicKey() + authenticateViewModel.keyModel.observe(this, { keyModel -> + if (keyModel.code == 200) { + val keyString = keyModel.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val dataByPublicKey = RSAUtils.encryptDataByPublicKey( + userPassword.toByteArray(), publicKey!! + ) + userViewModel.enter(keyModel.data!!.sid!!, userPhone, dataByPublicKey) + userViewModel.loginResult.observe(this, { + if (it.code == 200) { + AuthenticationHelper.saveToken(it.data!!.token!!) + PageNavigationManager.finishAllActivity() + navigatePageTo() + } + }) + } + }) + } } override fun initLayoutView(): Int = R.layout.activity_login override fun observeRequestState() { - + userViewModel.loadState.observe(this, { + when (it) { + LoadState.Loading -> LoadingDialogHub.show(this, "登录中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) } override fun setupTopBarLayout() { diff --git a/app/src/main/java/com/casic/xz/meterage/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/xz/meterage/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..96bf7ab --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/vm/AuthenticateViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.xz.meterage.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.xz.meterage.base.BaseApplication +import com.casic.xz.meterage.extensions.separateResponseCode +import com.casic.xz.meterage.extensions.toErrorMessage +import com.casic.xz.meterage.model.PublicKeyModel +import com.casic.xz.meterage.utils.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel + +class AuthenticateViewModel : BaseViewModel() { + + private val gson by lazy { Gson() } + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + val response = RetrofitServiceManager.getPublicKey() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.get()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt index 467d6e5..7f9077b 100644 --- a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt +++ b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt @@ -10,7 +10,7 @@ //根据不同设备状态栏高度设置"跳过"按钮的Margin值 var statusBarHeight = QMUIDisplayHelper.getStatusBarHeight(this) if (statusBarHeight == 0) { - statusBarHeight = 44f.dp2px(BaseApplication.obtainInstance()) + statusBarHeight = 44f.dp2px(BaseApplication.get()) } rootView.setPadding(0, statusBarHeight, 0, 0) rootView.requestLayout() diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..533d3a7 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.xz.meterage.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/String.kt b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt new file mode 100644 index 0000000..f4d63e2 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt @@ -0,0 +1,64 @@ +package com.casic.xz.meterage.extensions + +import android.content.Context +import com.casic.xz.meterage.callback.OnImageCompressListener +import com.casic.xz.meterage.model.ErrorMessageModel +import com.casic.xz.meterage.utils.LocaleConstant +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.createCompressImageDir +import com.pengxh.kt.lite.utils.SaveKeyValues +import org.json.JSONObject +import top.zibin.luban.Luban +import top.zibin.luban.OnCompressListener +import java.io.File +import java.util.* + +/** + * String扩展方法 + */ +fun String.compressImage(context: Context, listener: OnImageCompressListener) { + Luban.with(context) + .load(this) + .ignoreBy(100) + .setTargetDir(context.createCompressImageDir().toString()) + .filter { + !(it.isBlank() || it.lowercase(Locale.getDefault()).endsWith(".gif")) + } + .setCompressListener(object : OnCompressListener { + override fun onStart() { + + } + + override fun onSuccess(file: File) { + listener.onSuccess(file) + } + + override fun onError(e: Throwable) { + listener.onError(e) + } + }).launch() +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java new file mode 100644 index 0000000..afc0b3a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java @@ -0,0 +1,49 @@ +package com.casic.xz.meterage.model; + +public class ErrorMessageModel { + private int code; + private String data; + private String exceptionClazz; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getExceptionClazz() { + return exceptionClazz; + } + + public void setExceptionClazz(String exceptionClazz) { + this.exceptionClazz = exceptionClazz; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java new file mode 100644 index 0000000..3305db0 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.xz.meterage.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java new file mode 100644 index 0000000..5dbc99a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.xz.meterage.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt new file mode 100644 index 0000000..6ab6d16 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.xz.meterage.utils + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt new file mode 100644 index 0000000..18ddc8b --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt @@ -0,0 +1,14 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import me.leolin.shortcutbadger.ShortcutBadger + +object BadeHelper { + fun setBadgeNum(context: Context, badgeCount: Int) { + if (badgeCount == 0) { + ShortcutBadger.removeCount(context) + } else { + ShortcutBadger.applyCount(context, badgeCount) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..9feef94 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt @@ -0,0 +1,92 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.widget.ImageView +import androidx.annotation.Nullable +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.casic.xz.meterage.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.interfaces.OnCallbackListener +import com.luck.picture.lib.utils.ActivityCompatHelper + + +class GlideLoadEngine private constructor() : ImageEngine { + + companion object { + val instance: GlideLoadEngine by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + GlideLoadEngine() + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context).load(url).into(imageView); + } + + override fun loadImageBitmap( + context: Context, + url: String, + maxWidth: Int, + maxHeight: Int, + call: OnCallbackListener? + ) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .override(maxWidth, maxHeight) + .load(url) + .into(object : CustomTarget() { + override fun onResourceReady( + resource: Bitmap, @Nullable transition: Transition? + ) { + call?.onCall(resource) + } + + override fun onLoadFailed(@Nullable errorDrawable: Drawable?) { + call?.onCall(null) + } + + override fun onLoadCleared(@Nullable placeholder: Drawable?) {} + }) + } + + override fun loadAlbumCover(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .load(url) + .override(180, 180) + .sizeMultiplier(0.5f) + .transform(CenterCrop(), RoundedCorners(8)) + .placeholder(R.mipmap.load_image_error) + .into(imageView) + } + + override fun pauseRequests(context: Context?) { + context?.let { Glide.with(it).pauseRequests() } + } + + override fun resumeRequests(context: Context?) { + context?.let { Glide.with(it).resumeRequests() } + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.mipmap.load_image_error)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt new file mode 100644 index 0000000..f5a3e6c --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt @@ -0,0 +1,31 @@ +package com.casic.xz.meterage.utils + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object LoadingDialogHub { + + private lateinit var loadingDialog: QMUITipDialog + + fun show(activity: Activity, message: String) { + loadingDialog = QMUITipDialog + .Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismiss() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt index 8b7bef4..7bc5704 100644 --- a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt @@ -3,11 +3,40 @@ import android.Manifest object LocaleConstant { + + /** + * ============================================================================================= + * Array + * ============================================================================================= + * */ val USER_PERMISSIONS = arrayOf( Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ) + /** + * ============================================================================================= + * Int + * ============================================================================================= + * */ const val PERMISSIONS_CODE = 999 + const val PAGE_LIMIT = 20 + + /** + * ============================================================================================= + * Long + * ============================================================================================= + * */ + + + /** + * ============================================================================================= + * String + * ============================================================================================= + * */ + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val SERVER_BASE_URL = "http://111.198.10.15:21609" + const val ACCOUNT = "account" + const val PASSWORD = "password" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt new file mode 100644 index 0000000..5a440bd --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.xz.meterage.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt new file mode 100644 index 0000000..f40401a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt @@ -0,0 +1,20 @@ +package com.casic.xz.meterage.utils.retrofit + +import okhttp3.RequestBody +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST + +interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/sys/config/baseConfig") + suspend fun getPublicKey(): String + + /** + * 登录并获取Token + */ + @POST("/sys/user/login") + suspend fun login(@Body requestBody: RequestBody): String +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt new file mode 100644 index 0000000..c6e74be --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt @@ -0,0 +1,39 @@ +package com.casic.xz.meterage.utils.retrofit + +import com.casic.xz.meterage.utils.LocaleConstant +import com.pengxh.kt.lite.utils.RetrofitFactory +import com.pengxh.kt.lite.utils.SaveKeyValues +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject + +object RetrofitServiceManager { + + private val api by lazy { + val httpConfig = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + RetrofitFactory.createRetrofit(httpConfig) + } + + /** + * 验证PublicKey + */ + suspend fun getPublicKey(): String { + return api.getPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + val paramObject = JSONObject() + paramObject.put("sid", sid) + paramObject.put("username", account) + paramObject.put("password", secretKey) + val requestBody = paramObject.toString().toRequestBody( + "application/json;charset=UTF-8".toMediaType() + ) + return api.login(requestBody) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt b/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt index 3579db3..df0855c 100644 --- a/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt +++ b/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt @@ -1,25 +1,94 @@ package com.casic.xz.meterage.view +import androidx.lifecycle.ViewModelProvider import com.casic.xz.meterage.R import com.casic.xz.meterage.extensions.initLayoutImmersionBar +import com.casic.xz.meterage.utils.AuthenticationHelper +import com.casic.xz.meterage.utils.LoadingDialogHub +import com.casic.xz.meterage.utils.LocaleConstant +import com.casic.xz.meterage.utils.RSAUtils +import com.casic.xz.meterage.vm.AuthenticateViewModel +import com.casic.xz.meterage.vm.UserViewModel import com.gyf.immersionbar.ImmersionBar import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState import kotlinx.android.synthetic.main.activity_login.* class LoginActivity : KotlinBaseActivity() { - override fun initData() { + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var userViewModel: UserViewModel + override fun initData() { + userPhoneView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + passwordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] } override fun initEvent() { +// forgetPasswordView.setOnClickListener { +// navigatePageTo() +// } +// registerView.setOnClickListener { +// navigatePageTo() +// } + + loginButton.setOnClickListener { + val userPhone = userPhoneView.text.toString() + if (userPhone.isBlank()) { + "请输入手机号".show(this) + return@setOnClickListener + } + val userPassword = passwordView.text.toString() + if (userPassword.isBlank()) { + "请输入密码".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, userPhone) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + + authenticateViewModel.obtainPublicKey() + authenticateViewModel.keyModel.observe(this, { keyModel -> + if (keyModel.code == 200) { + val keyString = keyModel.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val dataByPublicKey = RSAUtils.encryptDataByPublicKey( + userPassword.toByteArray(), publicKey!! + ) + userViewModel.enter(keyModel.data!!.sid!!, userPhone, dataByPublicKey) + userViewModel.loginResult.observe(this, { + if (it.code == 200) { + AuthenticationHelper.saveToken(it.data!!.token!!) + PageNavigationManager.finishAllActivity() + navigatePageTo() + } + }) + } + }) + } } override fun initLayoutView(): Int = R.layout.activity_login override fun observeRequestState() { - + userViewModel.loadState.observe(this, { + when (it) { + LoadState.Loading -> LoadingDialogHub.show(this, "登录中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) } override fun setupTopBarLayout() { diff --git a/app/src/main/java/com/casic/xz/meterage/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/xz/meterage/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..96bf7ab --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/vm/AuthenticateViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.xz.meterage.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.xz.meterage.base.BaseApplication +import com.casic.xz.meterage.extensions.separateResponseCode +import com.casic.xz.meterage.extensions.toErrorMessage +import com.casic.xz.meterage.model.PublicKeyModel +import com.casic.xz.meterage.utils.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel + +class AuthenticateViewModel : BaseViewModel() { + + private val gson by lazy { Gson() } + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + val response = RetrofitServiceManager.getPublicKey() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.get()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/vm/UserViewModel.kt b/app/src/main/java/com/casic/xz/meterage/vm/UserViewModel.kt new file mode 100644 index 0000000..9d2a152 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/vm/UserViewModel.kt @@ -0,0 +1,110 @@ +package com.casic.xz.meterage.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.xz.meterage.base.BaseApplication +import com.casic.xz.meterage.extensions.separateResponseCode +import com.casic.xz.meterage.extensions.toErrorMessage +import com.casic.xz.meterage.model.LoginResultModel +import com.casic.xz.meterage.utils.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson by lazy { Gson() } + + // val registerResult = MutableLiveData() + val loginResult = MutableLiveData() +// val userDetailResult = MutableLiveData() +// val outResultModel = MutableLiveData() + +// fun register( +// account: String, +// password: String, +// positionLng: Double, +// positionLat: Double, +// clientId: String, +// syncName: String, +// syncDeptid: String +// ) = launch({ +// val response = RetrofitServiceManager.register( +// account, password, positionLng, positionLat, clientId, syncName, syncDeptid +// ) +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// loadState.value = LoadState.Success +// registerResult.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) +// } else { +// loadState.value = LoadState.Fail +// response.toErrorMessage().show(BaseApplication.obtainInstance()) +// } +// }, { +// loadState.value = LoadState.Fail +// it.printStackTrace() +// }) + + fun enter(sid: String, account: String, secretKey: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + loginResult.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.get()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + +// fun obtainUserDetail(account: String) = launch({ +// val response = RetrofitServiceManager.obtainUserDetail(account) +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// userDetailResult.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) +// } +// }, { +// it.printStackTrace() +// }) +// +// fun updateUserInfo(userData: UserDetailModel.DataModel) = launch({ +// loadState.value = LoadState.Loading +// val response = RetrofitServiceManager.updateUserInfo(userData) +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// loadState.value = LoadState.Success +// } else { +// loadState.value = LoadState.Fail +// response.toErrorMessage().show(BaseApplication.obtainInstance()) +// } +// }, { +// loadState.value = LoadState.Fail +// it.printStackTrace() +// }) +// +// fun out() = launch({ +// val response = RetrofitServiceManager.loginOut() +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// outResultModel.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) +// } else { +// response.toErrorMessage().show(BaseApplication.obtainInstance()) +// } +// }, { +// it.printStackTrace() +// }) +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3c25340..dc4e770 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,31 @@ implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' //图片加载框架 implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'io.github.lucksiege:pictureselector:v3.0.4' + //图片压缩 + implementation 'top.zibin:Luban:1.1.8' //官方Json解析库 implementation 'com.google.code.gson:gson:2.9.0' //Kotlin协程 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //ZBar(综合Java和C++扫码),生成二维码 + implementation 'cn.bertsir.zbarLibary:zbarlibary:1.4.2' + //桌面角标 + implementation "me.leolin:ShortcutBadger:1.1.22@aar" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt index 43f04b5..f19a4ea 100644 --- a/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/xz/meterage/base/BaseApplication.kt @@ -7,14 +7,14 @@ class BaseApplication : Application() { companion object { - private var instance: BaseApplication by Delegates.notNull() + private var application: BaseApplication by Delegates.notNull() - fun obtainInstance() = instance + fun get() = application } override fun onCreate() { super.onCreate() - instance = this + application = this SaveKeyValues.initSharedPreferences(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt new file mode 100644 index 0000000..892da5d --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/callback/OnImageCompressListener.kt @@ -0,0 +1,15 @@ +package com.casic.xz.meterage.callback + +import java.io.File + +interface OnImageCompressListener { + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + fun onSuccess(file: File) + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + fun onError(e: Throwable) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt index 467d6e5..7f9077b 100644 --- a/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt +++ b/app/src/main/java/com/casic/xz/meterage/extensions/Context.kt @@ -10,7 +10,7 @@ //根据不同设备状态栏高度设置"跳过"按钮的Margin值 var statusBarHeight = QMUIDisplayHelper.getStatusBarHeight(this) if (statusBarHeight == 0) { - statusBarHeight = 44f.dp2px(BaseApplication.obtainInstance()) + statusBarHeight = 44f.dp2px(BaseApplication.get()) } rootView.setPadding(0, statusBarHeight, 0, 0) rootView.requestLayout() diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..533d3a7 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.xz.meterage.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/extensions/String.kt b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt new file mode 100644 index 0000000..f4d63e2 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/extensions/String.kt @@ -0,0 +1,64 @@ +package com.casic.xz.meterage.extensions + +import android.content.Context +import com.casic.xz.meterage.callback.OnImageCompressListener +import com.casic.xz.meterage.model.ErrorMessageModel +import com.casic.xz.meterage.utils.LocaleConstant +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.createCompressImageDir +import com.pengxh.kt.lite.utils.SaveKeyValues +import org.json.JSONObject +import top.zibin.luban.Luban +import top.zibin.luban.OnCompressListener +import java.io.File +import java.util.* + +/** + * String扩展方法 + */ +fun String.compressImage(context: Context, listener: OnImageCompressListener) { + Luban.with(context) + .load(this) + .ignoreBy(100) + .setTargetDir(context.createCompressImageDir().toString()) + .filter { + !(it.isBlank() || it.lowercase(Locale.getDefault()).endsWith(".gif")) + } + .setCompressListener(object : OnCompressListener { + override fun onStart() { + + } + + override fun onSuccess(file: File) { + listener.onSuccess(file) + } + + override fun onError(e: Throwable) { + listener.onError(e) + } + }).launch() +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java new file mode 100644 index 0000000..afc0b3a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java @@ -0,0 +1,49 @@ +package com.casic.xz.meterage.model; + +public class ErrorMessageModel { + private int code; + private String data; + private String exceptionClazz; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getExceptionClazz() { + return exceptionClazz; + } + + public void setExceptionClazz(String exceptionClazz) { + this.exceptionClazz = exceptionClazz; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java new file mode 100644 index 0000000..3305db0 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.xz.meterage.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java new file mode 100644 index 0000000..5dbc99a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.xz.meterage.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt new file mode 100644 index 0000000..6ab6d16 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.xz.meterage.utils + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt new file mode 100644 index 0000000..18ddc8b --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt @@ -0,0 +1,14 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import me.leolin.shortcutbadger.ShortcutBadger + +object BadeHelper { + fun setBadgeNum(context: Context, badgeCount: Int) { + if (badgeCount == 0) { + ShortcutBadger.removeCount(context) + } else { + ShortcutBadger.applyCount(context, badgeCount) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..9feef94 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt @@ -0,0 +1,92 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.widget.ImageView +import androidx.annotation.Nullable +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.casic.xz.meterage.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.interfaces.OnCallbackListener +import com.luck.picture.lib.utils.ActivityCompatHelper + + +class GlideLoadEngine private constructor() : ImageEngine { + + companion object { + val instance: GlideLoadEngine by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + GlideLoadEngine() + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context).load(url).into(imageView); + } + + override fun loadImageBitmap( + context: Context, + url: String, + maxWidth: Int, + maxHeight: Int, + call: OnCallbackListener? + ) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .override(maxWidth, maxHeight) + .load(url) + .into(object : CustomTarget() { + override fun onResourceReady( + resource: Bitmap, @Nullable transition: Transition? + ) { + call?.onCall(resource) + } + + override fun onLoadFailed(@Nullable errorDrawable: Drawable?) { + call?.onCall(null) + } + + override fun onLoadCleared(@Nullable placeholder: Drawable?) {} + }) + } + + override fun loadAlbumCover(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .load(url) + .override(180, 180) + .sizeMultiplier(0.5f) + .transform(CenterCrop(), RoundedCorners(8)) + .placeholder(R.mipmap.load_image_error) + .into(imageView) + } + + override fun pauseRequests(context: Context?) { + context?.let { Glide.with(it).pauseRequests() } + } + + override fun resumeRequests(context: Context?) { + context?.let { Glide.with(it).resumeRequests() } + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.mipmap.load_image_error)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt new file mode 100644 index 0000000..f5a3e6c --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt @@ -0,0 +1,31 @@ +package com.casic.xz.meterage.utils + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object LoadingDialogHub { + + private lateinit var loadingDialog: QMUITipDialog + + fun show(activity: Activity, message: String) { + loadingDialog = QMUITipDialog + .Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismiss() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt index 8b7bef4..7bc5704 100644 --- a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt @@ -3,11 +3,40 @@ import android.Manifest object LocaleConstant { + + /** + * ============================================================================================= + * Array + * ============================================================================================= + * */ val USER_PERMISSIONS = arrayOf( Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ) + /** + * ============================================================================================= + * Int + * ============================================================================================= + * */ const val PERMISSIONS_CODE = 999 + const val PAGE_LIMIT = 20 + + /** + * ============================================================================================= + * Long + * ============================================================================================= + * */ + + + /** + * ============================================================================================= + * String + * ============================================================================================= + * */ + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val SERVER_BASE_URL = "http://111.198.10.15:21609" + const val ACCOUNT = "account" + const val PASSWORD = "password" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt new file mode 100644 index 0000000..5a440bd --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.xz.meterage.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt new file mode 100644 index 0000000..f40401a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt @@ -0,0 +1,20 @@ +package com.casic.xz.meterage.utils.retrofit + +import okhttp3.RequestBody +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST + +interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/sys/config/baseConfig") + suspend fun getPublicKey(): String + + /** + * 登录并获取Token + */ + @POST("/sys/user/login") + suspend fun login(@Body requestBody: RequestBody): String +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt new file mode 100644 index 0000000..c6e74be --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt @@ -0,0 +1,39 @@ +package com.casic.xz.meterage.utils.retrofit + +import com.casic.xz.meterage.utils.LocaleConstant +import com.pengxh.kt.lite.utils.RetrofitFactory +import com.pengxh.kt.lite.utils.SaveKeyValues +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject + +object RetrofitServiceManager { + + private val api by lazy { + val httpConfig = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + RetrofitFactory.createRetrofit(httpConfig) + } + + /** + * 验证PublicKey + */ + suspend fun getPublicKey(): String { + return api.getPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + val paramObject = JSONObject() + paramObject.put("sid", sid) + paramObject.put("username", account) + paramObject.put("password", secretKey) + val requestBody = paramObject.toString().toRequestBody( + "application/json;charset=UTF-8".toMediaType() + ) + return api.login(requestBody) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt b/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt index 3579db3..df0855c 100644 --- a/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt +++ b/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt @@ -1,25 +1,94 @@ package com.casic.xz.meterage.view +import androidx.lifecycle.ViewModelProvider import com.casic.xz.meterage.R import com.casic.xz.meterage.extensions.initLayoutImmersionBar +import com.casic.xz.meterage.utils.AuthenticationHelper +import com.casic.xz.meterage.utils.LoadingDialogHub +import com.casic.xz.meterage.utils.LocaleConstant +import com.casic.xz.meterage.utils.RSAUtils +import com.casic.xz.meterage.vm.AuthenticateViewModel +import com.casic.xz.meterage.vm.UserViewModel import com.gyf.immersionbar.ImmersionBar import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState import kotlinx.android.synthetic.main.activity_login.* class LoginActivity : KotlinBaseActivity() { - override fun initData() { + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var userViewModel: UserViewModel + override fun initData() { + userPhoneView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + passwordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] } override fun initEvent() { +// forgetPasswordView.setOnClickListener { +// navigatePageTo() +// } +// registerView.setOnClickListener { +// navigatePageTo() +// } + + loginButton.setOnClickListener { + val userPhone = userPhoneView.text.toString() + if (userPhone.isBlank()) { + "请输入手机号".show(this) + return@setOnClickListener + } + val userPassword = passwordView.text.toString() + if (userPassword.isBlank()) { + "请输入密码".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, userPhone) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + + authenticateViewModel.obtainPublicKey() + authenticateViewModel.keyModel.observe(this, { keyModel -> + if (keyModel.code == 200) { + val keyString = keyModel.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val dataByPublicKey = RSAUtils.encryptDataByPublicKey( + userPassword.toByteArray(), publicKey!! + ) + userViewModel.enter(keyModel.data!!.sid!!, userPhone, dataByPublicKey) + userViewModel.loginResult.observe(this, { + if (it.code == 200) { + AuthenticationHelper.saveToken(it.data!!.token!!) + PageNavigationManager.finishAllActivity() + navigatePageTo() + } + }) + } + }) + } } override fun initLayoutView(): Int = R.layout.activity_login override fun observeRequestState() { - + userViewModel.loadState.observe(this, { + when (it) { + LoadState.Loading -> LoadingDialogHub.show(this, "登录中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) } override fun setupTopBarLayout() { diff --git a/app/src/main/java/com/casic/xz/meterage/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/xz/meterage/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..96bf7ab --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/vm/AuthenticateViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.xz.meterage.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.xz.meterage.base.BaseApplication +import com.casic.xz.meterage.extensions.separateResponseCode +import com.casic.xz.meterage.extensions.toErrorMessage +import com.casic.xz.meterage.model.PublicKeyModel +import com.casic.xz.meterage.utils.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel + +class AuthenticateViewModel : BaseViewModel() { + + private val gson by lazy { Gson() } + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + val response = RetrofitServiceManager.getPublicKey() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.get()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/vm/UserViewModel.kt b/app/src/main/java/com/casic/xz/meterage/vm/UserViewModel.kt new file mode 100644 index 0000000..9d2a152 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/vm/UserViewModel.kt @@ -0,0 +1,110 @@ +package com.casic.xz.meterage.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.xz.meterage.base.BaseApplication +import com.casic.xz.meterage.extensions.separateResponseCode +import com.casic.xz.meterage.extensions.toErrorMessage +import com.casic.xz.meterage.model.LoginResultModel +import com.casic.xz.meterage.utils.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson by lazy { Gson() } + + // val registerResult = MutableLiveData() + val loginResult = MutableLiveData() +// val userDetailResult = MutableLiveData() +// val outResultModel = MutableLiveData() + +// fun register( +// account: String, +// password: String, +// positionLng: Double, +// positionLat: Double, +// clientId: String, +// syncName: String, +// syncDeptid: String +// ) = launch({ +// val response = RetrofitServiceManager.register( +// account, password, positionLng, positionLat, clientId, syncName, syncDeptid +// ) +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// loadState.value = LoadState.Success +// registerResult.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) +// } else { +// loadState.value = LoadState.Fail +// response.toErrorMessage().show(BaseApplication.obtainInstance()) +// } +// }, { +// loadState.value = LoadState.Fail +// it.printStackTrace() +// }) + + fun enter(sid: String, account: String, secretKey: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + loginResult.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.get()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + +// fun obtainUserDetail(account: String) = launch({ +// val response = RetrofitServiceManager.obtainUserDetail(account) +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// userDetailResult.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) +// } +// }, { +// it.printStackTrace() +// }) +// +// fun updateUserInfo(userData: UserDetailModel.DataModel) = launch({ +// loadState.value = LoadState.Loading +// val response = RetrofitServiceManager.updateUserInfo(userData) +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// loadState.value = LoadState.Success +// } else { +// loadState.value = LoadState.Fail +// response.toErrorMessage().show(BaseApplication.obtainInstance()) +// } +// }, { +// loadState.value = LoadState.Fail +// it.printStackTrace() +// }) +// +// fun out() = launch({ +// val response = RetrofitServiceManager.loginOut() +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// outResultModel.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) +// } else { +// response.toErrorMessage().show(BaseApplication.obtainInstance()) +// } +// }, { +// it.printStackTrace() +// }) +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 41a0ed3..4b7a0a6 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -12,8 +12,8 @@ android:orientation="vertical"> + android:textSize="@dimen/sp_16" /> + android:textSize="@dimen/sp_16" /> ( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java new file mode 100644 index 0000000..afc0b3a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java @@ -0,0 +1,49 @@ +package com.casic.xz.meterage.model; + +public class ErrorMessageModel { + private int code; + private String data; + private String exceptionClazz; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getExceptionClazz() { + return exceptionClazz; + } + + public void setExceptionClazz(String exceptionClazz) { + this.exceptionClazz = exceptionClazz; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java new file mode 100644 index 0000000..3305db0 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.xz.meterage.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java new file mode 100644 index 0000000..5dbc99a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.xz.meterage.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt new file mode 100644 index 0000000..6ab6d16 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.xz.meterage.utils + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt new file mode 100644 index 0000000..18ddc8b --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt @@ -0,0 +1,14 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import me.leolin.shortcutbadger.ShortcutBadger + +object BadeHelper { + fun setBadgeNum(context: Context, badgeCount: Int) { + if (badgeCount == 0) { + ShortcutBadger.removeCount(context) + } else { + ShortcutBadger.applyCount(context, badgeCount) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..9feef94 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt @@ -0,0 +1,92 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.widget.ImageView +import androidx.annotation.Nullable +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.casic.xz.meterage.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.interfaces.OnCallbackListener +import com.luck.picture.lib.utils.ActivityCompatHelper + + +class GlideLoadEngine private constructor() : ImageEngine { + + companion object { + val instance: GlideLoadEngine by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + GlideLoadEngine() + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context).load(url).into(imageView); + } + + override fun loadImageBitmap( + context: Context, + url: String, + maxWidth: Int, + maxHeight: Int, + call: OnCallbackListener? + ) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .override(maxWidth, maxHeight) + .load(url) + .into(object : CustomTarget() { + override fun onResourceReady( + resource: Bitmap, @Nullable transition: Transition? + ) { + call?.onCall(resource) + } + + override fun onLoadFailed(@Nullable errorDrawable: Drawable?) { + call?.onCall(null) + } + + override fun onLoadCleared(@Nullable placeholder: Drawable?) {} + }) + } + + override fun loadAlbumCover(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .load(url) + .override(180, 180) + .sizeMultiplier(0.5f) + .transform(CenterCrop(), RoundedCorners(8)) + .placeholder(R.mipmap.load_image_error) + .into(imageView) + } + + override fun pauseRequests(context: Context?) { + context?.let { Glide.with(it).pauseRequests() } + } + + override fun resumeRequests(context: Context?) { + context?.let { Glide.with(it).resumeRequests() } + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.mipmap.load_image_error)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt new file mode 100644 index 0000000..f5a3e6c --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt @@ -0,0 +1,31 @@ +package com.casic.xz.meterage.utils + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object LoadingDialogHub { + + private lateinit var loadingDialog: QMUITipDialog + + fun show(activity: Activity, message: String) { + loadingDialog = QMUITipDialog + .Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismiss() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt index 8b7bef4..7bc5704 100644 --- a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt @@ -3,11 +3,40 @@ import android.Manifest object LocaleConstant { + + /** + * ============================================================================================= + * Array + * ============================================================================================= + * */ val USER_PERMISSIONS = arrayOf( Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ) + /** + * ============================================================================================= + * Int + * ============================================================================================= + * */ const val PERMISSIONS_CODE = 999 + const val PAGE_LIMIT = 20 + + /** + * ============================================================================================= + * Long + * ============================================================================================= + * */ + + + /** + * ============================================================================================= + * String + * ============================================================================================= + * */ + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val SERVER_BASE_URL = "http://111.198.10.15:21609" + const val ACCOUNT = "account" + const val PASSWORD = "password" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt new file mode 100644 index 0000000..5a440bd --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.xz.meterage.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt new file mode 100644 index 0000000..f40401a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt @@ -0,0 +1,20 @@ +package com.casic.xz.meterage.utils.retrofit + +import okhttp3.RequestBody +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST + +interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/sys/config/baseConfig") + suspend fun getPublicKey(): String + + /** + * 登录并获取Token + */ + @POST("/sys/user/login") + suspend fun login(@Body requestBody: RequestBody): String +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt new file mode 100644 index 0000000..c6e74be --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt @@ -0,0 +1,39 @@ +package com.casic.xz.meterage.utils.retrofit + +import com.casic.xz.meterage.utils.LocaleConstant +import com.pengxh.kt.lite.utils.RetrofitFactory +import com.pengxh.kt.lite.utils.SaveKeyValues +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject + +object RetrofitServiceManager { + + private val api by lazy { + val httpConfig = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + RetrofitFactory.createRetrofit(httpConfig) + } + + /** + * 验证PublicKey + */ + suspend fun getPublicKey(): String { + return api.getPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + val paramObject = JSONObject() + paramObject.put("sid", sid) + paramObject.put("username", account) + paramObject.put("password", secretKey) + val requestBody = paramObject.toString().toRequestBody( + "application/json;charset=UTF-8".toMediaType() + ) + return api.login(requestBody) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt b/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt index 3579db3..df0855c 100644 --- a/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt +++ b/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt @@ -1,25 +1,94 @@ package com.casic.xz.meterage.view +import androidx.lifecycle.ViewModelProvider import com.casic.xz.meterage.R import com.casic.xz.meterage.extensions.initLayoutImmersionBar +import com.casic.xz.meterage.utils.AuthenticationHelper +import com.casic.xz.meterage.utils.LoadingDialogHub +import com.casic.xz.meterage.utils.LocaleConstant +import com.casic.xz.meterage.utils.RSAUtils +import com.casic.xz.meterage.vm.AuthenticateViewModel +import com.casic.xz.meterage.vm.UserViewModel import com.gyf.immersionbar.ImmersionBar import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState import kotlinx.android.synthetic.main.activity_login.* class LoginActivity : KotlinBaseActivity() { - override fun initData() { + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var userViewModel: UserViewModel + override fun initData() { + userPhoneView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + passwordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] } override fun initEvent() { +// forgetPasswordView.setOnClickListener { +// navigatePageTo() +// } +// registerView.setOnClickListener { +// navigatePageTo() +// } + + loginButton.setOnClickListener { + val userPhone = userPhoneView.text.toString() + if (userPhone.isBlank()) { + "请输入手机号".show(this) + return@setOnClickListener + } + val userPassword = passwordView.text.toString() + if (userPassword.isBlank()) { + "请输入密码".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, userPhone) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + + authenticateViewModel.obtainPublicKey() + authenticateViewModel.keyModel.observe(this, { keyModel -> + if (keyModel.code == 200) { + val keyString = keyModel.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val dataByPublicKey = RSAUtils.encryptDataByPublicKey( + userPassword.toByteArray(), publicKey!! + ) + userViewModel.enter(keyModel.data!!.sid!!, userPhone, dataByPublicKey) + userViewModel.loginResult.observe(this, { + if (it.code == 200) { + AuthenticationHelper.saveToken(it.data!!.token!!) + PageNavigationManager.finishAllActivity() + navigatePageTo() + } + }) + } + }) + } } override fun initLayoutView(): Int = R.layout.activity_login override fun observeRequestState() { - + userViewModel.loadState.observe(this, { + when (it) { + LoadState.Loading -> LoadingDialogHub.show(this, "登录中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) } override fun setupTopBarLayout() { diff --git a/app/src/main/java/com/casic/xz/meterage/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/xz/meterage/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..96bf7ab --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/vm/AuthenticateViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.xz.meterage.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.xz.meterage.base.BaseApplication +import com.casic.xz.meterage.extensions.separateResponseCode +import com.casic.xz.meterage.extensions.toErrorMessage +import com.casic.xz.meterage.model.PublicKeyModel +import com.casic.xz.meterage.utils.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel + +class AuthenticateViewModel : BaseViewModel() { + + private val gson by lazy { Gson() } + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + val response = RetrofitServiceManager.getPublicKey() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.get()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/vm/UserViewModel.kt b/app/src/main/java/com/casic/xz/meterage/vm/UserViewModel.kt new file mode 100644 index 0000000..9d2a152 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/vm/UserViewModel.kt @@ -0,0 +1,110 @@ +package com.casic.xz.meterage.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.xz.meterage.base.BaseApplication +import com.casic.xz.meterage.extensions.separateResponseCode +import com.casic.xz.meterage.extensions.toErrorMessage +import com.casic.xz.meterage.model.LoginResultModel +import com.casic.xz.meterage.utils.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson by lazy { Gson() } + + // val registerResult = MutableLiveData() + val loginResult = MutableLiveData() +// val userDetailResult = MutableLiveData() +// val outResultModel = MutableLiveData() + +// fun register( +// account: String, +// password: String, +// positionLng: Double, +// positionLat: Double, +// clientId: String, +// syncName: String, +// syncDeptid: String +// ) = launch({ +// val response = RetrofitServiceManager.register( +// account, password, positionLng, positionLat, clientId, syncName, syncDeptid +// ) +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// loadState.value = LoadState.Success +// registerResult.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) +// } else { +// loadState.value = LoadState.Fail +// response.toErrorMessage().show(BaseApplication.obtainInstance()) +// } +// }, { +// loadState.value = LoadState.Fail +// it.printStackTrace() +// }) + + fun enter(sid: String, account: String, secretKey: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + loginResult.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.get()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + +// fun obtainUserDetail(account: String) = launch({ +// val response = RetrofitServiceManager.obtainUserDetail(account) +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// userDetailResult.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) +// } +// }, { +// it.printStackTrace() +// }) +// +// fun updateUserInfo(userData: UserDetailModel.DataModel) = launch({ +// loadState.value = LoadState.Loading +// val response = RetrofitServiceManager.updateUserInfo(userData) +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// loadState.value = LoadState.Success +// } else { +// loadState.value = LoadState.Fail +// response.toErrorMessage().show(BaseApplication.obtainInstance()) +// } +// }, { +// loadState.value = LoadState.Fail +// it.printStackTrace() +// }) +// +// fun out() = launch({ +// val response = RetrofitServiceManager.loginOut() +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// outResultModel.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) +// } else { +// response.toErrorMessage().show(BaseApplication.obtainInstance()) +// } +// }, { +// it.printStackTrace() +// }) +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 41a0ed3..4b7a0a6 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -12,8 +12,8 @@ android:orientation="vertical"> + android:textSize="@dimen/sp_16" /> + android:textSize="@dimen/sp_16" /> ( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java new file mode 100644 index 0000000..afc0b3a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java @@ -0,0 +1,49 @@ +package com.casic.xz.meterage.model; + +public class ErrorMessageModel { + private int code; + private String data; + private String exceptionClazz; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getExceptionClazz() { + return exceptionClazz; + } + + public void setExceptionClazz(String exceptionClazz) { + this.exceptionClazz = exceptionClazz; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java new file mode 100644 index 0000000..3305db0 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.xz.meterage.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java new file mode 100644 index 0000000..5dbc99a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.xz.meterage.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt new file mode 100644 index 0000000..6ab6d16 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.xz.meterage.utils + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt new file mode 100644 index 0000000..18ddc8b --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt @@ -0,0 +1,14 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import me.leolin.shortcutbadger.ShortcutBadger + +object BadeHelper { + fun setBadgeNum(context: Context, badgeCount: Int) { + if (badgeCount == 0) { + ShortcutBadger.removeCount(context) + } else { + ShortcutBadger.applyCount(context, badgeCount) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..9feef94 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt @@ -0,0 +1,92 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.widget.ImageView +import androidx.annotation.Nullable +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.casic.xz.meterage.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.interfaces.OnCallbackListener +import com.luck.picture.lib.utils.ActivityCompatHelper + + +class GlideLoadEngine private constructor() : ImageEngine { + + companion object { + val instance: GlideLoadEngine by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + GlideLoadEngine() + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context).load(url).into(imageView); + } + + override fun loadImageBitmap( + context: Context, + url: String, + maxWidth: Int, + maxHeight: Int, + call: OnCallbackListener? + ) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .override(maxWidth, maxHeight) + .load(url) + .into(object : CustomTarget() { + override fun onResourceReady( + resource: Bitmap, @Nullable transition: Transition? + ) { + call?.onCall(resource) + } + + override fun onLoadFailed(@Nullable errorDrawable: Drawable?) { + call?.onCall(null) + } + + override fun onLoadCleared(@Nullable placeholder: Drawable?) {} + }) + } + + override fun loadAlbumCover(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .load(url) + .override(180, 180) + .sizeMultiplier(0.5f) + .transform(CenterCrop(), RoundedCorners(8)) + .placeholder(R.mipmap.load_image_error) + .into(imageView) + } + + override fun pauseRequests(context: Context?) { + context?.let { Glide.with(it).pauseRequests() } + } + + override fun resumeRequests(context: Context?) { + context?.let { Glide.with(it).resumeRequests() } + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.mipmap.load_image_error)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt new file mode 100644 index 0000000..f5a3e6c --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt @@ -0,0 +1,31 @@ +package com.casic.xz.meterage.utils + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object LoadingDialogHub { + + private lateinit var loadingDialog: QMUITipDialog + + fun show(activity: Activity, message: String) { + loadingDialog = QMUITipDialog + .Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismiss() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt index 8b7bef4..7bc5704 100644 --- a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt @@ -3,11 +3,40 @@ import android.Manifest object LocaleConstant { + + /** + * ============================================================================================= + * Array + * ============================================================================================= + * */ val USER_PERMISSIONS = arrayOf( Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ) + /** + * ============================================================================================= + * Int + * ============================================================================================= + * */ const val PERMISSIONS_CODE = 999 + const val PAGE_LIMIT = 20 + + /** + * ============================================================================================= + * Long + * ============================================================================================= + * */ + + + /** + * ============================================================================================= + * String + * ============================================================================================= + * */ + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val SERVER_BASE_URL = "http://111.198.10.15:21609" + const val ACCOUNT = "account" + const val PASSWORD = "password" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt new file mode 100644 index 0000000..5a440bd --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.xz.meterage.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt new file mode 100644 index 0000000..f40401a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt @@ -0,0 +1,20 @@ +package com.casic.xz.meterage.utils.retrofit + +import okhttp3.RequestBody +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST + +interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/sys/config/baseConfig") + suspend fun getPublicKey(): String + + /** + * 登录并获取Token + */ + @POST("/sys/user/login") + suspend fun login(@Body requestBody: RequestBody): String +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt new file mode 100644 index 0000000..c6e74be --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt @@ -0,0 +1,39 @@ +package com.casic.xz.meterage.utils.retrofit + +import com.casic.xz.meterage.utils.LocaleConstant +import com.pengxh.kt.lite.utils.RetrofitFactory +import com.pengxh.kt.lite.utils.SaveKeyValues +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject + +object RetrofitServiceManager { + + private val api by lazy { + val httpConfig = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + RetrofitFactory.createRetrofit(httpConfig) + } + + /** + * 验证PublicKey + */ + suspend fun getPublicKey(): String { + return api.getPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + val paramObject = JSONObject() + paramObject.put("sid", sid) + paramObject.put("username", account) + paramObject.put("password", secretKey) + val requestBody = paramObject.toString().toRequestBody( + "application/json;charset=UTF-8".toMediaType() + ) + return api.login(requestBody) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt b/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt index 3579db3..df0855c 100644 --- a/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt +++ b/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt @@ -1,25 +1,94 @@ package com.casic.xz.meterage.view +import androidx.lifecycle.ViewModelProvider import com.casic.xz.meterage.R import com.casic.xz.meterage.extensions.initLayoutImmersionBar +import com.casic.xz.meterage.utils.AuthenticationHelper +import com.casic.xz.meterage.utils.LoadingDialogHub +import com.casic.xz.meterage.utils.LocaleConstant +import com.casic.xz.meterage.utils.RSAUtils +import com.casic.xz.meterage.vm.AuthenticateViewModel +import com.casic.xz.meterage.vm.UserViewModel import com.gyf.immersionbar.ImmersionBar import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState import kotlinx.android.synthetic.main.activity_login.* class LoginActivity : KotlinBaseActivity() { - override fun initData() { + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var userViewModel: UserViewModel + override fun initData() { + userPhoneView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + passwordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] } override fun initEvent() { +// forgetPasswordView.setOnClickListener { +// navigatePageTo() +// } +// registerView.setOnClickListener { +// navigatePageTo() +// } + + loginButton.setOnClickListener { + val userPhone = userPhoneView.text.toString() + if (userPhone.isBlank()) { + "请输入手机号".show(this) + return@setOnClickListener + } + val userPassword = passwordView.text.toString() + if (userPassword.isBlank()) { + "请输入密码".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, userPhone) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + + authenticateViewModel.obtainPublicKey() + authenticateViewModel.keyModel.observe(this, { keyModel -> + if (keyModel.code == 200) { + val keyString = keyModel.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val dataByPublicKey = RSAUtils.encryptDataByPublicKey( + userPassword.toByteArray(), publicKey!! + ) + userViewModel.enter(keyModel.data!!.sid!!, userPhone, dataByPublicKey) + userViewModel.loginResult.observe(this, { + if (it.code == 200) { + AuthenticationHelper.saveToken(it.data!!.token!!) + PageNavigationManager.finishAllActivity() + navigatePageTo() + } + }) + } + }) + } } override fun initLayoutView(): Int = R.layout.activity_login override fun observeRequestState() { - + userViewModel.loadState.observe(this, { + when (it) { + LoadState.Loading -> LoadingDialogHub.show(this, "登录中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) } override fun setupTopBarLayout() { diff --git a/app/src/main/java/com/casic/xz/meterage/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/xz/meterage/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..96bf7ab --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/vm/AuthenticateViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.xz.meterage.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.xz.meterage.base.BaseApplication +import com.casic.xz.meterage.extensions.separateResponseCode +import com.casic.xz.meterage.extensions.toErrorMessage +import com.casic.xz.meterage.model.PublicKeyModel +import com.casic.xz.meterage.utils.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel + +class AuthenticateViewModel : BaseViewModel() { + + private val gson by lazy { Gson() } + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + val response = RetrofitServiceManager.getPublicKey() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.get()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/vm/UserViewModel.kt b/app/src/main/java/com/casic/xz/meterage/vm/UserViewModel.kt new file mode 100644 index 0000000..9d2a152 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/vm/UserViewModel.kt @@ -0,0 +1,110 @@ +package com.casic.xz.meterage.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.xz.meterage.base.BaseApplication +import com.casic.xz.meterage.extensions.separateResponseCode +import com.casic.xz.meterage.extensions.toErrorMessage +import com.casic.xz.meterage.model.LoginResultModel +import com.casic.xz.meterage.utils.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson by lazy { Gson() } + + // val registerResult = MutableLiveData() + val loginResult = MutableLiveData() +// val userDetailResult = MutableLiveData() +// val outResultModel = MutableLiveData() + +// fun register( +// account: String, +// password: String, +// positionLng: Double, +// positionLat: Double, +// clientId: String, +// syncName: String, +// syncDeptid: String +// ) = launch({ +// val response = RetrofitServiceManager.register( +// account, password, positionLng, positionLat, clientId, syncName, syncDeptid +// ) +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// loadState.value = LoadState.Success +// registerResult.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) +// } else { +// loadState.value = LoadState.Fail +// response.toErrorMessage().show(BaseApplication.obtainInstance()) +// } +// }, { +// loadState.value = LoadState.Fail +// it.printStackTrace() +// }) + + fun enter(sid: String, account: String, secretKey: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + loginResult.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.get()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + +// fun obtainUserDetail(account: String) = launch({ +// val response = RetrofitServiceManager.obtainUserDetail(account) +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// userDetailResult.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) +// } +// }, { +// it.printStackTrace() +// }) +// +// fun updateUserInfo(userData: UserDetailModel.DataModel) = launch({ +// loadState.value = LoadState.Loading +// val response = RetrofitServiceManager.updateUserInfo(userData) +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// loadState.value = LoadState.Success +// } else { +// loadState.value = LoadState.Fail +// response.toErrorMessage().show(BaseApplication.obtainInstance()) +// } +// }, { +// loadState.value = LoadState.Fail +// it.printStackTrace() +// }) +// +// fun out() = launch({ +// val response = RetrofitServiceManager.loginOut() +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// outResultModel.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) +// } else { +// response.toErrorMessage().show(BaseApplication.obtainInstance()) +// } +// }, { +// it.printStackTrace() +// }) +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 41a0ed3..4b7a0a6 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -12,8 +12,8 @@ android:orientation="vertical"> + android:textSize="@dimen/sp_16" /> + android:textSize="@dimen/sp_16" /> ( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java new file mode 100644 index 0000000..afc0b3a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/ErrorMessageModel.java @@ -0,0 +1,49 @@ +package com.casic.xz.meterage.model; + +public class ErrorMessageModel { + private int code; + private String data; + private String exceptionClazz; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getExceptionClazz() { + return exceptionClazz; + } + + public void setExceptionClazz(String exceptionClazz) { + this.exceptionClazz = exceptionClazz; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java new file mode 100644 index 0000000..3305db0 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.xz.meterage.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java new file mode 100644 index 0000000..5dbc99a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.xz.meterage.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt new file mode 100644 index 0000000..6ab6d16 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.xz.meterage.utils + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt new file mode 100644 index 0000000..18ddc8b --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/BadeHelper.kt @@ -0,0 +1,14 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import me.leolin.shortcutbadger.ShortcutBadger + +object BadeHelper { + fun setBadgeNum(context: Context, badgeCount: Int) { + if (badgeCount == 0) { + ShortcutBadger.removeCount(context) + } else { + ShortcutBadger.applyCount(context, badgeCount) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..9feef94 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/GlideLoadEngine.kt @@ -0,0 +1,92 @@ +package com.casic.xz.meterage.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.widget.ImageView +import androidx.annotation.Nullable +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.casic.xz.meterage.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.interfaces.OnCallbackListener +import com.luck.picture.lib.utils.ActivityCompatHelper + + +class GlideLoadEngine private constructor() : ImageEngine { + + companion object { + val instance: GlideLoadEngine by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + GlideLoadEngine() + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context).load(url).into(imageView); + } + + override fun loadImageBitmap( + context: Context, + url: String, + maxWidth: Int, + maxHeight: Int, + call: OnCallbackListener? + ) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .override(maxWidth, maxHeight) + .load(url) + .into(object : CustomTarget() { + override fun onResourceReady( + resource: Bitmap, @Nullable transition: Transition? + ) { + call?.onCall(resource) + } + + override fun onLoadFailed(@Nullable errorDrawable: Drawable?) { + call?.onCall(null) + } + + override fun onLoadCleared(@Nullable placeholder: Drawable?) {} + }) + } + + override fun loadAlbumCover(context: Context, url: String, imageView: ImageView) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .load(url) + .override(180, 180) + .sizeMultiplier(0.5f) + .transform(CenterCrop(), RoundedCorners(8)) + .placeholder(R.mipmap.load_image_error) + .into(imageView) + } + + override fun pauseRequests(context: Context?) { + context?.let { Glide.with(it).pauseRequests() } + } + + override fun resumeRequests(context: Context?) { + context?.let { Glide.with(it).resumeRequests() } + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.mipmap.load_image_error)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt new file mode 100644 index 0000000..f5a3e6c --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/LoadingDialogHub.kt @@ -0,0 +1,31 @@ +package com.casic.xz.meterage.utils + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object LoadingDialogHub { + + private lateinit var loadingDialog: QMUITipDialog + + fun show(activity: Activity, message: String) { + loadingDialog = QMUITipDialog + .Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismiss() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt index 8b7bef4..7bc5704 100644 --- a/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/xz/meterage/utils/LocaleConstant.kt @@ -3,11 +3,40 @@ import android.Manifest object LocaleConstant { + + /** + * ============================================================================================= + * Array + * ============================================================================================= + * */ val USER_PERMISSIONS = arrayOf( Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ) + /** + * ============================================================================================= + * Int + * ============================================================================================= + * */ const val PERMISSIONS_CODE = 999 + const val PAGE_LIMIT = 20 + + /** + * ============================================================================================= + * Long + * ============================================================================================= + * */ + + + /** + * ============================================================================================= + * String + * ============================================================================================= + * */ + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val SERVER_BASE_URL = "http://111.198.10.15:21609" + const val ACCOUNT = "account" + const val PASSWORD = "password" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt new file mode 100644 index 0000000..5a440bd --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.xz.meterage.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt new file mode 100644 index 0000000..f40401a --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitService.kt @@ -0,0 +1,20 @@ +package com.casic.xz.meterage.utils.retrofit + +import okhttp3.RequestBody +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST + +interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/sys/config/baseConfig") + suspend fun getPublicKey(): String + + /** + * 登录并获取Token + */ + @POST("/sys/user/login") + suspend fun login(@Body requestBody: RequestBody): String +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt new file mode 100644 index 0000000..c6e74be --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/utils/retrofit/RetrofitServiceManager.kt @@ -0,0 +1,39 @@ +package com.casic.xz.meterage.utils.retrofit + +import com.casic.xz.meterage.utils.LocaleConstant +import com.pengxh.kt.lite.utils.RetrofitFactory +import com.pengxh.kt.lite.utils.SaveKeyValues +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject + +object RetrofitServiceManager { + + private val api by lazy { + val httpConfig = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.SERVER_BASE_URL + ) as String + RetrofitFactory.createRetrofit(httpConfig) + } + + /** + * 验证PublicKey + */ + suspend fun getPublicKey(): String { + return api.getPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + val paramObject = JSONObject() + paramObject.put("sid", sid) + paramObject.put("username", account) + paramObject.put("password", secretKey) + val requestBody = paramObject.toString().toRequestBody( + "application/json;charset=UTF-8".toMediaType() + ) + return api.login(requestBody) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt b/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt index 3579db3..df0855c 100644 --- a/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt +++ b/app/src/main/java/com/casic/xz/meterage/view/LoginActivity.kt @@ -1,25 +1,94 @@ package com.casic.xz.meterage.view +import androidx.lifecycle.ViewModelProvider import com.casic.xz.meterage.R import com.casic.xz.meterage.extensions.initLayoutImmersionBar +import com.casic.xz.meterage.utils.AuthenticationHelper +import com.casic.xz.meterage.utils.LoadingDialogHub +import com.casic.xz.meterage.utils.LocaleConstant +import com.casic.xz.meterage.utils.RSAUtils +import com.casic.xz.meterage.vm.AuthenticateViewModel +import com.casic.xz.meterage.vm.UserViewModel import com.gyf.immersionbar.ImmersionBar import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState import kotlinx.android.synthetic.main.activity_login.* class LoginActivity : KotlinBaseActivity() { - override fun initData() { + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var userViewModel: UserViewModel + override fun initData() { + userPhoneView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + passwordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] } override fun initEvent() { +// forgetPasswordView.setOnClickListener { +// navigatePageTo() +// } +// registerView.setOnClickListener { +// navigatePageTo() +// } + + loginButton.setOnClickListener { + val userPhone = userPhoneView.text.toString() + if (userPhone.isBlank()) { + "请输入手机号".show(this) + return@setOnClickListener + } + val userPassword = passwordView.text.toString() + if (userPassword.isBlank()) { + "请输入密码".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, userPhone) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + + authenticateViewModel.obtainPublicKey() + authenticateViewModel.keyModel.observe(this, { keyModel -> + if (keyModel.code == 200) { + val keyString = keyModel.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val dataByPublicKey = RSAUtils.encryptDataByPublicKey( + userPassword.toByteArray(), publicKey!! + ) + userViewModel.enter(keyModel.data!!.sid!!, userPhone, dataByPublicKey) + userViewModel.loginResult.observe(this, { + if (it.code == 200) { + AuthenticationHelper.saveToken(it.data!!.token!!) + PageNavigationManager.finishAllActivity() + navigatePageTo() + } + }) + } + }) + } } override fun initLayoutView(): Int = R.layout.activity_login override fun observeRequestState() { - + userViewModel.loadState.observe(this, { + when (it) { + LoadState.Loading -> LoadingDialogHub.show(this, "登录中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) } override fun setupTopBarLayout() { diff --git a/app/src/main/java/com/casic/xz/meterage/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/xz/meterage/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..96bf7ab --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/vm/AuthenticateViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.xz.meterage.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.xz.meterage.base.BaseApplication +import com.casic.xz.meterage.extensions.separateResponseCode +import com.casic.xz.meterage.extensions.toErrorMessage +import com.casic.xz.meterage.model.PublicKeyModel +import com.casic.xz.meterage.utils.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel + +class AuthenticateViewModel : BaseViewModel() { + + private val gson by lazy { Gson() } + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + val response = RetrofitServiceManager.getPublicKey() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.get()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/xz/meterage/vm/UserViewModel.kt b/app/src/main/java/com/casic/xz/meterage/vm/UserViewModel.kt new file mode 100644 index 0000000..9d2a152 --- /dev/null +++ b/app/src/main/java/com/casic/xz/meterage/vm/UserViewModel.kt @@ -0,0 +1,110 @@ +package com.casic.xz.meterage.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.xz.meterage.base.BaseApplication +import com.casic.xz.meterage.extensions.separateResponseCode +import com.casic.xz.meterage.extensions.toErrorMessage +import com.casic.xz.meterage.model.LoginResultModel +import com.casic.xz.meterage.utils.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson by lazy { Gson() } + + // val registerResult = MutableLiveData() + val loginResult = MutableLiveData() +// val userDetailResult = MutableLiveData() +// val outResultModel = MutableLiveData() + +// fun register( +// account: String, +// password: String, +// positionLng: Double, +// positionLat: Double, +// clientId: String, +// syncName: String, +// syncDeptid: String +// ) = launch({ +// val response = RetrofitServiceManager.register( +// account, password, positionLng, positionLat, clientId, syncName, syncDeptid +// ) +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// loadState.value = LoadState.Success +// registerResult.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) +// } else { +// loadState.value = LoadState.Fail +// response.toErrorMessage().show(BaseApplication.obtainInstance()) +// } +// }, { +// loadState.value = LoadState.Fail +// it.printStackTrace() +// }) + + fun enter(sid: String, account: String, secretKey: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + loginResult.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.get()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + +// fun obtainUserDetail(account: String) = launch({ +// val response = RetrofitServiceManager.obtainUserDetail(account) +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// userDetailResult.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) +// } +// }, { +// it.printStackTrace() +// }) +// +// fun updateUserInfo(userData: UserDetailModel.DataModel) = launch({ +// loadState.value = LoadState.Loading +// val response = RetrofitServiceManager.updateUserInfo(userData) +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// loadState.value = LoadState.Success +// } else { +// loadState.value = LoadState.Fail +// response.toErrorMessage().show(BaseApplication.obtainInstance()) +// } +// }, { +// loadState.value = LoadState.Fail +// it.printStackTrace() +// }) +// +// fun out() = launch({ +// val response = RetrofitServiceManager.loginOut() +// val responseCode = response.separateResponseCode() +// if (responseCode == 200) { +// outResultModel.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) +// } else { +// response.toErrorMessage().show(BaseApplication.obtainInstance()) +// } +// }, { +// it.printStackTrace() +// }) +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 41a0ed3..4b7a0a6 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -12,8 +12,8 @@ android:orientation="vertical"> + android:textSize="@dimen/sp_16" /> + android:textSize="@dimen/sp_16" /> #FF000000 #FFFFFFFF - #098BFE - #80098BFE + #3d7eff + #803d7eff #E6E6E6 #E4E4E4 \ No newline at end of file