Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(android): implement Android Photo Picker #14131

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ dependencies {
// exclude group: 'com.google.android.gms'
}

implementation "androidx.activity:activity:1.6.0"
implementation "androidx.appcompat:appcompat:${project.ext.tiAndroidXAppCompatLibVersion}"
implementation "com.google.android.material:material:${project.ext.tiMaterialLibVersion}"
}
263 changes: 130 additions & 133 deletions android/modules/media/src/java/ti/modules/titanium/media/MediaModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import org.appcelerator.kroll.KrollDict;
import org.appcelerator.kroll.KrollFunction;
import org.appcelerator.kroll.KrollModule;
import org.appcelerator.kroll.KrollObject;
import org.appcelerator.kroll.KrollPromise;
import org.appcelerator.kroll.KrollProxy;
import org.appcelerator.kroll.annotations.Kroll;
import org.appcelerator.kroll.common.Log;
import org.appcelerator.titanium.ContextSpecific;
Expand All @@ -43,11 +45,13 @@
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
Expand All @@ -59,6 +63,7 @@
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
Expand All @@ -67,7 +72,11 @@
import android.util.Size;
import android.view.Window;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.PickVisualMediaRequest;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.camera.core.AspectRatio;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;

@Kroll.module
@ContextSpecific
Expand Down Expand Up @@ -242,11 +251,18 @@ public class MediaModule extends KrollModule implements Handler.Callback
private static ContentResolver contentResolver;
private boolean useCameraX = false;
private static boolean pathOnly = false;
ActivityResultLauncher<PickVisualMediaRequest> pickMedia;
private final IntentFilter mIntentFilter;

KrollFunction fSuccessCallback;
KrollFunction fCancelCallback;
KrollFunction fErrorCallback;

public MediaModule()
{
super();

mIntentFilter = new IntentFilter();
mIntentFilter.addAction("image");
if (contentResolver == null) {
contentResolver = TiApplication.getInstance().getContentResolver();
}
Expand Down Expand Up @@ -965,7 +981,7 @@ public void hideCamera()
}

/**
* @see org.appcelerator.kroll.KrollProxy#handleMessage(android.os.Message)
* @see KrollProxy#handleMessage(Message)
*/
@Override
public boolean handleMessage(Message message)
Expand Down Expand Up @@ -1105,9 +1121,9 @@ public void openPhotoGallery(KrollDict options)
errorCallback = (KrollFunction) options.get(TiC.EVENT_ERROR);
}

final KrollFunction fSuccessCallback = successCallback;
final KrollFunction fCancelCallback = cancelCallback;
final KrollFunction fErrorCallback = errorCallback;
fSuccessCallback = successCallback;
fCancelCallback = cancelCallback;
fErrorCallback = errorCallback;

Log.d(TAG, "openPhotoGallery called", Log.DEBUG_MODE);

Expand Down Expand Up @@ -1180,137 +1196,43 @@ public void openPhotoGallery(KrollDict options)

final int code = allowMultiple ? PICK_IMAGE_MULTIPLE : PICK_IMAGE_SINGLE;

