Commit 0a8e6598 authored by yinjiacheng's avatar yinjiacheng

add 图片服务功能

parent 75f0cf84
......@@ -89,6 +89,7 @@ dependencies {
implementation('com.yidian.android:CommonComponent:0.0.88') {
exclude(group: 'com.yidian.android', module: 'NetworkImageLib')
}
implementation files('libs/ks3-androidsdk-1.4.9.jar')
api 'com.github.bumptech.glide:glide:4.11.0'
kapt 'com.github.bumptech.glide:compiler:4.11.0'
......
......@@ -14,6 +14,7 @@ import com.scwang.smart.refresh.layout.constant.SpinnerStyle
import com.yidian.common.http.ApiSaveCookiesInterceptor
import com.yidian.common.http.ApiSetCookiesInterceptor
import com.yidian.common.services.AppUpgradeService
import com.yidian.common.services.ImageService
import com.yidian.common.services.XEventService
import com.yidian.common.services.XPageService
import com.yidian.common.services.http.MBHttpReqService
......@@ -209,6 +210,7 @@ open class YdBaseApplication : Application() {
Zap.addService(XEventService.SERVICE_NAME, XEventService())
Zap.addService(AppUpgradeService.SERVICE_NAME, AppUpgradeService())
Zap.addService(MBHttpReqService.SERVICE_NAME, MBHttpReqService())
Zap.addService(ImageService.SERVICE_NAME, ImageService())
}
/**
......
package com.yidian.common.bean
import com.google.gson.annotations.SerializedName
import java.io.Serializable
/**
* author: yinjiacheng
* date: 7/8/21 8:55 AM
* description: 图片服务-选择图片业务参数数据结构
*/
data class SelectImageParamsBean(
@SerializedName("maxCount")
val maxCount: Int,
@SerializedName("selectedImages")
val selectedImages: List<SelectedImage>
) : Serializable
data class SelectedImage(
@SerializedName("id")
val id: String
) : Serializable
package com.yidian.common.bean
/**
* author: yinjiacheng
* date: 7/8/21 8:43 AM
* description: 图片服务-已选择图片数据结构
*/
data class SelectedImageBean(val id: String, val photo_base64_str: String)
package com.yidian.common.bean
/**
* author: yinjiacheng
* date: 7/8/21 8:43 AM
* description: 图片服务-已上传图片数据结构
*/
data class UploadImageBean(val id: String, val image_uploaded_key: String)
package com.yidian.common.http
import com.google.gson.reflect.TypeToken
import com.yidian.common.YdBaseApplication
import com.yidian.common.http.bean.GetKSYunObjectIdBean
import com.yidian.common.http.bean.GetKSYunTokenBean
import com.yidian.common.http.callback.IGetKSYunObjectIdCallback
import com.yidian.http.ServiceFactory
import com.yidian.utils.ToastUtil
/**
* author: yinjiacheng
* date: 7/8/21 4:54 PM
* description: Common网络请求实现
*/
object CommonDataSource {
// 301 获取金山云token接口
fun getKSYunToken(requestParams: HashMap<String, String?>): HttpResult<GetKSYunTokenBean.Response>? {
val timeStamp = System.currentTimeMillis()
val publicParamsMap = HttpParamsUtils.getPublicParamsMap(timeStamp)
val privateParamsMap = HttpParamsUtils.getPrivateParamsMap(requestParams, timeStamp)
val res: HttpResult<Any?>? = ServiceFactory.getInstance().createService(CommonService::class.java)
.getKSYunToken(publicParamsMap, privateParamsMap)
.execute().body()
if (res?.code == 0) {
return HttpParamsUtils.rsaDecryptResult(res, object : TypeToken<GetKSYunTokenBean.Response>() {}.type)
} else {
val decodeResult = HttpResult<GetKSYunTokenBean.Response>()
decodeResult.code = res?.code!!
decodeResult.reason = res.reason
decodeResult.status = res.status
ToastUtil.showToast(YdBaseApplication.context, res.reason)
}
return null
}
// 302 获取金山云ObjectId和Bucket接口
fun getKSYunObjectId(apiCallback: IGetKSYunObjectIdCallback, requestParams: HashMap<String, String?>) {
val timeStamp = System.currentTimeMillis()
val publicParamsMap = HttpParamsUtils.getPublicParamsMap(timeStamp)
val privateParamsMap = HttpParamsUtils.getPrivateParamsMap(requestParams, timeStamp)
ServiceFactory.getInstance().createService(CommonService::class.java)
.getKSYunObjectId(publicParamsMap, privateParamsMap)
.compose(TransformUtil.defaultSchedulers())
.subscribe(object : HttpResultSubscriber<GetKSYunObjectIdBean.Response>() {
override fun onSuccess(result: HttpResult<GetKSYunObjectIdBean.Response>?) {
apiCallback.getKSYunObjectIdSuccess(result?.result)
}
override fun onFailer(result: HttpResult<GetKSYunObjectIdBean.Response>?) {
apiCallback.getKSYunObjectIdFailure(result?.reason)
}
})
}
}
package com.yidian.common.http
import io.reactivex.rxjava3.core.Observable
import retrofit2.Call
import retrofit2.http.*
/**
* author: yinjiacheng
* date: 7/8/21 4:53 PM
* description: Common网络请求定义
*/
interface CommonService {
@Headers("Content-Type: application/json")
@POST(URLs.getKSYunToken)
fun getKSYunToken(@QueryMap commonParams: Map<String, String>, @Body requestParams: Map<String, String?>): Call<HttpResult<Any?>>
@GET(URLs.getKSYunObjectId)
fun getKSYunObjectId(
@QueryMap commonParams: Map<String, String>,
@QueryMap requestParams: Map<String, String?>
): Observable<HttpResult<Any?>>
}
package com.yidian.common.http.bean
/**
* author: yinjiacheng
* date: 5/23/21 12:13 PM
* description: 获取金山云bucket和objectId
*/
class GetKSYunObjectIdBean {
data class Response(val bucket: String, val objectId: String)
}
package com.yidian.common.http.bean
/**
* author: yinjiacheng
* date: 5/23/21 12:13 PM
* description: 获取金山云token
*/
class GetKSYunTokenBean(val request: Request, val response: Response) {
data class Request(
val http_method: String,
val date: String,
val resource: String,
val content_md5: String,
val content_type: String,
val headers: String
)
data class Response(val token: String)
}
package com.yidian.common.http.callback
import com.yidian.common.http.bean.GetKSYunObjectIdBean
/**
* author: yinjiacheng
* date: 5/23/21 12:18 PM
* description: 获取金山云ObjectId
*/
interface IGetKSYunObjectIdCallback {
fun getKSYunObjectIdSuccess(result: GetKSYunObjectIdBean.Response?)
fun getKSYunObjectIdFailure(message: String?)
}
package com.yidian.common.services.BsNewsContentTest
import com.yidian.common.base.BaseFragment
import com.yidian.yac.core.core.YacRouteNode
import com.yidian.yac.core.zap.ZapService
abstract class NewsTest: ZapService() {
abstract fun log()
}
\ No newline at end of file
package com.yidian.common.services
import android.app.Activity
import android.util.Base64
import com.google.gson.Gson
import com.luck.picture.lib.PictureSelector
import com.luck.picture.lib.config.PictureMimeType
import com.luck.picture.lib.entity.LocalMedia
import com.luck.picture.lib.listener.OnResultCallbackListener
import com.yidian.common.bean.SelectImageParamsBean
import com.yidian.common.bean.SelectedImageBean
import com.yidian.common.bean.UploadImageBean
import com.yidian.common.utils.GlideEngine
import com.yidian.common.utils.KS3Core
import com.yidian.xarc.xrouter.ResultResolver
import com.yidian.xpage.observer.DStackActivityManager
import com.yidian.yac.core.zap.ZapService
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
import java.io.FileInputStream
/**
* author: yinjiacheng
* date: 7/7/21 7:38 PM
* description: 图片服务
* 提供调用原生相机拍照、图片选择器、图片预览、图片上传能力
*/
class ImageService : ZapService() {
companion object {
private const val TAG = "ImageService"
const val SERVICE_NAME = "ImageService"
// 选择图片
const val ACTION_SELECT_PIC = "selectImage"
// 拍照
const val ACTION_TAKE_PIC = "takePhoto"
// 上传图片
const val ACTION_UPLOAD_PIC = "uploadImage"
// 预览图片 本期不做
const val ACTION_PREVIEW_PIC = "previewPic"
// 权限获取失败
private const val CODE_PERMISSION_GRANT_FAIL = 20301
// 选择失败、选择取消、拍照取消
private const val CODE_ACTION_CANCEL = 20302
// 文件不存在
private const val CODE_FILE_NOT_EXIST = 20306
// 文件上传失败
private const val CODE_FILE_UPLOAD_FAIL = 20307
// 图片最大选择数量
private const val SELECT_PIC_MAX_COUNT = 9
}
private val gson by lazy { Gson() }
private lateinit var resolver: ResultResolver
/**
* 当前栈顶Activity实例
*/
private lateinit var curTopActivity: Activity
/**
* 内存缓存已选择图片
*/
private val cacheSelectedImages by lazy { ArrayList<LocalMedia>() }
override fun onAction(path: String, action: String, params: Any, options: Any, resolver: ResultResolver) {
this.resolver = resolver
curTopActivity = DStackActivityManager.getInstance().topActivity
val resolveParams = params as JSONObject
when (action) {
ACTION_SELECT_PIC -> {
selectPic(gson.fromJson(resolveParams.toString(), SelectImageParamsBean::class.java))
}
ACTION_TAKE_PIC -> {
takePic()
}
ACTION_UPLOAD_PIC -> {
uploadPic(resolveParams.optString("id"))
}
ACTION_PREVIEW_PIC -> {
// 本期不做
}
}
}
override fun onExit() {
}
override fun onInit() {
}
/**
* 选择图片
*/
private fun selectPic(params: SelectImageParamsBean) {
PictureSelector.create(curTopActivity)
.openGallery(PictureMimeType.ofImage())
.imageEngine(GlideEngine.createGlideEngine())
.maxSelectNum(SELECT_PIC_MAX_COUNT - params.maxCount)
.forResult(object : OnResultCallbackListener<LocalMedia> {
override fun onResult(result: MutableList<LocalMedia>?) {
if (result.isNullOrEmpty()) {
resolver.fail(CODE_ACTION_CANCEL, "select failed", "选择失败")
return
}
cacheSelectedImages.addAll(result as ArrayList<LocalMedia>)
val picList = ArrayList<SelectedImageBean>(SELECT_PIC_MAX_COUNT - params.maxCount)
result.forEach {
picList.add(
SelectedImageBean(
result[0].id.toString(),
Base64.encodeToString(FileInputStream(it.realPath).readBytes(), Base64.DEFAULT)
)
)
}
// 成功回传数据
resolver.success(JSONArray(gson.toJson(picList)))
}
override fun onCancel() {
resolver.fail(CODE_ACTION_CANCEL, "select cancel", "选择取消")
}
})
}
/**
* 拍摄图片
*/
private fun takePic() {
PictureSelector.create(curTopActivity)
.openCamera(PictureMimeType.ofImage())
.imageEngine(GlideEngine.createGlideEngine())
.forResult(object : OnResultCallbackListener<LocalMedia> {
override fun onResult(result: MutableList<LocalMedia>?) {
if (result.isNullOrEmpty()) {
resolver.fail(CODE_ACTION_CANCEL, "take failed", "拍照失败")
return
}
cacheSelectedImages.add(result[0])
// 成功回传数据
resolver.success(
JSONArray(
gson.toJson(
arrayListOf(
SelectedImageBean(
result[0].id.toString(),
Base64.encodeToString(FileInputStream(result[0].realPath).readBytes(), Base64.DEFAULT)
)
)
)
)
)
}
override fun onCancel() {
resolver.fail(CODE_ACTION_CANCEL, "take cancel", "拍照取消")
}
})
}
/**
* 上传图片
* @param id 需要上传的图片id
*/
private fun uploadPic(id: String) {
var uploadPic: LocalMedia? = null
cacheSelectedImages.forEach {
// 遍历寻找指定图片
if (id == it.id.toString()) {
uploadPic = it
return@forEach
}
}
uploadPic?.let {
val file = File(it.realPath)
if (!file.exists()) {
resolver.fail(CODE_FILE_NOT_EXIST, "file not exist", "文件不存在")
return
}
KS3Core.INSTANCE.uploadObject(file, KS3Core.ObjectType.IMAGE, KS3Core.ScenarioType.COMMODITY, object : KS3Core.OnKS3TaskListener {
override fun onTaskStart() {
}
override fun onTaskProgress(progress: Double) {
}
override fun onTaskFinish() {
}
override fun onTaskCancel() {
}
override fun onTaskSuccess(bucket: String?, objectKey: String?) {
resolver.success(JSONObject(gson.toJson(UploadImageBean(id, "${bucket}/${objectKey}"))))
}
override fun onTaskFailure(statesCode: Int, message: String?) {
resolver.fail(CODE_FILE_UPLOAD_FAIL, "upload failed", "上传失败")
}
})
}
}
}
package com.yidian.common.utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.BitmapImageViewTarget;
import com.bumptech.glide.request.target.ImageViewTarget;
import com.luck.picture.lib.engine.ImageEngine;
import com.luck.picture.lib.listener.OnImageCompleteCallback;
import com.luck.picture.lib.tools.MediaUtils;
import com.luck.picture.lib.widget.longimage.ImageSource;
import com.luck.picture.lib.widget.longimage.ImageViewState;
import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView;
import com.yidian.common.R;
/**
* @author:luck
* @date:2019-11-13 17:02
* @describe:Glide加载引擎
*/
public class GlideEngine implements ImageEngine {
/**
* 加载图片
*
* @param context
* @param url
* @param imageView
*/
@Override
public void loadImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
Glide.with(context)
.load(url)
.into(imageView);
}
/**
* 加载网络图片适配长图方案
* # 注意:此方法只有加载网络图片才会回调
*
* @param context
* @param url
* @param imageView
* @param longImageView
* @param callback 网络图片加载回调监听 {link after version 2.5.1 Please use the #OnImageCompleteCallback#}
*/
@Override
public void loadImage(@NonNull Context context, @NonNull String url,
@NonNull ImageView imageView,
SubsamplingScaleImageView longImageView, OnImageCompleteCallback callback) {
Glide.with(context)
.asBitmap()
.load(url)
.into(new ImageViewTarget<Bitmap>(imageView) {
@Override
public void onLoadStarted(@Nullable Drawable placeholder) {
super.onLoadStarted(placeholder);
if (callback != null) {
callback.onShowLoading();
}
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
super.onLoadFailed(errorDrawable);
if (callback != null) {
callback.onHideLoading();
}
}
@Override
protected void setResource(@Nullable Bitmap resource) {
if (callback != null) {
callback.onHideLoading();
}
if (resource != null) {
boolean eqLongImage = MediaUtils.isLongImg(resource.getWidth(),
resource.getHeight());
longImageView.setVisibility(eqLongImage ? View.VISIBLE : View.GONE);
imageView.setVisibility(eqLongImage ? View.GONE : View.VISIBLE);
if (eqLongImage) {
// 加载长图
longImageView.setQuickScaleEnabled(true);
longImageView.setZoomEnabled(true);
longImageView.setDoubleTapZoomDuration(100);
longImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP);
longImageView.setDoubleTapZoomDpi(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER);
longImageView.setImage(ImageSource.cachedBitmap(resource),
new ImageViewState(0, new PointF(0, 0), 0));
} else {
// 普通图片
imageView.setImageBitmap(resource);
}
}
}
});
}
/**
* 加载网络图片适配长图方案
* # 注意:此方法只有加载网络图片才会回调
*
* @param context
* @param url
* @param imageView
* @param longImageView
* @ 已废弃
*/
@Override
public void loadImage(@NonNull Context context, @NonNull String url,
@NonNull ImageView imageView,
SubsamplingScaleImageView longImageView) {
Glide.with(context)
.asBitmap()
.load(url)
.into(new ImageViewTarget<Bitmap>(imageView) {
@Override
protected void setResource(@Nullable Bitmap resource) {
if (resource != null) {
boolean eqLongImage = MediaUtils.isLongImg(resource.getWidth(),
resource.getHeight());
longImageView.setVisibility(eqLongImage ? View.VISIBLE : View.GONE);
imageView.setVisibility(eqLongImage ? View.GONE : View.VISIBLE);
if (eqLongImage) {
// 加载长图
longImageView.setQuickScaleEnabled(true);
longImageView.setZoomEnabled(true);
longImageView.setDoubleTapZoomDuration(100);
longImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP);
longImageView.setDoubleTapZoomDpi(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER);
longImageView.setImage(ImageSource.cachedBitmap(resource),
new ImageViewState(0, new PointF(0, 0), 0));
} else {
// 普通图片
imageView.setImageBitmap(resource);
}
}
}
});
}
/**
* 加载相册目录
*
* @param context 上下文
* @param url 图片路径
* @param imageView 承载图片ImageView
*/
@Override
public void loadFolderImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
Glide.with(context)
.asBitmap()
.load(url)
.override(180, 180)
.centerCrop()
.sizeMultiplier(0.5f)
.placeholder(R.drawable.picture_image_placeholder)
.into(new BitmapImageViewTarget(imageView) {
@Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable circularBitmapDrawable =
RoundedBitmapDrawableFactory.
create(context.getResources(), resource);
circularBitmapDrawable.setCornerRadius(8);
imageView.setImageDrawable(circularBitmapDrawable);
}
});
}
/**
* 加载gif
*
* @param context 上下文
* @param url 图片路径
* @param imageView 承载图片ImageView
*/
@Override
public void loadAsGifImage(@NonNull Context context, @NonNull String url,
@NonNull ImageView imageView) {
Glide.with(context)
.asGif()
.load(url)
.into(imageView);
}
/**
* 加载图片列表图片
*
* @param context 上下文
* @param url 图片路径
* @param imageView 承载图片ImageView
*/
@Override
public void loadGridImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
Glide.with(context)
.load(url)
.override(200, 200)
.centerCrop()
.placeholder(R.drawable.picture_image_placeholder)
.into(imageView);
}
private GlideEngine() {
}
private static GlideEngine instance;
public static GlideEngine createGlideEngine() {
if (null == instance) {
synchronized (GlideEngine.class) {
if (null == instance) {
instance = new GlideEngine();
}
}
}
return instance;
}
}
......@@ -277,7 +277,8 @@ public static final int *;
#网络相关类
-keep class com.yidian.http.**{*;}
-keep class com.yidian.common.http.**{*;}
-keep class com.yidian.common.bean.**{*;}
-keep class com.yidian.common.http.bean.**{*;}
-keep class com.yidian.shenghuoquan.newscontent.http.**{*;}
-keep class com.yidian.common.utils.*{*;}
-keep class com.yidian.shenghuoquan.newscontent.utils.*{*;}
......@@ -358,3 +359,11 @@ public static final int *;
public <init>(android.content.Context, android.util.AttributeSet, int);
}
#PictureSelector 2.0
-keep class com.luck.picture.lib.** { *; }
#Ucrop
-dontwarn com.yalantis.ucrop**
-keep class com.yalantis.ucrop** { *; }
-keep interface com.yalantis.ucrop** { *; }
......@@ -107,7 +107,8 @@
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
tools:replace="android:theme" />
<!-- 以下为友盟推送基本配置信息 start-->
<service
......
......@@ -99,7 +99,9 @@ ext.dependencies = [
// 屏幕适配
'me.jessyan:autosize:1.2.1',
//https://github.com/CymChad/BaseRecyclerViewAdapterHelper
'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4'
'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4',
// PictureSelector2.0 https://github.com/LuckSiege/PictureSelector
'io.github.lucksiege:pictureselector:v2.7.3-rc05'
],
other : [
':CommonLib:Common'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment