From a47e9cd7e9467eddeeb3cd6e2734eae08132db53 Mon Sep 17 00:00:00 2001 From: sunwei Date: Mon, 27 Sep 2021 19:13:23 +0800 Subject: [PATCH] refact --- README-zh.md | 151 ----------- README.md | 161 ++---------- app/build.gradle | 9 +- app/src/main/AndroidManifest.xml | 5 +- .../example/ExampleActivity.java | 151 ++++------- .../example/ExampleFragment.java | 154 +++++------ build.gradle | 4 +- gradle-mvn-push.gradle | 114 -------- gradle/wrapper/gradle-wrapper.properties | 2 +- library/build.gradle | 23 +- library/src/main/AndroidManifest.xml | 2 - .../com/sw926/imagefileselector/AppLogger.kt | 186 ------------- .../sw926/imagefileselector/CommonUtils.kt | 19 +- .../sw926/imagefileselector/Compatibility.kt | 131 +--------- .../imagefileselector/CompressFormatUtils.kt | 13 +- .../sw926/imagefileselector/CompressParams.kt | 11 + .../sw926/imagefileselector/ErrorResult.kt | 10 - .../imagefileselector/ImageCaptureHelper.kt | 246 +++++------------- .../imagefileselector/ImageCompressHelper.kt | 77 ------ .../imagefileselector/ImageCropHelper.kt | 155 +++++++++++ .../sw926/imagefileselector/ImageCropper.kt | 244 ----------------- .../ImageFileResultListener.kt | 15 ++ .../imagefileselector/ImageFileSelector.kt | 145 ++++------- .../imagefileselector/ImagePickHelper.kt | 171 ++---------- .../ImageUriResultListener.kt | 17 ++ .../com/sw926/imagefileselector/ImageUtils.kt | 198 +++++++------- .../imagefileselector/PermissionsHelper.kt | 36 --- .../contract/CropImageContract.kt | 54 ++++ .../contract/PickImageContract.kt | 26 ++ library/src/main/res/xml/files.xml | 2 +- 30 files changed, 701 insertions(+), 1831 deletions(-) delete mode 100644 README-zh.md delete mode 100644 gradle-mvn-push.gradle delete mode 100644 library/src/main/java/com/sw926/imagefileselector/AppLogger.kt create mode 100644 library/src/main/java/com/sw926/imagefileselector/CompressParams.kt delete mode 100644 library/src/main/java/com/sw926/imagefileselector/ErrorResult.kt delete mode 100644 library/src/main/java/com/sw926/imagefileselector/ImageCompressHelper.kt create mode 100644 library/src/main/java/com/sw926/imagefileselector/ImageCropHelper.kt delete mode 100644 library/src/main/java/com/sw926/imagefileselector/ImageCropper.kt create mode 100644 library/src/main/java/com/sw926/imagefileselector/ImageFileResultListener.kt create mode 100644 library/src/main/java/com/sw926/imagefileselector/ImageUriResultListener.kt delete mode 100644 library/src/main/java/com/sw926/imagefileselector/PermissionsHelper.kt create mode 100644 library/src/main/java/com/sw926/imagefileselector/contract/CropImageContract.kt create mode 100644 library/src/main/java/com/sw926/imagefileselector/contract/PickImageContract.kt diff --git a/README-zh.md b/README-zh.md deleted file mode 100644 index 4a32643..0000000 --- a/README-zh.md +++ /dev/null @@ -1,151 +0,0 @@ -# ImageFileSelector - -[![Build Status](https://travis-ci.org/sw926/ImageFileSelector.svg?branch=master)](https://travis-ci.org/sw926/ImageFileSelector) - -##### 轻量级的选取图片和裁切图片的库,使用系统自带的软件实现。 - -##### 支持Android版本 Api Level >= 16 - -使用方法 ----------- -Maven - -```xml - - com.sw926.imagefileselector - library - 1.1.0-SNAPSHOT - -``` -Gradle - -```gradle -compile 'com.sw926.imagefileselector:library:1.1.0-SNAPSHOT' -``` - - -选取图片 ----------- -初始化 - -``` java -ImageFileSelector mImageFileSelector = new ImageFileSelector(this); -mImageFileSelector.setCallback(new ImageFileSelector.Callback() { - @Override - public void onError(@NotNull ErrorResult errorResult) { - switch (errorResult) { - case permissionDenied: - break; - case canceled: - break; - case error: - break; - } - } - - @Override - public void onSuccess(@NotNull String file) { - } -}); -``` -在Activity中,加入以下代码 -```java -@Override -protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - mImageFileSelector.onActivityResult(this, requestCode, resultCode, data); -} - -@Override -protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - mImageFileSelector.onSaveInstanceState(outState); -} - -@Override -protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - mImageFileSelector.onRestoreInstanceState(savedInstanceState); -} - -// Android 6.0的动态权限 -@Override -public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - mImageFileSelector.onRequestPermissionsResult(this, requestCode, permissions, grantResults); -} -``` -设置参数 -```java -// 设置输出文件的尺寸 -mImageFileSelector.setOutPutImageSize(800, 600); -// 设置保存图片的质量 0到100 -mImageFileSelector.setQuality(80); -``` -现在开始选取图片 -```java -// 拍照选取 -mImageFileSelector.takePhoto(this, requestCode); -// 从文件选取 -mImageFileSelector.selectImage(this, requestCode); -// 设置图片输出路径,默认/sdcard/Android/data/{packagename}/cache/images/ -mImageFileSelector.setOutPutPath(); -``` - - -裁切图片 ----------- -初始化 -```java -ImageCropper mImageCropper = new ImageCropper(); -mImageCropper.setCallback(new ImageCropper.ImageCropperCallback() { - @Override - public void onError(@NotNull ImageCropper.CropperErrorResult result) { - switch (result) { - case error: - break; - case canceled: - break; - case notSupport: - break; - } - } - - @Override - public void onSuccess(@NotNull String outputFile) { - } - }); -``` -在Activity或Fragment中加入以下代码 -```java -@Override -protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - mImageCropper.onActivityResult(requestCode, resultCode, data); -} - -@Override -protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - mImageCropper.onSaveInstanceState(outState); -} - -@Override -protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - mImageCropper.onRestoreInstanceState(savedInstanceState); -} -``` -设置参数 -```java -// 设置输出文件的宽高比 -mImageCropper.setOutPutAspect(1, 1); -// 设置输出文件的尺寸 -mImageCropper.setOutPut(800, 800); -// 设置是否缩放到指定的尺寸 -mImageCropper.setScale(true); -``` -裁切图片 -```java -mImageCropper.cropImage(file); -``` diff --git a/README.md b/README.md index f7cab71..4503b8e 100644 --- a/README.md +++ b/README.md @@ -1,155 +1,50 @@ -[中文说明](README-zh.md) - # ImageFileSelector -[![Build Status](https://travis-ci.org/sw926/ImageFileSelector.svg?branch=master)](https://travis-ci.org/sw926/ImageFileSelector) - -##### Use the system software to select, compress, crop images - -##### support Android Api Level >= 16 +轻量级的选取图片和裁切图片的库,使用系统自带的软件实现。 -Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap]. +支持Android版本 Api Level >= 16 -How to use ----------- -Maven - -```xml - - com.sw926.imagefileselector - library - 1.1.0-SNAPSHOT - -``` -Gradle +## 使用方法 ```gradle -compile 'com.sw926.imagefileselector:library:1.1.0-SNAPSHOT' +compile 'com.sw926.imagefileselector:library:2.0.0' ``` - -Select Image ----------- -Init +在 Activity 或者 Fragment Start 之前进行初始化: ``` java -ImageFileSelector mImageFileSelector = new ImageFileSelector(this); -mImageFileSelector.setCallback(new ImageFileSelector.Callback() { +mImageFileSelector = new ImageFileSelector(this); +mImageFileSelector.setOutPutImageSize(w, h); +mImageFileSelector.setQuality(80); +mImageFileSelector.setListener(new ImageFileResultListener() { @Override - public void onError(@NotNull ErrorResult errorResult) { - switch (errorResult) { - case permissionDenied: - break; - case canceled: - break; - case error: - break; - } + public void onSuccess(@NonNull String filePath) { + loadImage(filePath); + mCurrentSelectFile = new File(filePath); + mBtnCrop.setVisibility(View.VISIBLE); } @Override - public void onSuccess(@NotNull String file) { + public void onCancel() { + Toast.makeText(ExampleActivity.this, "Canceled", Toast.LENGTH_LONG).show(); } -}); -``` -Add code to you Activity or Fragment -```java -@Override -protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - mImageFileSelector.onActivityResult(this, requestCode, resultCode, data); -} - -@Override -protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - mImageFileSelector.onSaveInstanceState(outState); -} - -@Override -protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - mImageFileSelector.onRestoreInstanceState(savedInstanceState); -} -@Override -public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - mImageFileSelector.onRequestPermissionsResult(this, requestCode, permissions, grantResults); -} -``` -Setting parameters -```java -// Set the output file size -mImageFileSelector.setOutPutImageSize(800, 600); -Set the picture save quality, 0 to 100 -mImageFileSelector.setQuality(80); -``` -Start select image -```java -// take picture from camera -mImageFileSelector.takePhoto(this, requestCode); -// select image from sdcard -mImageFileSelector.selectImage(this, requestCode); -// Set the save path of the image,default: /sdcard/Android/data/{packagename}/cache/images/ -mImageFileSelector.setOutPutPath(); + @Override + public void onError() { + Toast.makeText(ExampleActivity.this, "Unknown Error", Toast.LENGTH_LONG).show(); + } +}); ``` +在选择图片的地方调用: -Crop Image ----------- -Init -```java -ImageCropper mImageCropper = new ImageCropper(); -mImageCropper.setCallback(new ImageCropper.ImageCropperCallback() { - @Override - public void onError(@NotNull ImageCropper.CropperErrorResult result) { - switch (result) { - case error: - break; - case canceled: - break; - case notSupport: - break; - } - } - - @Override - public void onSuccess(@NotNull String outputFile) { - } - }); +``` java +mImageFileSelector.takePhoto(); +// 或者 +mImageFileSelector.selectImage(); ``` -Add code to you Activity or Fragment -```java -@Override -protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - mImageCropper.onActivityResult(requestCode, resultCode, data); -} -@Override -protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - mImageCropper.onSaveInstanceState(outState); -} +## 说明 -@Override -protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - mImageCropper.onRestoreInstanceState(savedInstanceState); -} -``` -Setting parameters -```java -// Sets the picture aspect ratio -mImageCropper.setOutPutAspect(1, 1); -// Sets the image size -mImageCropper.setOutPut(800, 800); -// Sets whether to scale -mImageCropper.setScale(true); -``` -crop image -```java -mImageCropper.cropImage(file); -``` -[snap]: https://oss.sonatype.org/content/repositories/snapshots/com/sw926/imagefileselector/library/ \ No newline at end of file +在 appcompat 支持 ActivityResultLauncher 后,选择图片已经非常简单,这个项目只是做了简单的封装,添加了拍照和图片压缩。选择图片不需要任何权限,如果 App 的 `AndroidManifest.xml` 没有添加 `` +,拍照也不需要申请权限。 \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 94dab94..81aa708 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,13 +2,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { - compileSdkVersion 30 - buildToolsVersion '30.0.2' + compileSdkVersion 31 defaultConfig { applicationId "com.sw926.imagefileselector.example" minSdkVersion 16 - targetSdkVersion 30 + targetSdkVersion 31 versionCode 10 versionName "1.0.7" } @@ -26,9 +25,9 @@ android { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - testImplementation 'junit:junit:4.13.1' + testImplementation 'junit:junit:4.13.2' implementation project(':library') - implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.appcompat:appcompat:1.3.1' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } repositories { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index aba2d78..7dde882 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,7 +2,6 @@ - - + diff --git a/app/src/main/java/com/sw926/imagefileselector/example/ExampleActivity.java b/app/src/main/java/com/sw926/imagefileselector/example/ExampleActivity.java index a750c71..87f47a5 100644 --- a/app/src/main/java/com/sw926/imagefileselector/example/ExampleActivity.java +++ b/app/src/main/java/com/sw926/imagefileselector/example/ExampleActivity.java @@ -1,10 +1,9 @@ package com.sw926.imagefileselector.example; -import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.net.Uri; import android.os.Bundle; -import androidx.appcompat.app.AppCompatActivity; import android.text.TextUtils; import android.view.View; import android.widget.Button; @@ -12,12 +11,12 @@ import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; - -import com.sw926.imagefileselector.ErrorResult; -import com.sw926.imagefileselector.ImageCropper; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import com.sw926.imagefileselector.ImageCropHelper; +import com.sw926.imagefileselector.ImageFileResultListener; import com.sw926.imagefileselector.ImageFileSelector; - - +import com.sw926.imagefileselector.ImageUriResultListener; import java.io.File; public class ExampleActivity extends AppCompatActivity implements View.OnClickListener { @@ -29,7 +28,7 @@ public class ExampleActivity extends AppCompatActivity implements View.OnClickLi private EditText mEtWidth; private EditText mEtHeight; - private ImageCropper mImageCropper; + private ImageCropHelper mImageCropHelper; private Button mBtnCrop; private File mCurrentSelectFile; @@ -40,8 +39,6 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); - ImageFileSelector.Companion.setDebug(true); - findViewById(R.id.btn_from_sdcard).setOnClickListener(this); findViewById(R.id.btn_from_camera).setOnClickListener(this); findViewById(R.id.btn_crop).setOnClickListener(this); @@ -53,112 +50,74 @@ protected void onCreate(Bundle savedInstanceState) { mBtnCrop = findViewById(R.id.btn_crop); mImageFileSelector = new ImageFileSelector(this); - mImageFileSelector.setCallback(new ImageFileSelector.Callback() { + mImageFileSelector.setListener(new ImageFileResultListener() { @Override - public void onError(ErrorResult errorResult) { - switch (errorResult) { - case permissionDenied: - Toast.makeText(ExampleActivity.this, "Permission Denied", Toast.LENGTH_LONG).show(); - break; - case canceled: - Toast.makeText(ExampleActivity.this, "Canceled", Toast.LENGTH_LONG).show(); - break; - case error: - Toast.makeText(ExampleActivity.this, "Unknown Error", Toast.LENGTH_LONG).show(); - break; - } + public void onSuccess(@NonNull String filePath) { + loadImage(filePath); + mCurrentSelectFile = new File(filePath); + mBtnCrop.setVisibility(View.VISIBLE); } @Override - public void onSuccess(String file) { - loadImage(file); - mCurrentSelectFile = new File(file); - mBtnCrop.setVisibility(View.VISIBLE); + public void onCancel() { + Toast.makeText(ExampleActivity.this, "Canceled", Toast.LENGTH_LONG).show(); + } + + @Override + public void onError() { + Toast.makeText(ExampleActivity.this, "Unknown Error", Toast.LENGTH_LONG).show(); } }); - mImageCropper = new ImageCropper(); + mImageCropHelper = new ImageCropHelper(this); - mImageCropper.setCallback(new ImageCropper.ImageCropperCallback() { + mImageCropHelper.setListener(new ImageFileResultListener() { @Override - public void onError(ImageCropper.CropperErrorResult result) { - switch (result) { - case error: - Toast.makeText(ExampleActivity.this, "crop image error", Toast.LENGTH_LONG).show(); - break; - case canceled: - Toast.makeText(ExampleActivity.this, "crop image canceled", Toast.LENGTH_LONG).show(); - break; - case notSupport: - Toast.makeText(ExampleActivity.this, "crop image not support", Toast.LENGTH_LONG).show(); - break; - } + public void onSuccess(@NonNull String filePath) { + loadImage(filePath); } @Override - public void onSuccess(String outputFile) { - loadImage(outputFile); + public void onError() { + Toast.makeText(ExampleActivity.this, "crop image error", Toast.LENGTH_LONG).show(); } - }); - } - - private void loadImage(final String file) { - new Thread(new Runnable() { @Override - public void run() { - final Bitmap bitmap = BitmapFactory.decodeFile(file); - File imageFile = new File(file); - final StringBuilder builder = new StringBuilder(); - builder.append("path: "); - builder.append(file); - builder.append("\n\n"); - builder.append("size: "); - builder.append((int) (imageFile.length() / 1024d)); - builder.append("KB"); - builder.append("\n\n"); - builder.append("image size: ("); - builder.append(bitmap.getWidth()); - builder.append(", "); - builder.append(bitmap.getHeight()); - builder.append(")"); - runOnUiThread(new Runnable() { - @Override - public void run() { - mImageView.setImageBitmap(bitmap); - mTvPath.setText(builder.toString()); - } - }); + public void onCancel() { + Toast.makeText(ExampleActivity.this, "crop image canceled", Toast.LENGTH_LONG).show(); } - }).start(); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - mImageFileSelector.onActivityResult(this, requestCode, resultCode, data); - mImageCropper.onActivityResult(requestCode, resultCode, data); + }); } - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - mImageFileSelector.onSaveInstanceState(outState); - mImageCropper.onSaveInstanceState(outState); + private void loadImage(final String file) { + new Thread(() -> { + final Bitmap bitmap = BitmapFactory.decodeFile(file); + File imageFile = new File(file); + final StringBuilder builder = new StringBuilder(); + builder.append("path: "); + builder.append(file); + builder.append("\n\n"); + builder.append("size: "); + builder.append((int) (imageFile.length() / 1024d)); + builder.append("KB"); + builder.append("\n\n"); + builder.append("image size: ("); + builder.append(bitmap.getWidth()); + builder.append(", "); + builder.append(bitmap.getHeight()); + builder.append(")"); + runOnUiThread(() -> { + mImageView.setImageBitmap(bitmap); + mTvPath.setText(builder.toString()); + }); + }).start(); } - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - mImageFileSelector.onRestoreInstanceState(savedInstanceState); - mImageCropper.onRestoreInstanceState(savedInstanceState); - } @SuppressWarnings("NullableProblems") @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); - mImageFileSelector.onRequestPermissionsResult(requestCode, permissions, grantResults); } private void initImageFileSelector() { @@ -178,19 +137,19 @@ public void onClick(View v) { switch (v.getId()) { case R.id.btn_from_camera: { initImageFileSelector(); - mImageFileSelector.takePhoto(this, 1); + mImageFileSelector.takePhoto(); break; } case R.id.btn_from_sdcard: { initImageFileSelector(); - mImageFileSelector.selectImage(this, 2); + mImageFileSelector.selectImage(); break; } case R.id.btn_crop: { if (mCurrentSelectFile != null) { - mImageCropper.setOutPut(800, 800); - mImageCropper.setOutPutAspect(1, 1); - mImageCropper.cropImage(this, mCurrentSelectFile.getPath(), 3); + mImageCropHelper.setOutPut(800, 800); + mImageCropHelper.setOutPutAspect(1, 1); + mImageCropHelper.cropImage(mCurrentSelectFile.getPath()); } break; } diff --git a/app/src/main/java/com/sw926/imagefileselector/example/ExampleFragment.java b/app/src/main/java/com/sw926/imagefileselector/example/ExampleFragment.java index e6d97f2..616bec5 100644 --- a/app/src/main/java/com/sw926/imagefileselector/example/ExampleFragment.java +++ b/app/src/main/java/com/sw926/imagefileselector/example/ExampleFragment.java @@ -1,10 +1,12 @@ package com.sw926.imagefileselector.example; -import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.net.Uri; import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import android.text.TextUtils; import android.view.LayoutInflater; @@ -16,10 +18,11 @@ import android.widget.TextView; import android.widget.Toast; -import com.sw926.imagefileselector.ErrorResult; -import com.sw926.imagefileselector.ImageCropper; +import com.sw926.imagefileselector.ImageCropHelper; +import com.sw926.imagefileselector.ImageFileResultListener; import com.sw926.imagefileselector.ImageFileSelector; +import com.sw926.imagefileselector.ImageUriResultListener; import java.io.File; @@ -35,7 +38,7 @@ public class ExampleFragment extends Fragment implements View.OnClickListener { private EditText mEtWidth; private EditText mEtHeight; - private ImageCropper mImageCropper; + private ImageCropHelper mImageCropHelper; private Button mBtnCrop; private File mCurrentSelectFile; @@ -44,6 +47,12 @@ public ExampleFragment() { // Required empty public constructor } + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mImageFileSelector = new ImageFileSelector(this); + mImageCropHelper = new ImageCropHelper(this); + } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -66,52 +75,39 @@ public void onViewCreated(View view, Bundle savedInstanceState) { mEtHeight = (EditText) view.findViewById(R.id.et_height); mBtnCrop = (Button) view.findViewById(R.id.btn_crop); - mImageFileSelector = new ImageFileSelector(getContext()); + mImageFileSelector.setListener(new ImageFileResultListener() { + @Override + public void onSuccess(@NonNull String filePath) { + loadImage(filePath); + mCurrentSelectFile = new File(filePath); + mBtnCrop.setVisibility(View.VISIBLE); + } - mImageFileSelector.setCallback(new ImageFileSelector.Callback() { @Override - public void onError(ErrorResult errorResult) { - switch (errorResult) { - case permissionDenied: - Toast.makeText(getContext(), "Permission Denied", Toast.LENGTH_LONG).show(); - break; - case canceled: - Toast.makeText(getContext(), "Canceled", Toast.LENGTH_LONG).show(); - break; - case error: - Toast.makeText(getContext(), "Unknown Error", Toast.LENGTH_LONG).show(); - break; - } + public void onCancel() { + Toast.makeText(getContext(), "Canceled", Toast.LENGTH_LONG).show(); } @Override - public void onSuccess(String file) { - loadImage(file); - mCurrentSelectFile = new File(file); - mBtnCrop.setVisibility(View.VISIBLE); + public void onError() { + Toast.makeText(getContext(), "Unknown Error", Toast.LENGTH_LONG).show(); } }); - mImageCropper = new ImageCropper(); - mImageCropper.setCallback(new ImageCropper.ImageCropperCallback() { + mImageCropHelper.setListener(new ImageFileResultListener() { @Override - public void onError(ImageCropper.CropperErrorResult result) { - switch (result) { - case error: - Toast.makeText(getContext(), "crop image error", Toast.LENGTH_LONG).show(); - break; - case canceled: - Toast.makeText(getContext(), "crop image canceled", Toast.LENGTH_LONG).show(); - break; - case notSupport: - Toast.makeText(getContext(), "crop image not support", Toast.LENGTH_LONG).show(); - break; - } + public void onSuccess(@NonNull String filePath) { + loadImage(filePath); + } + + @Override + public void onCancel() { + Toast.makeText(getContext(), "crop image canceled", Toast.LENGTH_LONG).show(); } @Override - public void onSuccess(String outputFile) { - loadImage(outputFile); + public void onError() { + Toast.makeText(getContext(), "crop image error", Toast.LENGTH_LONG).show(); } }); } @@ -121,19 +117,19 @@ public void onClick(View v) { switch (v.getId()) { case R.id.btn_from_camera: { initImageFileSelector(); - mImageFileSelector.takePhoto(this, 1); + mImageFileSelector.takePhoto(); break; } case R.id.btn_from_sdcard: { initImageFileSelector(); - mImageFileSelector.selectImage(this, 2); + mImageFileSelector.selectImage(); break; } case R.id.btn_crop: { if (mCurrentSelectFile != null) { - mImageCropper.setOutPut(800, 800); - mImageCropper.setOutPutAspect(1, 1); - mImageCropper.cropImage(this, mCurrentSelectFile.getPath(), 3); + mImageCropHelper.setOutPut(800, 800); + mImageCropHelper.setOutPutAspect(1, 1); + mImageCropHelper.cropImage(mCurrentSelectFile.getPath()); } break; } @@ -153,60 +149,28 @@ private void initImageFileSelector() { } private void loadImage(final String file) { - new Thread(new Runnable() { - @Override - public void run() { - final Bitmap bitmap = BitmapFactory.decodeFile(file); - File imageFile = new File(file); - final StringBuilder builder = new StringBuilder(); - builder.append("path: "); - builder.append(file); - builder.append("\n\n"); - builder.append("size: "); - builder.append((int) (imageFile.length() / 1024d)); - builder.append("KB"); - builder.append("\n\n"); - builder.append("image size: ("); - builder.append(bitmap.getWidth()); - builder.append(", "); - builder.append(bitmap.getHeight()); - builder.append(")"); - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - mImageView.setImageBitmap(bitmap); - mTvPath.setText(builder.toString()); - } + new Thread(() -> { + final Bitmap bitmap = BitmapFactory.decodeFile(file); + File imageFile = new File(file); + final StringBuilder builder = new StringBuilder(); + builder.append("path: "); + builder.append(file); + builder.append("\n\n"); + builder.append("size: "); + builder.append((int) (imageFile.length() / 1024d)); + builder.append("KB"); + builder.append("\n\n"); + builder.append("image size: ("); + builder.append(bitmap.getWidth()); + builder.append(", "); + builder.append(bitmap.getHeight()); + builder.append(")"); + if (getActivity() != null) { + getActivity().runOnUiThread(() -> { + mImageView.setImageBitmap(bitmap); + mTvPath.setText(builder.toString()); }); } }).start(); } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - mImageFileSelector.onActivityResult(getContext(), requestCode, resultCode, data); - mImageCropper.onActivityResult(requestCode, resultCode, data); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - mImageFileSelector.onSaveInstanceState(outState); - mImageCropper.onSaveInstanceState(outState); - } - - @Override - public void onViewStateRestored(Bundle savedInstanceState) { - super.onViewStateRestored(savedInstanceState); - mImageFileSelector.onRestoreInstanceState(savedInstanceState); - mImageCropper.onRestoreInstanceState(savedInstanceState); - } - - @SuppressWarnings("NullableProblems") - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - mImageFileSelector.onRequestPermissionsResult(requestCode, permissions, grantResults); - } } diff --git a/build.gradle b/build.gradle index 20df1ba..0dd96a8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,13 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.4.20' + ext.kotlin_version = '1.5.31' repositories { jcenter() google() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.1' + classpath 'com.android.tools.build:gradle:7.0.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle-mvn-push.gradle b/gradle-mvn-push.gradle deleted file mode 100644 index 81d6aa5..0000000 --- a/gradle-mvn-push.gradle +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2013 Chris Banes - * - * 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. - */ - -apply plugin: 'maven' -apply plugin: 'signing' - -def isReleaseBuild() { - return VERSION_NAME.contains("SNAPSHOT") == false -} - -def getReleaseRepositoryUrl() { - return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL - : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" -} - -def getSnapshotRepositoryUrl() { - return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL - : "https://oss.sonatype.org/content/repositories/snapshots/" -} - -def getRepositoryUsername() { - return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" -} - -def getRepositoryPassword() { - return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : "" -} - -afterEvaluate { project -> - uploadArchives { - repositories { - mavenDeployer { - beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } - - pom.groupId = GROUP - pom.artifactId = POM_ARTIFACT_ID - pom.version = VERSION_NAME - - repository(url: getReleaseRepositoryUrl()) { - authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) - } - snapshotRepository(url: getSnapshotRepositoryUrl()) { - authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) - } - - pom.project { - name POM_NAME - packaging POM_PACKAGING - description POM_DESCRIPTION - url POM_URL - - scm { - url POM_SCM_URL - connection POM_SCM_CONNECTION - developerConnection POM_SCM_DEV_CONNECTION - } - - licenses { - license { - name POM_LICENCE_NAME - url POM_LICENCE_URL - distribution POM_LICENCE_DIST - } - } - - developers { - developer { - id POM_DEVELOPER_ID - name POM_DEVELOPER_NAME - } - } - } - } - } - } - - signing { - required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } - sign configurations.archives - } - -// task androidJavadocs(type: Javadoc) { -// source = android.sourceSets.main.java.srcDirs -// classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) -// } -// -// task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { -// classifier = 'javadoc' -// from androidJavadocs.destinationDir -// } - - task androidSourcesJar(type: Jar) { - classifier = 'sources' - from android.sourceSets.main.java.sourceFiles - } - - artifacts { - archives androidSourcesJar -// archives androidJavadocsJar - } -} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d5cd684..2cb1021 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip diff --git a/library/build.gradle b/library/build.gradle index fa8d607..77a8735 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -2,14 +2,11 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion 30 - buildToolsVersion '30.0.0' + compileSdkVersion 31 defaultConfig { minSdkVersion 16 - targetSdkVersion 30 - versionCode 13 - versionName "1.0.10" + targetSdkVersion 31 } buildTypes { release { @@ -28,14 +25,12 @@ android { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - testImplementation 'junit:junit:4.13' - compileOnly 'androidx.fragment:fragment:1.2.5' + testImplementation 'junit:junit:4.13.2' + compileOnly 'androidx.fragment:fragment:1.3.6' api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "androidx.exifinterface:exifinterface:1.2.0" - implementation 'org.jetbrains.anko:anko-commons:0.10.8' + implementation "androidx.exifinterface:exifinterface:1.3.2" + implementation 'androidx.core:core-ktx:1.3.2' + implementation 'androidx.appcompat:appcompat:1.3.1' + + } -//https://raw.githubusercontent.com/chrisbanes/gradle-mvn-push/master/gradle-mvn-push.gradle -apply from: '../gradle-mvn-push.gradle' -repositories { - mavenCentral() -} \ No newline at end of file diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml index 043a5e7..847ee3e 100644 --- a/library/src/main/AndroidManifest.xml +++ b/library/src/main/AndroidManifest.xml @@ -1,8 +1,6 @@ - - diff --git a/library/src/main/java/com/sw926/imagefileselector/AppLogger.kt b/library/src/main/java/com/sw926/imagefileselector/AppLogger.kt deleted file mode 100644 index ecd69ec..0000000 --- a/library/src/main/java/com/sw926/imagefileselector/AppLogger.kt +++ /dev/null @@ -1,186 +0,0 @@ -@file:Suppress("unused") - -package com.sw926.imagefileselector - -import android.util.Log -import java.util.* - -/** - * User: Jiang Qi - * Date: 12-7-31 - */ -/** - * Wrapper API for sending log output. - */ -internal object AppLogger { - var DEBUG = true - private const val TAG = "ImageFileSelector" - private const val TIMER_TAG = "TraceTime" - - /** - * Send a VERBOSE log message. - * - * @param msg The message you would like logged. - */ - fun v(msg: String) { - if (DEBUG) Log.v(TAG, buildMessage(msg)) - } - - /** - * Send a VERBOSE log message and log the exception. - * - * @param msg The message you would like logged. - * @param thr An exception to log - */ - fun v(msg: String, thr: Throwable?) { - if (DEBUG) Log.v(TAG, buildMessage(msg), thr) - } - - /** - * Send a DEBUG log message. - * - * @param msg - */ - fun d(msg: String) { - if (DEBUG) Log.d(TAG, buildMessage(msg)) - } - - fun d(tag: String?, msg: String) { - if (DEBUG) Log.d(tag, msg) - // android.util.Log.d(tag, buildMessage(msg)); - } - - /** - * Send a DEBUG log message and log the exception. - * - * @param msg The message you would like logged. - * @param thr An exception to log - */ - fun d(msg: String, thr: Throwable?) { - if (DEBUG) Log.d(TAG, buildMessage(msg), thr) - } - - /** - * Send an INFO log message. - * - * @param msg The message you would like logged. - */ - fun i(msg: String) { - if (DEBUG) Log.i(TAG, buildMessage(msg)) - } - - fun i(tag: String?, msg: String) { - if (DEBUG) Log.i(tag, msg) - // Log.i(tag, buildMessage(msg)); - } - - /** - * Send a INFO log message and log the exception. - * - * @param msg The message you would like logged. - * @param thr An exception to log - */ - fun i(msg: String, thr: Throwable?) { - if (DEBUG) Log.i(TAG, buildMessage(msg), thr) - } - - /** - * Send an ERROR log message. - * - * @param msg The message you would like logged. - */ - fun e(msg: String) { - if (DEBUG) Log.e(TAG, buildMessage(msg)) - } - - fun e(tag: String?, msg: String) { - if (DEBUG) Log.e(tag, msg) - // android.util.Log.e(tag, buildMessage(msg)); - } - - /** - * Send a WARN log message - * - * @param msg The message you would like logged. - */ - fun w(msg: String) { - if (DEBUG) Log.w(TAG, buildMessage(msg)) - } - - fun w(tag: String?, msg: String) { - if (DEBUG) Log.w(tag, buildMessage(msg)) - } - - /** - * Send a WARN log message and log the exception. - * - * @param msg The message you would like logged. - * @param thr An exception to log - */ - fun w(msg: String, thr: Throwable?) { - if (DEBUG) Log.w(TAG, buildMessage(msg), thr) - } - - /** - * Send an empty WARN log message and log the exception. - * - * @param thr An exception to log - */ - fun w(thr: Throwable?) { - if (DEBUG) Log.w(TAG, buildMessage(""), thr) - } - - /** - * Send an ERROR log message and log the exception. - * - * @param msg The message you would like logged. - * @param thr An exception to log - */ - fun e(msg: String, thr: Throwable?) { - if (DEBUG) Log.e(TAG, buildMessage(msg), thr) - } - - fun e(tag: String?, msg: String, thr: Throwable?) { - if (DEBUG) Log.e(tag, buildMessage(msg), thr) - } - - fun printStackTrace(e: Throwable) { - if (DEBUG) e.printStackTrace() - } - - private val traceTimeStack = Stack() - fun resetTraceTime() { - traceTimeStack.clear() - } - - fun startTraceTime(msg: String) { - traceTimeStack.push(System.currentTimeMillis()) - if (DEBUG) { - Log.d(TIMER_TAG, msg + " time = " + System.currentTimeMillis()) - } - } - - fun stopTraceTime(msg: String) { - if (!traceTimeStack.isEmpty()) { - val time = traceTimeStack.pop() - val diff = System.currentTimeMillis() - time - if (DEBUG) { - Log.d(TIMER_TAG, "[" + diff + "]" + msg + " time = " + System.currentTimeMillis()) - } - } - } - - /** - * Building Message - * - * @param msg The message you would like logged. - * @return Message String - */ - private fun buildMessage(msg: String): String { - val caller = Throwable().fillInStackTrace().stackTrace[2] - return """ - ${caller.className}.${caller.methodName}(): - $msg - """.trimIndent() - } -} \ No newline at end of file diff --git a/library/src/main/java/com/sw926/imagefileselector/CommonUtils.kt b/library/src/main/java/com/sw926/imagefileselector/CommonUtils.kt index 717f37b..f4a8a85 100644 --- a/library/src/main/java/com/sw926/imagefileselector/CommonUtils.kt +++ b/library/src/main/java/com/sw926/imagefileselector/CommonUtils.kt @@ -2,14 +2,23 @@ package com.sw926.imagefileselector import android.content.Context import android.net.Uri -import android.os.Environment import androidx.core.content.FileProvider -import java.io.* +import java.io.File object CommonUtils { fun getOutPutPath(context: Context): File { - return File(context.externalCacheDir, "/imagefileselector/") + val file = File(context.cacheDir, "/imagefileselector/") + if (!file.exists()) { + file.mkdirs() + } + return file + } + + fun getPathFromFileProviderUri(context: Context, uri: Uri): String? { + return uri.lastPathSegment?.let { + File(getOutPutPath(context), it).path + } } fun getFileUri(context: Context, file: File): Uri { @@ -25,8 +34,4 @@ object CommonUtils { return File(getOutPutPath(context), fileName) } - fun hasSDCardMounted(): Boolean { - val state = Environment.getExternalStorageState() - return state != null && state == Environment.MEDIA_MOUNTED - } } \ No newline at end of file diff --git a/library/src/main/java/com/sw926/imagefileselector/Compatibility.kt b/library/src/main/java/com/sw926/imagefileselector/Compatibility.kt index b02f356..e72797a 100644 --- a/library/src/main/java/com/sw926/imagefileselector/Compatibility.kt +++ b/library/src/main/java/com/sw926/imagefileselector/Compatibility.kt @@ -1,138 +1,19 @@ package com.sw926.imagefileselector -import android.annotation.TargetApi -import android.content.ContentUris -import android.content.Context -import android.database.Cursor -import android.net.Uri import android.os.Build -import android.os.Environment -import android.provider.DocumentsContract -import android.provider.MediaStore -object Compatibility { +internal object Compatibility { fun shouldReturnCropData(): Boolean { return (Build.DEVICE.contains("milestone2", true) - || Build.DEVICE.contains("milestone3", true) - || Build.BOARD.contains("sholes", true) - || Build.PRODUCT.contains("sholes", true) - || Build.DEVICE.equals("olympus", true) - || Build.DEVICE.contains("umts_jordan", true)) + || Build.DEVICE.contains("milestone3", true) + || Build.BOARD.contains("sholes", true) + || Build.PRODUCT.contains("sholes", true) + || Build.DEVICE.equals("olympus", true) + || Build.DEVICE.contains("umts_jordan", true)) } fun scaleUpIfNeeded4Black(): Boolean { return Build.DEVICE.contains("mx2", true) } - @TargetApi(Build.VERSION_CODES.KITKAT) - fun getPath(context: Context, uri: Uri): String? { - val isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT - - // DocumentProvider - if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { - // ExternalStorageProvider - if (isExternalStorageDocument(uri)) { - val docId = DocumentsContract.getDocumentId(uri) - val split = docId.split(":").toTypedArray() - val type = split[0] - if ("primary".equals(type, ignoreCase = true)) { - return Environment.getExternalStorageDirectory().toString() + "/" + split[1] - } - - // TODO handle non-primary volumes - } else if (isDownloadsDocument(uri)) { - val id = DocumentsContract.getDocumentId(uri) - val contentUri = ContentUris.withAppendedId( - Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id)) - return getDataColumn(context, contentUri, null, null) - } else if (isMediaDocument(uri)) { - val docId = DocumentsContract.getDocumentId(uri) - val split = docId.split(":").toTypedArray() - val type = split[0] - var contentUri: Uri? = null - when (type) { - "image" -> contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI - "video" -> contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI - "audio" -> contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI - } - val selection = "_id=?" - val selectionArgs = arrayOf( - split[1] - ) - return contentUri?.let { getDataColumn(context, it, selection, selectionArgs) } - } - } else if ("content".equals(uri.scheme, ignoreCase = true)) { - // Return the remote address - return if (isGooglePhotosUri(uri)) { - uri.lastPathSegment - } else getDataColumn(context, uri, null, null) - } else if ("file".equals(uri.scheme, ignoreCase = true)) { - return uri.path - } - return null - } - - /** - * Get the value of the data column for this Uri. This is useful for - * MediaStore Uris, and other file-based ContentProviders. - * - * @param context The context. - * @param uri The Uri to query. - * @param selection (Optional) Filter used in the query. - * @param selectionArgs (Optional) Selection arguments used in the query. - * @return The value of the _data column, which is typically a file path. - */ - fun getDataColumn(context: Context, uri: Uri, selection: String?, - selectionArgs: Array?): String? { - var cursor: Cursor? = null - val column = "_data" - val projection = arrayOf( - column - ) - try { - cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, - null) - if (cursor != null && cursor.moveToFirst()) { - val index = cursor.getColumnIndexOrThrow(column) - return cursor.getString(index) - } - } catch (e: Throwable) { - e.printStackTrace() - } finally { - cursor?.close() - } - return null - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is ExternalStorageProvider. - */ - fun isExternalStorageDocument(uri: Uri): Boolean { - return "com.android.externalstorage.documents" == uri.authority - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is DownloadsProvider. - */ - fun isDownloadsDocument(uri: Uri): Boolean { - return "com.android.providers.downloads.documents" == uri.authority - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is MediaProvider. - */ - fun isMediaDocument(uri: Uri): Boolean { - return "com.android.providers.media.documents" == uri.authority - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is Google Photos. - */ - fun isGooglePhotosUri(uri: Uri): Boolean { - return "com.google.android.apps.photos.content" == uri.authority - } } \ No newline at end of file diff --git a/library/src/main/java/com/sw926/imagefileselector/CompressFormatUtils.kt b/library/src/main/java/com/sw926/imagefileselector/CompressFormatUtils.kt index 0e8273b..1e52aa9 100644 --- a/library/src/main/java/com/sw926/imagefileselector/CompressFormatUtils.kt +++ b/library/src/main/java/com/sw926/imagefileselector/CompressFormatUtils.kt @@ -1,7 +1,6 @@ package com.sw926.imagefileselector import android.graphics.Bitmap.CompressFormat -import android.os.Build /** * Created by sunwei on 15/11/16. @@ -19,19 +18,11 @@ object CompressFormatUtils { return if (ext.equals("png", ignoreCase = true)) { CompressFormat.PNG } else CompressFormat.JPEG - // TODO ignore webp? -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { -// if (ext.equals("webp", ignoreCase = true)) { -// return Bitmap.CompressFormat.WEBP -// } -// } } fun getExt(format: CompressFormat): String { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - if (format == CompressFormat.WEBP) { - return ".webp" - } + if (format == CompressFormat.WEBP) { + return ".webp" } return if (format == CompressFormat.PNG) { ".png" diff --git a/library/src/main/java/com/sw926/imagefileselector/CompressParams.kt b/library/src/main/java/com/sw926/imagefileselector/CompressParams.kt new file mode 100644 index 0000000..9a7de58 --- /dev/null +++ b/library/src/main/java/com/sw926/imagefileselector/CompressParams.kt @@ -0,0 +1,11 @@ +package com.sw926.imagefileselector + +import android.graphics.Bitmap +import android.graphics.Bitmap.CompressFormat.JPEG + +data class CompressParams( + val maxWidth: Int = 1000, + val maxHeight: Int = 1000, + val saveQuality: Int = 80, + val compressFormat: Bitmap.CompressFormat = JPEG, +) \ No newline at end of file diff --git a/library/src/main/java/com/sw926/imagefileselector/ErrorResult.kt b/library/src/main/java/com/sw926/imagefileselector/ErrorResult.kt deleted file mode 100644 index 7694638..0000000 --- a/library/src/main/java/com/sw926/imagefileselector/ErrorResult.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.sw926.imagefileselector - -/** - * Created by sunwei on 2016/11/3 下午10:31. - */ -enum class ErrorResult { - permissionDenied, - canceled, - error -} \ No newline at end of file diff --git a/library/src/main/java/com/sw926/imagefileselector/ImageCaptureHelper.kt b/library/src/main/java/com/sw926/imagefileselector/ImageCaptureHelper.kt index 668c621..140cdb3 100644 --- a/library/src/main/java/com/sw926/imagefileselector/ImageCaptureHelper.kt +++ b/library/src/main/java/com/sw926/imagefileselector/ImageCaptureHelper.kt @@ -1,231 +1,109 @@ package com.sw926.imagefileselector - import android.Manifest -import android.app.Activity -import android.content.ContentValues import android.content.Context -import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Build -import android.os.Bundle -import android.provider.MediaStore -import androidx.core.content.FileProvider +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts.RequestPermission +import androidx.activity.result.contract.ActivityResultContracts.TakePicture +import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import com.sw926.imagefileselector.ErrorResult.* -import java.io.File import java.lang.ref.WeakReference - class ImageCaptureHelper { - companion object { + private val requestPermission: ActivityResultLauncher + private val requestTakePicture: ActivityResultLauncher + private val context: WeakReference - private const val TAG = "ImageCaptureHelper" - - private fun getRequestPermissions(context: Context): Array { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (hasPermissionInManifest(context, Manifest.permission.CAMERA)) { - arrayOf(Manifest.permission.CAMERA) - } else { - arrayOf() - } + constructor(activity: AppCompatActivity) { + requestPermission = activity.registerForActivityResult(RequestPermission()) { + if (it) { + requestTakePicture() } else { - arrayOf() + listenerUri?.onCancel() } } + requestTakePicture = activity.registerForActivityResult(TakePicture()) { onTakePictureResult(it) } + context = WeakReference(activity) + } - @Suppress("SameParameterValue") - private fun hasPermissionInManifest(context: Context, permissionName: String): Boolean { - val packageName = context.packageName - try { - val packageInfo = context.packageManager - .getPackageInfo(packageName, PackageManager.GET_PERMISSIONS) - val declaredPermission = packageInfo.requestedPermissions - if (declaredPermission != null && declaredPermission.isNotEmpty()) { - for (p in declaredPermission) { - if (p == permissionName) { - return true - } - } - } - } catch (e: PackageManager.NameNotFoundException) { - AppLogger.printStackTrace(e) + constructor(fragment: Fragment) { + requestPermission = fragment.registerForActivityResult(RequestPermission()) { + if (it) { + requestTakePicture() + } else { + listenerUri?.onCancel() } - - return false } + requestTakePicture = fragment.registerForActivityResult(TakePicture()) { + onTakePictureResult(it) + } + context = WeakReference(fragment.context) } - private var mCallback: ImageFileSelector.Callback? = null - - private var mOutputFile: File? = null - private var callerWeakReference: WeakReference? = null + private var listenerUri: ImageUriResultListener? = null + private var cameraUri: Uri? = null - var requestCode = -1 - private set - - fun setCallback(callback: ImageFileSelector.Callback?) { - mCallback = callback + fun setListener(listenerUri: ImageUriResultListener) { + this.listenerUri = listenerUri } - fun onSaveInstanceState(outState: Bundle?) { - if (outState != null) { - if (requestCode > 0) { - outState.putInt("image_capture_request_code", requestCode) - } - mOutputFile?.path.let { outState.putString("output_file", it) } - } + private fun requestPermission() { + requestPermission.launch(Manifest.permission.CAMERA) } - fun onRestoreInstanceState(outState: Bundle?) { - if (outState != null) { - if (outState.containsKey("image_capture_request_code")) { - requestCode = outState.getInt("image_capture_request_code") - } - if (outState.containsKey("output_file")) { - val outputFilePath: String? = outState.getString("output_file") - if (outputFilePath?.isNotBlank() == true) { - mOutputFile = File(outputFilePath) - } - } - } - } - - @Suppress("UNUSED_PARAMETER") - fun onActivityResult(context: Context?, requestCode: Int, resultCode: Int, intent: Intent?) { - if (context != null) - if (requestCode == this.requestCode) { - if (resultCode == Activity.RESULT_CANCELED) { - AppLogger.i(TAG, "canceled capture image") - mCallback?.onError(canceled) - } else if (resultCode == Activity.RESULT_OK) { + private fun requestTakePicture() { - mOutputFile?.let { - if (it.exists()) { - AppLogger.i(TAG, "capture image success: " + it.path) - mCallback?.onSuccess(it.path) - } else { - AppLogger.i(TAG, "capture image error " + it.path) - mCallback?.onError(error) - } - } - } - } - } - - @Suppress("UNUSED_PARAMETER") - fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - if (PermissionsHelper.isAllGrant(grantResults)) { - capture() - } else { - mCallback?.onError(permissionDenied) - } - } + val context = this.context.get() ?: return - private fun createIntent(context: Context): Intent { - val file = File(context.getExternalFilesDir("app_share"), "capture.jpg") - val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + val file = CommonUtils.generateImageCacheFile(context, ".jpg") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val cameraTempUri = FileProvider.getUriForFile(context, "com.example.myapplication", file) - intent.putExtra(MediaStore.EXTRA_OUTPUT, cameraTempUri) - intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1) + cameraUri = CommonUtils.getFileUri(context, file) + requestTakePicture.launch(cameraUri) } else { - intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(mOutputFile)) + cameraUri = Uri.fromFile(file) + requestTakePicture.launch(cameraUri) } - return intent } - fun captureImage(activity: Activity, requestCode: Int) { - this.requestCode = requestCode - callerWeakReference = WeakReference(activity) - if (PermissionsHelper.isGrant(activity, *getRequestPermissions(activity))) { - capture() - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - activity.requestPermissions(getRequestPermissions(activity), requestCode) - } - } - } - - fun captureImage(fragment: Fragment, requestCode: Int) { - this.requestCode = requestCode - callerWeakReference = WeakReference(fragment) - val context = fragment.context - if (context == null) { - mCallback?.onError(error) + private fun onTakePictureResult(result: Boolean) { + val resultUri = cameraUri + if (!result || resultUri == null) { + listenerUri?.onCancel() return } - - if (PermissionsHelper.isGrant(context, *getRequestPermissions(context))) { - capture() - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - fragment.requestPermissions(getRequestPermissions(context), requestCode) - } - } + listenerUri?.onSuccess(resultUri) } - private fun getContext(): Context? { - val caller = callerWeakReference?.get() - if (caller is Fragment) { - return caller.context - } else if (caller is Activity) { - return caller + fun takePicture() { + val context = this.context.get() ?: return + if (needRequestCameraPermission(context)) { + requestPermission() + } else { + requestTakePicture() } - return null } - private fun capture() { - if (!CommonUtils.hasSDCardMounted()) { - mCallback?.onError(error) - return - } + private fun needRequestCameraPermission(context: Context): Boolean { + val packageName = context.packageName try { - AppLogger.i(TAG, "start capture image") - - val context = getContext() - if (context == null) { - mCallback?.onError(error) - return - } - - val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) - if (cameraIntent.resolveActivity(context.packageManager) == null) { - mCallback?.onError(error) - return - } - - val values = ContentValues(1) - values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpg") - - val fileName = "img_" + System.currentTimeMillis() + ".jpg" - mOutputFile = File(CommonUtils.getOutPutPath(context), fileName) - - - if (mOutputFile?.parentFile?.exists() != true) { - mOutputFile?.absoluteFile?.mkdirs() - } - AppLogger.d(TAG, "capture ouput file: $mOutputFile") - when (val caller = callerWeakReference?.get()) { - is Activity -> { - caller.startActivityForResult(createIntent(context), requestCode) - } - is Fragment -> { - caller.startActivityForResult(createIntent(context), requestCode) - } - else -> { - mCallback?.onError(error) + val packageInfo = context.packageManager + .getPackageInfo(packageName, PackageManager.GET_PERMISSIONS) + val declaredPermission = packageInfo.requestedPermissions + if (declaredPermission != null && declaredPermission.isNotEmpty()) { + for (p in declaredPermission) { + if (p == Manifest.permission.CAMERA) { + return true + } } } - - } catch (e: Throwable) { - mCallback?.onError(error) - AppLogger.printStackTrace(e) + } catch (e: PackageManager.NameNotFoundException) { + e.printStackTrace() } + return false } - } diff --git a/library/src/main/java/com/sw926/imagefileselector/ImageCompressHelper.kt b/library/src/main/java/com/sw926/imagefileselector/ImageCompressHelper.kt deleted file mode 100644 index b85e91e..0000000 --- a/library/src/main/java/com/sw926/imagefileselector/ImageCompressHelper.kt +++ /dev/null @@ -1,77 +0,0 @@ -package com.sw926.imagefileselector - -import android.annotation.SuppressLint -import android.graphics.Bitmap -import org.jetbrains.anko.doAsync -import org.jetbrains.anko.uiThread -import java.io.File - -object ImageCompressHelper { - const val TAG = "ImageCompressHelper" - - @SuppressLint("ParcelCreator") - data class CompressParams( - val outputPath: String? = null, - val maxWidth: Int = 1000, - val maxHeight: Int = 1000, - val saveQuality: Int = 80, - val compressFormat: Bitmap.CompressFormat? = null - ) - - @SuppressLint("ParcelCreator") - class CompressJop( - val inputFile: String, - val params: CompressParams = CompressParams(), - val deleteInputFile: Boolean = false - ) - - - fun compress(jop: CompressJop, callback: (String?) -> Unit) { - val param = jop.params - val outputPath = param.outputPath - if (outputPath == null) { - clearJop(jop) - callback(null) - return - } - - val format = param.compressFormat ?: CompressFormatUtils.parseFormat(jop.inputFile) - doAsync { - val parentDir = File(outputPath) - if (!parentDir.exists()) { - parentDir.mkdirs() - } - val outputFile = File(parentDir, "img_" + System.currentTimeMillis() + CompressFormatUtils.getExt(format)) - val bitmap = ImageUtils.compressImageFile(jop.inputFile, param.maxWidth, param.maxHeight) - if (bitmap != null) { - ImageUtils.saveBitmap(bitmap, outputFile.path, format, param.saveQuality) - } - if (outputFile.exists()) { - clearJop(jop) - uiThread { - callback.invoke(outputFile.path) - } - } else { - uiThread { - callback.invoke(null) - } - } - } - } - - /** - * clear temp file when job finished - * - * @param jop - */ - private fun clearJop(jop: CompressJop) { - if (jop.deleteInputFile) { - val file = File(jop.inputFile) - if (file.exists()) { - AppLogger.w(TAG, "delete input file: " + file.absolutePath) - file.delete() - } - } - } - -} diff --git a/library/src/main/java/com/sw926/imagefileselector/ImageCropHelper.kt b/library/src/main/java/com/sw926/imagefileselector/ImageCropHelper.kt new file mode 100644 index 0000000..6975737 --- /dev/null +++ b/library/src/main/java/com/sw926/imagefileselector/ImageCropHelper.kt @@ -0,0 +1,155 @@ +package com.sw926.imagefileselector + +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.activity.result.ActivityResultLauncher +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import com.sw926.imagefileselector.contract.CropImageContract +import java.io.File +import java.lang.ref.WeakReference + + +class ImageCropHelper { + + data class CropParam( + val inputUri: Uri, + val outputUri: Uri, + val outPutX: Int = -1, + val outPutY: Int = -1, + val aspectX: Int = -1, + val aspectY: Int = -1, + val scale: Boolean = true, + ) + + private val cropImage: ActivityResultLauncher + + private var outPutX = -1 + private var outPutY = -1 + private var aspectX = -1 + private var aspectY = -1 + private var scale = true + + private var outputFile: File? = null + + /** + * 记录裁切过程中产生的临时文件,裁切完成后进行删除 + */ + private val tempFiles = mutableListOf() + private var listener: ImageFileResultListener? = null + private val context: WeakReference + + + constructor(activity: AppCompatActivity) { + cropImage = activity.registerForActivityResult(CropImageContract()) { + onCropResult(it) + } + context = WeakReference(activity) + + } + + constructor(fragment: Fragment) { + cropImage = fragment.registerForActivityResult(CropImageContract()) { + onCropResult(it) + } + context = WeakReference(fragment.context) + + } + + private fun onCropResult(uri: Uri?) { + outputFile?.let { + if (it.exists()) { + listener?.onSuccess(it.path) + return + } + } + + if (uri == null) { + listener?.onCancel() + return + } + + val context = this.context.get() + if (context == null) { + listener?.onError() + return + } + + val filePath = CommonUtils.getPathFromFileProviderUri(context, uri) + + if (filePath != null) { + listener?.onSuccess(filePath) + } else { + listener?.onError() + } + } + + fun setOutPut(width: Int, height: Int) { + outPutX = width + outPutY = height + } + + fun setOutPutAspect(width: Int, height: Int) { + aspectX = width + aspectY = height + } + + fun setScale(scale: Boolean) { + this.scale = scale + } + + fun setListener(listener: ImageFileResultListener?) { + this.listener = listener + } + + private fun getCropperPackageName(context: Context): String? { + return kotlin.runCatching { + val intent = Intent("com.android.camera.action.CROP") + intent.putExtra("crop", "true") + intent.type = "image/*" + context.packageManager.resolveActivity(intent, 0)?.activityInfo?.packageName + }.getOrNull() + + + } + + fun cropImage(srcFile: String) { + val context = this.context.get() + if (context == null) { + listener?.onError() + return + } + + val cropperPackageName = getCropperPackageName(context) + if (cropperPackageName == null) { + listener?.onError() + return + } + + val inputFile = CommonUtils.generateImageCacheFile(context, ".jpg") + File(srcFile).copyTo(inputFile) + tempFiles.add(inputFile.path) + + val inputUri = CommonUtils.getFileUri(context, inputFile) + + context.grantUriPermission(cropperPackageName, inputUri, Intent.FLAG_GRANT_READ_URI_PERMISSION) + + val outputFile = CommonUtils.generateImageCacheFile(context, ".jpg") + this.outputFile = outputFile + val outputUri = CommonUtils.getFileUri(context, outputFile) + context.grantUriPermission(cropperPackageName, outputUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION) + + val param = CropParam( + inputUri = inputUri, + outputUri = outputUri, + outPutX = outPutX, + outPutY = outPutY, + aspectX = aspectX, + aspectY = aspectY, + scale = scale + ) + cropImage.launch(param) + } + +} \ No newline at end of file diff --git a/library/src/main/java/com/sw926/imagefileselector/ImageCropper.kt b/library/src/main/java/com/sw926/imagefileselector/ImageCropper.kt deleted file mode 100644 index 9b660f4..0000000 --- a/library/src/main/java/com/sw926/imagefileselector/ImageCropper.kt +++ /dev/null @@ -1,244 +0,0 @@ -package com.sw926.imagefileselector - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Context -import android.content.Intent -import android.graphics.Bitmap -import android.net.Uri -import android.os.Bundle -import android.provider.MediaStore -import androidx.fragment.app.Fragment -import com.sw926.imagefileselector.CommonUtils.generateImageCacheFile -import com.sw926.imagefileselector.ImageUtils.saveBitmap -import java.io.File - -class ImageCropper { - companion object { - private val TAG = ImageCropper::class.java.simpleName - private const val IMAGE_CROPPER_BUNDLE = "IMAGE_CROPPER_BUNDLE" - } - - enum class CropperErrorResult { - error, - canceled, - notSupport - } - - private var mOutPutX = -1 - private var mOutPutY = -1 - private var mAspectX = -1 - private var mAspectY = -1 - private var mScale = true - private var mSrcFile: File? = null - private var mOutFile: File? = null - private var mRequestCode = -1 - - /** - * 记录裁切过程中产生的临时文件,裁切完成后进行删除 - */ - private var mTempFile: File? = null - private var mCallback: ImageCropperCallback? = null - private var fragment: Fragment? = null - private var activity: Activity? = null - fun setOutPut(width: Int, height: Int) { - mOutPutX = width - mOutPutY = height - } - - fun setOutPutAspect(width: Int, height: Int) { - mAspectX = width - mAspectY = height - } - - fun setScale(scale: Boolean) { - mScale = scale - } - - fun setCallback(callback: ImageCropperCallback?) { - mCallback = callback - } - - private val context: Context? - private get() { - if (activity != null) { - return activity - } - return fragment?.context - } - - fun onSaveInstanceState(outState: Bundle) { - val bundle = Bundle() - bundle.putInt("outputX", mOutPutX) - bundle.putInt("outputY", mOutPutY) - bundle.putInt("aspectX", mAspectX) - bundle.putInt("aspectY", mAspectY) - bundle.putBoolean("scale", mScale) - bundle.putSerializable("outFile", mOutFile) - bundle.putSerializable("srcFile", mSrcFile) - bundle.putSerializable("tempFile", mTempFile) - outState.putBundle(IMAGE_CROPPER_BUNDLE, bundle) - } - - fun onRestoreInstanceState(savedInstanceState: Bundle?) { - if (savedInstanceState != null && savedInstanceState.containsKey(IMAGE_CROPPER_BUNDLE)) { - val bundle = savedInstanceState.getBundle(IMAGE_CROPPER_BUNDLE) - if (bundle != null) { - mOutPutX = bundle.getInt("outputX") - mOutPutY = bundle.getInt("outputY") - mAspectX = bundle.getInt("aspectX") - mAspectY = bundle.getInt("aspectY") - mScale = bundle.getBoolean("scale") - mOutFile = bundle.getSerializable("outFile") as File? - mSrcFile = bundle.getSerializable("srcFile") as File? - mTempFile = bundle.getSerializable("tempFile") as File? - } - } - } - - fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { - if (mRequestCode > 0 && requestCode == mRequestCode) { - - val context = this.context - if (context == null) { - mCallback?.onError(CropperErrorResult.error) - return - } - - if (resultCode == Activity.RESULT_CANCELED) { - mCallback?.onError(CropperErrorResult.canceled) - return - } - if (mTempFile?.exists() == true) { - AppLogger.i(TAG, "delete temp file: " + mTempFile?.path) - mTempFile?.delete() - } - if (mOutFile?.exists() == true) { - AppLogger.i(TAG, "crop success output file: " + mOutFile?.path) - mOutFile?.path?.let { mCallback?.onSuccess(it) } - return - } - - - intent?.data?.let { Compatibility.getPath(context, it) }?.let { path -> - if (path.isNotBlank()) { - val outputFile = File(path) - if (outputFile.exists()) { - AppLogger.i(TAG, "crop success output file:$path") - mCallback?.onSuccess(path) - return - } - } - } - - val bitmap = intent?.getParcelableExtra("data") - if (bitmap != null) { - val outputFile = generateImageCacheFile(context, ".jpg") - saveBitmap(bitmap, outputFile.path, Bitmap.CompressFormat.JPEG, 80) - AppLogger.i(TAG, "create output file from data: " + outputFile.path) - - mCallback?.onSuccess(outputFile.path) - return - } - - mCallback?.onError(CropperErrorResult.error) - } - } - - fun cropImage(activity: Activity?, srcFile: String, requestCode: Int) { - fragment = null - this.activity = activity - mRequestCode = requestCode - cropImage(srcFile) - } - - fun cropImage(fragment: Fragment?, srcFile: String, requestCode: Int) { - this.fragment = fragment - activity = null - mRequestCode = requestCode - cropImage(srcFile) - } - - private fun cropImage(srcFile: String) { - try { - AppLogger.i(TAG, "------------------ start crop file ---------------") - val context = context - if (context == null) { - AppLogger.e(TAG, "fragment or activity is null") - mCallback?.onError(CropperErrorResult.error) - return - } - val inputFile = File(srcFile) - if (!inputFile.exists()) { - AppLogger.e(TAG, "input file not exists") - mCallback?.onError(CropperErrorResult.error) - return - } - val outFile = generateImageCacheFile(context, ".jpg") - if (!outFile.parentFile.exists()) { - outFile.parentFile.mkdirs() - } - if (outFile.exists()) { - outFile.delete() - } - AppLogger.i(TAG, "set output file: " + outFile.path) - mSrcFile = inputFile - mOutFile = outFile - val uri: Uri - if (inputFile.path.contains("%")) { - val ext = srcFile.substring(srcFile.lastIndexOf(".")) - val tFile = generateImageCacheFile(context, ext) - inputFile.copyTo(tFile, true, 1024) - mTempFile = tFile - uri = CommonUtils.getFileUri(context, tFile) - } else { - uri = CommonUtils.getFileUri(context, inputFile) - } - val intent = Intent("com.android.camera.action.CROP") - intent.setDataAndType(uri, "image/*") - intent.putExtra("crop", "true") - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - if (mAspectX > 0 && mAspectY > 0) { - intent.putExtra("aspectX", mAspectX) - intent.putExtra("aspectY", mAspectY) - } - if (mOutPutX > 0 && mOutPutY > 0) { - intent.putExtra("outputX", mOutPutX) - intent.putExtra("outputY", mOutPutY) - } - if (mScale) { - intent.putExtra("scale", true) - intent.putExtra("scaleUpIfNeeded", true) // 黑边 - } - if (Compatibility.scaleUpIfNeeded4Black()) { - intent.putExtra("scaleUpIfNeeded", true) - } - intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(outFile)) - if (Compatibility.shouldReturnCropData()) { - intent.putExtra("return-data", true) - } - if (activity != null) { - try { - activity?.startActivityForResult(intent, mRequestCode) - } catch (e: ActivityNotFoundException) { - mCallback?.onError(CropperErrorResult.notSupport) - } - } - if (fragment != null) { - try { - fragment?.startActivityForResult(intent, mRequestCode) - } catch (e: ActivityNotFoundException) { - mCallback?.onError(CropperErrorResult.notSupport) - } - } - } catch (e: Exception) { - e.printStackTrace() - mCallback?.onError(CropperErrorResult.error) - } - } - - interface ImageCropperCallback { - fun onError(result: CropperErrorResult?) - fun onSuccess(outputFile: String) - } -} \ No newline at end of file diff --git a/library/src/main/java/com/sw926/imagefileselector/ImageFileResultListener.kt b/library/src/main/java/com/sw926/imagefileselector/ImageFileResultListener.kt new file mode 100644 index 0000000..3500517 --- /dev/null +++ b/library/src/main/java/com/sw926/imagefileselector/ImageFileResultListener.kt @@ -0,0 +1,15 @@ +package com.sw926.imagefileselector + +/** + * + * @author: sunwei + * @date: 2021/9/27 + */ +interface ImageFileResultListener { + + fun onSuccess(filePath: String) + + fun onCancel() + + fun onError() +} \ No newline at end of file diff --git a/library/src/main/java/com/sw926/imagefileselector/ImageFileSelector.kt b/library/src/main/java/com/sw926/imagefileselector/ImageFileSelector.kt index 6ea4ec4..4142341 100644 --- a/library/src/main/java/com/sw926/imagefileselector/ImageFileSelector.kt +++ b/library/src/main/java/com/sw926/imagefileselector/ImageFileSelector.kt @@ -2,45 +2,68 @@ package com.sw926.imagefileselector -import android.app.Activity import android.content.Context -import android.content.Intent import android.graphics.Bitmap -import android.os.Bundle +import android.net.Uri +import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import java.io.File +import java.lang.ref.WeakReference -class ImageFileSelector(context: Context) { +class ImageFileSelector { - companion object { + private var resultListener: ImageFileResultListener? = null + private val mImagePickHelper: ImagePickHelper + private val mImageCaptureHelper: ImageCaptureHelper - private const val TAG = "ImageFileSelector" + private var compressParams: CompressParams + private val context: WeakReference - fun setDebug(debug: Boolean) { - AppLogger.DEBUG = debug - } + constructor(activity: AppCompatActivity) { + mImageCaptureHelper = ImageCaptureHelper(activity) + mImagePickHelper = ImagePickHelper(activity) + + compressParams = CompressParams() + context = WeakReference(activity) + + init() } - private var mCallback: Callback? = null - private val mImagePickHelper: ImagePickHelper - private val mImageCaptureHelper: ImageCaptureHelper + constructor(fragment: Fragment) { + mImageCaptureHelper = ImageCaptureHelper(fragment) + mImagePickHelper = ImagePickHelper(fragment) - private var compressParams: ImageCompressHelper.CompressParams + compressParams = CompressParams() + context = WeakReference(fragment.context) + init() + } - init { - compressParams = ImageCompressHelper.CompressParams(outputPath = CommonUtils.getOutPutPath(context).absolutePath) + private fun init() { + val listener = object : ImageUriResultListener { + override fun onSuccess(uri: Uri) { - mImagePickHelper = ImagePickHelper() - mImagePickHelper.setCallback(ImageGetCallback(false)) + val context = this@ImageFileSelector.context.get() + if (context == null) { + resultListener?.onError() + return + } + ImageUtils.compress(context, uri, compressParams) { + if (it != null) { + resultListener?.onSuccess(it) + } else { + resultListener?.onError() + } + } + } - mImageCaptureHelper = ImageCaptureHelper() - mImageCaptureHelper.setCallback(ImageGetCallback(true)) - } + override fun onCancel() { + } + override fun onError() { + } + } - @Suppress("unused") - fun setOutPutPath(outPutPath: String) { - compressParams = compressParams.copy(outputPath = outPutPath) + mImagePickHelper.setListener(listener) + mImageCaptureHelper.setListener(listener) } @Suppress("unused") @@ -79,79 +102,17 @@ class ImageFileSelector(context: Context) { compressParams = compressParams.copy(compressFormat = compressFormat) } - fun onActivityResult(context: Context, requestCode: Int, resultCode: Int, data: Intent?) { - mImagePickHelper.onActivityResult(requestCode, resultCode, data) - mImageCaptureHelper.onActivityResult(context, requestCode, resultCode, data) - } - fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - when (requestCode) { - mImagePickHelper.requestCode -> { - mImagePickHelper.onRequestPermissionsResult(requestCode, permissions, grantResults) - } - mImageCaptureHelper.requestCode -> { - mImageCaptureHelper.onRequestPermissionsResult(requestCode, permissions, grantResults) - } - } + fun setListener(listenerUri: ImageFileResultListener?) { + resultListener = listenerUri } - fun onSaveInstanceState(outState: Bundle) { - mImageCaptureHelper.onSaveInstanceState(outState) - mImagePickHelper.onSaveInstanceState(outState) + fun selectImage() { + mImagePickHelper.pickImage() } - fun onRestoreInstanceState(outState: Bundle?) { - mImageCaptureHelper.onRestoreInstanceState(outState) - mImagePickHelper.onRestoreInstanceState(outState) - } - - fun setCallback(callback: Callback?) { - mCallback = callback - } - - fun selectImage(activity: Activity, requestCode: Int) { - mImagePickHelper.selectorImage(activity, requestCode) - } - - fun selectImage(fragment: Fragment, requestCode: Int) { - mImagePickHelper.selectImage(fragment, requestCode) - } - - fun takePhoto(activity: Activity, requestCode: Int) { - mImageCaptureHelper.captureImage(activity, requestCode) - } - - fun takePhoto(fragment: Fragment, requestCode: Int) { - mImageCaptureHelper.captureImage(fragment, requestCode) - } - - private inner class ImageGetCallback constructor(private val mDeleteOutputFile: Boolean) : Callback { - - override fun onError(errorResult: ErrorResult) { - mCallback?.onError(errorResult) - } - - override fun onSuccess(file: String) { - AppLogger.d(TAG, "get file: $file") - val jop = ImageCompressHelper.CompressJop( - inputFile = file, - params = compressParams, - deleteInputFile = mDeleteOutputFile - ) - ImageCompressHelper.compress(jop) { - if (it != null) { - mCallback?.onSuccess(it) - } else { - mCallback?.onError(ErrorResult.error) - } - } - } - } - - interface Callback { - fun onError(errorResult: ErrorResult) - - fun onSuccess(file: String) + fun takePhoto() { + mImageCaptureHelper.takePicture() } } diff --git a/library/src/main/java/com/sw926/imagefileselector/ImagePickHelper.kt b/library/src/main/java/com/sw926/imagefileselector/ImagePickHelper.kt index 089eb52..4f7fe0f 100644 --- a/library/src/main/java/com/sw926/imagefileselector/ImagePickHelper.kt +++ b/library/src/main/java/com/sw926/imagefileselector/ImagePickHelper.kt @@ -1,172 +1,43 @@ package com.sw926.imagefileselector - -import android.Manifest -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.os.Build -import android.os.Bundle -import androidx.core.app.ActivityCompat +import android.net.Uri +import androidx.activity.result.ActivityResultLauncher +import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import com.sw926.imagefileselector.ErrorResult.error -import com.sw926.imagefileselector.ErrorResult.permissionDenied -import java.lang.ref.WeakReference +import com.sw926.imagefileselector.contract.PickImageContract class ImagePickHelper { - companion object { - private const val TAG = "ImagePickHelper" - } - - private var callerWeakReference: WeakReference? = null - - private var mCallback: ImageFileSelector.Callback? = null - - var requestCode = -1 - private set - - private var mType = "image/*" - - fun setType(type: String) { - mType = type - } - - fun setCallback(callback: ImageFileSelector.Callback) { - mCallback = callback - } - - fun selectImage(fragment: Fragment, requestCode: Int) { - AppLogger.i(TAG, "start select image from fragment") - this.requestCode = requestCode - callerWeakReference = WeakReference(fragment) - - val context = fragment.context - if (context == null) { - mCallback?.onError(error) - return - } - - val permissions = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE) - if (PermissionsHelper.isGrant(context, *permissions)) { - startSelect() - } else { - fragment.requestPermissions(permissions, requestCode) - } - } - - fun selectorImage(activity: Activity, requestCode: Int) { - AppLogger.i(TAG, "start select image from activity") - this.requestCode = requestCode - callerWeakReference = WeakReference(activity) - - val permissions = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE) - if (PermissionsHelper.isGrant(activity, *permissions)) { - startSelect() - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - activity.requestPermissions(permissions, requestCode) - } - } - } + private var listenerUri: ImageUriResultListener? = null + private val requestPick: ActivityResultLauncher - private fun startSelect() { - AppLogger.i(TAG, "start system gallery activity") - when (val caller = callerWeakReference?.get()) { - is Fragment -> { - try { - caller.startActivityForResult(createIntent(), requestCode) - } catch (e: Exception) { - e.printStackTrace() - } - } - is Activity -> { - try { - caller.startActivityForResult(createIntent(), requestCode) - } catch (e: Exception) { - e.printStackTrace() - } - } - else -> { - AppLogger.e(TAG, "activity or fragment is null") - mCallback?.onError(error) - } - } - } - - private fun createIntent(): Intent { - val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - Intent(Intent.ACTION_OPEN_DOCUMENT) - } else { - Intent(Intent.ACTION_GET_CONTENT) - } - intent.addCategory(Intent.CATEGORY_OPENABLE) - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P - && Build.BOARD == "Xiaomi" - && Build.MODEL == "MIX 2") { - intent.type = "*/*" + private var type = "image/*" - } else { - intent.type = mType - } - return intent + constructor(activity: AppCompatActivity) { + requestPick = activity.registerForActivityResult(PickImageContract()) { onSelectResult(it) } } - - fun onSaveInstanceState(outState: Bundle) { - if (requestCode > 0) { - outState.putInt("image_pick_request_code", requestCode) - } + constructor(fragment: Fragment) { + requestPick = fragment.registerForActivityResult(PickImageContract()) { onSelectResult(it) } } - fun onRestoreInstanceState(outState: Bundle?) { - if (outState != null && outState.containsKey("image_pick_request_code")) { - requestCode = outState.getInt("image_pick_request_code") - } + fun setType(type: String) { + this.type = type } - private fun getContext(): Context? { - val caller = callerWeakReference?.get() - if (caller is Fragment) { - return caller.context - } else if (caller is Activity) { - return caller - } - return null + fun setListener(listenerUri: ImageUriResultListener) { + this.listenerUri = listenerUri } - fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { - if (requestCode == this.requestCode) { - if (resultCode == Activity.RESULT_CANCELED) { - AppLogger.i(TAG, "canceled select image") - mCallback?.onError(ErrorResult.canceled) - } else if (resultCode == Activity.RESULT_OK) { - val context = getContext() - if (context == null) { - mCallback?.onError(error) - return - } - if (intent == null) { - AppLogger.e(TAG, "select image error, intent null") - mCallback?.onError(error) - return - } - intent.data?.let { Compatibility.getPath(context, it) }?.let { path -> - if (path.isNotBlank()) { - mCallback?.onSuccess(path) - return - } - } - mCallback?.onError(error) - } - } + fun pickImage() { + requestPick.launch(type) } - fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - if (PermissionsHelper.isAllGrant(grantResults)) { - startSelect() + private fun onSelectResult(uri: Uri?) { + if (uri == null) { + listenerUri?.onCancel() } else { - mCallback?.onError(permissionDenied) + listenerUri?.onSuccess(uri) } } diff --git a/library/src/main/java/com/sw926/imagefileselector/ImageUriResultListener.kt b/library/src/main/java/com/sw926/imagefileselector/ImageUriResultListener.kt new file mode 100644 index 0000000..bb2906a --- /dev/null +++ b/library/src/main/java/com/sw926/imagefileselector/ImageUriResultListener.kt @@ -0,0 +1,17 @@ +package com.sw926.imagefileselector + +import android.net.Uri + +/** + * + * @author: sunwei + * @date: 2021/9/27 + */ +interface ImageUriResultListener { + + fun onSuccess(uri: Uri) + + fun onCancel() + + fun onError() +} \ No newline at end of file diff --git a/library/src/main/java/com/sw926/imagefileselector/ImageUtils.kt b/library/src/main/java/com/sw926/imagefileselector/ImageUtils.kt index 90aa061..7076e09 100644 --- a/library/src/main/java/com/sw926/imagefileselector/ImageUtils.kt +++ b/library/src/main/java/com/sw926/imagefileselector/ImageUtils.kt @@ -1,93 +1,25 @@ package com.sw926.imagefileselector +import android.content.Context import android.graphics.Bitmap import android.graphics.Bitmap.CompressFormat import android.graphics.BitmapFactory import android.graphics.Matrix +import android.net.Uri +import androidx.core.content.ContextCompat import androidx.exifinterface.media.ExifInterface import java.io.File +import java.io.FileInputStream import java.io.FileOutputStream import java.io.IOException +import java.io.InputStream +import java.util.concurrent.Executors object ImageUtils { - fun compressImageFile(srcFile: String, maxWidth: Int, maxHeight: Int): Bitmap? { - var bitmap: Bitmap? = null - val inputFileLength = File(srcFile).length() - AppLogger.i(ImageCompressHelper.TAG, "compress file:$srcFile") - AppLogger.i(ImageCompressHelper.TAG, "file length:" + inputFileLength / 1024.0 + "kb") - AppLogger.i(ImageCompressHelper.TAG, "max output size:($maxWidth,$maxHeight") - val decodeOptions = BitmapFactory.Options() - decodeOptions.inJustDecodeBounds = true - BitmapFactory.decodeFile(srcFile, decodeOptions) - val actualWidth = decodeOptions.outWidth - val actualHeight = decodeOptions.outHeight - AppLogger.i(ImageCompressHelper.TAG, "input size:($actualWidth, $actualHeight") - if (actualHeight <= maxHeight && actualWidth <= maxWidth) { - AppLogger.w(ImageCompressHelper.TAG, "no need to compress: input size < output size") - decodeOptions.inJustDecodeBounds = false - try { - bitmap = BitmapFactory.decodeFile(srcFile) - } catch (e: OutOfMemoryError) { - AppLogger.printStackTrace(e) - AppLogger.e(ImageCompressHelper.TAG, "OutOfMemoryError:$srcFile, size:$actualWidth,$actualHeight") - } - if (bitmap != null) { - val degree = getExifOrientation(srcFile) - if (degree != 0) { - AppLogger.w(ImageCompressHelper.TAG, "rotate image from: $degree") - bitmap = rotateImageNotNull(degree, bitmap) - } - } - return bitmap - } - val sampleSize: Int - val w: Int - val h: Int - if (actualWidth * maxHeight > maxWidth * actualHeight) { - w = maxWidth - h = (w * actualHeight / actualWidth.toDouble()).toInt() - sampleSize = (actualWidth / maxWidth.toDouble()).toInt() - } else { - h = maxHeight - w = (h * actualWidth / actualHeight.toDouble()).toInt() - sampleSize = (actualHeight / maxHeight.toDouble()).toInt() - } - AppLogger.i(ImageCompressHelper.TAG, "in simple size:$sampleSize") - decodeOptions.inJustDecodeBounds = false - decodeOptions.inSampleSize = sampleSize - decodeOptions.inPreferredConfig = Bitmap.Config.RGB_565 - decodeOptions.inPurgeable = true - decodeOptions.inInputShareable = true - try { - bitmap = BitmapFactory.decodeFile(srcFile, decodeOptions) - } catch (error: OutOfMemoryError) { - error.printStackTrace() - AppLogger.e(ImageCompressHelper.TAG, "OutOfMemoryError:$srcFile, size:$actualWidth,$actualHeight") - } - if (bitmap != null) { - AppLogger.i(ImageCompressHelper.TAG, "bitmap size after decode:(" + bitmap.width + ", " + bitmap.height + ")") - if (bitmap.width > maxWidth || bitmap.height > maxHeight) { - var tempBitmap: Bitmap? = null - try { - tempBitmap = Bitmap.createScaledBitmap(bitmap, w, h, true) - } catch (e: OutOfMemoryError) { - AppLogger.printStackTrace(e) - } - if (tempBitmap != null) { - bitmap.recycle() - bitmap = tempBitmap - AppLogger.i(ImageCompressHelper.TAG, "scale down:(" + bitmap.width + ", " + bitmap.height + ")") - } - } - val degree = getExifOrientation(srcFile) - if (degree != 0) { - AppLogger.i(ImageCompressHelper.TAG, "rotate image from: $degree") - bitmap = rotateImageNotNull(degree, bitmap) - } - AppLogger.i(ImageCompressHelper.TAG, "output file width: " + bitmap.width + ", height: " + bitmap.height) - } - return bitmap - } + + private val executor = Executors.newSingleThreadExecutor() + + data class SampleSize(val inSampleSize: Int, val width: Int, val height: Int) fun saveBitmap(bmp: Bitmap, filePath: String, format: CompressFormat, quality: Int) { val file = File(filePath) @@ -104,14 +36,22 @@ object ImageUtils { } } - fun getExifOrientation(filepath: String): Int { + private fun getImageSize(inputStream: InputStream): SampleSize { + val option = BitmapFactory.Options() + option.inJustDecodeBounds = true + BitmapFactory.decodeStream(inputStream, null, option) + return SampleSize(1, option.outWidth, option.outHeight) + } + + private fun getExifOrientation(inputStream: InputStream): Int { var degree = 0 var exif: ExifInterface? = null try { - exif = ExifInterface(filepath) + exif = ExifInterface(inputStream) } catch (e: IOException) { - AppLogger.printStackTrace(e) + e.printStackTrace() } + if (exif != null) { val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1) if (orientation != -1) { @@ -125,20 +65,92 @@ object ImageUtils { return degree } - private fun rotateImageNotNull(angle: Int, bitmap: Bitmap): Bitmap { - var out: Bitmap? = null - try { + fun getSampleSize(actualWidth: Int, actualHeight: Int, maxWidth: Int, maxHeight: Int): SampleSize { + val sampleSize: Int + val w: Int + val h: Int + if (actualWidth * maxHeight > maxWidth * actualHeight) { + w = maxWidth + h = (w * actualHeight / actualWidth.toDouble()).toInt() + sampleSize = (actualWidth / maxWidth.toDouble()).toInt() + } else { + h = maxHeight + w = (h * actualWidth / actualHeight.toDouble()).toInt() + sampleSize = (actualHeight / maxHeight.toDouble()).toInt() + } + return SampleSize(sampleSize, w, h) + } + + private fun Uri.openStream(context: Context): InputStream? { + return when (scheme) { + "content" -> context.contentResolver.openInputStream(this) + "file" -> FileInputStream(path) + else -> null + } + } + + fun compressImage(context: Context, uri: Uri, maxWidth: Int, maxHeight: Int): Bitmap? { + val imageSize = uri.openStream(context)?.use { getImageSize(it) } ?: return null + val degree = uri.openStream(context)?.use { getExifOrientation(it) } ?: return null + + val (w, h) = if (degree == 90 || degree == 270) { + Pair(imageSize.height, imageSize.width) + } else { + Pair(imageSize.width, imageSize.height) + } + val sampleSize = getSampleSize(w, h, maxWidth, maxHeight) + val bitmap = if (w <= maxWidth && h <= maxHeight) { + uri.openStream(context)?.use { BitmapFactory.decodeStream(it) } + } else { + val options = BitmapFactory.Options() + options.inSampleSize = sampleSize.inSampleSize + uri.openStream(context)?.use { BitmapFactory.decodeStream(it, null, options) } + + } + + val tempBitmap = if (degree != 0) { + bitmap?.let { + createRotateImage(degree, bitmap).also { + bitmap.recycle() + } + } + } else { + bitmap + } + return if (tempBitmap != null && (tempBitmap.width > maxWidth || tempBitmap.height > maxHeight)) { + Bitmap.createScaledBitmap(tempBitmap, sampleSize.width, sampleSize.height, true).also { + tempBitmap.recycle() + } + } else { + tempBitmap + } + } + + fun createRotateImage(angle: Int, bitmap: Bitmap): Bitmap? { + return kotlin.runCatching { val matrix = Matrix() matrix.postRotate(angle.toFloat()) - out = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) - } catch (e: OutOfMemoryError) { - AppLogger.printStackTrace(e) - AppLogger.e(ImageCompressHelper.TAG, "rotate image error, image will not display in current orientation") - } - if (out != null) { - bitmap.recycle() - return out + Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) + }.onFailure { + it.printStackTrace() + }.getOrNull() + + } + + fun compress(context: Context, uri: Uri, compressParams: CompressParams, callback: (String?) -> Unit) { + executor.execute { + val bitmap = compressImage(context, uri, compressParams.maxWidth, compressParams.maxWidth) + if (bitmap != null) { + val file = CommonUtils.generateImageCacheFile(context, CompressFormatUtils.getExt(compressParams.compressFormat)) + saveBitmap(bitmap, filePath = file.path, compressParams.compressFormat, compressParams.saveQuality) + ContextCompat.getMainExecutor(context).execute { + callback.invoke(file.path) + } + } else { + ContextCompat.getMainExecutor(context).execute { + callback.invoke(null) + } + } } - return bitmap } } \ No newline at end of file diff --git a/library/src/main/java/com/sw926/imagefileselector/PermissionsHelper.kt b/library/src/main/java/com/sw926/imagefileselector/PermissionsHelper.kt deleted file mode 100644 index 82131a4..0000000 --- a/library/src/main/java/com/sw926/imagefileselector/PermissionsHelper.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.sw926.imagefileselector - -import android.content.Context -import android.content.pm.PackageManager -import android.os.Build -import androidx.core.content.ContextCompat - -/** - * Created by sunwei on 7/1/16. - */ - -object PermissionsHelper { - - fun isAllGrant(grantResults: IntArray): Boolean { - grantResults.forEach { - if (it != PackageManager.PERMISSION_GRANTED) { - return false - } - } - return true - } - - fun isGrant(context: Context, vararg permissions: String): Boolean { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - return true - } - - permissions.forEach { - if (ContextCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED) { - return false - } - } - return true - } - -} diff --git a/library/src/main/java/com/sw926/imagefileselector/contract/CropImageContract.kt b/library/src/main/java/com/sw926/imagefileselector/contract/CropImageContract.kt new file mode 100644 index 0000000..abe9eb9 --- /dev/null +++ b/library/src/main/java/com/sw926/imagefileselector/contract/CropImageContract.kt @@ -0,0 +1,54 @@ +package com.sw926.imagefileselector.contract + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.provider.MediaStore +import androidx.activity.result.contract.ActivityResultContract +import com.sw926.imagefileselector.Compatibility +import com.sw926.imagefileselector.ImageCropHelper.CropParam + +/** + * + * @author: sunwei + * @date: 2021/9/27 + */ +class CropImageContract : ActivityResultContract() { + + override fun createIntent(context: Context, input: CropParam): Intent { + + val uri = input.inputUri + val intent = Intent("com.android.camera.action.CROP") + intent.setDataAndType(uri, "image/*") + intent.putExtra("crop", "true") + if (input.aspectX > 0 && input.aspectY > 0) { + intent.putExtra("aspectX", input.aspectX) + intent.putExtra("aspectY", input.aspectY) + } + if (input.outPutX > 0 && input.outPutY > 0) { + intent.putExtra("outputX", input.outPutX) + intent.putExtra("outputY", input.outPutY) + } + if (input.scale) { + intent.putExtra("scale", true) + intent.putExtra("scaleUpIfNeeded", true) // 黑边 + } + if (Compatibility.scaleUpIfNeeded4Black()) { + intent.putExtra("scaleUpIfNeeded", true) + } + + intent.putExtra(MediaStore.EXTRA_OUTPUT, input.outputUri) + if (Compatibility.shouldReturnCropData()) { + intent.putExtra("return-data", true) + } + + return intent + + } + + override fun parseResult(resultCode: Int, intent: Intent?): Uri? { + return if (intent == null || resultCode != Activity.RESULT_OK) null else intent.data + } + +} \ No newline at end of file diff --git a/library/src/main/java/com/sw926/imagefileselector/contract/PickImageContract.kt b/library/src/main/java/com/sw926/imagefileselector/contract/PickImageContract.kt new file mode 100644 index 0000000..7a42578 --- /dev/null +++ b/library/src/main/java/com/sw926/imagefileselector/contract/PickImageContract.kt @@ -0,0 +1,26 @@ +package com.sw926.imagefileselector.contract + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.provider.MediaStore.Images.Media +import androidx.activity.result.contract.ActivityResultContract + +/** + * + * @author: sunwei + * @date: 2021/9/26 + */ +class PickImageContract : ActivityResultContract() { + + override fun createIntent(context: Context, input: String?): Intent { + val pickIntent = Intent(Intent.ACTION_PICK, Media.EXTERNAL_CONTENT_URI) + pickIntent.type = input ?: "image/*" + return pickIntent + } + + override fun parseResult(resultCode: Int, intent: Intent?): Uri? { + return if (intent == null || resultCode != Activity.RESULT_OK) null else intent.data + } +} \ No newline at end of file diff --git a/library/src/main/res/xml/files.xml b/library/src/main/res/xml/files.xml index 17af476..338abbb 100644 --- a/library/src/main/res/xml/files.xml +++ b/library/src/main/res/xml/files.xml @@ -1,6 +1,6 @@ - \ No newline at end of file