This commit is contained in:
fengjignqi
2026-05-16 16:19:39 +08:00
commit 1b1575eb8a
1632 changed files with 242221 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
//package com.esafirm.imagepicker;
//
//import android.app.Application;
//import android.test.ApplicationTestCase;
//
///**
// * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
// */
//public class ApplicationTest extends ApplicationTestCase<Application> {
// public ApplicationTest() {
// super(Application.class);
// }
//}

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.esafirm.imagepicker">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>
<application>
<activity
android:name="com.esafirm.imagepicker.features.ImagePickerActivity"
android:configChanges="orientation|screenSize"
android:theme="@style/ef_AppTheme"/>
<provider
android:name=".helper.ImagePickerFileProvider"
android:authorities="${applicationId}.imagepicker.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/image_picker_provider_paths"/>
</provider>
</application>
</manifest>

View File

@@ -0,0 +1,32 @@
package com.esafirm.imagepicker.adapter;
import android.content.Context;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import com.esafirm.imagepicker.features.imageloader.ImageLoader;
public abstract class BaseListAdapter<T extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<T> {
private final Context context;
private final LayoutInflater inflater;
private final ImageLoader imageLoader;
public BaseListAdapter(Context context, ImageLoader imageLoader) {
this.context = context;
this.inflater = LayoutInflater.from(context);
this.imageLoader = imageLoader;
}
public ImageLoader getImageLoader() {
return imageLoader;
}
public Context getContext() {
return context;
}
public LayoutInflater getInflater() {
return inflater;
}
}

View File

@@ -0,0 +1,82 @@
package com.esafirm.imagepicker.adapter;
import android.content.Context;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.esafirm.imagepicker.R;
import com.esafirm.imagepicker.features.imageloader.ImageLoader;
import com.esafirm.imagepicker.features.imageloader.ImageType;
import com.esafirm.imagepicker.listeners.OnFolderClickListener;
import com.esafirm.imagepicker.model.Folder;
import java.util.ArrayList;
import java.util.List;
public class FolderPickerAdapter extends BaseListAdapter<FolderPickerAdapter.FolderViewHolder> {
private final OnFolderClickListener folderClickListener;
private List<Folder> folders = new ArrayList<>();
public FolderPickerAdapter(Context context, ImageLoader imageLoader, OnFolderClickListener folderClickListener) {
super(context, imageLoader);
this.folderClickListener = folderClickListener;
}
@Override
public FolderViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new FolderViewHolder(
getInflater().inflate(R.layout.ef_imagepicker_item_folder, parent, false));
}
@Override
public void onBindViewHolder(final FolderViewHolder holder, int position) {
final Folder folder = folders.get(position);
getImageLoader().loadImage(
folder.getImages().get(0).getPath(),
holder.image,
ImageType.FOLDER
);
holder.name.setText(folders.get(position).getFolderName());
holder.number.setText(String.valueOf(folders.get(position).getImages().size()));
holder.itemView.setOnClickListener(v -> {
if (folderClickListener != null)
folderClickListener.onFolderClick(folder);
});
}
public void setData(List<Folder> folders) {
if (folders != null) {
this.folders.clear();
this.folders.addAll(folders);
}
notifyDataSetChanged();
}
@Override
public int getItemCount() {
return folders.size();
}
static class FolderViewHolder extends RecyclerView.ViewHolder {
private ImageView image;
private TextView name;
private TextView number;
FolderViewHolder(View itemView) {
super(itemView);
image = (ImageView) itemView.findViewById(R.id.image);
name = (TextView) itemView.findViewById(R.id.tv_name);
number = (TextView) itemView.findViewById(R.id.tv_number);
}
}
}

View File

@@ -0,0 +1,174 @@
package com.esafirm.imagepicker.adapter;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.esafirm.imagepicker.R;
import com.esafirm.imagepicker.features.imageloader.ImageLoader;
import com.esafirm.imagepicker.features.imageloader.ImageType;
import com.esafirm.imagepicker.helper.ImagePickerUtils;
import com.esafirm.imagepicker.listeners.OnImageClickListener;
import com.esafirm.imagepicker.listeners.OnImageSelectedListener;
import com.esafirm.imagepicker.model.Image;
import java.util.ArrayList;
import java.util.List;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
public class ImagePickerAdapter extends BaseListAdapter<ImagePickerAdapter.ImageViewHolder> {
private List<Image> images = new ArrayList<>();
private List<Image> selectedImages = new ArrayList<>();
private OnImageClickListener itemClickListener;
private OnImageSelectedListener imageSelectedListener;
public ImagePickerAdapter(Context context, ImageLoader imageLoader,
List<Image> selectedImages, OnImageClickListener itemClickListener) {
super(context, imageLoader);
this.itemClickListener = itemClickListener;
if (selectedImages != null && !selectedImages.isEmpty()) {
this.selectedImages.addAll(selectedImages);
}
}
@Override
public ImageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ImageViewHolder(
getInflater().inflate(R.layout.ef_imagepicker_item_image, parent, false)
);
}
@Override
public void onBindViewHolder(ImageViewHolder viewHolder, int position) {
final Image image = images.get(position);
final boolean isSelected = isSelected(image);
getImageLoader().loadImage(
image.getPath(),
viewHolder.imageView,
ImageType.GALLERY
);
boolean showFileTypeIndicator = false;
String fileTypeLabel = "";
if(ImagePickerUtils.isGifFormat(image)) {
fileTypeLabel = getContext().getResources().getString(R.string.ef_gif);
showFileTypeIndicator = true;
}
if(ImagePickerUtils.isVideoFormat(image)) {
fileTypeLabel = getContext().getResources().getString(R.string.ef_video);
showFileTypeIndicator = true;
}
viewHolder.fileTypeIndicator.setText(fileTypeLabel);
viewHolder.fileTypeIndicator.setVisibility(showFileTypeIndicator
? View.VISIBLE
: View.GONE);
viewHolder.alphaView.setAlpha(isSelected
? 0.5f
: 0f);
viewHolder.itemView.setOnClickListener(v -> {
boolean shouldSelect = itemClickListener.onImageClick(
isSelected
);
if (isSelected) {
removeSelectedImage(image, position);
} else if (shouldSelect) {
addSelected(image, position);
}
});
viewHolder.container.setForeground(isSelected
? ContextCompat.getDrawable(getContext(), R.drawable.ef_ic_done_white)
: null);
}
private boolean isSelected(Image image) {
for (Image selectedImage : selectedImages) {
if (selectedImage.getPath().equals(image.getPath())) {
return true;
}
}
return false;
}
@Override
public int getItemCount() {
return images.size();
}
public void setData(List<Image> images) {
this.images.clear();
this.images.addAll(images);
}
private void addSelected(final Image image, final int position) {
mutateSelection(() -> {
selectedImages.add(image);
notifyItemChanged(position);
});
}
private void removeSelectedImage(final Image image, final int position) {
mutateSelection(() -> {
selectedImages.remove(image);
notifyItemChanged(position);
});
}
public void removeAllSelectedSingleClick() {
mutateSelection(() -> {
selectedImages.clear();
notifyDataSetChanged();
});
}
private void mutateSelection(Runnable runnable) {
runnable.run();
if (imageSelectedListener != null) {
imageSelectedListener.onSelectionUpdate(selectedImages);
}
}
public void setImageSelectedListener(OnImageSelectedListener imageSelectedListener) {
this.imageSelectedListener = imageSelectedListener;
}
public Image getItem(int position) {
return images.get(position);
}
public List<Image> getSelectedImages() {
return selectedImages;
}
static class ImageViewHolder extends RecyclerView.ViewHolder {
private ImageView imageView;
private View alphaView;
private TextView fileTypeIndicator;
private FrameLayout container;
ImageViewHolder(View itemView) {
super(itemView);
container = (FrameLayout) itemView;
imageView = itemView.findViewById(R.id.image_view);
alphaView = itemView.findViewById(R.id.view_alpha);
fileTypeIndicator = itemView.findViewById(R.id.ef_item_file_type_indicator);
}
}
}

View File

@@ -0,0 +1,30 @@
package com.esafirm.imagepicker.features;
import android.util.Log;
public class ClickUtils {
/**
* Android优雅地处理按钮重复点击的几种方法
* add by wangqianzhou
*/
private static long mLastClickTime = 0;
public static final long TIME_INTERVAL = 3000L;
public static boolean onclickTimes() {
long nowTime = System.currentTimeMillis();
if (nowTime - mLastClickTime > TIME_INTERVAL) {
// do something
Log.e("TAGTIME_INTERVAL", "nowTime: " + nowTime);
Log.e("TAGTIME_INTERVAL", "mLastClickTime: " + mLastClickTime);
Log.e("TAGTIME_INTERVAL", nowTime - mLastClickTime + "");
Log.e("TAGTIME_INTERVAL", "onclickTime: " + "11111111111");
mLastClickTime = nowTime;
return true;
} else {
Log.e("TAGTIME_INTERVAL", "onclickTime: " + "11111111111222");
return false;
}
}
}

View File

@@ -0,0 +1,155 @@
package com.esafirm.imagepicker.features;
import android.content.Context;
import android.database.Cursor;
import android.provider.MediaStore;
import androidx.annotation.Nullable;
import com.esafirm.imagepicker.features.common.ImageLoaderListener;
import com.esafirm.imagepicker.helper.ImagePickerUtils;
import com.esafirm.imagepicker.model.Folder;
import com.esafirm.imagepicker.model.Image;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ImageFileLoader {
private Context context;
private ExecutorService executorService;
public ImageFileLoader(Context context) {
this.context = context;
}
private final String[] projection = new String[]{
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.DATA,
MediaStore.Images.Media.BUCKET_DISPLAY_NAME
};
public void loadDeviceImages(final boolean isFolderMode, final boolean includeVideo, final boolean includeAnimation, final ArrayList<File> excludedImages, final ImageLoaderListener listener) {
getExecutorService().execute(new ImageLoadRunnable(isFolderMode, includeVideo, includeAnimation, excludedImages, listener));
}
public void abortLoadImages() {
if (executorService != null) {
executorService.shutdown();
executorService = null;
}
}
private ExecutorService getExecutorService() {
if (executorService == null) {
executorService = Executors.newSingleThreadExecutor();
}
return executorService;
}
private class ImageLoadRunnable implements Runnable {
private boolean isFolderMode;
private boolean includeVideo;
private boolean includeAnimation;
private ArrayList<File> exlucedImages;
private ImageLoaderListener listener;
public ImageLoadRunnable(boolean isFolderMode, boolean includeVideo, boolean includeAnimation, ArrayList<File> excludedImages, ImageLoaderListener listener) {
this.isFolderMode = isFolderMode;
this.includeVideo = includeVideo;
this.includeAnimation = includeAnimation;
this.exlucedImages = excludedImages;
this.listener = listener;
}
@Override
public void run() {
Cursor cursor;
if (includeVideo) {
String selection = MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE + " OR "
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO;
cursor = context.getContentResolver().query(MediaStore.Files.getContentUri("external"), projection,
selection, null, MediaStore.Images.Media.DATE_ADDED);
} else {
cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection,
null, null, MediaStore.Images.Media.DATE_ADDED);
}
if (cursor == null) {
listener.onFailed(new NullPointerException());
return;
}
List<Image> temp = new ArrayList<>();
Map<String, Folder> folderMap = null;
if (isFolderMode) {
folderMap = new HashMap<>();
}
if (cursor.moveToLast()) {
do {
long id = cursor.getLong(cursor.getColumnIndex(projection[0]));
String name = cursor.getString(cursor.getColumnIndex(projection[1]));
String path = cursor.getString(cursor.getColumnIndex(projection[2]));
String bucket = cursor.getString(cursor.getColumnIndex(projection[3]));
File file = makeSafeFile(path);
if (file != null) {
if (exlucedImages != null && exlucedImages.contains(file))
continue;
Image image = new Image(id, name, path);
if (!includeAnimation) {
if (ImagePickerUtils.isGifFormat(image))
continue;
}
temp.add(image);
if (folderMap != null) {
Folder folder = folderMap.get(bucket);
if (folder == null) {
folder = new Folder(bucket);
folderMap.put(bucket, folder);
}
folder.getImages().add(image);
}
}
} while (cursor.moveToPrevious());
}
cursor.close();
/* Convert HashMap to ArrayList if not null */
List<Folder> folders = null;
if (folderMap != null) {
folders = new ArrayList<>(folderMap.values());
}
listener.onImageLoaded(temp, folders);
}
}
@Nullable
private static File makeSafeFile(String path) {
if (path == null || path.isEmpty()) {
return null;
}
try {
return new File(path);
} catch (Exception ignored) {
return null;
}
}
}

View File

@@ -0,0 +1,237 @@
package com.esafirm.imagepicker.features;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import com.esafirm.imagepicker.features.cameraonly.ImagePickerCameraOnly;
import com.esafirm.imagepicker.features.imageloader.ImageLoader;
import com.esafirm.imagepicker.helper.ConfigUtils;
import com.esafirm.imagepicker.helper.IpLogger;
import com.esafirm.imagepicker.helper.LocaleManager;
import com.esafirm.imagepicker.model.Image;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.fragment.app.Fragment;
public abstract class ImagePicker {
private ImagePickerConfig config;
public abstract void start();
public abstract void start(int requestCode);
public static class ImagePickerWithActivity extends ImagePicker {
private Activity activity;
public ImagePickerWithActivity(Activity activity) {
this.activity = activity;
init();
}
@Override
public void start(int requestCode) {
activity.startActivityForResult(getIntent(activity), requestCode);
}
@Override
public void start() {
activity.startActivityForResult(getIntent(activity), IpCons.RC_IMAGE_PICKER);
}
}
public static class ImagePickerWithFragment extends ImagePicker {
private Fragment fragment;
public ImagePickerWithFragment(Fragment fragment) {
this.fragment = fragment;
init();
}
@Override
public void start(int requestCode) {
fragment.startActivityForResult(getIntent(fragment.getActivity()), requestCode);
}
@Override
public void start() {
fragment.startActivityForResult(getIntent(fragment.getActivity()), IpCons.RC_IMAGE_PICKER);
}
}
/* --------------------------------------------------- */
/* > Stater */
/* --------------------------------------------------- */
public void init() {
config = ImagePickerConfigFactory.createDefault();
}
public static ImagePickerWithActivity create(Activity activity) {
return new ImagePickerWithActivity(activity);
}
public static ImagePickerWithFragment create(Fragment fragment) {
return new ImagePickerWithFragment(fragment);
}
public static ImagePickerCameraOnly cameraOnly() {
return new ImagePickerCameraOnly();
}
/* --------------------------------------------------- */
/* > Builder */
/* --------------------------------------------------- */
public ImagePicker single() {
config.setMode(IpCons.MODE_SINGLE);
return this;
}
public ImagePicker multi() {
config.setMode(IpCons.MODE_MULTIPLE);
return this;
}
public ImagePicker returnMode(@NonNull ReturnMode returnMode) {
config.setReturnMode(returnMode);
return this;
}
public ImagePicker limit(int count) {
config.setLimit(count);
return this;
}
public ImagePicker showCamera(boolean show) {
config.setShowCamera(show);
return this;
}
public ImagePicker toolbarArrowColor(@ColorInt int color) {
config.setArrowColor(color);
return this;
}
public ImagePicker toolbarFolderTitle(String title) {
config.setFolderTitle(title);
return this;
}
public ImagePicker toolbarImageTitle(String title) {
config.setImageTitle(title);
return this;
}
public ImagePicker toolbarDoneButtonText(String text) {
config.setDoneButtonText(text);
return this;
}
public ImagePicker origin(ArrayList<Image> images) {
config.setSelectedImages(images);
return this;
}
public ImagePicker exclude(ArrayList<Image> images) {
config.setExcludedImages(images);
return this;
}
public ImagePicker excludeFiles(ArrayList<File> files) {
config.setExcludedImageFiles(files);
return this;
}
public ImagePicker folderMode(boolean folderMode) {
config.setFolderMode(folderMode);
return this;
}
public ImagePicker includeVideo(boolean includeVideo) {
config.setIncludeVideo(includeVideo);
return this;
}
public ImagePicker includeAnimation(boolean includeAnimation) {
config.setIncludeAnimation(includeAnimation);
return this;
}
public ImagePicker imageDirectory(String directory) {
config.setImageDirectory(directory);
return this;
}
public ImagePicker imageFullDirectory(String fullPath) {
config.setImageFullDirectory(fullPath);
return this;
}
public ImagePicker theme(@StyleRes int theme) {
config.setTheme(theme);
return this;
}
public ImagePicker imageLoader(ImageLoader imageLoader) {
config.setImageLoader(imageLoader);
return this;
}
public ImagePicker enableLog(boolean isEnable) {
IpLogger.getInstance().setEnable(isEnable);
return this;
}
public ImagePicker language(String language) {
config.setLanguage(language);
return this;
}
public ImagePickerConfig getConfig() {
LocaleManager.setLanguange(config.getLanguage());
return ConfigUtils.checkConfig(config);
}
public Intent getIntent(Context context) {
ImagePickerConfig config = getConfig();
Intent intent = new Intent(context, ImagePickerActivity.class);
intent.putExtra(ImagePickerConfig.class.getSimpleName(), config);
return intent;
}
/* --------------------------------------------------- */
/* > Helper */
/* --------------------------------------------------- */
public static boolean shouldHandle(int requestCode, int resultCode, Intent data) {
return resultCode == Activity.RESULT_OK
&& requestCode == IpCons.RC_IMAGE_PICKER
&& data != null;
}
public static List<Image> getImages(Intent intent) {
if (intent == null) {
return null;
}
return intent.getParcelableArrayListExtra(IpCons.EXTRA_SELECTED_IMAGES);
}
public static Image getFirstImageOrNull(Intent intent) {
List<Image> images = getImages(intent);
if (images == null || images.isEmpty()) {
return null;
}
return images.get(0);
}
}

View File

@@ -0,0 +1,215 @@
package com.esafirm.imagepicker.features;
import android.content.Context;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.FrameLayout;
import com.esafirm.imagepicker.R;
import com.esafirm.imagepicker.features.cameraonly.CameraOnlyConfig;
import com.esafirm.imagepicker.helper.ConfigUtils;
import com.esafirm.imagepicker.helper.IpLogger;
import com.esafirm.imagepicker.helper.LocaleManager;
import com.esafirm.imagepicker.helper.ViewUtils;
import com.esafirm.imagepicker.model.Folder;
import com.esafirm.imagepicker.model.Image;
import java.util.List;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.FragmentTransaction;
public class ImagePickerActivity extends AppCompatActivity implements ImagePickerInteractionListener, ImagePickerView {
private ActionBar actionBar;
private ImagePickerFragment imagePickerFragment;
private ImagePickerConfig config;
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(LocaleManager.updateResources(newBase));
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setResult(RESULT_CANCELED);
/* This should not happen */
Intent intent = getIntent();
if (intent == null || intent.getExtras() == null) {
IpLogger.getInstance().e("This should not happen. Please open an issue!");
finish();
return;
}
config = getIntent().getExtras().getParcelable(ImagePickerConfig.class.getSimpleName());
CameraOnlyConfig cameraOnlyConfig = getIntent().getExtras().getParcelable(CameraOnlyConfig.class.getSimpleName());
boolean isCameraOnly = cameraOnlyConfig != null;
// TODO extract camera only function so we don't have to rely to Fragment
if (!isCameraOnly) {
setTheme(config.getTheme());
setContentView(R.layout.ef_activity_image_picker);
setupView();
} else {
setContentView(createCameraLayout());
}
if (savedInstanceState != null) {
// The fragment has been restored.
imagePickerFragment = (ImagePickerFragment) getSupportFragmentManager().findFragmentById(R.id.ef_imagepicker_fragment_placeholder);
} else {
imagePickerFragment = ImagePickerFragment.newInstance(config, cameraOnlyConfig);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.ef_imagepicker_fragment_placeholder, imagePickerFragment);
ft.commit();
}
}
private FrameLayout createCameraLayout() {
FrameLayout frameLayout = new FrameLayout(this);
frameLayout.setId(R.id.ef_imagepicker_fragment_placeholder);
return frameLayout;
}
/**
* Create option menus.
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.ef_image_picker_menu_main, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem menuCamera = menu.findItem(R.id.menu_camera);
if (menuCamera != null) {
if (config != null) {
menuCamera.setVisible(config.isShowCamera());
}
}
MenuItem menuDone = menu.findItem(R.id.menu_done);
if (menuDone != null) {
menuDone.setTitle(ConfigUtils.getDoneButtonText(this, config));
menuDone.setVisible(imagePickerFragment.isShowDoneButton());
}
return super.onPrepareOptionsMenu(menu);
}
/**
* Handle option menu's click event
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
onBackPressed();
return true;
}
if (id == R.id.menu_done) {
imagePickerFragment.onDone();
return true;
}
if (id == R.id.menu_camera) {
imagePickerFragment.captureImageWithPermission();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
if (!imagePickerFragment.handleBack()) {
super.onBackPressed();
}
}
private void setupView() {
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
actionBar = getSupportActionBar();
if (actionBar != null) {
final Drawable arrowDrawable = ViewUtils.getArrowIcon(this);
final int arrowColor = config.getArrowColor();
if (arrowColor != ImagePickerConfig.NO_COLOR && arrowDrawable != null) {
arrowDrawable.setColorFilter(arrowColor, PorterDuff.Mode.SRC_ATOP);
}
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(arrowDrawable);
actionBar.setDisplayShowTitleEnabled(true);
}
}
/* --------------------------------------------------- */
/* > ImagePickerInteractionListener Methods */
/* --------------------------------------------------- */
@Override
public void setTitle(String title) {
actionBar.setTitle(title);
supportInvalidateOptionsMenu();
}
@Override
public void cancel() {
finish();
}
@Override
public void selectionChanged(List<Image> imageList) {
// Do nothing when the selection changes.
}
@Override
public void finishPickImages(Intent result) {
setResult(RESULT_OK, result);
finish();
}
/* --------------------------------------------------- */
/* > View Methods */
/* --------------------------------------------------- */
@Override
public void showLoading(boolean isLoading) {
imagePickerFragment.showLoading(isLoading);
}
@Override
public void showFetchCompleted(List<Image> images, List<Folder> folders) {
imagePickerFragment.showFetchCompleted(images, folders);
}
@Override
public void showError(Throwable throwable) {
imagePickerFragment.showError(throwable);
}
@Override
public void showEmpty() {
imagePickerFragment.showEmpty();
}
@Override
public void showCapturedImage() {
imagePickerFragment.showCapturedImage();
}
@Override
public void finishPickImages(List<Image> images) {
imagePickerFragment.finishPickImages(images);
}
}

View File

@@ -0,0 +1,241 @@
package com.esafirm.imagepicker.features;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.StyleRes;
import com.esafirm.imagepicker.features.common.BaseConfig;
import com.esafirm.imagepicker.features.imageloader.ImageLoader;
import com.esafirm.imagepicker.model.Image;
import java.io.File;
import java.util.ArrayList;
public class ImagePickerConfig extends BaseConfig implements Parcelable {
public static final int NO_COLOR = -1;
private ArrayList<Image> selectedImages;
private ArrayList<File> excludedImages;
private String folderTitle;
private String imageTitle;
private String doneButtonText;
private int arrowColor = NO_COLOR;
private int mode;
private int limit;
private int theme;
private boolean folderMode;
private boolean includeVideo;
private boolean includeAnimation;
private boolean showCamera;
private ImageLoader imageLoader;
private transient String language;
public ImagePickerConfig() {
}
public int getArrowColor() {
return arrowColor;
}
public void setArrowColor(int arrowColor) {
this.arrowColor = arrowColor;
}
public int getMode() {
return mode;
}
public void setMode(int mode) {
this.mode = mode;
}
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
}
public boolean isShowCamera() {
return showCamera;
}
public void setShowCamera(boolean showCamera) {
this.showCamera = showCamera;
}
public boolean isIncludeVideo() {
return includeVideo;
}
public void setIncludeVideo(boolean includeVideo) {
this.includeVideo = includeVideo;
}
public boolean isIncludeAnimation() {
return includeAnimation;
}
public void setIncludeAnimation(boolean includeAnimation) {
this.includeAnimation = includeAnimation;
}
public String getFolderTitle() {
return folderTitle;
}
public void setFolderTitle(String folderTitle) {
this.folderTitle = folderTitle;
}
public String getImageTitle() {
return imageTitle;
}
public void setImageTitle(String imageTitle) {
this.imageTitle = imageTitle;
}
public String getDoneButtonText() {
return doneButtonText;
}
public void setDoneButtonText(String doneButtonText) {
this.doneButtonText = doneButtonText;
}
public ArrayList<Image> getSelectedImages() {
return selectedImages;
}
public void setSelectedImages(ArrayList<Image> selectedImages) {
this.selectedImages = selectedImages;
}
public ArrayList<File> getExcludedImages() {
return excludedImages;
}
public void setExcludedImages(ArrayList<Image> excludedImages) {
if (excludedImages != null && !excludedImages.isEmpty()) {
this.excludedImages = new ArrayList<>();
for (Image image : excludedImages) {
this.excludedImages.add(new File(image.getPath()));
}
} else {
this.excludedImages = null;
}
}
public void setExcludedImageFiles(ArrayList<File> excludedImages) {
this.excludedImages = excludedImages;
}
public boolean isFolderMode() {
return folderMode;
}
public void setFolderMode(boolean folderMode) {
this.folderMode = folderMode;
}
public void setTheme(@StyleRes int theme) {
this.theme = theme;
}
public int getTheme() {
return theme;
}
public void setImageLoader(ImageLoader imageLoader) {
this.imageLoader = imageLoader;
}
public ImageLoader getImageLoader() {
return imageLoader;
}
public void setLanguage(String language) {
this.language = language;
}
public String getLanguage() {
return language;
}
/* --------------------------------------------------- */
/* > Parcelable */
/* --------------------------------------------------- */
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeTypedList(this.selectedImages);
dest.writeByte((byte) (excludedImages != null ? 1 : 0));
if (excludedImages != null) {
dest.writeList(this.excludedImages);
}
dest.writeString(this.folderTitle);
dest.writeString(this.imageTitle);
dest.writeString(this.doneButtonText);
dest.writeInt(this.arrowColor);
dest.writeInt(this.mode);
dest.writeInt(this.limit);
dest.writeInt(this.theme);
dest.writeByte(this.folderMode ? (byte) 1 : (byte) 0);
dest.writeByte(this.includeVideo ? (byte) 1 : (byte) 0);
dest.writeByte(this.includeAnimation ? (byte) 1: (byte) 0);
dest.writeByte(this.showCamera ? (byte) 1 : (byte) 0);
dest.writeSerializable(this.imageLoader);
}
protected ImagePickerConfig(Parcel in) {
super(in);
this.selectedImages = in.createTypedArrayList(Image.CREATOR);
boolean isPresent = in.readByte() != 0;
if (isPresent) {
this.excludedImages = new ArrayList<>();
in.readList(this.excludedImages, File.class.getClassLoader());
}
this.folderTitle = in.readString();
this.imageTitle = in.readString();
this.doneButtonText = in.readString();
this.arrowColor = in.readInt();
this.mode = in.readInt();
this.limit = in.readInt();
this.theme = in.readInt();
this.folderMode = in.readByte() != 0;
this.includeVideo = in.readByte() != 0;
this.includeAnimation = in.readByte() != 0;
this.showCamera = in.readByte() != 0;
this.imageLoader = (ImageLoader) in.readSerializable();
}
public static final Creator<ImagePickerConfig> CREATOR = new Creator<ImagePickerConfig>() {
@Override
public ImagePickerConfig createFromParcel(Parcel source) {
return new ImagePickerConfig(source);
}
@Override
public ImagePickerConfig[] newArray(int size) {
return new ImagePickerConfig[size];
}
};
}

View File

@@ -0,0 +1,29 @@
package com.esafirm.imagepicker.features;
import com.esafirm.imagepicker.features.cameraonly.CameraOnlyConfig;
import com.esafirm.imagepicker.features.imageloader.DefaultImageLoader;
import java.util.ArrayList;
public class ImagePickerConfigFactory {
public static CameraOnlyConfig createCameraDefault() {
CameraOnlyConfig config = new CameraOnlyConfig();
config.setSavePath(ImagePickerSavePath.DEFAULT);
config.setReturnMode(ReturnMode.ALL);
return config;
}
public static ImagePickerConfig createDefault() {
ImagePickerConfig config = new ImagePickerConfig();
config.setMode(IpCons.MODE_MULTIPLE);
config.setLimit(IpCons.MAX_LIMIT);
config.setShowCamera(true);
config.setFolderMode(false);
config.setSelectedImages(new ArrayList<>());
config.setSavePath(ImagePickerSavePath.DEFAULT);
config.setReturnMode(ReturnMode.NONE);
config.setImageLoader(new DefaultImageLoader());
return config;
}
}

View File

@@ -0,0 +1,566 @@
package com.esafirm.imagepicker.features;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.provider.MediaStore;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.esafirm.imagepicker.R;
import com.esafirm.imagepicker.features.camera.CameraHelper;
import com.esafirm.imagepicker.features.camera.DefaultCameraModule;
import com.esafirm.imagepicker.features.cameraonly.CameraOnlyConfig;
import com.esafirm.imagepicker.features.common.BaseConfig;
import com.esafirm.imagepicker.features.recyclers.RecyclerViewManager;
import com.esafirm.imagepicker.helper.ConfigUtils;
import com.esafirm.imagepicker.helper.ImagePickerPreferences;
import com.esafirm.imagepicker.helper.IpCrasher;
import com.esafirm.imagepicker.helper.IpLogger;
import com.esafirm.imagepicker.model.Folder;
import com.esafirm.imagepicker.model.Image;
import com.esafirm.imagepicker.view.SnackBarView;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import static android.app.Activity.RESULT_CANCELED;
import static android.app.Activity.RESULT_OK;
import static com.esafirm.imagepicker.helper.ImagePickerPreferences.PREF_WRITE_EXTERNAL_STORAGE_REQUESTED;
public class ImagePickerFragment extends Fragment implements ImagePickerView {
private static final String STATE_KEY_CAMERA_MODULE = "Key.CameraModule";
private static final String STATE_KEY_RECYCLER = "Key.Recycler";
private static final String STATE_KEY_SELECTED_IMAGES = "Key.SelectedImages";
private static final int RC_CAPTURE = 2000;
private static final int RC_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 23;
private static final int RC_PERMISSION_REQUEST_CAMERA = 24;
private IpLogger logger = IpLogger.getInstance();
private RecyclerView recyclerView;
private SnackBarView snackBarView;
private ProgressBar progressBar;
private TextView emptyTextView;
private RecyclerViewManager recyclerViewManager;
private ImagePickerPresenter presenter;
private ImagePickerPreferences preferences;
private ImagePickerConfig config;
private ImagePickerInteractionListener interactionListener;
private Handler handler;
private ContentObserver observer;
private boolean isCameraOnly;
public ImagePickerFragment() {
// Required empty public constructor.
}
public static ImagePickerFragment newInstance(@Nullable ImagePickerConfig config,
@Nullable CameraOnlyConfig cameraOnlyConfig) {
ImagePickerFragment fragment = new ImagePickerFragment();
Bundle args = new Bundle();
if (config != null) {
args.putParcelable(ImagePickerConfig.class.getSimpleName(), config);
}
if (cameraOnlyConfig != null) {
args.putParcelable(CameraOnlyConfig.class.getSimpleName(), cameraOnlyConfig);
}
fragment.setArguments(args);
return fragment;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
setupComponents();
if (interactionListener == null) {
throw new RuntimeException("ImagePickerFragment needs an " +
"ImagePickerInteractionListener. This will be set automatically if the " +
"activity implements ImagePickerInteractionListener, and can be set manually " +
"with fragment.setInteractionListener(listener).");
}
if (savedInstanceState != null) {
presenter.setCameraModule((DefaultCameraModule) savedInstanceState.getSerializable(STATE_KEY_CAMERA_MODULE));
}
if (isCameraOnly) {
if (savedInstanceState == null) {
captureImageWithPermission();
}
} else {
ImagePickerConfig config = getImagePickerConfig();
if (config == null) {
IpCrasher.openIssue();
}
// clone the inflater using the ContextThemeWrapper
LayoutInflater localInflater = inflater.cloneInContext(new ContextThemeWrapper(getActivity(), config.getTheme()));
// inflate the layout using the cloned inflater, not default inflater
View result = localInflater.inflate(R.layout.ef_fragment_image_picker, container, false);
setupView(result);
if (savedInstanceState == null) {
setupRecyclerView(config, config.getSelectedImages());
} else {
setupRecyclerView(config, savedInstanceState.getParcelableArrayList(STATE_KEY_SELECTED_IMAGES));
recyclerViewManager.onRestoreState(savedInstanceState.getParcelable(STATE_KEY_RECYCLER));
}
interactionListener.selectionChanged(recyclerViewManager.getSelectedImages());
return result;
}
return null;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
isCameraOnly = getArguments().containsKey(CameraOnlyConfig.class.getSimpleName());
startContentObserver();
}
private BaseConfig getBaseConfig() {
return isCameraOnly
? getCameraOnlyConfig()
: getImagePickerConfig();
}
@Nullable
private ImagePickerConfig getImagePickerConfig() {
if (config == null) {
Bundle bundle = getArguments();
if (bundle == null) {
IpCrasher.openIssue();
}
boolean hasImagePickerConfig = bundle.containsKey(ImagePickerConfig.class.getSimpleName());
boolean hasCameraOnlyConfig = bundle.containsKey(ImagePickerConfig.class.getSimpleName());
if (!hasCameraOnlyConfig && !hasImagePickerConfig) {
IpCrasher.openIssue();
}
config = bundle.getParcelable(ImagePickerConfig.class.getSimpleName());
}
return config;
}
private CameraOnlyConfig getCameraOnlyConfig() {
return getArguments().getParcelable(CameraOnlyConfig.class.getSimpleName());
}
private void setupView(View rootView) {
progressBar = rootView.findViewById(R.id.progress_bar);
emptyTextView = rootView.findViewById(R.id.tv_empty_images);
recyclerView = rootView.findViewById(R.id.recyclerView);
snackBarView = rootView.findViewById(R.id.ef_snackbar);
}
private void setupRecyclerView(ImagePickerConfig config, ArrayList<Image> selectedImages) {
recyclerViewManager = new RecyclerViewManager(
recyclerView,
config,
getResources().getConfiguration().orientation
);
recyclerViewManager.setupAdapters(selectedImages, (isSelected) -> recyclerViewManager.selectImage(isSelected)
, bucket -> setImageAdapter(bucket.getImages()));
recyclerViewManager.setImageSelectedListener(selectedImage -> {
updateTitle();
interactionListener.selectionChanged(recyclerViewManager.getSelectedImages());
if (ConfigUtils.shouldReturn(config, false) && !selectedImage.isEmpty()) {
onDone();
}
});
}
private void setupComponents() {
preferences = new ImagePickerPreferences(getActivity());
presenter = new ImagePickerPresenter(new ImageFileLoader(getActivity()));
presenter.attachView(this);
}
@Override
public void onResume() {
super.onResume();
if (!isCameraOnly) {
getDataWithPermission();
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(STATE_KEY_CAMERA_MODULE, presenter.getCameraModule());
if (!isCameraOnly) {
outState.putParcelable(STATE_KEY_RECYCLER, recyclerViewManager.getRecyclerState());
outState.putParcelableArrayList(STATE_KEY_SELECTED_IMAGES, (ArrayList<? extends Parcelable>)
recyclerViewManager.getSelectedImages());
}
}
/**
* Set image adapter
* 1. Set new data
* 2. Update item decoration
* 3. Update title
*/
void setImageAdapter(List<Image> images) {
recyclerViewManager.setImageAdapter(images);
updateTitle();
}
void setFolderAdapter(List<Folder> folders) {
recyclerViewManager.setFolderAdapter(folders);
updateTitle();
}
private void updateTitle() {
interactionListener.setTitle(recyclerViewManager.getTitle());
}
/**
* On finish selected image
* Get all selected images then return image to caller activity
*/
public void onDone() {
presenter.onDoneSelectImages(recyclerViewManager.getSelectedImages());
}
/**
* Config recyclerView when configuration changed
*/
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (recyclerViewManager != null) {
// recyclerViewManager can be null here if we use cameraOnly mode
recyclerViewManager.changeOrientation(newConfig.orientation);
}
}
/**
* Check permission
*/
private void getDataWithPermission() {
int rc = ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (rc == PackageManager.PERMISSION_GRANTED) {
getData();
} else {
requestWriteExternalPermission();
}
}
private void getData() {
presenter.abortLoad();
ImagePickerConfig config = getImagePickerConfig();
if (config != null) {
presenter.loadImages(config);
}
}
/**
* Request for permission
* If permission denied or app is first launched, request for permission
* If permission denied and user choose 'Never Ask Again', show snackbar with an action that navigate to app settings
*/
private void requestWriteExternalPermission() {
logger.w("Write External permission is not granted. Requesting permission");
final String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
requestPermissions(permissions, RC_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE);
} else {
final String permission = PREF_WRITE_EXTERNAL_STORAGE_REQUESTED;
if (!preferences.isPermissionRequested(permission)) {
preferences.setPermissionRequested(permission);
requestPermissions(permissions, RC_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE);
} else {
snackBarView.show(R.string.ef_msg_no_write_external_permission, v -> openAppSettings());
}
}
}
private void requestCameraPermissions() {
logger.w("Write External permission is not granted. Requesting permission");
ArrayList<String> permissions = new ArrayList<>(2);
if (ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.CAMERA);
}
if (ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
if (checkForRationale(permissions)) {
requestPermissions(permissions.toArray(new String[permissions.size()]), RC_PERMISSION_REQUEST_CAMERA);
} else {
final String permission = ImagePickerPreferences.PREF_CAMERA_REQUESTED;
if (!preferences.isPermissionRequested(permission)) {
preferences.setPermissionRequested(permission);
requestPermissions(permissions.toArray(new String[permissions.size()]), RC_PERMISSION_REQUEST_CAMERA);
} else {
if (isCameraOnly) {
Toast.makeText(getActivity().getApplicationContext(),
getString(R.string.ef_msg_no_camera_permission), Toast.LENGTH_SHORT).show();
interactionListener.cancel();
} else {
snackBarView.show(R.string.ef_msg_no_camera_permission, v -> openAppSettings());
}
}
}
}
private boolean checkForRationale(List<String> permissions) {
for (int i = 0, size = permissions.size(); i < size; i++) {
if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), permissions.get(i))) {
return true;
}
}
return false;
}
/**
* Handle permission results
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case RC_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE: {
if (grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
logger.d("Write External permission granted");
getData();
return;
}
logger.e("Permission not granted: results len = " + grantResults.length +
" Result code = " + (grantResults.length > 0 ? grantResults[0] : "(empty)"));
interactionListener.cancel();
}
break;
case RC_PERMISSION_REQUEST_CAMERA: {
if (grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
logger.d("Camera permission granted");
captureImage();
return;
}
logger.e("Permission not granted: results len = " + grantResults.length +
" Result code = " + (grantResults.length > 0 ? grantResults[0] : "(empty)"));
interactionListener.cancel();
break;
}
default: {
logger.d("Got unexpected permission result: " + requestCode);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
break;
}
}
}
/**
* Open app settings screen
*/
private void openAppSettings() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", getActivity().getPackageName(), null));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
/**
* Check if the captured image is stored successfully
* Then reload data
*/
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_CAPTURE) {
if (resultCode == RESULT_OK) {
presenter.finishCaptureImage(getActivity(), data, getBaseConfig());
} else if (resultCode == RESULT_CANCELED && isCameraOnly) {
presenter.abortCaptureImage();
interactionListener.cancel();
}
}
}
/**
* Request for camera permission
*/
public void captureImageWithPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
final boolean isCameraGranted = ActivityCompat
.checkSelfPermission(getActivity(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
final boolean isWriteGranted = ActivityCompat
.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
if (isCameraGranted && isWriteGranted) {
captureImage();
} else {
logger.w("Camera permission is not granted. Requesting permission");
requestCameraPermissions();
}
} else {
captureImage();
}
}
/**
* Start camera intent
* Create a temporary file and pass file Uri to camera intent
*/
private void captureImage() {
if (!CameraHelper.checkCameraAvailability(getActivity())) {
return;
}
presenter.captureImage(this, getBaseConfig(), RC_CAPTURE);
}
private void startContentObserver() {
if (isCameraOnly) {
return;
}
if (handler == null) {
handler = new Handler();
}
observer = new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange) {
getData();
}
};
getActivity().getContentResolver()
.registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, false, observer);
}
@Override
public void onDestroy() {
super.onDestroy();
if (presenter != null) {
presenter.abortLoad();
presenter.detachView();
}
if (observer != null) {
getActivity().getContentResolver().unregisterContentObserver(observer);
observer = null;
}
if (handler != null) {
handler.removeCallbacksAndMessages(null);
handler = null;
}
}
// Returns true if this Fragment can do anything with a "back" event, such as the containing
// Activity receiving onBackPressed(). Returns false if the containing Activity should handle
// it.
// This Fragment might handle a "back" event by, for example, going back to the list of folders.
// Or it might have no "back" to go, and return false.
public boolean handleBack() {
if (isCameraOnly) {
return false;
}
if (recyclerViewManager.handleBack()) {
// Handled.
updateTitle();
return true;
}
return false;
}
public boolean isShowDoneButton() {
return recyclerViewManager.isShowDoneButton();
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof ImagePickerInteractionListener) {
interactionListener = (ImagePickerInteractionListener) context;
}
}
public void setInteractionListener(ImagePickerInteractionListener listener) {
interactionListener = listener;
}
/* --------------------------------------------------- */
/* > View Methods */
/* --------------------------------------------------- */
@Override
public void finishPickImages(List<Image> images) {
Intent data = new Intent();
data.putParcelableArrayListExtra(IpCons.EXTRA_SELECTED_IMAGES, (ArrayList<? extends Parcelable>) images);
interactionListener.finishPickImages(data);
}
@Override
public void showCapturedImage() {
getDataWithPermission();
}
@Override
public void showFetchCompleted(List<Image> images, List<Folder> folders) {
ImagePickerConfig config = getImagePickerConfig();
if (config != null && config.isFolderMode()) {
setFolderAdapter(folders);
} else {
setImageAdapter(images);
}
}
@Override
public void showError(Throwable throwable) {
String message = "Unknown Error";
if (throwable != null && throwable instanceof NullPointerException) {
message = "Images do not exist";
}
Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show();
}
@Override
public void showLoading(boolean isLoading) {
progressBar.setVisibility(isLoading ? View.VISIBLE : View.GONE);
recyclerView.setVisibility(isLoading ? View.GONE : View.VISIBLE);
emptyTextView.setVisibility(View.GONE);
}
@Override
public void showEmpty() {
progressBar.setVisibility(View.GONE);
recyclerView.setVisibility(View.GONE);
emptyTextView.setVisibility(View.VISIBLE);
}
}

