From b134b82daa8fe9c22c73d52232173a0671b6bb74 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Fri, 7 Feb 2025 01:16:35 +0200 Subject: [PATCH] Konverting MediaSendActivity (#921) --- .../mediasend/MediaSendActivity.java | 446 -------------- .../securesms/mediasend/MediaSendActivity.kt | 557 ++++++++++++++++++ 2 files changed, 557 insertions(+), 446 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java deleted file mode 100644 index 3a1e9da5c7..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java +++ /dev/null @@ -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, - @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 = 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, @NonNull String message) { - viewModel.onSendClicked(); - - ArrayList 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); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt new file mode 100644 index 0000000000..26f50bcf9b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt @@ -0,0 +1,557 @@ +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.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.ViewModelProvider +import com.squareup.phrase.Phrase +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. + */ +class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragment.Controller, + MediaPickerItemFragment.Controller, MediaSendFragment.Controller, + ImageEditorFragment.Controller, + Camera1Fragment.Controller { + private var recipient: Recipient? = null + private var viewModel: MediaSendViewModel? = null + + 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) + + viewModel = ViewModelProvider( + this, MediaSendViewModel.Factory( + application, MediaRepository() + ) + ).get( + MediaSendViewModel::class.java + ) + recipient = Recipient.from( + this, fromSerialized( + intent.getStringExtra(KEY_ADDRESS)!! + ), true + ) + + viewModel!!.onBodyChanged(intent.getStringExtra(KEY_BODY)!!) + + val media: List? = 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 = viewModel!!.maxSelection + if (viewModel!!.selectedMedia.value != null && viewModel!!.selectedMedia.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, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults) + } + + override fun onFolderSelected(folder: MediaFolder) { + if (viewModel == null) { + return + } + + viewModel!!.onFolderSelected(folder.bucketId) + + val fragment = MediaPickerItemFragment.newInstance( + folder.bucketId, + folder.title, + viewModel!!.maxSelection + ) + 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, "", viewModel!!.maxSelection) + + 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, 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(Media.ALL_MEDIA_BUCKET_ID), + Optional.absent() + ) + } 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!!.countButtonState.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!!.cameraButtonVisibility.observe( + this + ) { visible: Boolean? -> + if (visible == null) return@observe + animateButtonVisibility( + cameraButton!!, + cameraButton!!.visibility, + if (visible) View.VISIBLE else View.GONE + ) + } + } + + private fun initializeErrorObserver() { + viewModel!!.error.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, + recipient: Recipient, + body: String + ): Intent { + val intent = buildGalleryIntent(context, recipient, body) + intent.putParcelableArrayListExtra(KEY_MEDIA, ArrayList(media)) + return intent + } + } +}