Fix null pointer exception when presenting latest media thumbnail.

This commit is contained in:
Alex Hart 2022-07-27 12:49:16 -03:00 committed by Cody Henthorne
parent 053b0eabde
commit c907a01077
6 changed files with 38 additions and 45 deletions

View file

@ -13,7 +13,6 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.LiveData;
import org.signal.imageeditor.core.model.EditorModel;
import org.thoughtcrime.securesms.R;
@ -22,13 +21,14 @@ import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.scribbles.ImageEditorFragment;
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
import org.thoughtcrime.securesms.util.MediaUtil;
import java.io.FileDescriptor;
import java.util.Collections;
import java.util.Optional;
import io.reactivex.rxjava3.core.Flowable;
public class AvatarSelectionActivity extends AppCompatActivity implements CameraFragment.Controller, ImageEditorFragment.Controller, MediaGalleryFragment.Callbacks {
private static final Point AVATAR_DIMENSIONS = new Point(AvatarHelper.AVATAR_DIMENSIONS, AvatarHelper.AVATAR_DIMENSIONS);
@ -131,8 +131,8 @@ public class AvatarSelectionActivity extends AppCompatActivity implements Camera
}
@Override
public @NonNull LiveData<Optional<Media>> getMostRecentMediaItem() {
return new DefaultValueLiveData<>(Optional.empty());
public @NonNull Flowable<Optional<Media>> getMostRecentMediaItem() {
return Flowable.just(Optional.empty());
}
@Override

View file

@ -27,7 +27,6 @@ import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.Observer;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.MultiTransformation;
@ -51,8 +50,8 @@ import org.thoughtcrime.securesms.util.Stopwatch;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.io.ByteArrayOutputStream;
import java.util.Optional;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.Disposable;
/**
@ -75,8 +74,8 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment,
private Camera1Controller.Properties properties;
private RotationListener rotationListener;
private Disposable rotationListenerDisposable;
private Disposable mostRecentItemDisposable = Disposable.disposed();
private final Observer<Optional<Media>> thumbObserver = this::presentRecentItemThumbnail;
private boolean isThumbAvailable;
private boolean isMediaSelected;
@ -192,7 +191,7 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment,
@Override
public void onDestroyView() {
super.onDestroyView();
controller.getMostRecentMediaItem().removeObserver(thumbObserver);
mostRecentItemDisposable.dispose();
}
@Override
@ -271,19 +270,13 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment,
controller.onCameraError();
}
private void presentRecentItemThumbnail(Optional<Media> media) {
if (media == null) {
isThumbAvailable = false;
updateGalleryVisibility();
return;
}
private void presentRecentItemThumbnail(@Nullable Media media) {
ImageView thumbnail = controlsContainer.findViewById(R.id.camera_gallery_button);
if (media.isPresent()) {
if (media != null) {
thumbnail.setVisibility(View.VISIBLE);
Glide.with(this)
.load(new DecryptableUri(media.get().getUri()))
.load(new DecryptableUri(media.getUri()))
.centerCrop()
.into(thumbnail);
} else {
@ -291,7 +284,7 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment,
thumbnail.setImageResource(0);
}
isThumbAvailable = media.isPresent();
isThumbAvailable = media != null;
updateGalleryVisibility();
}
@ -331,8 +324,10 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment,
View countButton = requireView().findViewById(R.id.camera_review_button);
View toggleSpacer = requireView().findViewById(R.id.toggle_spacer);
controller.getMostRecentMediaItem().removeObserver(thumbObserver);
controller.getMostRecentMediaItem().observeForever(thumbObserver);
mostRecentItemDisposable.dispose();
mostRecentItemDisposable = controller.getMostRecentMediaItem()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(item -> presentRecentItemThumbnail(item.orElse(null)));
if (toggleSpacer != null) {
if (Stories.isFeatureEnabled()) {

View file

@ -5,7 +5,6 @@ import android.content.res.Configuration;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.LiveData;
import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil;
import org.thoughtcrime.securesms.mms.MediaConstraints;
@ -13,6 +12,8 @@ import org.thoughtcrime.securesms.mms.MediaConstraints;
import java.io.FileDescriptor;
import java.util.Optional;
import io.reactivex.rxjava3.core.Flowable;
public interface CameraFragment {
float PORTRAIT_ASPECT_RATIO = 9 / 16f;
@ -54,7 +55,7 @@ public interface CameraFragment {
void onVideoCaptureError();
void onGalleryClicked();
void onCameraCountButtonClicked();
@NonNull LiveData<Optional<Media>> getMostRecentMediaItem();
@NonNull Flowable<Optional<Media>> getMostRecentMediaItem();
@NonNull MediaConstraints getMediaConstraints();
int getMaxVideoDuration();
}

View file

@ -31,7 +31,6 @@ import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.camera.view.SignalCameraView;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.Observer;
import com.bumptech.glide.Glide;
import com.bumptech.glide.util.Executors;
@ -57,7 +56,9 @@ import org.thoughtcrime.securesms.video.VideoUtil;
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.Optional;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.Disposable;
/**
* Camera captured implemented using the CameraX SDK, which uses Camera2 under the hood. Should be
@ -74,8 +75,8 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
private Controller controller;
private View selfieFlash;
private MemoryFileDescriptor videoFileDescriptor;
private Disposable mostRecentItemDisposable = Disposable.disposed();
private final Observer<Optional<Media>> thumbObserver = this::presentRecentItemThumbnail;
private boolean isThumbAvailable;
private boolean isMediaSelected;
@ -161,7 +162,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
@Override
public void onDestroyView() {
super.onDestroyView();
controller.getMostRecentMediaItem().removeObserver(thumbObserver);
mostRecentItemDisposable.dispose();
closeVideoFileDescriptor();
requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
@ -221,19 +222,13 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
initControls();
}
private void presentRecentItemThumbnail(Optional<Media> media) {
if (media == null) {
isThumbAvailable = false;
updateGalleryVisibility();
return;
}
private void presentRecentItemThumbnail(@Nullable Media media) {
ImageView thumbnail = controlsContainer.findViewById(R.id.camera_gallery_button);
if (media.isPresent()) {
if (media != null) {
thumbnail.setVisibility(View.VISIBLE);
Glide.with(this)
.load(new DecryptableUri(media.get().getUri()))
.load(new DecryptableUri(media.getUri()))
.centerCrop()
.into(thumbnail);
} else {
@ -241,7 +236,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
thumbnail.setImageResource(0);
}
isThumbAvailable = media.isPresent();
isThumbAvailable = media != null;
updateGalleryVisibility();
}
@ -292,8 +287,10 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
}
}
controller.getMostRecentMediaItem().removeObserver(thumbObserver);
controller.getMostRecentMediaItem().observeForever(thumbObserver);
mostRecentItemDisposable.dispose();
mostRecentItemDisposable = controller.getMostRecentMediaItem()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(item -> presentRecentItemThumbnail(item.orElse(null)));
selfieFlash = requireView().findViewById(R.id.camera_selfie_flash);

View file

@ -6,9 +6,9 @@ import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData
import androidx.navigation.fragment.findNavController
import app.cash.exhaustive.Exhaustive
import io.reactivex.rxjava3.core.Flowable
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.mediasend.CameraFragment
@ -149,7 +149,7 @@ class MediaCaptureFragment : Fragment(R.layout.fragment_container), CameraFragme
}
}
override fun getMostRecentMediaItem(): LiveData<Optional<Media>> {
override fun getMostRecentMediaItem(): Flowable<Optional<Media>> {
return viewModel.getMostRecentMedia()
}

View file

@ -1,18 +1,18 @@
package org.thoughtcrime.securesms.mediasend.v2.capture
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import io.reactivex.rxjava3.core.Flowable
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.util.SingleLiveEvent
import org.thoughtcrime.securesms.util.livedata.Store
import org.thoughtcrime.securesms.util.rx.RxStore
import java.io.FileDescriptor
import java.util.Optional
class MediaCaptureViewModel(private val repository: MediaCaptureRepository) : ViewModel() {
private val store: Store<MediaCaptureState> = Store(MediaCaptureState())
private val store: RxStore<MediaCaptureState> = RxStore(MediaCaptureState())
private val internalEvents: SingleLiveEvent<MediaCaptureEvent> = SingleLiveEvent()
@ -34,8 +34,8 @@ class MediaCaptureViewModel(private val repository: MediaCaptureRepository) : Vi
repository.renderVideoToMedia(fd, this::onMediaRendered, this::onMediaRenderFailed)
}
fun getMostRecentMedia(): LiveData<Optional<Media>> {
return Transformations.map(store.stateLiveData) { Optional.ofNullable(it.mostRecentMedia) }
fun getMostRecentMedia(): Flowable<Optional<Media>> {
return store.stateFlowable.map { Optional.ofNullable(it.mostRecentMedia) }
}
private fun onMediaRendered(media: Media) {