View File

@@ -0,0 +1,21 @@
package com.esafirm.imagepicker.features;
import android.content.Intent;
import com.esafirm.imagepicker.model.Image;
import java.util.List;
public interface ImagePickerInteractionListener {
void setTitle(String title);
void cancel();
// Get this callback by calling an ImagePickerFragment's finishPickImages() method. It
// removes Images whose files no longer exist.
void finishPickImages(Intent result);
/**
* Called when the user selects or deselects sn image. Also called in onCreateView.
* May include Images whose files no longer exist.
*/
void selectionChanged(List<Image> imageList);
}

View File

@@ -0,0 +1,131 @@
package com.esafirm.imagepicker.features;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import androidx.fragment.app.Fragment;
import android.widget.Toast;
import com.esafirm.imagepicker.R;
import com.esafirm.imagepicker.features.camera.DefaultCameraModule;
import com.esafirm.imagepicker.features.common.BaseConfig;
import com.esafirm.imagepicker.features.common.BasePresenter;
import com.esafirm.imagepicker.features.common.ImageLoaderListener;
import com.esafirm.imagepicker.helper.ConfigUtils;
import com.esafirm.imagepicker.model.Folder;
import com.esafirm.imagepicker.model.Image;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
class ImagePickerPresenter extends BasePresenter<ImagePickerView> {
private ImageFileLoader imageLoader;
private DefaultCameraModule cameraModule;
private Handler main = new Handler(Looper.getMainLooper());
ImagePickerPresenter(ImageFileLoader imageLoader) {
this.imageLoader = imageLoader;
}
DefaultCameraModule getCameraModule() {
if (cameraModule == null) {
cameraModule = new DefaultCameraModule();
}
return cameraModule;
}
/* Set the camera module in onRestoreInstance */
void setCameraModule(DefaultCameraModule cameraModule) {
this.cameraModule = cameraModule;
}
void abortLoad() {
imageLoader.abortLoadImages();
}
void loadImages(ImagePickerConfig config) {
if (!isViewAttached()) return;
boolean isFolder = config.isFolderMode();
boolean includeVideo = config.isIncludeVideo();
boolean includeAnimation = config.isIncludeAnimation();
ArrayList<File> excludedImages = config.getExcludedImages();
runOnUiIfAvailable(() -> getView().showLoading(true));
imageLoader.loadDeviceImages(isFolder, includeVideo, includeAnimation, excludedImages, new ImageLoaderListener() {
@Override
public void onImageLoaded(final List<Image> images, final List<Folder> folders) {
runOnUiIfAvailable(() -> {
getView().showFetchCompleted(images, folders);
final boolean isEmpty = folders != null
? folders.isEmpty()
: images.isEmpty();
if (isEmpty) {
getView().showEmpty();
} else {
getView().showLoading(false);
}
});
}
@Override
public void onFailed(final Throwable throwable) {
runOnUiIfAvailable(() -> getView().showError(throwable));
}
});
}
void onDoneSelectImages(List<Image> selectedImages) {
if (selectedImages != null && selectedImages.size() > 0) {
/* Scan selected images which not existed */
for (int i = 0; i < selectedImages.size(); i++) {
Image image = selectedImages.get(i);
File file = new File(image.getPath());
if (!file.exists()) {
selectedImages.remove(i);
i--;
}
}
getView().finishPickImages(selectedImages);
}
}
void captureImage(Fragment fragment, BaseConfig config, int requestCode) {
Context context = fragment.getActivity().getApplicationContext();
Intent intent = getCameraModule().getCameraIntent(fragment.getActivity(), config);
if (intent == null) {
Toast.makeText(context, context.getString(R.string.ef_error_create_image_file), Toast.LENGTH_LONG).show();
return;
}
fragment.startActivityForResult(intent, requestCode);
}
void finishCaptureImage(Context context, Intent data, final BaseConfig config) {
getCameraModule().getImage(context, data, images -> {
if (ConfigUtils.shouldReturn(config, true)) {
getView().finishPickImages(images);
} else {
getView().showCapturedImage();
}
});
}
void abortCaptureImage() {
getCameraModule().removeImage();
}
private void runOnUiIfAvailable(Runnable runnable) {
main.post(() -> {
if (isViewAttached()) {
runnable.run();
}
});
}
}

View File

@@ -0,0 +1,53 @@
package com.esafirm.imagepicker.features;
import android.os.Parcel;
import android.os.Parcelable;
public class ImagePickerSavePath implements Parcelable {
public static final ImagePickerSavePath DEFAULT = new ImagePickerSavePath("Camera", false);
private final String path;
private final boolean isFullPath;
public ImagePickerSavePath(String path, boolean isFullPath) {
this.path = path;
this.isFullPath = isFullPath;
}
public String getPath() {
return path;
}
public boolean isFullPath() {
return isFullPath;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.path);
dest.writeByte(this.isFullPath ? (byte) 1 : (byte) 0);
}
protected ImagePickerSavePath(Parcel in) {
this.path = in.readString();
this.isFullPath = in.readByte() != 0;
}
public static final Parcelable.Creator<ImagePickerSavePath> CREATOR = new Parcelable.Creator<ImagePickerSavePath>() {
@Override
public ImagePickerSavePath createFromParcel(Parcel source) {
return new ImagePickerSavePath(source);
}
@Override
public ImagePickerSavePath[] newArray(int size) {
return new ImagePickerSavePath[size];
}
};
}

View File

@@ -0,0 +1,16 @@
package com.esafirm.imagepicker.features;
import com.esafirm.imagepicker.features.common.MvpView;
import com.esafirm.imagepicker.model.Folder;
import com.esafirm.imagepicker.model.Image;
import java.util.List;
public interface ImagePickerView extends MvpView {
void showLoading(boolean isLoading);
void showFetchCompleted(List<Image> images, List<Folder> folders);
void showError(Throwable throwable);
void showEmpty();
void showCapturedImage();
void finishPickImages(List<Image> images);
}

View File

@@ -0,0 +1,12 @@
package com.esafirm.imagepicker.features;
public class IpCons {
public static final int MODE_SINGLE = 1;
public static final int MODE_MULTIPLE = 2;
public static final int MAX_LIMIT = 999;
public static final int RC_IMAGE_PICKER = 0x229;
static final String EXTRA_SELECTED_IMAGES = "selectedImages";
}

View File

@@ -0,0 +1,15 @@
package com.esafirm.imagepicker.features;
/**
* Define the ImagePicker return behaviour
* NONE -> When image is picked, ImagePickerActivity will not dismissed even in Single Mode
* ALL -> When image is picked dismiss then deliver result
* CAMERA_ONLY -> When image is picked with Camera, dismiss then deliver the result
* GALLERY_ONLY -> Same as CAMERA_ONLY but with Gallery
*/
public enum ReturnMode {
NONE,
ALL,
CAMERA_ONLY,
GALLERY_ONLY
}

View File

@@ -0,0 +1,22 @@
package com.esafirm.imagepicker.features.camera;
import android.content.Context;
import android.content.Intent;
import android.provider.MediaStore;
import android.widget.Toast;
import com.esafirm.imagepicker.R;
public class CameraHelper {
public static boolean checkCameraAvailability(Context context) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
boolean isAvailable = intent.resolveActivity(context.getPackageManager()) != null;
if (!isAvailable) {
Context appContext = context.getApplicationContext();
Toast.makeText(appContext,
appContext.getString(R.string.ef_error_no_camera), Toast.LENGTH_LONG).show();
}
return isAvailable;
}
}

View File

@@ -0,0 +1,12 @@
package com.esafirm.imagepicker.features.camera;
import android.content.Context;
import android.content.Intent;
import com.esafirm.imagepicker.features.common.BaseConfig;
public interface CameraModule {
Intent getCameraIntent(Context context, BaseConfig config);
void getImage(Context context, Intent intent, OnImageReadyListener imageReadyListener);
void removeImage();
}

View File

@@ -0,0 +1,86 @@
package com.esafirm.imagepicker.features.camera;
import android.content.Context;
import android.content.Intent;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.provider.MediaStore;
import androidx.core.content.FileProvider;
import com.esafirm.imagepicker.features.ImagePickerConfigFactory;
import com.esafirm.imagepicker.features.common.BaseConfig;
import com.esafirm.imagepicker.helper.ImagePickerUtils;
import com.esafirm.imagepicker.helper.IpLogger;
import com.esafirm.imagepicker.model.ImageFactory;
import java.io.File;
import java.io.Serializable;
import java.util.Locale;
public class DefaultCameraModule implements CameraModule, Serializable {
private String currentImagePath;
public Intent getCameraIntent(Context context) {
return getCameraIntent(context, ImagePickerConfigFactory.createDefault());
}
@Override
public Intent getCameraIntent(Context context, BaseConfig config) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File imageFile = ImagePickerUtils.createImageFile(config.getImageDirectory());
if (imageFile != null) {
Context appContext = context.getApplicationContext();
String providerName = String.format(Locale.ENGLISH, "%s%s", appContext.getPackageName(), ".imagepicker.provider");
Uri uri = FileProvider.getUriForFile(appContext, providerName, imageFile);
currentImagePath = "file:" + imageFile.getAbsolutePath();
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
ImagePickerUtils.grantAppPermission(context, intent, uri);
return intent;
}
return null;
}
@Override
public void getImage(final Context context, Intent intent, final OnImageReadyListener imageReadyListener) {
if (imageReadyListener == null) {
throw new IllegalStateException("OnImageReadyListener must not be null");
}
if (currentImagePath == null) {
IpLogger.getInstance().w("currentImagePath null. " +
"This happen if you haven't call #getCameraIntent() or the activity is being recreated");
imageReadyListener.onImageReady(null);
return;
}
final Uri imageUri = Uri.parse(currentImagePath);
if (imageUri != null) {
MediaScannerConnection.scanFile(context.getApplicationContext(),
new String[]{imageUri.getPath()}, null, (path, uri) -> {
IpLogger.getInstance().d("File " + path + " was scanned successfully: " + uri);
if (path == null) {
IpLogger.getInstance().d("This should not happen, go back to Immediate implemenation");
path = currentImagePath;
}
imageReadyListener.onImageReady(ImageFactory.singleListFromPath(path));
ImagePickerUtils.revokeAppPermission(context, imageUri);
});
}
}
@Override
public void removeImage() {
if (currentImagePath != null) {
File file = new File(currentImagePath);
if (file.exists()) {
file.delete();
}
}
}
}

View File

@@ -0,0 +1,9 @@
package com.esafirm.imagepicker.features.camera;
import com.esafirm.imagepicker.model.Image;
import java.util.List;
public interface OnImageReadyListener {
void onImageReady(List<Image> image);
}

View File

@@ -0,0 +1,37 @@
package com.esafirm.imagepicker.features.cameraonly;
import android.os.Parcel;
import com.esafirm.imagepicker.features.common.BaseConfig;
public class CameraOnlyConfig extends BaseConfig {
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
}
public CameraOnlyConfig() {
}
private CameraOnlyConfig(Parcel in) {
super(in);
}
public static final Creator<CameraOnlyConfig> CREATOR = new Creator<CameraOnlyConfig>() {
@Override
public CameraOnlyConfig createFromParcel(Parcel source) {
return new CameraOnlyConfig(source);
}
@Override
public CameraOnlyConfig[] newArray(int size) {
return new CameraOnlyConfig[size];
}
};
}

View File

@@ -0,0 +1,56 @@
package com.esafirm.imagepicker.features.cameraonly;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import com.esafirm.imagepicker.features.ImagePickerActivity;
import com.esafirm.imagepicker.features.ImagePickerConfigFactory;
import com.esafirm.imagepicker.features.IpCons;
import androidx.fragment.app.Fragment;
public class ImagePickerCameraOnly {
private CameraOnlyConfig config = ImagePickerConfigFactory.createCameraDefault();
public ImagePickerCameraOnly imageDirectory(String directory) {
config.setImageDirectory(directory);
return this;
}
public ImagePickerCameraOnly imageFullDirectory(String fullPath) {
config.setImageFullDirectory(fullPath);
return this;
}
public void start(Activity activity) {
start(activity, IpCons.RC_IMAGE_PICKER);
}
public void start(Activity activity, int requestCode) {
activity.startActivityForResult(getIntent(activity), requestCode);
}
public void start(Fragment fragment) {
start(fragment, IpCons.RC_IMAGE_PICKER);
}
public void start(Fragment fragment, int requestCode) {
fragment.startActivityForResult(getIntent(fragment.getActivity()), requestCode);
}
public void start(android.app.Fragment fragment) {
start(fragment, IpCons.RC_IMAGE_PICKER);
}
public void start(android.app.Fragment fragment, int requestCode) {
fragment.startActivityForResult(getIntent(fragment.getActivity()), requestCode);
}
public Intent getIntent(Context context) {
Intent intent = new Intent(context, ImagePickerActivity.class);
intent.putExtra(CameraOnlyConfig.class.getSimpleName(), config);
return intent;
}
}

View File

@@ -0,0 +1,69 @@
package com.esafirm.imagepicker.features.common;
import android.os.Parcel;
import android.os.Parcelable;
import com.esafirm.imagepicker.features.ImagePickerSavePath;
import com.esafirm.imagepicker.features.ReturnMode;
public class BaseConfig implements Parcelable {
private ImagePickerSavePath savePath;
private ReturnMode returnMode;
public ReturnMode getReturnMode() {
return returnMode;
}
public ImagePickerSavePath getImageDirectory() {
return savePath;
}
public void setSavePath(ImagePickerSavePath savePath) {
this.savePath = savePath;
}
public void setImageDirectory(String dirName) {
savePath = new ImagePickerSavePath(dirName, false);
}
public void setImageFullDirectory(String path) {
savePath = new ImagePickerSavePath(path, true);
}
public void setReturnMode(ReturnMode returnMode) {
this.returnMode = returnMode;
}
public BaseConfig() {
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(this.savePath, flags);
dest.writeInt(this.returnMode == null ? -1 : this.returnMode.ordinal());
}
protected BaseConfig(Parcel in) {
this.savePath = in.readParcelable(ImagePickerSavePath.class.getClassLoader());
int tmpReturnMode = in.readInt();
this.returnMode = tmpReturnMode == -1 ? null : ReturnMode.values()[tmpReturnMode];
}
public static final Creator<BaseConfig> CREATOR = new Creator<BaseConfig>() {
@Override
public BaseConfig createFromParcel(Parcel source) {
return new BaseConfig(source);
}
@Override
public BaseConfig[] newArray(int size) {
return new BaseConfig[size];
}
};
}

View File

@@ -0,0 +1,22 @@
package com.esafirm.imagepicker.features.common;
public class BasePresenter<T extends MvpView> {
private T view;
public void attachView(T view) {
this.view = view;
}
public T getView() {
return view;
}
public void detachView() {
view = null;
}
protected boolean isViewAttached() {
return view != null;
}
}

View File

@@ -0,0 +1,11 @@
package com.esafirm.imagepicker.features.common;
import com.esafirm.imagepicker.model.Folder;
import com.esafirm.imagepicker.model.Image;
import java.util.List;
public interface ImageLoaderListener {
void onImageLoaded(List<Image> images, List<Folder> folders);
void onFailed(Throwable throwable);
}

View File

@@ -0,0 +1,4 @@
package com.esafirm.imagepicker.features.common;
public interface MvpView {
}

View File

@@ -0,0 +1,27 @@
package com.esafirm.imagepicker.features.imageloader;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
import com.bumptech.glide.request.RequestOptions;
import com.esafirm.imagepicker.R;
public class DefaultImageLoader implements ImageLoader {
@Override
public void loadImage(String path, ImageView imageView, ImageType imageType) {
Glide.with(imageView.getContext())
.load(path)
.apply(RequestOptions
.placeholderOf(imageType == ImageType.FOLDER
? R.drawable.ef_folder_placeholder
: R.drawable.ef_image_placeholder)
.error(imageType == ImageType.FOLDER
? R.drawable.ef_folder_placeholder
: R.drawable.ef_image_placeholder)
)
.transition(DrawableTransitionOptions.withCrossFade())
.into(imageView);
}
}

View File

@@ -0,0 +1,9 @@
package com.esafirm.imagepicker.features.imageloader;
import android.widget.ImageView;
import java.io.Serializable;
public interface ImageLoader extends Serializable {
void loadImage(String path, ImageView imageView, ImageType imageType);
}

View File

@@ -0,0 +1,6 @@
package com.esafirm.imagepicker.features.imageloader;
public enum ImageType {
FOLDER,
GALLERY
}

View File

@@ -0,0 +1,202 @@
package com.esafirm.imagepicker.features.recyclers;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Parcelable;
import android.widget.Toast;
import com.esafirm.imagepicker.R;
import com.esafirm.imagepicker.adapter.FolderPickerAdapter;
import com.esafirm.imagepicker.adapter.ImagePickerAdapter;
import com.esafirm.imagepicker.features.ClickUtils;
import com.esafirm.imagepicker.features.ImagePickerConfig;
import com.esafirm.imagepicker.features.ReturnMode;
import com.esafirm.imagepicker.features.imageloader.ImageLoader;
import com.esafirm.imagepicker.helper.ConfigUtils;
import com.esafirm.imagepicker.helper.ImagePickerUtils;
import com.esafirm.imagepicker.listeners.OnFolderClickListener;
import com.esafirm.imagepicker.listeners.OnImageClickListener;
import com.esafirm.imagepicker.listeners.OnImageSelectedListener;
import com.esafirm.imagepicker.model.Folder;
import com.esafirm.imagepicker.model.Image;
import com.esafirm.imagepicker.view.GridSpacingItemDecoration;
import java.util.ArrayList;
import java.util.List;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import static com.esafirm.imagepicker.features.IpCons.MAX_LIMIT;
import static com.esafirm.imagepicker.features.IpCons.MODE_MULTIPLE;
import static com.esafirm.imagepicker.features.IpCons.MODE_SINGLE;
public class RecyclerViewManager {
private final Context context;
private final RecyclerView recyclerView;
private final ImagePickerConfig config;
private GridLayoutManager layoutManager;
private GridSpacingItemDecoration itemOffsetDecoration;
private ImagePickerAdapter imageAdapter;
private FolderPickerAdapter folderAdapter;
private Parcelable foldersState;
private int imageColumns;
private int folderColumns;
public RecyclerViewManager(RecyclerView recyclerView, ImagePickerConfig config, int orientation) {
this.recyclerView = recyclerView;
this.config = config;
this.context = recyclerView.getContext();
changeOrientation(orientation);
}
public void onRestoreState(Parcelable recyclerState) {
layoutManager.onRestoreInstanceState(recyclerState);
}
public Parcelable getRecyclerState() {
return layoutManager.onSaveInstanceState();
}
/**
* Set item size, column size base on the screen orientation
*/
public void changeOrientation(int orientation) {
imageColumns = orientation == Configuration.ORIENTATION_PORTRAIT ? 3 : 5;
folderColumns = orientation == Configuration.ORIENTATION_PORTRAIT ? 2 : 4;
boolean shouldShowFolder = config.isFolderMode() && isDisplayingFolderView();
int columns = shouldShowFolder ? folderColumns : imageColumns;
layoutManager = new GridLayoutManager(context, columns);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
setItemDecoration(columns);
}
public void setupAdapters(ArrayList<Image> selectedImages, OnImageClickListener onImageClickListener, OnFolderClickListener onFolderClickListener) {
if (config.getMode() == MODE_SINGLE && selectedImages != null && selectedImages.size() > 1) {
selectedImages = null;
}
/* Init folder and image adapter */
final ImageLoader imageLoader = config.getImageLoader();
imageAdapter = new ImagePickerAdapter(context, imageLoader, selectedImages, onImageClickListener);
folderAdapter = new FolderPickerAdapter(context, imageLoader, bucket -> {
foldersState = recyclerView.getLayoutManager().onSaveInstanceState();
onFolderClickListener.onFolderClick(bucket);
});
}
private void setItemDecoration(int columns) {
if (itemOffsetDecoration != null) {
recyclerView.removeItemDecoration(itemOffsetDecoration);
}
itemOffsetDecoration = new GridSpacingItemDecoration(
columns,
context.getResources().getDimensionPixelSize(R.dimen.ef_item_padding),
false
);
recyclerView.addItemDecoration(itemOffsetDecoration);
layoutManager.setSpanCount(columns);
}
// Returns true if a back action was handled by going back a folder; false otherwise.
public boolean handleBack() {
if (config.isFolderMode() && !isDisplayingFolderView()) {
setFolderAdapter(null);
return true;
}
return false;
}
private boolean isDisplayingFolderView() {
return recyclerView.getAdapter() == null || recyclerView.getAdapter() instanceof FolderPickerAdapter;
}
public String getTitle() {
if (isDisplayingFolderView()) {
return ConfigUtils.getFolderTitle(context, config);
}
if (config.getMode() == MODE_SINGLE) {
return ConfigUtils.getImageTitle(context, config);
}
final int imageSize = imageAdapter.getSelectedImages().size();
final boolean useDefaultTitle = !ImagePickerUtils.isStringEmpty(config.getImageTitle()) && imageSize == 0;
if (useDefaultTitle) {
return ConfigUtils.getImageTitle(context, config);
}
return config.getLimit() == MAX_LIMIT
? String.format(context.getString(R.string.ef_selected), imageSize)
: String.format(context.getString(R.string.ef_selected_with_limit), imageSize, config.getLimit());
}
public void setImageAdapter(List<Image> images) {
imageAdapter.setData(images);
setItemDecoration(imageColumns);
recyclerView.setAdapter(imageAdapter);
}
public void setFolderAdapter(List<Folder> folders) {
folderAdapter.setData(folders);
setItemDecoration(folderColumns);
recyclerView.setAdapter(folderAdapter);
if (foldersState != null) {
layoutManager.setSpanCount(folderColumns);
recyclerView.getLayoutManager().onRestoreInstanceState(foldersState);
}
}
/* --------------------------------------------------- */
/* > Images */
/* --------------------------------------------------- */
private void checkAdapterIsInitialized() {
if (imageAdapter == null) {
throw new IllegalStateException("Must call setupAdapters first!");
}
}
public List<Image> getSelectedImages() {
checkAdapterIsInitialized();
return imageAdapter.getSelectedImages();
}
public void setImageSelectedListener(OnImageSelectedListener listener) {
checkAdapterIsInitialized();
imageAdapter.setImageSelectedListener(listener);
}
public boolean selectImage(boolean isSelected) {
if (config.getMode() == MODE_MULTIPLE) {
if (imageAdapter.getSelectedImages().size() >= config.getLimit() && !isSelected) {
if(ClickUtils.onclickTimes()){
Toast.makeText(context, "已达到最多选择照片数量", Toast.LENGTH_SHORT).show();
return false;
}else{
return false;
}
}
} else if (config.getMode() == MODE_SINGLE) {
if (imageAdapter.getSelectedImages().size() > 0) {
imageAdapter.removeAllSelectedSingleClick();
}
}
return true;
}
public boolean isShowDoneButton() {
return !isDisplayingFolderView()
&& !imageAdapter.getSelectedImages().isEmpty()
&& (config.getReturnMode() != ReturnMode.ALL && config.getReturnMode() != ReturnMode.GALLERY_ONLY);
}
}

