Merge branch 'dev' of github.com:session-foundation/session-android into dev
commit
f83db366b0
@ -1,446 +0,0 @@
|
||||
package org.thoughtcrime.securesms.mediasend;
|
||||
|
||||
import static org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.OvershootInterpolator;
|
||||
import android.view.animation.ScaleAnimation;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import com.squareup.phrase.Phrase;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import network.loki.messenger.R;
|
||||
import org.session.libsession.utilities.Address;
|
||||
import org.session.libsession.utilities.MediaTypes;
|
||||
import org.session.libsession.utilities.Util;
|
||||
import org.session.libsession.utilities.concurrent.SimpleTask;
|
||||
import org.session.libsession.utilities.recipients.Recipient;
|
||||
import org.session.libsignal.utilities.Log;
|
||||
import org.session.libsignal.utilities.guava.Optional;
|
||||
import org.thoughtcrime.securesms.ScreenLockActionBarActivity;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.scribbles.ImageEditorFragment;
|
||||
import org.thoughtcrime.securesms.util.FilenameUtils;
|
||||
|
||||
/**
|
||||
* Encompasses the entire flow of sending media, starting from the selection process to the actual
|
||||
* captioning and editing of the content.
|
||||
*
|
||||
* This activity is intended to be launched via {@link #startActivityForResult(Intent, int)}.
|
||||
* It will return the {@link Media} that the user decided to send.
|
||||
*/
|
||||
public class MediaSendActivity extends ScreenLockActionBarActivity implements MediaPickerFolderFragment.Controller,
|
||||
MediaPickerItemFragment.Controller,
|
||||
MediaSendFragment.Controller,
|
||||
ImageEditorFragment.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";
|
||||
|
||||
private static final String KEY_ADDRESS = "address";
|
||||
private static final String KEY_BODY = "body";
|
||||
private static final String KEY_MEDIA = "media";
|
||||
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 Recipient recipient;
|
||||
private MediaSendViewModel viewModel;
|
||||
|
||||
private View countButton;
|
||||
private TextView countButtonText;
|
||||
private View cameraButton;
|
||||
|
||||
/**
|
||||
* Get an intent to launch the media send flow starting with the picker.
|
||||
*/
|
||||
public static Intent buildGalleryIntent(@NonNull Context context, @NonNull Recipient recipient, @NonNull String body) {
|
||||
Intent intent = new Intent(context, MediaSendActivity.class);
|
||||
intent.putExtra(KEY_ADDRESS, recipient.getAddress().serialize());
|
||||
intent.putExtra(KEY_BODY, body);
|
||||
return intent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an intent to launch the media send flow starting with the camera.
|
||||
*/
|
||||
public static Intent buildCameraIntent(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
Intent intent = buildGalleryIntent(context, recipient, "");
|
||||
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 buildEditorIntent(@NonNull Context context,
|
||||
@NonNull List<Media> media,
|
||||
@NonNull Recipient recipient,
|
||||
@NonNull String body)
|
||||
{
|
||||
Intent intent = buildGalleryIntent(context, recipient, body);
|
||||
intent.putParcelableArrayListExtra(KEY_MEDIA, new ArrayList<>(media));
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
||||
super.onCreate(savedInstanceState, ready);
|
||||
|
||||
setContentView(R.layout.mediasend_activity);
|
||||
setResult(RESULT_CANCELED);
|
||||
|
||||
if (savedInstanceState != null) { return; }
|
||||
|
||||
countButton = findViewById(R.id.mediasend_count_button);
|
||||
countButtonText = findViewById(R.id.mediasend_count_button_text);
|
||||
cameraButton = findViewById(R.id.mediasend_camera_button);
|
||||
|
||||
viewModel = new ViewModelProvider(this, new MediaSendViewModel.Factory(getApplication(), new MediaRepository())).get(MediaSendViewModel.class);
|
||||
recipient = Recipient.from(this, Address.fromSerialized(getIntent().getStringExtra(KEY_ADDRESS)), true);
|
||||
|
||||
viewModel.onBodyChanged(getIntent().getStringExtra(KEY_BODY));
|
||||
|
||||
List<Media> media = getIntent().getParcelableArrayListExtra(KEY_MEDIA);
|
||||
boolean isCamera = getIntent().getBooleanExtra(KEY_IS_CAMERA, false);
|
||||
|
||||
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(recipient);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.mediasend_fragment_container, fragment, TAG_SEND)
|
||||
.commit();
|
||||
} else {
|
||||
MediaPickerFolderFragment fragment = MediaPickerFolderFragment.newInstance(recipient);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.mediasend_fragment_container, fragment, TAG_FOLDER_PICKER)
|
||||
.commit();
|
||||
}
|
||||
|
||||
initializeCountButtonObserver();
|
||||
initializeCameraButtonObserver();
|
||||
initializeErrorObserver();
|
||||
|
||||
cameraButton.setOnClickListener(v -> {
|
||||
int maxSelection = viewModel.getMaxSelection();
|
||||
|
||||
if (viewModel.getSelectedMedia().getValue() != null && viewModel.getSelectedMedia().getValue().size() >= maxSelection) {
|
||||
Toast.makeText(this, getString(R.string.attachmentsErrorNumber), Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
navigateToCamera();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFolderSelected(@NonNull MediaFolder folder) {
|
||||
if (viewModel == null) { return; }
|
||||
|
||||
viewModel.onFolderSelected(folder.getBucketId());
|
||||
|
||||
MediaPickerItemFragment fragment = MediaPickerItemFragment.newInstance(folder.getBucketId(), folder.getTitle(), viewModel.getMaxSelection());
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.setCustomAnimations(R.anim.slide_from_right, R.anim.slide_to_left, R.anim.slide_from_left, R.anim.slide_to_right)
|
||||
.replace(R.id.mediasend_fragment_container, fragment, TAG_ITEM_PICKER)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaSelected(@NonNull Media media) {
|
||||
viewModel.onSingleMediaSelected(this, media);
|
||||
navigateToMediaSend(recipient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddMediaClicked(@NonNull String bucketId) {
|
||||
MediaPickerFolderFragment folderFragment = MediaPickerFolderFragment.newInstance(recipient);
|
||||
MediaPickerItemFragment itemFragment = MediaPickerItemFragment.newInstance(bucketId, "", viewModel.getMaxSelection());
|
||||
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.setCustomAnimations(R.anim.stationary, R.anim.slide_to_left, R.anim.slide_from_left, R.anim.slide_to_right)
|
||||
.replace(R.id.mediasend_fragment_container, folderFragment, TAG_FOLDER_PICKER)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.setCustomAnimations(R.anim.slide_from_right, R.anim.stationary, R.anim.slide_from_left, R.anim.slide_to_right)
|
||||
.replace(R.id.mediasend_fragment_container, itemFragment, TAG_ITEM_PICKER)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendClicked(@NonNull List<Media> media, @NonNull String message) {
|
||||
viewModel.onSendClicked();
|
||||
|
||||
ArrayList<Media> mediaList = new ArrayList<>(media);
|
||||
Intent intent = new Intent();
|
||||
|
||||
intent.putParcelableArrayListExtra(EXTRA_MEDIA, mediaList);
|
||||
intent.putExtra(EXTRA_MESSAGE, message);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
|
||||
overridePendingTransition(R.anim.stationary, R.anim.camera_slide_to_bottom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNoMediaAvailable() {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEventsNeeded(boolean needed) {
|
||||
MediaSendFragment fragment = (MediaSendFragment) getSupportFragmentManager().findFragmentByTag(TAG_SEND);
|
||||
if (fragment != null) {
|
||||
fragment.onTouchEventsNeeded(needed);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraError() {
|
||||
Toast.makeText(this, R.string.cameraErrorUnavailable, 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(MediaTypes.IMAGE_JPEG)
|
||||
.createForSingleSessionOnDisk(this, e -> Log.w(TAG, "Failed to write to disk.", e));
|
||||
|
||||
return new Media(uri,
|
||||
FilenameUtils.constructPhotoFilename(this),
|
||||
MediaTypes.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) {
|
||||
onNoMediaAvailable();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Camera capture stored: " + media.getUri().toString());
|
||||
|
||||
viewModel.onImageCaptured(media);
|
||||
navigateToMediaSend(recipient);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDisplayRotation() {
|
||||
return getWindowManager().getDefaultDisplay().getRotation();
|
||||
}
|
||||
|
||||
private void initializeCountButtonObserver() {
|
||||
viewModel.getCountButtonState().observe(this, buttonState -> {
|
||||
if (buttonState == null) return;
|
||||
|
||||
countButtonText.setText(String.valueOf(buttonState.getCount()));
|
||||
countButton.setEnabled(buttonState.isVisible());
|
||||
animateButtonVisibility(countButton, countButton.getVisibility(), buttonState.isVisible() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (buttonState.getCount() > 0) {
|
||||
countButton.setOnClickListener(v -> navigateToMediaSend(recipient));
|
||||
if (buttonState.isVisible()) {
|
||||
animateButtonTextChange(countButton);
|
||||
}
|
||||
} else {
|
||||
countButton.setOnClickListener(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeCameraButtonObserver() {
|
||||
viewModel.getCameraButtonVisibility().observe(this, visible -> {
|
||||
if (visible == null) return;
|
||||
animateButtonVisibility(cameraButton, cameraButton.getVisibility(), visible ? View.VISIBLE : View.GONE);
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeErrorObserver() {
|
||||
viewModel.getError().observe(this, error -> {
|
||||
if (error == null) return;
|
||||
|
||||
switch (error) {
|
||||
case ITEM_TOO_LARGE:
|
||||
Toast.makeText(this, R.string.attachmentsErrorSize, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case TOO_MANY_ITEMS:
|
||||
// In modern session we'll say you can't sent more than 32 items, but if we ever want
|
||||
// the exact count of how many items the user attempted to send it's: viewModel.getMaxSelection()
|
||||
Toast.makeText(this, getString(R.string.attachmentsErrorNumber), Toast.LENGTH_SHORT).show();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void navigateToMediaSend(@NonNull Recipient recipient) {
|
||||
MediaSendFragment fragment = MediaSendFragment.newInstance(recipient);
|
||||
String backstackTag = null;
|
||||
|
||||
if (getSupportFragmentManager().findFragmentByTag(TAG_SEND) != null) {
|
||||
getSupportFragmentManager().popBackStack(TAG_SEND, FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
backstackTag = TAG_SEND;
|
||||
}
|
||||
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.setCustomAnimations(R.anim.slide_from_right, R.anim.slide_to_left, R.anim.slide_from_left, R.anim.slide_to_right)
|
||||
.replace(R.id.mediasend_fragment_container, fragment, TAG_SEND)
|
||||
.addToBackStack(backstackTag)
|
||||
.commit();
|
||||
}
|
||||
|
||||
private void navigateToCamera() {
|
||||
|
||||
Context c = getApplicationContext();
|
||||
String permanentDenialTxt = Phrase.from(c, R.string.permissionsCameraDenied)
|
||||
.put(APP_NAME_KEY, c.getString(R.string.app_name))
|
||||
.format().toString();
|
||||
String requireCameraPermissionsTxt = Phrase.from(c, R.string.cameraGrantAccessDescription)
|
||||
.put(APP_NAME_KEY, c.getString(R.string.app_name))
|
||||
.format().toString();
|
||||
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.withPermanentDenialDialog(permanentDenialTxt)
|
||||
.onAllGranted(() -> {
|
||||
Camera1Fragment fragment = getOrCreateCameraFragment();
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.setCustomAnimations(R.anim.slide_from_right, R.anim.slide_to_left, R.anim.slide_from_left, R.anim.slide_to_right)
|
||||
.replace(R.id.mediasend_fragment_container, fragment, TAG_CAMERA)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
})
|
||||
.onAnyDenied(() -> Toast.makeText(MediaSendActivity.this, requireCameraPermissionsTxt, Toast.LENGTH_LONG).show())
|
||||
.execute();
|
||||
}
|
||||
|
||||
private Camera1Fragment getOrCreateCameraFragment() {
|
||||
Camera1Fragment fragment = (Camera1Fragment) getSupportFragmentManager().findFragmentByTag(TAG_CAMERA);
|
||||
|
||||
return fragment != null ? fragment
|
||||
: Camera1Fragment.newInstance();
|
||||
}
|
||||
|
||||
private void animateButtonVisibility(@NonNull View button, int oldVisibility, int newVisibility) {
|
||||
if (oldVisibility == newVisibility) return;
|
||||
|
||||
if (button.getAnimation() != null) {
|
||||
button.clearAnimation();
|
||||
button.setVisibility(newVisibility);
|
||||
} else if (newVisibility == View.VISIBLE) {
|
||||
button.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());
|
||||
button.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) {
|
||||
button.clearAnimation();
|
||||
button.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
button.startAnimation(animation);
|
||||
}
|
||||
}
|
||||
|
||||
private void animateButtonTextChange(@NonNull View button) {
|
||||
if (button.getAnimation() != null) {
|
||||
button.clearAnimation();
|
||||
}
|
||||
|
||||
Animation grow = new ScaleAnimation(1f, 1.3f, 1f, 1.3f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
|
||||
grow.setDuration(125);
|
||||
grow.setInterpolator(new AccelerateInterpolator());
|
||||
grow.setAnimationListener(new SimpleAnimationListener() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
Animation shrink = new ScaleAnimation(1.3f, 1f, 1.3f, 1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
|
||||
shrink.setDuration(125);
|
||||
shrink.setInterpolator(new DecelerateInterpolator());
|
||||
button.startAnimation(shrink);
|
||||
}
|
||||
});
|
||||
|
||||
button.startAnimation(grow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestFullScreen(boolean fullScreen) {
|
||||
MediaSendFragment sendFragment = (MediaSendFragment) getSupportFragmentManager().findFragmentByTag(TAG_SEND);
|
||||
if (sendFragment != null && sendFragment.isVisible()) {
|
||||
sendFragment.onRequestFullScreen(fullScreen);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,548 @@
|
||||
package org.thoughtcrime.securesms.mediasend
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import android.view.animation.ScaleAnimation
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.squareup.phrase.Phrase
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.utilities.Address.Companion.fromSerialized
|
||||
import org.session.libsession.utilities.MediaTypes
|
||||
import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY
|
||||
import org.session.libsession.utilities.Util.isEmpty
|
||||
import org.session.libsession.utilities.concurrent.SimpleTask
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.guava.Optional
|
||||
import org.thoughtcrime.securesms.ScreenLockActionBarActivity
|
||||
import org.thoughtcrime.securesms.mediasend.MediaSendViewModel.CountButtonState
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.scribbles.ImageEditorFragment
|
||||
import org.thoughtcrime.securesms.util.FilenameUtils.constructPhotoFilename
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Encompasses the entire flow of sending media, starting from the selection process to the actual
|
||||
* captioning and editing of the content.
|
||||
*
|
||||
* This activity is intended to be launched via [.startActivityForResult].
|
||||
* It will return the [Media] that the user decided to send.
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragment.Controller,
|
||||
MediaPickerItemFragment.Controller, MediaSendFragment.Controller,
|
||||
ImageEditorFragment.Controller,
|
||||
Camera1Fragment.Controller {
|
||||
private var recipient: Recipient? = null
|
||||
private val viewModel: MediaSendViewModel by viewModels()
|
||||
|
||||
private var countButton: View? = null
|
||||
private var countButtonText: TextView? = null
|
||||
private var cameraButton: View? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
|
||||
super.onCreate(savedInstanceState, ready)
|
||||
|
||||
setContentView(R.layout.mediasend_activity)
|
||||
setResult(RESULT_CANCELED)
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
return
|
||||
}
|
||||
|
||||
countButton = findViewById(R.id.mediasend_count_button)
|
||||
countButtonText = findViewById(R.id.mediasend_count_button_text)
|
||||
cameraButton = findViewById(R.id.mediasend_camera_button)
|
||||
|
||||
recipient = Recipient.from(
|
||||
this, fromSerialized(
|
||||
intent.getStringExtra(KEY_ADDRESS)!!
|
||||
), true
|
||||
)
|
||||
|
||||
viewModel.onBodyChanged(intent.getStringExtra(KEY_BODY)!!)
|
||||
|
||||
val media: List<Media?>? = intent.getParcelableArrayListExtra(KEY_MEDIA)
|
||||
val isCamera = intent.getBooleanExtra(KEY_IS_CAMERA, false)
|
||||
|
||||
if (isCamera) {
|
||||
val fragment: Fragment = Camera1Fragment.newInstance()
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.mediasend_fragment_container, fragment, TAG_CAMERA)
|
||||
.commit()
|
||||
} else if (!isEmpty(media)) {
|
||||
viewModel.onSelectedMediaChanged(this, media!!)
|
||||
|
||||
val fragment: Fragment = MediaSendFragment.newInstance(recipient!!)
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.mediasend_fragment_container, fragment, TAG_SEND)
|
||||
.commit()
|
||||
} else {
|
||||
val fragment = MediaPickerFolderFragment.newInstance(
|
||||
recipient!!
|
||||
)
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.mediasend_fragment_container, fragment, TAG_FOLDER_PICKER)
|
||||
.commit()
|
||||
}
|
||||
|
||||
initializeCountButtonObserver()
|
||||
initializeCameraButtonObserver()
|
||||
initializeErrorObserver()
|
||||
|
||||
cameraButton?.setOnClickListener { v: View? ->
|
||||
val maxSelection = MediaSendViewModel.MAX_SELECTED_FILES
|
||||
if (viewModel.getSelectedMedia().value != null && viewModel.getSelectedMedia().value!!.size >= maxSelection) {
|
||||
Toast.makeText(this, getString(R.string.attachmentsErrorNumber), Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
} else {
|
||||
navigateToCamera()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
val sendFragment = supportFragmentManager.findFragmentByTag(TAG_SEND) as MediaSendFragment?
|
||||
if (sendFragment == null || !sendFragment.isVisible || !sendFragment.handleBackPress()) {
|
||||
super.onBackPressed()
|
||||
|
||||
if (intent.getBooleanExtra(
|
||||
KEY_IS_CAMERA,
|
||||
false
|
||||
) && supportFragmentManager.backStackEntryCount == 0
|
||||
) {
|
||||
viewModel.onImageCaptureUndo(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
override fun onFolderSelected(folder: MediaFolder) {
|
||||
viewModel.onFolderSelected(folder.bucketId)
|
||||
|
||||
val fragment = MediaPickerItemFragment.newInstance(
|
||||
folder.bucketId,
|
||||
folder.title,
|
||||
MediaSendViewModel.MAX_SELECTED_FILES
|
||||
)
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(
|
||||
R.anim.slide_from_right,
|
||||
R.anim.slide_to_left,
|
||||
R.anim.slide_from_left,
|
||||
R.anim.slide_to_right
|
||||
)
|
||||
.replace(R.id.mediasend_fragment_container, fragment, TAG_ITEM_PICKER)
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
}
|
||||
|
||||
override fun onMediaSelected(media: Media) {
|
||||
viewModel.onSingleMediaSelected(this, media)
|
||||
navigateToMediaSend(recipient!!)
|
||||
}
|
||||
|
||||
override fun onAddMediaClicked(bucketId: String) {
|
||||
val folderFragment = MediaPickerFolderFragment.newInstance(
|
||||
recipient!!
|
||||
)
|
||||
val itemFragment =
|
||||
MediaPickerItemFragment.newInstance(bucketId, "", MediaSendViewModel.MAX_SELECTED_FILES)
|
||||
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(
|
||||
R.anim.stationary,
|
||||
R.anim.slide_to_left,
|
||||
R.anim.slide_from_left,
|
||||
R.anim.slide_to_right
|
||||
)
|
||||
.replace(R.id.mediasend_fragment_container, folderFragment, TAG_FOLDER_PICKER)
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(
|
||||
R.anim.slide_from_right,
|
||||
R.anim.stationary,
|
||||
R.anim.slide_from_left,
|
||||
R.anim.slide_to_right
|
||||
)
|
||||
.replace(R.id.mediasend_fragment_container, itemFragment, TAG_ITEM_PICKER)
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
}
|
||||
|
||||
override fun onSendClicked(media: List<Media>, message: String) {
|
||||
viewModel.onSendClicked()
|
||||
|
||||
val mediaList = ArrayList(media)
|
||||
val intent = Intent()
|
||||
|
||||
intent.putParcelableArrayListExtra(EXTRA_MEDIA, mediaList)
|
||||
intent.putExtra(EXTRA_MESSAGE, message)
|
||||
setResult(RESULT_OK, intent)
|
||||
finish()
|
||||
|
||||
overridePendingTransition(R.anim.stationary, R.anim.camera_slide_to_bottom)
|
||||
}
|
||||
|
||||
override fun onNoMediaAvailable() {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onTouchEventsNeeded(needed: Boolean) {
|
||||
val fragment = supportFragmentManager.findFragmentByTag(TAG_SEND) as MediaSendFragment?
|
||||
fragment?.onTouchEventsNeeded(needed)
|
||||
}
|
||||
|
||||
override fun onCameraError() {
|
||||
Toast.makeText(this, R.string.cameraErrorUnavailable, Toast.LENGTH_SHORT).show()
|
||||
setResult(RESULT_CANCELED, Intent())
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onImageCaptured(data: ByteArray, width: Int, height: Int) {
|
||||
Log.i(TAG, "Camera image captured.")
|
||||
SimpleTask.run(lifecycle, {
|
||||
try {
|
||||
val uri = BlobProvider.getInstance()
|
||||
.forData(data)
|
||||
.withMimeType(MediaTypes.IMAGE_JPEG)
|
||||
.createForSingleSessionOnDisk(
|
||||
this
|
||||
) { e: IOException? ->
|
||||
Log.w(
|
||||
TAG,
|
||||
"Failed to write to disk.",
|
||||
e
|
||||
)
|
||||
}
|
||||
|
||||
return@run Media(
|
||||
uri,
|
||||
constructPhotoFilename(this),
|
||||
MediaTypes.IMAGE_JPEG,
|
||||
System.currentTimeMillis(),
|
||||
width,
|
||||
height,
|
||||
data.size.toLong(),
|
||||
Optional.of<String>(Media.ALL_MEDIA_BUCKET_ID),
|
||||
Optional.absent<String>()
|
||||
)
|
||||
} catch (e: IOException) {
|
||||
return@run null
|
||||
}
|
||||
}, { media: Media? ->
|
||||
if (media == null) {
|
||||
onNoMediaAvailable()
|
||||
return@run
|
||||
}
|
||||
Log.i(TAG, "Camera capture stored: " + media.uri.toString())
|
||||
|
||||
viewModel.onImageCaptured(media)
|
||||
navigateToMediaSend(recipient!!)
|
||||
})
|
||||
}
|
||||
|
||||
override fun getDisplayRotation(): Int {
|
||||
return windowManager.defaultDisplay.rotation
|
||||
}
|
||||
|
||||
private fun initializeCountButtonObserver() {
|
||||
viewModel.getCountButtonState().observe(
|
||||
this
|
||||
) { buttonState: CountButtonState? ->
|
||||
if (buttonState == null) return@observe
|
||||
countButtonText!!.text = buttonState.count.toString()
|
||||
countButton!!.isEnabled = buttonState.isVisible
|
||||
animateButtonVisibility(
|
||||
countButton!!,
|
||||
countButton!!.visibility,
|
||||
if (buttonState.isVisible) View.VISIBLE else View.GONE
|
||||
)
|
||||
if (buttonState.count > 0) {
|
||||
countButton!!.setOnClickListener { v: View? ->
|
||||
navigateToMediaSend(
|
||||
recipient!!
|
||||
)
|
||||
}
|
||||
if (buttonState.isVisible) {
|
||||
animateButtonTextChange(countButton!!)
|
||||
}
|
||||
} else {
|
||||
countButton!!.setOnClickListener(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeCameraButtonObserver() {
|
||||
viewModel.getCameraButtonVisibility().observe(
|
||||
this
|
||||
) { visible: Boolean? ->
|
||||
if (visible == null) return@observe
|
||||
animateButtonVisibility(
|
||||
cameraButton!!,
|
||||
cameraButton!!.visibility,
|
||||
if (visible) View.VISIBLE else View.GONE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeErrorObserver() {
|
||||
viewModel.getError().observe(
|
||||
this
|
||||
) { error: MediaSendViewModel.Error? ->
|
||||
if (error == null) return@observe
|
||||
when (error) {
|
||||
MediaSendViewModel.Error.ITEM_TOO_LARGE -> Toast.makeText(
|
||||
this,
|
||||
R.string.attachmentsErrorSize,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
|
||||
MediaSendViewModel.Error.TOO_MANY_ITEMS -> // In modern session we'll say you can't sent more than 32 items, but if we ever want
|
||||
// the exact count of how many items the user attempted to send it's: viewModel.getMaxSelection()
|
||||
Toast.makeText(
|
||||
this,
|
||||
getString(R.string.attachmentsErrorNumber),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToMediaSend(recipient: Recipient) {
|
||||
val fragment = MediaSendFragment.newInstance(recipient)
|
||||
var backstackTag: String? = null
|
||||
|
||||
if (supportFragmentManager.findFragmentByTag(TAG_SEND) != null) {
|
||||
supportFragmentManager.popBackStack(TAG_SEND, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||
backstackTag = TAG_SEND
|
||||
}
|
||||
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(
|
||||
R.anim.slide_from_right,
|
||||
R.anim.slide_to_left,
|
||||
R.anim.slide_from_left,
|
||||
R.anim.slide_to_right
|
||||
)
|
||||
.replace(R.id.mediasend_fragment_container, fragment, TAG_SEND)
|
||||
.addToBackStack(backstackTag)
|
||||
.commit()
|
||||
}
|
||||
|
||||
private fun navigateToCamera() {
|
||||
val c = applicationContext
|
||||
val permanentDenialTxt = Phrase.from(c, R.string.permissionsCameraDenied)
|
||||
.put(APP_NAME_KEY, c.getString(R.string.app_name))
|
||||
.format().toString()
|
||||
val requireCameraPermissionsTxt = Phrase.from(c, R.string.cameraGrantAccessDescription)
|
||||
.put(APP_NAME_KEY, c.getString(R.string.app_name))
|
||||
.format().toString()
|
||||
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.withPermanentDenialDialog(permanentDenialTxt)
|
||||
.onAllGranted {
|
||||
val fragment = orCreateCameraFragment
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(
|
||||
R.anim.slide_from_right,
|
||||
R.anim.slide_to_left,
|
||||
R.anim.slide_from_left,
|
||||
R.anim.slide_to_right
|
||||
)
|
||||
.replace(
|
||||
R.id.mediasend_fragment_container,
|
||||
fragment,
|
||||
TAG_CAMERA
|
||||
)
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
}
|
||||
.onAnyDenied {
|
||||
Toast.makeText(
|
||||
this@MediaSendActivity,
|
||||
requireCameraPermissionsTxt,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
.execute()
|
||||
}
|
||||
|
||||
private val orCreateCameraFragment: Camera1Fragment
|
||||
get() {
|
||||
val fragment =
|
||||
supportFragmentManager.findFragmentByTag(TAG_CAMERA) as Camera1Fragment?
|
||||
|
||||
return fragment ?: Camera1Fragment.newInstance()
|
||||
}
|
||||
|
||||
private fun animateButtonVisibility(button: View, oldVisibility: Int, newVisibility: Int) {
|
||||
if (oldVisibility == newVisibility) return
|
||||
|
||||
if (button.animation != null) {
|
||||
button.clearAnimation()
|
||||
button.visibility = newVisibility
|
||||
} else if (newVisibility == View.VISIBLE) {
|
||||
button.visibility = View.VISIBLE
|
||||
|
||||
val animation: Animation = ScaleAnimation(
|
||||
0f,
|
||||
1f,
|
||||
0f,
|
||||
1f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f
|
||||
)
|
||||
animation.duration = 250
|
||||
animation.interpolator = OvershootInterpolator()
|
||||
button.startAnimation(animation)
|
||||
} else {
|
||||
val animation: Animation = ScaleAnimation(
|
||||
1f,
|
||||
0f,
|
||||
1f,
|
||||
0f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f
|
||||
)
|
||||
animation.duration = 150
|
||||
animation.interpolator = AccelerateDecelerateInterpolator()
|
||||
animation.setAnimationListener(object : SimpleAnimationListener() {
|
||||
override fun onAnimationEnd(animation: Animation) {
|
||||
button.clearAnimation()
|
||||
button.visibility = View.GONE
|
||||
}
|
||||
})
|
||||
|
||||
button.startAnimation(animation)
|
||||
}
|
||||
}
|
||||
|
||||
private fun animateButtonTextChange(button: View) {
|
||||
if (button.animation != null) {
|
||||
button.clearAnimation()
|
||||
}
|
||||
|
||||
val grow: Animation = ScaleAnimation(
|
||||
1f,
|
||||
1.3f,
|
||||
1f,
|
||||
1.3f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f
|
||||
)
|
||||
grow.duration = 125
|
||||
grow.interpolator = AccelerateInterpolator()
|
||||
grow.setAnimationListener(object : SimpleAnimationListener() {
|
||||
override fun onAnimationEnd(animation: Animation) {
|
||||
val shrink: Animation = ScaleAnimation(
|
||||
1.3f,
|
||||
1f,
|
||||
1.3f,
|
||||
1f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f
|
||||
)
|
||||
shrink.duration = 125
|
||||
shrink.interpolator = DecelerateInterpolator()
|
||||
button.startAnimation(shrink)
|
||||
}
|
||||
})
|
||||
|
||||
button.startAnimation(grow)
|
||||
}
|
||||
|
||||
override fun onRequestFullScreen(fullScreen: Boolean) {
|
||||
val sendFragment = supportFragmentManager.findFragmentByTag(TAG_SEND) as MediaSendFragment?
|
||||
if (sendFragment != null && sendFragment.isVisible) {
|
||||
sendFragment.onRequestFullScreen(fullScreen)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG: String = MediaSendActivity::class.java.simpleName
|
||||
|
||||
const val EXTRA_MEDIA: String = "media"
|
||||
const val EXTRA_MESSAGE: String = "message"
|
||||
|
||||
private const val KEY_ADDRESS = "address"
|
||||
private const val KEY_BODY = "body"
|
||||
private const val KEY_MEDIA = "media"
|
||||
private const val KEY_IS_CAMERA = "is_camera"
|
||||
|
||||
private const val TAG_FOLDER_PICKER = "folder_picker"
|
||||
private const val TAG_ITEM_PICKER = "item_picker"
|
||||
private const val TAG_SEND = "send"
|
||||
private const val TAG_CAMERA = "camera"
|
||||
|
||||
/**
|
||||
* Get an intent to launch the media send flow starting with the picker.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun buildGalleryIntent(context: Context, recipient: Recipient, body: String): Intent {
|
||||
val intent = Intent(context, MediaSendActivity::class.java)
|
||||
intent.putExtra(KEY_ADDRESS, recipient.address.serialize())
|
||||
intent.putExtra(KEY_BODY, body)
|
||||
return intent
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an intent to launch the media send flow starting with the camera.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun buildCameraIntent(context: Context, recipient: Recipient): Intent {
|
||||
val intent = buildGalleryIntent(context, recipient, "")
|
||||
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.
|
||||
*/
|
||||
fun buildEditorIntent(
|
||||
context: Context,
|
||||
media: List<Media>,
|
||||
recipient: Recipient,
|
||||
body: String
|
||||
): Intent {
|
||||
val intent = buildGalleryIntent(context, recipient, body)
|
||||
intent.putParcelableArrayListExtra(KEY_MEDIA, ArrayList(media))
|
||||
return intent
|
||||
}
|
||||
}
|
||||
}
|
@ -1,360 +0,0 @@
|
||||
package org.thoughtcrime.securesms.mediasend;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import com.annimon.stream.Stream;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.session.libsession.utilities.FileUtils;
|
||||
import org.session.libsession.utilities.Util;
|
||||
import org.session.libsignal.utilities.Log;
|
||||
import org.session.libsignal.utilities.guava.Optional;
|
||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||
|
||||
/**
|
||||
* Manages the observable datasets available in {@link MediaSendActivity}.
|
||||
*/
|
||||
class MediaSendViewModel extends ViewModel {
|
||||
|
||||
private static final String TAG = MediaSendViewModel.class.getSimpleName();
|
||||
|
||||
private static final int MAX_SELECTION = 32;
|
||||
|
||||
private final Application application;
|
||||
private final MediaRepository repository;
|
||||
private final MutableLiveData<List<Media>> selectedMedia;
|
||||
private final MutableLiveData<List<Media>> bucketMedia;
|
||||
private final MutableLiveData<Integer> position;
|
||||
private final MutableLiveData<String> bucketId;
|
||||
private final MutableLiveData<List<MediaFolder>> folders;
|
||||
private final MutableLiveData<CountButtonState> countButtonState;
|
||||
private final MutableLiveData<Boolean> cameraButtonVisibility;
|
||||
private final SingleLiveEvent<Error> error;
|
||||
private final Map<Uri, Object> savedDrawState;
|
||||
|
||||
private final MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints();
|
||||
|
||||
private CharSequence body;
|
||||
private CountButtonState.Visibility countButtonVisibility;
|
||||
private boolean sentMedia;
|
||||
private Optional<Media> lastImageCapture;
|
||||
|
||||
private MediaSendViewModel(@NonNull Application application, @NonNull MediaRepository repository) {
|
||||
this.application = application;
|
||||
this.repository = repository;
|
||||
this.selectedMedia = new MutableLiveData<>();
|
||||
this.bucketMedia = new MutableLiveData<>();
|
||||
this.position = new MutableLiveData<>();
|
||||
this.bucketId = new MutableLiveData<>();
|
||||
this.folders = new MutableLiveData<>();
|
||||
this.countButtonState = new MutableLiveData<>();
|
||||
this.cameraButtonVisibility = new MutableLiveData<>();
|
||||
this.error = new SingleLiveEvent<>();
|
||||
this.savedDrawState = new HashMap<>();
|
||||
this.countButtonVisibility = CountButtonState.Visibility.FORCED_OFF;
|
||||
this.lastImageCapture = Optional.absent();
|
||||
this.body = "";
|
||||
|
||||
position.setValue(-1);
|
||||
countButtonState.setValue(new CountButtonState(0, countButtonVisibility));
|
||||
cameraButtonVisibility.setValue(false);
|
||||
}
|
||||
|
||||
void onSelectedMediaChanged(@NonNull Context context, @NonNull List<Media> newMedia) {
|
||||
repository.getPopulatedMedia(context, newMedia, populatedMedia -> {
|
||||
Util.runOnMain(() -> {
|
||||
|
||||
List<Media> filteredMedia = getFilteredMedia(context, populatedMedia, mediaConstraints);
|
||||
|
||||
if (filteredMedia.size() != newMedia.size()) {
|
||||
error.setValue(Error.ITEM_TOO_LARGE);
|
||||
} else if (filteredMedia.size() > MAX_SELECTION) {
|
||||
filteredMedia = filteredMedia.subList(0, MAX_SELECTION);
|
||||
error.setValue(Error.TOO_MANY_ITEMS);
|
||||
}
|
||||
|
||||
if (filteredMedia.size() > 0) {
|
||||
String computedId = Stream.of(filteredMedia)
|
||||
.skip(1)
|
||||
.reduce(filteredMedia.get(0).getBucketId().or(Media.ALL_MEDIA_BUCKET_ID), (id, m) -> {
|
||||
if (Util.equals(id, m.getBucketId().or(Media.ALL_MEDIA_BUCKET_ID))) {
|
||||
return id;
|
||||
} else {
|
||||
return Media.ALL_MEDIA_BUCKET_ID;
|
||||
}
|
||||
});
|
||||
bucketId.setValue(computedId);
|
||||
} else {
|
||||
bucketId.setValue(Media.ALL_MEDIA_BUCKET_ID);
|
||||
countButtonVisibility = CountButtonState.Visibility.CONDITIONAL;
|
||||
}
|
||||
|
||||
selectedMedia.setValue(filteredMedia);
|
||||
countButtonState.setValue(new CountButtonState(filteredMedia.size(), countButtonVisibility));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void onSingleMediaSelected(@NonNull Context context, @NonNull Media media) {
|
||||
repository.getPopulatedMedia(context, Collections.singletonList(media), populatedMedia -> {
|
||||
Util.runOnMain(() -> {
|
||||
List<Media> filteredMedia = getFilteredMedia(context, populatedMedia, mediaConstraints);
|
||||
|
||||
if (filteredMedia.isEmpty()) {
|
||||
error.setValue(Error.ITEM_TOO_LARGE);
|
||||
bucketId.setValue(Media.ALL_MEDIA_BUCKET_ID);
|
||||
} else {
|
||||
bucketId.setValue(filteredMedia.get(0).getBucketId().or(Media.ALL_MEDIA_BUCKET_ID));
|
||||
}
|
||||
|
||||
countButtonVisibility = CountButtonState.Visibility.FORCED_OFF;
|
||||
|
||||
selectedMedia.setValue(filteredMedia);
|
||||
countButtonState.setValue(new CountButtonState(filteredMedia.size(), countButtonVisibility));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void onMultiSelectStarted() {
|
||||
countButtonVisibility = CountButtonState.Visibility.FORCED_ON;
|
||||
countButtonState.setValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
|
||||
}
|
||||
|
||||
void onImageEditorStarted() {
|
||||
countButtonVisibility = CountButtonState.Visibility.FORCED_OFF;
|
||||
countButtonState.setValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
|
||||
cameraButtonVisibility.setValue(false);
|
||||
}
|
||||
|
||||
void onCameraStarted() {
|
||||
countButtonVisibility = CountButtonState.Visibility.CONDITIONAL;
|
||||
countButtonState.setValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
|
||||
cameraButtonVisibility.setValue(false);
|
||||
}
|
||||
|
||||
void onItemPickerStarted() {
|
||||
countButtonVisibility = CountButtonState.Visibility.CONDITIONAL;
|
||||
countButtonState.setValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
|
||||
cameraButtonVisibility.setValue(true);
|
||||
}
|
||||
|
||||
void onFolderPickerStarted() {
|
||||
countButtonVisibility = CountButtonState.Visibility.CONDITIONAL;
|
||||
countButtonState.setValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
|
||||
cameraButtonVisibility.setValue(true);
|
||||
}
|
||||
|
||||
void onBodyChanged(@NonNull CharSequence body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
void onFolderSelected(@NonNull String bucketId) {
|
||||
this.bucketId.setValue(bucketId);
|
||||
bucketMedia.setValue(Collections.emptyList());
|
||||
}
|
||||
|
||||
void onPageChanged(int position) {
|
||||
if (position < 0 || position >= getSelectedMediaOrDefault().size()) {
|
||||
Log.w(TAG, "Tried to move to an out-of-bounds item. Size: " + getSelectedMediaOrDefault().size() + ", position: " + position);
|
||||
return;
|
||||
}
|
||||
|
||||
this.position.setValue(position);
|
||||
}
|
||||
|
||||
void onMediaItemRemoved(@NonNull Context context, int position) {
|
||||
if (position < 0 || position >= getSelectedMediaOrDefault().size()) {
|
||||
Log.w(TAG, "Tried to remove an out-of-bounds item. Size: " + getSelectedMediaOrDefault().size() + ", position: " + position);
|
||||
return;
|
||||
}
|
||||
|
||||
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<>();
|
||||
}
|
||||
|
||||
if (selected.size() >= MAX_SELECTION) {
|
||||
error.setValue(Error.TOO_MANY_ITEMS);
|
||||
return;
|
||||
}
|
||||
|
||||
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 saveDrawState(@NonNull Map<Uri, Object> state) {
|
||||
savedDrawState.clear();
|
||||
savedDrawState.putAll(state);
|
||||
}
|
||||
|
||||
void onSendClicked() {
|
||||
sentMedia = true;
|
||||
}
|
||||
|
||||
@NonNull Map<Uri, Object> getDrawState() {
|
||||
return savedDrawState;
|
||||
}
|
||||
|
||||
@NonNull LiveData<List<Media>> getSelectedMedia() {
|
||||
return selectedMedia;
|
||||
}
|
||||
|
||||
@NonNull LiveData<List<Media>> getMediaInBucket(@NonNull Context context, @NonNull String bucketId) {
|
||||
repository.getMediaInBucket(context, bucketId, bucketMedia::postValue);
|
||||
return bucketMedia;
|
||||
}
|
||||
|
||||
@NonNull LiveData<List<MediaFolder>> getFolders(@NonNull Context context) {
|
||||
repository.getFolders(context, folders::postValue);
|
||||
return folders;
|
||||
}
|
||||
|
||||
@NonNull LiveData<CountButtonState> getCountButtonState() {
|
||||
return countButtonState;
|
||||
}
|
||||
|
||||
@NonNull LiveData<Boolean> getCameraButtonVisibility() {
|
||||
return cameraButtonVisibility;
|
||||
}
|
||||
|
||||
@NonNull CharSequence getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
@NonNull LiveData<Integer> getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@NonNull LiveData<String> getBucketId() {
|
||||
return bucketId;
|
||||
}
|
||||
|
||||
@NonNull LiveData<Error> getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
int getMaxSelection() {
|
||||
return MAX_SELECTION;
|
||||
}
|
||||
|
||||
private @NonNull List<Media> getSelectedMediaOrDefault() {
|
||||
return selectedMedia.getValue() == null ? Collections.emptyList()
|
||||
: selectedMedia.getValue();
|
||||
}
|
||||
|
||||
private @NonNull List<Media> getFilteredMedia(@NonNull Context context, @NonNull List<Media> media, @NonNull MediaConstraints mediaConstraints) {
|
||||
return Stream.of(media).filter(m -> MediaUtil.isGif(m.getMimeType()) ||
|
||||
MediaUtil.isImageType(m.getMimeType()) ||
|
||||
MediaUtil.isVideoType(m.getMimeType()))
|
||||
.filter(m -> {
|
||||
return (MediaUtil.isImageType(m.getMimeType()) && !MediaUtil.isGif(m.getMimeType())) ||
|
||||
(MediaUtil.isGif(m.getMimeType()) && m.getSize() < mediaConstraints.getGifMaxSize(context)) ||
|
||||
(MediaUtil.isVideoType(m.getMimeType()) && m.getSize() < mediaConstraints.getVideoMaxSize(context));
|
||||
}).toList();
|
||||
|
||||
}
|
||||
|
||||
@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, TOO_MANY_ITEMS
|
||||
}
|
||||
|
||||
static class CountButtonState {
|
||||
private final int count;
|
||||
private final Visibility visibility;
|
||||
|
||||
private CountButtonState(int count, @NonNull Visibility visibility) {
|
||||
this.count = count;
|
||||
this.visibility = visibility;
|
||||
}
|
||||
|
||||
int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
boolean isVisible() {
|
||||
switch (visibility) {
|
||||
case FORCED_ON: return true;
|
||||
case FORCED_OFF: return false;
|
||||
case CONDITIONAL: return count > 0;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
enum Visibility {
|
||||
CONDITIONAL, FORCED_ON, FORCED_OFF
|
||||
}
|
||||
}
|
||||
|
||||
static class Factory extends ViewModelProvider.NewInstanceFactory {
|
||||
|
||||
private final Application application;
|
||||
private final MediaRepository 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(application, repository));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,367 @@
|
||||
package org.thoughtcrime.securesms.mediasend
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.annimon.stream.Stream
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import org.session.libsession.utilities.Util.equals
|
||||
import org.session.libsession.utilities.Util.runOnMain
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.guava.Optional
|
||||
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 java.util.LinkedList
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Manages the observable datasets available in [MediaSendActivity].
|
||||
*/
|
||||
|
||||
@HiltViewModel
|
||||
internal class MediaSendViewModel @Inject constructor(
|
||||
private val application: Application
|
||||
) : ViewModel() {
|
||||
private val selectedMedia: MutableLiveData<List<Media>?>
|
||||
private val bucketMedia: MutableLiveData<List<Media>>
|
||||
private val position: MutableLiveData<Int>
|
||||
private val bucketId: MutableLiveData<String>
|
||||
private val folders: MutableLiveData<List<MediaFolder>>
|
||||
private val countButtonState: MutableLiveData<CountButtonState>
|
||||
private val cameraButtonVisibility: MutableLiveData<Boolean>
|
||||
private val error: SingleLiveEvent<Error>
|
||||
private val savedDrawState: MutableMap<Uri, Any>
|
||||
|
||||
private val mediaConstraints: MediaConstraints = MediaConstraints.getPushMediaConstraints()
|
||||
private val repository: MediaRepository = MediaRepository()
|
||||
|
||||
var body: CharSequence
|
||||
private set
|
||||
private var countButtonVisibility: CountButtonState.Visibility
|
||||
private var sentMedia: Boolean = false
|
||||
private var lastImageCapture: Optional<Media>
|
||||
|
||||
init {
|
||||
this.selectedMedia = MutableLiveData()
|
||||
this.bucketMedia = MutableLiveData()
|
||||
this.position = MutableLiveData()
|
||||
this.bucketId = MutableLiveData()
|
||||
this.folders = MutableLiveData()
|
||||
this.countButtonState = MutableLiveData()
|
||||
this.cameraButtonVisibility = MutableLiveData()
|
||||
this.error = SingleLiveEvent()
|
||||
this.savedDrawState = HashMap()
|
||||
this.countButtonVisibility = CountButtonState.Visibility.FORCED_OFF
|
||||
this.lastImageCapture = Optional.absent()
|
||||
this.body = ""
|
||||
|
||||
position.value = -1
|
||||
countButtonState.value = CountButtonState(0, countButtonVisibility)
|
||||
cameraButtonVisibility.value = false
|
||||
}
|
||||
|
||||
fun onSelectedMediaChanged(context: Context, newMedia: List<Media?>) {
|
||||
repository.getPopulatedMedia(context, newMedia,
|
||||
{ populatedMedia: List<Media> ->
|
||||
runOnMain(
|
||||
{
|
||||
var filteredMedia: List<Media> =
|
||||
getFilteredMedia(context, populatedMedia, mediaConstraints)
|
||||
if (filteredMedia.size != newMedia.size) {
|
||||
error.setValue(Error.ITEM_TOO_LARGE)
|
||||
} else if (filteredMedia.size > MAX_SELECTED_FILES) {
|
||||
filteredMedia = filteredMedia.subList(0, MAX_SELECTED_FILES)
|
||||
error.setValue(Error.TOO_MANY_ITEMS)
|
||||
}
|
||||
|
||||
if (filteredMedia.size > 0) {
|
||||
val computedId: String = Stream.of(filteredMedia)
|
||||
.skip(1)
|
||||
.reduce(filteredMedia.get(0).bucketId.or(Media.ALL_MEDIA_BUCKET_ID),
|
||||
{ id: String?, m: Media ->
|
||||
if (equals(id, m.bucketId.or(Media.ALL_MEDIA_BUCKET_ID))) {
|
||||
return@reduce id
|
||||
} else {
|
||||
return@reduce Media.ALL_MEDIA_BUCKET_ID
|
||||
}
|
||||
})
|
||||
bucketId.setValue(computedId)
|
||||
} else {
|
||||
bucketId.setValue(Media.ALL_MEDIA_BUCKET_ID)
|
||||
countButtonVisibility = CountButtonState.Visibility.CONDITIONAL
|
||||
}
|
||||
|
||||
selectedMedia.setValue(filteredMedia)
|
||||
countButtonState.setValue(
|
||||
CountButtonState(
|
||||
filteredMedia.size,
|
||||
countButtonVisibility
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fun onSingleMediaSelected(context: Context, media: Media) {
|
||||
repository.getPopulatedMedia(context, listOf(media),
|
||||
{ populatedMedia: List<Media> ->
|
||||
runOnMain(
|
||||
{
|
||||
val filteredMedia: List<Media> =
|
||||
getFilteredMedia(context, populatedMedia, mediaConstraints)
|
||||
if (filteredMedia.isEmpty()) {
|
||||
error.setValue(Error.ITEM_TOO_LARGE)
|
||||
bucketId.setValue(Media.ALL_MEDIA_BUCKET_ID)
|
||||
} else {
|
||||
bucketId.setValue(filteredMedia.get(0).bucketId.or(Media.ALL_MEDIA_BUCKET_ID))
|
||||
}
|
||||
|
||||
countButtonVisibility = CountButtonState.Visibility.FORCED_OFF
|
||||
|
||||
selectedMedia.value = filteredMedia
|
||||
countButtonState.setValue(
|
||||
CountButtonState(
|
||||
filteredMedia.size,
|
||||
countButtonVisibility
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fun onMultiSelectStarted() {
|
||||
countButtonVisibility = CountButtonState.Visibility.FORCED_ON
|
||||
countButtonState.value =
|
||||
CountButtonState(selectedMediaOrDefault.size, countButtonVisibility)
|
||||
}
|
||||
|
||||
fun onImageEditorStarted() {
|
||||
countButtonVisibility = CountButtonState.Visibility.FORCED_OFF
|
||||
countButtonState.value =
|
||||
CountButtonState(selectedMediaOrDefault.size, countButtonVisibility)
|
||||
cameraButtonVisibility.value = false
|
||||
}
|
||||
|
||||
fun onCameraStarted() {
|
||||
countButtonVisibility = CountButtonState.Visibility.CONDITIONAL
|
||||
countButtonState.value =
|
||||
CountButtonState(selectedMediaOrDefault.size, countButtonVisibility)
|
||||
cameraButtonVisibility.value = false
|
||||
}
|
||||
|
||||
fun onItemPickerStarted() {
|
||||
countButtonVisibility = CountButtonState.Visibility.CONDITIONAL
|
||||
countButtonState.value =
|
||||
CountButtonState(selectedMediaOrDefault.size, countButtonVisibility)
|
||||
cameraButtonVisibility.value = true
|
||||
}
|
||||
|
||||
fun onFolderPickerStarted() {
|
||||
countButtonVisibility = CountButtonState.Visibility.CONDITIONAL
|
||||
countButtonState.value =
|
||||
CountButtonState(selectedMediaOrDefault.size, countButtonVisibility)
|
||||
cameraButtonVisibility.value = true
|
||||
}
|
||||
|
||||
fun onBodyChanged(body: CharSequence) {
|
||||
this.body = body
|
||||
}
|
||||
|
||||
fun onFolderSelected(bucketId: String) {
|
||||
this.bucketId.value = bucketId
|
||||
bucketMedia.value =
|
||||
emptyList()
|
||||
}
|
||||
|
||||
fun onPageChanged(position: Int) {
|
||||
if (position < 0 || position >= selectedMediaOrDefault.size) {
|
||||
Log.w(TAG,
|
||||
"Tried to move to an out-of-bounds item. Size: " + selectedMediaOrDefault.size + ", position: " + position
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
this.position.value = position
|
||||
}
|
||||
|
||||
fun onMediaItemRemoved(context: Context, position: Int) {
|
||||
if (position < 0 || position >= selectedMediaOrDefault.size) {
|
||||
Log.w(
|
||||
TAG,
|
||||
"Tried to remove an out-of-bounds item. Size: " + selectedMediaOrDefault.size + ", position: " + position
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val updatedList = selectedMediaOrDefault.toMutableList()
|
||||
val removed: Media = updatedList.removeAt(position)
|
||||
|
||||
if (BlobProvider.isAuthority(removed.uri)) {
|
||||
BlobProvider.getInstance().delete(context, removed.uri)
|
||||
}
|
||||
|
||||
selectedMedia.setValue(updatedList)
|
||||
}
|
||||
|
||||
fun onImageCaptured(media: Media) {
|
||||
var selected: MutableList<Media>? = selectedMedia.value?.toMutableList()
|
||||
|
||||
if (selected == null) {
|
||||
selected = LinkedList()
|
||||
}
|
||||
|
||||
if (selected.size >= MAX_SELECTED_FILES) {
|
||||
error.setValue(Error.TOO_MANY_ITEMS)
|
||||
return
|
||||
}
|
||||
|
||||
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(CountButtonState(selected.size, countButtonVisibility))
|
||||
}
|
||||
|
||||
fun onImageCaptureUndo(context: Context) {
|
||||
val selected: MutableList<Media> = selectedMediaOrDefault.toMutableList()
|
||||
|
||||
if (lastImageCapture.isPresent && selected.contains(lastImageCapture.get()) && selected.size == 1) {
|
||||
selected.remove(lastImageCapture.get())
|
||||
selectedMedia.value = selected
|
||||
countButtonState.value = CountButtonState(selected.size, countButtonVisibility)
|
||||
BlobProvider.getInstance().delete(context, lastImageCapture.get().uri)
|
||||
}
|
||||
}
|
||||
|
||||
fun saveDrawState(state: Map<Uri, Any>) {
|
||||
savedDrawState.clear()
|
||||
savedDrawState.putAll(state)
|
||||
}
|
||||
|
||||
fun onSendClicked() {
|
||||
sentMedia = true
|
||||
}
|
||||
|
||||
val drawState: Map<Uri, Any>
|
||||
get() = savedDrawState
|
||||
|
||||
fun getSelectedMedia(): LiveData<List<Media>?> {
|
||||
return selectedMedia
|
||||
}
|
||||
|
||||
fun getMediaInBucket(context: Context, bucketId: String): LiveData<List<Media>> {
|
||||
repository.getMediaInBucket(context, bucketId,
|
||||
{ value: List<Media> -> bucketMedia.postValue(value) })
|
||||
return bucketMedia
|
||||
}
|
||||
|
||||
fun getFolders(context: Context): LiveData<List<MediaFolder>> {
|
||||
repository.getFolders(context,
|
||||
{ value: List<MediaFolder> -> folders.postValue(value) })
|
||||
return folders
|
||||
}
|
||||
|
||||
fun getCountButtonState(): LiveData<CountButtonState> {
|
||||
return countButtonState
|
||||
}
|
||||
|
||||
fun getCameraButtonVisibility(): LiveData<Boolean> {
|
||||
return cameraButtonVisibility
|
||||
}
|
||||
|
||||
fun getPosition(): LiveData<Int> {
|
||||
return position
|
||||
}
|
||||
|
||||
fun getBucketId(): LiveData<String> {
|
||||
return bucketId
|
||||
}
|
||||
|
||||
fun getError(): LiveData<Error> {
|
||||
return error
|
||||
}
|
||||
|
||||
private val selectedMediaOrDefault: List<Media>
|
||||
get() = if (selectedMedia.value == null) emptyList() else
|
||||
selectedMedia.value!!
|
||||
|
||||
private fun getFilteredMedia(
|
||||
context: Context,
|
||||
media: List<Media>,
|
||||
mediaConstraints: MediaConstraints
|
||||
): List<Media> {
|
||||
return Stream.of(media).filter(
|
||||
{ m: Media ->
|
||||
MediaUtil.isGif(m.mimeType) ||
|
||||
MediaUtil.isImageType(m.mimeType) ||
|
||||
MediaUtil.isVideoType(m.mimeType)
|
||||
})
|
||||
.filter({ m: Media ->
|
||||
(MediaUtil.isImageType(m.mimeType) && !MediaUtil.isGif(m.mimeType)) ||
|
||||
(MediaUtil.isGif(m.mimeType) && m.size < mediaConstraints.getGifMaxSize(
|
||||
context
|
||||
)) ||
|
||||
(MediaUtil.isVideoType(m.mimeType) && m.size < mediaConstraints.getVideoMaxSize(
|
||||
context
|
||||
))
|
||||
}).toList()
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
if (!sentMedia) {
|
||||
Stream.of(selectedMediaOrDefault)
|
||||
.map({ obj: Media -> obj.uri })
|
||||
.filter({ uri: Uri? ->
|
||||
BlobProvider.isAuthority(
|
||||
uri!!
|
||||
)
|
||||
})
|
||||
.forEach({ uri: Uri? ->
|
||||
BlobProvider.getInstance().delete(
|
||||
application.applicationContext, uri!!
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
internal enum class Error {
|
||||
ITEM_TOO_LARGE, TOO_MANY_ITEMS
|
||||
}
|
||||
|
||||
internal class CountButtonState(val count: Int, private val visibility: Visibility) {
|
||||
val isVisible: Boolean
|
||||
get() {
|
||||
when (visibility) {
|
||||
Visibility.FORCED_ON -> return true
|
||||
Visibility.FORCED_OFF -> return false
|
||||
Visibility.CONDITIONAL -> return count > 0
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
internal enum class Visibility {
|
||||
CONDITIONAL, FORCED_ON, FORCED_OFF
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG: String = MediaSendViewModel::class.java.simpleName
|
||||
|
||||
// the maximum amount of files that can be selected to send as attachment
|
||||
const val MAX_SELECTED_FILES: Int = 32
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue