优化
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2241
.idea/caches/deviceStreaming.xml
generated
2241
.idea/caches/deviceStreaming.xml
generated
File diff suppressed because it is too large
Load Diff
4
.idea/deploymentTargetSelector.xml
generated
4
.idea/deploymentTargetSelector.xml
generated
@@ -4,10 +4,10 @@
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<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">
|
||||
<handle>
|
||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=D122222104A9" />
|
||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=80bece4" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
|
||||
@@ -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,14 +77,12 @@ class HealthFragment: CKBaseFragment() {
|
||||
}
|
||||
binding.refreshLayout.setEnableLoadMore(false)
|
||||
binding.refreshLayout.setOnRefreshListener {
|
||||
if (!CKApp.app.isUserLoggedIn) {
|
||||
binding.refreshLayout.finishRefresh()
|
||||
return@setOnRefreshListener
|
||||
}
|
||||
page = 1
|
||||
getData()
|
||||
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
|
||||
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
|
||||
)
|
||||
i.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,"image/*");
|
||||
startActivityForResult(i, 56)
|
||||
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,12 +187,121 @@ 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
|
||||
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,7 +137,7 @@ class LogisticsInfoActivity : CKBaseActivity() {
|
||||
model = Gson().fromJson<OrderModel.Order>(getReceiveParam().get("data")!!.stringValue.toString(),
|
||||
OrderModel.Order::class.java)
|
||||
|
||||
binding.textReceiveName.text = model.receiverName + " " + model.receiverPhone
|
||||
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) {
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
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">
|
||||
|
||||
<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