View File

@@ -0,0 +1,60 @@
package com.esafirm.imagepicker.helper;
import android.content.Context;
import com.esafirm.imagepicker.R;
import com.esafirm.imagepicker.features.ImagePickerConfig;
import com.esafirm.imagepicker.features.IpCons;
import com.esafirm.imagepicker.features.ReturnMode;
import com.esafirm.imagepicker.features.common.BaseConfig;
import java.io.Serializable;
public class ConfigUtils {
public static ImagePickerConfig checkConfig(ImagePickerConfig config) {
if (config == null) {
throw new IllegalStateException("ImagePickerConfig cannot be null");
}
if (config.getMode() != IpCons.MODE_SINGLE
&& (config.getReturnMode() == ReturnMode.GALLERY_ONLY
|| config.getReturnMode() == ReturnMode.ALL)) {
throw new IllegalStateException("ReturnMode.GALLERY_ONLY and ReturnMode.ALL is only applicable in Single Mode!");
}
if (config.getImageLoader() != null && !(config.getImageLoader() instanceof Serializable)) {
throw new IllegalStateException("Custom image loader must be a class that implement ImageLoader." +
" This limitation due to Serializeable");
}
return config;
}
public static boolean shouldReturn(BaseConfig config, boolean isCamera) {
ReturnMode mode = config.getReturnMode();
if (isCamera) {
return mode == ReturnMode.ALL || mode == ReturnMode.CAMERA_ONLY;
} else {
return mode == ReturnMode.ALL || mode == ReturnMode.GALLERY_ONLY;
}
}
public static String getFolderTitle(Context context, ImagePickerConfig config) {
final String folderTitle = config.getFolderTitle();
return ImagePickerUtils.isStringEmpty(folderTitle)
? context.getString(R.string.ef_title_folder)
: folderTitle;
}
public static String getImageTitle(Context context, ImagePickerConfig config) {
final String configImageTitle = config.getImageTitle();
return ImagePickerUtils.isStringEmpty(configImageTitle)
? context.getString(R.string.ef_title_select_image)
: configImageTitle;
}
public static String getDoneButtonText(Context context, ImagePickerConfig config) {
final String doneButtonText = config.getDoneButtonText();
return ImagePickerUtils.isStringEmpty(doneButtonText)
? context.getString(R.string.ef_done)
: doneButtonText;
}
}

View File

@@ -0,0 +1,6 @@
package com.esafirm.imagepicker.helper;
import androidx.core.content.FileProvider;
public class ImagePickerFileProvider extends FileProvider {
}

View File

@@ -0,0 +1,35 @@
package com.esafirm.imagepicker.helper;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
public class ImagePickerPreferences {
public static final String PREF_WRITE_EXTERNAL_STORAGE_REQUESTED = "writeExternalRequested";
public static final String PREF_CAMERA_REQUESTED = "cameraRequested";
private Context context;
public ImagePickerPreferences(Context context) {
this.context = context;
}
/**
* Set a permission is requested
*/
public void setPermissionRequested(String permission) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean(permission, true);
editor.apply();
}
/**
* Check if a permission is requestted or not (false by default)
*/
public boolean isPermissionRequested(String permission) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
return preferences.getBoolean(permission, false);
}
}

View File

@@ -0,0 +1,107 @@
package com.esafirm.imagepicker.helper;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Environment;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;
import com.esafirm.imagepicker.features.ImagePickerSavePath;
import com.esafirm.imagepicker.model.Image;
import java.io.File;
import java.io.IOException;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import androidx.annotation.Nullable;
public class ImagePickerUtils {
public static boolean isStringEmpty(@Nullable String str) {
return str == null || str.length() == 0;
}
public static File createImageFile(ImagePickerSavePath savePath) {
// External sdcard location
final String path = savePath.getPath();
File mediaStorageDir = savePath.isFullPath()
? new File(path)
: new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), path);
// Create the storage directory if it does not exist
if (!mediaStorageDir.exists()) {
if (!mediaStorageDir.mkdirs()) {
IpLogger.getInstance().d("Oops! Failed create " + path);
return null;
}
}
// Create a media file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
String imageFileName = "IMG_" + timeStamp;
File imageFile = null;
try {
imageFile = File.createTempFile(imageFileName, ".jpg", mediaStorageDir);
} catch (IOException e) {
IpLogger.getInstance().d("Oops! Failed create " + imageFileName + " file");
}
return imageFile;
}
public static String getNameFromFilePath(String path) {
if (path.contains(File.separator)) {
return path.substring(path.lastIndexOf(File.separator) + 1);
}
return path;
}
public static void grantAppPermission(Context context, Intent intent, Uri fileUri) {
List<ResolveInfo> resolvedIntentActivities = context.getPackageManager()
.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolvedIntentInfo : resolvedIntentActivities) {
String packageName = resolvedIntentInfo.activityInfo.packageName;
context.grantUriPermission(packageName, fileUri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
public static void revokeAppPermission(Context context, Uri fileUri) {
context.revokeUriPermission(fileUri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
public static boolean isGifFormat(Image image) {
String extension = getExtension(image.getPath());
return extension.equalsIgnoreCase("gif");
}
public static boolean isVideoFormat(Image image) {
String extension = getExtension(image.getPath());
String mimeType = TextUtils.isEmpty(extension)
? URLConnection.guessContentTypeFromName(image.getPath())
: MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
return mimeType != null && mimeType.startsWith("video");
}
private static String getExtension(String path) {
String extension = MimeTypeMap.getFileExtensionFromUrl(path);
if (!TextUtils.isEmpty(extension)) {
return extension;
}
if (path.contains(".")) {
return path.substring(path.lastIndexOf(".") + 1, path.length());
} else {
return "";
}
}
}

View File

@@ -0,0 +1,7 @@
package com.esafirm.imagepicker.helper;
public class IpCrasher {
public static void openIssue() {
throw new IllegalStateException("This should not happen. Please open an issue!");
}
}

View File

@@ -0,0 +1,44 @@
package com.esafirm.imagepicker.helper;
import android.util.Log;
public class IpLogger {
private static final String TAG = "ImagePicker";
private static IpLogger INSTANCE;
private boolean isEnable = true;
public static IpLogger getInstance() {
if (INSTANCE == null) {
INSTANCE = new IpLogger();
}
return INSTANCE;
}
private IpLogger() {
}
public void setEnable(boolean enable) {
isEnable = enable;
}
public void d(String message) {
if (isEnable) {
Log.d(TAG, message);
}
}
public void e(String message) {
if (isEnable) {
Log.e(TAG, message);
}
}
public void w(String message) {
if (isEnable) {
Log.w(TAG, message);
}
}
}

View File

@@ -0,0 +1,39 @@
package com.esafirm.imagepicker.helper;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import java.util.Locale;
public class LocaleManager {
private static String language;
public static void setLanguange(String newLanguage) {
language = newLanguage;
}
private static String getLanguage() {
return language != null && !language.isEmpty()
? language
: Locale.getDefault().getLanguage();
}
public static Context updateResources(Context context) {
Locale locale = new Locale(getLanguage());
Locale.setDefault(locale);
Resources res = context.getResources();
Configuration config = new Configuration(res.getConfiguration());
if (Build.VERSION.SDK_INT >= 17) {
config.setLocale(locale);
context = context.createConfigurationContext(config);
} else {
config.locale = locale;
res.updateConfiguration(config, res.getDisplayMetrics());
}
return context;
}
}

View File

@@ -0,0 +1,36 @@
package com.esafirm.imagepicker.helper;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build;
import androidx.core.content.ContextCompat;
import android.view.View;
import android.view.ViewTreeObserver;
import com.esafirm.imagepicker.R;
public class ViewUtils {
public static Drawable getArrowIcon(Context context) {
final int backResourceId;
if (Build.VERSION.SDK_INT >= 17 && context.getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
// For right-to-left layouts, pick the drawable that points to the right (forward).
backResourceId = R.drawable.ef_ic_arrow_forward;
} else {
// For left-to-right layouts, pick the drawable that points to the left (back).
backResourceId = R.drawable.ef_ic_arrow_back;
}
return ContextCompat.getDrawable(context.getApplicationContext(), backResourceId);
}
public static void onPreDraw(final View view, final Runnable runnable) {
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
view.getViewTreeObserver().removeOnPreDrawListener(this);
runnable.run();
return false;
}
});
}
}

View File

@@ -0,0 +1,7 @@
package com.esafirm.imagepicker.listeners;
import com.esafirm.imagepicker.model.Folder;
public interface OnFolderClickListener {
void onFolderClick(Folder bucket);
}

View File

@@ -0,0 +1,5 @@
package com.esafirm.imagepicker.listeners;
public interface OnImageClickListener {
boolean onImageClick(boolean isSelected);
}

View File

@@ -0,0 +1,9 @@
package com.esafirm.imagepicker.listeners;
import com.esafirm.imagepicker.model.Image;
import java.util.List;
public interface OnImageSelectedListener {
void onSelectionUpdate(List<Image> selectedImage);
}

View File

@@ -0,0 +1,33 @@
package com.esafirm.imagepicker.model;
import java.util.ArrayList;
/**
* Created by boss1088 on 8/22/16.
*/
public class Folder {
private String folderName;
private ArrayList<Image> images;
public Folder(String bucket) {
folderName = bucket;
images = new ArrayList<>();
}
public String getFolderName() {
return folderName;
}
public void setFolderName(String folderName) {
this.folderName = folderName;
}
public ArrayList<Image> getImages() {
return images;
}
public void setImages(ArrayList<Image> images) {
this.images = images;
}
}

View File

@@ -0,0 +1,84 @@
package com.esafirm.imagepicker.model;
import android.os.Parcel;
import android.os.Parcelable;
public class Image implements Parcelable {
private long id;
private String name;
private String path;
public Image(long id, String name, String path) {
this.id = id;
this.name = name;
this.path = path;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Image image = (Image) o;
return image.getPath().equalsIgnoreCase(getPath());
}
/* --------------------------------------------------- */
/* > Parcelable */
/* --------------------------------------------------- */
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(this.id);
dest.writeString(this.name);
dest.writeString(this.path);
}
protected Image(Parcel in) {
this.id = in.readLong();
this.name = in.readString();
this.path = in.readString();
}
public static final Creator<Image> CREATOR = new Creator<Image>() {
@Override
public Image createFromParcel(Parcel source) {
return new Image(source);
}
@Override
public Image[] newArray(int size) {
return new Image[size];
}
};
}

View File

@@ -0,0 +1,15 @@
package com.esafirm.imagepicker.model;
import com.esafirm.imagepicker.helper.ImagePickerUtils;
import java.util.ArrayList;
import java.util.List;
public class ImageFactory {
public static List<Image> singleListFromPath(String path) {
List<Image> images = new ArrayList<>();
images.add(new Image(0, ImagePickerUtils.getNameFromFilePath(path), path));
return images;
}
}

View File

@@ -0,0 +1,43 @@
package com.esafirm.imagepicker.view;
import android.graphics.Rect;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
/**
* Created by hoanglam on 9/5/16.
*/
public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
private int spanCount;
private int spacing;
private boolean includeEdge;
public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
this.spanCount = spanCount;
this.spacing = spacing;
this.includeEdge = includeEdge;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
int column = position % spanCount;
if (includeEdge) {
outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)
if (position < spanCount) {
outRect.top = spacing;
}
outRect.bottom = spacing;
} else {
outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing)
if (position >= spanCount) {
outRect.top = spacing;
}
}
}
}

View File

