diff --git a/app/build.gradle b/app/build.gradle
index af9b29a..c95e04f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -102,4 +102,6 @@
implementation 'com.github.bumptech.glide:glide:4.9.0'
//图片选择框架
implementation 'io.github.lucksiege:pictureselector:v3.11.1'
+ //图片压缩
+ implementation 'top.zibin:Luban:1.1.8'
}
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index af9b29a..c95e04f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -102,4 +102,6 @@
implementation 'com.github.bumptech.glide:glide:4.9.0'
//图片选择框架
implementation 'io.github.lucksiege:pictureselector:v3.11.1'
+ //图片压缩
+ implementation 'top.zibin:Luban:1.1.8'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1e93201..607ca75 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -57,6 +57,9 @@
+
diff --git a/app/build.gradle b/app/build.gradle
index af9b29a..c95e04f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -102,4 +102,6 @@
implementation 'com.github.bumptech.glide:glide:4.9.0'
//图片选择框架
implementation 'io.github.lucksiege:pictureselector:v3.11.1'
+ //图片压缩
+ implementation 'top.zibin:Luban:1.1.8'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1e93201..607ca75 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -57,6 +57,9 @@
+
diff --git a/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
new file mode 100644
index 0000000..41e3603
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
@@ -0,0 +1,95 @@
+package com.casic.br.app.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.casic.br.app.R
+import com.pengxh.kt.lite.adapter.ViewHolder
+
+/**
+ * 数量可编辑图片适配器
+ *
+ * @param context 使用适配的上下文
+ * @param viewWidth RecyclerView实际宽度,一般情况下就是屏幕宽度,但是如果有其他控件和它在同一行,需要计算实际宽度,不然无法正确显示RecyclerView item的布局
+ * @param imageCountLimit 最多显示的图片数目
+ * @param spanCount 每行显示的图片数目
+ * */
+class EditableImageAdapter(
+ private val context: Context,
+ private val viewWidth: Int,
+ private val imageCountLimit: Int,
+ private val spanCount: Int
+) : RecyclerView.Adapter() {
+
+ private val kTag = "EditableImageAdapter"
+ private var images: MutableList = ArrayList()
+
+ fun setupImage(images: MutableList) {
+ this.images = images
+ }
+
+ fun deleteImage(position: Int) {
+ if (images.isNotEmpty()) {
+ images.removeAt(position)
+ /**
+ * 发生变化的item数目
+ * */
+ notifyItemRangeRemoved(position, 1)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ LayoutInflater.from(context).inflate(R.layout.item_editable_rv_g, parent, false)
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val imageView = holder.getView(R.id.imageView)
+ val imageSize = viewWidth / spanCount
+ val params = LinearLayout.LayoutParams(imageSize, imageSize)
+ imageView.layoutParams = params
+
+ if (position == itemCount - 1 && images.size < imageCountLimit) {
+ imageView.setImageResource(R.drawable.ic_add_pic)
+ imageView.setOnClickListener { //添加图片
+ itemClickListener?.onAddImageClick()
+ }
+ } else {
+ Glide.with(context).load(images[position]).into(imageView)
+ imageView.setOnClickListener { // 点击操作,查看大图
+ itemClickListener?.onItemClick(holder.bindingAdapterPosition)
+ }
+ // 长按监听
+ imageView.setOnLongClickListener { v -> //长按删除
+ itemClickListener?.onItemLongClick(v, holder.bindingAdapterPosition)
+ true
+ }
+ }
+ }
+
+ override fun getItemCount(): Int = if (images.size >= imageCountLimit) {
+ imageCountLimit
+ } else {
+ images.size + 1
+ }
+
+ private var itemClickListener: OnItemClickListener? = null
+
+ fun setOnItemClickListener(itemClickListener: OnItemClickListener?) {
+ this.itemClickListener = itemClickListener
+ }
+
+ interface OnItemClickListener {
+ fun onAddImageClick()
+
+ fun onItemClick(position: Int)
+
+ fun onItemLongClick(view: View?, position: Int)
+ }
+}
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index af9b29a..c95e04f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -102,4 +102,6 @@
implementation 'com.github.bumptech.glide:glide:4.9.0'
//图片选择框架
implementation 'io.github.lucksiege:pictureselector:v3.11.1'
+ //图片压缩
+ implementation 'top.zibin:Luban:1.1.8'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1e93201..607ca75 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -57,6 +57,9 @@
+
diff --git a/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
new file mode 100644
index 0000000..41e3603
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
@@ -0,0 +1,95 @@
+package com.casic.br.app.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.casic.br.app.R
+import com.pengxh.kt.lite.adapter.ViewHolder
+
+/**
+ * 数量可编辑图片适配器
+ *
+ * @param context 使用适配的上下文
+ * @param viewWidth RecyclerView实际宽度,一般情况下就是屏幕宽度,但是如果有其他控件和它在同一行,需要计算实际宽度,不然无法正确显示RecyclerView item的布局
+ * @param imageCountLimit 最多显示的图片数目
+ * @param spanCount 每行显示的图片数目
+ * */
+class EditableImageAdapter(
+ private val context: Context,
+ private val viewWidth: Int,
+ private val imageCountLimit: Int,
+ private val spanCount: Int
+) : RecyclerView.Adapter() {
+
+ private val kTag = "EditableImageAdapter"
+ private var images: MutableList = ArrayList()
+
+ fun setupImage(images: MutableList) {
+ this.images = images
+ }
+
+ fun deleteImage(position: Int) {
+ if (images.isNotEmpty()) {
+ images.removeAt(position)
+ /**
+ * 发生变化的item数目
+ * */
+ notifyItemRangeRemoved(position, 1)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ LayoutInflater.from(context).inflate(R.layout.item_editable_rv_g, parent, false)
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val imageView = holder.getView(R.id.imageView)
+ val imageSize = viewWidth / spanCount
+ val params = LinearLayout.LayoutParams(imageSize, imageSize)
+ imageView.layoutParams = params
+
+ if (position == itemCount - 1 && images.size < imageCountLimit) {
+ imageView.setImageResource(R.drawable.ic_add_pic)
+ imageView.setOnClickListener { //添加图片
+ itemClickListener?.onAddImageClick()
+ }
+ } else {
+ Glide.with(context).load(images[position]).into(imageView)
+ imageView.setOnClickListener { // 点击操作,查看大图
+ itemClickListener?.onItemClick(holder.bindingAdapterPosition)
+ }
+ // 长按监听
+ imageView.setOnLongClickListener { v -> //长按删除
+ itemClickListener?.onItemLongClick(v, holder.bindingAdapterPosition)
+ true
+ }
+ }
+ }
+
+ override fun getItemCount(): Int = if (images.size >= imageCountLimit) {
+ imageCountLimit
+ } else {
+ images.size + 1
+ }
+
+ private var itemClickListener: OnItemClickListener? = null
+
+ fun setOnItemClickListener(itemClickListener: OnItemClickListener?) {
+ this.itemClickListener = itemClickListener
+ }
+
+ interface OnItemClickListener {
+ fun onAddImageClick()
+
+ fun onItemClick(position: Int)
+
+ fun onItemLongClick(view: View?, position: Int)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
index 41f8f85..8c431ef 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
@@ -1,11 +1,13 @@
package com.casic.br.app.retrofit
+import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
+import retrofit2.http.Part
import retrofit2.http.PartMap
import retrofit2.http.Query
import retrofit2.http.QueryMap
@@ -116,4 +118,14 @@
*/
@GET("/knowledge-class/list")
suspend fun getLibraryList(@Header("token") token: String): String
+
+ /**
+ * 上传文件
+ */
+ @Multipart
+ @POST("/file/upload")
+ suspend fun uploadImage(
+ @Header("token") token: String,
+ @Part file: MultipartBody.Part
+ ): String
}
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index af9b29a..c95e04f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -102,4 +102,6 @@
implementation 'com.github.bumptech.glide:glide:4.9.0'
//图片选择框架
implementation 'io.github.lucksiege:pictureselector:v3.11.1'
+ //图片压缩
+ implementation 'top.zibin:Luban:1.1.8'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1e93201..607ca75 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -57,6 +57,9 @@
+
diff --git a/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
new file mode 100644
index 0000000..41e3603
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
@@ -0,0 +1,95 @@
+package com.casic.br.app.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.casic.br.app.R
+import com.pengxh.kt.lite.adapter.ViewHolder
+
+/**
+ * 数量可编辑图片适配器
+ *
+ * @param context 使用适配的上下文
+ * @param viewWidth RecyclerView实际宽度,一般情况下就是屏幕宽度,但是如果有其他控件和它在同一行,需要计算实际宽度,不然无法正确显示RecyclerView item的布局
+ * @param imageCountLimit 最多显示的图片数目
+ * @param spanCount 每行显示的图片数目
+ * */
+class EditableImageAdapter(
+ private val context: Context,
+ private val viewWidth: Int,
+ private val imageCountLimit: Int,
+ private val spanCount: Int
+) : RecyclerView.Adapter() {
+
+ private val kTag = "EditableImageAdapter"
+ private var images: MutableList = ArrayList()
+
+ fun setupImage(images: MutableList) {
+ this.images = images
+ }
+
+ fun deleteImage(position: Int) {
+ if (images.isNotEmpty()) {
+ images.removeAt(position)
+ /**
+ * 发生变化的item数目
+ * */
+ notifyItemRangeRemoved(position, 1)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ LayoutInflater.from(context).inflate(R.layout.item_editable_rv_g, parent, false)
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val imageView = holder.getView(R.id.imageView)
+ val imageSize = viewWidth / spanCount
+ val params = LinearLayout.LayoutParams(imageSize, imageSize)
+ imageView.layoutParams = params
+
+ if (position == itemCount - 1 && images.size < imageCountLimit) {
+ imageView.setImageResource(R.drawable.ic_add_pic)
+ imageView.setOnClickListener { //添加图片
+ itemClickListener?.onAddImageClick()
+ }
+ } else {
+ Glide.with(context).load(images[position]).into(imageView)
+ imageView.setOnClickListener { // 点击操作,查看大图
+ itemClickListener?.onItemClick(holder.bindingAdapterPosition)
+ }
+ // 长按监听
+ imageView.setOnLongClickListener { v -> //长按删除
+ itemClickListener?.onItemLongClick(v, holder.bindingAdapterPosition)
+ true
+ }
+ }
+ }
+
+ override fun getItemCount(): Int = if (images.size >= imageCountLimit) {
+ imageCountLimit
+ } else {
+ images.size + 1
+ }
+
+ private var itemClickListener: OnItemClickListener? = null
+
+ fun setOnItemClickListener(itemClickListener: OnItemClickListener?) {
+ this.itemClickListener = itemClickListener
+ }
+
+ interface OnItemClickListener {
+ fun onAddImageClick()
+
+ fun onItemClick(position: Int)
+
+ fun onItemLongClick(view: View?, position: Int)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
index 41f8f85..8c431ef 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
@@ -1,11 +1,13 @@
package com.casic.br.app.retrofit
+import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
+import retrofit2.http.Part
import retrofit2.http.PartMap
import retrofit2.http.Query
import retrofit2.http.QueryMap
@@ -116,4 +118,14 @@
*/
@GET("/knowledge-class/list")
suspend fun getLibraryList(@Header("token") token: String): String
+
+ /**
+ * 上传文件
+ */
+ @Multipart
+ @POST("/file/upload")
+ suspend fun uploadImage(
+ @Header("token") token: String,
+ @Part file: MultipartBody.Part
+ ): String
}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
index 94d44e9..8712d19 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
@@ -6,8 +6,12 @@
import com.pengxh.kt.lite.utils.RetrofitFactory
import com.pengxh.kt.lite.utils.SaveKeyValues
import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
import okhttp3.RequestBody
+import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
+import java.io.File
object RetrofitServiceManager {
@@ -142,4 +146,13 @@
suspend fun getLibraryList(): String {
return api.getLibraryList(AuthenticationHelper.token)
}
+
+ /**
+ * 上传图片
+ */
+ suspend fun uploadImage(image: File): String {
+ val requestBody = image.asRequestBody("image/png".toMediaTypeOrNull())
+ val imagePart = MultipartBody.Part.createFormData("file", image.name, requestBody)
+ return api.uploadImage(AuthenticationHelper.token, imagePart)
+ }
}
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index af9b29a..c95e04f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -102,4 +102,6 @@
implementation 'com.github.bumptech.glide:glide:4.9.0'
//图片选择框架
implementation 'io.github.lucksiege:pictureselector:v3.11.1'
+ //图片压缩
+ implementation 'top.zibin:Luban:1.1.8'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1e93201..607ca75 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -57,6 +57,9 @@
+
diff --git a/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
new file mode 100644
index 0000000..41e3603
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
@@ -0,0 +1,95 @@
+package com.casic.br.app.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.casic.br.app.R
+import com.pengxh.kt.lite.adapter.ViewHolder
+
+/**
+ * 数量可编辑图片适配器
+ *
+ * @param context 使用适配的上下文
+ * @param viewWidth RecyclerView实际宽度,一般情况下就是屏幕宽度,但是如果有其他控件和它在同一行,需要计算实际宽度,不然无法正确显示RecyclerView item的布局
+ * @param imageCountLimit 最多显示的图片数目
+ * @param spanCount 每行显示的图片数目
+ * */
+class EditableImageAdapter(
+ private val context: Context,
+ private val viewWidth: Int,
+ private val imageCountLimit: Int,
+ private val spanCount: Int
+) : RecyclerView.Adapter() {
+
+ private val kTag = "EditableImageAdapter"
+ private var images: MutableList = ArrayList()
+
+ fun setupImage(images: MutableList) {
+ this.images = images
+ }
+
+ fun deleteImage(position: Int) {
+ if (images.isNotEmpty()) {
+ images.removeAt(position)
+ /**
+ * 发生变化的item数目
+ * */
+ notifyItemRangeRemoved(position, 1)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ LayoutInflater.from(context).inflate(R.layout.item_editable_rv_g, parent, false)
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val imageView = holder.getView(R.id.imageView)
+ val imageSize = viewWidth / spanCount
+ val params = LinearLayout.LayoutParams(imageSize, imageSize)
+ imageView.layoutParams = params
+
+ if (position == itemCount - 1 && images.size < imageCountLimit) {
+ imageView.setImageResource(R.drawable.ic_add_pic)
+ imageView.setOnClickListener { //添加图片
+ itemClickListener?.onAddImageClick()
+ }
+ } else {
+ Glide.with(context).load(images[position]).into(imageView)
+ imageView.setOnClickListener { // 点击操作,查看大图
+ itemClickListener?.onItemClick(holder.bindingAdapterPosition)
+ }
+ // 长按监听
+ imageView.setOnLongClickListener { v -> //长按删除
+ itemClickListener?.onItemLongClick(v, holder.bindingAdapterPosition)
+ true
+ }
+ }
+ }
+
+ override fun getItemCount(): Int = if (images.size >= imageCountLimit) {
+ imageCountLimit
+ } else {
+ images.size + 1
+ }
+
+ private var itemClickListener: OnItemClickListener? = null
+
+ fun setOnItemClickListener(itemClickListener: OnItemClickListener?) {
+ this.itemClickListener = itemClickListener
+ }
+
+ interface OnItemClickListener {
+ fun onAddImageClick()
+
+ fun onItemClick(position: Int)
+
+ fun onItemLongClick(view: View?, position: Int)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
index 41f8f85..8c431ef 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
@@ -1,11 +1,13 @@
package com.casic.br.app.retrofit
+import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
+import retrofit2.http.Part
import retrofit2.http.PartMap
import retrofit2.http.Query
import retrofit2.http.QueryMap
@@ -116,4 +118,14 @@
*/
@GET("/knowledge-class/list")
suspend fun getLibraryList(@Header("token") token: String): String
+
+ /**
+ * 上传文件
+ */
+ @Multipart
+ @POST("/file/upload")
+ suspend fun uploadImage(
+ @Header("token") token: String,
+ @Part file: MultipartBody.Part
+ ): String
}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
index 94d44e9..8712d19 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
@@ -6,8 +6,12 @@
import com.pengxh.kt.lite.utils.RetrofitFactory
import com.pengxh.kt.lite.utils.SaveKeyValues
import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
import okhttp3.RequestBody
+import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
+import java.io.File
object RetrofitServiceManager {
@@ -142,4 +146,13 @@
suspend fun getLibraryList(): String {
return api.getLibraryList(AuthenticationHelper.token)
}
+
+ /**
+ * 上传图片
+ */
+ suspend fun uploadImage(image: File): String {
+ val requestBody = image.asRequestBody("image/png".toMediaTypeOrNull())
+ val imagePart = MultipartBody.Part.createFormData("file", image.name, requestBody)
+ return api.uploadImage(AuthenticationHelper.token, imagePart)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt
new file mode 100644
index 0000000..8187b83
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt
@@ -0,0 +1,71 @@
+package com.casic.br.app.utils
+
+import android.content.Context
+import android.widget.ImageView
+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.casic.br.app.R
+import com.luck.picture.lib.engine.ImageEngine
+import com.luck.picture.lib.utils.ActivityCompatHelper
+
+class GlideLoadEngine private constructor() : ImageEngine {
+ companion object {
+ val get: 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 loadImage(
+ context: Context,
+ imageView: ImageView,
+ url: String,
+ maxWidth: Int,
+ maxHeight: Int
+ ) {
+ if (!ActivityCompatHelper.assertValidRequest(context)) {
+ return
+ }
+ Glide.with(context)
+ .load(url)
+ .override(maxWidth, maxHeight)
+ .into(imageView)
+ }
+
+ 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 af9b29a..c95e04f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -102,4 +102,6 @@
implementation 'com.github.bumptech.glide:glide:4.9.0'
//图片选择框架
implementation 'io.github.lucksiege:pictureselector:v3.11.1'
+ //图片压缩
+ implementation 'top.zibin:Luban:1.1.8'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1e93201..607ca75 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -57,6 +57,9 @@
+
diff --git a/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
new file mode 100644
index 0000000..41e3603
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
@@ -0,0 +1,95 @@
+package com.casic.br.app.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.casic.br.app.R
+import com.pengxh.kt.lite.adapter.ViewHolder
+
+/**
+ * 数量可编辑图片适配器
+ *
+ * @param context 使用适配的上下文
+ * @param viewWidth RecyclerView实际宽度,一般情况下就是屏幕宽度,但是如果有其他控件和它在同一行,需要计算实际宽度,不然无法正确显示RecyclerView item的布局
+ * @param imageCountLimit 最多显示的图片数目
+ * @param spanCount 每行显示的图片数目
+ * */
+class EditableImageAdapter(
+ private val context: Context,
+ private val viewWidth: Int,
+ private val imageCountLimit: Int,
+ private val spanCount: Int
+) : RecyclerView.Adapter() {
+
+ private val kTag = "EditableImageAdapter"
+ private var images: MutableList = ArrayList()
+
+ fun setupImage(images: MutableList) {
+ this.images = images
+ }
+
+ fun deleteImage(position: Int) {
+ if (images.isNotEmpty()) {
+ images.removeAt(position)
+ /**
+ * 发生变化的item数目
+ * */
+ notifyItemRangeRemoved(position, 1)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ LayoutInflater.from(context).inflate(R.layout.item_editable_rv_g, parent, false)
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val imageView = holder.getView(R.id.imageView)
+ val imageSize = viewWidth / spanCount
+ val params = LinearLayout.LayoutParams(imageSize, imageSize)
+ imageView.layoutParams = params
+
+ if (position == itemCount - 1 && images.size < imageCountLimit) {
+ imageView.setImageResource(R.drawable.ic_add_pic)
+ imageView.setOnClickListener { //添加图片
+ itemClickListener?.onAddImageClick()
+ }
+ } else {
+ Glide.with(context).load(images[position]).into(imageView)
+ imageView.setOnClickListener { // 点击操作,查看大图
+ itemClickListener?.onItemClick(holder.bindingAdapterPosition)
+ }
+ // 长按监听
+ imageView.setOnLongClickListener { v -> //长按删除
+ itemClickListener?.onItemLongClick(v, holder.bindingAdapterPosition)
+ true
+ }
+ }
+ }
+
+ override fun getItemCount(): Int = if (images.size >= imageCountLimit) {
+ imageCountLimit
+ } else {
+ images.size + 1
+ }
+
+ private var itemClickListener: OnItemClickListener? = null
+
+ fun setOnItemClickListener(itemClickListener: OnItemClickListener?) {
+ this.itemClickListener = itemClickListener
+ }
+
+ interface OnItemClickListener {
+ fun onAddImageClick()
+
+ fun onItemClick(position: Int)
+
+ fun onItemLongClick(view: View?, position: Int)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
index 41f8f85..8c431ef 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
@@ -1,11 +1,13 @@
package com.casic.br.app.retrofit
+import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
+import retrofit2.http.Part
import retrofit2.http.PartMap
import retrofit2.http.Query
import retrofit2.http.QueryMap
@@ -116,4 +118,14 @@
*/
@GET("/knowledge-class/list")
suspend fun getLibraryList(@Header("token") token: String): String
+
+ /**
+ * 上传文件
+ */
+ @Multipart
+ @POST("/file/upload")
+ suspend fun uploadImage(
+ @Header("token") token: String,
+ @Part file: MultipartBody.Part
+ ): String
}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
index 94d44e9..8712d19 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
@@ -6,8 +6,12 @@
import com.pengxh.kt.lite.utils.RetrofitFactory
import com.pengxh.kt.lite.utils.SaveKeyValues
import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
import okhttp3.RequestBody
+import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
+import java.io.File
object RetrofitServiceManager {
@@ -142,4 +146,13 @@
suspend fun getLibraryList(): String {
return api.getLibraryList(AuthenticationHelper.token)
}
+
+ /**
+ * 上传图片
+ */
+ suspend fun uploadImage(image: File): String {
+ val requestBody = image.asRequestBody("image/png".toMediaTypeOrNull())
+ val imagePart = MultipartBody.Part.createFormData("file", image.name, requestBody)
+ return api.uploadImage(AuthenticationHelper.token, imagePart)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt
new file mode 100644
index 0000000..8187b83
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt
@@ -0,0 +1,71 @@
+package com.casic.br.app.utils
+
+import android.content.Context
+import android.widget.ImageView
+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.casic.br.app.R
+import com.luck.picture.lib.engine.ImageEngine
+import com.luck.picture.lib.utils.ActivityCompatHelper
+
+class GlideLoadEngine private constructor() : ImageEngine {
+ companion object {
+ val get: 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 loadImage(
+ context: Context,
+ imageView: ImageView,
+ url: String,
+ maxWidth: Int,
+ maxHeight: Int
+ ) {
+ if (!ActivityCompatHelper.assertValidRequest(context)) {
+ return
+ }
+ Glide.with(context)
+ .load(url)
+ .override(maxWidth, maxHeight)
+ .into(imageView)
+ }
+
+ 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/br/app/view/AddHiddenTroubleActivity.kt b/app/src/main/java/com/casic/br/app/view/AddHiddenTroubleActivity.kt
new file mode 100644
index 0000000..08be23c
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/view/AddHiddenTroubleActivity.kt
@@ -0,0 +1,163 @@
+package com.casic.br.app.view
+
+import android.app.ActionBar.LayoutParams
+import android.os.Bundle
+import android.os.CountDownTimer
+import android.os.Handler
+import android.os.Message
+import android.util.Log
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import com.casic.br.app.adapter.EditableImageAdapter
+import com.casic.br.app.databinding.ActivityAddHiddenTroubleBinding
+import com.casic.br.app.extensions.combineFilePath
+import com.casic.br.app.utils.GlideLoadEngine
+import com.casic.br.app.vm.ImageFileViewModel
+import com.luck.picture.lib.basic.PictureSelector
+import com.luck.picture.lib.config.SelectMimeType
+import com.luck.picture.lib.entity.LocalMedia
+import com.luck.picture.lib.interfaces.OnResultCallbackListener
+import com.pengxh.kt.lite.base.KotlinBaseActivity
+import com.pengxh.kt.lite.divider.RecyclerViewItemOffsets
+import com.pengxh.kt.lite.extensions.createCompressImageDir
+import com.pengxh.kt.lite.extensions.dp2px
+import com.pengxh.kt.lite.extensions.getScreenWidth
+import com.pengxh.kt.lite.extensions.show
+import com.pengxh.kt.lite.extensions.toJson
+import com.pengxh.kt.lite.utils.LoadState
+import com.pengxh.kt.lite.utils.WeakReferenceHandler
+import top.zibin.luban.Luban
+import top.zibin.luban.OnCompressListener
+import java.io.File
+
+class AddHiddenTroubleActivity : KotlinBaseActivity(),
+ Handler.Callback {
+
+ private val kTag = "AddHiddenTroubleActivity"
+ private val selectedImages = ArrayList()
+ private val imagePaths = ArrayList()
+ private val recyclerViewImages = ArrayList()
+ private val marginOffset by lazy { 1.dp2px(this) }
+ private val weakReferenceHandler by lazy { WeakReferenceHandler(this) }
+ private lateinit var editableImageAdapter: EditableImageAdapter
+ private lateinit var imageFileViewModel: ImageFileViewModel
+
+ override fun initViewBinding(): ActivityAddHiddenTroubleBinding {
+ return ActivityAddHiddenTroubleBinding.inflate(layoutInflater)
+ }
+
+ override fun setupTopBarLayout() {
+
+ }
+
+ override fun observeRequestState() {
+ imageFileViewModel.loadState.observe(this) {
+ if (it == LoadState.Loading) {
+ "图片上传中,请稍后...".show(this)
+ }
+ }
+ }
+
+ override fun initOnCreate(savedInstanceState: Bundle?) {
+ val params = window.attributes
+ params.height = LayoutParams.WRAP_CONTENT
+ window.attributes = params
+
+ imageFileViewModel = ViewModelProvider(this)[ImageFileViewModel::class.java]
+ imageFileViewModel.resultModel.observe(this) {
+ if (it.code == 200) {
+ val url = it.data.toString()
+ imagePaths.add(url)
+ recyclerViewImages.add(url.combineFilePath())
+ editableImageAdapter.notifyDataSetChanged()
+ }
+ }
+
+ //左外边距,左内边距,TextView宽度,内间距,右内边距,右外边距
+ val viewWidth = getScreenWidth() - (15 + 15 + 65 + 10 + 15 + 15).dp2px(this)
+ editableImageAdapter = EditableImageAdapter(this, viewWidth, 3, 3)
+ editableImageAdapter.setupImage(recyclerViewImages)
+ binding.recyclerView.addItemDecoration(
+ RecyclerViewItemOffsets(marginOffset, marginOffset, marginOffset, marginOffset)
+ )
+ binding.recyclerView.adapter = editableImageAdapter
+ }
+
+ override fun initEvent() {
+ editableImageAdapter.setOnItemClickListener(object :
+ EditableImageAdapter.OnItemClickListener {
+ override fun onAddImageClick() {
+ selectPicture()
+ }
+
+ override fun onItemClick(position: Int) {
+
+ }
+
+ override fun onItemLongClick(view: View?, position: Int) {
+ editableImageAdapter.deleteImage(position)
+ selectedImages.removeAt(position)
+ }
+ })
+
+ binding.dialogConfirmButton.setOnClickListener {
+ Log.d(kTag, imagePaths.toJson())
+ }
+
+ binding.dialogCancelButton.setOnClickListener { finish() }
+ }
+
+ private fun selectPicture() {
+ PictureSelector.create(this).openGallery(SelectMimeType.ofImage()).isGif(false)
+ .isMaxSelectEnabledMask(true).setFilterMinFileSize(100).setMaxSelectNum(3)
+ .isDisplayCamera(true).setImageEngine(GlideLoadEngine.get)
+ .setSelectedData(selectedImages)
+ .forResult(object : OnResultCallbackListener {
+ override fun onResult(result: ArrayList) {
+ // 如果设置了setSelectedData,它会将之前的结果作为最新的返回值,要注意重复问题
+ // 线程控制图片压缩上传过程,防止速度过快导致压缩失败
+ val sum = (result.size * 500).toLong()
+ object : CountDownTimer(sum, 500) {
+ override fun onTick(millisUntilFinished: Long) {
+ val i = millisUntilFinished / 500
+ val message = weakReferenceHandler.obtainMessage()
+ message.obj = result[i.toInt()]
+ message.what = 2024042201
+ weakReferenceHandler.handleMessage(message)
+ }
+
+ override fun onFinish() {
+
+ }
+ }.start()
+ }
+
+ override fun onCancel() {}
+ })
+ }
+
+ override fun handleMessage(msg: Message): Boolean {
+ if (msg.what == 2024042201) {
+ val result = msg.obj as LocalMedia
+ selectedImages.add(result)
+ //压缩图片
+ Luban.with(this).load(result.realPath).ignoreBy(100)
+ .setTargetDir(createCompressImageDir().toString())
+ .setCompressListener(object : OnCompressListener {
+ override fun onStart() {
+
+ }
+
+ override fun onSuccess(file: File) {
+ //上传图片
+ imageFileViewModel.uploadImage(file)
+ }
+
+ override fun onError(e: Throwable) {
+ e.printStackTrace()
+ }
+ }).launch()
+ }
+ return true
+ }
+}
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index af9b29a..c95e04f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -102,4 +102,6 @@
implementation 'com.github.bumptech.glide:glide:4.9.0'
//图片选择框架
implementation 'io.github.lucksiege:pictureselector:v3.11.1'
+ //图片压缩
+ implementation 'top.zibin:Luban:1.1.8'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1e93201..607ca75 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -57,6 +57,9 @@
+
diff --git a/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
new file mode 100644
index 0000000..41e3603
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
@@ -0,0 +1,95 @@
+package com.casic.br.app.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.casic.br.app.R
+import com.pengxh.kt.lite.adapter.ViewHolder
+
+/**
+ * 数量可编辑图片适配器
+ *
+ * @param context 使用适配的上下文
+ * @param viewWidth RecyclerView实际宽度,一般情况下就是屏幕宽度,但是如果有其他控件和它在同一行,需要计算实际宽度,不然无法正确显示RecyclerView item的布局
+ * @param imageCountLimit 最多显示的图片数目
+ * @param spanCount 每行显示的图片数目
+ * */
+class EditableImageAdapter(
+ private val context: Context,
+ private val viewWidth: Int,
+ private val imageCountLimit: Int,
+ private val spanCount: Int
+) : RecyclerView.Adapter() {
+
+ private val kTag = "EditableImageAdapter"
+ private var images: MutableList = ArrayList()
+
+ fun setupImage(images: MutableList) {
+ this.images = images
+ }
+
+ fun deleteImage(position: Int) {
+ if (images.isNotEmpty()) {
+ images.removeAt(position)
+ /**
+ * 发生变化的item数目
+ * */
+ notifyItemRangeRemoved(position, 1)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ LayoutInflater.from(context).inflate(R.layout.item_editable_rv_g, parent, false)
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val imageView = holder.getView(R.id.imageView)
+ val imageSize = viewWidth / spanCount
+ val params = LinearLayout.LayoutParams(imageSize, imageSize)
+ imageView.layoutParams = params
+
+ if (position == itemCount - 1 && images.size < imageCountLimit) {
+ imageView.setImageResource(R.drawable.ic_add_pic)
+ imageView.setOnClickListener { //添加图片
+ itemClickListener?.onAddImageClick()
+ }
+ } else {
+ Glide.with(context).load(images[position]).into(imageView)
+ imageView.setOnClickListener { // 点击操作,查看大图
+ itemClickListener?.onItemClick(holder.bindingAdapterPosition)
+ }
+ // 长按监听
+ imageView.setOnLongClickListener { v -> //长按删除
+ itemClickListener?.onItemLongClick(v, holder.bindingAdapterPosition)
+ true
+ }
+ }
+ }
+
+ override fun getItemCount(): Int = if (images.size >= imageCountLimit) {
+ imageCountLimit
+ } else {
+ images.size + 1
+ }
+
+ private var itemClickListener: OnItemClickListener? = null
+
+ fun setOnItemClickListener(itemClickListener: OnItemClickListener?) {
+ this.itemClickListener = itemClickListener
+ }
+
+ interface OnItemClickListener {
+ fun onAddImageClick()
+
+ fun onItemClick(position: Int)
+
+ fun onItemLongClick(view: View?, position: Int)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
index 41f8f85..8c431ef 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
@@ -1,11 +1,13 @@
package com.casic.br.app.retrofit
+import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
+import retrofit2.http.Part
import retrofit2.http.PartMap
import retrofit2.http.Query
import retrofit2.http.QueryMap
@@ -116,4 +118,14 @@
*/
@GET("/knowledge-class/list")
suspend fun getLibraryList(@Header("token") token: String): String
+
+ /**
+ * 上传文件
+ */
+ @Multipart
+ @POST("/file/upload")
+ suspend fun uploadImage(
+ @Header("token") token: String,
+ @Part file: MultipartBody.Part
+ ): String
}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
index 94d44e9..8712d19 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
@@ -6,8 +6,12 @@
import com.pengxh.kt.lite.utils.RetrofitFactory
import com.pengxh.kt.lite.utils.SaveKeyValues
import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
import okhttp3.RequestBody
+import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
+import java.io.File
object RetrofitServiceManager {
@@ -142,4 +146,13 @@
suspend fun getLibraryList(): String {
return api.getLibraryList(AuthenticationHelper.token)
}
+
+ /**
+ * 上传图片
+ */
+ suspend fun uploadImage(image: File): String {
+ val requestBody = image.asRequestBody("image/png".toMediaTypeOrNull())
+ val imagePart = MultipartBody.Part.createFormData("file", image.name, requestBody)
+ return api.uploadImage(AuthenticationHelper.token, imagePart)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt
new file mode 100644
index 0000000..8187b83
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt
@@ -0,0 +1,71 @@
+package com.casic.br.app.utils
+
+import android.content.Context
+import android.widget.ImageView
+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.casic.br.app.R
+import com.luck.picture.lib.engine.ImageEngine
+import com.luck.picture.lib.utils.ActivityCompatHelper
+
+class GlideLoadEngine private constructor() : ImageEngine {
+ companion object {
+ val get: 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 loadImage(
+ context: Context,
+ imageView: ImageView,
+ url: String,
+ maxWidth: Int,
+ maxHeight: Int
+ ) {
+ if (!ActivityCompatHelper.assertValidRequest(context)) {
+ return
+ }
+ Glide.with(context)
+ .load(url)
+ .override(maxWidth, maxHeight)
+ .into(imageView)
+ }
+
+ 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/br/app/view/AddHiddenTroubleActivity.kt b/app/src/main/java/com/casic/br/app/view/AddHiddenTroubleActivity.kt
new file mode 100644
index 0000000..08be23c
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/view/AddHiddenTroubleActivity.kt
@@ -0,0 +1,163 @@
+package com.casic.br.app.view
+
+import android.app.ActionBar.LayoutParams
+import android.os.Bundle
+import android.os.CountDownTimer
+import android.os.Handler
+import android.os.Message
+import android.util.Log
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import com.casic.br.app.adapter.EditableImageAdapter
+import com.casic.br.app.databinding.ActivityAddHiddenTroubleBinding
+import com.casic.br.app.extensions.combineFilePath
+import com.casic.br.app.utils.GlideLoadEngine
+import com.casic.br.app.vm.ImageFileViewModel
+import com.luck.picture.lib.basic.PictureSelector
+import com.luck.picture.lib.config.SelectMimeType
+import com.luck.picture.lib.entity.LocalMedia
+import com.luck.picture.lib.interfaces.OnResultCallbackListener
+import com.pengxh.kt.lite.base.KotlinBaseActivity
+import com.pengxh.kt.lite.divider.RecyclerViewItemOffsets
+import com.pengxh.kt.lite.extensions.createCompressImageDir
+import com.pengxh.kt.lite.extensions.dp2px
+import com.pengxh.kt.lite.extensions.getScreenWidth
+import com.pengxh.kt.lite.extensions.show
+import com.pengxh.kt.lite.extensions.toJson
+import com.pengxh.kt.lite.utils.LoadState
+import com.pengxh.kt.lite.utils.WeakReferenceHandler
+import top.zibin.luban.Luban
+import top.zibin.luban.OnCompressListener
+import java.io.File
+
+class AddHiddenTroubleActivity : KotlinBaseActivity(),
+ Handler.Callback {
+
+ private val kTag = "AddHiddenTroubleActivity"
+ private val selectedImages = ArrayList()
+ private val imagePaths = ArrayList()
+ private val recyclerViewImages = ArrayList()
+ private val marginOffset by lazy { 1.dp2px(this) }
+ private val weakReferenceHandler by lazy { WeakReferenceHandler(this) }
+ private lateinit var editableImageAdapter: EditableImageAdapter
+ private lateinit var imageFileViewModel: ImageFileViewModel
+
+ override fun initViewBinding(): ActivityAddHiddenTroubleBinding {
+ return ActivityAddHiddenTroubleBinding.inflate(layoutInflater)
+ }
+
+ override fun setupTopBarLayout() {
+
+ }
+
+ override fun observeRequestState() {
+ imageFileViewModel.loadState.observe(this) {
+ if (it == LoadState.Loading) {
+ "图片上传中,请稍后...".show(this)
+ }
+ }
+ }
+
+ override fun initOnCreate(savedInstanceState: Bundle?) {
+ val params = window.attributes
+ params.height = LayoutParams.WRAP_CONTENT
+ window.attributes = params
+
+ imageFileViewModel = ViewModelProvider(this)[ImageFileViewModel::class.java]
+ imageFileViewModel.resultModel.observe(this) {
+ if (it.code == 200) {
+ val url = it.data.toString()
+ imagePaths.add(url)
+ recyclerViewImages.add(url.combineFilePath())
+ editableImageAdapter.notifyDataSetChanged()
+ }
+ }
+
+ //左外边距,左内边距,TextView宽度,内间距,右内边距,右外边距
+ val viewWidth = getScreenWidth() - (15 + 15 + 65 + 10 + 15 + 15).dp2px(this)
+ editableImageAdapter = EditableImageAdapter(this, viewWidth, 3, 3)
+ editableImageAdapter.setupImage(recyclerViewImages)
+ binding.recyclerView.addItemDecoration(
+ RecyclerViewItemOffsets(marginOffset, marginOffset, marginOffset, marginOffset)
+ )
+ binding.recyclerView.adapter = editableImageAdapter
+ }
+
+ override fun initEvent() {
+ editableImageAdapter.setOnItemClickListener(object :
+ EditableImageAdapter.OnItemClickListener {
+ override fun onAddImageClick() {
+ selectPicture()
+ }
+
+ override fun onItemClick(position: Int) {
+
+ }
+
+ override fun onItemLongClick(view: View?, position: Int) {
+ editableImageAdapter.deleteImage(position)
+ selectedImages.removeAt(position)
+ }
+ })
+
+ binding.dialogConfirmButton.setOnClickListener {
+ Log.d(kTag, imagePaths.toJson())
+ }
+
+ binding.dialogCancelButton.setOnClickListener { finish() }
+ }
+
+ private fun selectPicture() {
+ PictureSelector.create(this).openGallery(SelectMimeType.ofImage()).isGif(false)
+ .isMaxSelectEnabledMask(true).setFilterMinFileSize(100).setMaxSelectNum(3)
+ .isDisplayCamera(true).setImageEngine(GlideLoadEngine.get)
+ .setSelectedData(selectedImages)
+ .forResult(object : OnResultCallbackListener {
+ override fun onResult(result: ArrayList) {
+ // 如果设置了setSelectedData,它会将之前的结果作为最新的返回值,要注意重复问题
+ // 线程控制图片压缩上传过程,防止速度过快导致压缩失败
+ val sum = (result.size * 500).toLong()
+ object : CountDownTimer(sum, 500) {
+ override fun onTick(millisUntilFinished: Long) {
+ val i = millisUntilFinished / 500
+ val message = weakReferenceHandler.obtainMessage()
+ message.obj = result[i.toInt()]
+ message.what = 2024042201
+ weakReferenceHandler.handleMessage(message)
+ }
+
+ override fun onFinish() {
+
+ }
+ }.start()
+ }
+
+ override fun onCancel() {}
+ })
+ }
+
+ override fun handleMessage(msg: Message): Boolean {
+ if (msg.what == 2024042201) {
+ val result = msg.obj as LocalMedia
+ selectedImages.add(result)
+ //压缩图片
+ Luban.with(this).load(result.realPath).ignoreBy(100)
+ .setTargetDir(createCompressImageDir().toString())
+ .setCompressListener(object : OnCompressListener {
+ override fun onStart() {
+
+ }
+
+ override fun onSuccess(file: File) {
+ //上传图片
+ imageFileViewModel.uploadImage(file)
+ }
+
+ override fun onError(e: Throwable) {
+ e.printStackTrace()
+ }
+ }).launch()
+ }
+ return true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt b/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt
index 0d7828f..b0f7dbf 100644
--- a/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt
+++ b/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt
@@ -28,6 +28,7 @@
import com.casic.br.app.widgets.SelectSceneDialog
import com.google.common.util.concurrent.ListenableFuture
import com.pengxh.kt.lite.base.KotlinBaseActivity
+import com.pengxh.kt.lite.extensions.navigatePageTo
import com.pengxh.kt.lite.extensions.rotateImage
import com.pengxh.kt.lite.widget.TitleBarView
import java.io.ByteArrayOutputStream
@@ -79,6 +80,7 @@
// 检查 CameraProvider 可用性
cameraProviderFuture.addListener({
try {
+ //TODO 暂时注掉
bindPreview(cameraProviderFuture.get())
} catch (e: ExecutionException) {
e.printStackTrace()
@@ -139,7 +141,7 @@
camera.cameraInfo.cameraState.observe(this) {
//开始预览之后才人脸检测
if (it.type == CameraState.Type.OPEN) {
- imageAnalysis.setAnalyzer(cameraExecutor, imageAnalyzer)
+// imageAnalysis.setAnalyzer(cameraExecutor, imageAnalyzer)
}
}
} catch (e: Exception) {
@@ -211,20 +213,7 @@
override fun initEvent() {
binding.stopButton.setOnClickListener {
- CheckResultDialog.Builder()
- .setContext(this)
- .setPositiveButton("确认,检查结束")
- .setNegativeButton("返回")
- .setOnDialogButtonClickListener(object :
- CheckResultDialog.OnDialogButtonClickListener {
- override fun onConfirmClick() {
-
- }
-
- override fun onCancelClick() {
-
- }
- }).build().show()
+ CheckResultDialog(this).show()
}
binding.tipsButton.setOnClickListener {
@@ -232,7 +221,7 @@
}
binding.addButton.setOnClickListener {
-
+ navigatePageTo()
}
binding.listButton.setOnClickListener {
diff --git a/app/build.gradle b/app/build.gradle
index af9b29a..c95e04f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -102,4 +102,6 @@
implementation 'com.github.bumptech.glide:glide:4.9.0'
//图片选择框架
implementation 'io.github.lucksiege:pictureselector:v3.11.1'
+ //图片压缩
+ implementation 'top.zibin:Luban:1.1.8'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1e93201..607ca75 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -57,6 +57,9 @@
+
diff --git a/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
new file mode 100644
index 0000000..41e3603
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
@@ -0,0 +1,95 @@
+package com.casic.br.app.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.casic.br.app.R
+import com.pengxh.kt.lite.adapter.ViewHolder
+
+/**
+ * 数量可编辑图片适配器
+ *
+ * @param context 使用适配的上下文
+ * @param viewWidth RecyclerView实际宽度,一般情况下就是屏幕宽度,但是如果有其他控件和它在同一行,需要计算实际宽度,不然无法正确显示RecyclerView item的布局
+ * @param imageCountLimit 最多显示的图片数目
+ * @param spanCount 每行显示的图片数目
+ * */
+class EditableImageAdapter(
+ private val context: Context,
+ private val viewWidth: Int,
+ private val imageCountLimit: Int,
+ private val spanCount: Int
+) : RecyclerView.Adapter() {
+
+ private val kTag = "EditableImageAdapter"
+ private var images: MutableList = ArrayList()
+
+ fun setupImage(images: MutableList) {
+ this.images = images
+ }
+
+ fun deleteImage(position: Int) {
+ if (images.isNotEmpty()) {
+ images.removeAt(position)
+ /**
+ * 发生变化的item数目
+ * */
+ notifyItemRangeRemoved(position, 1)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ LayoutInflater.from(context).inflate(R.layout.item_editable_rv_g, parent, false)
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val imageView = holder.getView(R.id.imageView)
+ val imageSize = viewWidth / spanCount
+ val params = LinearLayout.LayoutParams(imageSize, imageSize)
+ imageView.layoutParams = params
+
+ if (position == itemCount - 1 && images.size < imageCountLimit) {
+ imageView.setImageResource(R.drawable.ic_add_pic)
+ imageView.setOnClickListener { //添加图片
+ itemClickListener?.onAddImageClick()
+ }
+ } else {
+ Glide.with(context).load(images[position]).into(imageView)
+ imageView.setOnClickListener { // 点击操作,查看大图
+ itemClickListener?.onItemClick(holder.bindingAdapterPosition)
+ }
+ // 长按监听
+ imageView.setOnLongClickListener { v -> //长按删除
+ itemClickListener?.onItemLongClick(v, holder.bindingAdapterPosition)
+ true
+ }
+ }
+ }
+
+ override fun getItemCount(): Int = if (images.size >= imageCountLimit) {
+ imageCountLimit
+ } else {
+ images.size + 1
+ }
+
+ private var itemClickListener: OnItemClickListener? = null
+
+ fun setOnItemClickListener(itemClickListener: OnItemClickListener?) {
+ this.itemClickListener = itemClickListener
+ }
+
+ interface OnItemClickListener {
+ fun onAddImageClick()
+
+ fun onItemClick(position: Int)
+
+ fun onItemLongClick(view: View?, position: Int)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
index 41f8f85..8c431ef 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
@@ -1,11 +1,13 @@
package com.casic.br.app.retrofit
+import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
+import retrofit2.http.Part
import retrofit2.http.PartMap
import retrofit2.http.Query
import retrofit2.http.QueryMap
@@ -116,4 +118,14 @@
*/
@GET("/knowledge-class/list")
suspend fun getLibraryList(@Header("token") token: String): String
+
+ /**
+ * 上传文件
+ */
+ @Multipart
+ @POST("/file/upload")
+ suspend fun uploadImage(
+ @Header("token") token: String,
+ @Part file: MultipartBody.Part
+ ): String
}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
index 94d44e9..8712d19 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
@@ -6,8 +6,12 @@
import com.pengxh.kt.lite.utils.RetrofitFactory
import com.pengxh.kt.lite.utils.SaveKeyValues
import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
import okhttp3.RequestBody
+import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
+import java.io.File
object RetrofitServiceManager {
@@ -142,4 +146,13 @@
suspend fun getLibraryList(): String {
return api.getLibraryList(AuthenticationHelper.token)
}
+
+ /**
+ * 上传图片
+ */
+ suspend fun uploadImage(image: File): String {
+ val requestBody = image.asRequestBody("image/png".toMediaTypeOrNull())
+ val imagePart = MultipartBody.Part.createFormData("file", image.name, requestBody)
+ return api.uploadImage(AuthenticationHelper.token, imagePart)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt
new file mode 100644
index 0000000..8187b83
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt
@@ -0,0 +1,71 @@
+package com.casic.br.app.utils
+
+import android.content.Context
+import android.widget.ImageView
+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.casic.br.app.R
+import com.luck.picture.lib.engine.ImageEngine
+import com.luck.picture.lib.utils.ActivityCompatHelper
+
+class GlideLoadEngine private constructor() : ImageEngine {
+ companion object {
+ val get: 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 loadImage(
+ context: Context,
+ imageView: ImageView,
+ url: String,
+ maxWidth: Int,
+ maxHeight: Int
+ ) {
+ if (!ActivityCompatHelper.assertValidRequest(context)) {
+ return
+ }
+ Glide.with(context)
+ .load(url)
+ .override(maxWidth, maxHeight)
+ .into(imageView)
+ }
+
+ 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/br/app/view/AddHiddenTroubleActivity.kt b/app/src/main/java/com/casic/br/app/view/AddHiddenTroubleActivity.kt
new file mode 100644
index 0000000..08be23c
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/view/AddHiddenTroubleActivity.kt
@@ -0,0 +1,163 @@
+package com.casic.br.app.view
+
+import android.app.ActionBar.LayoutParams
+import android.os.Bundle
+import android.os.CountDownTimer
+import android.os.Handler
+import android.os.Message
+import android.util.Log
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import com.casic.br.app.adapter.EditableImageAdapter
+import com.casic.br.app.databinding.ActivityAddHiddenTroubleBinding
+import com.casic.br.app.extensions.combineFilePath
+import com.casic.br.app.utils.GlideLoadEngine
+import com.casic.br.app.vm.ImageFileViewModel
+import com.luck.picture.lib.basic.PictureSelector
+import com.luck.picture.lib.config.SelectMimeType
+import com.luck.picture.lib.entity.LocalMedia
+import com.luck.picture.lib.interfaces.OnResultCallbackListener
+import com.pengxh.kt.lite.base.KotlinBaseActivity
+import com.pengxh.kt.lite.divider.RecyclerViewItemOffsets
+import com.pengxh.kt.lite.extensions.createCompressImageDir
+import com.pengxh.kt.lite.extensions.dp2px
+import com.pengxh.kt.lite.extensions.getScreenWidth
+import com.pengxh.kt.lite.extensions.show
+import com.pengxh.kt.lite.extensions.toJson
+import com.pengxh.kt.lite.utils.LoadState
+import com.pengxh.kt.lite.utils.WeakReferenceHandler
+import top.zibin.luban.Luban
+import top.zibin.luban.OnCompressListener
+import java.io.File
+
+class AddHiddenTroubleActivity : KotlinBaseActivity(),
+ Handler.Callback {
+
+ private val kTag = "AddHiddenTroubleActivity"
+ private val selectedImages = ArrayList()
+ private val imagePaths = ArrayList()
+ private val recyclerViewImages = ArrayList()
+ private val marginOffset by lazy { 1.dp2px(this) }
+ private val weakReferenceHandler by lazy { WeakReferenceHandler(this) }
+ private lateinit var editableImageAdapter: EditableImageAdapter
+ private lateinit var imageFileViewModel: ImageFileViewModel
+
+ override fun initViewBinding(): ActivityAddHiddenTroubleBinding {
+ return ActivityAddHiddenTroubleBinding.inflate(layoutInflater)
+ }
+
+ override fun setupTopBarLayout() {
+
+ }
+
+ override fun observeRequestState() {
+ imageFileViewModel.loadState.observe(this) {
+ if (it == LoadState.Loading) {
+ "图片上传中,请稍后...".show(this)
+ }
+ }
+ }
+
+ override fun initOnCreate(savedInstanceState: Bundle?) {
+ val params = window.attributes
+ params.height = LayoutParams.WRAP_CONTENT
+ window.attributes = params
+
+ imageFileViewModel = ViewModelProvider(this)[ImageFileViewModel::class.java]
+ imageFileViewModel.resultModel.observe(this) {
+ if (it.code == 200) {
+ val url = it.data.toString()
+ imagePaths.add(url)
+ recyclerViewImages.add(url.combineFilePath())
+ editableImageAdapter.notifyDataSetChanged()
+ }
+ }
+
+ //左外边距,左内边距,TextView宽度,内间距,右内边距,右外边距
+ val viewWidth = getScreenWidth() - (15 + 15 + 65 + 10 + 15 + 15).dp2px(this)
+ editableImageAdapter = EditableImageAdapter(this, viewWidth, 3, 3)
+ editableImageAdapter.setupImage(recyclerViewImages)
+ binding.recyclerView.addItemDecoration(
+ RecyclerViewItemOffsets(marginOffset, marginOffset, marginOffset, marginOffset)
+ )
+ binding.recyclerView.adapter = editableImageAdapter
+ }
+
+ override fun initEvent() {
+ editableImageAdapter.setOnItemClickListener(object :
+ EditableImageAdapter.OnItemClickListener {
+ override fun onAddImageClick() {
+ selectPicture()
+ }
+
+ override fun onItemClick(position: Int) {
+
+ }
+
+ override fun onItemLongClick(view: View?, position: Int) {
+ editableImageAdapter.deleteImage(position)
+ selectedImages.removeAt(position)
+ }
+ })
+
+ binding.dialogConfirmButton.setOnClickListener {
+ Log.d(kTag, imagePaths.toJson())
+ }
+
+ binding.dialogCancelButton.setOnClickListener { finish() }
+ }
+
+ private fun selectPicture() {
+ PictureSelector.create(this).openGallery(SelectMimeType.ofImage()).isGif(false)
+ .isMaxSelectEnabledMask(true).setFilterMinFileSize(100).setMaxSelectNum(3)
+ .isDisplayCamera(true).setImageEngine(GlideLoadEngine.get)
+ .setSelectedData(selectedImages)
+ .forResult(object : OnResultCallbackListener {
+ override fun onResult(result: ArrayList) {
+ // 如果设置了setSelectedData,它会将之前的结果作为最新的返回值,要注意重复问题
+ // 线程控制图片压缩上传过程,防止速度过快导致压缩失败
+ val sum = (result.size * 500).toLong()
+ object : CountDownTimer(sum, 500) {
+ override fun onTick(millisUntilFinished: Long) {
+ val i = millisUntilFinished / 500
+ val message = weakReferenceHandler.obtainMessage()
+ message.obj = result[i.toInt()]
+ message.what = 2024042201
+ weakReferenceHandler.handleMessage(message)
+ }
+
+ override fun onFinish() {
+
+ }
+ }.start()
+ }
+
+ override fun onCancel() {}
+ })
+ }
+
+ override fun handleMessage(msg: Message): Boolean {
+ if (msg.what == 2024042201) {
+ val result = msg.obj as LocalMedia
+ selectedImages.add(result)
+ //压缩图片
+ Luban.with(this).load(result.realPath).ignoreBy(100)
+ .setTargetDir(createCompressImageDir().toString())
+ .setCompressListener(object : OnCompressListener {
+ override fun onStart() {
+
+ }
+
+ override fun onSuccess(file: File) {
+ //上传图片
+ imageFileViewModel.uploadImage(file)
+ }
+
+ override fun onError(e: Throwable) {
+ e.printStackTrace()
+ }
+ }).launch()
+ }
+ return true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt b/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt
index 0d7828f..b0f7dbf 100644
--- a/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt
+++ b/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt
@@ -28,6 +28,7 @@
import com.casic.br.app.widgets.SelectSceneDialog
import com.google.common.util.concurrent.ListenableFuture
import com.pengxh.kt.lite.base.KotlinBaseActivity
+import com.pengxh.kt.lite.extensions.navigatePageTo
import com.pengxh.kt.lite.extensions.rotateImage
import com.pengxh.kt.lite.widget.TitleBarView
import java.io.ByteArrayOutputStream
@@ -79,6 +80,7 @@
// 检查 CameraProvider 可用性
cameraProviderFuture.addListener({
try {
+ //TODO 暂时注掉
bindPreview(cameraProviderFuture.get())
} catch (e: ExecutionException) {
e.printStackTrace()
@@ -139,7 +141,7 @@
camera.cameraInfo.cameraState.observe(this) {
//开始预览之后才人脸检测
if (it.type == CameraState.Type.OPEN) {
- imageAnalysis.setAnalyzer(cameraExecutor, imageAnalyzer)
+// imageAnalysis.setAnalyzer(cameraExecutor, imageAnalyzer)
}
}
} catch (e: Exception) {
@@ -211,20 +213,7 @@
override fun initEvent() {
binding.stopButton.setOnClickListener {
- CheckResultDialog.Builder()
- .setContext(this)
- .setPositiveButton("确认,检查结束")
- .setNegativeButton("返回")
- .setOnDialogButtonClickListener(object :
- CheckResultDialog.OnDialogButtonClickListener {
- override fun onConfirmClick() {
-
- }
-
- override fun onCancelClick() {
-
- }
- }).build().show()
+ CheckResultDialog(this).show()
}
binding.tipsButton.setOnClickListener {
@@ -232,7 +221,7 @@
}
binding.addButton.setOnClickListener {
-
+ navigatePageTo()
}
binding.listButton.setOnClickListener {
diff --git a/app/src/main/java/com/casic/br/app/vm/ImageFileViewModel.kt b/app/src/main/java/com/casic/br/app/vm/ImageFileViewModel.kt
new file mode 100644
index 0000000..c76af42
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/vm/ImageFileViewModel.kt
@@ -0,0 +1,35 @@
+package com.casic.br.app.vm
+
+import androidx.lifecycle.MutableLiveData
+import com.casic.br.app.extensions.getResponseCode
+import com.casic.br.app.model.ActionResultModel
+import com.casic.br.app.retrofit.RetrofitServiceManager
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import com.pengxh.kt.lite.base.BaseViewModel
+import com.pengxh.kt.lite.extensions.launch
+import com.pengxh.kt.lite.utils.LoadState
+import java.io.File
+
+class ImageFileViewModel : BaseViewModel() {
+
+ private val gson by lazy { Gson() }
+ val resultModel = MutableLiveData()
+
+ fun uploadImage(image: File) = launch({
+ loadState.value = LoadState.Loading
+ val response = RetrofitServiceManager.uploadImage(image)
+ val responseCode = response.getResponseCode()
+ if (responseCode == 200) {
+ loadState.value = LoadState.Success
+ resultModel.value = gson.fromJson(
+ response, object : TypeToken() {}.type
+ )
+ } else {
+ loadState.value = LoadState.Fail
+ }
+ }, {
+ loadState.value = LoadState.Fail
+ it.printStackTrace()
+ })
+}
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index af9b29a..c95e04f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -102,4 +102,6 @@
implementation 'com.github.bumptech.glide:glide:4.9.0'
//图片选择框架
implementation 'io.github.lucksiege:pictureselector:v3.11.1'
+ //图片压缩
+ implementation 'top.zibin:Luban:1.1.8'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1e93201..607ca75 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -57,6 +57,9 @@
+
diff --git a/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
new file mode 100644
index 0000000..41e3603
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
@@ -0,0 +1,95 @@
+package com.casic.br.app.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.casic.br.app.R
+import com.pengxh.kt.lite.adapter.ViewHolder
+
+/**
+ * 数量可编辑图片适配器
+ *
+ * @param context 使用适配的上下文
+ * @param viewWidth RecyclerView实际宽度,一般情况下就是屏幕宽度,但是如果有其他控件和它在同一行,需要计算实际宽度,不然无法正确显示RecyclerView item的布局
+ * @param imageCountLimit 最多显示的图片数目
+ * @param spanCount 每行显示的图片数目
+ * */
+class EditableImageAdapter(
+ private val context: Context,
+ private val viewWidth: Int,
+ private val imageCountLimit: Int,
+ private val spanCount: Int
+) : RecyclerView.Adapter() {
+
+ private val kTag = "EditableImageAdapter"
+ private var images: MutableList = ArrayList()
+
+ fun setupImage(images: MutableList) {
+ this.images = images
+ }
+
+ fun deleteImage(position: Int) {
+ if (images.isNotEmpty()) {
+ images.removeAt(position)
+ /**
+ * 发生变化的item数目
+ * */
+ notifyItemRangeRemoved(position, 1)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ LayoutInflater.from(context).inflate(R.layout.item_editable_rv_g, parent, false)
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val imageView = holder.getView(R.id.imageView)
+ val imageSize = viewWidth / spanCount
+ val params = LinearLayout.LayoutParams(imageSize, imageSize)
+ imageView.layoutParams = params
+
+ if (position == itemCount - 1 && images.size < imageCountLimit) {
+ imageView.setImageResource(R.drawable.ic_add_pic)
+ imageView.setOnClickListener { //添加图片
+ itemClickListener?.onAddImageClick()
+ }
+ } else {
+ Glide.with(context).load(images[position]).into(imageView)
+ imageView.setOnClickListener { // 点击操作,查看大图
+ itemClickListener?.onItemClick(holder.bindingAdapterPosition)
+ }
+ // 长按监听
+ imageView.setOnLongClickListener { v -> //长按删除
+ itemClickListener?.onItemLongClick(v, holder.bindingAdapterPosition)
+ true
+ }
+ }
+ }
+
+ override fun getItemCount(): Int = if (images.size >= imageCountLimit) {
+ imageCountLimit
+ } else {
+ images.size + 1
+ }
+
+ private var itemClickListener: OnItemClickListener? = null
+
+ fun setOnItemClickListener(itemClickListener: OnItemClickListener?) {
+ this.itemClickListener = itemClickListener
+ }
+
+ interface OnItemClickListener {
+ fun onAddImageClick()
+
+ fun onItemClick(position: Int)
+
+ fun onItemLongClick(view: View?, position: Int)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
index 41f8f85..8c431ef 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
@@ -1,11 +1,13 @@
package com.casic.br.app.retrofit
+import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
+import retrofit2.http.Part
import retrofit2.http.PartMap
import retrofit2.http.Query
import retrofit2.http.QueryMap
@@ -116,4 +118,14 @@
*/
@GET("/knowledge-class/list")
suspend fun getLibraryList(@Header("token") token: String): String
+
+ /**
+ * 上传文件
+ */
+ @Multipart
+ @POST("/file/upload")
+ suspend fun uploadImage(
+ @Header("token") token: String,
+ @Part file: MultipartBody.Part
+ ): String
}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
index 94d44e9..8712d19 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
@@ -6,8 +6,12 @@
import com.pengxh.kt.lite.utils.RetrofitFactory
import com.pengxh.kt.lite.utils.SaveKeyValues
import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
import okhttp3.RequestBody
+import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
+import java.io.File
object RetrofitServiceManager {
@@ -142,4 +146,13 @@
suspend fun getLibraryList(): String {
return api.getLibraryList(AuthenticationHelper.token)
}
+
+ /**
+ * 上传图片
+ */
+ suspend fun uploadImage(image: File): String {
+ val requestBody = image.asRequestBody("image/png".toMediaTypeOrNull())
+ val imagePart = MultipartBody.Part.createFormData("file", image.name, requestBody)
+ return api.uploadImage(AuthenticationHelper.token, imagePart)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt
new file mode 100644
index 0000000..8187b83
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt
@@ -0,0 +1,71 @@
+package com.casic.br.app.utils
+
+import android.content.Context
+import android.widget.ImageView
+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.casic.br.app.R
+import com.luck.picture.lib.engine.ImageEngine
+import com.luck.picture.lib.utils.ActivityCompatHelper
+
+class GlideLoadEngine private constructor() : ImageEngine {
+ companion object {
+ val get: 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 loadImage(
+ context: Context,
+ imageView: ImageView,
+ url: String,
+ maxWidth: Int,
+ maxHeight: Int
+ ) {
+ if (!ActivityCompatHelper.assertValidRequest(context)) {
+ return
+ }
+ Glide.with(context)
+ .load(url)
+ .override(maxWidth, maxHeight)
+ .into(imageView)
+ }
+
+ 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/br/app/view/AddHiddenTroubleActivity.kt b/app/src/main/java/com/casic/br/app/view/AddHiddenTroubleActivity.kt
new file mode 100644
index 0000000..08be23c
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/view/AddHiddenTroubleActivity.kt
@@ -0,0 +1,163 @@
+package com.casic.br.app.view
+
+import android.app.ActionBar.LayoutParams
+import android.os.Bundle
+import android.os.CountDownTimer
+import android.os.Handler
+import android.os.Message
+import android.util.Log
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import com.casic.br.app.adapter.EditableImageAdapter
+import com.casic.br.app.databinding.ActivityAddHiddenTroubleBinding
+import com.casic.br.app.extensions.combineFilePath
+import com.casic.br.app.utils.GlideLoadEngine
+import com.casic.br.app.vm.ImageFileViewModel
+import com.luck.picture.lib.basic.PictureSelector
+import com.luck.picture.lib.config.SelectMimeType
+import com.luck.picture.lib.entity.LocalMedia
+import com.luck.picture.lib.interfaces.OnResultCallbackListener
+import com.pengxh.kt.lite.base.KotlinBaseActivity
+import com.pengxh.kt.lite.divider.RecyclerViewItemOffsets
+import com.pengxh.kt.lite.extensions.createCompressImageDir
+import com.pengxh.kt.lite.extensions.dp2px
+import com.pengxh.kt.lite.extensions.getScreenWidth
+import com.pengxh.kt.lite.extensions.show
+import com.pengxh.kt.lite.extensions.toJson
+import com.pengxh.kt.lite.utils.LoadState
+import com.pengxh.kt.lite.utils.WeakReferenceHandler
+import top.zibin.luban.Luban
+import top.zibin.luban.OnCompressListener
+import java.io.File
+
+class AddHiddenTroubleActivity : KotlinBaseActivity(),
+ Handler.Callback {
+
+ private val kTag = "AddHiddenTroubleActivity"
+ private val selectedImages = ArrayList()
+ private val imagePaths = ArrayList()
+ private val recyclerViewImages = ArrayList()
+ private val marginOffset by lazy { 1.dp2px(this) }
+ private val weakReferenceHandler by lazy { WeakReferenceHandler(this) }
+ private lateinit var editableImageAdapter: EditableImageAdapter
+ private lateinit var imageFileViewModel: ImageFileViewModel
+
+ override fun initViewBinding(): ActivityAddHiddenTroubleBinding {
+ return ActivityAddHiddenTroubleBinding.inflate(layoutInflater)
+ }
+
+ override fun setupTopBarLayout() {
+
+ }
+
+ override fun observeRequestState() {
+ imageFileViewModel.loadState.observe(this) {
+ if (it == LoadState.Loading) {
+ "图片上传中,请稍后...".show(this)
+ }
+ }
+ }
+
+ override fun initOnCreate(savedInstanceState: Bundle?) {
+ val params = window.attributes
+ params.height = LayoutParams.WRAP_CONTENT
+ window.attributes = params
+
+ imageFileViewModel = ViewModelProvider(this)[ImageFileViewModel::class.java]
+ imageFileViewModel.resultModel.observe(this) {
+ if (it.code == 200) {
+ val url = it.data.toString()
+ imagePaths.add(url)
+ recyclerViewImages.add(url.combineFilePath())
+ editableImageAdapter.notifyDataSetChanged()
+ }
+ }
+
+ //左外边距,左内边距,TextView宽度,内间距,右内边距,右外边距
+ val viewWidth = getScreenWidth() - (15 + 15 + 65 + 10 + 15 + 15).dp2px(this)
+ editableImageAdapter = EditableImageAdapter(this, viewWidth, 3, 3)
+ editableImageAdapter.setupImage(recyclerViewImages)
+ binding.recyclerView.addItemDecoration(
+ RecyclerViewItemOffsets(marginOffset, marginOffset, marginOffset, marginOffset)
+ )
+ binding.recyclerView.adapter = editableImageAdapter
+ }
+
+ override fun initEvent() {
+ editableImageAdapter.setOnItemClickListener(object :
+ EditableImageAdapter.OnItemClickListener {
+ override fun onAddImageClick() {
+ selectPicture()
+ }
+
+ override fun onItemClick(position: Int) {
+
+ }
+
+ override fun onItemLongClick(view: View?, position: Int) {
+ editableImageAdapter.deleteImage(position)
+ selectedImages.removeAt(position)
+ }
+ })
+
+ binding.dialogConfirmButton.setOnClickListener {
+ Log.d(kTag, imagePaths.toJson())
+ }
+
+ binding.dialogCancelButton.setOnClickListener { finish() }
+ }
+
+ private fun selectPicture() {
+ PictureSelector.create(this).openGallery(SelectMimeType.ofImage()).isGif(false)
+ .isMaxSelectEnabledMask(true).setFilterMinFileSize(100).setMaxSelectNum(3)
+ .isDisplayCamera(true).setImageEngine(GlideLoadEngine.get)
+ .setSelectedData(selectedImages)
+ .forResult(object : OnResultCallbackListener {
+ override fun onResult(result: ArrayList) {
+ // 如果设置了setSelectedData,它会将之前的结果作为最新的返回值,要注意重复问题
+ // 线程控制图片压缩上传过程,防止速度过快导致压缩失败
+ val sum = (result.size * 500).toLong()
+ object : CountDownTimer(sum, 500) {
+ override fun onTick(millisUntilFinished: Long) {
+ val i = millisUntilFinished / 500
+ val message = weakReferenceHandler.obtainMessage()
+ message.obj = result[i.toInt()]
+ message.what = 2024042201
+ weakReferenceHandler.handleMessage(message)
+ }
+
+ override fun onFinish() {
+
+ }
+ }.start()
+ }
+
+ override fun onCancel() {}
+ })
+ }
+
+ override fun handleMessage(msg: Message): Boolean {
+ if (msg.what == 2024042201) {
+ val result = msg.obj as LocalMedia
+ selectedImages.add(result)
+ //压缩图片
+ Luban.with(this).load(result.realPath).ignoreBy(100)
+ .setTargetDir(createCompressImageDir().toString())
+ .setCompressListener(object : OnCompressListener {
+ override fun onStart() {
+
+ }
+
+ override fun onSuccess(file: File) {
+ //上传图片
+ imageFileViewModel.uploadImage(file)
+ }
+
+ override fun onError(e: Throwable) {
+ e.printStackTrace()
+ }
+ }).launch()
+ }
+ return true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt b/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt
index 0d7828f..b0f7dbf 100644
--- a/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt
+++ b/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt
@@ -28,6 +28,7 @@
import com.casic.br.app.widgets.SelectSceneDialog
import com.google.common.util.concurrent.ListenableFuture
import com.pengxh.kt.lite.base.KotlinBaseActivity
+import com.pengxh.kt.lite.extensions.navigatePageTo
import com.pengxh.kt.lite.extensions.rotateImage
import com.pengxh.kt.lite.widget.TitleBarView
import java.io.ByteArrayOutputStream
@@ -79,6 +80,7 @@
// 检查 CameraProvider 可用性
cameraProviderFuture.addListener({
try {
+ //TODO 暂时注掉
bindPreview(cameraProviderFuture.get())
} catch (e: ExecutionException) {
e.printStackTrace()
@@ -139,7 +141,7 @@
camera.cameraInfo.cameraState.observe(this) {
//开始预览之后才人脸检测
if (it.type == CameraState.Type.OPEN) {
- imageAnalysis.setAnalyzer(cameraExecutor, imageAnalyzer)
+// imageAnalysis.setAnalyzer(cameraExecutor, imageAnalyzer)
}
}
} catch (e: Exception) {
@@ -211,20 +213,7 @@
override fun initEvent() {
binding.stopButton.setOnClickListener {
- CheckResultDialog.Builder()
- .setContext(this)
- .setPositiveButton("确认,检查结束")
- .setNegativeButton("返回")
- .setOnDialogButtonClickListener(object :
- CheckResultDialog.OnDialogButtonClickListener {
- override fun onConfirmClick() {
-
- }
-
- override fun onCancelClick() {
-
- }
- }).build().show()
+ CheckResultDialog(this).show()
}
binding.tipsButton.setOnClickListener {
@@ -232,7 +221,7 @@
}
binding.addButton.setOnClickListener {
-
+ navigatePageTo()
}
binding.listButton.setOnClickListener {
diff --git a/app/src/main/java/com/casic/br/app/vm/ImageFileViewModel.kt b/app/src/main/java/com/casic/br/app/vm/ImageFileViewModel.kt
new file mode 100644
index 0000000..c76af42
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/vm/ImageFileViewModel.kt
@@ -0,0 +1,35 @@
+package com.casic.br.app.vm
+
+import androidx.lifecycle.MutableLiveData
+import com.casic.br.app.extensions.getResponseCode
+import com.casic.br.app.model.ActionResultModel
+import com.casic.br.app.retrofit.RetrofitServiceManager
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import com.pengxh.kt.lite.base.BaseViewModel
+import com.pengxh.kt.lite.extensions.launch
+import com.pengxh.kt.lite.utils.LoadState
+import java.io.File
+
+class ImageFileViewModel : BaseViewModel() {
+
+ private val gson by lazy { Gson() }
+ val resultModel = MutableLiveData()
+
+ fun uploadImage(image: File) = launch({
+ loadState.value = LoadState.Loading
+ val response = RetrofitServiceManager.uploadImage(image)
+ val responseCode = response.getResponseCode()
+ if (responseCode == 200) {
+ loadState.value = LoadState.Success
+ resultModel.value = gson.fromJson(
+ response, object : TypeToken() {}.type
+ )
+ } else {
+ loadState.value = LoadState.Fail
+ }
+ }, {
+ loadState.value = LoadState.Fail
+ it.printStackTrace()
+ })
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/widgets/CheckResultDialog.kt b/app/src/main/java/com/casic/br/app/widgets/CheckResultDialog.kt
index 5721f81..43e5881 100644
--- a/app/src/main/java/com/casic/br/app/widgets/CheckResultDialog.kt
+++ b/app/src/main/java/com/casic/br/app/widgets/CheckResultDialog.kt
@@ -3,49 +3,11 @@
import android.app.Dialog
import android.content.Context
import android.os.Bundle
-import com.casic.br.app.R
import com.casic.br.app.databinding.DialogCheckResultBinding
import com.pengxh.kt.lite.extensions.binding
import com.pengxh.kt.lite.extensions.initDialogLayoutParams
-class CheckResultDialog private constructor(builder: Builder) : Dialog(
- builder.context, R.style.UserDefinedDialogStyle
-) {
- private val context = builder.context
- private val positiveBtn = builder.positiveBtn
- private val negativeBtn = builder.negativeBtn
- private val listener = builder.listener
-
- class Builder {
- lateinit var context: Context
- lateinit var positiveBtn: String
- lateinit var negativeBtn: String
- lateinit var listener: OnDialogButtonClickListener
-
- fun setContext(context: Context): Builder {
- this.context = context
- return this
- }
-
- fun setPositiveButton(name: String): Builder {
- this.positiveBtn = name
- return this
- }
-
- fun setNegativeButton(name: String): Builder {
- this.negativeBtn = name
- return this
- }
-
- fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder {
- this.listener = listener
- return this
- }
-
- fun build(): CheckResultDialog {
- return CheckResultDialog(this)
- }
- }
+class CheckResultDialog constructor(context: Context) : Dialog(context) {
private val binding: DialogCheckResultBinding by binding()
@@ -54,24 +16,10 @@
this.initDialogLayoutParams(1f)
setCanceledOnTouchOutside(false)
-
binding.dialogConfirmButton.setOnClickListener {
-
- }
-
-
- if (negativeBtn.isNotBlank()) {
- binding.dialogCancelButton.text = negativeBtn
- }
- binding.dialogCancelButton.setOnClickListener {
- listener.onCancelClick()
dismiss()
}
- }
- interface OnDialogButtonClickListener {
- fun onConfirmClick()
-
- fun onCancelClick()
+ binding.dialogCancelButton.setOnClickListener { dismiss() }
}
}
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index af9b29a..c95e04f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -102,4 +102,6 @@
implementation 'com.github.bumptech.glide:glide:4.9.0'
//图片选择框架
implementation 'io.github.lucksiege:pictureselector:v3.11.1'
+ //图片压缩
+ implementation 'top.zibin:Luban:1.1.8'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1e93201..607ca75 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -57,6 +57,9 @@
+
diff --git a/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
new file mode 100644
index 0000000..41e3603
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
@@ -0,0 +1,95 @@
+package com.casic.br.app.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.casic.br.app.R
+import com.pengxh.kt.lite.adapter.ViewHolder
+
+/**
+ * 数量可编辑图片适配器
+ *
+ * @param context 使用适配的上下文
+ * @param viewWidth RecyclerView实际宽度,一般情况下就是屏幕宽度,但是如果有其他控件和它在同一行,需要计算实际宽度,不然无法正确显示RecyclerView item的布局
+ * @param imageCountLimit 最多显示的图片数目
+ * @param spanCount 每行显示的图片数目
+ * */
+class EditableImageAdapter(
+ private val context: Context,
+ private val viewWidth: Int,
+ private val imageCountLimit: Int,
+ private val spanCount: Int
+) : RecyclerView.Adapter() {
+
+ private val kTag = "EditableImageAdapter"
+ private var images: MutableList = ArrayList()
+
+ fun setupImage(images: MutableList) {
+ this.images = images
+ }
+
+ fun deleteImage(position: Int) {
+ if (images.isNotEmpty()) {
+ images.removeAt(position)
+ /**
+ * 发生变化的item数目
+ * */
+ notifyItemRangeRemoved(position, 1)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ LayoutInflater.from(context).inflate(R.layout.item_editable_rv_g, parent, false)
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val imageView = holder.getView(R.id.imageView)
+ val imageSize = viewWidth / spanCount
+ val params = LinearLayout.LayoutParams(imageSize, imageSize)
+ imageView.layoutParams = params
+
+ if (position == itemCount - 1 && images.size < imageCountLimit) {
+ imageView.setImageResource(R.drawable.ic_add_pic)
+ imageView.setOnClickListener { //添加图片
+ itemClickListener?.onAddImageClick()
+ }
+ } else {
+ Glide.with(context).load(images[position]).into(imageView)
+ imageView.setOnClickListener { // 点击操作,查看大图
+ itemClickListener?.onItemClick(holder.bindingAdapterPosition)
+ }
+ // 长按监听
+ imageView.setOnLongClickListener { v -> //长按删除
+ itemClickListener?.onItemLongClick(v, holder.bindingAdapterPosition)
+ true
+ }
+ }
+ }
+
+ override fun getItemCount(): Int = if (images.size >= imageCountLimit) {
+ imageCountLimit
+ } else {
+ images.size + 1
+ }
+
+ private var itemClickListener: OnItemClickListener? = null
+
+ fun setOnItemClickListener(itemClickListener: OnItemClickListener?) {
+ this.itemClickListener = itemClickListener
+ }
+
+ interface OnItemClickListener {
+ fun onAddImageClick()
+
+ fun onItemClick(position: Int)
+
+ fun onItemLongClick(view: View?, position: Int)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
index 41f8f85..8c431ef 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
@@ -1,11 +1,13 @@
package com.casic.br.app.retrofit
+import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
+import retrofit2.http.Part
import retrofit2.http.PartMap
import retrofit2.http.Query
import retrofit2.http.QueryMap
@@ -116,4 +118,14 @@
*/
@GET("/knowledge-class/list")
suspend fun getLibraryList(@Header("token") token: String): String
+
+ /**
+ * 上传文件
+ */
+ @Multipart
+ @POST("/file/upload")
+ suspend fun uploadImage(
+ @Header("token") token: String,
+ @Part file: MultipartBody.Part
+ ): String
}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
index 94d44e9..8712d19 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
@@ -6,8 +6,12 @@
import com.pengxh.kt.lite.utils.RetrofitFactory
import com.pengxh.kt.lite.utils.SaveKeyValues
import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
import okhttp3.RequestBody
+import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
+import java.io.File
object RetrofitServiceManager {
@@ -142,4 +146,13 @@
suspend fun getLibraryList(): String {
return api.getLibraryList(AuthenticationHelper.token)
}
+
+ /**
+ * 上传图片
+ */
+ suspend fun uploadImage(image: File): String {
+ val requestBody = image.asRequestBody("image/png".toMediaTypeOrNull())
+ val imagePart = MultipartBody.Part.createFormData("file", image.name, requestBody)
+ return api.uploadImage(AuthenticationHelper.token, imagePart)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt
new file mode 100644
index 0000000..8187b83
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt
@@ -0,0 +1,71 @@
+package com.casic.br.app.utils
+
+import android.content.Context
+import android.widget.ImageView
+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.casic.br.app.R
+import com.luck.picture.lib.engine.ImageEngine
+import com.luck.picture.lib.utils.ActivityCompatHelper
+
+class GlideLoadEngine private constructor() : ImageEngine {
+ companion object {
+ val get: 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 loadImage(
+ context: Context,
+ imageView: ImageView,
+ url: String,
+ maxWidth: Int,
+ maxHeight: Int
+ ) {
+ if (!ActivityCompatHelper.assertValidRequest(context)) {
+ return
+ }
+ Glide.with(context)
+ .load(url)
+ .override(maxWidth, maxHeight)
+ .into(imageView)
+ }
+
+ 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/br/app/view/AddHiddenTroubleActivity.kt b/app/src/main/java/com/casic/br/app/view/AddHiddenTroubleActivity.kt
new file mode 100644
index 0000000..08be23c
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/view/AddHiddenTroubleActivity.kt
@@ -0,0 +1,163 @@
+package com.casic.br.app.view
+
+import android.app.ActionBar.LayoutParams
+import android.os.Bundle
+import android.os.CountDownTimer
+import android.os.Handler
+import android.os.Message
+import android.util.Log
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import com.casic.br.app.adapter.EditableImageAdapter
+import com.casic.br.app.databinding.ActivityAddHiddenTroubleBinding
+import com.casic.br.app.extensions.combineFilePath
+import com.casic.br.app.utils.GlideLoadEngine
+import com.casic.br.app.vm.ImageFileViewModel
+import com.luck.picture.lib.basic.PictureSelector
+import com.luck.picture.lib.config.SelectMimeType
+import com.luck.picture.lib.entity.LocalMedia
+import com.luck.picture.lib.interfaces.OnResultCallbackListener
+import com.pengxh.kt.lite.base.KotlinBaseActivity
+import com.pengxh.kt.lite.divider.RecyclerViewItemOffsets
+import com.pengxh.kt.lite.extensions.createCompressImageDir
+import com.pengxh.kt.lite.extensions.dp2px
+import com.pengxh.kt.lite.extensions.getScreenWidth
+import com.pengxh.kt.lite.extensions.show
+import com.pengxh.kt.lite.extensions.toJson
+import com.pengxh.kt.lite.utils.LoadState
+import com.pengxh.kt.lite.utils.WeakReferenceHandler
+import top.zibin.luban.Luban
+import top.zibin.luban.OnCompressListener
+import java.io.File
+
+class AddHiddenTroubleActivity : KotlinBaseActivity(),
+ Handler.Callback {
+
+ private val kTag = "AddHiddenTroubleActivity"
+ private val selectedImages = ArrayList()
+ private val imagePaths = ArrayList()
+ private val recyclerViewImages = ArrayList()
+ private val marginOffset by lazy { 1.dp2px(this) }
+ private val weakReferenceHandler by lazy { WeakReferenceHandler(this) }
+ private lateinit var editableImageAdapter: EditableImageAdapter
+ private lateinit var imageFileViewModel: ImageFileViewModel
+
+ override fun initViewBinding(): ActivityAddHiddenTroubleBinding {
+ return ActivityAddHiddenTroubleBinding.inflate(layoutInflater)
+ }
+
+ override fun setupTopBarLayout() {
+
+ }
+
+ override fun observeRequestState() {
+ imageFileViewModel.loadState.observe(this) {
+ if (it == LoadState.Loading) {
+ "图片上传中,请稍后...".show(this)
+ }
+ }
+ }
+
+ override fun initOnCreate(savedInstanceState: Bundle?) {
+ val params = window.attributes
+ params.height = LayoutParams.WRAP_CONTENT
+ window.attributes = params
+
+ imageFileViewModel = ViewModelProvider(this)[ImageFileViewModel::class.java]
+ imageFileViewModel.resultModel.observe(this) {
+ if (it.code == 200) {
+ val url = it.data.toString()
+ imagePaths.add(url)
+ recyclerViewImages.add(url.combineFilePath())
+ editableImageAdapter.notifyDataSetChanged()
+ }
+ }
+
+ //左外边距,左内边距,TextView宽度,内间距,右内边距,右外边距
+ val viewWidth = getScreenWidth() - (15 + 15 + 65 + 10 + 15 + 15).dp2px(this)
+ editableImageAdapter = EditableImageAdapter(this, viewWidth, 3, 3)
+ editableImageAdapter.setupImage(recyclerViewImages)
+ binding.recyclerView.addItemDecoration(
+ RecyclerViewItemOffsets(marginOffset, marginOffset, marginOffset, marginOffset)
+ )
+ binding.recyclerView.adapter = editableImageAdapter
+ }
+
+ override fun initEvent() {
+ editableImageAdapter.setOnItemClickListener(object :
+ EditableImageAdapter.OnItemClickListener {
+ override fun onAddImageClick() {
+ selectPicture()
+ }
+
+ override fun onItemClick(position: Int) {
+
+ }
+
+ override fun onItemLongClick(view: View?, position: Int) {
+ editableImageAdapter.deleteImage(position)
+ selectedImages.removeAt(position)
+ }
+ })
+
+ binding.dialogConfirmButton.setOnClickListener {
+ Log.d(kTag, imagePaths.toJson())
+ }
+
+ binding.dialogCancelButton.setOnClickListener { finish() }
+ }
+
+ private fun selectPicture() {
+ PictureSelector.create(this).openGallery(SelectMimeType.ofImage()).isGif(false)
+ .isMaxSelectEnabledMask(true).setFilterMinFileSize(100).setMaxSelectNum(3)
+ .isDisplayCamera(true).setImageEngine(GlideLoadEngine.get)
+ .setSelectedData(selectedImages)
+ .forResult(object : OnResultCallbackListener {
+ override fun onResult(result: ArrayList) {
+ // 如果设置了setSelectedData,它会将之前的结果作为最新的返回值,要注意重复问题
+ // 线程控制图片压缩上传过程,防止速度过快导致压缩失败
+ val sum = (result.size * 500).toLong()
+ object : CountDownTimer(sum, 500) {
+ override fun onTick(millisUntilFinished: Long) {
+ val i = millisUntilFinished / 500
+ val message = weakReferenceHandler.obtainMessage()
+ message.obj = result[i.toInt()]
+ message.what = 2024042201
+ weakReferenceHandler.handleMessage(message)
+ }
+
+ override fun onFinish() {
+
+ }
+ }.start()
+ }
+
+ override fun onCancel() {}
+ })
+ }
+
+ override fun handleMessage(msg: Message): Boolean {
+ if (msg.what == 2024042201) {
+ val result = msg.obj as LocalMedia
+ selectedImages.add(result)
+ //压缩图片
+ Luban.with(this).load(result.realPath).ignoreBy(100)
+ .setTargetDir(createCompressImageDir().toString())
+ .setCompressListener(object : OnCompressListener {
+ override fun onStart() {
+
+ }
+
+ override fun onSuccess(file: File) {
+ //上传图片
+ imageFileViewModel.uploadImage(file)
+ }
+
+ override fun onError(e: Throwable) {
+ e.printStackTrace()
+ }
+ }).launch()
+ }
+ return true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt b/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt
index 0d7828f..b0f7dbf 100644
--- a/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt
+++ b/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt
@@ -28,6 +28,7 @@
import com.casic.br.app.widgets.SelectSceneDialog
import com.google.common.util.concurrent.ListenableFuture
import com.pengxh.kt.lite.base.KotlinBaseActivity
+import com.pengxh.kt.lite.extensions.navigatePageTo
import com.pengxh.kt.lite.extensions.rotateImage
import com.pengxh.kt.lite.widget.TitleBarView
import java.io.ByteArrayOutputStream
@@ -79,6 +80,7 @@
// 检查 CameraProvider 可用性
cameraProviderFuture.addListener({
try {
+ //TODO 暂时注掉
bindPreview(cameraProviderFuture.get())
} catch (e: ExecutionException) {
e.printStackTrace()
@@ -139,7 +141,7 @@
camera.cameraInfo.cameraState.observe(this) {
//开始预览之后才人脸检测
if (it.type == CameraState.Type.OPEN) {
- imageAnalysis.setAnalyzer(cameraExecutor, imageAnalyzer)
+// imageAnalysis.setAnalyzer(cameraExecutor, imageAnalyzer)
}
}
} catch (e: Exception) {
@@ -211,20 +213,7 @@
override fun initEvent() {
binding.stopButton.setOnClickListener {
- CheckResultDialog.Builder()
- .setContext(this)
- .setPositiveButton("确认,检查结束")
- .setNegativeButton("返回")
- .setOnDialogButtonClickListener(object :
- CheckResultDialog.OnDialogButtonClickListener {
- override fun onConfirmClick() {
-
- }
-
- override fun onCancelClick() {
-
- }
- }).build().show()
+ CheckResultDialog(this).show()
}
binding.tipsButton.setOnClickListener {
@@ -232,7 +221,7 @@
}
binding.addButton.setOnClickListener {
-
+ navigatePageTo()
}
binding.listButton.setOnClickListener {
diff --git a/app/src/main/java/com/casic/br/app/vm/ImageFileViewModel.kt b/app/src/main/java/com/casic/br/app/vm/ImageFileViewModel.kt
new file mode 100644
index 0000000..c76af42
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/vm/ImageFileViewModel.kt
@@ -0,0 +1,35 @@
+package com.casic.br.app.vm
+
+import androidx.lifecycle.MutableLiveData
+import com.casic.br.app.extensions.getResponseCode
+import com.casic.br.app.model.ActionResultModel
+import com.casic.br.app.retrofit.RetrofitServiceManager
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import com.pengxh.kt.lite.base.BaseViewModel
+import com.pengxh.kt.lite.extensions.launch
+import com.pengxh.kt.lite.utils.LoadState
+import java.io.File
+
+class ImageFileViewModel : BaseViewModel() {
+
+ private val gson by lazy { Gson() }
+ val resultModel = MutableLiveData()
+
+ fun uploadImage(image: File) = launch({
+ loadState.value = LoadState.Loading
+ val response = RetrofitServiceManager.uploadImage(image)
+ val responseCode = response.getResponseCode()
+ if (responseCode == 200) {
+ loadState.value = LoadState.Success
+ resultModel.value = gson.fromJson(
+ response, object : TypeToken() {}.type
+ )
+ } else {
+ loadState.value = LoadState.Fail
+ }
+ }, {
+ loadState.value = LoadState.Fail
+ it.printStackTrace()
+ })
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/widgets/CheckResultDialog.kt b/app/src/main/java/com/casic/br/app/widgets/CheckResultDialog.kt
index 5721f81..43e5881 100644
--- a/app/src/main/java/com/casic/br/app/widgets/CheckResultDialog.kt
+++ b/app/src/main/java/com/casic/br/app/widgets/CheckResultDialog.kt
@@ -3,49 +3,11 @@
import android.app.Dialog
import android.content.Context
import android.os.Bundle
-import com.casic.br.app.R
import com.casic.br.app.databinding.DialogCheckResultBinding
import com.pengxh.kt.lite.extensions.binding
import com.pengxh.kt.lite.extensions.initDialogLayoutParams
-class CheckResultDialog private constructor(builder: Builder) : Dialog(
- builder.context, R.style.UserDefinedDialogStyle
-) {
- private val context = builder.context
- private val positiveBtn = builder.positiveBtn
- private val negativeBtn = builder.negativeBtn
- private val listener = builder.listener
-
- class Builder {
- lateinit var context: Context
- lateinit var positiveBtn: String
- lateinit var negativeBtn: String
- lateinit var listener: OnDialogButtonClickListener
-
- fun setContext(context: Context): Builder {
- this.context = context
- return this
- }
-
- fun setPositiveButton(name: String): Builder {
- this.positiveBtn = name
- return this
- }
-
- fun setNegativeButton(name: String): Builder {
- this.negativeBtn = name
- return this
- }
-
- fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder {
- this.listener = listener
- return this
- }
-
- fun build(): CheckResultDialog {
- return CheckResultDialog(this)
- }
- }
+class CheckResultDialog constructor(context: Context) : Dialog(context) {
private val binding: DialogCheckResultBinding by binding()
@@ -54,24 +16,10 @@
this.initDialogLayoutParams(1f)
setCanceledOnTouchOutside(false)
-
binding.dialogConfirmButton.setOnClickListener {
-
- }
-
-
- if (negativeBtn.isNotBlank()) {
- binding.dialogCancelButton.text = negativeBtn
- }
- binding.dialogCancelButton.setOnClickListener {
- listener.onCancelClick()
dismiss()
}
- }
- interface OnDialogButtonClickListener {
- fun onConfirmClick()
-
- fun onCancelClick()
+ binding.dialogCancelButton.setOnClickListener { dismiss() }
}
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_add_hidden_trouble.xml b/app/src/main/res/layout/activity_add_hidden_trouble.xml
new file mode 100644
index 0000000..bd2fb8d
--- /dev/null
+++ b/app/src/main/res/layout/activity_add_hidden_trouble.xml
@@ -0,0 +1,189 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/build.gradle b/app/build.gradle
index af9b29a..c95e04f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -102,4 +102,6 @@
implementation 'com.github.bumptech.glide:glide:4.9.0'
//图片选择框架
implementation 'io.github.lucksiege:pictureselector:v3.11.1'
+ //图片压缩
+ implementation 'top.zibin:Luban:1.1.8'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1e93201..607ca75 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -57,6 +57,9 @@
+
diff --git a/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
new file mode 100644
index 0000000..41e3603
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
@@ -0,0 +1,95 @@
+package com.casic.br.app.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.casic.br.app.R
+import com.pengxh.kt.lite.adapter.ViewHolder
+
+/**
+ * 数量可编辑图片适配器
+ *
+ * @param context 使用适配的上下文
+ * @param viewWidth RecyclerView实际宽度,一般情况下就是屏幕宽度,但是如果有其他控件和它在同一行,需要计算实际宽度,不然无法正确显示RecyclerView item的布局
+ * @param imageCountLimit 最多显示的图片数目
+ * @param spanCount 每行显示的图片数目
+ * */
+class EditableImageAdapter(
+ private val context: Context,
+ private val viewWidth: Int,
+ private val imageCountLimit: Int,
+ private val spanCount: Int
+) : RecyclerView.Adapter() {
+
+ private val kTag = "EditableImageAdapter"
+ private var images: MutableList = ArrayList()
+
+ fun setupImage(images: MutableList) {
+ this.images = images
+ }
+
+ fun deleteImage(position: Int) {
+ if (images.isNotEmpty()) {
+ images.removeAt(position)
+ /**
+ * 发生变化的item数目
+ * */
+ notifyItemRangeRemoved(position, 1)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ LayoutInflater.from(context).inflate(R.layout.item_editable_rv_g, parent, false)
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val imageView = holder.getView(R.id.imageView)
+ val imageSize = viewWidth / spanCount
+ val params = LinearLayout.LayoutParams(imageSize, imageSize)
+ imageView.layoutParams = params
+
+ if (position == itemCount - 1 && images.size < imageCountLimit) {
+ imageView.setImageResource(R.drawable.ic_add_pic)
+ imageView.setOnClickListener { //添加图片
+ itemClickListener?.onAddImageClick()
+ }
+ } else {
+ Glide.with(context).load(images[position]).into(imageView)
+ imageView.setOnClickListener { // 点击操作,查看大图
+ itemClickListener?.onItemClick(holder.bindingAdapterPosition)
+ }
+ // 长按监听
+ imageView.setOnLongClickListener { v -> //长按删除
+ itemClickListener?.onItemLongClick(v, holder.bindingAdapterPosition)
+ true
+ }
+ }
+ }
+
+ override fun getItemCount(): Int = if (images.size >= imageCountLimit) {
+ imageCountLimit
+ } else {
+ images.size + 1
+ }
+
+ private var itemClickListener: OnItemClickListener? = null
+
+ fun setOnItemClickListener(itemClickListener: OnItemClickListener?) {
+ this.itemClickListener = itemClickListener
+ }
+
+ interface OnItemClickListener {
+ fun onAddImageClick()
+
+ fun onItemClick(position: Int)
+
+ fun onItemLongClick(view: View?, position: Int)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
index 41f8f85..8c431ef 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
@@ -1,11 +1,13 @@
package com.casic.br.app.retrofit
+import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
+import retrofit2.http.Part
import retrofit2.http.PartMap
import retrofit2.http.Query
import retrofit2.http.QueryMap
@@ -116,4 +118,14 @@
*/
@GET("/knowledge-class/list")
suspend fun getLibraryList(@Header("token") token: String): String
+
+ /**
+ * 上传文件
+ */
+ @Multipart
+ @POST("/file/upload")
+ suspend fun uploadImage(
+ @Header("token") token: String,
+ @Part file: MultipartBody.Part
+ ): String
}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
index 94d44e9..8712d19 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
@@ -6,8 +6,12 @@
import com.pengxh.kt.lite.utils.RetrofitFactory
import com.pengxh.kt.lite.utils.SaveKeyValues
import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
import okhttp3.RequestBody
+import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
+import java.io.File
object RetrofitServiceManager {
@@ -142,4 +146,13 @@
suspend fun getLibraryList(): String {
return api.getLibraryList(AuthenticationHelper.token)
}
+
+ /**
+ * 上传图片
+ */
+ suspend fun uploadImage(image: File): String {
+ val requestBody = image.asRequestBody("image/png".toMediaTypeOrNull())
+ val imagePart = MultipartBody.Part.createFormData("file", image.name, requestBody)
+ return api.uploadImage(AuthenticationHelper.token, imagePart)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt
new file mode 100644
index 0000000..8187b83
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt
@@ -0,0 +1,71 @@
+package com.casic.br.app.utils
+
+import android.content.Context
+import android.widget.ImageView
+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.casic.br.app.R
+import com.luck.picture.lib.engine.ImageEngine
+import com.luck.picture.lib.utils.ActivityCompatHelper
+
+class GlideLoadEngine private constructor() : ImageEngine {
+ companion object {
+ val get: 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 loadImage(
+ context: Context,
+ imageView: ImageView,
+ url: String,
+ maxWidth: Int,
+ maxHeight: Int
+ ) {
+ if (!ActivityCompatHelper.assertValidRequest(context)) {
+ return
+ }
+ Glide.with(context)
+ .load(url)
+ .override(maxWidth, maxHeight)
+ .into(imageView)
+ }
+
+ 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/br/app/view/AddHiddenTroubleActivity.kt b/app/src/main/java/com/casic/br/app/view/AddHiddenTroubleActivity.kt
new file mode 100644
index 0000000..08be23c
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/view/AddHiddenTroubleActivity.kt
@@ -0,0 +1,163 @@
+package com.casic.br.app.view
+
+import android.app.ActionBar.LayoutParams
+import android.os.Bundle
+import android.os.CountDownTimer
+import android.os.Handler
+import android.os.Message
+import android.util.Log
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import com.casic.br.app.adapter.EditableImageAdapter
+import com.casic.br.app.databinding.ActivityAddHiddenTroubleBinding
+import com.casic.br.app.extensions.combineFilePath
+import com.casic.br.app.utils.GlideLoadEngine
+import com.casic.br.app.vm.ImageFileViewModel
+import com.luck.picture.lib.basic.PictureSelector
+import com.luck.picture.lib.config.SelectMimeType
+import com.luck.picture.lib.entity.LocalMedia
+import com.luck.picture.lib.interfaces.OnResultCallbackListener
+import com.pengxh.kt.lite.base.KotlinBaseActivity
+import com.pengxh.kt.lite.divider.RecyclerViewItemOffsets
+import com.pengxh.kt.lite.extensions.createCompressImageDir
+import com.pengxh.kt.lite.extensions.dp2px
+import com.pengxh.kt.lite.extensions.getScreenWidth
+import com.pengxh.kt.lite.extensions.show
+import com.pengxh.kt.lite.extensions.toJson
+import com.pengxh.kt.lite.utils.LoadState
+import com.pengxh.kt.lite.utils.WeakReferenceHandler
+import top.zibin.luban.Luban
+import top.zibin.luban.OnCompressListener
+import java.io.File
+
+class AddHiddenTroubleActivity : KotlinBaseActivity(),
+ Handler.Callback {
+
+ private val kTag = "AddHiddenTroubleActivity"
+ private val selectedImages = ArrayList()
+ private val imagePaths = ArrayList()
+ private val recyclerViewImages = ArrayList()
+ private val marginOffset by lazy { 1.dp2px(this) }
+ private val weakReferenceHandler by lazy { WeakReferenceHandler(this) }
+ private lateinit var editableImageAdapter: EditableImageAdapter
+ private lateinit var imageFileViewModel: ImageFileViewModel
+
+ override fun initViewBinding(): ActivityAddHiddenTroubleBinding {
+ return ActivityAddHiddenTroubleBinding.inflate(layoutInflater)
+ }
+
+ override fun setupTopBarLayout() {
+
+ }
+
+ override fun observeRequestState() {
+ imageFileViewModel.loadState.observe(this) {
+ if (it == LoadState.Loading) {
+ "图片上传中,请稍后...".show(this)
+ }
+ }
+ }
+
+ override fun initOnCreate(savedInstanceState: Bundle?) {
+ val params = window.attributes
+ params.height = LayoutParams.WRAP_CONTENT
+ window.attributes = params
+
+ imageFileViewModel = ViewModelProvider(this)[ImageFileViewModel::class.java]
+ imageFileViewModel.resultModel.observe(this) {
+ if (it.code == 200) {
+ val url = it.data.toString()
+ imagePaths.add(url)
+ recyclerViewImages.add(url.combineFilePath())
+ editableImageAdapter.notifyDataSetChanged()
+ }
+ }
+
+ //左外边距,左内边距,TextView宽度,内间距,右内边距,右外边距
+ val viewWidth = getScreenWidth() - (15 + 15 + 65 + 10 + 15 + 15).dp2px(this)
+ editableImageAdapter = EditableImageAdapter(this, viewWidth, 3, 3)
+ editableImageAdapter.setupImage(recyclerViewImages)
+ binding.recyclerView.addItemDecoration(
+ RecyclerViewItemOffsets(marginOffset, marginOffset, marginOffset, marginOffset)
+ )
+ binding.recyclerView.adapter = editableImageAdapter
+ }
+
+ override fun initEvent() {
+ editableImageAdapter.setOnItemClickListener(object :
+ EditableImageAdapter.OnItemClickListener {
+ override fun onAddImageClick() {
+ selectPicture()
+ }
+
+ override fun onItemClick(position: Int) {
+
+ }
+
+ override fun onItemLongClick(view: View?, position: Int) {
+ editableImageAdapter.deleteImage(position)
+ selectedImages.removeAt(position)
+ }
+ })
+
+ binding.dialogConfirmButton.setOnClickListener {
+ Log.d(kTag, imagePaths.toJson())
+ }
+
+ binding.dialogCancelButton.setOnClickListener { finish() }
+ }
+
+ private fun selectPicture() {
+ PictureSelector.create(this).openGallery(SelectMimeType.ofImage()).isGif(false)
+ .isMaxSelectEnabledMask(true).setFilterMinFileSize(100).setMaxSelectNum(3)
+ .isDisplayCamera(true).setImageEngine(GlideLoadEngine.get)
+ .setSelectedData(selectedImages)
+ .forResult(object : OnResultCallbackListener {
+ override fun onResult(result: ArrayList) {
+ // 如果设置了setSelectedData,它会将之前的结果作为最新的返回值,要注意重复问题
+ // 线程控制图片压缩上传过程,防止速度过快导致压缩失败
+ val sum = (result.size * 500).toLong()
+ object : CountDownTimer(sum, 500) {
+ override fun onTick(millisUntilFinished: Long) {
+ val i = millisUntilFinished / 500
+ val message = weakReferenceHandler.obtainMessage()
+ message.obj = result[i.toInt()]
+ message.what = 2024042201
+ weakReferenceHandler.handleMessage(message)
+ }
+
+ override fun onFinish() {
+
+ }
+ }.start()
+ }
+
+ override fun onCancel() {}
+ })
+ }
+
+ override fun handleMessage(msg: Message): Boolean {
+ if (msg.what == 2024042201) {
+ val result = msg.obj as LocalMedia
+ selectedImages.add(result)
+ //压缩图片
+ Luban.with(this).load(result.realPath).ignoreBy(100)
+ .setTargetDir(createCompressImageDir().toString())
+ .setCompressListener(object : OnCompressListener {
+ override fun onStart() {
+
+ }
+
+ override fun onSuccess(file: File) {
+ //上传图片
+ imageFileViewModel.uploadImage(file)
+ }
+
+ override fun onError(e: Throwable) {
+ e.printStackTrace()
+ }
+ }).launch()
+ }
+ return true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt b/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt
index 0d7828f..b0f7dbf 100644
--- a/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt
+++ b/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt
@@ -28,6 +28,7 @@
import com.casic.br.app.widgets.SelectSceneDialog
import com.google.common.util.concurrent.ListenableFuture
import com.pengxh.kt.lite.base.KotlinBaseActivity
+import com.pengxh.kt.lite.extensions.navigatePageTo
import com.pengxh.kt.lite.extensions.rotateImage
import com.pengxh.kt.lite.widget.TitleBarView
import java.io.ByteArrayOutputStream
@@ -79,6 +80,7 @@
// 检查 CameraProvider 可用性
cameraProviderFuture.addListener({
try {
+ //TODO 暂时注掉
bindPreview(cameraProviderFuture.get())
} catch (e: ExecutionException) {
e.printStackTrace()
@@ -139,7 +141,7 @@
camera.cameraInfo.cameraState.observe(this) {
//开始预览之后才人脸检测
if (it.type == CameraState.Type.OPEN) {
- imageAnalysis.setAnalyzer(cameraExecutor, imageAnalyzer)
+// imageAnalysis.setAnalyzer(cameraExecutor, imageAnalyzer)
}
}
} catch (e: Exception) {
@@ -211,20 +213,7 @@
override fun initEvent() {
binding.stopButton.setOnClickListener {
- CheckResultDialog.Builder()
- .setContext(this)
- .setPositiveButton("确认,检查结束")
- .setNegativeButton("返回")
- .setOnDialogButtonClickListener(object :
- CheckResultDialog.OnDialogButtonClickListener {
- override fun onConfirmClick() {
-
- }
-
- override fun onCancelClick() {
-
- }
- }).build().show()
+ CheckResultDialog(this).show()
}
binding.tipsButton.setOnClickListener {
@@ -232,7 +221,7 @@
}
binding.addButton.setOnClickListener {
-
+ navigatePageTo()
}
binding.listButton.setOnClickListener {
diff --git a/app/src/main/java/com/casic/br/app/vm/ImageFileViewModel.kt b/app/src/main/java/com/casic/br/app/vm/ImageFileViewModel.kt
new file mode 100644
index 0000000..c76af42
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/vm/ImageFileViewModel.kt
@@ -0,0 +1,35 @@
+package com.casic.br.app.vm
+
+import androidx.lifecycle.MutableLiveData
+import com.casic.br.app.extensions.getResponseCode
+import com.casic.br.app.model.ActionResultModel
+import com.casic.br.app.retrofit.RetrofitServiceManager
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import com.pengxh.kt.lite.base.BaseViewModel
+import com.pengxh.kt.lite.extensions.launch
+import com.pengxh.kt.lite.utils.LoadState
+import java.io.File
+
+class ImageFileViewModel : BaseViewModel() {
+
+ private val gson by lazy { Gson() }
+ val resultModel = MutableLiveData()
+
+ fun uploadImage(image: File) = launch({
+ loadState.value = LoadState.Loading
+ val response = RetrofitServiceManager.uploadImage(image)
+ val responseCode = response.getResponseCode()
+ if (responseCode == 200) {
+ loadState.value = LoadState.Success
+ resultModel.value = gson.fromJson(
+ response, object : TypeToken() {}.type
+ )
+ } else {
+ loadState.value = LoadState.Fail
+ }
+ }, {
+ loadState.value = LoadState.Fail
+ it.printStackTrace()
+ })
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/widgets/CheckResultDialog.kt b/app/src/main/java/com/casic/br/app/widgets/CheckResultDialog.kt
index 5721f81..43e5881 100644
--- a/app/src/main/java/com/casic/br/app/widgets/CheckResultDialog.kt
+++ b/app/src/main/java/com/casic/br/app/widgets/CheckResultDialog.kt
@@ -3,49 +3,11 @@
import android.app.Dialog
import android.content.Context
import android.os.Bundle
-import com.casic.br.app.R
import com.casic.br.app.databinding.DialogCheckResultBinding
import com.pengxh.kt.lite.extensions.binding
import com.pengxh.kt.lite.extensions.initDialogLayoutParams
-class CheckResultDialog private constructor(builder: Builder) : Dialog(
- builder.context, R.style.UserDefinedDialogStyle
-) {
- private val context = builder.context
- private val positiveBtn = builder.positiveBtn
- private val negativeBtn = builder.negativeBtn
- private val listener = builder.listener
-
- class Builder {
- lateinit var context: Context
- lateinit var positiveBtn: String
- lateinit var negativeBtn: String
- lateinit var listener: OnDialogButtonClickListener
-
- fun setContext(context: Context): Builder {
- this.context = context
- return this
- }
-
- fun setPositiveButton(name: String): Builder {
- this.positiveBtn = name
- return this
- }
-
- fun setNegativeButton(name: String): Builder {
- this.negativeBtn = name
- return this
- }
-
- fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder {
- this.listener = listener
- return this
- }
-
- fun build(): CheckResultDialog {
- return CheckResultDialog(this)
- }
- }
+class CheckResultDialog constructor(context: Context) : Dialog(context) {
private val binding: DialogCheckResultBinding by binding()
@@ -54,24 +16,10 @@
this.initDialogLayoutParams(1f)
setCanceledOnTouchOutside(false)
-
binding.dialogConfirmButton.setOnClickListener {
-
- }
-
-
- if (negativeBtn.isNotBlank()) {
- binding.dialogCancelButton.text = negativeBtn
- }
- binding.dialogCancelButton.setOnClickListener {
- listener.onCancelClick()
dismiss()
}
- }
- interface OnDialogButtonClickListener {
- fun onConfirmClick()
-
- fun onCancelClick()
+ binding.dialogCancelButton.setOnClickListener { dismiss() }
}
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_add_hidden_trouble.xml b/app/src/main/res/layout/activity_add_hidden_trouble.xml
new file mode 100644
index 0000000..bd2fb8d
--- /dev/null
+++ b/app/src/main/res/layout/activity_add_hidden_trouble.xml
@@ -0,0 +1,189 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index e48db89..9fa8932 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -29,6 +29,7 @@
50dp
55dp
60dp
+ 65dp
70dp
75dp
80dp
diff --git a/app/build.gradle b/app/build.gradle
index af9b29a..c95e04f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -102,4 +102,6 @@
implementation 'com.github.bumptech.glide:glide:4.9.0'
//图片选择框架
implementation 'io.github.lucksiege:pictureselector:v3.11.1'
+ //图片压缩
+ implementation 'top.zibin:Luban:1.1.8'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1e93201..607ca75 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -57,6 +57,9 @@
+
diff --git a/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
new file mode 100644
index 0000000..41e3603
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/adapter/EditableImageAdapter.kt
@@ -0,0 +1,95 @@
+package com.casic.br.app.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.casic.br.app.R
+import com.pengxh.kt.lite.adapter.ViewHolder
+
+/**
+ * 数量可编辑图片适配器
+ *
+ * @param context 使用适配的上下文
+ * @param viewWidth RecyclerView实际宽度,一般情况下就是屏幕宽度,但是如果有其他控件和它在同一行,需要计算实际宽度,不然无法正确显示RecyclerView item的布局
+ * @param imageCountLimit 最多显示的图片数目
+ * @param spanCount 每行显示的图片数目
+ * */
+class EditableImageAdapter(
+ private val context: Context,
+ private val viewWidth: Int,
+ private val imageCountLimit: Int,
+ private val spanCount: Int
+) : RecyclerView.Adapter() {
+
+ private val kTag = "EditableImageAdapter"
+ private var images: MutableList = ArrayList()
+
+ fun setupImage(images: MutableList) {
+ this.images = images
+ }
+
+ fun deleteImage(position: Int) {
+ if (images.isNotEmpty()) {
+ images.removeAt(position)
+ /**
+ * 发生变化的item数目
+ * */
+ notifyItemRangeRemoved(position, 1)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ LayoutInflater.from(context).inflate(R.layout.item_editable_rv_g, parent, false)
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val imageView = holder.getView(R.id.imageView)
+ val imageSize = viewWidth / spanCount
+ val params = LinearLayout.LayoutParams(imageSize, imageSize)
+ imageView.layoutParams = params
+
+ if (position == itemCount - 1 && images.size < imageCountLimit) {
+ imageView.setImageResource(R.drawable.ic_add_pic)
+ imageView.setOnClickListener { //添加图片
+ itemClickListener?.onAddImageClick()
+ }
+ } else {
+ Glide.with(context).load(images[position]).into(imageView)
+ imageView.setOnClickListener { // 点击操作,查看大图
+ itemClickListener?.onItemClick(holder.bindingAdapterPosition)
+ }
+ // 长按监听
+ imageView.setOnLongClickListener { v -> //长按删除
+ itemClickListener?.onItemLongClick(v, holder.bindingAdapterPosition)
+ true
+ }
+ }
+ }
+
+ override fun getItemCount(): Int = if (images.size >= imageCountLimit) {
+ imageCountLimit
+ } else {
+ images.size + 1
+ }
+
+ private var itemClickListener: OnItemClickListener? = null
+
+ fun setOnItemClickListener(itemClickListener: OnItemClickListener?) {
+ this.itemClickListener = itemClickListener
+ }
+
+ interface OnItemClickListener {
+ fun onAddImageClick()
+
+ fun onItemClick(position: Int)
+
+ fun onItemLongClick(view: View?, position: Int)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
index 41f8f85..8c431ef 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitService.kt
@@ -1,11 +1,13 @@
package com.casic.br.app.retrofit
+import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
+import retrofit2.http.Part
import retrofit2.http.PartMap
import retrofit2.http.Query
import retrofit2.http.QueryMap
@@ -116,4 +118,14 @@
*/
@GET("/knowledge-class/list")
suspend fun getLibraryList(@Header("token") token: String): String
+
+ /**
+ * 上传文件
+ */
+ @Multipart
+ @POST("/file/upload")
+ suspend fun uploadImage(
+ @Header("token") token: String,
+ @Part file: MultipartBody.Part
+ ): String
}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
index 94d44e9..8712d19 100644
--- a/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
+++ b/app/src/main/java/com/casic/br/app/retrofit/RetrofitServiceManager.kt
@@ -6,8 +6,12 @@
import com.pengxh.kt.lite.utils.RetrofitFactory
import com.pengxh.kt.lite.utils.SaveKeyValues
import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
import okhttp3.RequestBody
+import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
+import java.io.File
object RetrofitServiceManager {
@@ -142,4 +146,13 @@
suspend fun getLibraryList(): String {
return api.getLibraryList(AuthenticationHelper.token)
}
+
+ /**
+ * 上传图片
+ */
+ suspend fun uploadImage(image: File): String {
+ val requestBody = image.asRequestBody("image/png".toMediaTypeOrNull())
+ val imagePart = MultipartBody.Part.createFormData("file", image.name, requestBody)
+ return api.uploadImage(AuthenticationHelper.token, imagePart)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt
new file mode 100644
index 0000000..8187b83
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/utils/GlideLoadEngine.kt
@@ -0,0 +1,71 @@
+package com.casic.br.app.utils
+
+import android.content.Context
+import android.widget.ImageView
+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.casic.br.app.R
+import com.luck.picture.lib.engine.ImageEngine
+import com.luck.picture.lib.utils.ActivityCompatHelper
+
+class GlideLoadEngine private constructor() : ImageEngine {
+ companion object {
+ val get: 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 loadImage(
+ context: Context,
+ imageView: ImageView,
+ url: String,
+ maxWidth: Int,
+ maxHeight: Int
+ ) {
+ if (!ActivityCompatHelper.assertValidRequest(context)) {
+ return
+ }
+ Glide.with(context)
+ .load(url)
+ .override(maxWidth, maxHeight)
+ .into(imageView)
+ }
+
+ 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/br/app/view/AddHiddenTroubleActivity.kt b/app/src/main/java/com/casic/br/app/view/AddHiddenTroubleActivity.kt
new file mode 100644
index 0000000..08be23c
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/view/AddHiddenTroubleActivity.kt
@@ -0,0 +1,163 @@
+package com.casic.br.app.view
+
+import android.app.ActionBar.LayoutParams
+import android.os.Bundle
+import android.os.CountDownTimer
+import android.os.Handler
+import android.os.Message
+import android.util.Log
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import com.casic.br.app.adapter.EditableImageAdapter
+import com.casic.br.app.databinding.ActivityAddHiddenTroubleBinding
+import com.casic.br.app.extensions.combineFilePath
+import com.casic.br.app.utils.GlideLoadEngine
+import com.casic.br.app.vm.ImageFileViewModel
+import com.luck.picture.lib.basic.PictureSelector
+import com.luck.picture.lib.config.SelectMimeType
+import com.luck.picture.lib.entity.LocalMedia
+import com.luck.picture.lib.interfaces.OnResultCallbackListener
+import com.pengxh.kt.lite.base.KotlinBaseActivity
+import com.pengxh.kt.lite.divider.RecyclerViewItemOffsets
+import com.pengxh.kt.lite.extensions.createCompressImageDir
+import com.pengxh.kt.lite.extensions.dp2px
+import com.pengxh.kt.lite.extensions.getScreenWidth
+import com.pengxh.kt.lite.extensions.show
+import com.pengxh.kt.lite.extensions.toJson
+import com.pengxh.kt.lite.utils.LoadState
+import com.pengxh.kt.lite.utils.WeakReferenceHandler
+import top.zibin.luban.Luban
+import top.zibin.luban.OnCompressListener
+import java.io.File
+
+class AddHiddenTroubleActivity : KotlinBaseActivity(),
+ Handler.Callback {
+
+ private val kTag = "AddHiddenTroubleActivity"
+ private val selectedImages = ArrayList()
+ private val imagePaths = ArrayList()
+ private val recyclerViewImages = ArrayList()
+ private val marginOffset by lazy { 1.dp2px(this) }
+ private val weakReferenceHandler by lazy { WeakReferenceHandler(this) }
+ private lateinit var editableImageAdapter: EditableImageAdapter
+ private lateinit var imageFileViewModel: ImageFileViewModel
+
+ override fun initViewBinding(): ActivityAddHiddenTroubleBinding {
+ return ActivityAddHiddenTroubleBinding.inflate(layoutInflater)
+ }
+
+ override fun setupTopBarLayout() {
+
+ }
+
+ override fun observeRequestState() {
+ imageFileViewModel.loadState.observe(this) {
+ if (it == LoadState.Loading) {
+ "图片上传中,请稍后...".show(this)
+ }
+ }
+ }
+
+ override fun initOnCreate(savedInstanceState: Bundle?) {
+ val params = window.attributes
+ params.height = LayoutParams.WRAP_CONTENT
+ window.attributes = params
+
+ imageFileViewModel = ViewModelProvider(this)[ImageFileViewModel::class.java]
+ imageFileViewModel.resultModel.observe(this) {
+ if (it.code == 200) {
+ val url = it.data.toString()
+ imagePaths.add(url)
+ recyclerViewImages.add(url.combineFilePath())
+ editableImageAdapter.notifyDataSetChanged()
+ }
+ }
+
+ //左外边距,左内边距,TextView宽度,内间距,右内边距,右外边距
+ val viewWidth = getScreenWidth() - (15 + 15 + 65 + 10 + 15 + 15).dp2px(this)
+ editableImageAdapter = EditableImageAdapter(this, viewWidth, 3, 3)
+ editableImageAdapter.setupImage(recyclerViewImages)
+ binding.recyclerView.addItemDecoration(
+ RecyclerViewItemOffsets(marginOffset, marginOffset, marginOffset, marginOffset)
+ )
+ binding.recyclerView.adapter = editableImageAdapter
+ }
+
+ override fun initEvent() {
+ editableImageAdapter.setOnItemClickListener(object :
+ EditableImageAdapter.OnItemClickListener {
+ override fun onAddImageClick() {
+ selectPicture()
+ }
+
+ override fun onItemClick(position: Int) {
+
+ }
+
+ override fun onItemLongClick(view: View?, position: Int) {
+ editableImageAdapter.deleteImage(position)
+ selectedImages.removeAt(position)
+ }
+ })
+
+ binding.dialogConfirmButton.setOnClickListener {
+ Log.d(kTag, imagePaths.toJson())
+ }
+
+ binding.dialogCancelButton.setOnClickListener { finish() }
+ }
+
+ private fun selectPicture() {
+ PictureSelector.create(this).openGallery(SelectMimeType.ofImage()).isGif(false)
+ .isMaxSelectEnabledMask(true).setFilterMinFileSize(100).setMaxSelectNum(3)
+ .isDisplayCamera(true).setImageEngine(GlideLoadEngine.get)
+ .setSelectedData(selectedImages)
+ .forResult(object : OnResultCallbackListener {
+ override fun onResult(result: ArrayList) {
+ // 如果设置了setSelectedData,它会将之前的结果作为最新的返回值,要注意重复问题
+ // 线程控制图片压缩上传过程,防止速度过快导致压缩失败
+ val sum = (result.size * 500).toLong()
+ object : CountDownTimer(sum, 500) {
+ override fun onTick(millisUntilFinished: Long) {
+ val i = millisUntilFinished / 500
+ val message = weakReferenceHandler.obtainMessage()
+ message.obj = result[i.toInt()]
+ message.what = 2024042201
+ weakReferenceHandler.handleMessage(message)
+ }
+
+ override fun onFinish() {
+
+ }
+ }.start()
+ }
+
+ override fun onCancel() {}
+ })
+ }
+
+ override fun handleMessage(msg: Message): Boolean {
+ if (msg.what == 2024042201) {
+ val result = msg.obj as LocalMedia
+ selectedImages.add(result)
+ //压缩图片
+ Luban.with(this).load(result.realPath).ignoreBy(100)
+ .setTargetDir(createCompressImageDir().toString())
+ .setCompressListener(object : OnCompressListener {
+ override fun onStart() {
+
+ }
+
+ override fun onSuccess(file: File) {
+ //上传图片
+ imageFileViewModel.uploadImage(file)
+ }
+
+ override fun onError(e: Throwable) {
+ e.printStackTrace()
+ }
+ }).launch()
+ }
+ return true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt b/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt
index 0d7828f..b0f7dbf 100644
--- a/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt
+++ b/app/src/main/java/com/casic/br/app/view/StartCheckActivity.kt
@@ -28,6 +28,7 @@
import com.casic.br.app.widgets.SelectSceneDialog
import com.google.common.util.concurrent.ListenableFuture
import com.pengxh.kt.lite.base.KotlinBaseActivity
+import com.pengxh.kt.lite.extensions.navigatePageTo
import com.pengxh.kt.lite.extensions.rotateImage
import com.pengxh.kt.lite.widget.TitleBarView
import java.io.ByteArrayOutputStream
@@ -79,6 +80,7 @@
// 检查 CameraProvider 可用性
cameraProviderFuture.addListener({
try {
+ //TODO 暂时注掉
bindPreview(cameraProviderFuture.get())
} catch (e: ExecutionException) {
e.printStackTrace()
@@ -139,7 +141,7 @@
camera.cameraInfo.cameraState.observe(this) {
//开始预览之后才人脸检测
if (it.type == CameraState.Type.OPEN) {
- imageAnalysis.setAnalyzer(cameraExecutor, imageAnalyzer)
+// imageAnalysis.setAnalyzer(cameraExecutor, imageAnalyzer)
}
}
} catch (e: Exception) {
@@ -211,20 +213,7 @@
override fun initEvent() {
binding.stopButton.setOnClickListener {
- CheckResultDialog.Builder()
- .setContext(this)
- .setPositiveButton("确认,检查结束")
- .setNegativeButton("返回")
- .setOnDialogButtonClickListener(object :
- CheckResultDialog.OnDialogButtonClickListener {
- override fun onConfirmClick() {
-
- }
-
- override fun onCancelClick() {
-
- }
- }).build().show()
+ CheckResultDialog(this).show()
}
binding.tipsButton.setOnClickListener {
@@ -232,7 +221,7 @@
}
binding.addButton.setOnClickListener {
-
+ navigatePageTo()
}
binding.listButton.setOnClickListener {
diff --git a/app/src/main/java/com/casic/br/app/vm/ImageFileViewModel.kt b/app/src/main/java/com/casic/br/app/vm/ImageFileViewModel.kt
new file mode 100644
index 0000000..c76af42
--- /dev/null
+++ b/app/src/main/java/com/casic/br/app/vm/ImageFileViewModel.kt
@@ -0,0 +1,35 @@
+package com.casic.br.app.vm
+
+import androidx.lifecycle.MutableLiveData
+import com.casic.br.app.extensions.getResponseCode
+import com.casic.br.app.model.ActionResultModel
+import com.casic.br.app.retrofit.RetrofitServiceManager
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import com.pengxh.kt.lite.base.BaseViewModel
+import com.pengxh.kt.lite.extensions.launch
+import com.pengxh.kt.lite.utils.LoadState
+import java.io.File
+
+class ImageFileViewModel : BaseViewModel() {
+
+ private val gson by lazy { Gson() }
+ val resultModel = MutableLiveData()
+
+ fun uploadImage(image: File) = launch({
+ loadState.value = LoadState.Loading
+ val response = RetrofitServiceManager.uploadImage(image)
+ val responseCode = response.getResponseCode()
+ if (responseCode == 200) {
+ loadState.value = LoadState.Success
+ resultModel.value = gson.fromJson(
+ response, object : TypeToken() {}.type
+ )
+ } else {
+ loadState.value = LoadState.Fail
+ }
+ }, {
+ loadState.value = LoadState.Fail
+ it.printStackTrace()
+ })
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/casic/br/app/widgets/CheckResultDialog.kt b/app/src/main/java/com/casic/br/app/widgets/CheckResultDialog.kt
index 5721f81..43e5881 100644
--- a/app/src/main/java/com/casic/br/app/widgets/CheckResultDialog.kt
+++ b/app/src/main/java/com/casic/br/app/widgets/CheckResultDialog.kt
@@ -3,49 +3,11 @@
import android.app.Dialog
import android.content.Context
import android.os.Bundle
-import com.casic.br.app.R
import com.casic.br.app.databinding.DialogCheckResultBinding
import com.pengxh.kt.lite.extensions.binding
import com.pengxh.kt.lite.extensions.initDialogLayoutParams
-class CheckResultDialog private constructor(builder: Builder) : Dialog(
- builder.context, R.style.UserDefinedDialogStyle
-) {
- private val context = builder.context
- private val positiveBtn = builder.positiveBtn
- private val negativeBtn = builder.negativeBtn
- private val listener = builder.listener
-
- class Builder {
- lateinit var context: Context
- lateinit var positiveBtn: String
- lateinit var negativeBtn: String
- lateinit var listener: OnDialogButtonClickListener
-
- fun setContext(context: Context): Builder {
- this.context = context
- return this
- }
-
- fun setPositiveButton(name: String): Builder {
- this.positiveBtn = name
- return this
- }
-
- fun setNegativeButton(name: String): Builder {
- this.negativeBtn = name
- return this
- }
-
- fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder {
- this.listener = listener
- return this
- }
-
- fun build(): CheckResultDialog {
- return CheckResultDialog(this)
- }
- }
+class CheckResultDialog constructor(context: Context) : Dialog(context) {
private val binding: DialogCheckResultBinding by binding()
@@ -54,24 +16,10 @@
this.initDialogLayoutParams(1f)
setCanceledOnTouchOutside(false)
-
binding.dialogConfirmButton.setOnClickListener {
-
- }
-
-
- if (negativeBtn.isNotBlank()) {
- binding.dialogCancelButton.text = negativeBtn
- }
- binding.dialogCancelButton.setOnClickListener {
- listener.onCancelClick()
dismiss()
}
- }
- interface OnDialogButtonClickListener {
- fun onConfirmClick()
-
- fun onCancelClick()
+ binding.dialogCancelButton.setOnClickListener { dismiss() }
}
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_add_hidden_trouble.xml b/app/src/main/res/layout/activity_add_hidden_trouble.xml
new file mode 100644
index 0000000..bd2fb8d
--- /dev/null
+++ b/app/src/main/res/layout/activity_add_hidden_trouble.xml
@@ -0,0 +1,189 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index e48db89..9fa8932 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -29,6 +29,7 @@
50dp
55dp
60dp
+ 65dp
70dp
75dp
80dp
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 4286f89..5d51a10 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -18,4 +18,23 @@
- @anim/activity_in
- @anim/activity_out
+
+
\ No newline at end of file