activitySupport.launchActivityForResult(galleryIntent.getIntent(), code, new TiActivityResultHandler() {
@Override
public void onResult(Activity activity, int requestCode, int resultCode, Intent data)
{
if (requestCode != code) {
return;
}

// Do not continue if no selection was made.
Log.d(TAG, "OnResult called: " + resultCode, Log.DEBUG_MODE);
if ((resultCode == Activity.RESULT_CANCELED) || (data == null)) {
if (fCancelCallback != null) {
KrollDict response = new KrollDict();
response.putCodeAndMessage(NO_ERROR, null);
fCancelCallback.callAsync(getKrollObject(), response);
}
return;
}
LocalBroadcastManager.getInstance(TiApplication.getAppRootOrCurrentActivity())
.registerReceiver(new LocalBroadcastReceiver(), mIntentFilter);

// Fetch a URI to file selected. (Only applicable to single file selection.)
Uri uri = data.getData();
String path = (uri != null) ? uri.toString() : null;

// Handle multiple file selection, if enabled.
if (requestCode == PICK_IMAGE_MULTIPLE) {
// Wrap all selected file(s) in Titanium "CameraMediaItemType" dictionaries.
ArrayList<KrollDict> selectedFiles = new ArrayList<>();
ClipData clipData = data.getClipData();
if (clipData != null) {
// Fetch file(s) from clip data.
int count = clipData.getItemCount();
for (int index = 0; index < count; index++) {
ClipData.Item item = clipData.getItemAt(index);
if ((item == null) || (item.getUri() == null)) {
continue;
}
KrollDict dictionary = createDictForImage(item.getUri().toString());
if (dictionary == null) {
continue;
}
selectedFiles.add(dictionary);
}
} else if (path != null) {
// Only a single file was found.
KrollDict dictionary = createDictForImage(path);
if (dictionary != null) {
selectedFiles.add(dictionary);
}
}

// Copy each selected file to either an "images" or "videos" collection.
ArrayList<KrollDict> selectedImages = new ArrayList<>();
ArrayList<KrollDict> selectedVideos = new ArrayList<>();
for (KrollDict dictionary : selectedFiles) {
String mediaType = dictionary.getString("mediaType");
if (mediaType != null) {
if (mediaType.equals(MEDIA_TYPE_PHOTO)) {
selectedImages.add(dictionary);
} else if (mediaType.equals(MEDIA_TYPE_VIDEO)) {
selectedVideos.add(dictionary);
}
}
}

// Invoke a callback with the selection result.
if (selectedImages.isEmpty() && selectedVideos.isEmpty()) {
if (selectedFiles.isEmpty()) {
// Invoke the "cancel" callback if no files were selected.
if (fCancelCallback != null) {
KrollDict response = new KrollDict();
response.putCodeAndMessage(NO_ERROR, null);
fCancelCallback.callAsync(getKrollObject(), response);
}
} else {
// Invoke the "error" callback if non-image/video files were selected.
String message = "Invalid file types were selected";
Log.e(TAG, message);
if (fErrorCallback != null) {
fErrorCallback.callAsync(getKrollObject(),
createErrorResponse(UNKNOWN_ERROR, message));
}
}
} else {
// Invoke the "success" callback with the selected file(s).
if (fSuccessCallback != null) {
KrollDict d = new KrollDict();
d.putCodeAndMessage(NO_ERROR, null);
d.put("images", selectedImages.toArray(new KrollDict[0]));
d.put("videos", selectedVideos.toArray(new KrollDict[0]));
fSuccessCallback.callAsync(getKrollObject(), d);
}
}
return;
}

// Handle single file selection.
try {
//Check for invalid path
if (path == null) {
String msg = "File path is invalid";
Log.e(TAG, msg);
if (fErrorCallback != null) {
fErrorCallback.callAsync(getKrollObject(), createErrorResponse(UNKNOWN_ERROR, msg));
}
return;
}
if (fSuccessCallback != null) {
fSuccessCallback.callAsync(getKrollObject(), createDictForImage(path));
}
} catch (OutOfMemoryError e) {
String msg = "Not enough memory to get image: " + e.getMessage();
Log.e(TAG, msg);
if (fErrorCallback != null) {
fErrorCallback.callAsync(getKrollObject(), createErrorResponse(UNKNOWN_ERROR, msg));
}
}
if (isSelectingPhoto && isSelectingVideo) {
// photo and video
if (allowMultiple) {
((TiBaseActivity) activity).pickMultipleMediaResult.launch(new PickVisualMediaRequest.Builder()
.setMediaType(ActivityResultContracts.PickVisualMedia.ImageAndVideo.INSTANCE)
.build());
} else {
((TiBaseActivity) activity).pickMediaResult.launch(new PickVisualMediaRequest.Builder()
.setMediaType(ActivityResultContracts.PickVisualMedia.ImageAndVideo.INSTANCE)
.build());
}

@Override
public void onError(Activity activity, int requestCode, Exception e)
{
if (requestCode != code) {
return;
}
String msg = "Gallery problem: " + e.getMessage();
Log.e(TAG, msg, e);
if (fErrorCallback != null) {
fErrorCallback.callAsync(getKrollObject(), createErrorResponse(UNKNOWN_ERROR, msg));
}
} else if (isSelectingPhoto) {
// photo
if (allowMultiple) {
((TiBaseActivity) activity).pickMultipleMediaResult.launch(new PickVisualMediaRequest.Builder()
.setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE)
.build());
} else {
((TiBaseActivity) activity).pickMediaResult.launch(new PickVisualMediaRequest.Builder()
.setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE)
.build());
}
});
} else {
// video
if (allowMultiple) {
((TiBaseActivity) activity).pickMultipleMediaResult.launch(new PickVisualMediaRequest.Builder()
.setMediaType(ActivityResultContracts.PickVisualMedia.VideoOnly.INSTANCE)
.build());
} else {
((TiBaseActivity) activity).pickMediaResult.launch(new PickVisualMediaRequest.Builder()
.setMediaType(ActivityResultContracts.PickVisualMedia.VideoOnly.INSTANCE)
.build());
}
}
}