@@ -0,0 +1,99 @@
package com.esafirm.imagepicker.view;
import android.content.Context;
import androidx.annotation.StringRes;
import androidx.core.view.ViewCompat;
import androidx.interpolator.view.animation.FastOutLinearInInterpolator;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Interpolator;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.esafirm.imagepicker.R;
public class SnackBarView extends RelativeLayout {
private static final int ANIM_DURATION = 200;
private static final Interpolator INTERPOLATOR = new FastOutLinearInInterpolator();
private TextView txtCaption;
private Button btnAction;
public SnackBarView(Context context) {
this(context, null);
}
public SnackBarView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SnackBarView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
View.inflate(getContext(), R.layout.ef_imagepikcer_snackbar, this);
if (isInEditMode()) {
return;
}
int height = getContext().getResources().getDimensionPixelSize(R.dimen.ef_height_snackbar);
ViewCompat.setTranslationY(this, height);
ViewCompat.setAlpha(this, 0f);
int padding = getContext().getResources().getDimensionPixelSize(R.dimen.ef_spacing_double);
setPadding(padding, 0, padding, 0);
txtCaption = (TextView) findViewById(R.id.ef_snackbar_txt_bottom_caption);
btnAction = (Button) findViewById(R.id.ef_snackbar_btn_action);
}
public void setText(@StringRes int textResId) {
txtCaption.setText(textResId);
}
public void setOnActionClickListener(@StringRes int textId, final OnClickListener onClickListener) {
if (textId == 0) {
textId = R.string.ef_ok;
}
btnAction.setText(textId);
btnAction.setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
hide(new Runnable() {
@Override
public void run() {
onClickListener.onClick(v);
}
});
}
});
}
public void show(@StringRes int textResId, OnClickListener onClickListener) {
setText(textResId);
setOnActionClickListener(0, onClickListener);
ViewCompat.animate(this)
.translationY(0f)
.setDuration(ANIM_DURATION)
.setInterpolator(INTERPOLATOR)
.alpha(1f);
}
public void hide() {
hide(null);
}
private void hide(Runnable runnable) {
ViewCompat.animate(this)
.translationY(getHeight())
.setDuration(ANIM_DURATION)
.alpha(0.5f)
.withEndAction(runnable);
}
}

View File

