diff --git a/app/src/main/java/com/nnbc123/app/application/XChatApplication.java b/app/src/main/java/com/nnbc123/app/application/XChatApplication.java index 129349c59..f589e494e 100644 --- a/app/src/main/java/com/nnbc123/app/application/XChatApplication.java +++ b/app/src/main/java/com/nnbc123/app/application/XChatApplication.java @@ -20,7 +20,6 @@ import com.bumptech.glide.request.target.ViewTarget; import com.bytedance.hume.readapk.HumeSDK; import com.chuhai.utils.MetaDataUtils; import com.coorchice.library.utils.LogUtils; -import com.facebook.stetho.Stetho; import com.hjq.toast.ToastUtils; import com.mob.MobSDK; import com.mob.moblink.MobLink; @@ -256,7 +255,6 @@ public class XChatApplication extends BaseApp { ViewTarget.setTagId(R.id.tag_glide); init(channel); - initStetho(context); //生命周期监听 if (lifeManager == null) { @@ -551,18 +549,4 @@ public class XChatApplication extends BaseApp { } GlobalHandleManager.get().unInit(); } - - /** - * 初始化Stetho(网络调试) - */ - private static void initStetho(Context context) { - if (Env.isDebug()) { - Stetho.initialize( - Stetho.newInitializerBuilder(context) - .enableDumpapp(Stetho.defaultDumperPluginsProvider(context)) - .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(context)) - .build() - ); - } - } } diff --git a/app/src/main/java/com/nnbc123/app/skill/activity/AddSkillActivity.kt b/app/src/main/java/com/nnbc123/app/skill/activity/AddSkillActivity.kt index 4b62902ac..72f644a2f 100644 --- a/app/src/main/java/com/nnbc123/app/skill/activity/AddSkillActivity.kt +++ b/app/src/main/java/com/nnbc123/app/skill/activity/AddSkillActivity.kt @@ -4,6 +4,7 @@ import android.Manifest import android.annotation.SuppressLint import android.content.Context import android.content.Intent +import android.os.Build import android.view.View import com.netease.nim.uikit.StatusBarUtil import com.nnbc123.app.R @@ -87,8 +88,7 @@ class AddSkillActivity : BaseBindingActivity() { .append("\n2.").append(ResUtil.getString(R.string.permission_denied_tips_image)) .toString() val params = arrayOf( - Manifest.permission.RECORD_AUDIO, - Manifest.permission.WRITE_EXTERNAL_STORAGE + Manifest.permission.RECORD_AUDIO ) if (RequestPermissionPromptDialog.isNeedPrompt() && !PermissionHelper.isAllGranted( rxPermissions, diff --git a/app/src/main/java/com/nnbc123/app/takephoto/permission/PermissionManager.java b/app/src/main/java/com/nnbc123/app/takephoto/permission/PermissionManager.java index 8dd2d299a..7b9305807 100644 --- a/app/src/main/java/com/nnbc123/app/takephoto/permission/PermissionManager.java +++ b/app/src/main/java/com/nnbc123/app/takephoto/permission/PermissionManager.java @@ -6,6 +6,8 @@ import android.content.pm.PackageManager; import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; + +import android.os.Build; import android.text.TextUtils; import com.nnbc123.app.takephoto.app.TakePhoto; @@ -23,7 +25,7 @@ import java.util.ArrayList; */ public class PermissionManager { public enum TPermission { - STORAGE(Manifest.permission.WRITE_EXTERNAL_STORAGE), + STORAGE((Build.VERSION.SDK_INT >= 33) ? Manifest.permission.READ_MEDIA_IMAGES : Manifest.permission.WRITE_EXTERNAL_STORAGE), CAMERA(Manifest.permission.CAMERA); String stringValue; 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 009cad81b..8df30bf12 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 @@ -38,6 +38,8 @@ public class CustomItem implements Parcelable, Serializable { */ private int fileType; private String format; + private int width; + private int height; public CustomItem(String path, int fileType) { this(path, fileType, "jpeg"); @@ -50,12 +52,16 @@ public class CustomItem implements Parcelable, Serializable { this.path = path; this.fileType = fileType; this.format = format; + this.width = 0; + this.height = 0; } 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 boolean isImage() { @@ -104,6 +110,22 @@ public class CustomItem implements Parcelable, Serializable { this.format = format; } + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + @Override public int describeContents() { return 0; @@ -114,5 +136,7 @@ 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); } } diff --git a/app/src/module_album/java/com/zhihu/matisse/internal/entity/Item.java b/app/src/module_album/java/com/zhihu/matisse/internal/entity/Item.java index 62f3ec259..f7a43aaab 100644 --- a/app/src/module_album/java/com/zhihu/matisse/internal/entity/Item.java +++ b/app/src/module_album/java/com/zhihu/matisse/internal/entity/Item.java @@ -22,6 +22,7 @@ import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.provider.MediaStore; + import androidx.annotation.Nullable; import com.zhihu.matisse.MimeType; @@ -46,8 +47,10 @@ public class Item implements Parcelable { public final Uri uri; public final long size; public final long duration; // only for video, in ms + public final int width; + public final int height; - private Item(long id, String mimeType, long size, long duration) { + private Item(long id, String mimeType, long size, long duration,int width,int height) { this.id = id; this.mimeType = mimeType; Uri contentUri; @@ -62,14 +65,18 @@ public class Item implements Parcelable { this.uri = ContentUris.withAppendedId(contentUri, id); this.size = size; this.duration = duration; + this.width = width; + this.height = height; } - public Item(String mimeType, Uri uri, long size, long duration) { + public Item(String mimeType, Uri uri, long size, long duration, int width, int height) { this.id = 1; this.mimeType = mimeType; this.uri = uri; this.size = size; this.duration = duration; + this.width = width; + this.height = height; } private Item(Parcel source) { @@ -78,13 +85,44 @@ public class Item implements Parcelable { uri = source.readParcelable(Uri.class.getClassLoader()); size = source.readLong(); duration = source.readLong(); + width = source.readInt(); + height = source.readInt(); } public static Item valueOf(Cursor cursor) { - return new Item(cursor.getLong(cursor.getColumnIndex(MediaStore.Files.FileColumns._ID)), - cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE)), - cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns.SIZE)), - cursor.getLong(cursor.getColumnIndex("duration"))); + return new Item(getCursorLong(cursor, MediaStore.Files.FileColumns._ID, 0), + getCursorString(cursor, MediaStore.MediaColumns.MIME_TYPE, ""), + getCursorLong(cursor, MediaStore.MediaColumns.SIZE, 0), + getCursorLong(cursor, "duration", 0), + getCursorInt(cursor, MediaStore.MediaColumns.WIDTH, 0), + getCursorInt(cursor, MediaStore.MediaColumns.HEIGHT, 0)); + } + + private static long getCursorLong(Cursor cursor, String key, long defaultValue) { + int index = cursor.getColumnIndex(key); + long value = defaultValue; + if (index >= 0) { + value = cursor.getLong(index); + } + return value; + } + + private static String getCursorString(Cursor cursor, String key, String defaultValue) { + int index = cursor.getColumnIndex(key); + String value = defaultValue; + if (index >= 0) { + value = cursor.getString(index); + } + return value; + } + + private static int getCursorInt(Cursor cursor, String key, int defaultValue) { + int index = cursor.getColumnIndex(key); + int value = defaultValue; + if (index >= 0) { + value = cursor.getInt(index); + } + return value; } @Override @@ -99,6 +137,8 @@ public class Item implements Parcelable { dest.writeParcelable(uri, 0); dest.writeLong(size); dest.writeLong(duration); + dest.writeInt(width); + dest.writeInt(height); } public Uri getContentUri() { @@ -134,7 +174,9 @@ public class Item implements Parcelable { && (uri != null && uri.equals(other.uri) || (uri == null && other.uri == null)) && size == other.size - && duration == other.duration; + && duration == other.duration + && width == other.width + && height == other.height; } @Override @@ -147,6 +189,8 @@ public class Item implements Parcelable { result = 31 * result + uri.hashCode(); result = 31 * result + Long.valueOf(size).hashCode(); result = 31 * result + Long.valueOf(duration).hashCode(); + result = 31 * result + Integer.valueOf(width).hashCode(); + result = 31 * result + Integer.valueOf(height).hashCode(); return result; } } diff --git a/app/src/module_album/java/com/zhihu/matisse/internal/loader/AlbumMediaLoader.java b/app/src/module_album/java/com/zhihu/matisse/internal/loader/AlbumMediaLoader.java index 47c22b0d2..71ab876c7 100644 --- a/app/src/module_album/java/com/zhihu/matisse/internal/loader/AlbumMediaLoader.java +++ b/app/src/module_album/java/com/zhihu/matisse/internal/loader/AlbumMediaLoader.java @@ -39,6 +39,8 @@ public class AlbumMediaLoader extends CursorLoader { MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.MIME_TYPE, MediaStore.MediaColumns.SIZE, + MediaStore.MediaColumns.WIDTH, + MediaStore.MediaColumns.HEIGHT, "duration"}; // === params for album ALL && showSingleMediaType: false === @@ -143,7 +145,7 @@ public class AlbumMediaLoader extends CursorLoader { return result; } MatrixCursor dummy = new MatrixCursor(PROJECTION); - dummy.addRow(new Object[]{Item.ITEM_ID_CAPTURE, Item.ITEM_DISPLAY_NAME_CAPTURE, "", 0, 0}); + dummy.addRow(new Object[]{Item.ITEM_ID_CAPTURE, Item.ITEM_DISPLAY_NAME_CAPTURE, "", 0, 0, 0, 0}); return new MergeCursor(new Cursor[]{dummy, result}); } diff --git a/app/src/module_album/java/com/zhihu/matisse/internal/model/SelectedItemCollection.java b/app/src/module_album/java/com/zhihu/matisse/internal/model/SelectedItemCollection.java index 51b83ed31..7c024811f 100644 --- a/app/src/module_album/java/com/zhihu/matisse/internal/model/SelectedItemCollection.java +++ b/app/src/module_album/java/com/zhihu/matisse/internal/model/SelectedItemCollection.java @@ -165,6 +165,9 @@ public class SelectedItemCollection { CustomItem customItem = new CustomItem(); customItem.setPath(PathUtils.getPath(mContext, item.getContentUri())); customItem.setFileType(item.isGif() ? 1 : 0); + customItem.setWidth(item.width); + customItem.setHeight(item.height); + customItem.setFormat(item.mimeType); paths.add(customItem); } return paths; diff --git a/app/src/module_album/java/com/zhihu/matisse/ui/MatisseActivity.java b/app/src/module_album/java/com/zhihu/matisse/ui/MatisseActivity.java index 47c16038d..fc9f88bee 100644 --- a/app/src/module_album/java/com/zhihu/matisse/ui/MatisseActivity.java +++ b/app/src/module_album/java/com/zhihu/matisse/ui/MatisseActivity.java @@ -21,6 +21,8 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.TypedArray; import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -250,6 +252,9 @@ public class MatisseActivity extends AppCompatActivity implements for (Item item : selected) { selectedUris.add(item.getContentUri()); CustomItem customItem = new CustomItem(); + customItem.setFormat(item.mimeType); + customItem.setWidth(item.width); + customItem.setHeight(item.height); customItem.setFileType(item.isGif() ? 1 : 0); customItem.setPath(PathUtils.getPath(this, item.getContentUri())); selectedPaths.add(customItem); @@ -280,6 +285,7 @@ public class MatisseActivity extends AppCompatActivity implements CustomItem customItem = new CustomItem(); customItem.setFileType(0); customItem.setPath(path); + trySetImageInfo(customItem); selectedPath.add(customItem); Intent result = new Intent(); result.putParcelableArrayListExtra(EXTRA_RESULT_SELECTION, selected); @@ -298,6 +304,9 @@ public class MatisseActivity extends AppCompatActivity implements ArrayList selectedPath = new ArrayList<>(1); Item item = selected.get(0); CustomItem customItem = new CustomItem(); + customItem.setFormat(item.mimeType); + customItem.setWidth(item.width); + customItem.setHeight(item.height); customItem.setFileType(2); customItem.setPath(item.uri.toString()); selectedPath.add(customItem); @@ -309,6 +318,26 @@ public class MatisseActivity extends AppCompatActivity implements } } + // 补充图片信息 + private void trySetImageInfo(CustomItem customItem) { + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + Bitmap bitmap = BitmapFactory.decodeFile(customItem.getPath(), options); + if (customItem.getWidth() <= 0) { + customItem.setWidth(options.outWidth); + } + if (customItem.getHeight() <= 0) { + customItem.setHeight(options.outHeight); + } + if (customItem.getFormat() == null || customItem.getFormat().length() == 0) { + customItem.setFormat(options.outMimeType); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + private void updateBottomToolbar() { int selectedCount = mSelectedCollection.count(); if (selectedCount == 0) { @@ -521,20 +550,16 @@ public class MatisseActivity extends AppCompatActivity implements @Override public void capture() { -// if (mMediaStoreCompat != null) { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED + if (ContextCompat.checkSelfPermission(this, (Build.VERSION.SDK_INT >= 33) ? Manifest.permission.READ_MEDIA_IMAGES : Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { - mMediaStoreCompat.dispatchCaptureIntent(this, REQUEST_CODE_CAPTURE); -// startActivityForResult(new Intent(this, CameraActivity.class) -// .putExtra(ConstantValue.KEY_TYPE, mSpec.type), REQUEST_CODE_CAPTURE); + mMediaStoreCompat.dispatchCaptureIntent(this, REQUEST_CODE_CAPTURE); } else { ActivityCompat.requestPermissions(this, new String[]{ - Manifest.permission.WRITE_EXTERNAL_STORAGE, + (Build.VERSION.SDK_INT >= 33) ? Manifest.permission.READ_MEDIA_IMAGES : Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA}, 10); } -// } } @Override diff --git a/app/src/module_community/java/com/nnbc123/app/community/photo/BigPhotoActivity.java b/app/src/module_community/java/com/nnbc123/app/community/photo/BigPhotoActivity.java index 133df3940..e25496f80 100644 --- a/app/src/module_community/java/com/nnbc123/app/community/photo/BigPhotoActivity.java +++ b/app/src/module_community/java/com/nnbc123/app/community/photo/BigPhotoActivity.java @@ -4,6 +4,7 @@ import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import android.view.View; import android.widget.ImageView; @@ -158,7 +159,7 @@ public class BigPhotoActivity extends BaseActivity implements OnFragmentOptionLi if (item == null) { return; } - checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) + checkPermission((Build.VERSION.SDK_INT >= 33) ? Manifest.permission.READ_MEDIA_IMAGES : Manifest.permission.WRITE_EXTERNAL_STORAGE) .compose(bindToLifecycle()) .subscribe(aBoolean -> { if (aBoolean) { diff --git a/app/src/module_community/java/com/nnbc123/app/community/publish/presenter/PublishPresenter.java b/app/src/module_community/java/com/nnbc123/app/community/publish/presenter/PublishPresenter.java index 8b72fad1e..ed7ffad73 100644 --- a/app/src/module_community/java/com/nnbc123/app/community/publish/presenter/PublishPresenter.java +++ b/app/src/module_community/java/com/nnbc123/app/community/publish/presenter/PublishPresenter.java @@ -48,9 +48,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 +77,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 +91,31 @@ 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("文件大小:" + 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()); + } + } LogUtil.print("压缩后:" + compressFile); } if (!TextUtils.isEmpty(compressFile)) { @@ -118,21 +127,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("上传成功:", media); - publishBody.addDynamicMedia(media); + LogUtil.print("上传成功:", url); + item.setResUrl(url); + publishBody.addDynamicMedia(item); imagePaths.remove(0); upload(imagePaths); } diff --git a/app/src/module_community/java/com/nnbc123/app/community/publish/view/PublishActivity.java b/app/src/module_community/java/com/nnbc123/app/community/publish/view/PublishActivity.java index a601a95e8..b982d4c7d 100644 --- a/app/src/module_community/java/com/nnbc123/app/community/publish/view/PublishActivity.java +++ b/app/src/module_community/java/com/nnbc123/app/community/publish/view/PublishActivity.java @@ -338,7 +338,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) { @@ -40,6 +56,8 @@ public class ObjectTypeHelper { } for (DynamicMedia media : paramsList) { CustomItem item = new CustomItem(); + item.setWidth(media.getWidth()); + item.setHeight(media.getHeight()); item.setPath(media.getResUrl()); item.setFileType(CustomItem.UNKOWN); if (media.isJpgOrPng()) { diff --git a/core/build.gradle b/core/build.gradle index 70c568421..6e3d037a8 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -97,6 +97,7 @@ dependencies { implementation 'com.google.firebase:firebase-analytics-ktx' api 'com.android.billingclient:billing:6.0.1' + api 'com.qcloud.cos:cos-android:5.9.23' } repositories { mavenCentral() diff --git a/core/src/main/java/com/nnbc123/core/file/FileModel.java b/core/src/main/java/com/nnbc123/core/file/FileModel.java index c5106548d..4c9560ae2 100644 --- a/core/src/main/java/com/nnbc123/core/file/FileModel.java +++ b/core/src/main/java/com/nnbc123/core/file/FileModel.java @@ -2,21 +2,17 @@ package com.nnbc123.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.nnbc123.core.file.cos.CosClient; +import com.nnbc123.core.file.cos.CosToken; import com.nnbc123.core.base.BaseModel; import com.nnbc123.core.bean.response.ServiceResult; -import com.nnbc123.core.community.bean.DynamicMedia; import com.nnbc123.core.exception.ErrorThrowable; import com.nnbc123.core.utils.net.RxHelper; import com.nnbc123.library.net.rxnet.RxNet; - -import org.json.JSONObject; - import java.io.File; - +import java.util.UUID; import io.reactivex.Single; import retrofit2.http.GET; @@ -27,119 +23,49 @@ public class FileModel extends BaseModel implements IFileModel { } private FileModel() { - Configuration config = new Configuration.Builder() - .zone(FixedZone.zone2) // 设置区域,不指定会自动选择。指定不同区域的上传域名、备用域名、备用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 + " 为空或者该文件不存在!")); } 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 + " 为空或者该文件不存在!")); - } - - 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("上传成功"); - 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("七牛上传", media); - return media; - } - interface Api { /** - * 获取钱包 - * * @return */ - @GET("/qiniu/upload/getUploadToken") - Single> getUploadToken(); - + @GET("/tencentCos/getToken") + Single> getCosToken(); } } diff --git a/core/src/main/java/com/nnbc123/core/file/IFileModel.java b/core/src/main/java/com/nnbc123/core/file/IFileModel.java index 08646238b..a89ff4e1c 100644 --- a/core/src/main/java/com/nnbc123/core/file/IFileModel.java +++ b/core/src/main/java/com/nnbc123/core/file/IFileModel.java @@ -23,8 +23,4 @@ public interface IFileModel extends IModel { */ Single downloadFile(String url); - Single uploadFileReturnImageInfo(String path); - - Single uploadFileReturnImageInfo(String path, String qiniuName); - } diff --git a/core/src/main/java/com/nnbc123/core/file/cos/CosClient.kt b/core/src/main/java/com/nnbc123/core/file/cos/CosClient.kt new file mode 100644 index 000000000..a7c754bec --- /dev/null +++ b/core/src/main/java/com/nnbc123/core/file/cos/CosClient.kt @@ -0,0 +1,113 @@ +package com.nnbc123.core.file.cos + +import android.content.Context +import android.util.Log +import com.nnbc123.core.Env +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 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/nnbc123/core/file/cos/CosCredentialProvider.kt b/core/src/main/java/com/nnbc123/core/file/cos/CosCredentialProvider.kt new file mode 100644 index 000000000..41ffc4db2 --- /dev/null +++ b/core/src/main/java/com/nnbc123/core/file/cos/CosCredentialProvider.kt @@ -0,0 +1,20 @@ +package com.nnbc123.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/nnbc123/core/file/cos/CosException.kt b/core/src/main/java/com/nnbc123/core/file/cos/CosException.kt new file mode 100644 index 000000000..b27ab91c3 --- /dev/null +++ b/core/src/main/java/com/nnbc123/core/file/cos/CosException.kt @@ -0,0 +1,21 @@ +package com.nnbc123.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/nnbc123/core/file/cos/CosToken.kt b/core/src/main/java/com/nnbc123/core/file/cos/CosToken.kt new file mode 100644 index 000000000..15919ffc2 --- /dev/null +++ b/core/src/main/java/com/nnbc123/core/file/cos/CosToken.kt @@ -0,0 +1,43 @@ +package com.nnbc123.core.file.cos + +import com.nnbc123.core.utils.CurrentTimeUtils +import com.tencent.qcloud.core.auth.QCloudLifecycleCredentials +import com.tencent.qcloud.core.auth.SessionQCloudCredentials + +/** + * Created by Max on 2024/1/16 11:44 + * Desc: + **/ +data class CosToken( + val tmpSecretId: String?, + val tmpSecretKey: 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( + tmpSecretId ?: "", tmpSecretKey ?: "", + sessionToken ?: "", startTime ?: 0, expireTime ?: 0 + ) + } +} \ No newline at end of file diff --git a/core/src/main/java/com/nnbc123/core/utils/UploadUtils.java b/core/src/main/java/com/nnbc123/core/utils/UploadUtils.java deleted file mode 100644 index 0685cc8c8..000000000 --- a/core/src/main/java/com/nnbc123/core/utils/UploadUtils.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.nnbc123.core.utils; - -/** - * create by lvzebiao @2019/11/20 - */ -public class UploadUtils { - - - -} diff --git a/core/src/model_community/java/com/nnbc123/core/community/bean/DynamicMedia.java b/core/src/model_community/java/com/nnbc123/core/community/bean/DynamicMedia.java index 48aeaf7e6..dda9fbefa 100644 --- a/core/src/model_community/java/com/nnbc123/core/community/bean/DynamicMedia.java +++ b/core/src/model_community/java/com/nnbc123/core/community/bean/DynamicMedia.java @@ -2,6 +2,8 @@ package com.nnbc123.core.community.bean; import android.text.TextUtils; +import kotlin.jvm.Transient; + /** * create by lvzebiao @2019/11/21 */ @@ -21,6 +23,17 @@ public class DynamicMedia { private int width; private int height; + @Transient + private String localFilePath; + + public String getLocalFilePath() { + return localFilePath; + } + + public void setLocalFilePath(String localFilePath) { + this.localFilePath = localFilePath; + } + /** * 是否是网络图片 * @return - @@ -38,14 +51,14 @@ public class DynamicMedia { if (TextUtils.isEmpty(format)) { return false; } - return format.toLowerCase().equals("jpeg") || format.toLowerCase().equals("jpg") || format.toLowerCase().equals("png"); + return format.toLowerCase().contains("jpeg") || format.toLowerCase().contains("jpg") || format.toLowerCase().contains("png"); } public boolean isGif() { if (TextUtils.isEmpty(format)) { return false; } - return format.toLowerCase().equals("gif"); + return format.toLowerCase().contains("gif"); } // diff --git a/library/build.gradle b/library/build.gradle index cde92c6b2..ac3948b85 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -121,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-jdk7:$kotlin_version" @@ -143,10 +141,6 @@ dependencies { api 'com.google.android.exoplayer:exoplayer:2.18.1' - // 网络请求chrome数据调试 - api 'com.facebook.stetho:stetho:1.5.1' - api 'com.facebook.stetho:stetho-okhttp3:1.5.1' - api 'com.geyifeng.immersionbar:immersionbar:3.2.2' api files("libs/alicloud-android-utdid-2.6.0.jar") diff --git a/library/src/main/java/com/nnbc123/library/net/rxnet/interceptor/HttpLoggingInterceptor.java b/library/src/main/java/com/nnbc123/library/net/rxnet/interceptor/HttpLoggingInterceptor.java deleted file mode 100644 index 4b2667811..000000000 --- a/library/src/main/java/com/nnbc123/library/net/rxnet/interceptor/HttpLoggingInterceptor.java +++ /dev/null @@ -1,338 +0,0 @@ -/* - * Copyright (C) 2015 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.nnbc123.library.net.rxnet.interceptor; - -import static okhttp3.internal.platform.Platform.INFO; - -import androidx.annotation.NonNull; - -import java.io.EOFException; -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.Collections; -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.TimeUnit; - -import okhttp3.Connection; -import okhttp3.Headers; -import okhttp3.Interceptor; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.internal.http.HttpHeaders; -import okhttp3.internal.platform.Platform; -import okio.Buffer; -import okio.BufferedSource; -import okio.GzipSource; - -/** - * An OkHttp interceptor which logs request and response information. Can be applied as an - * {@linkplain OkHttpClient#interceptors() application interceptor} or as a {@linkplain - * OkHttpClient#networkInterceptors() network interceptor}.

The format of the logs created by - * this class should not be considered stable and may change slightly between releases. If you need - * a stable logging format, use your own interceptor. - */ -public final class HttpLoggingInterceptor implements Interceptor { - private static final Charset UTF8 = Charset.forName("UTF-8"); - private final Logger logger; - private volatile Set headersToRedact = Collections.emptySet(); - private volatile Level level = Level.NONE; - - public HttpLoggingInterceptor() { - this(Logger.DEFAULT); - } - - public HttpLoggingInterceptor(Logger logger) { - this.logger = logger; - } - - /** - * Returns true if the body in question probably contains human readable text. Uses a small sample - * of code points to detect unicode control characters commonly used in binary file signatures. - */ - static boolean isPlaintext(Buffer buffer) { - try { - Buffer prefix = new Buffer(); - long byteCount = buffer.size() < 64 ? buffer.size() : 64; - buffer.copyTo(prefix, 0, byteCount); - for (int i = 0; i < 16; i++) { - if (prefix.exhausted()) { - break; - } - int codePoint = prefix.readUtf8CodePoint(); - if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) { - return false; - } - } - return true; - } catch (EOFException e) { - return false; // Truncated UTF-8 sequence. - } - } - - private static boolean bodyHasUnknownEncoding(Headers headers) { - String contentEncoding = headers.get("Content-Encoding"); - return contentEncoding != null - && !contentEncoding.equalsIgnoreCase("identity") - && !contentEncoding.equalsIgnoreCase("gzip"); - } - - public void redactHeader(String name) { - Set newHeadersToRedact = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - newHeadersToRedact.addAll(headersToRedact); - newHeadersToRedact.add(name); - headersToRedact = newHeadersToRedact; - } - - public Level getLevel() { - return level; - } - - /** - * Change the level at which this interceptor logs. - */ - public HttpLoggingInterceptor setLevel(Level level) { - if (level == null) throw new NullPointerException("level == null. Use Level.NONE instead."); - this.level = level; - return this; - } - - @NonNull - @Override - public Response intercept(Chain chain) throws IOException { - Level level = this.level; - - Request request = chain.request(); - if (level == Level.NONE) { - return chain.proceed(request); - } - - boolean logBody = level == Level.BODY; - boolean logHeaders = logBody || level == Level.HEADERS; - - synchronized (this) { - RequestBody requestBody = request.body(); - boolean hasRequestBody = requestBody != null; - - Connection connection = chain.connection(); - String requestStartMessage = "--> " - + request.method() - + ' ' + request.url() - + (connection != null ? " " + connection.protocol() : ""); - if (!logHeaders && hasRequestBody) { - requestStartMessage += " (" + requestBody.contentLength() + "-byte body)"; - } - logger.log(requestStartMessage); - - if (logHeaders) { - if (hasRequestBody) { - // Request body headers are only present when installed as a network interceptor. Force - // them to be included (when available) so there values are known. - if (requestBody.contentType() != null) { - logger.log("Content-Type: " + requestBody.contentType()); - } - if (requestBody.contentLength() != -1) { - logger.log("Content-Length: " + requestBody.contentLength()); - } - } - - Headers headers = request.headers(); - for (int i = 0, count = headers.size(); i < count; i++) { - String name = headers.name(i); - // Skip headers from the request body as they are explicitly logged above. - if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) { - logHeader(headers, i); - } - } - - if (!logBody || !hasRequestBody) { - logger.log("--> END " + request.method()); - } else if (bodyHasUnknownEncoding(request.headers())) { - logger.log("--> END " + request.method() + " (encoded body omitted)"); - } else if (requestBody.isDuplex()) { - logger.log("--> END " + request.method() + " (duplex request body omitted)"); - } else { - Buffer buffer = new Buffer(); - requestBody.writeTo(buffer); - - Charset charset = UTF8; - MediaType contentType = requestBody.contentType(); - if (contentType != null) { - charset = contentType.charset(UTF8); - } - - logger.log(""); - if (isPlaintext(buffer)) { - logger.log(buffer.readString(charset)); - logger.log("--> END " + request.method() - + " (" + requestBody.contentLength() + "-byte body)"); - } else { - logger.log("--> END " + request.method() + " (binary " - + requestBody.contentLength() + "-byte body omitted)"); - } - } - } - } - - long startNs = System.nanoTime(); - Response response; - try { - response = chain.proceed(request); - } catch (Exception e) { - logger.log("<-- HTTP FAILED: " + e); - throw e; - } - synchronized (this) { - long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs); - - ResponseBody responseBody = response.body(); - long contentLength = responseBody.contentLength(); - String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length"; - logger.log("<-- " - + response.code() - + (response.message().isEmpty() ? "" : ' ' + response.message()) - + ' ' + response.request().url() - + " (" + tookMs + "ms" + (!logHeaders ? ", " + bodySize + " body" : "") + ')'); - - if (logHeaders) { - Headers headers = response.headers(); - for (int i = 0, count = headers.size(); i < count; i++) { - logHeader(headers, i); - } - - if (!logBody || !HttpHeaders.hasBody(response)) { - logger.log("<-- END HTTP"); - } else if (bodyHasUnknownEncoding(response.headers())) { - logger.log("<-- END HTTP (encoded body omitted)"); - } else { - BufferedSource source = responseBody.source(); - source.request(Long.MAX_VALUE); // Buffer the entire body. - Buffer buffer = source.getBuffer(); - - Long gzippedLength = null; - if ("gzip".equalsIgnoreCase(headers.get("Content-Encoding"))) { - gzippedLength = buffer.size(); - try (GzipSource gzippedResponseBody = new GzipSource(buffer.clone())) { - buffer = new Buffer(); - buffer.writeAll(gzippedResponseBody); - } - } - - Charset charset = UTF8; - MediaType contentType = responseBody.contentType(); - if (contentType != null) { - charset = contentType.charset(UTF8); - } - - if (!isPlaintext(buffer)) { - logger.log(""); - logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)"); - return response; - } - - if (contentLength != 0) { - logger.log(""); - logger.log(buffer.clone().readString(charset)); - } - - if (gzippedLength != null) { - logger.log("<-- END HTTP (" + buffer.size() + "-byte, " - + gzippedLength + "-gzipped-byte body)"); - } else { - logger.log("<-- END HTTP (" + buffer.size() + "-byte body)"); - } - } - } - } - return response; - } - - private void logHeader(Headers headers, int i) { - String value = headersToRedact.contains(headers.name(i)) ? "██" : headers.value(i); - logger.log(headers.name(i) + ": " + value); - } - - public enum Level { - /** - * No logs. - */ - NONE, - /** - * Logs request and response lines. - * - *

Example: - *

{@code
-         * --> POST /greeting http/1.1 (3-byte body)
-         *
-         * <-- 200 OK (22ms, 6-byte body)
-         * }
- */ - BASIC, - /** - * Logs request and response lines and their respective headers. - * - *

Example: - *

{@code
-         * --> POST /greeting http/1.1
-         * Host: example.com
-         * Content-Type: plain/text
-         * Content-Length: 3
-         * --> END POST
-         *
-         * <-- 200 OK (22ms)
-         * Content-Type: plain/text
-         * Content-Length: 6
-         * <-- END HTTP
-         * }
- */ - HEADERS, - /** - * Logs request and response lines and their respective headers and bodies (if present). - * - *

Example: - *

{@code
-         * --> POST /greeting http/1.1
-         * Host: example.com
-         * Content-Type: plain/text
-         * Content-Length: 3
-         *
-         * Hi?
-         * --> END POST
-         *
-         * <-- 200 OK (22ms)
-         * Content-Type: plain/text
-         * Content-Length: 6
-         *
-         * Hello!
-         * <-- END HTTP
-         * }
- */ - BODY - } - - public interface Logger { - /** - * A {@link Logger} defaults output appropriate for the current platform. - */ - Logger DEFAULT = message -> Platform.get().log(INFO, message, null); - - void log(String message); - } -} diff --git a/library/src/main/java/com/nnbc123/library/net/rxnet/manager/RxNetManager.java b/library/src/main/java/com/nnbc123/library/net/rxnet/manager/RxNetManager.java index ed35d3402..468bd313c 100644 --- a/library/src/main/java/com/nnbc123/library/net/rxnet/manager/RxNetManager.java +++ b/library/src/main/java/com/nnbc123/library/net/rxnet/manager/RxNetManager.java @@ -2,13 +2,11 @@ package com.nnbc123.library.net.rxnet.manager; import android.content.Context; -import com.facebook.stetho.okhttp3.StethoInterceptor; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.nnbc123.library.BuildConfig; import com.nnbc123.library.net.rxnet.converter.GsonConverterFactory; import com.nnbc123.library.net.rxnet.https.HttpsUtils; -import com.nnbc123.library.net.rxnet.interceptor.HttpLoggingInterceptor; import com.nnbc123.library.net.rxnet.utils.RxNetLog; import java.io.IOException; @@ -29,6 +27,7 @@ import okhttp3.Cache; import okhttp3.ConnectionPool; import okhttp3.Interceptor; import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Retrofit; import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; @@ -56,16 +55,9 @@ public final class RxNetManager { mBuilder = new OkHttpClient.Builder(); if (RxNetLog.DEBUG) { - //正式环境千万不要加这玩意,为了方便日志查看,拦截器里面加了synchronized关键字,接口请求是串行的 - HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { - @Override - public void log(String message) { - RxNetLog.d("OKHttp-------%s", message); - } - }); + HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); mBuilder.addInterceptor(loggingInterceptor); - mBuilder.addNetworkInterceptor(new StethoInterceptor()); } for (Interceptor interceptor : interceptors) { diff --git a/library/src/main/java/com/nnbc123/library/utils/image/JXImageUtils.java b/library/src/main/java/com/nnbc123/library/utils/image/JXImageUtils.java index aea39840b..7308334e3 100644 --- a/library/src/main/java/com/nnbc123/library/utils/image/JXImageUtils.java +++ b/library/src/main/java/com/nnbc123/library/utils/image/JXImageUtils.java @@ -19,6 +19,7 @@ import android.graphics.RectF; import android.media.ExifInterface; import android.util.Log; + import com.nnbc123.library.utils.LogUtil; import com.nnbc123.library.utils.StringUtils; import com.nnbc123.library.utils.file.JXFileUtils; @@ -690,78 +691,79 @@ public class JXImageUtils { return baos.toByteArray(); } + public static CompressResult compressImagePxAndQuality(String inPath, File outDir, String outFileName, int expectWidth, long expectSize) { + try { + if (outDir == null) { + return null; + } + if (!outDir.exists()) { + //创建目录 + outDir.mkdirs(); + } + if (!outDir.exists()) { + return null; + } - public static String compressImagePxAndQuality(String inPath, File outDir, String outFileName, int expectWidth, long expectSize) { - try { - if (outDir == null) { - return null; - } - if (!outDir.exists()) { - //创建目录 - outDir.mkdirs(); - } - if (!outDir.exists()) { - return null; - } + //读取原图的旋转角度,并写入到压缩图片中 + ExifInterface exif = new ExifInterface(inPath); + String orientation = exif.getAttribute(ExifInterface.TAG_ORIENTATION); + LogUtil.print("原图:" + inPath); +// LogUtil.print("原图旋转角度:" + getPicRotate(inPath)); - //读取原图的旋转角度,并写入到压缩图片中 - ExifInterface exif = new ExifInterface(inPath); - String orientation = exif.getAttribute(ExifInterface.TAG_ORIENTATION); - LogUtil.print("原图:" + inPath); - LogUtil.print("原图旋转角度:" + getPicRotate(inPath)); + BitmapFactory.Options bmOptions = new BitmapFactory.Options(); + bmOptions.inPreferredConfig = Config.RGB_565; + bmOptions.inSampleSize = getExpectInSampleSize(inPath, expectWidth); + Bitmap bitmap = BitmapFactory.decodeFile(inPath, bmOptions); - BitmapFactory.Options bmOptions = new BitmapFactory.Options(); - bmOptions.inPreferredConfig = Config.RGB_565; - bmOptions.inSampleSize = getExpectInSampleSize(inPath, expectWidth); - Bitmap bitmap = BitmapFactory.decodeFile(inPath, bmOptions); + LogUtil.print("宽高," + bitmap.getWidth() + "," + bitmap.getHeight()); - LogUtil.print("宽高," + bitmap.getWidth() + "," + bitmap.getHeight()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int options = 100; + int minQuality = 20; + bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);//质量压缩方法,把压缩后的数据存放到baos中 (100表示不压缩,0表示压缩到最小) + Log.e("mouse_debug", "压缩前质量:" + baos.toByteArray().length); + while (baos.toByteArray().length > expectSize) {//循环判断如果压缩后图片是否大于指定大小,大于继续压缩 + baos.reset();//重置baos即让下一次的写入覆盖之前的内容 + options -= 5;//图片质量每次减少5 - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - int options = 100; - int minQuality = 20; - bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);//质量压缩方法,把压缩后的数据存放到baos中 (100表示不压缩,0表示压缩到最小) - Log.e("mouse_debug", "压缩前质量:" + baos.toByteArray().length); - while (baos.toByteArray().length > expectSize) {//循环判断如果压缩后图片是否大于指定大小,大于继续压缩 - baos.reset();//重置baos即让下一次的写入覆盖之前的内容 - options -= 5;//图片质量每次减少5 + if (options <= minQuality) options = minQuality;//如果图片质量小于5,为保证压缩后的图片质量,图片最底压缩质量为5 + LogUtil.print("压缩参数:" + options); + bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);//将压缩后的图片保存到baos中 + if (options == minQuality) break;//如果图片的质量已降到最低则,不再进行压缩 + } + if (!bitmap.isRecycled()) { + bitmap.recycle();//回收内存中的图片 + } - if (options <= minQuality) options = minQuality;//如果图片质量小于5,为保证压缩后的图片质量,图片最底压缩质量为5 - LogUtil.print("压缩参数:" + options); - bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);//将压缩后的图片保存到baos中 - if (options == minQuality) break;//如果图片的质量已降到最低则,不再进行压缩 - } - if (!bitmap.isRecycled()) { - bitmap.recycle();//回收内存中的图片 - } - - File thumbnailFile = new File(outDir, outFileName); - if (thumbnailFile.exists()) { - thumbnailFile.delete(); - } - thumbnailFile.createNewFile(); - FileOutputStream fos = new FileOutputStream(thumbnailFile);//将压缩后的图片保存的本地上指定路径中 - fos.write(baos.toByteArray()); - fos.flush(); - fos.close(); - if (thumbnailFile.exists()) { - String compressPath = thumbnailFile.getPath(); - if (orientation != null) { - exif = new ExifInterface(compressPath); - exif.setAttribute(ExifInterface.TAG_ORIENTATION, orientation); - exif.saveAttributes(); - } - LogUtil.print("压缩图旋转角度:" + getPicRotate(compressPath)); - return compressPath; - } - } catch (Exception ex) { - Log.e("mouse_debug", "压缩失败..."); - ex.printStackTrace(); - } catch (OutOfMemoryError error) { - LogUtil.print("压缩图片OOM了"); - } - return null; - } + File thumbnailFile = new File(outDir, outFileName); + if (thumbnailFile.exists()) { + thumbnailFile.delete(); + } + thumbnailFile.createNewFile(); + FileOutputStream fos = new FileOutputStream(thumbnailFile);//将压缩后的图片保存的本地上指定路径中 + fos.write(baos.toByteArray()); + fos.flush(); + fos.close(); + if (thumbnailFile.exists()) { + String compressPath = thumbnailFile.getPath(); + exif = new ExifInterface(compressPath); + if (orientation != null) { + exif.setAttribute(ExifInterface.TAG_ORIENTATION, orientation); + exif.saveAttributes(); + } + int width = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 0); + int height = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 0); +// LogUtil.print("压缩图旋转角度:" + getPicRotate(compressPath)); + return new CompressResult(compressPath, width, height, "image/jpeg"); + } + } catch (Exception ex) { + Log.e("mouse_debug", "压缩失败..."); + ex.printStackTrace(); + } catch (OutOfMemoryError error) { + LogUtil.print("压缩图片OOM了"); + } + return null; + } public static int getExpectInSampleSize(String path, int expectWidth) { //先获取图片旋转角度,如果是90或者270°,则以高作为参考值,否则以宽作为参考值 @@ -814,5 +816,33 @@ 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/nnbc123/library/common/file/FileHelper.java b/library/src/module_common/java/com/nnbc123/library/common/file/FileHelper.java index 73440eb7e..d64c3da67 100644 --- a/library/src/module_common/java/com/nnbc123/library/common/file/FileHelper.java +++ b/library/src/module_common/java/com/nnbc123/library/common/file/FileHelper.java @@ -5,8 +5,9 @@ import android.net.Uri; import android.os.Environment; import android.text.TextUtils; +import androidx.core.text.TextUtilsCompat; + import com.nnbc123.library.utils.FP; -import com.qiniu.android.utils.StringUtils; import com.nnbc123.library.common.application.BaseApp; import com.nnbc123.library.common.util.Logger; @@ -877,41 +878,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 index 888e9c97b..4b42f970b 100644 --- a/library/src/module_utils/java/com/chuhai/utils/PathUtils.kt +++ b/library/src/module_utils/java/com/chuhai/utils/PathUtils.kt @@ -45,451 +45,475 @@ import java.io.File * 路径 工具类 By https://github.com/Blankj/AndroidUtilCode -> PathUtils.java * Created by Max on 2018/12/12. */ -class PathUtils private constructor() { - init { - throw UnsupportedOperationException("u can't instantiate me...") +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 } - companion object { - /** - * Return the path of /system. - * - * @return the path of /system - */ - val rootPath: String - get() = Environment.getRootDirectory().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. - * - * @return the path of /data - */ - val dataPath: String - get() = Environment.getDataDirectory().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 /cache. - * - * @return the path of /cache - */ - val downloadCachePath: String - get() = Environment.getDownloadCacheDirectory().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. - * - * @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/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/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/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/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/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/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/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 /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 /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 /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 /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 /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 /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 /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/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. - * - * @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/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/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/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/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/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/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/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/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/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/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/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/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? { + /** + * 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) { - application.getExternalFilesDir(null)?.absolutePath + "/Documents" - } else application.getExternalFilesDir( + Environment.getExternalStorageDirectory().absolutePath + "/Documents" + } else Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_DOCUMENTS - )?.absolutePath + ).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 - } + /** + * 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 + } - private val isExternalStorageDisable: Boolean - private get() = Environment.MEDIA_MOUNTED != Environment.getExternalStorageState() + /** + * 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 + } - /** - * 判断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 - } - } + /** + * 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 + } - /** - * 获取子绝对路径与父绝对路径的相对路径 - * - * @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 - } - } + /** + * 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 + } - /** - * 拼接两个路径 - * - * @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) - } + /** + * 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 + } - /** - * 拼接两个路径 - * - * @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 - } + /** + * 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 diff --git a/library/src/module_utils/java/com/chuhai/utils/ServiceTime.kt b/library/src/module_utils/java/com/chuhai/utils/ServiceTime.kt deleted file mode 100644 index fdaf87b46..000000000 --- a/library/src/module_utils/java/com/chuhai/utils/ServiceTime.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.chuhai.utils - -import android.os.SystemClock - -/** - * Created by Max on 2023/10/24 15:11 - * Desc:服务器时间 - */ -object ServiceTime { - - // 服务器时间与系统开机时间的时差 - private var serviceTimeDiff: Long? = null - - val time - get() = if (serviceTimeDiff == null) System.currentTimeMillis() - else SystemClock.elapsedRealtime() + serviceTimeDiff!! - - /** - * 刷新服务器时间 - */ - fun refreshServiceTime(time: Long) { - //serviceTimeDiff = 服务器时间 - 此刻系统启动时间 - serviceTimeDiff = time - SystemClock.elapsedRealtime() - } -} \ No newline at end of file diff --git a/nim_uikit/src/com/netease/nim/uikit/business/session/activity/WatchMessagePictureActivity.java b/nim_uikit/src/com/netease/nim/uikit/business/session/activity/WatchMessagePictureActivity.java index 33c89c904..3800c62ef 100644 --- a/nim_uikit/src/com/netease/nim/uikit/business/session/activity/WatchMessagePictureActivity.java +++ b/nim_uikit/src/com/netease/nim/uikit/business/session/activity/WatchMessagePictureActivity.java @@ -5,6 +5,7 @@ import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Color; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.text.TextUtils; @@ -589,7 +590,7 @@ public class WatchMessagePictureActivity extends UI { } protected String[] getNeedPermissions() { - return new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + return new String[]{(Build.VERSION.SDK_INT >= 33) ? Manifest.permission.READ_MEDIA_IMAGES : Manifest.permission.WRITE_EXTERNAL_STORAGE}; } }