diff --git a/.gradle/8.13/checksums/checksums.lock b/.gradle/8.13/checksums/checksums.lock
index c70b405..d32605b 100644
Binary files a/.gradle/8.13/checksums/checksums.lock and b/.gradle/8.13/checksums/checksums.lock differ
diff --git a/.gradle/8.13/checksums/md5-checksums.bin b/.gradle/8.13/checksums/md5-checksums.bin
index 2109238..f6c4b55 100644
Binary files a/.gradle/8.13/checksums/md5-checksums.bin and b/.gradle/8.13/checksums/md5-checksums.bin differ
diff --git a/.gradle/8.13/checksums/sha1-checksums.bin b/.gradle/8.13/checksums/sha1-checksums.bin
index dc0ec33..d87e1d1 100644
Binary files a/.gradle/8.13/checksums/sha1-checksums.bin and b/.gradle/8.13/checksums/sha1-checksums.bin differ
diff --git a/.gradle/8.13/executionHistory/executionHistory.bin b/.gradle/8.13/executionHistory/executionHistory.bin
index e4da689..3c05a85 100644
Binary files a/.gradle/8.13/executionHistory/executionHistory.bin and b/.gradle/8.13/executionHistory/executionHistory.bin differ
diff --git a/.gradle/8.13/executionHistory/executionHistory.lock b/.gradle/8.13/executionHistory/executionHistory.lock
index 81cebf5..881a7e5 100644
Binary files a/.gradle/8.13/executionHistory/executionHistory.lock and b/.gradle/8.13/executionHistory/executionHistory.lock differ
diff --git a/.gradle/8.13/fileHashes/fileHashes.bin b/.gradle/8.13/fileHashes/fileHashes.bin
index 5d176c2..6ff3adc 100644
Binary files a/.gradle/8.13/fileHashes/fileHashes.bin and b/.gradle/8.13/fileHashes/fileHashes.bin differ
diff --git a/.gradle/8.13/fileHashes/fileHashes.lock b/.gradle/8.13/fileHashes/fileHashes.lock
index 23857b8..85e5057 100644
Binary files a/.gradle/8.13/fileHashes/fileHashes.lock and b/.gradle/8.13/fileHashes/fileHashes.lock differ
diff --git a/.gradle/8.13/fileHashes/resourceHashesCache.bin b/.gradle/8.13/fileHashes/resourceHashesCache.bin
index 6083891..d6e9e8d 100644
Binary files a/.gradle/8.13/fileHashes/resourceHashesCache.bin and b/.gradle/8.13/fileHashes/resourceHashesCache.bin differ
diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock
index 5481a74..11ad109 100644
Binary files a/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ
diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe
index 71c0a1d..7e500f0 100644
Binary files a/.gradle/file-system.probe and b/.gradle/file-system.probe differ
diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml
deleted file mode 100644
index e112b74..0000000
--- a/.idea/caches/deviceStreaming.xml
+++ /dev/null
@@ -1,2241 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index cb69049..70f6ed5 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,10 +4,10 @@
-
+
-
+
diff --git a/app/build.gradle b/app/build.gradle
index 5bc9936..c84f781 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -85,6 +85,7 @@ dependencies {
implementation libs.material
implementation libs.appcompat
implementation libs.luban
+ implementation 'io.noties.markwon:core:4.6.2'
testImplementation libs.junit
androidTestImplementation libs.androidx.junit
androidTestImplementation libs.androidx.espresso.core
diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/customer/ChatImageLoader.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/customer/ChatImageLoader.kt
new file mode 100644
index 0000000..55e7e4f
--- /dev/null
+++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/customer/ChatImageLoader.kt
@@ -0,0 +1,69 @@
+package com.ck.ckcollar.app.ui.act.customer
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.widget.ImageView
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.DataSource
+import com.bumptech.glide.load.engine.GlideException
+import com.bumptech.glide.request.RequestListener
+import com.bumptech.glide.request.target.Target
+import com.ck.ckcollar.app.R
+
+object ChatImageLoader {
+ fun load(context: Context, imageView: ImageView, imageUrl: String) {
+ imageView.tag = imageUrl
+ resetSize(imageView)
+ Glide.with(context)
+ .load(imageUrl)
+ .placeholder(R.mipmap.image_default)
+ .listener(object : RequestListener {
+ override fun onLoadFailed(
+ e: GlideException?,
+ model: Any?,
+ target: Target?,
+ isFirstResource: Boolean
+ ): Boolean = false
+
+ override fun onResourceReady(
+ resource: Drawable,
+ model: Any,
+ target: Target?,
+ dataSource: DataSource,
+ isFirstResource: Boolean
+ ): Boolean {
+ if (imageView.tag == imageUrl) {
+ setSize(imageView, resource.intrinsicWidth, resource.intrinsicHeight)
+ }
+ return false
+ }
+ })
+ .into(imageView)
+ }
+
+ private fun resetSize(imageView: ImageView) {
+ val density = imageView.resources.displayMetrics.density
+ imageView.layoutParams = imageView.layoutParams.apply {
+ width = maxWidth(imageView, density)
+ height = (200 * density).toInt()
+ }
+ }
+
+ private fun setSize(imageView: ImageView, sourceWidth: Int, sourceHeight: Int) {
+ if (sourceWidth <= 0 || sourceHeight <= 0) return
+ val density = imageView.resources.displayMetrics.density
+ val maxWidth = maxWidth(imageView, density).toFloat()
+ val maxHeight = 200 * density
+ val scale = minOf(1f, maxWidth / sourceWidth, maxHeight / sourceHeight)
+ imageView.layoutParams = imageView.layoutParams.apply {
+ width = (sourceWidth * scale).toInt().coerceAtLeast(1)
+ height = (sourceHeight * scale).toInt().coerceAtLeast(1)
+ }
+ }
+
+ private fun maxWidth(imageView: ImageView, density: Float): Int {
+ val requestedWidth = (200 * density).toInt()
+ val availableWidth = imageView.resources.displayMetrics.widthPixels - (155 * density).toInt()
+ return minOf(requestedWidth, availableWidth.coerceAtLeast(1))
+ }
+}
diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/customer/CustomerActivity.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/customer/CustomerActivity.kt
index dbc6f32..4b33cc6 100644
--- a/app/src/main/java/com/ck/ckcollar/app/ui/act/customer/CustomerActivity.kt
+++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/customer/CustomerActivity.kt
@@ -58,9 +58,50 @@ import com.tt.kit.model.DataCollection
import com.tt.kit.utils.KeyboardUtils
import com.tt.kit.utils.TTLog
import com.tt.kit.utils.TTUtils
+import io.noties.markwon.Markwon
import java.io.File
import java.util.UUID
+private object CustomerMarkdownDetector {
+ private val blockMarkdown = Regex(
+ "(?m)^(?:\\s{0,3}(?:#{1,6}\\s+|[-*+]\\s+|>\\s+|\\d+\\.\\s+|```|~~~|(?:-{3,}|_{3,}|\\*{3,})\\s*$))"
+ )
+ private val inlineMarkdown = Regex("(?:\\*\\*[^\\n]+?\\*\\*|`[^\\n]+?`|\\[[^\\]]+?\\]\\([^\\s)]+?\\))")
+
+ fun isMarkdown(content: String): Boolean = content.isNotBlank() &&
+ (blockMarkdown.containsMatchIn(content) || inlineMarkdown.containsMatchIn(content))
+
+ fun normalize(content: String): String {
+ val normalized = content
+ // SSE chunks contain JSON-escaped line breaks; Markwon otherwise renders
+ // "\\n\\n" as "nn" and keeps the first list item inside the heading.
+ .replace("\\r\\n", "\n")
+ .replace("\\n", "\n")
+ // AI responses commonly omit the required spaces after block markers.
+ .replace(Regex("(?m)^(\\s{0,3}#{1,6})(?=\\S)"), "$1 ")
+ .replace(Regex("(?m)^(\\s{0,3}>)(?=\\S)"), "$1 ")
+ .replace(Regex("(?m)^(\\s{0,3}[-+])(?!\\s)(?=\\S)"), "$1 ")
+ .replace(Regex("(?m)^(\\s{0,3}\\*)(?![\\s*])(?=\\S)"), "$1 ")
+ .replace(Regex("(?m)^(\\s{0,3}\\d+\\.)(?=\\S)"), "$1 ")
+ // Split compact numbered sections such as "。2.**标题**" into list rows.
+ .replace(Regex("(\\d+)\\.\\s*(?=\\*\\*)"), "$1. ")
+ .replace(Regex("([^\\n])(?=\\d+\\. \\*\\*)")) { match ->
+ match.value + "\n"
+ }
+ .replace(Regex("([^\\n])(?=免责声明[::])")) { match ->
+ match.value + "\n\n"
+ }
+
+ // TextView has no CSS-like heading margins, so preserve a blank line
+ // after Markdown headings for readable streamed responses.
+ return normalized.replace(
+ Regex("(?m)^(\\s{0,3}#{1,6}\\s+[^\\n]+)\\n(?!\\n)")
+ ) { match ->
+ match.groupValues[1] + "\n\n"
+ }
+ }
+}
+
class CustomerActivity : CKBaseActivity() {
lateinit var binding: ActivityCustomerBinding
@@ -340,6 +381,7 @@ class CustomerActivity : CKBaseActivity() {
fun sendMessage(uuid :String ,content: String,isResend : Boolean, imageUrl: String) {
isAiResponding = true
+ val rawStreamResponse = StringBuilder()
if (!TextUtils.isEmpty(imageUrl)) {
this@CustomerActivity.data.add(CustomerMessageModel.MessageItemModel().apply {
@@ -373,6 +415,8 @@ class CustomerActivity : CKBaseActivity() {
}, object : ICallback {
override fun onSuccess(data: String) {
+ rawStreamResponse.append(data)
+
// var model = Gson().fromJson(data, AIMessageModel::class.java)
var flag = false
@@ -415,6 +459,7 @@ class CustomerActivity : CKBaseActivity() {
override fun onFailed(code: String, msg: String) {
isAiResponding = false
lock = true
+ TTLog.e("AI_STREAM_FULL[$uuid][FAILED]: $rawStreamResponse")
// toast(msg)
//TTLog.e(msg)
}
@@ -422,6 +467,7 @@ class CustomerActivity : CKBaseActivity() {
override fun onComplete() {
isAiResponding = false
lock = true
+ TTLog.e("AI_STREAM_FULL[$uuid]: $rawStreamResponse")
}
})
@@ -436,6 +482,7 @@ class CustomerActivity : CKBaseActivity() {
fun sendMessage2(uuid :String ,content: String,isResend : Boolean, imageUrl: String) {
isAiResponding = true
+ val rawStreamResponse = StringBuilder()
lock = false
if (!TextUtils.isEmpty(content) && !isResend) {
this@CustomerActivity.data.add(CustomerMessageModel.MessageItemModel().apply {
@@ -460,6 +507,8 @@ class CustomerActivity : CKBaseActivity() {
}, object : ICallback {
override fun onSuccess(data: String) {
+ rawStreamResponse.append(data)
+
// var model = Gson().fromJson(data, AIMessageModel::class.java)
var flag = false
@@ -502,6 +551,7 @@ class CustomerActivity : CKBaseActivity() {
override fun onFailed(code: String, msg: String) {
isAiResponding = false
lock = true
+ TTLog.e("AI_STREAM_FULL[$uuid][FAILED]: $rawStreamResponse")
// toast(msg)
//TTLog.e(msg)
}
@@ -509,6 +559,7 @@ class CustomerActivity : CKBaseActivity() {
override fun onComplete() {
isAiResponding = false
lock = true
+ TTLog.e("AI_STREAM_FULL[$uuid]: $rawStreamResponse")
}
})
@@ -707,6 +758,8 @@ class CustomerActivity : CKBaseActivity() {
var data: List,
var onItemClickListenner: OnItemClickListenner
) : RecyclerView.Adapter() {
+ private val markwon = Markwon.create(context)
+
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
@@ -731,7 +784,16 @@ class CustomerActivity : CKBaseActivity() {
holder.binding.contentTo.visibility = View.GONE
holder.binding.contentFrom.visibility = View.VISIBLE
- holder.binding.textFrom.text = model.content
+ val content = model.content.orEmpty()
+ val normalizedContent = CustomerMarkdownDetector.normalize(content)
+ if (CustomerMarkdownDetector.isMarkdown(normalizedContent)) {
+ markwon.setMarkdown(
+ holder.binding.textFrom,
+ normalizedContent
+ )
+ } else {
+ holder.binding.textFrom.text = content
+ }
holder.binding.textFromNick.text = model.senderName
Glide.with(context)
diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/customer/CustomerActivity_copy.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/customer/CustomerActivity_copy.kt
new file mode 100644
index 0000000..3a5df83
--- /dev/null
+++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/customer/CustomerActivity_copy.kt
@@ -0,0 +1,891 @@
+package com.ck.ckcollar.app.ui.act.customer
+
+import android.Manifest
+import android.content.Context
+import android.content.Intent
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.os.Environment
+import android.os.Handler
+import android.os.Looper
+import android.provider.MediaStore
+import android.text.Editable
+import android.text.InputType
+import android.text.TextUtils
+import android.text.TextWatcher
+import android.view.KeyEvent
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputMethodManager
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.core.content.FileProvider
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.DataSource
+import com.bumptech.glide.load.engine.GlideException
+import com.bumptech.glide.request.RequestListener
+import com.bumptech.glide.request.target.Target
+import com.ck.ckcollar.app.CKApp
+import com.ck.ckcollar.app.R
+import com.ck.ckcollar.app.action.ActionCode
+import com.ck.ckcollar.app.databinding.ActivityCustomerBinding
+import com.ck.ckcollar.app.databinding.LayoutItemAiCustomerMessageBinding
+import com.ck.ckcollar.app.databinding.LayoutItemCustomerMessageBinding
+import com.ck.ckcollar.app.listenner.OnItemClickListenner
+import com.ck.ckcollar.app.model.AIMessageModel
+import com.ck.ckcollar.app.model.CustomerMessageModel
+import com.ck.ckcollar.app.model.UploadModel
+import com.ck.ckcollar.app.ui.act.petmanager.ImageViewActivity
+import com.ck.ckcollar.app.ui.base.CKBaseActivity
+import com.ck.ckcollar.app.ui.dialog.CKAlertDialog
+import com.ck.ckcollar.app.utils.CKUtils
+import com.ck.ckcollar.app.utils.Const
+import com.ck.ckcollar.app.utils.LubanCompressUtil
+import com.ck.ckcollar.app.utils.networkone.ICallback
+import com.ck.ckcollar.app.utils.networkone.TTHttpClient
+import com.ck.ckcollar.app.utils.networkone.model.TTRequestData
+import com.ck.ckcollar.app.utils.networkone.model.TTRequestParam
+import com.cyclone.shadowsocks.utils.PreferencesUtils
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import com.tt.kit.base.TTBaseActivity
+import com.tt.kit.model.Data
+import com.tt.kit.model.DataCollection
+import com.tt.kit.utils.KeyboardUtils
+import com.tt.kit.utils.TTLog
+import com.tt.kit.utils.TTUtils
+import java.io.File
+import java.util.UUID
+
+class CustomerActivity_copy : CKBaseActivity() {
+
+ lateinit var binding: ActivityCustomerBinding
+ lateinit var customerMessageAdapter: CustomerMessageAdapter
+ var data = mutableListOf()
+
+ var path = ""
+ private var isSending = false
+ private var isAiResponding = false
+ var url = ""
+
+ var lock = true
+ override fun onCreate(savedInstanceState: Bundle?) {
+ binding = ActivityCustomerBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ super.onCreate(savedInstanceState)
+
+ setTitleText(getString(R.string.customer_service4))
+
+ setRightText(getString(R.string.clear))
+ }
+
+ override fun initView() {
+ binding.recyclerView.layoutManager =
+ LinearLayoutManager(this@CustomerActivity_copy, RecyclerView.VERTICAL, false)
+ binding.recyclerView.setHasFixedSize(true)
+ customerMessageAdapter =
+ CustomerMessageAdapter(this@CustomerActivity_copy, data, object : OnItemClickListenner {
+ override fun onClick(
+ pos: Int,
+ model: Any
+ ) {
+
+ }
+ })
+ binding.recyclerView.adapter = customerMessageAdapter
+
+ binding.editMessage.setHorizontallyScrolling(false);
+ binding.btnSend.setOnClickListener {
+ if (isAiResponding) {
+ toast("请等待当前问题回答结束")
+ return@setOnClickListener
+ }
+ isSending = true
+ // Reset the action controls immediately. The send flow captures the
+ // submitted text and image path before clearing the visible draft.
+ binding.btnSend.visibility = View.GONE
+ binding.btnMore.visibility = View.VISIBLE
+ binding.editMessage.onEditorAction(EditorInfo.IME_ACTION_SEND)
+ }
+ updateSendControls()
+ // Use the traditional multiline keyboard action; sending is handled by the
+ // activity's own message flow rather than the IME action button.
+ binding.editMessage.setSingleLine(false)
+ binding.editMessage.imeOptions = EditorInfo.IME_ACTION_NONE
+ binding.editMessage.setRawInputType(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE)
+ // setSingleLine(false) resets the XML maxLines value, so apply it last.
+ binding.editMessage.maxLines = 4
+ binding.editMessage.maxHeight = (70 * resources.displayMetrics.density).toInt()
+ binding.editMessage.isVerticalScrollBarEnabled = true
+
+ binding.editMessage.setOnEditorActionListener(object : TextView.OnEditorActionListener {
+ override fun onEditorAction(
+ p0: TextView?,
+ actionId: Int,
+ ketEvent: KeyEvent?
+ ): Boolean {
+ if (actionId == EditorInfo.IME_ACTION_SEND) {
+ if (lock) {
+ // Whitespace-only input is not a message. A selected image may still be sent alone.
+ val text = binding.editMessage.text.toString().trim()
+ if (text.isNotEmpty() || path.isNotEmpty()) {
+ lock = false
+ KeyboardUtils.hideKeyboard(this@CustomerActivity_copy)
+ binding.editMessage.clearFocus()
+ handler.post {
+ if (!TextUtils.isEmpty(path)) {
+ uploadImage(text, path)
+ } else {
+ sendMessage(UUID.randomUUID().toString(), text, false, "")
+ }
+ clearInputViews()
+ }
+ }
+ }
+ }
+ return false
+ }
+ })
+ binding.editMessage.setOnClickListener {
+ binding.contentBottom.visibility = View.GONE
+ }
+ binding.editMessage.onFocusChangeListener = object : View.OnFocusChangeListener{
+ override fun onFocusChange(p0: View?, p1: Boolean) {
+ if (p1){
+ binding.contentBottom.visibility = View.GONE
+ }
+ }
+ }
+ binding.editMessage.addTextChangedListener(object : TextWatcher{
+ override fun afterTextChanged(p0: Editable?) {
+
+ }
+
+ override fun beforeTextChanged(
+ p0: CharSequence?,
+ p1: Int,
+ p2: Int,
+ p3: Int
+ ) {
+
+ }
+
+ override fun onTextChanged(
+ p0: CharSequence?,
+ p1: Int,
+ p2: Int,
+ p3: Int
+ ) {
+ if (p0?.toString()?.trim()?.isNotEmpty() == true) isSending = false
+ updateSendControls()
+ }
+ })
+ binding.btnMore.setOnClickListener {
+ if (binding.contentBottom.visibility == View.GONE) {
+ val inputMethodManager =
+ getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
+ inputMethodManager.hideSoftInputFromWindow(binding.editMessage.windowToken, 0)
+ binding.editMessage.clearFocus()
+ binding.contentBottom.visibility = View.VISIBLE
+ } else {
+ binding.contentBottom.visibility = View.GONE
+ }
+ }
+ binding.btnSmile.setOnClickListener {
+ binding.contentBottom.visibility = View.GONE
+ binding.editMessage.requestFocus()
+ KeyboardUtils.showKeyboard(binding.editMessage)
+ }
+ binding.btnDeleteImage.setOnClickListener {
+ clearSelectedImage()
+ }
+ //拍照
+ binding.conetnt1.setOnClickListener {
+
+ if (TextUtils.isEmpty(path)) {
+ getPermissionRequst(
+ Manifest.permission.CAMERA,
+ REQUEST_CAMERA_PERMISSION
+ )
+ } else {
+ toast(getString(R.string.only_one_photo))
+ }
+
+ }
+ //相册
+ binding.conetnt2.setOnClickListener {
+
+ if (TextUtils.isEmpty(path)) {
+ val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ Manifest.permission.READ_MEDIA_IMAGES
+ } else {
+ Manifest.permission.READ_EXTERNAL_STORAGE
+ }
+ getPermissionRequst(
+ permission,
+ REQUEST_PHOTO_PERMISSION
+ )
+ } else {
+ toast(getString(R.string.only_one_photo))
+ }
+
+ }
+
+ Handler(Looper.getMainLooper()).post {
+ binding.recyclerView.scrollToPosition(this@CustomerActivity_copy.data.size - 1)
+ binding.recyclerView.post {
+ if (customerMessageAdapter.itemCount > 0) {
+ val lastView = binding.recyclerView.layoutManager?.findViewByPosition(customerMessageAdapter.itemCount - 1)
+ lastView?.let {
+ val bottomOffset = it.bottom - binding.recyclerView.height
+ binding.recyclerView.scrollBy(0, bottomOffset)
+ }
+ }
+ }
+ }
+ handleMessage()
+ binding.btnSmile.visibility = View.GONE;
+ }
+
+ override fun onPermissionsGranted(requestCode: Int, permissions: Array) {
+ when (requestCode) {
+ REQUEST_CAMERA_PERMISSION -> takePhoto()
+ REQUEST_PHOTO_PERMISSION -> {
+ val intent = Intent(
+ Intent.ACTION_PICK,
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+ ).apply {
+ setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*")
+ }
+ startActivityForResult(intent, 56)
+ }
+ }
+ }
+
+ override fun initData() {
+
+ //getList()
+ var dataStr = PreferencesUtils.getString(this, Const.MESSAGE,"[]")
+
+ var models = Gson().fromJson>(dataStr,object : TypeToken>(){}.type)
+
+ var data2 = models.stream().filter { it-> !TextUtils.isEmpty(it.url) || !TextUtils.isEmpty(it.content) }.toList()
+ data.addAll(data2)
+
+ }
+
+ fun handleMessage(){
+ if (data.size > 0){
+ var model = data.last()
+ var isResend = false
+ var sendConetnt = ""
+ if (model.senderId == CKApp.app.userInfo?.id){
+ isResend = true
+ if (!TextUtils.isEmpty(model.url)){
+ url = model.url.toString()
+ }else{
+ sendConetnt = model.content.toString()
+ }
+ }else{
+ if (model.content!!.indexOf("不能替代兽医诊断") < 0){
+ isResend = true
+ if (data.size - 2 >= 0){
+ if (!TextUtils.isEmpty(model.url)){
+ url = data.get(data.size - 2).url.toString()
+ }else{
+ sendConetnt = data.get(data.size - 2).content.toString()
+ }
+ }
+ data.removeAt(data.size -1)
+ }
+ }
+
+ if (isResend){
+ customerMessageAdapter.data =data
+ customerMessageAdapter.notifyDataSetChanged()
+
+ if (!TextUtils.isEmpty(sendConetnt) || !TextUtils.isEmpty(url)){
+ handler.post {
+ sendMessage2(UUID.randomUUID().toString(),sendConetnt,true, url)
+ }
+ }
+ }
+ }
+ }
+
+ var handler = object : Handler(Looper.getMainLooper()){
+
+ }
+ override fun rightButton1OnClick() {
+ CKAlertDialog(this,getString(R.string.alert),getString(R.string.clear_alert),object : CKAlertDialog.AlertClickListener{
+
+ override fun onBack(isConfirm: Boolean) {
+
+ if (isConfirm){
+ lock = true
+ PreferencesUtils.putString(this@CustomerActivity_copy, Const.MESSAGE,"[]")
+ data.clear()
+
+ customerMessageAdapter.data = data
+ customerMessageAdapter.notifyDataSetChanged()
+ }
+ }
+ }).show()
+ }
+
+ fun sendMessage(uuid :String ,content: String,isResend : Boolean, imageUrl: String) {
+
+ isAiResponding = true
+
+ if (!TextUtils.isEmpty(imageUrl)) {
+ this@CustomerActivity_copy.data.add(CustomerMessageModel.MessageItemModel().apply {
+ senderId = CKApp.app.userInfo?.id!!
+ this.content = content
+ this.senderAvatar = CKApp.app.userInfo?.avatar
+ this.senderName = CKApp.app.userInfo?.custName ?: "我"
+ this.url = imageUrl
+ })
+ }
+ if (!TextUtils.isEmpty(content) && !isResend) {
+ this@CustomerActivity_copy.data.add(CustomerMessageModel.MessageItemModel().apply {
+ senderId = CKApp.app.userInfo?.id!!
+ this.content = content
+ this.senderAvatar = CKApp.app.userInfo?.avatar
+ this.senderName = CKApp.app.userInfo?.custName ?: "我"
+ })
+ }
+ this@CustomerActivity_copy.data.add(CustomerMessageModel.MessageItemModel().apply {
+ senderId = -1
+ this.content = ""
+ this.senderName = getString(R.string.ai_doctor)
+ this.uuid = uuid
+ })
+
+
+ TTHttpClient.postJsonStream3(this, "发送会话消息stream", TTRequestParam().apply {
+ add(TTRequestData("imageUrl", imageUrl))
+
+ add(TTRequestData("question", content))
+ }, object : ICallback {
+ override fun onSuccess(data: String) {
+
+ // var model = Gson().fromJson(data, AIMessageModel::class.java)
+
+ var flag = false
+ for (i in 0..this@CustomerActivity_copy.data.size -1){
+ if (this@CustomerActivity_copy.data.get(i).uuid.equals(uuid)){
+ flag = true
+ this@CustomerActivity_copy.data.get(i).content = CKUtils.subMessage(this@CustomerActivity_copy.data.get(i).content + data)
+
+ }
+ }
+ if (!flag){
+ this@CustomerActivity_copy.data.add(CustomerMessageModel.MessageItemModel().apply {
+ senderId = -1
+ this.content =data
+ // this.senderAvatar = CKApp.app.userInfo?.avatar
+ this.senderName = getString(R.string.ai_doctor)
+ this.uuid = uuid
+ })
+ }
+
+
+ customerMessageAdapter.data = this@CustomerActivity_copy.data
+ customerMessageAdapter.notifyDataSetChanged()
+
+ if (this@CustomerActivity_copy.data.last().content!!.indexOf("不能替代兽医诊断") > 0) {
+ binding.recyclerView.scrollToPosition(this@CustomerActivity_copy.data.size - 1)
+ binding.recyclerView.post {
+ if (customerMessageAdapter.itemCount > 0) {
+ val lastView = binding.recyclerView.layoutManager?.findViewByPosition(customerMessageAdapter.itemCount - 1)
+ lastView?.let {
+ val bottomOffset = it.bottom - binding.recyclerView.height
+ binding.recyclerView.scrollBy(0, bottomOffset)
+ }
+ }
+ }
+ }
+
+ }
+
+ override fun onFailed(code: String, msg: String) {
+ isAiResponding = false
+ lock = true
+ // toast(msg)
+ //TTLog.e(msg)
+ }
+
+ override fun onComplete() {
+ isAiResponding = false
+ lock = true
+ }
+ })
+
+ customerMessageAdapter.data = this@CustomerActivity_copy.data
+ customerMessageAdapter.notifyDataSetChanged()
+
+ binding.recyclerView.scrollToPosition(this@CustomerActivity_copy.data.size - 1)
+
+ }
+
+
+
+ fun sendMessage2(uuid :String ,content: String,isResend : Boolean, imageUrl: String) {
+ isAiResponding = true
+ lock = false
+ if (!TextUtils.isEmpty(content) && !isResend) {
+ this@CustomerActivity_copy.data.add(CustomerMessageModel.MessageItemModel().apply {
+ senderId = CKApp.app.userInfo?.id!!
+ this.content = content
+ this.senderAvatar = CKApp.app.userInfo?.avatar
+ this.senderName = CKApp.app.userInfo?.custName ?: "我"
+ })
+ }
+ this@CustomerActivity_copy.data.add(CustomerMessageModel.MessageItemModel().apply {
+ senderId = -1
+ this.content = ""
+ this.senderName = getString(R.string.ai_doctor)
+ this.uuid = uuid
+ })
+
+
+ TTHttpClient.postJsonStream3(this, "发送会话消息stream", TTRequestParam().apply {
+ add(TTRequestData("imageUrl", imageUrl))
+
+ add(TTRequestData("question", content))
+ }, object : ICallback {
+ override fun onSuccess(data: String) {
+
+ // var model = Gson().fromJson(data, AIMessageModel::class.java)
+
+ var flag = false
+ for (i in 0..this@CustomerActivity_copy.data.size -1){
+ if (this@CustomerActivity_copy.data.get(i).uuid.equals(uuid)){
+ flag = true
+ this@CustomerActivity_copy.data.get(i).content = CKUtils.subMessage(this@CustomerActivity_copy.data.get(i).content + data)
+
+ }
+ }
+ if (!flag){
+ this@CustomerActivity_copy.data.add(CustomerMessageModel.MessageItemModel().apply {
+ senderId = -1
+ this.content =data
+ // this.senderAvatar = CKApp.app.userInfo?.avatar
+ this.senderName = getString(R.string.ai_doctor)
+ this.uuid = uuid
+ })
+ }
+
+
+ customerMessageAdapter.data = this@CustomerActivity_copy.data
+ customerMessageAdapter.notifyDataSetChanged()
+
+ if (this@CustomerActivity_copy.data.last().content!!.indexOf("不能替代兽医诊断") > 0) {
+ binding.recyclerView.scrollToPosition(this@CustomerActivity_copy.data.size - 1)
+ binding.recyclerView.post {
+ if (customerMessageAdapter.itemCount > 0) {
+ val lastView = binding.recyclerView.layoutManager?.findViewByPosition(customerMessageAdapter.itemCount - 1)
+ lastView?.let {
+ val bottomOffset = it.bottom - binding.recyclerView.height
+ binding.recyclerView.scrollBy(0, bottomOffset)
+ }
+ }
+ }
+ }
+
+ }
+
+ override fun onFailed(code: String, msg: String) {
+ isAiResponding = false
+ lock = true
+ // toast(msg)
+ //TTLog.e(msg)
+ }
+
+ override fun onComplete() {
+ isAiResponding = false
+ lock = true
+ }
+ })
+
+ customerMessageAdapter.data = this@CustomerActivity_copy.data
+ customerMessageAdapter.notifyDataSetChanged()
+
+ binding.recyclerView.scrollToPosition(this@CustomerActivity_copy.data.size - 1)
+
+ }
+
+
+ fun uploadImage(content: String, imagePath: String) {
+
+ var param = TTRequestParam()
+ param.files.add(File(imagePath))
+
+ showProgeassDialog()
+ TTHttpClient.postFile(this, "上传照片s", param, object : ICallback {
+ override fun onSuccess(data: String) {
+ hiddenProgeassDialog()
+ var model = Gson().fromJson(data, UploadModel::class.java)
+
+ handler.post {
+ sendMessage(UUID.randomUUID().toString(), content, false, model.urls)
+ }
+
+ }
+
+ override fun onFailed(code: String, msg: String) {
+ hiddenProgeassDialog()
+ lock = true
+ if (path.isEmpty() && binding.editMessage.text.toString().isEmpty()) {
+ path = imagePath
+ showImagePreview(imagePath)
+ binding.editMessage.setText(content)
+ }
+ toast(msg)
+ }
+ })
+ }
+
+ fun getList() {
+ TTHttpClient.get(this, "会话消息列表", TTRequestParam(), object : ICallback {
+ override fun onSuccess(data: String) {
+// var model = Gson().fromJson(data,CustomerMessageModel::class.java)
+// model.rows?.let {
+// this@CustomerActivity.data.addAll(model.rows!!)
+// }
+ var model = Gson().fromJson>(
+ data,
+ object : TypeToken>() {}.type
+ )
+
+ this@CustomerActivity_copy.data.addAll(model)
+ customerMessageAdapter.data = this@CustomerActivity_copy.data
+ customerMessageAdapter.notifyDataSetChanged()
+ }
+
+ override fun onFailed(code: String, msg: String) {
+
+ }
+ })
+ }
+
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+
+ if (requestCode == 55 && resultCode == RESULT_OK) {
+ path = PreferencesUtils.getString(this@CustomerActivity_copy, Const.IMAGE_PATH, "")!!
+ prepareSelectedImage()
+ } else if (requestCode == 56 && resultCode == RESULT_OK) {
+ path = TTUtils.getImageUrl(this, data!!.getData());
+ prepareSelectedImage()
+ }
+
+ }
+
+ private fun prepareSelectedImage() {
+ if (TextUtils.isEmpty(path)) return
+
+ LubanCompressUtil.getCompressFile(
+ this,
+ File(path),
+ object : LubanCompressUtil.CompressCallback {
+ override fun success(file: File) {
+ if (isFinishing || isDestroyed) return
+ path = file.absolutePath
+ lock = true
+ binding.contentBottom.visibility = View.GONE
+ showImagePreview(path)
+ binding.editMessage.requestFocus()
+ KeyboardUtils.showKeyboard(binding.editMessage)
+ // Rebind the IME after selecting an image so its action button is
+ // available even though the text field itself is empty.
+ binding.editMessage.postDelayed({
+ val imm = getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager
+ imm?.restartInput(binding.editMessage)
+ }, 150L)
+ }
+
+ override fun fail(failMsg: String) {
+ if (isFinishing || isDestroyed) return
+ lock = true
+ path = ""
+ toast(failMsg)
+ }
+ }
+ )
+ }
+
+ private fun showImagePreview(imagePath: String) {
+ isSending = false
+ binding.contentImagePreview.visibility = View.VISIBLE
+ updateSendControls()
+ binding.imagePreview.scaleType = ImageView.ScaleType.CENTER_CROP
+ Glide.with(this)
+ .load(imagePath)
+ .into(binding.imagePreview)
+ }
+
+ private fun clearSelectedImage() {
+ path = ""
+ url = ""
+ binding.imagePreview.setImageDrawable(null)
+ binding.contentImagePreview.visibility = View.GONE
+ updateSendControls()
+ }
+
+ private fun clearInputViews() {
+ binding.editMessage.setText("")
+ path = ""
+ url = ""
+ binding.imagePreview.setImageDrawable(null)
+ binding.contentImagePreview.visibility = View.GONE
+ // Sending has already been initiated; keep the controls in their idle state
+ // until the user starts composing the next draft.
+ binding.btnSend.visibility = View.GONE
+ binding.btnMore.visibility = View.VISIBLE
+ }
+
+ private fun updateSendControls() {
+ val hasImage = path.isNotEmpty()
+ val hasText = binding.editMessage.text.toString().trim().isNotEmpty()
+ if (isSending) {
+ binding.btnSend.visibility = View.GONE
+ binding.btnMore.visibility = View.VISIBLE
+ return
+ }
+ binding.btnSend.visibility = if (hasImage || hasText) View.VISIBLE else View.GONE
+ binding.btnMore.visibility = if (hasImage) View.GONE else View.VISIBLE
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+
+ PreferencesUtils.putString(this, Const.MESSAGE,Gson().toJson(data))
+ }
+
+ fun takePhoto() {
+ try {
+ val openCameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
+
+ val vFile = File(
+ getExternalFilesDir(Environment.DIRECTORY_PICTURES),
+ "${System.currentTimeMillis()}.png"
+ )
+
+ val parentDir = vFile.parentFile
+ if (parentDir != null && !parentDir.exists()) {
+ parentDir.mkdirs()
+ }
+ TTLog.e(vFile.absolutePath)
+ PreferencesUtils.putString(getApplicationContext(), Const.IMAGE_PATH, vFile.absolutePath)
+
+ val cameraUri: Uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ FileProvider.getUriForFile(
+ this@CustomerActivity_copy,
+ packageName + ".fileprovider",
+ vFile
+ )
+ } else {
+ Uri.fromFile(vFile)
+ }
+
+ openCameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraUri)
+ startActivityForResult(openCameraIntent, 55)
+ }catch (e: Exception){
+ e.printStackTrace()
+ }
+
+ }
+
+ class CustomerMessageAdapter(
+ var context: TTBaseActivity,
+ var data: List,
+ var onItemClickListenner: OnItemClickListenner
+ ) : RecyclerView.Adapter() {
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): CustomerMessageHolder {
+ return CustomerMessageHolder(
+ LayoutItemAiCustomerMessageBinding.inflate(
+ LayoutInflater.from(
+ context
+ ), parent, false
+ )
+ )
+ }
+
+ override fun onBindViewHolder(
+ holder: CustomerMessageHolder,
+ position: Int
+ ) {
+ // holder.binding.text.text = data.get(position).name
+
+ var model = data.get(position)
+ if (model.senderId != CKApp.app.userInfo?.id) {
+ holder.binding.contentTo.visibility = View.GONE
+ holder.binding.contentFrom.visibility = View.VISIBLE
+
+ holder.binding.textFrom.text = model.content
+ holder.binding.textFromNick.text = model.senderName
+
+ Glide.with(context)
+ .load(model.senderAvatar)
+ .placeholder(R.mipmap.image_ai_doctor)
+ .into(holder.binding.imageFrom)
+
+ holder.binding.imageContentTo.setOnClickListener(imageClick)
+
+ if (TextUtils.isEmpty(model.content)){
+ holder.binding.textFrom.visibility= View.GONE
+ holder.binding.progress.visibility= View.VISIBLE
+ }else{
+ holder.binding.textFrom.visibility= View.VISIBLE
+ holder.binding.progress.visibility= View.GONE
+ }
+
+ } else {
+ holder.binding.contentTo.visibility = View.VISIBLE
+ holder.binding.contentFrom.visibility = View.GONE
+
+ holder.binding.textTo.text = model.content
+ holder.binding.textToNick.text = model.senderName
+
+ Glide.with(context)
+ .load(model.senderAvatar)
+ .placeholder(R.mipmap.image_default)
+ .into(holder.binding.imageTo)
+ if (!TextUtils.isEmpty(model.url)) {
+ holder.binding.textTo.visibility = View.GONE
+ holder.binding.imageContentTo.visibility = View.VISIBLE
+ val imageView = holder.binding.imageContentTo
+ val imageUrl = model.url!!
+ imageView.setTag(imageUrl)
+ resetImageContentSize(imageView)
+
+ Glide.with(context)
+ .load(imageUrl)
+ .placeholder(R.mipmap.image_default)
+ .listener(object : RequestListener {
+ override fun onLoadFailed(
+ e: GlideException?,
+ model: Any?,
+ target: Target?,
+ isFirstResource: Boolean
+ ): Boolean = false
+
+ override fun onResourceReady(
+ resource: Drawable,
+ model: Any,
+ target: Target?,
+ dataSource: DataSource,
+ isFirstResource: Boolean
+ ): Boolean {
+ if (imageView.tag == imageUrl) {
+ setImageContentSize(
+ imageView,
+ resource.intrinsicWidth,
+ resource.intrinsicHeight
+ )
+ }
+ return false
+ }
+ })
+ .into(imageView)
+
+ imageView.setOnClickListener(imageClick)
+ } else {
+ holder.binding.textTo.visibility = View.VISIBLE
+ holder.binding.imageContentTo.visibility = View.GONE
+ }
+ }
+
+
+ holder.binding.root.setTag(position)
+ holder.binding.root.setOnClickListener {
+ val position = it.tag as Int
+ onItemClickListenner.onClick(position, data.get(position))
+ }
+ }
+
+ private fun resetImageContentSize(imageView: View) {
+ val density = imageView.resources.displayMetrics.density
+ imageView.layoutParams = imageView.layoutParams.apply {
+ width = getMaxImageWidth(density)
+ height = (200 * density).toInt()
+ }
+ }
+
+ private fun setImageContentSize(imageView: View, sourceWidth: Int, sourceHeight: Int) {
+ if (sourceWidth <= 0 || sourceHeight <= 0) return
+
+ val density = imageView.resources.displayMetrics.density
+ val maxWidth = getMaxImageWidth(density).toFloat()
+ val maxHeight = (200 * density).toFloat()
+ val scale = minOf(
+ 1f,
+ maxWidth / sourceWidth.toFloat(),
+ maxHeight / sourceHeight.toFloat()
+ )
+
+ imageView.layoutParams = imageView.layoutParams.apply {
+ width = (sourceWidth * scale).toInt().coerceAtLeast(1)
+ height = (sourceHeight * scale).toInt().coerceAtLeast(1)
+ }
+ }
+
+ private fun getMaxImageWidth(density: Float): Int {
+ val requestedWidth = (200 * density).toInt()
+ // Reserve the avatar, paddings, and the bubble's left inset.
+ val availableWidth = context.resources.displayMetrics.widthPixels - (155 * density).toInt()
+ return minOf(requestedWidth, availableWidth.coerceAtLeast(1))
+ }
+
+ override fun getItemCount(): Int {
+ return data.size
+ }
+
+
+ class CustomerMessageHolder(var binding: LayoutItemAiCustomerMessageBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+
+ }
+
+
+ var imageClick = object : View.OnClickListener{
+ override fun onClick(v: View?) {
+ var url = v?.tag as String
+
+ var urls = mutableListOf()
+ var n = 0
+ var pos = 0
+ for (m in data){
+ if (!TextUtils.isEmpty(m.url)){
+ urls.add(m.url!!)
+ if (url.equals(m.url)){
+ pos = n
+ }
+ n++
+ }
+ }
+ context.toActivity(ImageViewActivity::class.java, DataCollection().apply {
+ add(Data("data",Gson().toJson(urls)))
+ add(Data("pos",pos.toString()))
+ })
+ }
+ }
+
+ }
+
+ companion object {
+ private const val REQUEST_CAMERA_PERMISSION = 1001
+ private const val REQUEST_PHOTO_PERMISSION = 1002
+ }
+}
diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/home/fragment/HealthFragment.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/home/fragment/HealthFragment.kt
index 3f2ca44..da274e9 100644
--- a/app/src/main/java/com/ck/ckcollar/app/ui/act/home/fragment/HealthFragment.kt
+++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/home/fragment/HealthFragment.kt
@@ -26,9 +26,6 @@ import com.ck.ckcollar.app.utils.networkone.TTHttpClient
import com.ck.ckcollar.app.utils.networkone.model.TTRequestData
import com.ck.ckcollar.app.utils.networkone.model.TTRequestParam
import com.google.gson.Gson
-import com.scwang.smartrefresh.layout.api.RefreshLayout
-import com.scwang.smartrefresh.layout.listener.OnLoadMoreListener
-import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener
import com.tt.kit.base.TTBaseActivity
import com.tt.kit.model.Data
import com.tt.kit.model.DataCollection
@@ -80,13 +77,11 @@ class HealthFragment: CKBaseFragment() {
}
binding.refreshLayout.setEnableLoadMore(false)
binding.refreshLayout.setOnRefreshListener {
- if (!CKApp.app.isUserLoggedIn) {
- binding.refreshLayout.finishRefresh()
- return@setOnRefreshListener
- }
page = 1
getData()
- getCustomerTime()
+ if (CKApp.app.isUserLoggedIn) {
+ getCustomerTime()
+ }
}
@@ -174,7 +169,8 @@ class HealthFragment: CKBaseFragment() {
override fun onFailed(code: String, msg: String) {
try {
toast(msg)
- binding.refreshLayout.finishLoadMore(true)
+ binding.refreshLayout.finishLoadMore(false)
+ binding.refreshLayout.finishRefresh(false)
}catch (e : Exception){
e.printStackTrace()
diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/home/fragment/HomeFragment.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/home/fragment/HomeFragment.kt
index c5cd8a7..11062fb 100644
--- a/app/src/main/java/com/ck/ckcollar/app/ui/act/home/fragment/HomeFragment.kt
+++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/home/fragment/HomeFragment.kt
@@ -119,6 +119,7 @@ class HomeFragment : CKBaseFragment() {
var isFirstShow = false
private var waitingForLoginResult = false
private var loadedLoginToken: String? = null
+ private var shouldRefreshPetListOnResume = false
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -267,7 +268,7 @@ class HomeFragment : CKBaseFragment() {
LiveDataBus.get().with("FIRST", Int::class.java)
.observe(viewLifecycleOwner, object : Observer {
override fun onChanged(result: Int?) {
- if (CKApp.app.isUserLoggedIn) getPetList2()
+ shouldRefreshPetListOnResume = true
}
})
}
@@ -539,6 +540,12 @@ class HomeFragment : CKBaseFragment() {
private fun setBannerData(data: List) {
bannarData = data
+ if (::imageAdapter.isInitialized) {
+ imageAdapter.mDatas = bannarData
+ homeBinding.banner.setDatas(bannarData)
+ return
+ }
+
imageAdapter = ImageAdapter(activity!!, bannarData, object : OnItemClickListenner {
override fun onClick(pos: Int, model: Any) = Unit
})
@@ -904,7 +911,15 @@ class HomeFragment : CKBaseFragment() {
super.onResume()
if (CKApp.app.isUserLoggedIn && loadedLoginToken != CKApp.app.token) {
initData()
+ } else if (CKApp.app.isUserLoggedIn && shouldRefreshPetListOnResume) {
+ getPetList()
}
+ shouldRefreshPetListOnResume = false
+ }
+
+ override fun onPause() {
+ super.onPause()
+ shouldRefreshPetListOnResume = true
}
private fun resetLoggedOutState() {
diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/mine/FeedBackActivity.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/mine/FeedBackActivity.kt
index 9cd2f39..389fd58 100644
--- a/app/src/main/java/com/ck/ckcollar/app/ui/act/mine/FeedBackActivity.kt
+++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/mine/FeedBackActivity.kt
@@ -1,5 +1,6 @@
package com.ck.ckcollar.app.ui.act.mine
+import android.Manifest
import android.content.Intent
import android.net.Uri
import android.os.Build
@@ -10,24 +11,23 @@ import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.webkit.MimeTypeMap
import android.widget.AdapterView
import android.widget.BaseAdapter
import android.widget.ImageView
import androidx.core.content.FileProvider
+import androidx.lifecycle.lifecycleScope
import com.ck.ckcollar.app.R
import com.ck.ckcollar.app.databinding.ActivityFeedBackBinding
import com.ck.ckcollar.app.databinding.LayoutItemImage2Binding
-import com.ck.ckcollar.app.databinding.LayoutItemImageBinding
import com.ck.ckcollar.app.model.ImageModel
import com.ck.ckcollar.app.model.UploadModel
-import com.ck.ckcollar.app.ui.act.petmanager.AddPetActivity
-import com.ck.ckcollar.app.ui.act.petmanager.AddPetActivity.ImageAdapter
import com.ck.ckcollar.app.ui.act.petmanager.ImageViewActivity
import com.ck.ckcollar.app.ui.base.CKBaseActivity
import com.ck.ckcollar.app.ui.dialog.CKAlertDialog
import com.ck.ckcollar.app.ui.dialog.CKPhotoDialog
-import com.ck.ckcollar.app.utils.CKUtils
import com.ck.ckcollar.app.utils.Const
+import com.ck.ckcollar.app.utils.LubanCompressUtil
import com.ck.ckcollar.app.utils.networkone.ICallback
import com.ck.ckcollar.app.utils.networkone.TTHttpClient
import com.ck.ckcollar.app.utils.networkone.model.TTRequestData
@@ -36,8 +36,10 @@ import com.cyclone.shadowsocks.utils.PreferencesUtils
import com.google.gson.Gson
import com.tt.kit.model.Data
import com.tt.kit.model.DataCollection
-import com.tt.kit.utils.TTLog
import com.tt.kit.utils.TTUtils
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import java.io.File
class FeedBackActivity : CKBaseActivity() {
@@ -84,13 +86,30 @@ class FeedBackActivity : CKBaseActivity() {
})
return
}
- //相册
- val i = Intent(
- Intent.ACTION_PICK,
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI
- )
- i.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,"image/*");
- startActivityForResult(i, 56)
+ CKPhotoDialog(
+ this@FeedBackActivity,
+ object : CKPhotoDialog.PhotoClickListener {
+ override fun onBack(mark: Int, position: Int) {
+ try {
+ if (mark == 0) {
+ if (!checkCameraPermissions()) {
+ getPermissionRequst(
+ Manifest.permission.CAMERA,
+ REQUEST_CAMERA_PERMISSION
+ )
+ return
+ }
+ takePhoto()
+ } else {
+ openAlbum()
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ toast(getString(R.string.read_photo_failed))
+ }
+ }
+ }
+ ).show()
}
}
@@ -105,6 +124,12 @@ class FeedBackActivity : CKBaseActivity() {
}
+ override fun onPermissionsGranted(requestCode: Int, permissions: Array) {
+ if (requestCode == REQUEST_CAMERA_PERMISSION) {
+ takePhoto()
+ }
+ }
+
fun upload(){
if (binding.editConten.isEmpty()){
@@ -162,11 +187,120 @@ class FeedBackActivity : CKBaseActivity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
- if (requestCode == 56 && resultCode == RESULT_OK) {
- var path = TTUtils.getImageUrl(this, data!!.getData());
- this.data.add(this.data.size - 1,ImageModel().apply { this.path = path })
- imageAdapter.data = this.data
- imageAdapter.notifyDataSetChanged()
+ if (requestCode == REQUEST_CAMERA && resultCode == RESULT_OK) {
+ val path = PreferencesUtils.getString(this, Const.IMAGE_PATH, "").orEmpty()
+ compressAndAddImage(path)
+ } else if (requestCode == REQUEST_ALBUM && resultCode == RESULT_OK) {
+ data?.data?.let(::handleSelectedImage)
+ ?: toast(getString(R.string.read_photo_failed))
+ }
+ }
+
+ private fun openAlbum() {
+ val intent = Intent(
+ Intent.ACTION_PICK,
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+ ).apply {
+ setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*")
+ }
+ startActivityForResult(intent, REQUEST_ALBUM)
+ }
+
+ private fun handleSelectedImage(uri: Uri) {
+ lifecycleScope.launch {
+ val path = withContext(Dispatchers.IO) {
+ copySelectedImageToCache(uri)
+ }
+ if (path == null) {
+ toast(getString(R.string.read_photo_failed))
+ return@launch
+ }
+ compressAndAddImage(path)
+ }
+ }
+
+ private fun compressAndAddImage(path: String) {
+ if (path.isBlank()) {
+ toast(getString(R.string.read_photo_failed))
+ return
+ }
+
+ LubanCompressUtil.getCompressFile(
+ this,
+ File(path),
+ object : LubanCompressUtil.CompressCallback {
+ override fun success(file: File) {
+ if (isFinishing || isDestroyed) return
+ data.add(data.size - 1, ImageModel().apply {
+ this.path = file.absolutePath
+ })
+ imageAdapter.data = data
+ imageAdapter.notifyDataSetChanged()
+ }
+
+ override fun fail(failMsg: String) {
+ if (isFinishing || isDestroyed) return
+ toast(failMsg)
+ }
+ }
+ )
+ }
+
+ private fun copySelectedImageToCache(uri: Uri): String? {
+ val extension = contentResolver.getType(uri)
+ ?.let(MimeTypeMap.getSingleton()::getExtensionFromMimeType)
+ ?.takeIf { it.isNotBlank() }
+ ?: "jpg"
+ val imageDir = File(cacheDir, "feedback_images")
+ if (!imageDir.exists() && !imageDir.mkdirs()) return null
+
+ val target = File(imageDir, System.currentTimeMillis().toString() + "." + extension)
+ return try {
+ val input = contentResolver.openInputStream(uri) ?: return null
+ input.use { source ->
+ target.outputStream().use { output -> source.copyTo(output) }
+ }
+ target.absolutePath.takeIf { target.length() > 0L }
+ ?: run {
+ target.delete()
+ null
+ }
+ } catch (e: Exception) {
+ target.delete()
+ e.printStackTrace()
+ null
+ }
+ }
+
+ private fun takePhoto() {
+ try {
+ val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
+ val imageFile = File(
+ getExternalFilesDir(Environment.DIRECTORY_PICTURES),
+ System.currentTimeMillis().toString() + ".png"
+ )
+ imageFile.parentFile?.let { parent ->
+ if (!parent.exists()) parent.mkdirs()
+ }
+ PreferencesUtils.putString(this, Const.IMAGE_PATH, imageFile.absolutePath)
+
+ val cameraUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ FileProvider.getUriForFile(
+ this,
+ packageName + ".fileprovider",
+ imageFile
+ )
+ } else {
+ Uri.fromFile(imageFile)
+ }
+ cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraUri)
+ cameraIntent.addFlags(
+ Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ )
+ startActivityForResult(cameraIntent, REQUEST_CAMERA)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ toast(getString(R.string.read_photo_failed))
}
}
@@ -234,4 +368,10 @@ class FeedBackActivity : CKBaseActivity() {
}
}
-}
\ No newline at end of file
+ companion object {
+ private const val REQUEST_CAMERA = 55
+ private const val REQUEST_ALBUM = 56
+ private const val REQUEST_CAMERA_PERMISSION = 1301
+ }
+
+}
diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/mine/ReturnDeviceActivity.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/mine/ReturnDeviceActivity.kt
index 52b8093..b471fed 100644
--- a/app/src/main/java/com/ck/ckcollar/app/ui/act/mine/ReturnDeviceActivity.kt
+++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/mine/ReturnDeviceActivity.kt
@@ -97,6 +97,12 @@ class ReturnDeviceActivity : CKBaseActivity() {
return
}
+ val phone = binding.textPhone.text.toString().trim()
+ if (!PHONE_PATTERN.matches(phone)) {
+ toast(getString(R.string.invalid_phone_format))
+ return
+ }
+
if (binding.textReason.isEmpty()){
toast(getString(R.string.input_return_reason))
return
@@ -124,4 +130,8 @@ class ReturnDeviceActivity : CKBaseActivity() {
}
})
}
-}
\ No newline at end of file
+
+ companion object {
+ private val PHONE_PATTERN = Regex("^1[3-9]\\d{9}$")
+ }
+}
diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/order/LogisticsInfoActivity.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/order/LogisticsInfoActivity.kt
index 06fdc03..f999c22 100644
--- a/app/src/main/java/com/ck/ckcollar/app/ui/act/order/LogisticsInfoActivity.kt
+++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/order/LogisticsInfoActivity.kt
@@ -137,8 +137,8 @@ class LogisticsInfoActivity : CKBaseActivity() {
model = Gson().fromJson(getReceiveParam().get("data")!!.stringValue.toString(),
OrderModel.Order::class.java)
- binding.textReceiveName.text = model.receiverName + " " + model.receiverPhone
- getData()
+ binding.textReceiveName.text = model.receiverAddress + "\n" + model.receiverName + " " + model.receiverPhone
+ getData()
}
diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/order/PayOrderActivity.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/order/PayOrderActivity.kt
index efa4d48..116cdd8 100644
--- a/app/src/main/java/com/ck/ckcollar/app/ui/act/order/PayOrderActivity.kt
+++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/order/PayOrderActivity.kt
@@ -41,6 +41,7 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.math.BigDecimal
+import java.math.RoundingMode
class PayOrderActivity : CKBaseActivity() {
@@ -88,7 +89,7 @@ class PayOrderActivity : CKBaseActivity() {
binding.btnBalance.setOnClickListener {
- if (memberModel?.balance == 0.0){
+ if ((memberModel?.balance ?: 0.0) <= 0.0){
toast(getString(R.string.balance_over))
return@setOnClickListener
}
@@ -204,17 +205,21 @@ class PayOrderActivity : CKBaseActivity() {
fun showPayBottomView(){
- var showBalance = model.payAmount
+ var showBalance = BigDecimal.valueOf(model.payAmount).setScale(2, RoundingMode.HALF_UP)
if (selectPoints){
- CKApp.app.userInfo?.intoAmounts?.let {
- showBalance = showBalance - BigDecimal(memberModel?.points!!).multiply(
- BigDecimal(memberModel!!.intoAmounts!!)).setScale(2).toDouble()
- if (showBalance < 0.0){
- showBalance = 0.00
- }
+ memberModel?.intoAmounts?.let { intoAmounts ->
+ val pointsDeduction = BigDecimal.valueOf(memberModel!!.points)
+ .multiply(BigDecimal(intoAmounts))
+ .setScale(2, RoundingMode.HALF_UP)
+ showBalance = showBalance.subtract(pointsDeduction)
+ .max(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP))
}
}
+ val userBalance = BigDecimal.valueOf(memberModel?.balance ?: 0.0)
+ .setScale(2, RoundingMode.HALF_UP)
+ showBalance = showBalance.min(userBalance)
+
var passwordBottomFragment = PayPasswordBottomFragment(showBalance.toString(), "余额",object : PayPasswordBottomFragment.PasswordCallBack{
override fun callBack(password: String) {
@@ -432,4 +437,4 @@ class PayOrderActivity : CKBaseActivity() {
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/order/RefundActivity.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/order/RefundActivity.kt
index 6c008bb..0b0a07c 100644
--- a/app/src/main/java/com/ck/ckcollar/app/ui/act/order/RefundActivity.kt
+++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/order/RefundActivity.kt
@@ -251,10 +251,12 @@ class RefundActivity: CKBaseActivity() {
showProgeassDialog(getString(R.string.submit))
- var commodityState = if(model.orderStatus >= 3){
- "1"
+
+ var commodityState = -1;
+ if ("0".equals(refundType)){
+ commodityState = 0
}else{
- "0"
+ commodityState = 1
}
var param = TTRequestParam().apply {
add(TTRequestData("orderId",model.id))
diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/petmanager/AddPetActivity.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/petmanager/AddPetActivity.kt
index a026c65..d8a34e9 100644
--- a/app/src/main/java/com/ck/ckcollar/app/ui/act/petmanager/AddPetActivity.kt
+++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/petmanager/AddPetActivity.kt
@@ -2,7 +2,6 @@ package com.ck.ckcollar.app.ui.act.petmanager
import android.Manifest
import android.content.Intent
-import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.os.Bundle
@@ -12,10 +11,12 @@ import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.webkit.MimeTypeMap
import android.widget.AdapterView
import android.widget.BaseAdapter
import android.widget.ImageView
import androidx.core.content.FileProvider
+import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
import com.ck.ckcollar.app.R
import com.ck.ckcollar.app.databinding.ActivityAddPetBinding
@@ -29,6 +30,7 @@ import com.ck.ckcollar.app.ui.dialog.CKPhotoDialog
import com.ck.ckcollar.app.ui.dialog.CKPickViewDialog
import com.ck.ckcollar.app.utils.CKUtils
import com.ck.ckcollar.app.utils.Const
+import com.ck.ckcollar.app.utils.LubanCompressUtil
import com.ck.ckcollar.app.utils.networkone.ICallback
import com.ck.ckcollar.app.utils.networkone.TTHttpClient
import com.ck.ckcollar.app.utils.networkone.model.TTRequestData
@@ -41,6 +43,9 @@ import com.tt.kit.model.DataCollection
import com.tt.kit.utils.LiveDataBus
import com.tt.kit.utils.TTLog
import com.tt.kit.utils.TTUtils
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import java.io.File
class AddPetActivity : CKBaseActivity() {
@@ -284,6 +289,7 @@ class AddPetActivity : CKBaseActivity() {
}
}
showProgeassDialog()
+ //先上传照片
TTHttpClient.postFile(this,"上传照片s", param,object : ICallback{
override fun onSuccess(data: String) {
var model = Gson().fromJson(data,UploadModel::class.java)
@@ -330,21 +336,86 @@ class AddPetActivity : CKBaseActivity() {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 55 && resultCode == RESULT_OK) {
- var path = PreferencesUtils.getString(this@AddPetActivity, Const.IMAGE_PATH,"")
- this.data.add(this.data.size - 1,ImageModel().apply { this.path = path })
- imageAdapter.data = this.data
- imageAdapter.notifyDataSetChanged()
+ val path = PreferencesUtils.getString(this@AddPetActivity, Const.IMAGE_PATH, "")
+ compressAndAddImage(path.orEmpty())
}else if (requestCode == 56 && resultCode == RESULT_OK) {
- var path = TTUtils.getImageUrl(this, data!!.getData());
- this.data.add(this.data.size - 1,ImageModel().apply { this.path = path })
- imageAdapter.data = this.data
- imageAdapter.notifyDataSetChanged()
+ data?.data?.let(::handleSelectedImage)
+ ?: toast(getString(R.string.read_photo_failed))
}else if (requestCode == 555 && resultCode == RESULT_OK) {
var path = PreferencesUtils.getString(this@AddPetActivity, Const.IMAGE_PATH,"")
uploadImage(path!!)
}
binding.textPhotoNum.setText((this.data.size - 1 ).toString() + "/3")
}
+
+ private fun handleSelectedImage(uri: Uri) {
+ lifecycleScope.launch {
+ val path = withContext(Dispatchers.IO) {
+ copySelectedImageToCache(uri)
+ }
+ if (path == null) {
+ toast(getString(R.string.read_photo_failed))
+ return@launch
+ }
+ compressAndAddImage(path)
+ }
+ }
+
+ private fun compressAndAddImage(path: String) {
+ if (path.isBlank()) {
+ toast(getString(R.string.read_photo_failed))
+ return
+ }
+
+ LubanCompressUtil.getCompressFile(
+ this,
+ File(path),
+ object : LubanCompressUtil.CompressCallback {
+ override fun success(file: File) {
+ if (isFinishing || isDestroyed) return
+ data.add(data.size - 1, ImageModel().apply {
+ this.path = file.absolutePath
+ })
+ imageAdapter.data = data
+ imageAdapter.notifyDataSetChanged()
+ binding.textPhotoNum.text = "${data.size - 1}/3"
+ }
+
+ override fun fail(failMsg: String) {
+ if (isFinishing || isDestroyed) return
+ toast(failMsg)
+ }
+ }
+ )
+ }
+
+ private fun copySelectedImageToCache(uri: Uri): String? {
+ val extension = contentResolver.getType(uri)
+ ?.let(MimeTypeMap.getSingleton()::getExtensionFromMimeType)
+ ?.takeIf { it.isNotBlank() }
+ ?: "jpg"
+ val imageDir = File(cacheDir, "selected_images")
+ if (!imageDir.exists() && !imageDir.mkdirs()) {
+ return null
+ }
+
+ val target = File(imageDir, "${System.currentTimeMillis()}.$extension")
+ return try {
+ val input = contentResolver.openInputStream(uri) ?: return null
+ input.use { source ->
+ target.outputStream().use { output -> source.copyTo(output) }
+ }
+ target.absolutePath.takeIf { target.length() > 0L }
+ ?: run {
+ target.delete()
+ null
+ }
+ } catch (e: Exception) {
+ target.delete()
+ e.printStackTrace()
+ null
+ }
+ }
fun takePhoto() {
try {
val openCameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
@@ -509,13 +580,10 @@ class AddPetActivity : CKBaseActivity() {
layoutItemImageBinding.imageDelete.visibility = View.GONE
} else {
layoutItemImageBinding.image.scaleType = ImageView.ScaleType.CENTER_CROP
- layoutItemImageBinding.image.setImageBitmap(
- TTUtils.getBitmap(
- model.path,
- 200,
- 200
- )
- )
+ Glide.with(context)
+ .load(model.path?.let(::File))
+ .centerCrop()
+ .into(layoutItemImageBinding.image)
layoutItemImageBinding.imageDelete.visibility = View.VISIBLE
layoutItemImageBinding.imageDelete.setOnClickListener {
diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/petmanager/ImageViewActivity.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/petmanager/ImageViewActivity.kt
index 5e00223..074b8c6 100644
--- a/app/src/main/java/com/ck/ckcollar/app/ui/act/petmanager/ImageViewActivity.kt
+++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/petmanager/ImageViewActivity.kt
@@ -3,29 +3,19 @@ package com.ck.ckcollar.app.ui.act.petmanager
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
-import android.view.Window
-import android.view.WindowManager
import androidx.viewpager.widget.PagerAdapter
import com.bumptech.glide.Glide
import com.ck.ckcollar.app.R
import com.ck.ckcollar.app.databinding.ActivityImageViewBinding
-import com.ck.ckcollar.app.databinding.ActivityWelcomeBinding
import com.ck.ckcollar.app.databinding.LayoutItemImageViewBinding
-import com.ck.ckcollar.app.databinding.LayoutItemWelcomeBinding
-import com.ck.ckcollar.app.ui.act.login.LoginActivity
-import com.ck.ckcollar.app.ui.act.welcome.PermisstionActivity
import com.ck.ckcollar.app.ui.base.CKBaseActivity
-import com.ck.ckcollar.app.utils.Const
-import com.cyclone.shadowsocks.utils.PreferencesUtils
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
-import com.tt.kit.model.DataCollection
class ImageViewActivity : CKBaseActivity() {
override val useDarkStatusBarIcons: Boolean = false
lateinit var binding: ActivityImageViewBinding
- var views = mutableListOf()
var data : List? = null
var pos = 0
override fun onCreate(savedInstanceState: Bundle?) {
@@ -38,17 +28,9 @@ class ImageViewActivity : CKBaseActivity() {
}
override fun initView() {
- for (i in data!!) {
- var view = LayoutItemImageViewBinding.inflate(layoutInflater);
- Glide.with(this)
- .load(i)
- .placeholder(R.mipmap.image_default)
- .into(view.imageView)
- views.add(view.root)
- }
binding.viewPager.adapter = object : PagerAdapter() {
override fun getCount(): Int {
- return views.size
+ return data.orEmpty().size
}
override fun isViewFromObject(
@@ -59,12 +41,21 @@ class ImageViewActivity : CKBaseActivity() {
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
- container.removeView(views.get(position))
+ container.removeView(`object` as View)
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
- container.addView(views.get(position))
- return views.get(position)
+ val itemBinding = LayoutItemImageViewBinding.inflate(
+ layoutInflater,
+ container,
+ false
+ )
+ Glide.with(this@ImageViewActivity)
+ .load(data.orEmpty()[position])
+ .placeholder(R.mipmap.image_default)
+ .into(itemBinding.imageView)
+ container.addView(itemBinding.root)
+ return itemBinding.root
}
}
diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/shop/OrderActivity.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/shop/OrderActivity.kt
index 8a775ee..cc12a50 100644
--- a/app/src/main/java/com/ck/ckcollar/app/ui/act/shop/OrderActivity.kt
+++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/shop/OrderActivity.kt
@@ -1,6 +1,7 @@
package com.ck.ckcollar.app.ui.act.shop
import android.content.Context
+import android.content.pm.ApplicationInfo
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
@@ -8,6 +9,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
+import com.alipay.sdk.app.EnvUtils
import com.alipay.sdk.app.PayTask
import com.bumptech.glide.Glide
import com.ck.ckcollar.app.CKApp
@@ -49,6 +51,7 @@ import kotlinx.coroutines.withContext
import org.json.JSONArray
import org.json.JSONObject
import java.math.BigDecimal
+import java.math.RoundingMode
class OrderActivity : CKBaseActivity() {
@@ -95,7 +98,7 @@ class OrderActivity : CKBaseActivity() {
binding.btnWX.setOnClickListener(payClick)
binding.btnBalance.setOnClickListener {
- if (memberModel?.balance == 0.0){
+ if ((memberModel?.balance ?: 0.0) <= 0.0){
toast(getString(R.string.balance_over))
return@setOnClickListener
}
@@ -108,6 +111,11 @@ class OrderActivity : CKBaseActivity() {
}
}
binding.btnPoints.setOnClickListener {
+ if ((memberModel?.points ?: 0.0) <= 0.0) {
+ toast(getString(R.string.points_over))
+ return@setOnClickListener
+ }
+
selectPoints = !selectPoints
if (selectPoints){
binding.image4.setImageResource(R.mipmap.image_address_check_select)
@@ -123,7 +131,7 @@ class OrderActivity : CKBaseActivity() {
}
binding.btnBuyNow.setOnClickListener {
- if (selectBalance){
+ if (selectBalance && !canPointsCoverPayAmount()){
getPayPassword()
}else{
order("")
@@ -327,6 +335,10 @@ class OrderActivity : CKBaseActivity() {
fun aliPay(orderStr: String) {
GlobalScope.launch(Dispatchers.IO) {
+ // [支付宝沙箱测试] 仅 Debug 包启用,Release 包继续使用正式环境。
+ if (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0) {
+ EnvUtils.setEnv(EnvUtils.EnvEnum.SANDBOX)
+ }
val alipay = PayTask(this@OrderActivity)
val result = alipay.payV2(orderStr, true)
@@ -393,16 +405,18 @@ class OrderActivity : CKBaseActivity() {
fun showPayBottomView(){
- var showBalance = model.payAmount
+ var showBalance = BigDecimal.valueOf(model.payAmount).setScale(2, RoundingMode.HALF_UP)
if (selectPoints){
- CKApp.app.userInfo?.intoAmounts?.let {
- showBalance = showBalance - BigDecimal(memberModel?.points!!).multiply(
- BigDecimal(memberModel!!.intoAmounts!!)).setScale(2).toDouble()
- if (showBalance < 0.0){
- showBalance = 0.00
- }
+ calculatePointsDeduction()?.let { pointsDeduction ->
+ showBalance = showBalance.subtract(pointsDeduction)
+ .max(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP))
}
}
+
+ val userBalance = BigDecimal.valueOf(memberModel?.balance ?: 0.0)
+ .setScale(2, RoundingMode.HALF_UP)
+ showBalance = showBalance.min(userBalance)
+
var passwordBottomFragment = PayPasswordBottomFragment(showBalance.toString(), "余额",object : PayPasswordBottomFragment.PasswordCallBack{
override fun callBack(password: String) {
@@ -413,6 +427,21 @@ class OrderActivity : CKBaseActivity() {
passwordBottomFragment.show(supportFragmentManager, "PayPasswordBottomFragment")
}
+ private fun canPointsCoverPayAmount(): Boolean {
+ if (!selectPoints) return false
+ val pointsDeduction = calculatePointsDeduction() ?: return false
+ val payAmount = BigDecimal.valueOf(model.payAmount).setScale(2, RoundingMode.HALF_UP)
+ return pointsDeduction >= payAmount
+ }
+
+ private fun calculatePointsDeduction(): BigDecimal? {
+ val member = memberModel ?: return null
+ val intoAmounts = member.intoAmounts?.toBigDecimalOrNull() ?: return null
+ return BigDecimal.valueOf(member.points)
+ .multiply(intoAmounts)
+ .setScale(2, RoundingMode.HALF_UP)
+ }
+
var payClick = object : View.OnClickListener {
override fun onClick(v: View?) {
binding.image1.setImageResource(R.mipmap.image_address_check_narmal)
@@ -517,4 +546,4 @@ class OrderActivity : CKBaseActivity() {
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/ck/ckcollar/app/utils/LubanCompressUtil.kt b/app/src/main/java/com/ck/ckcollar/app/utils/LubanCompressUtil.kt
new file mode 100644
index 0000000..0f37f5d
--- /dev/null
+++ b/app/src/main/java/com/ck/ckcollar/app/utils/LubanCompressUtil.kt
@@ -0,0 +1,53 @@
+package com.ck.ckcollar.app.utils
+
+import android.content.Context
+import java.io.File
+import java.util.Locale
+import top.zibin.luban.Luban
+import top.zibin.luban.OnCompressListener
+
+object LubanCompressUtil {
+
+ private const val DEFAULT_IGNORE_SIZE_KB = 100
+
+ interface CompressCallback {
+ fun success(file: File)
+
+ fun fail(failMsg: String)
+ }
+
+ fun getInstance(): LubanCompressUtil {
+ return this
+ }
+
+ fun getCompressFile(
+ context: Context,
+ oldFile: File,
+ callback: CompressCallback?
+ ) {
+ if (!oldFile.exists() || !oldFile.isFile) {
+ callback?.fail("图片文件不存在")
+ return
+ }
+
+ Luban.with(context.applicationContext)
+ .load(oldFile)
+ .ignoreBy(DEFAULT_IGNORE_SIZE_KB)
+ .filter { path ->
+ path.isNotBlank() && !path.lowercase(Locale.ROOT).endsWith(".gif")
+ }
+ .setCompressListener(object : OnCompressListener {
+ override fun onStart() {
+ }
+
+ override fun onSuccess(file: File) {
+ callback?.success(file)
+ }
+
+ override fun onError(error: Throwable) {
+ callback?.fail(error.message ?: "图片压缩失败")
+ }
+ })
+ .launch()
+ }
+}
diff --git a/app/src/main/java/com/ck/ckcollar/app/utils/MemberPriceCache.kt b/app/src/main/java/com/ck/ckcollar/app/utils/MemberPriceCache.kt
new file mode 100644
index 0000000..97cb57e
--- /dev/null
+++ b/app/src/main/java/com/ck/ckcollar/app/utils/MemberPriceCache.kt
@@ -0,0 +1,31 @@
+package com.ck.ckcollar.app.utils
+
+import android.content.Context
+import com.cyclone.shadowsocks.utils.PreferencesUtils
+
+object MemberPriceCache {
+
+ private const val CACHE_VALID_DURATION = 24 * 60 * 60 * 1000L
+
+ fun getPrice(context: Context): Double? {
+ return PreferencesUtils.getString(context, Const.MEMBER_PRICE, "")
+ .toString()
+ .toDoubleOrNull()
+ }
+
+ fun isExpired(context: Context): Boolean {
+ val cacheTime = PreferencesUtils.getString(context, Const.MEMBER_PRICE_CACHE_TIME, "0")
+ .toString()
+ .toLongOrNull() ?: 0L
+ return System.currentTimeMillis() - cacheTime >= CACHE_VALID_DURATION
+ }
+
+ fun save(context: Context, price: Double) {
+ PreferencesUtils.putString(context, Const.MEMBER_PRICE, price.toString())
+ PreferencesUtils.putString(
+ context,
+ Const.MEMBER_PRICE_CACHE_TIME,
+ System.currentTimeMillis().toString()
+ )
+ }
+}
diff --git a/app/src/main/java/com/ck/ckcollar/app/widget/CKViewPager.kt b/app/src/main/java/com/ck/ckcollar/app/widget/CKViewPager.kt
index 4f770ba..8599b8d 100644
--- a/app/src/main/java/com/ck/ckcollar/app/widget/CKViewPager.kt
+++ b/app/src/main/java/com/ck/ckcollar/app/widget/CKViewPager.kt
@@ -3,24 +3,63 @@ package com.ck.ckcollar.app.widget
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
-import androidx.appcompat.widget.AppCompatImageView
import androidx.viewpager.widget.ViewPager
class CKViewPager : ViewPager{
+ private var isMultiTouchGesture = false
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+ override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
+ when (ev.actionMasked) {
+ MotionEvent.ACTION_DOWN -> isMultiTouchGesture = false
+ MotionEvent.ACTION_POINTER_DOWN -> {
+ if (ev.pointerCount > 1) {
+ isMultiTouchGesture = true
+ }
+ }
+ }
+
+ return try {
+ super.dispatchTouchEvent(ev)
+ } finally {
+ // dispatchTouchEvent always sees the terminal event, even when
+ // PhotoView has disabled parent interception during scaling.
+ if (ev.actionMasked == MotionEvent.ACTION_UP ||
+ ev.actionMasked == MotionEvent.ACTION_CANCEL
+ ) {
+ isMultiTouchGesture = false
+ }
+ }
+ }
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
+ if (ev == null) return false
+
+ // Keep the complete pinch sequence in PhotoView. If ViewPager intercepts
+ // one MOVE event, the child receives CANCEL and scaling stops entirely.
+ if (isMultiTouchGesture || ev.pointerCount > 1) {
+ return false
+ }
+
try {
return super.onInterceptTouchEvent(ev)
-
- }catch (e : Exception){
+ } catch (e: IllegalArgumentException) {
e.printStackTrace()
}
- return false
+ return false
}
-}
\ No newline at end of file
+
+ override fun onTouchEvent(ev: MotionEvent?): Boolean {
+ if (ev == null) return false
+ return try {
+ super.onTouchEvent(ev)
+ } catch (e: IllegalArgumentException) {
+ e.printStackTrace()
+ false
+ }
+ }
+}
diff --git a/app/src/main/res/layout/activity_add_address.xml b/app/src/main/res/layout/activity_add_address.xml
index c054629..4e68eda 100644
--- a/app/src/main/res/layout/activity_add_address.xml
+++ b/app/src/main/res/layout/activity_add_address.xml
@@ -88,6 +88,7 @@
android:textColorHint="@color/textSecondColor"
android:maxLength="11"
android:maxLines="1"
+ android:singleLine="true"
android:inputType="number"
android:textSize="14sp">
@@ -165,7 +166,8 @@
android:textColorHint="@color/textSecondColor"
android:maxLength="50"
android:maxLines="3"
- android:textSize="14sp">
+ android:textSize="14sp"
+ android:singleLine="true">
diff --git a/app/src/main/res/layout/fragment_health.xml b/app/src/main/res/layout/fragment_health.xml
index 212f3cf..ef53755 100644
--- a/app/src/main/res/layout/fragment_health.xml
+++ b/app/src/main/res/layout/fragment_health.xml
@@ -9,13 +9,22 @@
android:background="?attr/mainBackground"
android:orientation="vertical">
-
+
+
+
+
@@ -223,8 +232,9 @@
@@ -232,17 +242,8 @@
-
-
-
-
+
diff --git a/app/src/main/res/layout/layout_item_ai_customer_message.xml b/app/src/main/res/layout/layout_item_ai_customer_message.xml
index 9c40d57..b4297cb 100644
--- a/app/src/main/res/layout/layout_item_ai_customer_message.xml
+++ b/app/src/main/res/layout/layout_item_ai_customer_message.xml
@@ -12,8 +12,7 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingLeft="20dp"
- android:visibility="gone"
- >
+ android:visibility="gone">
@@ -54,7 +53,7 @@
android:background="@drawable/bg_message_from"
android:padding="8dp"
android:layout_marginTop="3dp"
- android:maxWidth="200dp"
+ android:maxWidth="280dp"
android:textSize="14sp">
@@ -68,7 +67,7 @@
android:paddingVertical="3dp"
android:paddingHorizontal="5dp"
android:layout_marginTop="3dp"
- android:maxWidth="200dp"
+ android:maxWidth="280dp"
android:visibility="gone"
>
diff --git a/app/src/main/res/layout/layout_item_order.xml b/app/src/main/res/layout/layout_item_order.xml
index ab5bbc8..54bc71f 100644
--- a/app/src/main/res/layout/layout_item_order.xml
+++ b/app/src/main/res/layout/layout_item_order.xml
@@ -97,7 +97,7 @@
android:text="@string/no_reason_text"
android:background="@drawable/bg_no_reason"
android:paddingHorizontal="5dp"
- >
+ android:visibility="gone"/>
+ android:visibility="gone"/>
添加宠物
智能项圈设备
未绑定
- 宠遇智能项圈,科技护宠每一刻
+ 宠盾智能项圈,科技护宠每一刻
扫一扫
设备定位
当前位置:当前无定位
@@ -122,6 +122,7 @@
请选择是否接种
上传照片
请上传照片
+ 图片读取失败,请重新选择
宠物顾问(早 9:00-晚 21:00)
宠物顾问
@@ -319,6 +320,7 @@
请输入姓名
联系方式
请输入联系方式
+ 请输入正确的手机号
退回理由
请输入退回理由
项圈编号
@@ -481,7 +483,7 @@
退款
打电话
查看物流
- 选择宠物
+ 宠物名字
添加成功
修改成功
删除成功
@@ -577,7 +579,7 @@
管理
库存不足
余额不足
- 余额不足
+ 积分不足
请选择商品
请选择支付方式
新密码和旧密码相同