@@ -0,0 +1,27 @@
package com.esafirm.imagepicker.view;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
/**
* Created by hoanglam on 9/5/16.
*/
public class SquareFrameLayout extends FrameLayout {
public SquareFrameLayout(Context context) {
super(context);
}
public SquareFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SquareFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#ff888888"/>
</shape>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#ff888888"/>
</shape>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/toolbar"
layout="@layout/ef_imagepicker_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<FrameLayout
android:id="@+id/ef_imagepicker_fragment_placeholder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/toolbar" />
</RelativeLayout>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_empty_images"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="@dimen/ef_spacing_double"
android:text="@string/ef_msg_empty_images"
android:textSize="@dimen/ef_font_medium"
android:visibility="gone"/>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerInParent="true"
android:visibility="gone"/>
<com.esafirm.imagepicker.view.SnackBarView
android:id="@+id/ef_snackbar"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_alignBottom="@+id/recyclerView"
android:layout_alignParentBottom="true"
android:background="@color/ef_black_alpha_aa"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<com.esafirm.imagepicker.view.SquareFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:background="@drawable/ef_folder_placeholder"
android:contentDescription="@string/ef_content_desc_folder"
android:scaleType="centerCrop"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@color/ef_black_alpha_50"
android:orientation="vertical"
android:padding="@dimen/ef_padding_small">
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="3"
android:textColor="@color/ef_white"
android:textSize="@dimen/ef_font_small"/>
<TextView
android:id="@+id/tv_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:textColor="@color/ef_grey"
android:textSize="@dimen/ef_font_small"/>
</LinearLayout>
</com.esafirm.imagepicker.view.SquareFrameLayout>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<com.esafirm.imagepicker.view.SquareFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foregroundGravity="center">
<ImageView
android:id="@+id/image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/ef_content_desc_image"
android:scaleType="centerCrop"/>
<View
android:id="@+id/view_alpha"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"/>
<TextView
android:id="@+id/ef_item_file_type_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@color/ef_black_alpha_aa"
android:paddingBottom="@dimen/ef_spacing_half"
android:paddingLeft="@dimen/ef_spacing_double"
android:paddingRight="@dimen/ef_spacing_double"
android:paddingTop="@dimen/ef_spacing_half"
android:text="@string/ef_gif"
android:textColor="@color/ef_white"
android:textSize="@dimen/ef_font_small"
android:visibility="gone"
tools:visibility="visible"/>
</com.esafirm.imagepicker.view.SquareFrameLayout>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="?colorPrimary"
android:elevation="@dimen/ef_toolbar_elevation"
android:theme="@style/ef_CustomToolbarTheme"
app:contentInsetStart="0dp"
tools:ignore="UnusedAttribute"/>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="@dimen/ef_height_snackbar"
tools:parentTag="RelativeLayout">
<TextView
android:id="@+id/ef_snackbar_txt_bottom_caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/ef_snackbar_btn_action"
android:layout_toStartOf="@+id/ef_snackbar_btn_action"
android:maxLines="2"
android:textColor="@color/ef_white"
tools:text="There'es something about you that make me very happy and anxious at the same time"/>
<Button
android:id="@+id/ef_snackbar_btn_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="?selectableItemBackground"
android:textColor="?colorAccent"
tools:text="@string/ef_ok"/>
</merge>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<item
android:id="@+id/menu_camera"
android:icon="@drawable/ef_ic_camera_white"
android:title="@string/ef_camera"
app:showAsAction="always"
tools:ignore="AlwaysShowAction"/>
<item
android:id="@+id/menu_done"
android:title="@string/ef_done"
app:showAsAction="always"/>
</menu>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ef_permission_write_external_rationale">عليك إعطاء التطبيق الصلاحية لاستخدام الصور</string>
<string name="ef_ok">وافق</string>
<string name="ef_done">انهي</string>
<string name="ef_camera">كاميرا</string>
<string name="ef_title_folder">دليل</string>
<string name="ef_title_select_image">أنقر لإختيار الصور</string>
<string name="ef_ltitle_permission_denied">طلب الاذن مرفوض</string>
<string name="ef_selected">إخترت %d</string>
<string name="ef_selected_with_limit">إخترت %1$d/%2$d</string>
<string name="ef_error_create_image_file">حدث خطأ في تسجيل الصوره</string>
<string name="ef_error_no_camera">لا يوجد كاميرا</string>
<string name="ef_error_null_cursor">عذراً، هناك خطأ ما!</string>
<string name="ef_msg_empty_images">لم يتم العثور على صور</string>
<string name="ef_msg_no_write_external_permission">عليك إعطاء التطبيق الصلاحية لاستخدام الصور</string>
<string name="ef_msg_no_camera_permission">عليك إعطاء التطبيق الصلاحية لاستخدام الكاميرا لتتمكن من التصوير</string>
<string name="ef_msg_limit_images">تحديد إختيار الصور</string>
</resources>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ef_permission_write_external_rationale">Giv adgang til ekstern hukommelse, for at vælge billeder</string>
<string name="ef_ok">Ok</string>
<string name="ef_done">Færdig</string>
<string name="ef_camera">Kamera</string>
<string name="ef_title_folder">Mappe</string>
<string name="ef_title_select_image">Tryk for at vælge billeder</string>
<string name="ef_ltitle_permission_denied">Adgang nægtet</string>
<string name="ef_selected">%d valgt</string>
<string name="ef_selected_with_limit">%1$d/%2$d valgt</string>
<string name="ef_error_create_image_file">Fejl, kunne ikke oprette billedfilen</string>
<string name="ef_error_no_camera">Kamera blev ikke fundet</string>
<string name="ef_error_null_cursor">Beklager, der er sket en fejl!</string>
<string name="ef_msg_empty_images">Ingen billeder fundet</string>
<string name="ef_msg_no_write_external_permission">Giv tilladelse til at vælge billeder fra telefon hukkommelsen</string>
<string name="ef_msg_no_camera_permission">Giv tilladelse til kamera til at tage billeder</string>
<string name="ef_msg_limit_images">Der kan ikke vælges flere billeder</string>
<!--Don't Translate-->
<string name="ef_gif" translatable="false">GIF</string>
<string name="ef_video" translatable="false">Video</string>
<string name="ef_content_desc_folder" translatable="false">Folder</string>
<string name="ef_content_desc_image" translatable="false">Image</string>
</resources>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ef_permission_write_external_rationale">Erlaube den Zugriff auf den Speicher um Bilder auszuwählen</string>
<string name="ef_ok">OK</string>
<string name="ef_done">FERTIG</string>
<string name="ef_camera">KAMERA</string>
<string name="ef_title_folder">Verzeichnis</string>
<string name="ef_title_select_image">Tippen um Bilder auszuwählen</string>
<string name="ef_ltitle_permission_denied">Erlaubnis verweigert</string>
<string name="ef_selected">%d ausgewählt</string>
<string name="ef_selected_with_limit">%1$d/%2$d ausgewählt</string>
<string name="ef_error_create_image_file">Fehler beim Erstellen der Bilddatei</string>
<string name="ef_error_no_camera">Keine Kamera gefunden</string>
<string name="ef_error_null_cursor">Ups, da ist was schiefgegangen!</string>
<string name="ef_msg_empty_images">Keine Bilder gefunden</string>
<string name="ef_msg_no_write_external_permission">Bitte erlaube Zugriff auf den Speicher um Bilder auszuwählen</string>
<string name="ef_msg_no_camera_permission">Bitte erlaube Zugriff auf die Kamera um Bilder aufzunehmen</string>
<string name="ef_msg_limit_images">Maximale Anzahl Bilder erreicht</string>
<!--Don't Translate-->
<string name="ef_gif" translatable="false">GIF</string>
<string name="ef_video" translatable="false">Video</string>
<string name="ef_content_desc_folder" translatable="false">Folder</string>
<string name="ef_content_desc_image" translatable="false">Image</string>
</resources>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ef_permission_write_external_rationale">Autoriser l\'application à accéder au stockage externe pour afficher les images</string>
<string name="ef_ok">OK</string>
<string name="ef_done">TERMINER</string>
<string name="ef_camera">CAMERA</string>
<string name="ef_title_folder">Dossier</string>
<string name="ef_title_select_image">Toucher pour sélectionner des images</string>
<string name="ef_ltitle_permission_denied">Permission refusée</string>
<string name="ef_selected">%d sélectionnées</string>
<string name="ef_selected_with_limit">%1$d/%2$d sélectionnées</string>
<string name="ef_error_create_image_file">Impossible de créer le fichier photo</string>
<string name="ef_error_no_camera">Aucune caméra détectée</string>
<string name="ef_error_null_cursor">Oups, quelque chose s\'est mal déroulé!</string>
<string name="ef_msg_empty_images">Aucune image trouvée</string>
<string name="ef_msg_no_write_external_permission">L\'application nécessite l\'autorisation de stockage pour afficher les images</string>
<string name="ef_msg_no_camera_permission">L\'application nécessite l\'autorisation de la caméra</string>
<string name="ef_msg_limit_images">Limite du nombre d\'images</string>
<!--Don\'t Translate-->
<string name="ef_gif">GIF</string>
</resources>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ef_permission_write_external_rationale">Izinkan akses penyimpanan eksternal untuk memilih gambar</string>
<string name="ef_ok">OK</string>
<string name="ef_done">SELESAI</string>
<string name="ef_camera">KAMERA</string>
<string name="ef_title_folder">Folder</string>
<string name="ef_title_select_image">Ketuk untuk memilih gambar</string>
<string name="ef_ltitle_permission_denied">Izin ditolak</string>
<string name="ef_selected">%d terpilih</string>
<string name="ef_selected_with_limit">%1$d/%2$d terpilih</string>
<string name="ef_error_create_image_file">Gagal membuat berkas gambar</string>
<string name="ef_error_no_camera">Kamera tidak ditemukan</string>
<string name="ef_error_null_cursor">Ups, ada yang tidak beres!</string>
<string name="ef_msg_empty_images">Gambar tidak ditemukan</string>
<string name="ef_msg_no_write_external_permission">Mohon berikan izin terhadap penyimpanan untuk memilih gambar</string>
<string name="ef_msg_no_camera_permission">Mohon berikan izin terhadap kamera untuk mengambil gambar</string>
<string name="ef_msg_limit_images">Gambar yang terpilih sudah mencapai batas maksimal</string>
</resources>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ef_permission_write_external_rationale">Permetti l\'accesso allo storage locale per poter selezionare le immagini</string>
<string name="ef_ok">OK</string>
<string name="ef_done">FATTO</string>
<string name="ef_camera">CAMERA</string>
<string name="ef_title_folder">Cartella</string>
<string name="ef_title_select_image">Premi per selezionare l\'immagine</string>
<string name="ef_selected">%d selezionate</string>
<string name="ef_selected_with_limit">%1$d/%2$d selezionate</string>
<string name="ef_error_create_image_file">Si è verificato un problema nella creazione dell\'immagine</string>
<string name="ef_error_no_camera">Nessuna fotocamera trovata</string>
<string name="ef_error_null_cursor">Oops, qualcosa è andato storto!</string>
<string name="ef_msg_empty_images">Nessuna immagine trovata</string>
<string name="ef_msg_no_write_external_permission">Dai il permesso per poter selezionare le immagini</string>
<string name="ef_msg_no_camera_permission">Dai il permesso per poter scattare una foto</string>
<string name="ef_msg_limit_images">Hai raggiunto il limite massimo di selezione</string>
<string name="ef_ltitle_permission_denied">Permesso negato</string>
</resources>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ef_permission_write_external_rationale">이미지를 불러오기 위해 외부 저장공간 권한을 허용해주세요</string>
<string name="ef_ok">확인</string>
<string name="ef_done">완료</string>
<string name="ef_camera">카메라</string>
<string name="ef_title_folder">사진첩</string>
<string name="ef_title_select_image">이미지를 선택하세요</string>
<string name="ef_ltitle_permission_denied">권한이 없음</string>
<string name="ef_selected">%d 선택됨</string>
<string name="ef_selected_with_limit">%1$d/%2$d 선택됨</string>
<string name="ef_error_create_image_file">이미지 생성에 실패하였습니다.</string>
<string name="ef_error_no_camera">카메라를 찾을 수 없습니다.</string>
<string name="ef_error_null_cursor">알 수 없는 오류가 발생하였습니다.</string>
<string name="ef_msg_empty_images">이미지가 비어있음</string>
<string name="ef_msg_no_write_external_permission">이미지를 불러오기 위해 저장공간 권한을 허용해주세요</string>
<string name="ef_msg_no_camera_permission">사진을 찍기위해 카메라 권한을 허용해주세요</string>
<string name="ef_msg_limit_images">이미지 선택 제한 초과</string>
</resources>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ef_permission_write_external_rationale">Dê permissão ao armazenamento para selecionar as imagens</string>
<string name="ef_ok">OK</string>
<string name="ef_done">FEITO</string>
<string name="ef_camera">CAMERA</string>
<string name="ef_title_folder">Pasta</string>
<string name="ef_title_select_image">Toque para selecionar as imagens</string>
<string name="ef_ltitle_permission_denied">Permissão negada</string>
<string name="ef_selected">%d selecionadas</string>
<string name="ef_selected_with_limit">%1$d/%2$d selecionadas</string>
<string name="ef_error_create_image_file">Erro ao criar arquivo de imagem</string>
<string name="ef_error_no_camera">Câmera não encontrada</string>
<string name="ef_error_null_cursor">Ops, algo deu errado!</string>
<string name="ef_msg_empty_images">Nenhuma imagem encontrada</string>
<string name="ef_msg_no_write_external_permission">Por favor dê permissão ao armazenamento para selecionar as imagens</string>
<string name="ef_msg_no_camera_permission">Por favor dê permissão à câmera para tirar fotos</string>
<string name="ef_msg_limit_images">Limite de imagens atingido</string>
</resources>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ef_permission_write_external_rationale">Permite accesul de stocare extern pentru a selecta imagini</string>
<string name="ef_ok">OK</string>
<string name="ef_done">TERMINAT</string>
<string name="ef_camera">CAMERA</string>
<string name="ef_title_folder">Fișier</string>
<string name="ef_title_select_image">Atingeți pentru a selecta imagini</string>
<string name="ef_selected">%d selectate</string>
<string name="ef_selected_with_limit">%1$d/%2$d selectate</string>
<string name="ef_error_create_image_file">Nu am putut crea imaginea</string>
<string name="ef_error_no_camera">Camera foto nu a fost găsită</string>
<string name="ef_error_null_cursor">Hopa, ceva a mers prost!</string>
<string name="ef_msg_empty_images">Nu am găsit imaginea</string>
<string name="ef_msg_no_write_external_permission">Vă rugăm să acordați permisiunea de stocare pentru a selecta imagini</string>
<string name="ef_msg_no_camera_permission">Vă rugăm să acordați permisiunea fotocamerei pentru a captura imagini</string>
<string name="ef_msg_limit_images">Limita de selecție a imaginii depăşită</string>
<string name="ef_ltitle_permission_denied">Acces refuzat</string>
</resources>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ef_permission_write_external_rationale">Разрешите доступ к хранилищу, чтобы выбрать изображения</string>
<string name="ef_ok">ОК</string>
<string name="ef_done">ГОТОВО</string>
<string name="ef_camera">КАМЕРА</string>
<string name="ef_title_folder">Папка</string>
<string name="ef_title_select_image">Коснитесь, чтобы выбрать</string>
<string name="ef_ltitle_permission_denied">Доступ запрещён</string>
<string name="ef_selected">%d выбрано</string>
<string name="ef_selected_with_limit">%1$d/%2$d выбрано</string>
<string name="ef_error_create_image_file">Не удалось создать файл</string>
<string name="ef_error_no_camera">Камера недоступна</string>
<string name="ef_error_null_cursor">Ой! Что-то пошло не так!</string>
<string name="ef_msg_empty_images">Изображений не найдено</string>
<string name="ef_msg_no_write_external_permission">Пожалуйста, разрешите доступ к хранилищу, чтобы выбрать изображения</string>
<string name="ef_msg_no_camera_permission">Пожалуйста, разрешите доступ к камере, чтобы сделать снимок</string>
<string name="ef_msg_limit_images">Лимит выбора изображений</string>
</resources>

View File

@@ -0,0 +1,4 @@
<resources>
<dimen name="ef_font_small">15sp</dimen>
<dimen name="ef_font_medium">20sp</dimen>
</resources>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ef_ok">TAMAM</string>
<string name="ef_done">BİTTİ</string>
<string name="ef_camera">KAMERA</string>
<string name="ef_permission_write_external_rationale">Görüntüleri seçmek için harici depolama alanına erişime izin ver</string>
<string name="ef_title_folder">Klasör</string>
<string name="ef_title_select_image">Resimleri seçmek için hafifçe dokunun</string>
<string name="ef_ltitle_permission_denied">İzin reddedildi</string>
<string name="ef_selected">%d seçili</string>
<string name="ef_selected_with_limit">%1$d/%2$d seçili</string>
<string name="ef_error_create_image_file">Resim dosyası oluşturulamadı</string>
<string name="ef_error_no_camera">Kamera bulunamadı</string>
<string name="ef_error_null_cursor">Hoop! Birşeyler yanlış gitti!</string>
<string name="ef_msg_empty_images">Resimler bulunamadı</string>
<string name="ef_msg_no_write_external_permission">Lütfen resim seçmek için depolama iznini verin</string>
<string name="ef_msg_no_camera_permission">Resim çekmek için lütfen kamera iznini verin</string>
<string name="ef_msg_limit_images">Resim seçimi sınırı</string>
</resources>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ef_permission_write_external_rationale">Дозвольте доступ до сховища, щоб обрати зображення</string>
<string name="ef_ok">ОК</string>
<string name="ef_done">ГОТОВО</string>
<string name="ef_camera">КАМЕРА</string>
<string name="ef_title_folder">Папка</string>
<string name="ef_title_select_image">Торкніться, щоб обрати</string>
<string name="ef_ltitle_permission_denied">Доступ заборонено</string>
<string name="ef_selected">%d обрано</string>
<string name="ef_selected_with_limit">%1$d/%2$d обрано</string>
<string name="ef_error_create_image_file">Не вдалося створити файл</string>
<string name="ef_error_no_camera">Камера недоступна</string>
<string name="ef_error_null_cursor">Ой! Щось пішло не так!</string>
<string name="ef_msg_empty_images">Зображень не знайдено</string>
<string name="ef_msg_no_write_external_permission">Будь ласка, дозвольте доступ до сховища, щоб обрати зображення</string>
<string name="ef_msg_no_camera_permission">Будь ласка, дозвольте доступ до камери, щоб зробити знімок</string>
<string name="ef_msg_limit_images">Ліміт вибору зображень</string>
</resources>

View File

@@ -0,0 +1,24 @@
<resources>
<string name="ef_permission_write_external_rationale">请给予存取外部存储权限以选择照片</string>
<string name="ef_ok"></string>
<string name="ef_done">完成</string>
<string name="ef_camera">相机</string>
<string name="ef_title_folder">文件夹</string>
<string name="ef_title_select_image">选择照片</string>
<string name="ef_selected">已选择 %d</string>
<string name="ef_selected_with_limit">已选择 %1$d/%2$d</string>
<string name="ef_error_create_image_file">建立照片文件失败</string>
<string name="ef_error_no_camera">找不到相机</string>
<string name="ef_error_null_cursor">啊哦,发生错误!</string>
<string name="ef_msg_empty_images">找不到照片</string>
<string name="ef_msg_no_write_external_permission">请给予存取外部存储权限以选择照片</string>
<string name="ef_msg_no_camera_permission">请给予获取相机权限以拍摄照片</string>
<string name="ef_msg_limit_images">已达到最多选择照片数量</string>
<string name="ef_ltitle_permission_denied">没有权限</string>
</resources>

View File

@@ -0,0 +1,24 @@
<resources>
<string name="ef_permission_write_external_rationale">請允許存取外部存儲以選擇相片</string>
<string name="ef_ok"></string>
<string name="ef_done">完成</string>
<string name="ef_camera">相機</string>
<string name="ef_title_folder">檔案夹</string>
<string name="ef_title_select_image">選擇相片</string>
<string name="ef_selected">已選擇 %d</string>
<string name="ef_selected_with_limit">已選擇 %1$d/%2$d</string>
<string name="ef_error_create_image_file">建立相片檔案失敗</string>
<string name="ef_error_no_camera">找不到相機</string>
<string name="ef_error_null_cursor">噢,發生問題</string>
<string name="ef_msg_empty_images">找不到相片</string>
<string name="ef_msg_no_write_external_permission">請允許存取外部存儲以選擇相片</string>
<string name="ef_msg_no_camera_permission">請允許存取相機以拍攝相片</string>
<string name="ef_msg_limit_images">已達到最多選擇相片數量</string>
<string name="ef_ltitle_permission_denied">沒有權限</string>
</resources>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<color name="ef_colorPrimary" tools:override="true">#3F51B5</color>
<color name="ef_colorPrimaryDark" tools:override="true">#303F9F</color>
<color name="ef_colorAccent" tools:override="true">#FF4081</color>
<color name="ef_colorTextPrimary" tools:override="true">#FFFFFF</color>
<color name="ef_white">#FFFFFF</color>
<color name="ef_grey">#9E9E9E</color>
<color name="ef_black_alpha_50">#80000000</color>
<color name="ef_black_alpha_aa">#AA000000</color>
</resources>

View File

@@ -0,0 +1,15 @@
<resources>
<dimen name="ef_item_padding">1dp</dimen>
<dimen name="ef_padding_small">5dp</dimen>
<dimen name="ef_spacing_half">4dp</dimen>
<dimen name="ef_spacing_double">16dp</dimen>
<dimen name="ef_font_small">12sp</dimen>
<dimen name="ef_font_medium">16sp</dimen>
<dimen name="ef_height_snackbar">80dp</dimen>
<dimen name="ef_toolbar_elevation">6dp</dimen>
</resources>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ef_ok">OK</string>
<string name="ef_done">DONE</string>
<string name="ef_camera">CAMERA</string>
<string name="ef_title_folder">Folder</string>
<string name="ef_title_select_image">Tap to select images</string>
<string name="ef_selected">%d selected</string>
<string name="ef_selected_with_limit">%1$d/%2$d selected</string>
<string name="ef_error_create_image_file">Failed to create image file</string>
<string name="ef_error_no_camera">No camera found</string>
<string name="ef_msg_empty_images">No images found</string>
<string name="ef_msg_no_write_external_permission">Please grant storage permission to select images</string>
<string name="ef_msg_no_camera_permission">Please grant camera permission to capture image</string>
<!--Don't Translate-->
<string name="ef_gif" translatable="false">GIF</string>
<string name="ef_video" translatable="false">Video</string>
<string name="ef_content_desc_folder" translatable="false">Folder</string>
<string name="ef_content_desc_image" translatable="false">Image</string>
</resources>

View File

@@ -0,0 +1,20 @@
<resources>
<style name="ef_BaseTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="windowActionModeOverlay">true</item>
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="ef_AppTheme" parent="ef_BaseTheme">
<item name="colorPrimary">@color/ef_colorPrimary</item>
<item name="colorPrimaryDark">@color/ef_colorPrimaryDark</item>
<item name="colorAccent">@color/ef_colorAccent</item>
</style>
<style name="ef_CustomToolbarTheme" parent="Base.ThemeOverlay.AppCompat.ActionBar">
<item name="android:textColorPrimary">@color/ef_colorTextPrimary</item>
<item name="actionMenuTextColor">@color/ef_colorTextPrimary</item>
</style>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="."/>
</paths>

View File

@@ -0,0 +1,15 @@
package com.esafirm.imagepicker;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* To work on unit tests, switch the Test Artifact in the Build Variants view.
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}