This commit is contained in:
fengjignqi
2026-08-07 08:33:52 +08:00
parent c6eeb1321c
commit 9806e66790
35 changed files with 1529 additions and 2364 deletions

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -4,10 +4,10 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-08-05T02:01:17.427812300Z"> <DropdownSelection timestamp="2026-08-06T08:17:58.704098200Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=D122222104A9" /> <DeviceId pluginId="PhysicalDevice" identifier="serial=80bece4" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>

View File

@@ -85,6 +85,7 @@ dependencies {
implementation libs.material implementation libs.material
implementation libs.appcompat implementation libs.appcompat
implementation libs.luban implementation libs.luban
implementation 'io.noties.markwon:core:4.6.2'
testImplementation libs.junit testImplementation libs.junit
androidTestImplementation libs.androidx.junit androidTestImplementation libs.androidx.junit
androidTestImplementation libs.androidx.espresso.core androidTestImplementation libs.androidx.espresso.core

View File

@@ -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<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean = false
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>?,
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))
}
}

View File

@@ -58,9 +58,50 @@ import com.tt.kit.model.DataCollection
import com.tt.kit.utils.KeyboardUtils import com.tt.kit.utils.KeyboardUtils
import com.tt.kit.utils.TTLog import com.tt.kit.utils.TTLog
import com.tt.kit.utils.TTUtils import com.tt.kit.utils.TTUtils
import io.noties.markwon.Markwon
import java.io.File import java.io.File
import java.util.UUID 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() { class CustomerActivity : CKBaseActivity() {
lateinit var binding: ActivityCustomerBinding lateinit var binding: ActivityCustomerBinding
@@ -340,6 +381,7 @@ class CustomerActivity : CKBaseActivity() {
fun sendMessage(uuid :String ,content: String,isResend : Boolean, imageUrl: String) { fun sendMessage(uuid :String ,content: String,isResend : Boolean, imageUrl: String) {
isAiResponding = true isAiResponding = true
val rawStreamResponse = StringBuilder()
if (!TextUtils.isEmpty(imageUrl)) { if (!TextUtils.isEmpty(imageUrl)) {
this@CustomerActivity.data.add(CustomerMessageModel.MessageItemModel().apply { this@CustomerActivity.data.add(CustomerMessageModel.MessageItemModel().apply {
@@ -373,6 +415,8 @@ class CustomerActivity : CKBaseActivity() {
}, object : ICallback { }, object : ICallback {
override fun onSuccess(data: String) { override fun onSuccess(data: String) {
rawStreamResponse.append(data)
// var model = Gson().fromJson<AIMessageModel>(data, AIMessageModel::class.java) // var model = Gson().fromJson<AIMessageModel>(data, AIMessageModel::class.java)
var flag = false var flag = false
@@ -415,6 +459,7 @@ class CustomerActivity : CKBaseActivity() {
override fun onFailed(code: String, msg: String) { override fun onFailed(code: String, msg: String) {
isAiResponding = false isAiResponding = false
lock = true lock = true
TTLog.e("AI_STREAM_FULL[$uuid][FAILED]: $rawStreamResponse")
// toast(msg) // toast(msg)
//TTLog.e(msg) //TTLog.e(msg)
} }
@@ -422,6 +467,7 @@ class CustomerActivity : CKBaseActivity() {
override fun onComplete() { override fun onComplete() {
isAiResponding = false isAiResponding = false
lock = true 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) { fun sendMessage2(uuid :String ,content: String,isResend : Boolean, imageUrl: String) {
isAiResponding = true isAiResponding = true
val rawStreamResponse = StringBuilder()
lock = false lock = false
if (!TextUtils.isEmpty(content) && !isResend) { if (!TextUtils.isEmpty(content) && !isResend) {
this@CustomerActivity.data.add(CustomerMessageModel.MessageItemModel().apply { this@CustomerActivity.data.add(CustomerMessageModel.MessageItemModel().apply {
@@ -460,6 +507,8 @@ class CustomerActivity : CKBaseActivity() {
}, object : ICallback { }, object : ICallback {
override fun onSuccess(data: String) { override fun onSuccess(data: String) {
rawStreamResponse.append(data)
// var model = Gson().fromJson<AIMessageModel>(data, AIMessageModel::class.java) // var model = Gson().fromJson<AIMessageModel>(data, AIMessageModel::class.java)
var flag = false var flag = false
@@ -502,6 +551,7 @@ class CustomerActivity : CKBaseActivity() {
override fun onFailed(code: String, msg: String) { override fun onFailed(code: String, msg: String) {
isAiResponding = false isAiResponding = false
lock = true lock = true
TTLog.e("AI_STREAM_FULL[$uuid][FAILED]: $rawStreamResponse")
// toast(msg) // toast(msg)
//TTLog.e(msg) //TTLog.e(msg)
} }
@@ -509,6 +559,7 @@ class CustomerActivity : CKBaseActivity() {
override fun onComplete() { override fun onComplete() {
isAiResponding = false isAiResponding = false
lock = true lock = true
TTLog.e("AI_STREAM_FULL[$uuid]: $rawStreamResponse")
} }
}) })
@@ -707,6 +758,8 @@ class CustomerActivity : CKBaseActivity() {
var data: List<CustomerMessageModel.MessageItemModel>, var data: List<CustomerMessageModel.MessageItemModel>,
var onItemClickListenner: OnItemClickListenner var onItemClickListenner: OnItemClickListenner
) : RecyclerView.Adapter<CustomerMessageAdapter.CustomerMessageHolder>() { ) : RecyclerView.Adapter<CustomerMessageAdapter.CustomerMessageHolder>() {
private val markwon = Markwon.create(context)
override fun onCreateViewHolder( override fun onCreateViewHolder(
parent: ViewGroup, parent: ViewGroup,
viewType: Int viewType: Int
@@ -731,7 +784,16 @@ class CustomerActivity : CKBaseActivity() {
holder.binding.contentTo.visibility = View.GONE holder.binding.contentTo.visibility = View.GONE
holder.binding.contentFrom.visibility = View.VISIBLE 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 holder.binding.textFromNick.text = model.senderName
Glide.with(context) Glide.with(context)

View File

@@ -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<CustomerMessageModel.MessageItemModel>()
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<String>) {
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<List<CustomerMessageModel.MessageItemModel>>(dataStr,object : TypeToken<List<CustomerMessageModel.MessageItemModel>>(){}.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<AIMessageModel>(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<AIMessageModel>(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<UploadModel>(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<CustomerMessageModel>(data,CustomerMessageModel::class.java)
// model.rows?.let {
// this@CustomerActivity.data.addAll(model.rows!!)
// }
var model = Gson().fromJson<List<CustomerMessageModel.MessageItemModel>>(
data,
object : TypeToken<List<CustomerMessageModel.MessageItemModel>>() {}.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<CustomerMessageModel.MessageItemModel>,
var onItemClickListenner: OnItemClickListenner
) : RecyclerView.Adapter<CustomerMessageAdapter.CustomerMessageHolder>() {
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<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean = false
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>?,
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<String>()
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
}
}

View File

@@ -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.TTRequestData
import com.ck.ckcollar.app.utils.networkone.model.TTRequestParam import com.ck.ckcollar.app.utils.networkone.model.TTRequestParam
import com.google.gson.Gson 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.base.TTBaseActivity
import com.tt.kit.model.Data import com.tt.kit.model.Data
import com.tt.kit.model.DataCollection import com.tt.kit.model.DataCollection
@@ -80,13 +77,11 @@ class HealthFragment: CKBaseFragment() {
} }
binding.refreshLayout.setEnableLoadMore(false) binding.refreshLayout.setEnableLoadMore(false)
binding.refreshLayout.setOnRefreshListener { binding.refreshLayout.setOnRefreshListener {
if (!CKApp.app.isUserLoggedIn) {
binding.refreshLayout.finishRefresh()
return@setOnRefreshListener
}
page = 1 page = 1
getData() getData()
getCustomerTime() if (CKApp.app.isUserLoggedIn) {
getCustomerTime()
}
} }
@@ -174,7 +169,8 @@ class HealthFragment: CKBaseFragment() {
override fun onFailed(code: String, msg: String) { override fun onFailed(code: String, msg: String) {
try { try {
toast(msg) toast(msg)
binding.refreshLayout.finishLoadMore(true) binding.refreshLayout.finishLoadMore(false)
binding.refreshLayout.finishRefresh(false)
}catch (e : Exception){ }catch (e : Exception){
e.printStackTrace() e.printStackTrace()

View File

@@ -119,6 +119,7 @@ class HomeFragment : CKBaseFragment() {
var isFirstShow = false var isFirstShow = false
private var waitingForLoginResult = false private var waitingForLoginResult = false
private var loadedLoginToken: String? = null private var loadedLoginToken: String? = null
private var shouldRefreshPetListOnResume = false
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@@ -267,7 +268,7 @@ class HomeFragment : CKBaseFragment() {
LiveDataBus.get().with("FIRST", Int::class.java) LiveDataBus.get().with("FIRST", Int::class.java)
.observe(viewLifecycleOwner, object : Observer<Int?> { .observe(viewLifecycleOwner, object : Observer<Int?> {
override fun onChanged(result: Int?) { override fun onChanged(result: Int?) {
if (CKApp.app.isUserLoggedIn) getPetList2() shouldRefreshPetListOnResume = true
} }
}) })
} }
@@ -539,6 +540,12 @@ class HomeFragment : CKBaseFragment() {
private fun setBannerData(data: List<BannarModel>) { private fun setBannerData(data: List<BannarModel>) {
bannarData = data bannarData = data
if (::imageAdapter.isInitialized) {
imageAdapter.mDatas = bannarData
homeBinding.banner.setDatas(bannarData)
return
}
imageAdapter = ImageAdapter(activity!!, bannarData, object : OnItemClickListenner { imageAdapter = ImageAdapter(activity!!, bannarData, object : OnItemClickListenner {
override fun onClick(pos: Int, model: Any) = Unit override fun onClick(pos: Int, model: Any) = Unit
}) })
@@ -904,7 +911,15 @@ class HomeFragment : CKBaseFragment() {
super.onResume() super.onResume()
if (CKApp.app.isUserLoggedIn && loadedLoginToken != CKApp.app.token) { if (CKApp.app.isUserLoggedIn && loadedLoginToken != CKApp.app.token) {
initData() initData()
} else if (CKApp.app.isUserLoggedIn && shouldRefreshPetListOnResume) {
getPetList()
} }
shouldRefreshPetListOnResume = false
}
override fun onPause() {
super.onPause()
shouldRefreshPetListOnResume = true
} }
private fun resetLoggedOutState() { private fun resetLoggedOutState() {

View File

@@ -1,5 +1,6 @@
package com.ck.ckcollar.app.ui.act.mine package com.ck.ckcollar.app.ui.act.mine
import android.Manifest
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
@@ -10,24 +11,23 @@ import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.webkit.MimeTypeMap
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.BaseAdapter import android.widget.BaseAdapter
import android.widget.ImageView import android.widget.ImageView
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.lifecycle.lifecycleScope
import com.ck.ckcollar.app.R import com.ck.ckcollar.app.R
import com.ck.ckcollar.app.databinding.ActivityFeedBackBinding import com.ck.ckcollar.app.databinding.ActivityFeedBackBinding
import com.ck.ckcollar.app.databinding.LayoutItemImage2Binding 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.ImageModel
import com.ck.ckcollar.app.model.UploadModel 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.act.petmanager.ImageViewActivity
import com.ck.ckcollar.app.ui.base.CKBaseActivity import com.ck.ckcollar.app.ui.base.CKBaseActivity
import com.ck.ckcollar.app.ui.dialog.CKAlertDialog import com.ck.ckcollar.app.ui.dialog.CKAlertDialog
import com.ck.ckcollar.app.ui.dialog.CKPhotoDialog 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.Const
import com.ck.ckcollar.app.utils.LubanCompressUtil
import com.ck.ckcollar.app.utils.networkone.ICallback import com.ck.ckcollar.app.utils.networkone.ICallback
import com.ck.ckcollar.app.utils.networkone.TTHttpClient 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.TTRequestData
@@ -36,8 +36,10 @@ import com.cyclone.shadowsocks.utils.PreferencesUtils
import com.google.gson.Gson import com.google.gson.Gson
import com.tt.kit.model.Data import com.tt.kit.model.Data
import com.tt.kit.model.DataCollection import com.tt.kit.model.DataCollection
import com.tt.kit.utils.TTLog
import com.tt.kit.utils.TTUtils import com.tt.kit.utils.TTUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
class FeedBackActivity : CKBaseActivity() { class FeedBackActivity : CKBaseActivity() {
@@ -84,13 +86,30 @@ class FeedBackActivity : CKBaseActivity() {
}) })
return return
} }
//相册 CKPhotoDialog(
val i = Intent( this@FeedBackActivity,
Intent.ACTION_PICK, object : CKPhotoDialog.PhotoClickListener {
MediaStore.Images.Media.EXTERNAL_CONTENT_URI override fun onBack(mark: Int, position: Int) {
) try {
i.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,"image/*"); if (mark == 0) {
startActivityForResult(i, 56) 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<String>) {
if (requestCode == REQUEST_CAMERA_PERMISSION) {
takePhoto()
}
}
fun upload(){ fun upload(){
if (binding.editConten.isEmpty()){ if (binding.editConten.isEmpty()){
@@ -162,11 +187,120 @@ class FeedBackActivity : CKBaseActivity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 56 && resultCode == RESULT_OK) { if (requestCode == REQUEST_CAMERA && resultCode == RESULT_OK) {
var path = TTUtils.getImageUrl(this, data!!.getData()); val path = PreferencesUtils.getString(this, Const.IMAGE_PATH, "").orEmpty()
this.data.add(this.data.size - 1,ImageModel().apply { this.path = path }) compressAndAddImage(path)
imageAdapter.data = this.data } else if (requestCode == REQUEST_ALBUM && resultCode == RESULT_OK) {
imageAdapter.notifyDataSetChanged() 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() {
} }
} }
companion object {
private const val REQUEST_CAMERA = 55
private const val REQUEST_ALBUM = 56
private const val REQUEST_CAMERA_PERMISSION = 1301
}
} }

View File

@@ -97,6 +97,12 @@ class ReturnDeviceActivity : CKBaseActivity() {
return 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()){ if (binding.textReason.isEmpty()){
toast(getString(R.string.input_return_reason)) toast(getString(R.string.input_return_reason))
return return
@@ -124,4 +130,8 @@ class ReturnDeviceActivity : CKBaseActivity() {
} }
}) })
} }
companion object {
private val PHONE_PATTERN = Regex("^1[3-9]\\d{9}$")
}
} }

View File

@@ -137,8 +137,8 @@ class LogisticsInfoActivity : CKBaseActivity() {
model = Gson().fromJson<OrderModel.Order>(getReceiveParam().get("data")!!.stringValue.toString(), model = Gson().fromJson<OrderModel.Order>(getReceiveParam().get("data")!!.stringValue.toString(),
OrderModel.Order::class.java) OrderModel.Order::class.java)
binding.textReceiveName.text = model.receiverName + " " + model.receiverPhone binding.textReceiveName.text = model.receiverAddress + "\n" + model.receiverName + " " + model.receiverPhone
getData() getData()
} }

View File

@@ -41,6 +41,7 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.math.BigDecimal import java.math.BigDecimal
import java.math.RoundingMode
class PayOrderActivity : CKBaseActivity() { class PayOrderActivity : CKBaseActivity() {
@@ -88,7 +89,7 @@ class PayOrderActivity : CKBaseActivity() {
binding.btnBalance.setOnClickListener { binding.btnBalance.setOnClickListener {
if (memberModel?.balance == 0.0){ if ((memberModel?.balance ?: 0.0) <= 0.0){
toast(getString(R.string.balance_over)) toast(getString(R.string.balance_over))
return@setOnClickListener return@setOnClickListener
} }
@@ -204,17 +205,21 @@ class PayOrderActivity : CKBaseActivity() {
fun showPayBottomView(){ fun showPayBottomView(){
var showBalance = model.payAmount var showBalance = BigDecimal.valueOf(model.payAmount).setScale(2, RoundingMode.HALF_UP)
if (selectPoints){ if (selectPoints){
CKApp.app.userInfo?.intoAmounts?.let { memberModel?.intoAmounts?.let { intoAmounts ->
showBalance = showBalance - BigDecimal(memberModel?.points!!).multiply( val pointsDeduction = BigDecimal.valueOf(memberModel!!.points)
BigDecimal(memberModel!!.intoAmounts!!)).setScale(2).toDouble() .multiply(BigDecimal(intoAmounts))
if (showBalance < 0.0){ .setScale(2, RoundingMode.HALF_UP)
showBalance = 0.00 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{ var passwordBottomFragment = PayPasswordBottomFragment(showBalance.toString(), "余额",object : PayPasswordBottomFragment.PasswordCallBack{
override fun callBack(password: String) { override fun callBack(password: String) {

View File

@@ -251,10 +251,12 @@ class RefundActivity: CKBaseActivity() {
showProgeassDialog(getString(R.string.submit)) showProgeassDialog(getString(R.string.submit))
var commodityState = if(model.orderStatus >= 3){
"1" var commodityState = -1;
if ("0".equals(refundType)){
commodityState = 0
}else{ }else{
"0" commodityState = 1
} }
var param = TTRequestParam().apply { var param = TTRequestParam().apply {
add(TTRequestData("orderId",model.id)) add(TTRequestData("orderId",model.id))

View File

@@ -2,7 +2,6 @@ package com.ck.ckcollar.app.ui.act.petmanager
import android.Manifest import android.Manifest
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@@ -12,10 +11,12 @@ import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.webkit.MimeTypeMap
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.BaseAdapter import android.widget.BaseAdapter
import android.widget.ImageView import android.widget.ImageView
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.ck.ckcollar.app.R import com.ck.ckcollar.app.R
import com.ck.ckcollar.app.databinding.ActivityAddPetBinding 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.ui.dialog.CKPickViewDialog
import com.ck.ckcollar.app.utils.CKUtils import com.ck.ckcollar.app.utils.CKUtils
import com.ck.ckcollar.app.utils.Const 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.ICallback
import com.ck.ckcollar.app.utils.networkone.TTHttpClient 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.TTRequestData
@@ -41,6 +43,9 @@ import com.tt.kit.model.DataCollection
import com.tt.kit.utils.LiveDataBus import com.tt.kit.utils.LiveDataBus
import com.tt.kit.utils.TTLog import com.tt.kit.utils.TTLog
import com.tt.kit.utils.TTUtils import com.tt.kit.utils.TTUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
class AddPetActivity : CKBaseActivity() { class AddPetActivity : CKBaseActivity() {
@@ -284,6 +289,7 @@ class AddPetActivity : CKBaseActivity() {
} }
} }
showProgeassDialog() showProgeassDialog()
//先上传照片
TTHttpClient.postFile(this,"上传照片s", param,object : ICallback{ TTHttpClient.postFile(this,"上传照片s", param,object : ICallback{
override fun onSuccess(data: String) { override fun onSuccess(data: String) {
var model = Gson().fromJson<UploadModel>(data,UploadModel::class.java) var model = Gson().fromJson<UploadModel>(data,UploadModel::class.java)
@@ -330,21 +336,86 @@ class AddPetActivity : CKBaseActivity() {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 55 && resultCode == RESULT_OK) { if (requestCode == 55 && resultCode == RESULT_OK) {
var path = PreferencesUtils.getString(this@AddPetActivity, Const.IMAGE_PATH,"") val path = PreferencesUtils.getString(this@AddPetActivity, Const.IMAGE_PATH, "")
this.data.add(this.data.size - 1,ImageModel().apply { this.path = path }) compressAndAddImage(path.orEmpty())
imageAdapter.data = this.data
imageAdapter.notifyDataSetChanged()
}else if (requestCode == 56 && resultCode == RESULT_OK) { }else if (requestCode == 56 && resultCode == RESULT_OK) {
var path = TTUtils.getImageUrl(this, data!!.getData()); data?.data?.let(::handleSelectedImage)
this.data.add(this.data.size - 1,ImageModel().apply { this.path = path }) ?: toast(getString(R.string.read_photo_failed))
imageAdapter.data = this.data
imageAdapter.notifyDataSetChanged()
}else if (requestCode == 555 && resultCode == RESULT_OK) { }else if (requestCode == 555 && resultCode == RESULT_OK) {
var path = PreferencesUtils.getString(this@AddPetActivity, Const.IMAGE_PATH,"") var path = PreferencesUtils.getString(this@AddPetActivity, Const.IMAGE_PATH,"")
uploadImage(path!!) uploadImage(path!!)
} }
binding.textPhotoNum.setText((this.data.size - 1 ).toString() + "/3") 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() { fun takePhoto() {
try { try {
val openCameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) val openCameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
@@ -509,13 +580,10 @@ class AddPetActivity : CKBaseActivity() {
layoutItemImageBinding.imageDelete.visibility = View.GONE layoutItemImageBinding.imageDelete.visibility = View.GONE
} else { } else {
layoutItemImageBinding.image.scaleType = ImageView.ScaleType.CENTER_CROP layoutItemImageBinding.image.scaleType = ImageView.ScaleType.CENTER_CROP
layoutItemImageBinding.image.setImageBitmap( Glide.with(context)
TTUtils.getBitmap( .load(model.path?.let(::File))
model.path, .centerCrop()
200, .into(layoutItemImageBinding.image)
200
)
)
layoutItemImageBinding.imageDelete.visibility = View.VISIBLE layoutItemImageBinding.imageDelete.visibility = View.VISIBLE
layoutItemImageBinding.imageDelete.setOnClickListener { layoutItemImageBinding.imageDelete.setOnClickListener {

View File

@@ -3,29 +3,19 @@ package com.ck.ckcollar.app.ui.act.petmanager
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import androidx.viewpager.widget.PagerAdapter import androidx.viewpager.widget.PagerAdapter
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.ck.ckcollar.app.R import com.ck.ckcollar.app.R
import com.ck.ckcollar.app.databinding.ActivityImageViewBinding 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.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.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.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.tt.kit.model.DataCollection
class ImageViewActivity : CKBaseActivity() { class ImageViewActivity : CKBaseActivity() {
override val useDarkStatusBarIcons: Boolean = false override val useDarkStatusBarIcons: Boolean = false
lateinit var binding: ActivityImageViewBinding lateinit var binding: ActivityImageViewBinding
var views = mutableListOf<View>()
var data : List<String>? = null var data : List<String>? = null
var pos = 0 var pos = 0
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -38,17 +28,9 @@ class ImageViewActivity : CKBaseActivity() {
} }
override fun initView() { 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() { binding.viewPager.adapter = object : PagerAdapter() {
override fun getCount(): Int { override fun getCount(): Int {
return views.size return data.orEmpty().size
} }
override fun isViewFromObject( override fun isViewFromObject(
@@ -59,12 +41,21 @@ class ImageViewActivity : CKBaseActivity() {
} }
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { 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 { override fun instantiateItem(container: ViewGroup, position: Int): Any {
container.addView(views.get(position)) val itemBinding = LayoutItemImageViewBinding.inflate(
return views.get(position) 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
} }
} }

View File

@@ -1,6 +1,7 @@
package com.ck.ckcollar.app.ui.act.shop package com.ck.ckcollar.app.ui.act.shop
import android.content.Context import android.content.Context
import android.content.pm.ApplicationInfo
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
@@ -8,6 +9,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.alipay.sdk.app.EnvUtils
import com.alipay.sdk.app.PayTask import com.alipay.sdk.app.PayTask
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.ck.ckcollar.app.CKApp import com.ck.ckcollar.app.CKApp
@@ -49,6 +51,7 @@ import kotlinx.coroutines.withContext
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import java.math.BigDecimal import java.math.BigDecimal
import java.math.RoundingMode
class OrderActivity : CKBaseActivity() { class OrderActivity : CKBaseActivity() {
@@ -95,7 +98,7 @@ class OrderActivity : CKBaseActivity() {
binding.btnWX.setOnClickListener(payClick) binding.btnWX.setOnClickListener(payClick)
binding.btnBalance.setOnClickListener { binding.btnBalance.setOnClickListener {
if (memberModel?.balance == 0.0){ if ((memberModel?.balance ?: 0.0) <= 0.0){
toast(getString(R.string.balance_over)) toast(getString(R.string.balance_over))
return@setOnClickListener return@setOnClickListener
} }
@@ -108,6 +111,11 @@ class OrderActivity : CKBaseActivity() {
} }
} }
binding.btnPoints.setOnClickListener { binding.btnPoints.setOnClickListener {
if ((memberModel?.points ?: 0.0) <= 0.0) {
toast(getString(R.string.points_over))
return@setOnClickListener
}
selectPoints = !selectPoints selectPoints = !selectPoints
if (selectPoints){ if (selectPoints){
binding.image4.setImageResource(R.mipmap.image_address_check_select) binding.image4.setImageResource(R.mipmap.image_address_check_select)
@@ -123,7 +131,7 @@ class OrderActivity : CKBaseActivity() {
} }
binding.btnBuyNow.setOnClickListener { binding.btnBuyNow.setOnClickListener {
if (selectBalance){ if (selectBalance && !canPointsCoverPayAmount()){
getPayPassword() getPayPassword()
}else{ }else{
order("") order("")
@@ -327,6 +335,10 @@ class OrderActivity : CKBaseActivity() {
fun aliPay(orderStr: String) { fun aliPay(orderStr: String) {
GlobalScope.launch(Dispatchers.IO) { 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 alipay = PayTask(this@OrderActivity)
val result = alipay.payV2(orderStr, true) val result = alipay.payV2(orderStr, true)
@@ -393,16 +405,18 @@ class OrderActivity : CKBaseActivity() {
fun showPayBottomView(){ fun showPayBottomView(){
var showBalance = model.payAmount var showBalance = BigDecimal.valueOf(model.payAmount).setScale(2, RoundingMode.HALF_UP)
if (selectPoints){ if (selectPoints){
CKApp.app.userInfo?.intoAmounts?.let { calculatePointsDeduction()?.let { pointsDeduction ->
showBalance = showBalance - BigDecimal(memberModel?.points!!).multiply( showBalance = showBalance.subtract(pointsDeduction)
BigDecimal(memberModel!!.intoAmounts!!)).setScale(2).toDouble() .max(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP))
if (showBalance < 0.0){
showBalance = 0.00
}
} }
} }
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{ var passwordBottomFragment = PayPasswordBottomFragment(showBalance.toString(), "余额",object : PayPasswordBottomFragment.PasswordCallBack{
override fun callBack(password: String) { override fun callBack(password: String) {
@@ -413,6 +427,21 @@ class OrderActivity : CKBaseActivity() {
passwordBottomFragment.show(supportFragmentManager, "PayPasswordBottomFragment") 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 { var payClick = object : View.OnClickListener {
override fun onClick(v: View?) { override fun onClick(v: View?) {
binding.image1.setImageResource(R.mipmap.image_address_check_narmal) binding.image1.setImageResource(R.mipmap.image_address_check_narmal)

View File

@@ -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()
}
}

View File

@@ -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()
)
}
}

View File

@@ -3,24 +3,63 @@ package com.ck.ckcollar.app.widget
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.MotionEvent import android.view.MotionEvent
import androidx.appcompat.widget.AppCompatImageView
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
class CKViewPager : ViewPager{ class CKViewPager : ViewPager{
private var isMultiTouchGesture = false
constructor(context: Context) : super(context) constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 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 { 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 { try {
return super.onInterceptTouchEvent(ev) return super.onInterceptTouchEvent(ev)
} catch (e: IllegalArgumentException) {
}catch (e : Exception){
e.printStackTrace() e.printStackTrace()
} }
return false return false
}
override fun onTouchEvent(ev: MotionEvent?): Boolean {
if (ev == null) return false
return try {
super.onTouchEvent(ev)
} catch (e: IllegalArgumentException) {
e.printStackTrace()
false
}
} }
} }

View File

@@ -88,6 +88,7 @@
android:textColorHint="@color/textSecondColor" android:textColorHint="@color/textSecondColor"
android:maxLength="11" android:maxLength="11"
android:maxLines="1" android:maxLines="1"
android:singleLine="true"
android:inputType="number" android:inputType="number"
android:textSize="14sp"></com.ck.ckcollar.app.widget.CKEditView> android:textSize="14sp"></com.ck.ckcollar.app.widget.CKEditView>
@@ -165,7 +166,8 @@
android:textColorHint="@color/textSecondColor" android:textColorHint="@color/textSecondColor"
android:maxLength="50" android:maxLength="50"
android:maxLines="3" android:maxLines="3"
android:textSize="14sp"></com.ck.ckcollar.app.widget.CKEditView> android:textSize="14sp"
android:singleLine="true"></com.ck.ckcollar.app.widget.CKEditView>
</com.ck.ckcollar.app.widget.CKLinearLayout> </com.ck.ckcollar.app.widget.CKLinearLayout>

View File

@@ -9,13 +9,22 @@
android:background="?attr/mainBackground" android:background="?attr/mainBackground"
android:orientation="vertical"> android:orientation="vertical">
<androidx.core.widget.NestedScrollView <com.scwang.smartrefresh.layout.SmartRefreshLayout
android:id="@+id/refreshLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<com.scwang.smartrefresh.layout.header.ClassicsHeader
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.ck.ckcollar.app.widget.CKLinearLayout <com.ck.ckcollar.app.widget.CKLinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
@@ -223,8 +232,9 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView" android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:nestedScrollingEnabled="false"
/> />
</com.ck.ckcollar.app.widget.CKLinearLayout> </com.ck.ckcollar.app.widget.CKLinearLayout>
@@ -232,17 +242,8 @@
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
<com.scwang.smartrefresh.layout.SmartRefreshLayout
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.scwang.smartrefresh.layout.header.ClassicsHeader
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<!--内容-->
</com.scwang.smartrefresh.layout.SmartRefreshLayout> </com.scwang.smartrefresh.layout.SmartRefreshLayout>
<!--内容-->
</com.ck.ckcollar.app.widget.CKLinearLayout> </com.ck.ckcollar.app.widget.CKLinearLayout>

View File

@@ -12,8 +12,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingLeft="20dp" android:paddingLeft="20dp"
android:visibility="gone" android:visibility="gone">
>
<com.ck.ckcollar.app.widget.CKRoundImageView <com.ck.ckcollar.app.widget.CKRoundImageView
android:id="@+id/imageFrom" android:id="@+id/imageFrom"
@@ -27,7 +26,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingLeft="5dp" android:paddingLeft="5dp"
android:paddingRight="80dp" android:paddingRight="20dp"
android:orientation="vertical"> android:orientation="vertical">
@@ -54,7 +53,7 @@
android:background="@drawable/bg_message_from" android:background="@drawable/bg_message_from"
android:padding="8dp" android:padding="8dp"
android:layout_marginTop="3dp" android:layout_marginTop="3dp"
android:maxWidth="200dp" android:maxWidth="280dp"
android:textSize="14sp"></com.ck.ckcollar.app.widget.CKTextView> android:textSize="14sp"></com.ck.ckcollar.app.widget.CKTextView>
@@ -68,7 +67,7 @@
android:paddingVertical="3dp" android:paddingVertical="3dp"
android:paddingHorizontal="5dp" android:paddingHorizontal="5dp"
android:layout_marginTop="3dp" android:layout_marginTop="3dp"
android:maxWidth="200dp" android:maxWidth="280dp"
android:visibility="gone" android:visibility="gone"
></com.ck.ckcollar.app.widget.CKImageView> ></com.ck.ckcollar.app.widget.CKImageView>
</com.ck.ckcollar.app.widget.CKLinearLayout> </com.ck.ckcollar.app.widget.CKLinearLayout>

View File

@@ -97,7 +97,7 @@
android:text="@string/no_reason_text" android:text="@string/no_reason_text"
android:background="@drawable/bg_no_reason" android:background="@drawable/bg_no_reason"
android:paddingHorizontal="5dp" android:paddingHorizontal="5dp"
></com.ck.ckcollar.app.widget.CKTextView> android:visibility="gone"/>
</com.ck.ckcollar.app.widget.CKLinearLayout> </com.ck.ckcollar.app.widget.CKLinearLayout>
<com.ck.ckcollar.app.widget.CKLinearLayout <com.ck.ckcollar.app.widget.CKLinearLayout

View File

@@ -69,7 +69,7 @@
android:text="@string/no_reason_text" android:text="@string/no_reason_text"
android:background="@drawable/bg_cz_button" android:background="@drawable/bg_cz_button"
android:paddingHorizontal="5dp" android:paddingHorizontal="5dp"
></com.ck.ckcollar.app.widget.CKTextView> android:visibility="gone"/>
</com.ck.ckcollar.app.widget.CKLinearLayout> </com.ck.ckcollar.app.widget.CKLinearLayout>
<com.ck.ckcollar.app.widget.CKLinearLayout <com.ck.ckcollar.app.widget.CKLinearLayout

View File

@@ -75,7 +75,7 @@
<string name="add_pet">添加宠物</string> <string name="add_pet">添加宠物</string>
<string name="intelligent_collar_device">智能项圈设备</string> <string name="intelligent_collar_device">智能项圈设备</string>
<string name="unbound">未绑定</string> <string name="unbound">未绑定</string>
<string name="bound_device_alert">智能项圈,科技护宠每一刻</string> <string name="bound_device_alert">智能项圈,科技护宠每一刻</string>
<string name="scan">扫一扫</string> <string name="scan">扫一扫</string>
<string name="equipment_positioning">设备定位</string> <string name="equipment_positioning">设备定位</string>
<string name="no_position">当前位置:当前无定位</string> <string name="no_position">当前位置:当前无定位</string>
@@ -122,6 +122,7 @@
<string name="select_vaccination">请选择是否接种</string> <string name="select_vaccination">请选择是否接种</string>
<string name="upload_photos">上传照片</string> <string name="upload_photos">上传照片</string>
<string name="please_dupload_photos">请上传照片</string> <string name="please_dupload_photos">请上传照片</string>
<string name="read_photo_failed">图片读取失败,请重新选择</string>
<!-- 宠物顾问模块 --> <!-- 宠物顾问模块 -->
<string name="pet_advisor">宠物顾问(早 9:00-晚 21:00</string> <string name="pet_advisor">宠物顾问(早 9:00-晚 21:00</string>
<string name="pet_advisor2">宠物顾问</string> <string name="pet_advisor2">宠物顾问</string>
@@ -319,6 +320,7 @@
<string name="input_name2">请输入姓名</string> <string name="input_name2">请输入姓名</string>
<string name="contact_info">联系方式</string> <string name="contact_info">联系方式</string>
<string name="input_contact_info">请输入联系方式</string> <string name="input_contact_info">请输入联系方式</string>
<string name="invalid_phone_format">请输入正确的手机号</string>
<string name="return_reason">退回理由</string> <string name="return_reason">退回理由</string>
<string name="input_return_reason">请输入退回理由</string> <string name="input_return_reason">请输入退回理由</string>
<string name="coll_num">项圈编号</string> <string name="coll_num">项圈编号</string>
@@ -481,7 +483,7 @@
<string name="return_amount">退款</string> <string name="return_amount">退款</string>
<string name="make_phone">打电话</string> <string name="make_phone">打电话</string>
<string name="view_express">查看物流</string> <string name="view_express">查看物流</string>
<string name="choost_pet">选择宠物</string> <string name="choost_pet">宠物名字</string>
<string name="add_success">添加成功</string> <string name="add_success">添加成功</string>
<string name="edit_success">修改成功</string> <string name="edit_success">修改成功</string>
<string name="delete_success">删除成功</string> <string name="delete_success">删除成功</string>
@@ -577,7 +579,7 @@
<string name="manage">管理</string> <string name="manage">管理</string>
<string name="over_stock">库存不足</string> <string name="over_stock">库存不足</string>
<string name="balance_over">余额不足</string> <string name="balance_over">余额不足</string>
<string name="points_over">余额不足</string> <string name="points_over">积分不足</string>
<string name="please_select_data">请选择商品</string> <string name="please_select_data">请选择商品</string>
<string name="please_select_payment_method">请选择支付方式</string> <string name="please_select_payment_method">请选择支付方式</string>
<string name="old_new_password_both">新密码和旧密码相同</string> <string name="old_new_password_both">新密码和旧密码相同</string>