优化
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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<AIMessageModel>(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<AIMessageModel>(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<CustomerMessageModel.MessageItemModel>,
|
||||
var onItemClickListenner: OnItemClickListenner
|
||||
) : RecyclerView.Adapter<CustomerMessageAdapter.CustomerMessageHolder>() {
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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<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>) {
|
||||
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() {
|
||||
|
||||
@@ -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<String>) {
|
||||
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() {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
companion object {
|
||||
private const val REQUEST_CAMERA = 55
|
||||
private const val REQUEST_ALBUM = 56
|
||||
private const val REQUEST_CAMERA_PERMISSION = 1301
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val PHONE_PATTERN = Regex("^1[3-9]\\d{9}$")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +137,8 @@ class LogisticsInfoActivity : CKBaseActivity() {
|
||||
model = Gson().fromJson<OrderModel.Order>(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()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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<UploadModel>(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 {
|
||||
|
||||
@@ -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<View>()
|
||||
var data : List<String>? = 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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTouchEvent(ev: MotionEvent?): Boolean {
|
||||
if (ev == null) return false
|
||||
return try {
|
||||
super.onTouchEvent(ev)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
android:textColorHint="@color/textSecondColor"
|
||||
android:maxLength="11"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:inputType="number"
|
||||
android:textSize="14sp"></com.ck.ckcollar.app.widget.CKEditView>
|
||||
|
||||
@@ -165,7 +166,8 @@
|
||||
android:textColorHint="@color/textSecondColor"
|
||||
android:maxLength="50"
|
||||
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>
|
||||
|
||||
|
||||
@@ -9,13 +9,22 @@
|
||||
android:background="?attr/mainBackground"
|
||||
android:orientation="vertical">
|
||||
|
||||
<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" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.ck.ckcollar.app.widget.CKLinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
@@ -223,8 +232,9 @@
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:nestedScrollingEnabled="false"
|
||||
/>
|
||||
</com.ck.ckcollar.app.widget.CKLinearLayout>
|
||||
|
||||
@@ -232,17 +242,8 @@
|
||||
|
||||
</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.ck.ckcollar.app.widget.CKLinearLayout>
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="20dp"
|
||||
android:visibility="gone"
|
||||
>
|
||||
android:visibility="gone">
|
||||
|
||||
<com.ck.ckcollar.app.widget.CKRoundImageView
|
||||
android:id="@+id/imageFrom"
|
||||
@@ -27,7 +26,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="5dp"
|
||||
android:paddingRight="80dp"
|
||||
android:paddingRight="20dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
@@ -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"></com.ck.ckcollar.app.widget.CKTextView>
|
||||
|
||||
|
||||
@@ -68,7 +67,7 @@
|
||||
android:paddingVertical="3dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:maxWidth="200dp"
|
||||
android:maxWidth="280dp"
|
||||
android:visibility="gone"
|
||||
></com.ck.ckcollar.app.widget.CKImageView>
|
||||
</com.ck.ckcollar.app.widget.CKLinearLayout>
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
android:text="@string/no_reason_text"
|
||||
android:background="@drawable/bg_no_reason"
|
||||
android:paddingHorizontal="5dp"
|
||||
></com.ck.ckcollar.app.widget.CKTextView>
|
||||
android:visibility="gone"/>
|
||||
</com.ck.ckcollar.app.widget.CKLinearLayout>
|
||||
|
||||
<com.ck.ckcollar.app.widget.CKLinearLayout
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
android:text="@string/no_reason_text"
|
||||
android:background="@drawable/bg_cz_button"
|
||||
android:paddingHorizontal="5dp"
|
||||
></com.ck.ckcollar.app.widget.CKTextView>
|
||||
android:visibility="gone"/>
|
||||
</com.ck.ckcollar.app.widget.CKLinearLayout>
|
||||
|
||||
<com.ck.ckcollar.app.widget.CKLinearLayout
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
<string name="add_pet">添加宠物</string>
|
||||
<string name="intelligent_collar_device">智能项圈设备</string>
|
||||
<string name="unbound">未绑定</string>
|
||||
<string name="bound_device_alert">宠遇智能项圈,科技护宠每一刻</string>
|
||||
<string name="bound_device_alert">宠盾智能项圈,科技护宠每一刻</string>
|
||||
<string name="scan">扫一扫</string>
|
||||
<string name="equipment_positioning">设备定位</string>
|
||||
<string name="no_position">当前位置:当前无定位</string>
|
||||
@@ -122,6 +122,7 @@
|
||||
<string name="select_vaccination">请选择是否接种</string>
|
||||
<string name="upload_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_advisor2">宠物顾问</string>
|
||||
@@ -319,6 +320,7 @@
|
||||
<string name="input_name2">请输入姓名</string>
|
||||
<string name="contact_info">联系方式</string>
|
||||
<string name="input_contact_info">请输入联系方式</string>
|
||||
<string name="invalid_phone_format">请输入正确的手机号</string>
|
||||
<string name="return_reason">退回理由</string>
|
||||
<string name="input_return_reason">请输入退回理由</string>
|
||||
<string name="coll_num">项圈编号</string>
|
||||
@@ -481,7 +483,7 @@
|
||||
<string name="return_amount">退款</string>
|
||||
<string name="make_phone">打电话</string>
|
||||
<string name="view_express">查看物流</string>
|
||||
<string name="choost_pet">选择宠物</string>
|
||||
<string name="choost_pet">宠物名字</string>
|
||||
<string name="add_success">添加成功</string>
|
||||
<string name="edit_success">修改成功</string>
|
||||
<string name="delete_success">删除成功</string>
|
||||
@@ -577,7 +579,7 @@
|
||||
<string name="manage">管理</string>
|
||||
<string name="over_stock">库存不足</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_payment_method">请选择支付方式</string>
|
||||
<string name="old_new_password_both">新密码和旧密码相同</string>
|
||||
|
||||
Reference in New Issue
Block a user