protected static KrollDict createDictForImage(String path)
Expand Down Expand Up @@ -1768,4 +1690,79 @@ public String getApiName()
{
return "Ti.Media";
}

public class LocalBroadcastReceiver extends BroadcastReceiver
{
@Override
public void onReceive(Context context, Intent intent)
{
KrollDict kd = new KrollDict();
String action = intent.getAction();
if (action.equals("image")) {
Bundle extras = intent.getExtras();
if (extras.get("uri") != null) {
// single image
Uri uri = (Uri) extras.get("uri");
String path = (uri != null) ? uri.toString() : null;

if (fSuccessCallback != null && path != null) {
fSuccessCallback.callAsync(getKrollObject(), createDictForImage(path));
}
} else if (extras.get("uris") != null) {
// multiple images
List<Uri> uri = (List<Uri>) extras.get("uris");

ArrayList<KrollDict> selectedFiles = new ArrayList<>();
// Fetch file(s) from clip data.
int count = uri.size();
for (int index = 0; index < count; index++) {
KrollDict dictionary = createDictForImage(uri.get(index).toString());
if (dictionary == null) {
continue;
}
selectedFiles.add(dictionary);
}

ArrayList<KrollDict> selectedImages = new ArrayList<>();
for (KrollDict dictionary : selectedFiles) {
String mediaType = dictionary.getString("mediaType");
if (mediaType != null) {
if (mediaType.equals(MEDIA_TYPE_PHOTO)) {
selectedImages.add(dictionary);
}
}
}

// Invoke a callback with the selection result.
if (selectedImages.isEmpty()) {
if (selectedFiles.isEmpty()) {
// Invoke the "cancel" callback if no files were selected.
if (fCancelCallback != null) {
KrollDict response = new KrollDict();
response.putCodeAndMessage(NO_ERROR, null);
fCancelCallback.callAsync(getKrollObject(), response);
}
} else {
// Invoke the "error" callback if non-image/video files were selected.
String message = "Invalid file types were selected";
Log.e(TAG, message);
if (fErrorCallback != null) {
fErrorCallback.callAsync(getKrollObject(),
createErrorResponse(UNKNOWN_ERROR, message));
}
}
} else {
// Invoke the "success" callback with the selected file(s).
if (fSuccessCallback != null) {
KrollDict d = new KrollDict();
d.putCodeAndMessage(NO_ERROR, null);
d.put("images", selectedImages.toArray(new KrollDict[0]));
fSuccessCallback.callAsync(getKrollObject(), d);
}
}
}
}
}

}
}
1 change: 1 addition & 0 deletions android/templates/build/app.build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ android {
tasks.lint.enabled = false

dependencies {
implementation "androidx.activity:activity:1.6.0"
implementation "androidx.appcompat:appcompat:${project.ext.tiAndroidXAppCompatLibVersion}"
implementation fileTree(dir: "${rootDir}/libs", include: ['*.aar', '*.jar'])
implementation fileTree(dir: "${projectDir}/src/main", include: ['*.aar', '*.jar'])
Expand Down
1 change: 1 addition & 0 deletions android/titanium/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ dependencies {
compileOnly project(':kroll-apt')

// AndroidX Library dependencies.
implementation "androidx.activity:activity:1.6.0"
implementation "androidx.appcompat:appcompat:${project.ext.tiAndroidXAppCompatLibVersion}"
implementation 'androidx.cardview:cardview:1.0.0'
implementation "androidx.core:core:${project.ext.tiAndroidXCoreLibVersion}"
Expand Down
Loading
Loading