From a1891718f5e37beb63c0929e1908dde1968d8670 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 15 Mar 2024 15:25:22 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E7=A7=BB=E9=99=A4=E4=B8=83?= =?UTF-8?q?=E7=89=9B=E4=BA=91=E6=94=B9=E7=94=A8=E8=85=BE=E8=AE=AFCOS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UploadRoomAlbumDialogFragment.kt | 2 +- .../erban/common/dialog/PhotoDialog.kt | 4 +- .../erban/common/photo/PhotoProvider.kt | 27 +- .../matisse/internal/entity/CustomItem.java | 10 +- .../publish/presenter/PublishPresenter.java | 42 +- .../publish/view/PublishActivity.java | 25 +- .../community/utils/ObjectTypeHelper.java | 16 + core/build.gradle | 2 + .../xchat_android_core/file/FileModel.java | 118 +--- .../xchat_android_core/file/IFileModel.java | 5 - .../xchat_android_core/file/UploadToken.java | 9 - .../xchat_android_core/file/cos/CosClient.kt | 116 ++++ .../file/cos/CosCredentialProvider.kt | 20 + .../file/cos/CosException.kt | 21 + .../xchat_android_core/file/cos/CosToken.kt | 43 ++ .../community/bean/DynamicMedia.java | 10 + library/build.gradle | 3 - .../utils/image/JXImageUtils.java | 39 +- .../common/file/FileHelper.java | 37 -- .../java/com/chuhai/utils/PathUtils.kt | 519 ++++++++++++++++++ 20 files changed, 883 insertions(+), 185 deletions(-) delete mode 100644 core/src/main/java/com/yizhuan/xchat_android_core/file/UploadToken.java create mode 100644 core/src/main/java/com/yizhuan/xchat_android_core/file/cos/CosClient.kt create mode 100644 core/src/main/java/com/yizhuan/xchat_android_core/file/cos/CosCredentialProvider.kt create mode 100644 core/src/main/java/com/yizhuan/xchat_android_core/file/cos/CosException.kt create mode 100644 core/src/main/java/com/yizhuan/xchat_android_core/file/cos/CosToken.kt create mode 100644 library/src/module_utils/java/com/chuhai/utils/PathUtils.kt diff --git a/app/src/main/java/com/yizhuan/erban/avroom/room_album/UploadRoomAlbumDialogFragment.kt b/app/src/main/java/com/yizhuan/erban/avroom/room_album/UploadRoomAlbumDialogFragment.kt index e96af645a..8ad5963eb 100644 --- a/app/src/main/java/com/yizhuan/erban/avroom/room_album/UploadRoomAlbumDialogFragment.kt +++ b/app/src/main/java/com/yizhuan/erban/avroom/room_album/UploadRoomAlbumDialogFragment.kt @@ -251,7 +251,7 @@ class UploadRoomAlbumDialogFragment : BottomSheetDialogFragment() { } if (requestCode == 200) { PhotoProvider.getResultPathListAsync(data) { - it?.let { paths -> + it?.mapNotNull { it.path }?.let { paths -> compressPhotos(paths.toMutableList()) } } diff --git a/app/src/main/java/com/yizhuan/erban/common/dialog/PhotoDialog.kt b/app/src/main/java/com/yizhuan/erban/common/dialog/PhotoDialog.kt index 0c215cbdd..e77731005 100644 --- a/app/src/main/java/com/yizhuan/erban/common/dialog/PhotoDialog.kt +++ b/app/src/main/java/com/yizhuan/erban/common/dialog/PhotoDialog.kt @@ -207,7 +207,7 @@ class PhotoDialog : BaseDialogFragment(), EasyPermissions.Pe REQUEST_CODE_OPEN_CAMERA_PROVIDER -> { if (mOnResultCallBack == null || data == null) return PhotoProvider.getResultPathListAsync(data) { paths -> - val list = paths?.toMutableList() ?: ArrayList() + val list = paths?.mapNotNull { it.path }?.toMutableList() ?: ArrayList() val path = list[0] if (!TextUtils.isEmpty(path)) { mJob?.cancel() @@ -233,7 +233,7 @@ class PhotoDialog : BaseDialogFragment(), EasyPermissions.Pe REQUEST_CODE_OPEN_PHOTO_PROVIDER -> { if (mOnResultCallBack == null || data == null) return PhotoProvider.getResultPathListAsync(data) { list -> - val paths = list?.toMutableList() ?: ArrayList() + val paths = list?.mapNotNull { it.path }?.toMutableList() ?: ArrayList() if (paths.isEmpty()) { mOnResultCallBack?.choicePhotoCallBack(paths) } else { diff --git a/app/src/main/java/com/yizhuan/erban/common/photo/PhotoProvider.kt b/app/src/main/java/com/yizhuan/erban/common/photo/PhotoProvider.kt index 4c7b6767c..539161a3d 100644 --- a/app/src/main/java/com/yizhuan/erban/common/photo/PhotoProvider.kt +++ b/app/src/main/java/com/yizhuan/erban/common/photo/PhotoProvider.kt @@ -86,13 +86,25 @@ object PhotoProvider { @JvmStatic @JvmOverloads - fun photoProvider(activity: FragmentActivity, maxSelect: Int = 1, canChooseGif: Boolean = false, resultCode: Int, isClearCache: Boolean = true) { + fun photoProvider( + activity: FragmentActivity, + maxSelect: Int = 1, + canChooseGif: Boolean = false, + resultCode: Int, + isClearCache: Boolean = true, + useWidth: Boolean = false + ) { cancelJop() mPhotoJob = MainScope().launch { if (isClearCache && isClearByTime()) { withContext(Dispatchers.IO) { clearCache() } } - EasyPhotos.createAlbum(activity, false, false, GlideEngine())//参数说明:上下文,是否显示相机按钮,是否使用宽高数据(false时宽高数据为0,扫描速度更快),[配置Glide为图片加载引擎](https://github.com/HuanTanSheng/EasyPhotos/wiki/12-%E9%85%8D%E7%BD%AEImageEngine%EF%BC%8C%E6%94%AF%E6%8C%81%E6%89%80%E6%9C%89%E5%9B%BE%E7%89%87%E5%8A%A0%E8%BD%BD%E5%BA%93) + EasyPhotos.createAlbum( + activity, + false, + useWidth, + GlideEngine() + )//参数说明:上下文,是否显示相机按钮,是否使用宽高数据(false时宽高数据为0,扫描速度更快),[配置Glide为图片加载引擎](https://github.com/HuanTanSheng/EasyPhotos/wiki/12-%E9%85%8D%E7%BD%AEImageEngine%EF%BC%8C%E6%94%AF%E6%8C%81%E6%89%80%E6%9C%89%E5%9B%BE%E7%89%87%E5%8A%A0%E8%BD%BD%E5%BA%93) .setCount(maxSelect)//参数说明:最大可选数,默认1 .setGif(canChooseGif) .setPuzzleMenu(false) @@ -131,7 +143,7 @@ object PhotoProvider { } @JvmStatic - fun getResultPathListAsync(data: Intent?, resultListener: ((List?) -> Unit)) { + fun getResultPathListAsync(data: Intent?, resultListener: ((List?) -> Unit)) { cancelJop() mPhotoJob = MainScope().launch { val list: List? = data?.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS) @@ -151,22 +163,21 @@ object PhotoProvider { * 1. 项目使用到BitmapFactory.decodeFile(imgPath, options)之类的方法,该方法在android Q直接使用外部path测试中发现,获取图片信息失败 * */ - private fun copyToInternalCache(photos: List?): List? { + private fun copyToInternalCache(photos: List?): List? { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val foldPath = getInternalPath() + File.separator - val newPaths = ArrayList() photos?.forEach { if (it.uri != null && !it.name.isNullOrEmpty()) { val path = "$foldPath${it.name}" if (FileHelper.copyFileFromUri(it.uri, path, true)) { - newPaths.add(path) + it.path = path Logger.debug(TAG, "path: ${it.path} , displayName: ${it.name} , newPath: $path ") } } } - newPaths + photos } else { - photos?.takeIf { it.isNotEmpty() }?.map { it.path } + photos } } diff --git a/app/src/module_album/java/com/zhihu/matisse/internal/entity/CustomItem.java b/app/src/module_album/java/com/zhihu/matisse/internal/entity/CustomItem.java index 2296967f6..0aa7c3307 100644 --- a/app/src/module_album/java/com/zhihu/matisse/internal/entity/CustomItem.java +++ b/app/src/module_album/java/com/zhihu/matisse/internal/entity/CustomItem.java @@ -36,6 +36,10 @@ public class CustomItem implements Parcelable, Serializable { private String format; + private int width; + + private int height; + public String getPath(){ return path; } @@ -66,7 +70,7 @@ public class CustomItem implements Parcelable, Serializable { } public CustomItem(String path, int fileType) { - this(path, fileType, "jpeg"); + this(path, fileType, "jpeg", 0, 0); } public CustomItem() { @@ -82,12 +86,16 @@ public class CustomItem implements Parcelable, Serializable { dest.writeString(this.path); dest.writeInt(this.fileType); dest.writeString(this.format); + dest.writeInt(this.width); + dest.writeInt(this.height); } protected CustomItem(Parcel in) { this.path = in.readString(); this.fileType = in.readInt(); this.format = in.readString(); + this.width = in.readInt(); + this.height = in.readInt(); } public static final Creator CREATOR = new Creator() { diff --git a/app/src/module_community/java/com/yizhuan/erban/community/publish/presenter/PublishPresenter.java b/app/src/module_community/java/com/yizhuan/erban/community/publish/presenter/PublishPresenter.java index c5ca335ca..2ebbea40c 100644 --- a/app/src/module_community/java/com/yizhuan/erban/community/publish/presenter/PublishPresenter.java +++ b/app/src/module_community/java/com/yizhuan/erban/community/publish/presenter/PublishPresenter.java @@ -22,6 +22,7 @@ import com.yizhuan.xchat_android_library.utils.ListUtils; import com.yizhuan.xchat_android_library.utils.ResUtil; import com.yizhuan.xchat_android_library.utils.file.JXFileUtils; import com.yizhuan.xchat_android_library.utils.image.JXImageUtils; +import com.zhihu.matisse.internal.entity.CustomItem; import java.util.ArrayList; import java.util.List; @@ -48,9 +49,9 @@ public class PublishPresenter extends BaseMvpPresenter { private MiniWorldChooseInfo miniWorldChooseInfo = new MiniWorldChooseInfo(); - public void publishDy(List list, long worldId, String content, boolean isOriginalImage) { + public void publishDy(List list, long worldId, String content, boolean isOriginalImage) { publishBody = new PublishBody(); - List uploadList = new ArrayList<>(list); + List uploadList = new ArrayList<>(list); int type = ListUtils.isListEmpty(uploadList) ? 0 : 2; publishBody.setType(type); publishBody.setUid(AuthModel.get().getCurrentUid()); @@ -77,7 +78,7 @@ public class PublishPresenter extends BaseMvpPresenter { }); } - private Single uploadImage(List imagePaths) { + private Single uploadImage(List imagePaths) { upload(imagePaths); return Single.create(emitter -> mImageUploadSubscribe = Observable.interval(500, TimeUnit.MILLISECONDS) @@ -91,22 +92,34 @@ public class PublishPresenter extends BaseMvpPresenter { })); } - private void upload(List imagePaths) { + private void upload(List imagePaths) { if (imagePaths == null || imagePaths.size() == 0) { return; } - String file = imagePaths.get(0); + DynamicMedia item = imagePaths.get(0); + String file = item.getLocalFilePath(); Single.create((SingleOnSubscribe) e -> { long fileLength = JXFileUtils.getFileLength(file); LogUtil.print(ResUtil.getString(R.string.publish_presenter_publishpresenter_01) + fileLength); String compressFile = null; if (!isOriginalImage && fileLength > ImageUploadConfig.MAX_FILE_SIZE) { - compressFile = JXImageUtils.compressImagePxAndQuality( + JXImageUtils.CompressResult result = JXImageUtils.compressImagePxAndQuality( file, DirectoryHelper.get().getDynamicDir(), "dynamic_" + System.currentTimeMillis() + ".jpg", ImageUploadConfig.EXPECT_MIN_WIDTH, ImageUploadConfig.EXPECT_COMPRESS_SIZE); - + if (result != null) { + compressFile = result.getPath(); + if (result.getWidth() > 0 && item.getWidth() != result.getWidth()) { + item.setWidth(result.getWidth()); + } + if (result.getHeight() > 0 && item.getHeight() != result.getHeight()) { + item.setHeight(result.getHeight()); + } + if (result.getFormat() != null) { + item.setFormat(result.getFormat()); + } + } LogUtil.print(ResUtil.getString(R.string.publish_presenter_publishpresenter_02) + compressFile); } if (!TextUtils.isEmpty(compressFile)) { @@ -118,21 +131,22 @@ public class PublishPresenter extends BaseMvpPresenter { } }) .compose(RxHelper.handleSchedulers()) - .flatMap((Function>) - path -> FileModel.get().uploadFileReturnImageInfo(path)) + .flatMap((Function>) + path -> FileModel.get().uploadFile(path)) .compose(bindUntilEvent(PresenterEvent.DESTROY)) - .subscribe(new DontWarnObserver() { + .subscribe(new DontWarnObserver() { @Override - public void acceptThrowable(DynamicMedia media, Throwable throwable) { - super.acceptThrowable(media, throwable); + public void acceptThrowable(String url, Throwable throwable) { + super.acceptThrowable(url, throwable); if (throwable != null) { if (mImageUploadSubscribe != null) { mImageUploadSubscribe.dispose(); } dealUploadFileError(throwable); } else { - LogUtil.print(ResUtil.getString(R.string.publish_presenter_publishpresenter_04), media); - publishBody.addDynamicMedia(media); + item.setResUrl(url); + LogUtil.print(ResUtil.getString(R.string.publish_presenter_publishpresenter_04), item); + publishBody.addDynamicMedia(item); imagePaths.remove(0); upload(imagePaths); } diff --git a/app/src/module_community/java/com/yizhuan/erban/community/publish/view/PublishActivity.java b/app/src/module_community/java/com/yizhuan/erban/community/publish/view/PublishActivity.java index b196ae164..98b364984 100644 --- a/app/src/module_community/java/com/yizhuan/erban/community/publish/view/PublishActivity.java +++ b/app/src/module_community/java/com/yizhuan/erban/community/publish/view/PublishActivity.java @@ -54,6 +54,7 @@ import com.yizhuan.xchat_android_library.common.photo.PhotoProvider; import com.yizhuan.xchat_android_library.common.util.PhotoCompressUtil; import com.yizhuan.xchat_android_library.common.util.PhotosCompressCallback; import com.yizhuan.xchat_android_library.easypermisssion.EasyPermissions; +import com.yizhuan.xchat_android_library.easyphoto.models.album.entity.Photo; import com.yizhuan.xchat_android_library.easyphoto.utils.settings.SettingsUtils; import com.yizhuan.xchat_android_library.utils.ResUtil; import com.yizhuan.xchat_android_library.utils.SingleToastUtil; @@ -297,7 +298,7 @@ public class PublishActivity extends BaseMvpActivity, Unit>() { + PhotoProvider.getResultPathListAsync(data, new Function1, Unit>() { @Override - public Unit invoke(List list) { + public Unit invoke(List list) { if (list.isEmpty()) { return null; } else { if (mJob != null) { mJob.cancel(null); } - mJob = PhotoCompressUtil.compress(PublishActivity.this, list, + ArrayList pathList = new ArrayList<>(); + for (Photo photo : list) { + pathList.add(photo.path); + } + mJob = PhotoCompressUtil.compress(PublishActivity.this, pathList, PhotoCompressUtil.getCompressCachePath("publish") , new PhotosCompressCallback() { @Override public void onSuccess(@NonNull ArrayList compressedImgList) { List pathResult = new ArrayList<>(); - for (String path : compressedImgList) { - pathResult.add(new CustomItem(path, CustomItem.IMAGE_NORMAL, "jpeg")); + for (int i = 0; i < compressedImgList.size(); i++) { + if (i < list.size()) { + Photo photo = list.get(i); + String format = "image/jpeg"; + if (photo.type != null) { + format = photo.type; + } + pathResult.add(new CustomItem(compressedImgList.get(i), CustomItem.IMAGE_NORMAL, format, photo.width, photo.height)); + } } if (pathResult.size() == 0) { return; @@ -635,6 +647,7 @@ public class PublishActivity extends BaseMvpActivity customToMediaList(List paramsList) { + List resultList = new ArrayList<>(); + if (paramsList == null) { + return resultList; + } + for (CustomItem item : paramsList) { + DynamicMedia media = new DynamicMedia(); + media.setLocalFilePath(item.getPath()); + media.setWidth(item.getWidth()); + media.setHeight(item.getHeight()); + media.setFormat(item.getFormat()); + resultList.add(media); + } + return resultList; + } + public static List stringToCustomList(List paramsList) { List resultList = new ArrayList<>(); if (paramsList == null) { diff --git a/core/build.gradle b/core/build.gradle index 9a2963a63..e1158e1ea 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -107,6 +107,8 @@ dependencies { implementation 'com.liulishuo.okdownload:okhttp:1.0.7' implementation 'com.tencent.liteav:LiteAVSDK_TRTC:11.4.0.13189' + + api 'com.qcloud.cos:cos-android:5.9.23' } repositories { mavenCentral() diff --git a/core/src/main/java/com/yizhuan/xchat_android_core/file/FileModel.java b/core/src/main/java/com/yizhuan/xchat_android_core/file/FileModel.java index 0554f2b67..c9261bfb2 100644 --- a/core/src/main/java/com/yizhuan/xchat_android_core/file/FileModel.java +++ b/core/src/main/java/com/yizhuan/xchat_android_core/file/FileModel.java @@ -2,22 +2,20 @@ package com.yizhuan.xchat_android_core.file; import android.text.TextUtils; -import com.netease.nim.uikit.common.util.log.LogUtil; -import com.qiniu.android.common.FixedZone; -import com.qiniu.android.storage.Configuration; -import com.qiniu.android.storage.UploadManager; +import com.chuhai.utils.AppUtils; +import com.chuhai.utils.PathUtils; import com.yizhuan.xchat_android_core.R; import com.yizhuan.xchat_android_core.base.BaseModel; import com.yizhuan.xchat_android_core.bean.response.ServiceResult; -import com.yizhuan.xchat_android_core.community.bean.DynamicMedia; +import com.yizhuan.xchat_android_core.file.cos.CosToken; import com.yizhuan.xchat_android_core.exception.ErrorThrowable; +import com.yizhuan.xchat_android_core.file.cos.CosClient; import com.yizhuan.xchat_android_core.utils.net.RxHelper; import com.yizhuan.xchat_android_library.net.rxnet.RxNet; import com.yizhuan.xchat_android_library.utils.ResUtil; -import org.json.JSONObject; - import java.io.File; +import java.util.UUID; import io.reactivex.Single; import retrofit2.http.GET; @@ -29,120 +27,50 @@ public class FileModel extends BaseModel implements IFileModel { } private FileModel() { - Configuration config = new Configuration.Builder() - .zone(FixedZone.zoneAs0) // 设置区域,不指定会自动选择。指定不同区域的上传域名、备用域名、备用IP。 - .build(); - uploadManager = new UploadManager(config); } public static FileModel get() { return Helper.INSTANCE; } - private UploadManager uploadManager; private final Api api = RxNet.create(Api.class); + private CosToken cosToken; + + private Single getCosToken() { + if (cosToken != null && cosToken.isValid()) { + return Single.just(cosToken); + } else { + return api.getCosToken().compose(RxHelper.handleSchedulers()) + .compose(RxHelper.handleBeanData()).map(cosToken -> { + FileModel.get().cosToken = cosToken; + return cosToken; + }); + } + } + @Override public Single uploadFile(String path) { - File file; if (TextUtils.isEmpty(path) || !((file = new File(path)).exists())) { return Single.error(new ErrorThrowable(path + ResUtil.getString(R.string.xchat_android_core_file_filemodel_01))); } File finalFile = file; - return api.getUploadToken() - .compose(RxHelper.handleSchedulers()) - .compose(RxHelper.handleBeanData()) - .flatMap(uploadToken -> Single.create(singleEmitter -> - uploadManager.put(finalFile, uploadToken.getKey(), uploadToken.getToken() , - (key, info, response) -> { - if (info.isOK()) { - try { - String imgUrl = response.getString("path"); - singleEmitter.onSuccess(imgUrl); - } catch (Exception e) { - singleEmitter.onError(e); - } - } else { - singleEmitter.onError(new Throwable(info.error)); - } - }, null) - )); + String outName = UUID.randomUUID().toString() + PathUtils.INSTANCE.getSuffixType(finalFile.getName()); + return getCosToken().flatMap(token -> CosClient.INSTANCE.upload(AppUtils.getApp(), finalFile, outName, token).map(cosXmlResult -> cosXmlResult.accessUrl)); } - @Override - public Single uploadFileReturnImageInfo(String path) { - return uploadFileReturnImageInfo(path, null); - } - - @Override - public Single uploadFileReturnImageInfo(String path, String qiniuName) { - File file; - if (TextUtils.isEmpty(path) || !((file = new File(path)).exists())) { - return Single.error(new ErrorThrowable(path + ResUtil.getString(R.string.xchat_android_core_file_filemodel_02))); - } - - File finalFile = file; - return api.getUploadToken() - .compose(RxHelper.handleSchedulers()) - .compose(RxHelper.handleBeanData()) - .flatMap(uploadToken -> Single.create(singleEmitter -> - uploadManager.put(finalFile, uploadToken.getKey(), uploadToken.getToken(), - (key, info, response) -> { - if (info.isOK()) { - try { - LogUtil.print(ResUtil.getString(R.string.xchat_android_core_file_filemodel_03)); - LogUtil.print(response); - DynamicMedia media = responseToMeia(response); - if (media != null) { - singleEmitter.onSuccess(media); - return; - } - } catch (Exception e) { - e.printStackTrace(); - } - singleEmitter.onError(new Throwable("qiniu json error")); - } else { - singleEmitter.onError(new Throwable(info.error)); - } - }, null))); - } - - @Override public Single downloadFile(String url) { return null; } - private DynamicMedia responseToMeia(JSONObject response) { - DynamicMedia media = new DynamicMedia(); - try { - String imgNamePath = response.getString("path"); - media.setResUrl(imgNamePath); - } catch (Exception ex) { - ex.printStackTrace(); - return null; - } - try { - media.setFormat(response.getString("format")); - media.setWidth(response.getInt("w")); - media.setHeight(response.getInt("h")); - } catch (Exception ex) { - ex.printStackTrace(); - } - LogUtil.print(ResUtil.getString(R.string.xchat_android_core_file_filemodel_04), media); - return media; - } - interface Api { /** - * 上传文件 - * * @return */ - @GET("/qiniu/upload/getUploadToken") - Single> getUploadToken(); - + @GET("/tencent/cos/getToken") + Single> getCosToken(); } } diff --git a/core/src/main/java/com/yizhuan/xchat_android_core/file/IFileModel.java b/core/src/main/java/com/yizhuan/xchat_android_core/file/IFileModel.java index dcc82dafd..092f60953 100644 --- a/core/src/main/java/com/yizhuan/xchat_android_core/file/IFileModel.java +++ b/core/src/main/java/com/yizhuan/xchat_android_core/file/IFileModel.java @@ -22,9 +22,4 @@ public interface IFileModel extends IModel { * @return */ Single downloadFile(String url); - - Single uploadFileReturnImageInfo(String path); - - Single uploadFileReturnImageInfo(String path, String qiniuName); - } diff --git a/core/src/main/java/com/yizhuan/xchat_android_core/file/UploadToken.java b/core/src/main/java/com/yizhuan/xchat_android_core/file/UploadToken.java deleted file mode 100644 index 55a7d8ebc..000000000 --- a/core/src/main/java/com/yizhuan/xchat_android_core/file/UploadToken.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.yizhuan.xchat_android_core.file; - -import lombok.Data; - -@Data -public class UploadToken { - private String key; - private String token; -} diff --git a/core/src/main/java/com/yizhuan/xchat_android_core/file/cos/CosClient.kt b/core/src/main/java/com/yizhuan/xchat_android_core/file/cos/CosClient.kt new file mode 100644 index 000000000..46e3a98c8 --- /dev/null +++ b/core/src/main/java/com/yizhuan/xchat_android_core/file/cos/CosClient.kt @@ -0,0 +1,116 @@ +package com.yizhuan.xchat_android_core.file.cos + +import android.content.Context +import android.util.Log +import com.tencent.cos.xml.CosXmlService +import com.tencent.cos.xml.CosXmlServiceConfig +import com.tencent.cos.xml.exception.CosXmlClientException +import com.tencent.cos.xml.exception.CosXmlServiceException +import com.tencent.cos.xml.listener.CosXmlResultListener +import com.tencent.cos.xml.model.CosXmlRequest +import com.tencent.cos.xml.model.CosXmlResult +import com.tencent.cos.xml.transfer.COSXMLUploadTask +import com.tencent.cos.xml.transfer.TransferConfig +import com.tencent.cos.xml.transfer.TransferManager +import com.yizhuan.xchat_android_core.file.cos.CosCredentialProvider +import com.yizhuan.xchat_android_core.file.cos.CosException +import com.yizhuan.xchat_android_core.file.cos.CosToken +import com.yizhuan.xchat_android_library.common.application.Env +import io.reactivex.Single +import java.io.File + + +/** + * Created by Max on 2024/1/16 11:09 + * Desc: + **/ +object CosClient { + private var cosXmlClient: CosXmlService? = null + private var credentialProvider: CosCredentialProvider? = null + + private fun getCosXmlClient(context: Context, token: CosToken): CosXmlService { + var client = this.cosXmlClient + if (client != null && credentialProvider != null) { + credentialProvider?.updateCredentials(token.toCredential()) + return client + } + // 创建 CosXmlServiceConfig 对象,根据需要修改默认的配置参数 + val serviceConfig: CosXmlServiceConfig = CosXmlServiceConfig.Builder() + .setRegion(token.region) + .isHttps(true) // 使用 HTTPS 请求, 默认为 HTTP 请求 + .builder() + + val credentials = token.toCredential() + credentialProvider = CosCredentialProvider(credentials) + // 初始化 COS Service,获取实例 + client = CosXmlService( + context, + serviceConfig, credentialProvider + ) + cosXmlClient = client + return client + } + + /** + * 上传文件 + * @param outName 远端文件名 + */ + fun upload( + context: Context, + file: File, + outName: String, + token: CosToken + ): Single { + if (Env.isDebug()) { + Log.e("CosClient", "upload file:${file.absolutePath} outName:${outName}") + } + return Single.create { + uploadFile(context, file, outName, token).apply { + setCosXmlResultListener(object : CosXmlResultListener { + override fun onSuccess(request: CosXmlRequest?, result: CosXmlResult) { + if (Env.isDebug()) { + Log.e("CosClient", "upload onSuccess result:${result.accessUrl}") + } + it.onSuccess(result) + } + + override fun onFail( + request: CosXmlRequest?, + clientException: CosXmlClientException?, + serviceException: CosXmlServiceException? + ) { + if (Env.isDebug()) { + Log.e("CosClient", "upload onFail clientException:$clientException") + Log.e("CosClient", "upload onFail serviceException:$serviceException") + } + it.onError(CosException(clientException, serviceException)) + } + }) + } + } + } + + /** + * 上传文件 + * @param outName 远端文件名 + */ + private fun uploadFile( + context: Context, + file: File, + outName: String, + token: CosToken + ): COSXMLUploadTask { + val cosXmlService = getCosXmlClient(context, token) + // 初始化 TransferConfig,这里使用默认配置,如果需要定制,请参考 SDK 接口文档 + val transferConfig = TransferConfig.Builder().build() + // 初始化 TransferManager + val transferManager = TransferManager( + cosXmlService, + transferConfig + ) + return transferManager.upload( + token.bucket, outName, + file.absolutePath, null + ) + } +} \ No newline at end of file diff --git a/core/src/main/java/com/yizhuan/xchat_android_core/file/cos/CosCredentialProvider.kt b/core/src/main/java/com/yizhuan/xchat_android_core/file/cos/CosCredentialProvider.kt new file mode 100644 index 000000000..c4a3a2ff7 --- /dev/null +++ b/core/src/main/java/com/yizhuan/xchat_android_core/file/cos/CosCredentialProvider.kt @@ -0,0 +1,20 @@ +package com.yizhuan.xchat_android_core.file.cos + +import com.tencent.qcloud.core.auth.BasicLifecycleCredentialProvider +import com.tencent.qcloud.core.auth.QCloudLifecycleCredentials + +/** + * Created by Max on 2024/1/16 11:28 + * Desc: + **/ +class CosCredentialProvider(private var credentials: QCloudLifecycleCredentials) : + BasicLifecycleCredentialProvider() { + + fun updateCredentials(credentials: QCloudLifecycleCredentials) { + this.credentials = credentials + } + + override fun fetchNewCredentials(): QCloudLifecycleCredentials { + return credentials + } +} \ No newline at end of file diff --git a/core/src/main/java/com/yizhuan/xchat_android_core/file/cos/CosException.kt b/core/src/main/java/com/yizhuan/xchat_android_core/file/cos/CosException.kt new file mode 100644 index 000000000..f746851c9 --- /dev/null +++ b/core/src/main/java/com/yizhuan/xchat_android_core/file/cos/CosException.kt @@ -0,0 +1,21 @@ +package com.yizhuan.xchat_android_core.file.cos + +import com.tencent.cos.xml.exception.CosXmlClientException +import com.tencent.cos.xml.exception.CosXmlServiceException + +/** + * Created by Max on 2024/1/16 15:49 + * Desc: + **/ +class CosException : Exception { + var clientException: CosXmlClientException? = null + var serviceException: CosXmlServiceException? = null + + constructor( + clientException: CosXmlClientException?, + serviceException: CosXmlServiceException? + ) : super(clientException ?: serviceException) { + this.clientException = clientException + this.serviceException = serviceException + } +} \ No newline at end of file diff --git a/core/src/main/java/com/yizhuan/xchat_android_core/file/cos/CosToken.kt b/core/src/main/java/com/yizhuan/xchat_android_core/file/cos/CosToken.kt new file mode 100644 index 000000000..b1e436c01 --- /dev/null +++ b/core/src/main/java/com/yizhuan/xchat_android_core/file/cos/CosToken.kt @@ -0,0 +1,43 @@ +package com.yizhuan.xchat_android_core.file.cos + +import com.tencent.qcloud.core.auth.QCloudLifecycleCredentials +import com.tencent.qcloud.core.auth.SessionQCloudCredentials +import com.yizhuan.xchat_android_core.utils.CurrentTimeUtils + +/** + * Created by Max on 2024/1/16 11:44 + * Desc: + **/ +data class CosToken( + val secretId: String?, + val secretKey: String?, + val sessionToken: String?, + val bucket: String?, + val region: String?, + val startTime: Long?, + val expireTime: Long?, +) { + + /** + * 是否有效 + */ + fun isValid(): Boolean { + if (expireTime == null) { + return false + } + val currentTime = CurrentTimeUtils.getCurrentTime() / 1000 + // 预留一点安全时长 + var safeTime = 30 + if (safeTime >= (expireTime - (startTime ?: 0))) { + safeTime = 0 + } + return currentTime <= (expireTime - safeTime) + } + + fun toCredential(): QCloudLifecycleCredentials { + return SessionQCloudCredentials( + secretId ?: "", secretKey ?: "", + sessionToken ?: "", startTime ?: 0, expireTime ?: 0 + ) + } +} \ No newline at end of file diff --git a/core/src/model_community/java/com/yizhuan/xchat_android_core/community/bean/DynamicMedia.java b/core/src/model_community/java/com/yizhuan/xchat_android_core/community/bean/DynamicMedia.java index 8e5a55155..5c361794d 100644 --- a/core/src/model_community/java/com/yizhuan/xchat_android_core/community/bean/DynamicMedia.java +++ b/core/src/model_community/java/com/yizhuan/xchat_android_core/community/bean/DynamicMedia.java @@ -2,6 +2,7 @@ package com.yizhuan.xchat_android_core.community.bean; import android.text.TextUtils; +import kotlin.jvm.Transient; import lombok.Data; /** @@ -26,6 +27,15 @@ public class DynamicMedia { private String format; private int width; private int height; + @Transient + private String localFilePath; + public String getLocalFilePath() { + return localFilePath; + } + + public void setLocalFilePath(String localFilePath) { + this.localFilePath = localFilePath; + } /** * 是否是网络图片 diff --git a/library/build.gradle b/library/build.gradle index c29605115..fd06d40ec 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -77,7 +77,6 @@ dependencies { def rxjava_android = "2.0.1" def rxlifecycle = "3.1.0" def loggerVersion = "2.2.0" - def qiniu = "8.4.4" def SmartRefreshLayoutVersion = "1.0.3" def eventbusVersion = "3.3.1" def fragment_version = "1.6.1" @@ -122,8 +121,6 @@ dependencies { api "com.orhanobut:logger:${loggerVersion}" - api "com.qiniu:qiniu-android-sdk:${qiniu}" - api "org.greenrobot:eventbus:${eventbusVersion}" api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" diff --git a/library/src/main/java/com/yizhuan/xchat_android_library/utils/image/JXImageUtils.java b/library/src/main/java/com/yizhuan/xchat_android_library/utils/image/JXImageUtils.java index afddac6dd..4ca67b160 100644 --- a/library/src/main/java/com/yizhuan/xchat_android_library/utils/image/JXImageUtils.java +++ b/library/src/main/java/com/yizhuan/xchat_android_library/utils/image/JXImageUtils.java @@ -693,7 +693,7 @@ public class JXImageUtils { } - public static String compressImagePxAndQuality(String inPath, File outDir, String outFileName, int expectWidth, long expectSize) { + public static CompressResult compressImagePxAndQuality(String inPath, File outDir, String outFileName, int expectWidth, long expectSize) { try { if (outDir == null) { return null; @@ -748,14 +748,16 @@ public class JXImageUtils { fos.close(); if (thumbnailFile.exists()) { String compressPath = thumbnailFile.getPath(); + exif = new ExifInterface(compressPath); if (orientation != null) { - exif = new ExifInterface(compressPath); exif.setAttribute(ExifInterface.TAG_ORIENTATION, orientation); exif.saveAttributes(); } LogUtil.print(ResUtil.getString(R.string.utils_image_jximageutils_06) + getPicRotate(compressPath)); - return compressPath; - } + int width = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 0); + int height = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 0); + return new CompressResult(compressPath, width, height, "image/jpeg"); + } } catch (Exception ex) { Log.e("mouse_debug", ResUtil.getString(R.string.utils_image_jximageutils_07)); ex.printStackTrace(); @@ -816,5 +818,34 @@ public class JXImageUtils { return degree; } + public static class CompressResult { + private String path; + private int width; + private int height; + private String format; + + public CompressResult(String path, int width, int height, String format) { + this.path = path; + this.width = width; + this.height = height; + this.format = format; + } + + public String getPath() { + return path; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public String getFormat() { + return format; + } + } } diff --git a/library/src/module_common/java/com/yizhuan/xchat_android_library/common/file/FileHelper.java b/library/src/module_common/java/com/yizhuan/xchat_android_library/common/file/FileHelper.java index 24bc5948d..02c90890a 100644 --- a/library/src/module_common/java/com/yizhuan/xchat_android_library/common/file/FileHelper.java +++ b/library/src/module_common/java/com/yizhuan/xchat_android_library/common/file/FileHelper.java @@ -5,7 +5,6 @@ import android.net.Uri; import android.os.Environment; import android.text.TextUtils; -import com.qiniu.android.utils.StringUtils; import com.yizhuan.xchat_android_library.common.application.BaseApp; import com.yizhuan.xchat_android_library.common.util.Logger; import com.yizhuan.xchat_android_library.utils.FP; @@ -878,40 +877,4 @@ public class FileHelper { return content; } - /** - * 文件转换成字符串 - * - * @param filePath 文件路径 - * @return 字符串内容 - */ - public static String getTxtFileContent(String filePath) { - String content = ""; - if (!StringUtils.isNullOrEmpty(filePath)) { - File file = new File(filePath); - if (file.isFile()) { - FileInputStream inputStream = null; - try { - inputStream = new FileInputStream(file); - String line; - StringBuilder sb = new StringBuilder(); - BufferedReader buffReader = new BufferedReader(new InputStreamReader(inputStream)); - while ((line = buffReader.readLine()) != null) { - sb.append(line).append("\n"); - } - content = sb.toString(); - } catch (Exception e) { - Logger.error(TAG, "getTxtFileContent read fail, e = " + e); - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (Exception ignore) { - } - } - } - } - } - return content; - } - } diff --git a/library/src/module_utils/java/com/chuhai/utils/PathUtils.kt b/library/src/module_utils/java/com/chuhai/utils/PathUtils.kt new file mode 100644 index 000000000..4b42f970b --- /dev/null +++ b/library/src/module_utils/java/com/chuhai/utils/PathUtils.kt @@ -0,0 +1,519 @@ +package com.chuhai.utils + +import android.app.Application +import android.os.Build +import android.os.Environment +import java.io.File + +/** + * getRootPath : 获取根路径 + * getDataPath : 获取数据路径 + * getDownloadCachePath : 获取下载缓存路径 + * getInternalAppDataPath : 获取内存应用数据路径 + * getInternalAppCodeCacheDir : 获取内存应用代码缓存路径 + * getInternalAppCachePath : 获取内存应用缓存路径 + * getInternalAppDbsPath : 获取内存应用数据库路径 + * getInternalAppDbPath : 获取内存应用数据库路径 + * getInternalAppFilesPath : 获取内存应用文件路径 + * getInternalAppSpPath : 获取内存应用 SP 路径 + * getInternalAppNoBackupFilesPath: 获取内存应用未备份文件路径 + * getExternalStoragePath : 获取外存路径 + * getExternalMusicPath : 获取外存音乐路径 + * getExternalPodcastsPath : 获取外存播客路径 + * getExternalRingtonesPath : 获取外存铃声路径 + * getExternalAlarmsPath : 获取外存闹铃路径 + * getExternalNotificationsPath : 获取外存通知路径 + * getExternalPicturesPath : 获取外存图片路径 + * getExternalMoviesPath : 获取外存影片路径 + * getExternalDownloadsPath : 获取外存下载路径 + * getExternalDcimPath : 获取外存数码相机图片路径 + * getExternalDocumentsPath : 获取外存文档路径 + * getExternalAppDataPath : 获取外存应用数据路径 + * getExternalAppCachePath : 获取外存应用缓存路径 + * getExternalAppFilesPath : 获取外存应用文件路径 + * getExternalAppMusicPath : 获取外存应用音乐路径 + * getExternalAppPodcastsPath : 获取外存应用播客路径 + * getExternalAppRingtonesPath : 获取外存应用铃声路径 + * getExternalAppAlarmsPath : 获取外存应用闹铃路径 + * getExternalAppNotificationsPath: 获取外存应用通知路径 + * getExternalAppPicturesPath : 获取外存应用图片路径 + * getExternalAppMoviesPath : 获取外存应用影片路径 + * getExternalAppDownloadPath : 获取外存应用下载路径 + * getExternalAppDcimPath : 获取外存应用数码相机图片路径 + * getExternalAppDocumentsPath : 获取外存应用文档路径 + * getExternalAppObbPath : 获取外存应用 OBB 路径 + * 路径 工具类 By https://github.com/Blankj/AndroidUtilCode -> PathUtils.java + * Created by Max on 2018/12/12. + */ +object PathUtils { + + /** + * Return the path of /system. + * + * @return the path of /system + */ + val rootPath: String + get() = Environment.getRootDirectory().absolutePath + + /** + * Return the path of /data. + * + * @return the path of /data + */ + val dataPath: String + get() = Environment.getDataDirectory().absolutePath + + /** + * Return the path of /cache. + * + * @return the path of /cache + */ + val downloadCachePath: String + get() = Environment.getDownloadCacheDirectory().absolutePath + + /** + * Return the path of /data/data/package. + * + * @return the path of /data/data/package + */ + fun getInternalAppDataPath(application: Application): String { + return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + application.applicationInfo.dataDir + } else application.dataDir.absolutePath + } + + /** + * Return the path of /data/data/package/code_cache. + * + * @return the path of /data/data/package/code_cache + */ + fun getInternalAppCodeCacheDir(application: Application): String { + return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + application.applicationInfo.dataDir + "/code_cache" + } else application.codeCacheDir.absolutePath + } + + /** + * Return the path of /data/data/package/cache. + * + * @return the path of /data/data/package/cache + */ + fun getInternalAppCachePath(application: Application): String { + return application.cacheDir.absolutePath + } + + /** + * Return the path of /data/data/package/databases. + * + * @return the path of /data/data/package/databases + */ + fun getInternalAppDbsPath(application: Application): String { + return application.applicationInfo.dataDir + "/databases" + } + + /** + * Return the path of /data/data/package/databases/name. + * + * @param name The name of database. + * @return the path of /data/data/package/databases/name + */ + fun getInternalAppDbPath(application: Application, name: String?): String { + return application.getDatabasePath(name).absolutePath + } + + /** + * Return the path of /data/data/package/files. + * + * @return the path of /data/data/package/files + */ + fun getInternalAppFilesPath(application: Application): String { + return application.filesDir.absolutePath + } + + /** + * Return the path of /data/data/package/shared_prefs. + * + * @return the path of /data/data/package/shared_prefs + */ + fun getInternalAppSpPath(application: Application): String { + return application.applicationInfo.dataDir + "shared_prefs" + } + + /** + * Return the path of /data/data/package/no_backup. + * + * @return the path of /data/data/package/no_backup + */ + fun getInternalAppNoBackupFilesPath(application: Application): String { + return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + application.applicationInfo.dataDir + "no_backup" + } else application.noBackupFilesDir.absolutePath + } + + /** + * Return the path of /storage/emulated/0. + * + * @return the path of /storage/emulated/0 + */ + val externalStoragePath: String? + get() = if (isExternalStorageDisable) null else Environment.getExternalStorageDirectory().absolutePath + + /** + * Return the path of /storage/emulated/0/Music. + * + * @return the path of /storage/emulated/0/Music + */ + val externalMusicPath: String? + get() = if (isExternalStorageDisable) null else Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_MUSIC + ).absolutePath + + /** + * Return the path of /storage/emulated/0/Podcasts. + * + * @return the path of /storage/emulated/0/Podcasts + */ + val externalPodcastsPath: String? + get() = if (isExternalStorageDisable) null else Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PODCASTS + ).absolutePath + + /** + * Return the path of /storage/emulated/0/Ringtones. + * + * @return the path of /storage/emulated/0/Ringtones + */ + val externalRingtonesPath: String? + get() = if (isExternalStorageDisable) null else Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_RINGTONES + ).absolutePath + + /** + * Return the path of /storage/emulated/0/Alarms. + * + * @return the path of /storage/emulated/0/Alarms + */ + val externalAlarmsPath: String? + get() = if (isExternalStorageDisable) null else Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_ALARMS + ).absolutePath + + /** + * Return the path of /storage/emulated/0/Notifications. + * + * @return the path of /storage/emulated/0/Notifications + */ + val externalNotificationsPath: String? + get() = if (isExternalStorageDisable) null else Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_NOTIFICATIONS + ).absolutePath + + /** + * Return the path of /storage/emulated/0/Pictures. + * + * @return the path of /storage/emulated/0/Pictures + */ + val externalPicturesPath: String? + get() = if (isExternalStorageDisable) null else Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES + ).absolutePath + + /** + * Return the path of /storage/emulated/0/Movies. + * + * @return the path of /storage/emulated/0/Movies + */ + val externalMoviesPath: String? + get() = if (isExternalStorageDisable) null else Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_MOVIES + ).absolutePath + + /** + * Return the path of /storage/emulated/0/Download. + * + * @return the path of /storage/emulated/0/Download + */ + val externalDownloadsPath: String? + get() = if (isExternalStorageDisable) null else Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS + ).absolutePath + + /** + * Return the path of /storage/emulated/0/DCIM. + * + * @return the path of /storage/emulated/0/DCIM + */ + val externalDcimPath: String? + get() = if (isExternalStorageDisable) null else Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DCIM + ).absolutePath + + /** + * Return the path of /storage/emulated/0/Documents. + * + * @return the path of /storage/emulated/0/Documents + */ + val externalDocumentsPath: String? + get() { + if (isExternalStorageDisable) return null + return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + Environment.getExternalStorageDirectory().absolutePath + "/Documents" + } else Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOCUMENTS + ).absolutePath + } + + /** + * Return the path of /storage/emulated/0/Android/data/package. + * + * @return the path of /storage/emulated/0/Android/data/package + */ + fun getExternalAppDataPath(application: Application): String? { + return if (isExternalStorageDisable) null else application.externalCacheDir?.parentFile?.absolutePath + } + + /** + * Return the path of /storage/emulated/0/Android/data/package/cache. + * + * @return the path of /storage/emulated/0/Android/data/package/cache + */ + fun getExternalAppCachePath(application: Application): String? { + return if (isExternalStorageDisable) null else application.externalCacheDir?.absolutePath + } + + /** + * Return the path of /storage/emulated/0/Android/data/package/files. + * + * @return the path of /storage/emulated/0/Android/data/package/files + */ + fun getExternalAppFilesPath(application: Application): String? { + return if (isExternalStorageDisable) null else application.getExternalFilesDir(null)?.absolutePath + } + + /** + * Return the path of /storage/emulated/0/Android/data/package/files/Music. + * + * @return the path of /storage/emulated/0/Android/data/package/files/Music + */ + fun getExternalAppMusicPath(application: Application): String? { + return if (isExternalStorageDisable) null else application.getExternalFilesDir( + Environment.DIRECTORY_MUSIC + )?.absolutePath + } + + /** + * Return the path of /storage/emulated/0/Android/data/package/files/Podcasts. + * + * @return the path of /storage/emulated/0/Android/data/package/files/Podcasts + */ + fun getExternalAppPodcastsPath(application: Application): String? { + return if (isExternalStorageDisable) null else application.getExternalFilesDir( + Environment.DIRECTORY_PODCASTS + )?.absolutePath + } + + /** + * Return the path of /storage/emulated/0/Android/data/package/files/Ringtones. + * + * @return the path of /storage/emulated/0/Android/data/package/files/Ringtones + */ + fun getExternalAppRingtonesPath(application: Application): String? { + return if (isExternalStorageDisable) null else application.getExternalFilesDir( + Environment.DIRECTORY_RINGTONES + )?.absolutePath + } + + /** + * Return the path of /storage/emulated/0/Android/data/package/files/Alarms. + * + * @return the path of /storage/emulated/0/Android/data/package/files/Alarms + */ + fun getExternalAppAlarmsPath(application: Application): String? { + return if (isExternalStorageDisable) null else application.getExternalFilesDir( + Environment.DIRECTORY_ALARMS + )?.absolutePath + } + + /** + * Return the path of /storage/emulated/0/Android/data/package/files/Notifications. + * + * @return the path of /storage/emulated/0/Android/data/package/files/Notifications + */ + fun getExternalAppNotificationsPath(application: Application): String? { + return if (isExternalStorageDisable) null else application.getExternalFilesDir( + Environment.DIRECTORY_NOTIFICATIONS + )?.absolutePath + } + + /** + * Return the path of /storage/emulated/0/Android/data/package/files/Pictures. + * + * @return path of /storage/emulated/0/Android/data/package/files/Pictures + */ + fun getExternalAppPicturesPath(application: Application): String? { + return if (isExternalStorageDisable) null else application.getExternalFilesDir( + Environment.DIRECTORY_PICTURES + )?.absolutePath + } + + /** + * Return the path of /storage/emulated/0/Android/data/package/files/Movies. + * + * @return the path of /storage/emulated/0/Android/data/package/files/Movies + */ + fun getExternalAppMoviesPath(application: Application): String? { + return if (isExternalStorageDisable) null else application.getExternalFilesDir( + Environment.DIRECTORY_MOVIES + )?.absolutePath + } + + /** + * Return the path of /storage/emulated/0/Android/data/package/files/Download. + * + * @return the path of /storage/emulated/0/Android/data/package/files/Download + */ + fun getExternalAppDownloadPath(application: Application): String? { + return if (isExternalStorageDisable) null else application.getExternalFilesDir( + Environment.DIRECTORY_DOWNLOADS + )?.absolutePath + } + + /** + * Return the path of /storage/emulated/0/Android/data/package/files/DCIM. + * + * @return the path of /storage/emulated/0/Android/data/package/files/DCIM + */ + fun getExternalAppDcimPath(application: Application): String? { + return if (isExternalStorageDisable) null else application.getExternalFilesDir( + Environment.DIRECTORY_DCIM + )?.absolutePath + } + + /** + * Return the path of /storage/emulated/0/Android/data/package/files/Documents. + * + * @return the path of /storage/emulated/0/Android/data/package/files/Documents + */ + fun getExternalAppDocumentsPath(application: Application): String? { + if (isExternalStorageDisable) return null + return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + application.getExternalFilesDir(null)?.absolutePath + "/Documents" + } else application.getExternalFilesDir( + Environment.DIRECTORY_DOCUMENTS + )?.absolutePath + } + + /** + * Return the path of /storage/emulated/0/Android/obb/package. + * + * @return the path of /storage/emulated/0/Android/obb/package + */ + fun getExternalAppObbPath(application: Application): String? { + return if (isExternalStorageDisable) null else application.obbDir.absolutePath + } + + private val isExternalStorageDisable: Boolean + private get() = Environment.MEDIA_MOUNTED != Environment.getExternalStorageState() + + /** + * 判断sub是否在parent之下的文件或子文件夹

+ * + * @param parent + * @param sub + * @return + */ + fun isSub(parent: File, sub: File): Boolean { + return try { + sub.absolutePath.startsWith(parent.absolutePath) + } catch (e: Exception) { + false + } + } + + /** + * 获取子绝对路径与父绝对路径的相对路径 + * + * @param parentPath + * @param subPath + * @return + */ + fun getRelativePath(parentPath: String?, subPath: String?): String? { + return try { + if (parentPath == null || subPath == null) { + return null + } + if (subPath.startsWith(parentPath)) { + subPath.substring(parentPath.length) + } else { + null + } + } catch (e: Exception) { + null + } + } + + /** + * 拼接两个路径 + * + * @param pathA 路径A + * @param pathB 路径B + * @return 拼接后的路径 + */ + fun plusPath(pathA: String?, pathB: String?): String? { + if (pathA == null) { + return pathB + } + if (pathB == null) { + return pathA + } + return plusPathNotNull(pathA, pathB) + } + + /** + * 拼接两个路径 + * + * @param pathA 路径A + * @param pathB 路径B + * @return 拼接后的路径 + */ + fun plusPathNotNull(pathA: String, pathB: String): String { + val pathAEndSeparator = pathA.endsWith(File.separator) + val pathBStartSeparator = pathB.startsWith(File.separator) + return if (pathAEndSeparator && pathBStartSeparator) { + pathA + pathB.substring(1) + } else if (pathAEndSeparator || pathBStartSeparator) { + pathA + pathB + } else { + pathA + File.separator + pathB + } + } + + /** + * 获取后缀名称 + * @param path 路径 + * @return 后缀格式 .mp4 .gif 等 + */ + fun getSuffixType(path: String): String? { + if (path.isEmpty()) { + return null + } + val dotIndex = path.indexOfLast { + '.' == it + } + val separatorIndex = path.indexOfLast { + '/' == it + } + if (dotIndex >= 0 && dotIndex > separatorIndex) { + val suffix = path.substring(dotIndex) + val askIndex = suffix.indexOfLast { + '?' == it + } + return if (askIndex >= 0) { + suffix.substring(0, askIndex) + } else { + suffix + } + } + return null + } +} \ No newline at end of file