mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
Merge camera into send flow.
This commit is contained in:
parent
eb1dd58a0b
commit
0a8bbf14a6
@ -439,11 +439,6 @@
|
||||
android:exported="true"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".camera.CameraActivity"
|
||||
android:theme="@style/TextSecure.ScribbleTheme"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<service android:enabled="true" android:name="org.thoughtcrime.securesms.service.WebRtcCallService"/>
|
||||
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
|
||||
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:interpolator="@android:anim/decelerate_interpolator">
|
||||
<translate
|
||||
android:duration="150"
|
||||
android:duration="250"
|
||||
android:fromXDelta="-100%"
|
||||
android:toXDelta="0%" />
|
||||
</set>
|
@ -3,7 +3,7 @@
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:interpolator="@android:anim/decelerate_interpolator">
|
||||
<translate
|
||||
android:duration="150"
|
||||
android:duration="250"
|
||||
android:fromXDelta="100%"
|
||||
android:toXDelta="0%" />
|
||||
</set>
|
@ -3,7 +3,7 @@
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:interpolator="@android:anim/decelerate_interpolator">
|
||||
<translate
|
||||
android:duration="150"
|
||||
android:duration="250"
|
||||
android:fromXDelta="0%"
|
||||
android:toXDelta="-100%" />
|
||||
</set>
|
@ -3,7 +3,7 @@
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:interpolator="@android:anim/decelerate_interpolator">
|
||||
<translate
|
||||
android:duration="150"
|
||||
android:duration="250"
|
||||
android:fromXDelta="0%"
|
||||
android:toXDelta="100%" />
|
||||
</set>
|
@ -20,8 +20,8 @@
|
||||
android:id="@+id/camera_flip_button"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/camera_capture_button"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_above="@+id/camera_capture_button"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:src="@drawable/ic_camera_front"
|
||||
android:scaleType="fitCenter"
|
||||
|
@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/core_black">
|
||||
|
||||
<TextureView
|
||||
android:id="@+id/camera_preview"
|
||||
|
@ -83,7 +83,6 @@
|
||||
<string name="CallScreen_Incoming_call">Incoming call</string>
|
||||
|
||||
<!-- CameraActivity -->
|
||||
<string name="CameraActivity_camera_unavailable">Camera unavailable.</string>
|
||||
<string name="CameraActivity_image_save_failure">Failed to save image.</string>
|
||||
|
||||
<!-- ClearProfileActivity -->
|
||||
@ -467,6 +466,7 @@
|
||||
<!-- MediaSendActivity -->
|
||||
<string name="MediaSendActivity_add_a_caption">Add a caption...</string>
|
||||
<string name="MediaSendActivity_an_item_was_removed_because_it_exceeded_the_size_limit">An item was removed because it exceeded the size limit</string>
|
||||
<string name="MediaSendActivity_camera_unavailable">Camera unavailable.</string>
|
||||
|
||||
<!-- MediaRepository -->
|
||||
<string name="MediaRepository_all_media">All media</string>
|
||||
|
@ -109,6 +109,9 @@ public class Camera1Fragment extends Fragment implements TextureView.SurfaceText
|
||||
camera.setScreenRotation(controller.getDisplayRotation());
|
||||
});
|
||||
orderEnforcer.run(Stage.CAMERA_PROPERTIES_AVAILABLE, this::updatePreviewScale);
|
||||
|
||||
requireActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
requireActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -156,6 +159,10 @@ public class Camera1Fragment extends Fragment implements TextureView.SurfaceText
|
||||
controller.onCameraError();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
orderEnforcer.reset();
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void initControls() {
|
||||
flipButton = getView().findViewById(R.id.camera_flip_button);
|
||||
@ -202,7 +209,7 @@ public class Camera1Fragment extends Fragment implements TextureView.SurfaceText
|
||||
}
|
||||
|
||||
private void onCaptureClicked() {
|
||||
orderEnforcer.reset();
|
||||
reset();
|
||||
|
||||
Stopwatch fastCaptureTimer = new Stopwatch("Capture");
|
||||
|
||||
@ -230,7 +237,7 @@ public class Camera1Fragment extends Fragment implements TextureView.SurfaceText
|
||||
fastCaptureTimer.split("bytes");
|
||||
fastCaptureTimer.stop(TAG);
|
||||
|
||||
controller.onImageCaptured(data);
|
||||
controller.onImageCaptured(data, resource.getWidth(), resource.getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -299,7 +306,7 @@ public class Camera1Fragment extends Fragment implements TextureView.SurfaceText
|
||||
|
||||
public interface Controller {
|
||||
void onCameraError();
|
||||
void onImageCaptured(@NonNull byte[] data);
|
||||
void onImageCaptured(@NonNull byte[] data, int width, int height);
|
||||
int getDisplayRotation();
|
||||
}
|
||||
|
||||
|
@ -1,166 +0,0 @@
|
||||
package org.thoughtcrime.securesms.camera;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.TransportOption;
|
||||
import org.thoughtcrime.securesms.components.GlideDrawableListeningTarget;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.scribbles.ScribbleFragment;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
public class CameraActivity extends PassphraseRequiredActionBarActivity implements Camera1Fragment.Controller,
|
||||
ScribbleFragment.Controller
|
||||
{
|
||||
|
||||
private static final String TAG = CameraActivity.class.getSimpleName();
|
||||
|
||||
private static final String TAG_CAMERA = "camera";
|
||||
private static final String TAG_EDITOR = "editor";
|
||||
|
||||
private static final String KEY_TRANSPORT = "transport";
|
||||
|
||||
public static final String EXTRA_MESSAGE = "message";
|
||||
public static final String EXTRA_TRANSPORT = "transport";
|
||||
public static final String EXTRA_WIDTH = "width";
|
||||
public static final String EXTRA_HEIGHT = "height";
|
||||
public static final String EXTRA_SIZE = "size";
|
||||
|
||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
private ImageView snapshot;
|
||||
private TransportOption transport;
|
||||
private Uri captureUri;
|
||||
private boolean imageSent;
|
||||
|
||||
public static Intent getIntent(@NonNull Context context, @NonNull TransportOption transport) {
|
||||
Intent intent = new Intent(context, CameraActivity.class);
|
||||
intent.putExtra(KEY_TRANSPORT, transport);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreCreate() {
|
||||
dynamicLanguage.onCreate(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState, boolean ready) {
|
||||
setContentView(R.layout.camera_activity);
|
||||
|
||||
snapshot = findViewById(R.id.camera_snapshot);
|
||||
transport = getIntent().getParcelableExtra(KEY_TRANSPORT);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
Camera1Fragment fragment = Camera1Fragment.newInstance();
|
||||
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, fragment, TAG_CAMERA).commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
dynamicLanguage.onResume(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
ScribbleFragment editorFragment = (ScribbleFragment) getSupportFragmentManager().findFragmentByTag(TAG_EDITOR);
|
||||
if (editorFragment != null && editorFragment.isEmojiKeyboardVisible()) {
|
||||
editorFragment.dismissEmojiKeyboard();
|
||||
} else {
|
||||
if (editorFragment != null && captureUri != null) {
|
||||
Log.i(TAG, "Cleaning up unused capture: " + captureUri);
|
||||
BlobProvider.getInstance().delete(this, captureUri);
|
||||
captureUri = null;
|
||||
}
|
||||
super.onBackPressed();
|
||||
overridePendingTransition(R.anim.stationary, R.anim.camera_slide_to_bottom);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (captureUri != null) {
|
||||
Log.i(TAG, "Cleaning up capture in onDestroy: " + captureUri);
|
||||
BlobProvider.getInstance().delete(this, captureUri);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraError() {
|
||||
Toast.makeText(this, R.string.CameraActivity_camera_unavailable, Toast.LENGTH_SHORT).show();
|
||||
setResult(RESULT_CANCELED, new Intent());
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImageCaptured(@NonNull byte[] data) {
|
||||
Log.i(TAG, "Fast image captured.");
|
||||
|
||||
captureUri = BlobProvider.getInstance().forData(data).createForSingleSessionInMemory();
|
||||
Log.i(TAG, "Fast image stored: " + captureUri.toString());
|
||||
|
||||
SettableFuture<Boolean> result = new SettableFuture<>();
|
||||
GlideApp.with(this).load(new DecryptableStreamUriLoader.DecryptableUri(captureUri)).into(new GlideDrawableListeningTarget(snapshot, result));
|
||||
result.addListener(new AssertedSuccessListener<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean result) {
|
||||
ScribbleFragment fragment = ScribbleFragment.newInstance(captureUri, dynamicLanguage.getCurrentLocale(), Optional.of(transport), true);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
|
||||
.replace(R.id.fragment_container, fragment, TAG_EDITOR)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDisplayRotation() {
|
||||
return getWindowManager().getDefaultDisplay().getRotation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImageEditComplete(@NonNull Uri uri, int width, int height, long size, @NonNull Optional<String> message, @NonNull Optional<TransportOption> transport) {
|
||||
imageSent = true;
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setData(uri);
|
||||
intent.putExtra(EXTRA_WIDTH, width);
|
||||
intent.putExtra(EXTRA_HEIGHT, height);
|
||||
intent.putExtra(EXTRA_SIZE, size);
|
||||
intent.putExtra(EXTRA_MESSAGE, message.or(""));
|
||||
intent.putExtra(EXTRA_TRANSPORT, transport.orNull());
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
|
||||
overridePendingTransition(R.anim.stationary, R.anim.camera_slide_to_bottom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImageEditFailure() {
|
||||
Log.w(TAG, "Failed to save edited image.");
|
||||
Toast.makeText(this, R.string.CameraActivity_image_save_failure, Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEventsNeeded(boolean needed) { }
|
||||
}
|
@ -96,7 +96,6 @@ import org.thoughtcrime.securesms.TransportOption;
|
||||
import org.thoughtcrime.securesms.VerifyIdentityActivity;
|
||||
import org.thoughtcrime.securesms.audio.AudioRecorder;
|
||||
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
|
||||
import org.thoughtcrime.securesms.camera.CameraActivity;
|
||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
||||
import org.thoughtcrime.securesms.components.AnimatingToggle;
|
||||
import org.thoughtcrime.securesms.components.AttachmentTypeSelector;
|
||||
@ -269,8 +268,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private static final int PICK_LOCATION = 9;
|
||||
private static final int PICK_GIF = 10;
|
||||
private static final int SMS_DEFAULT = 11;
|
||||
private static final int PICK_CAMERA = 12;
|
||||
private static final int MEDIA_SENDER = 13;
|
||||
private static final int MEDIA_SENDER = 12;
|
||||
|
||||
private GlideRequests glideRequests;
|
||||
protected ComposeText composeText;
|
||||
@ -533,35 +531,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
case SMS_DEFAULT:
|
||||
initializeSecurity(isSecureText, isDefaultSms);
|
||||
break;
|
||||
case PICK_CAMERA:
|
||||
int imageWidth = data.getIntExtra(CameraActivity.EXTRA_WIDTH, 0);
|
||||
int imageHeight = data.getIntExtra(CameraActivity.EXTRA_HEIGHT, 0);
|
||||
long imageSize = data.getLongExtra(CameraActivity.EXTRA_SIZE, 0);
|
||||
TransportOption transport = data.getParcelableExtra(CameraActivity.EXTRA_TRANSPORT);
|
||||
String message = data.getStringExtra(CameraActivity.EXTRA_MESSAGE);
|
||||
SlideDeck slideDeck = new SlideDeck();
|
||||
case MEDIA_SENDER:
|
||||
long expiresIn = recipient.getExpireMessages() * 1000L;
|
||||
int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1);
|
||||
boolean initiating = threadId == -1;
|
||||
|
||||
if (transport == null) {
|
||||
throw new IllegalStateException("Received a null transport from the CameraActivity.");
|
||||
}
|
||||
|
||||
sendButton.setTransport(transport);
|
||||
|
||||
slideDeck.addSlide(new ImageSlide(this, data.getData(), imageSize, imageWidth, imageHeight));
|
||||
|
||||
sendMediaMessage(transport.isSms(), message, slideDeck, Collections.emptyList(), Collections.emptyList(), expiresIn, subscriptionId, initiating);
|
||||
break;
|
||||
|
||||
case MEDIA_SENDER:
|
||||
expiresIn = recipient.getExpireMessages() * 1000L;
|
||||
subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1);
|
||||
initiating = threadId == -1;
|
||||
transport = data.getParcelableExtra(MediaSendActivity.EXTRA_TRANSPORT);
|
||||
message = data.getStringExtra(MediaSendActivity.EXTRA_MESSAGE);
|
||||
slideDeck = new SlideDeck();
|
||||
TransportOption transport = data.getParcelableExtra(MediaSendActivity.EXTRA_TRANSPORT);
|
||||
String message = data.getStringExtra(MediaSendActivity.EXTRA_MESSAGE);
|
||||
SlideDeck slideDeck = new SlideDeck();
|
||||
|
||||
if (transport == null) {
|
||||
throw new IllegalStateException("Received a null transport from the MediaSendActivity.");
|
||||
@ -592,6 +568,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
Stream.of(slideDeck.getSlides())
|
||||
.map(Slide::getUri)
|
||||
.withoutNulls()
|
||||
.filter(BlobProvider::isAuthority)
|
||||
.forEach(uri -> BlobProvider.getInstance().delete(context, uri));
|
||||
});
|
||||
}
|
||||
@ -1231,7 +1208,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
final List<Media> mediaList = getIntent().getParcelableArrayListExtra(MEDIA_EXTRA);
|
||||
|
||||
if (!Util.isEmpty(mediaList)) {
|
||||
Intent sendIntent = MediaSendActivity.getIntent(this, mediaList, recipient, draftText, sendButton.getSelectedTransport());
|
||||
Intent sendIntent = MediaSendActivity.buildEditorIntent(this, mediaList, recipient, draftText, sendButton.getSelectedTransport());
|
||||
startActivityForResult(sendIntent, MEDIA_SENDER);
|
||||
return new SettableFuture<>(false);
|
||||
}
|
||||
@ -1743,7 +1720,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
return new SettableFuture<>(false);
|
||||
} else if (MediaType.IMAGE.equals(mediaType) || MediaType.GIF.equals(mediaType) || MediaType.VIDEO.equals(mediaType)) {
|
||||
Media media = new Media(uri, MediaUtil.getMimeType(this, uri), 0, width, height, 0, Optional.absent(), Optional.absent());
|
||||
startActivityForResult(MediaSendActivity.getIntent(ConversationActivity.this, Collections.singletonList(media), recipient, composeText.getTextTrimmed(), sendButton.getSelectedTransport()), MEDIA_SENDER);
|
||||
startActivityForResult(MediaSendActivity.buildEditorIntent(ConversationActivity.this, Collections.singletonList(media), recipient, composeText.getTextTrimmed(), sendButton.getSelectedTransport()), MEDIA_SENDER);
|
||||
return new SettableFuture<>(false);
|
||||
} else {
|
||||
return attachmentManager.setMedia(glideRequests, uri, mediaType, getCurrentMediaConstraints(), width, height);
|
||||
@ -2392,7 +2369,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
linkPreviewViewModel.onUserCancel();
|
||||
// TODO: Carry over size?
|
||||
Media media = new Media(uri, mimeType, dateTaken, width, height, 0, Optional.of(Media.ALL_MEDIA_BUCKET_ID), Optional.absent());
|
||||
startActivityForResult(MediaSendActivity.getIntent(ConversationActivity.this, Collections.singletonList(media), recipient, composeText.getTextTrimmed(), sendButton.getSelectedTransport()), MEDIA_SENDER);
|
||||
startActivityForResult(MediaSendActivity.buildEditorIntent(ConversationActivity.this, Collections.singletonList(media), recipient, composeText.getTextTrimmed(), sendButton.getSelectedTransport()), MEDIA_SENDER);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2406,7 +2383,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
.withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video))
|
||||
.onAllGranted(() -> {
|
||||
composeText.clearFocus();
|
||||
startActivityForResult(CameraActivity.getIntent(ConversationActivity.this, sendButton.getSelectedTransport()), PICK_CAMERA);
|
||||
startActivityForResult(MediaSendActivity.buildCameraIntent(ConversationActivity.this, recipient, sendButton.getSelectedTransport()), MEDIA_SENDER);
|
||||
overridePendingTransition(R.anim.camera_slide_from_bottom, R.anim.stationary);
|
||||
})
|
||||
.onAnyDenied(() -> Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show())
|
||||
|
@ -52,7 +52,7 @@ public class MediaPickerFolderFragment extends Fragment implements MediaPickerFo
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
recipientName = getArguments().getString(KEY_RECIPIENT_NAME);
|
||||
viewModel = ViewModelProviders.of(requireActivity(), new MediaSendViewModel.Factory(new MediaRepository())).get(MediaSendViewModel.class);
|
||||
viewModel = ViewModelProviders.of(requireActivity(), new MediaSendViewModel.Factory(requireActivity().getApplication(), new MediaRepository())).get(MediaSendViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -71,7 +71,7 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
|
||||
bucketId = getArguments().getString(KEY_BUCKET_ID);
|
||||
folderTitle = getArguments().getString(KEY_FOLDER_TITLE);
|
||||
maxSelection = getArguments().getInt(KEY_MAX_SELECTION);
|
||||
viewModel = ViewModelProviders.of(requireActivity(), new MediaSendViewModel.Factory(new MediaRepository())).get(MediaSendViewModel.class);
|
||||
viewModel = ViewModelProviders.of(requireActivity(), new MediaSendViewModel.Factory(requireActivity().getApplication(), new MediaRepository())).get(MediaSendViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -8,27 +8,34 @@ import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.OvershootInterpolator;
|
||||
import android.view.animation.ScaleAnimation;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.TransportOption;
|
||||
import org.thoughtcrime.securesms.camera.Camera1Fragment;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.scribbles.ScribbleFragment;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@ -42,8 +49,11 @@ import java.util.Locale;
|
||||
public class MediaSendActivity extends PassphraseRequiredActionBarActivity implements MediaPickerFolderFragment.Controller,
|
||||
MediaPickerItemFragment.Controller,
|
||||
MediaSendFragment.Controller,
|
||||
ScribbleFragment.Controller
|
||||
ScribbleFragment.Controller,
|
||||
Camera1Fragment.Controller
|
||||
{
|
||||
private static final String TAG = MediaSendActivity.class.getSimpleName();
|
||||
|
||||
public static final String EXTRA_MEDIA = "media";
|
||||
public static final String EXTRA_MESSAGE = "message";
|
||||
public static final String EXTRA_TRANSPORT = "transport";
|
||||
@ -55,10 +65,12 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
||||
private static final String KEY_BODY = "body";
|
||||
private static final String KEY_MEDIA = "media";
|
||||
private static final String KEY_TRANSPORT = "transport";
|
||||
private static final String KEY_IS_CAMERA = "is_camera";
|
||||
|
||||
private static final String TAG_FOLDER_PICKER = "folder_picker";
|
||||
private static final String TAG_ITEM_PICKER = "item_picker";
|
||||
private static final String TAG_SEND = "send";
|
||||
private static final String TAG_CAMERA = "camera";
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
@ -73,7 +85,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
||||
/**
|
||||
* Get an intent to launch the media send flow starting with the picker.
|
||||
*/
|
||||
public static Intent getIntent(@NonNull Context context, @NonNull Recipient recipient, @NonNull String body, @NonNull TransportOption transport) {
|
||||
public static Intent buildGalleryIntent(@NonNull Context context, @NonNull Recipient recipient, @NonNull String body, @NonNull TransportOption transport) {
|
||||
Intent intent = new Intent(context, MediaSendActivity.class);
|
||||
intent.putExtra(KEY_ADDRESS, recipient.getAddress().serialize());
|
||||
intent.putExtra(KEY_TRANSPORT, transport);
|
||||
@ -81,17 +93,26 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
||||
return intent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an intent to launch the media send flow starting with the picker.
|
||||
*/
|
||||
public static Intent buildCameraIntent(@NonNull Context context, @NonNull Recipient recipient, @NonNull TransportOption transport) {
|
||||
Intent intent = buildGalleryIntent(context, recipient, "", transport);
|
||||
intent.putExtra(KEY_IS_CAMERA, true);
|
||||
return intent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an intent to launch the media send flow with a specific list of media. Will jump right to
|
||||
* the editor screen.
|
||||
*/
|
||||
public static Intent getIntent(@NonNull Context context,
|
||||
@NonNull List<Media> media,
|
||||
@NonNull Recipient recipient,
|
||||
@NonNull String body,
|
||||
@NonNull TransportOption transport)
|
||||
public static Intent buildEditorIntent(@NonNull Context context,
|
||||
@NonNull List<Media> media,
|
||||
@NonNull Recipient recipient,
|
||||
@NonNull String body,
|
||||
@NonNull TransportOption transport)
|
||||
{
|
||||
Intent intent = getIntent(context, recipient, body, transport);
|
||||
Intent intent = buildGalleryIntent(context, recipient, body, transport);
|
||||
intent.putParcelableArrayListExtra(KEY_MEDIA, new ArrayList<>(media));
|
||||
return intent;
|
||||
}
|
||||
@ -114,7 +135,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
||||
countButton = findViewById(R.id.mediasend_count_button);
|
||||
countButtonText = findViewById(R.id.mediasend_count_button_text);
|
||||
|
||||
viewModel = ViewModelProviders.of(this, new MediaSendViewModel.Factory(new MediaRepository())).get(MediaSendViewModel.class);
|
||||
viewModel = ViewModelProviders.of(this, new MediaSendViewModel.Factory(getApplication(), new MediaRepository())).get(MediaSendViewModel.class);
|
||||
recipient = Recipient.from(this, Address.fromSerialized(getIntent().getStringExtra(KEY_ADDRESS)), true);
|
||||
transport = getIntent().getParcelableExtra(KEY_TRANSPORT);
|
||||
|
||||
@ -123,9 +144,16 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
||||
|
||||
viewModel.onBodyChanged(getIntent().getStringExtra(KEY_BODY));
|
||||
|
||||
List<Media> media = getIntent().getParcelableArrayListExtra(KEY_MEDIA);
|
||||
List<Media> media = getIntent().getParcelableArrayListExtra(KEY_MEDIA);
|
||||
boolean isCamera = getIntent().getBooleanExtra(KEY_IS_CAMERA, false);
|
||||
|
||||
if (!Util.isEmpty(media)) {
|
||||
if (isCamera) {
|
||||
Fragment fragment = Camera1Fragment.newInstance();
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.mediasend_fragment_container, fragment, TAG_CAMERA)
|
||||
.commit();
|
||||
|
||||
} else if (!Util.isEmpty(media)) {
|
||||
viewModel.onSelectedMediaChanged(this, media);
|
||||
|
||||
Fragment fragment = MediaSendFragment.newInstance(transport, dynamicLanguage.getCurrentLocale());
|
||||
@ -154,6 +182,10 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
||||
MediaSendFragment sendFragment = (MediaSendFragment) getSupportFragmentManager().findFragmentByTag(TAG_SEND);
|
||||
if (sendFragment == null || !sendFragment.isVisible() || !sendFragment.handleBackPress()) {
|
||||
super.onBackPressed();
|
||||
|
||||
if (getIntent().getBooleanExtra(KEY_IS_CAMERA, false) && getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
||||
viewModel.onImageCaptureUndo(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,6 +227,8 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
||||
|
||||
@Override
|
||||
public void onSendClicked(@NonNull List<Media> media, @NonNull String message, @NonNull TransportOption transport) {
|
||||
viewModel.onSendClicked();
|
||||
|
||||
ArrayList<Media> mediaList = new ArrayList<>(media);
|
||||
Intent intent = new Intent();
|
||||
|
||||
@ -231,13 +265,72 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraError() {
|
||||
Toast.makeText(this, R.string.MediaSendActivity_camera_unavailable, Toast.LENGTH_SHORT).show();
|
||||
setResult(RESULT_CANCELED, new Intent());
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImageCaptured(@NonNull byte[] data, int width, int height) {
|
||||
Log.i(TAG, "Camera image captured.");
|
||||
|
||||
SimpleTask.run(getLifecycle(), () -> {
|
||||
try {
|
||||
Uri uri = BlobProvider.getInstance()
|
||||
.forData(data)
|
||||
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||
.createForSingleSessionOnDisk(this);
|
||||
return new Media(uri,
|
||||
MediaUtil.IMAGE_JPEG,
|
||||
System.currentTimeMillis(),
|
||||
width,
|
||||
height,
|
||||
data.length,
|
||||
Optional.of(Media.ALL_MEDIA_BUCKET_ID),
|
||||
Optional.absent());
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}, media -> {
|
||||
if (media == null) {
|
||||
onImageEditFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Camera capture stored: " + media.getUri().toString());
|
||||
|
||||
viewModel.onImageCaptured(media);
|
||||
navigateToMediaSend(transport, dynamicLanguage.getCurrentLocale());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDisplayRotation() {
|
||||
return getWindowManager().getDefaultDisplay().getRotation();
|
||||
}
|
||||
|
||||
private void initializeCountButtonObserver(@NonNull TransportOption transport, @NonNull Locale locale) {
|
||||
viewModel.getCountButtonState().observe(this, buttonState -> {
|
||||
if (buttonState == null) return;
|
||||
|
||||
countButton.setVisibility(buttonState.getVisibility() ? View.VISIBLE : View.GONE);
|
||||
countButton.setOnClickListener(v -> navigateToMediaSend(transport, locale));
|
||||
countButtonText.setText(String.valueOf(buttonState.getCount()));
|
||||
countButton.setEnabled(buttonState.getVisibility());
|
||||
animateCountButtonVisibility(countButton, countButton.getVisibility(), buttonState.getVisibility() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (buttonState.getCount() > 0) {
|
||||
countButton.setOnClickListener(v -> {
|
||||
Camera1Fragment fragment = (Camera1Fragment) getSupportFragmentManager().findFragmentByTag(TAG_CAMERA);
|
||||
if (fragment != null) {
|
||||
fragment.reset();
|
||||
}
|
||||
|
||||
navigateToMediaSend(transport, locale);
|
||||
});
|
||||
} else {
|
||||
countButton.setOnClickListener(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -256,4 +349,33 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
||||
.addToBackStack(backstackTag)
|
||||
.commit();
|
||||
}
|
||||
|
||||
private void animateCountButtonVisibility(View countButton, int oldVisibility, int newVisibility) {
|
||||
if (oldVisibility == newVisibility) return;
|
||||
|
||||
if (countButton.getAnimation() != null) {
|
||||
countButton.getAnimation().cancel();
|
||||
countButton.setVisibility(newVisibility);
|
||||
} else if (newVisibility == View.VISIBLE) {
|
||||
countButton.setVisibility(View.VISIBLE);
|
||||
|
||||
Animation animation = new ScaleAnimation(0, 1, 0, 1, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
|
||||
animation.setDuration(250);
|
||||
animation.setInterpolator(new OvershootInterpolator());
|
||||
countButton.startAnimation(animation);
|
||||
} else {
|
||||
Animation animation = new ScaleAnimation(1, 0, 1, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
|
||||
animation.setDuration(150);
|
||||
animation.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||
animation.setAnimationListener(new SimpleAnimationListener() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
countButton.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
countButton.startAnimation(animation);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -259,7 +259,7 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
|
||||
|
||||
@Override
|
||||
public void onRailItemDeleteClicked(int distanceFromActive) {
|
||||
viewModel.onMediaItemRemoved(fragmentPager.getCurrentItem() + distanceFromActive);
|
||||
viewModel.onMediaItemRemoved(requireContext(), fragmentPager.getCurrentItem() + distanceFromActive);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -296,7 +296,7 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
|
||||
}
|
||||
|
||||
private void initViewModel() {
|
||||
viewModel = ViewModelProviders.of(requireActivity(), new MediaSendViewModel.Factory(new MediaRepository())).get(MediaSendViewModel.class);
|
||||
viewModel = ViewModelProviders.of(requireActivity(), new MediaSendViewModel.Factory(requireActivity().getApplication(), new MediaRepository())).get(MediaSendViewModel.class);
|
||||
|
||||
viewModel.getSelectedMedia().observe(this, media -> {
|
||||
if (Util.isEmpty(media)) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.thoughtcrime.securesms.mediasend;
|
||||
|
||||
import android.app.Application;
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.lifecycle.MutableLiveData;
|
||||
import android.arch.lifecycle.ViewModel;
|
||||
@ -12,12 +13,15 @@ import android.text.TextUtils;
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -26,6 +30,7 @@ import java.util.Map;
|
||||
*/
|
||||
class MediaSendViewModel extends ViewModel {
|
||||
|
||||
private final Application application;
|
||||
private final MediaRepository repository;
|
||||
private final MutableLiveData<List<Media>> selectedMedia;
|
||||
private final MutableLiveData<List<Media>> bucketMedia;
|
||||
@ -39,8 +44,11 @@ class MediaSendViewModel extends ViewModel {
|
||||
private MediaConstraints mediaConstraints;
|
||||
private CharSequence body;
|
||||
private CountButtonState.Visibility countButtonVisibility;
|
||||
private boolean sentMedia;
|
||||
private Optional<Media> lastImageCapture;
|
||||
|
||||
private MediaSendViewModel(@NonNull MediaRepository repository) {
|
||||
private MediaSendViewModel(@NonNull Application application, @NonNull MediaRepository repository) {
|
||||
this.application = application;
|
||||
this.repository = repository;
|
||||
this.selectedMedia = new MutableLiveData<>();
|
||||
this.bucketMedia = new MutableLiveData<>();
|
||||
@ -51,6 +59,7 @@ class MediaSendViewModel extends ViewModel {
|
||||
this.error = new SingleLiveEvent<>();
|
||||
this.savedDrawState = new HashMap<>();
|
||||
this.countButtonVisibility = CountButtonState.Visibility.CONDITIONAL;
|
||||
this.lastImageCapture = Optional.absent();
|
||||
|
||||
position.setValue(-1);
|
||||
countButtonState.setValue(new CountButtonState(0, CountButtonState.Visibility.CONDITIONAL));
|
||||
@ -91,17 +100,17 @@ class MediaSendViewModel extends ViewModel {
|
||||
|
||||
void onMultiSelectStarted() {
|
||||
countButtonVisibility = CountButtonState.Visibility.FORCED_ON;
|
||||
countButtonState.postValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
|
||||
countButtonState.setValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
|
||||
}
|
||||
|
||||
void onImageEditorStarted() {
|
||||
countButtonVisibility = CountButtonState.Visibility.FORCED_OFF;
|
||||
countButtonState.postValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
|
||||
countButtonState.setValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
|
||||
}
|
||||
|
||||
void onImageEditorEnded() {
|
||||
countButtonVisibility = CountButtonState.Visibility.CONDITIONAL;
|
||||
countButtonState.postValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
|
||||
countButtonState.setValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
|
||||
}
|
||||
|
||||
void onBodyChanged(@NonNull CharSequence body) {
|
||||
@ -117,11 +126,51 @@ class MediaSendViewModel extends ViewModel {
|
||||
this.position.setValue(position);
|
||||
}
|
||||
|
||||
void onMediaItemRemoved(int position) {
|
||||
getSelectedMediaOrDefault().remove(position);
|
||||
void onMediaItemRemoved(@NonNull Context context, int position) {
|
||||
Media removed = getSelectedMediaOrDefault().remove(position);
|
||||
|
||||
if (removed != null && BlobProvider.isAuthority(removed.getUri())) {
|
||||
BlobProvider.getInstance().delete(context, removed.getUri());
|
||||
}
|
||||
|
||||
selectedMedia.setValue(selectedMedia.getValue());
|
||||
}
|
||||
|
||||
void onImageCaptured(@NonNull Media media) {
|
||||
List<Media> selected = selectedMedia.getValue();
|
||||
|
||||
if (selected == null) {
|
||||
selected = new LinkedList<>();
|
||||
}
|
||||
|
||||
lastImageCapture = Optional.of(media);
|
||||
|
||||
selected.add(media);
|
||||
selectedMedia.setValue(selected);
|
||||
position.setValue(selected.size() - 1);
|
||||
bucketId.setValue(Media.ALL_MEDIA_BUCKET_ID);
|
||||
|
||||
if (selected.size() == 1) {
|
||||
countButtonVisibility = CountButtonState.Visibility.FORCED_OFF;
|
||||
} else {
|
||||
countButtonVisibility = CountButtonState.Visibility.CONDITIONAL;
|
||||
}
|
||||
|
||||
countButtonState.setValue(new CountButtonState(selected.size(), countButtonVisibility));
|
||||
}
|
||||
|
||||
void onImageCaptureUndo(@NonNull Context context) {
|
||||
List<Media> selected = getSelectedMediaOrDefault();
|
||||
|
||||
if (lastImageCapture.isPresent() && selected.contains(lastImageCapture.get()) && selected.size() == 1) {
|
||||
selected.remove(lastImageCapture.get());
|
||||
selectedMedia.setValue(selected);
|
||||
countButtonState.setValue(new CountButtonState(selected.size(), countButtonVisibility));
|
||||
BlobProvider.getInstance().delete(context, lastImageCapture.get().getUri());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void onCaptionChanged(@NonNull String newCaption) {
|
||||
if (position.getValue() >= 0 && !Util.isEmpty(selectedMedia.getValue())) {
|
||||
selectedMedia.getValue().get(position.getValue()).setCaption(TextUtils.isEmpty(newCaption) ? null : newCaption);
|
||||
@ -133,6 +182,10 @@ class MediaSendViewModel extends ViewModel {
|
||||
savedDrawState.putAll(state);
|
||||
}
|
||||
|
||||
void onSendClicked() {
|
||||
sentMedia = true;
|
||||
}
|
||||
|
||||
@NonNull Map<Uri, Object> getDrawState() {
|
||||
return savedDrawState;
|
||||
}
|
||||
@ -188,6 +241,16 @@ class MediaSendViewModel extends ViewModel {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
if (!sentMedia) {
|
||||
Stream.of(getSelectedMediaOrDefault())
|
||||
.map(Media::getUri)
|
||||
.filter(BlobProvider::isAuthority)
|
||||
.forEach(uri -> BlobProvider.getInstance().delete(application.getApplicationContext(), uri));
|
||||
}
|
||||
}
|
||||
|
||||
enum Error {
|
||||
ITEM_TOO_LARGE
|
||||
}
|
||||
@ -221,15 +284,17 @@ class MediaSendViewModel extends ViewModel {
|
||||
|
||||
static class Factory extends ViewModelProvider.NewInstanceFactory {
|
||||
|
||||
private final Application application;
|
||||
private final MediaRepository repository;
|
||||
|
||||
Factory(@NonNull MediaRepository repository) {
|
||||
this.repository = repository;
|
||||
Factory(@NonNull Application application, @NonNull MediaRepository repository) {
|
||||
this.application = application;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||
return modelClass.cast(new MediaSendViewModel(repository));
|
||||
return modelClass.cast(new MediaSendViewModel(application, repository));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
package org.thoughtcrime.securesms.mediasend;
|
||||
|
||||
import android.view.animation.Animation;
|
||||
|
||||
/**
|
||||
* Basic implementation of {@link android.view.animation.Animation.AnimationListener} with empty
|
||||
* implementation so you don't have to override every method.
|
||||
*/
|
||||
public class SimpleAnimationListener implements Animation.AnimationListener {
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation animation) {
|
||||
}
|
||||
}
|
@ -387,7 +387,7 @@ public class AttachmentManager {
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio))
|
||||
.onAllGranted(() -> selectMediaType(activity, "image/*", new String[] {"image/*", "video/*"}, requestCode))
|
||||
.onAllGranted(() -> activity.startActivityForResult(MediaSendActivity.getIntent(activity, recipient, body, transport), requestCode))
|
||||
.onAllGranted(() -> activity.startActivityForResult(MediaSendActivity.buildGalleryIntent(activity, recipient, body, transport), requestCode))
|
||||
.execute();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user