Files
assistant-android/app/src/main/java/com/gh/base/BaseRichEditorViewModel.kt

465 lines
18 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.gh.base
import android.app.Application
import android.content.Intent
import android.graphics.Bitmap
import android.media.ThumbnailUtils
import android.provider.MediaStore
import android.text.TextUtils
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.WaitingDialogFragment
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.runOnUiThread
import com.gh.gamecenter.core.utils.MD5Utils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.common.entity.ErrorEntity
import com.gh.gamecenter.entity.LocalVideoEntity
import com.gh.gamecenter.entity.QuoteCountEntity
import com.gh.gamecenter.qa.BbsType
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService
import com.gh.gamecenter.video.upload.OnUploadListener
import com.gh.gamecenter.video.upload.UploadManager
import com.google.gson.JsonObject
import com.lightgame.utils.Utils
import com.zhihu.matisse.Matisse
import com.zhihu.matisse.internal.utils.PathUtils
import io.reactivex.disposables.Disposable
import okhttp3.ResponseBody
import retrofit2.HttpException
import java.io.File
import java.io.FileOutputStream
import kotlin.collections.set
// TODO: 移动到module_bbs模块
abstract class BaseRichEditorViewModel(application: Application) : AndroidViewModel(application) {
val mApi: ApiService = RetrofitManager.getInstance().api
val processDialog = MediatorLiveData<WaitingDialogFragment.WaitingDialogData>()
val chooseImagesUpload = MutableLiveData<LinkedHashMap<String, String>>()
val chooseImagesUploadSuccess = MutableLiveData<LinkedHashMap<String, String>>()
var uploadImageSubscription: Disposable? = null
val mapImages = HashMap<String, String>()
val localVideoList = ArrayList<LocalVideoEntity>()
val uploadVideoErrorList = ArrayList<LocalVideoEntity>()
var currentUploadingVideo: LocalVideoEntity? = null
var type: String = "" //游戏论坛game_bbs 官方论坛official_bbs
private var mUploadVideoListener: UploadVideoListener? = null
val TITLE_MIN_LENGTH = 6
val MIN_TEXT_LENGTH = 6
val MAX_TEXT_LENGTH = 10000
val FILE_HOST = "file:///"
var id = ""//视频标记
var videoId = ""//更改封面视频id
val quoteCountEntity = QuoteCountEntity()//数据上报用
fun setUploadVideoListener(uploadVideoListener: UploadVideoListener) {
this.mUploadVideoListener = uploadVideoListener
}
//检查图片是否符合规则并上传图片
fun uploadPic(data: Intent) {
val uris = Matisse.obtainResult(data)
val rawImgUrlList = ArrayList<String>() // 需要上传图片的原始地址列表
val uploadingImgList = ArrayList<String>() // 正在上传图片的地址列表(已压缩处理)
val compressedImgUrlList = ArrayList<String>() // 压缩处理后图片的地址列表
for (uri in uris) {
val picturePath = PathUtils.getPath(getApplication(), uri)
if (picturePath != null) {
if (File(picturePath).length() > ImageUtils.getUploadFileMaxSize()) {
val count = ImageUtils.getUploadFileMaxSize() / 1024 / 1024
val application: Application = getApplication()
Utils.toast(
getApplication(),
application.getString(R.string.pic_max_hint, count)
)
continue
}
Utils.log("picturePath = $picturePath")
rawImgUrlList.add(picturePath)
} else {
Utils.log("picturePath is null")
}
}
if (rawImgUrlList.size == 0) return
val imageType = when (getRichType()) {
RichType.ARTICLE -> UploadImageUtils.UploadType.community_article
RichType.QUESTION -> UploadImageUtils.UploadType.question
else -> UploadImageUtils.UploadType.poster
}
uploadImageSubscription = UploadImageUtils.compressAndUploadImageList(
imageType,
rawImgUrlList,
false,
object : UploadImageUtils.OnUploadImageListListener {
override fun onProgress(total: Long, progress: Long) {}
override fun onCompressSuccess(imageUrls: List<String>) {
val chooseImageMd5Map = LinkedHashMap<String, String>()
imageUrls.forEach {
chooseImageMd5Map[MD5Utils.getUrlMD5(it)] = ""
}
uploadingImgList.addAll(imageUrls)
compressedImgUrlList.addAll(imageUrls)
chooseImagesUpload.postValue(chooseImageMd5Map)
}
override fun onSingleSuccess(imageUrlMap: Map<String, String>) {
imageUrlMap.forEach {
if (uploadingImgList.contains(it.key)) {
uploadingImgList.remove(it.key)
}
}
val map = LinkedHashMap<String, String>()
for (key in imageUrlMap.keys) {
map[MD5Utils.getUrlMD5(key)] = FILE_HOST + key.decodeURI()
mapImages[TextUtils.htmlEncode(key).decodeURI()] = imageUrlMap[key] ?: ""
}
chooseImagesUploadSuccess.value = map
}
override fun onSuccess(imageUrlMap: LinkedHashMap<String, String>, errorMap: Map<String, Exception>) {
imageUrlMap.forEach {
if (uploadingImgList.contains(it.key)) {
uploadingImgList.remove(it.key)
}
}
var errorSize = compressedImgUrlList.size - imageUrlMap.size
if (errorSize > 0 || uploadingImgList.isNotEmpty()) {
val errorImageMap = LinkedHashMap<String, String>()
for (key in errorMap.keys) {
errorImageMap[MD5Utils.getUrlMD5(key)] = ""
}
for (rawImgUrl in compressedImgUrlList) {
if (!imageUrlMap.containsKey(rawImgUrl)) {
errorImageMap[MD5Utils.getUrlMD5(rawImgUrl)] = ""
}
}
errorSize = if (errorMap.isEmpty()) {
errorImageMap.size
} else {
errorMap.size + errorImageMap.size
}
// value为空会删除PlaceholderImage
chooseImagesUploadSuccess.value = errorImageMap
if (handleUploadError(errorSize, errorMap)) return
if (errorSize > 0) {
ToastUtils.showToast(errorSize.toString() + "张图片上传失败")
}
}
}
override fun onError(errorMap: Map<String, Exception>) {
val errorSize = uploadingImgList.size
if (uploadingImgList.size > 0) {
val map = LinkedHashMap<String, String>()
for (key in uploadingImgList) {
map[MD5Utils.getUrlMD5(key)] = ""
}
// value为空会删除PlaceholderImage
chooseImagesUploadSuccess.value = map
}
if (handleUploadError(errorSize, errorMap)) return
if (errorSize == 0) return
if (errorSize == 1) {
ToastUtils.showToast("图片上传失败")
} else {
ToastUtils.showToast(errorSize.toString() + "张图片上传失败")
}
}
})
}
/**
* 处理上传错误 (包括超时 cancel 的异常)
* @return 如果 toast 过了就返回 true
*/
private fun handleUploadError(
errorSize: Int,
errorMap: Map<String, Exception>
): Boolean {
for (error in errorMap.values) {
if (error is HttpException && error.code() == 403) {
val e = error.response()?.errorBody()?.string()?.toObject<ErrorEntity>()
if (e != null && e.code == 403017) {
ToastUtils.showToast(errorSize.toString() + "张图片的宽或高超过限制,请裁剪后上传")
} else {
ToastUtils.showToast(errorSize.toString() + "张违规图片上传失败")
}
return true
}
}
return false
}
fun uploadPoster(picturePath: String) {
processDialog.postValue(WaitingDialogFragment.WaitingDialogData("封面上传中...", true))
uploadImageSubscription =
UploadImageUtils.compressAndUploadImage(
UploadImageUtils.UploadType.poster,
picturePath,
false,
object : UploadImageUtils.OnUploadImageListener {
override fun onSuccess(imageUrl: String) {
patchVideoPoster(imageUrl)
}
override fun onError(e: Throwable?) {
handleUploadPosterResult(true)
}
override fun onProgress(total: Long, progress: Long) {
}
})
}
private fun patchVideoPoster(poster: String) {
if (id.isEmpty() || videoId.isEmpty()) return
val map = hashMapOf("poster" to poster, "type" to getVideoType())
mApi.patchInsertVideo(videoId, map.toRequestBody())
.compose(observableToMain())
.subscribe(object : Response<ResponseBody>() {
override fun onResponse(response: ResponseBody?) {
super.onResponse(response)
mUploadVideoListener?.changePoster(id, poster)
handleUploadPosterResult(false)
}
override fun onFailure(e: HttpException?) {
super.onFailure(e)
handleUploadPosterResult(true)
}
})
}
private fun handleUploadPosterResult(isFailure: Boolean = false) {
processDialog.postValue(WaitingDialogFragment.WaitingDialogData("封面上传中...", false))
if (isFailure) {
ToastUtils.showToast("封面更改失败")
}
id = ""
videoId = ""
}
fun deleteVideo(id: String) {
if (localVideoList.isNotEmpty()) {
val video = localVideoList.find { it.id == id }
if (video != null) {
if (UploadManager.isUploading(video.filePath)) {
UploadManager.cancelTask(video.filePath)
}
localVideoList.remove(video)
}
}
if (uploadVideoErrorList.isNotEmpty()) {
val video = uploadVideoErrorList.find { it.id == id }
if (video != null) {
uploadVideoErrorList.remove(video)
}
}
if (currentUploadingVideo?.id == id) {
currentUploadingVideo = null
uploadVideo()
}
}
fun uploadVideo() {
if (currentUploadingVideo != null) return
if (localVideoList.isEmpty()) return
currentUploadingVideo = localVideoList[0]
UploadManager.createUploadTask(currentUploadingVideo?.filePath
?: "", object : OnUploadListener {
override fun onProgressChanged(
uploadFilePath: String,
currentSize: Long,
totalSize: Long,
speed: Long
) {
runOnUiThread {
val percent = (currentSize * 100 / totalSize.toFloat()).roundTo(1)
currentUploadingVideo?.id?.let {
mUploadVideoListener?.updateVideoProgress(it, percent.toString())
}
}
}
override fun onUploadSuccess(uploadFilePath: String, url: String) {
if (currentUploadingVideo != null) {
postVideoPosterAndInfo(uploadFilePath, url)
}
}
override fun onUploadFailure(uploadFilePath: String, errorMsg: String) {
uploadVideoFailure()
}
})
}
private fun postVideoPosterAndInfo(uploadFilePath: String, url: String) {
val localVideoPoster =
getApplication<Application>().cacheDir.absolutePath + File.separator + System.currentTimeMillis() + ".jpg"
try {
val bmp = ThumbnailUtils.createVideoThumbnail(
uploadFilePath,
MediaStore.Images.Thumbnails.MINI_KIND
)
// bmp 可能为空
FileOutputStream(localVideoPoster).use { out ->
bmp?.compress(Bitmap.CompressFormat.PNG, 100, out)
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
ToastUtils.showToast("视频封面操作失败")
uploadVideoFailure()
return
}
uploadImageSubscription =
UploadImageUtils.compressAndUploadImage(
UploadImageUtils.UploadType.poster,
localVideoPoster,
false,
object : UploadImageUtils.OnUploadImageListener {
override fun onSuccess(imageUrl: String) {
postVideoInfo(url, imageUrl)
}
override fun onError(e: Throwable?) {
uploadVideoFailure()
}
override fun onProgress(total: Long, progress: Long) {
}
})
}
private fun postVideoInfo(url: String, poster: String) {
val map = HashMap<String, Any>().apply {
put("poster", poster)
put("url", url)
put("format", currentUploadingVideo?.format ?: "")
put("size", currentUploadingVideo?.size ?: 0)
put("length", (currentUploadingVideo?.duration ?: 0) / 1000)
put("type", getVideoType())
}
val requestBody = map.toRequestBody()
mApi.insertVideo(requestBody)
.compose(observableToMain())
.subscribe(object : Response<JsonObject>() {
override fun onResponse(response: JsonObject?) {
super.onResponse(response)
if (response != null) {
uploadVideoSuccess(poster, url, response)
}
}
override fun onFailure(e: HttpException?) {
super.onFailure(e)
uploadVideoFailure()
}
})
}
private fun uploadVideoSuccess(poster: String, url: String, data: JsonObject) {
processDialog.postValue(WaitingDialogFragment.WaitingDialogData("封面上传中...", false))
currentUploadingVideo?.let {
mUploadVideoListener?.changePoster(it.id, poster)
mUploadVideoListener?.videoUploadFinished(it.id, url, data)
UploadManager.cancelTask(it.filePath)
localVideoList.remove(it)
}
currentUploadingVideo = null
uploadVideo()
}
private fun uploadVideoFailure() {
processDialog.postValue(WaitingDialogFragment.WaitingDialogData("封面上传中...", false))
currentUploadingVideo?.let {
runOnUiThread {
mUploadVideoListener?.videoUploadFailed(it.id)
}
uploadVideoErrorList.add(it)
localVideoList.remove(it)
UploadManager.cancelTask(it.filePath)
}
currentUploadingVideo = null
uploadVideo()
}
fun checkIsAllUploadedAndToast(): Boolean {
if (localVideoList.isNotEmpty() || uploadVideoErrorList.isNotEmpty()) {
ToastUtils.showToast("视频未上传完成,视频内容保存失败")
return false
}
return true
}
private fun getVideoType(): String {
return when (type) {
BbsType.GAME_BBS.value -> {
when (getRichType()) {
RichType.ARTICLE -> BbsType.GAME_BBS_ARTICLE_INSERT.value
RichType.QUESTION -> BbsType.GAME_BBS_QUESTION_INSERT.value
else -> ""
}
}
BbsType.OFFICIAL_BBS.value -> {
when (getRichType()) {
RichType.ARTICLE -> BbsType.OFFICIAL_BBS_ARTICLE_INSERT.value
RichType.QUESTION -> BbsType.OFFICIAL_BBS_QUESTION_INSERT.value
else -> ""
}
}
else -> ""
}
}
abstract fun getRichType(): RichType
}
interface UploadVideoListener {
/**
* 插入视频占位图
*/
fun insertPlaceholderVideo(id: String, poster: String)
/**
* 更新视频进度条
*/
fun updateVideoProgress(id: String, progress: String)
/**
* 上传视频完成
*/
fun videoUploadFinished(id: String, url: String, msg: JsonObject)
/**
* 更换封面图
*/
fun changePoster(id: String, poster: String)
/**
* 上传失败
*/
fun videoUploadFailed(id: String)
}
enum class RichType {
ARTICLE,
QUESTION,
ANSWER
}