Fix null pointer exception when presenting latest media thumbnail.
This commit is contained in:
parent
053b0eabde
commit
c907a01077
6 changed files with 38 additions and 45 deletions
|
@ -13,7 +13,6 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
|
|
||||||
import org.signal.imageeditor.core.model.EditorModel;
|
import org.signal.imageeditor.core.model.EditorModel;
|
||||||
import org.thoughtcrime.securesms.R;
|
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.profiles.AvatarHelper;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.scribbles.ImageEditorFragment;
|
import org.thoughtcrime.securesms.scribbles.ImageEditorFragment;
|
||||||
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
|
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.core.Flowable;
|
||||||
|
|
||||||
public class AvatarSelectionActivity extends AppCompatActivity implements CameraFragment.Controller, ImageEditorFragment.Controller, MediaGalleryFragment.Callbacks {
|
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);
|
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
|
@Override
|
||||||
public @NonNull LiveData<Optional<Media>> getMostRecentMediaItem() {
|
public @NonNull Flowable<Optional<Media>> getMostRecentMediaItem() {
|
||||||
return new DefaultValueLiveData<>(Optional.empty());
|
return Flowable.just(Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -27,7 +27,6 @@ import android.widget.ImageView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.lifecycle.Observer;
|
|
||||||
|
|
||||||
import com.bumptech.glide.Glide;
|
import com.bumptech.glide.Glide;
|
||||||
import com.bumptech.glide.load.MultiTransformation;
|
import com.bumptech.glide.load.MultiTransformation;
|
||||||
|
@ -51,8 +50,8 @@ import org.thoughtcrime.securesms.util.Stopwatch;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.rxjava3.disposables.Disposable;
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -75,8 +74,8 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment,
|
||||||
private Camera1Controller.Properties properties;
|
private Camera1Controller.Properties properties;
|
||||||
private RotationListener rotationListener;
|
private RotationListener rotationListener;
|
||||||
private Disposable rotationListenerDisposable;
|
private Disposable rotationListenerDisposable;
|
||||||
|
private Disposable mostRecentItemDisposable = Disposable.disposed();
|
||||||
|
|
||||||
private final Observer<Optional<Media>> thumbObserver = this::presentRecentItemThumbnail;
|
|
||||||
private boolean isThumbAvailable;
|
private boolean isThumbAvailable;
|
||||||
private boolean isMediaSelected;
|
private boolean isMediaSelected;
|
||||||
|
|
||||||
|
@ -192,7 +191,7 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment,
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
controller.getMostRecentMediaItem().removeObserver(thumbObserver);
|
mostRecentItemDisposable.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -271,19 +270,13 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment,
|
||||||
controller.onCameraError();
|
controller.onCameraError();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void presentRecentItemThumbnail(Optional<Media> media) {
|
private void presentRecentItemThumbnail(@Nullable Media media) {
|
||||||
if (media == null) {
|
|
||||||
isThumbAvailable = false;
|
|
||||||
updateGalleryVisibility();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageView thumbnail = controlsContainer.findViewById(R.id.camera_gallery_button);
|
ImageView thumbnail = controlsContainer.findViewById(R.id.camera_gallery_button);
|
||||||
|
|
||||||
if (media.isPresent()) {
|
if (media != null) {
|
||||||
thumbnail.setVisibility(View.VISIBLE);
|
thumbnail.setVisibility(View.VISIBLE);
|
||||||
Glide.with(this)
|
Glide.with(this)
|
||||||
.load(new DecryptableUri(media.get().getUri()))
|
.load(new DecryptableUri(media.getUri()))
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.into(thumbnail);
|
.into(thumbnail);
|
||||||
} else {
|
} else {
|
||||||
|
@ -291,7 +284,7 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment,
|
||||||
thumbnail.setImageResource(0);
|
thumbnail.setImageResource(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
isThumbAvailable = media.isPresent();
|
isThumbAvailable = media != null;
|
||||||
updateGalleryVisibility();
|
updateGalleryVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,8 +324,10 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment,
|
||||||
View countButton = requireView().findViewById(R.id.camera_review_button);
|
View countButton = requireView().findViewById(R.id.camera_review_button);
|
||||||
View toggleSpacer = requireView().findViewById(R.id.toggle_spacer);
|
View toggleSpacer = requireView().findViewById(R.id.toggle_spacer);
|
||||||
|
|
||||||
controller.getMostRecentMediaItem().removeObserver(thumbObserver);
|
mostRecentItemDisposable.dispose();
|
||||||
controller.getMostRecentMediaItem().observeForever(thumbObserver);
|
mostRecentItemDisposable = controller.getMostRecentMediaItem()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(item -> presentRecentItemThumbnail(item.orElse(null)));
|
||||||
|
|
||||||
if (toggleSpacer != null) {
|
if (toggleSpacer != null) {
|
||||||
if (Stories.isFeatureEnabled()) {
|
if (Stories.isFeatureEnabled()) {
|
||||||
|
|
|
@ -5,7 +5,6 @@ import android.content.res.Configuration;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil;
|
import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil;
|
||||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||||
|
@ -13,6 +12,8 @@ import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.core.Flowable;
|
||||||
|
|
||||||
public interface CameraFragment {
|
public interface CameraFragment {
|
||||||
|
|
||||||
float PORTRAIT_ASPECT_RATIO = 9 / 16f;
|
float PORTRAIT_ASPECT_RATIO = 9 / 16f;
|
||||||
|
@ -54,7 +55,7 @@ public interface CameraFragment {
|
||||||
void onVideoCaptureError();
|
void onVideoCaptureError();
|
||||||
void onGalleryClicked();
|
void onGalleryClicked();
|
||||||
void onCameraCountButtonClicked();
|
void onCameraCountButtonClicked();
|
||||||
@NonNull LiveData<Optional<Media>> getMostRecentMediaItem();
|
@NonNull Flowable<Optional<Media>> getMostRecentMediaItem();
|
||||||
@NonNull MediaConstraints getMediaConstraints();
|
@NonNull MediaConstraints getMediaConstraints();
|
||||||
int getMaxVideoDuration();
|
int getMaxVideoDuration();
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,6 @@ import androidx.camera.lifecycle.ProcessCameraProvider;
|
||||||
import androidx.camera.view.PreviewView;
|
import androidx.camera.view.PreviewView;
|
||||||
import androidx.camera.view.SignalCameraView;
|
import androidx.camera.view.SignalCameraView;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.lifecycle.Observer;
|
|
||||||
|
|
||||||
import com.bumptech.glide.Glide;
|
import com.bumptech.glide.Glide;
|
||||||
import com.bumptech.glide.util.Executors;
|
import com.bumptech.glide.util.Executors;
|
||||||
|
@ -57,7 +56,9 @@ import org.thoughtcrime.securesms.video.VideoUtil;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.IOException;
|
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
|
* 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 Controller controller;
|
||||||
private View selfieFlash;
|
private View selfieFlash;
|
||||||
private MemoryFileDescriptor videoFileDescriptor;
|
private MemoryFileDescriptor videoFileDescriptor;
|
||||||
|
private Disposable mostRecentItemDisposable = Disposable.disposed();
|
||||||
|
|
||||||
private final Observer<Optional<Media>> thumbObserver = this::presentRecentItemThumbnail;
|
|
||||||
private boolean isThumbAvailable;
|
private boolean isThumbAvailable;
|
||||||
private boolean isMediaSelected;
|
private boolean isMediaSelected;
|
||||||
|
|
||||||
|
@ -161,7 +162,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
controller.getMostRecentMediaItem().removeObserver(thumbObserver);
|
mostRecentItemDisposable.dispose();
|
||||||
closeVideoFileDescriptor();
|
closeVideoFileDescriptor();
|
||||||
requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
||||||
}
|
}
|
||||||
|
@ -221,19 +222,13 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
|
||||||
initControls();
|
initControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void presentRecentItemThumbnail(Optional<Media> media) {
|
private void presentRecentItemThumbnail(@Nullable Media media) {
|
||||||
if (media == null) {
|
|
||||||
isThumbAvailable = false;
|
|
||||||
updateGalleryVisibility();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageView thumbnail = controlsContainer.findViewById(R.id.camera_gallery_button);
|
ImageView thumbnail = controlsContainer.findViewById(R.id.camera_gallery_button);
|
||||||
|
|
||||||
if (media.isPresent()) {
|
if (media != null) {
|
||||||
thumbnail.setVisibility(View.VISIBLE);
|
thumbnail.setVisibility(View.VISIBLE);
|
||||||
Glide.with(this)
|
Glide.with(this)
|
||||||
.load(new DecryptableUri(media.get().getUri()))
|
.load(new DecryptableUri(media.getUri()))
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.into(thumbnail);
|
.into(thumbnail);
|
||||||
} else {
|
} else {
|
||||||
|
@ -241,7 +236,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
|
||||||
thumbnail.setImageResource(0);
|
thumbnail.setImageResource(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
isThumbAvailable = media.isPresent();
|
isThumbAvailable = media != null;
|
||||||
updateGalleryVisibility();
|
updateGalleryVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,8 +287,10 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.getMostRecentMediaItem().removeObserver(thumbObserver);
|
mostRecentItemDisposable.dispose();
|
||||||
controller.getMostRecentMediaItem().observeForever(thumbObserver);
|
mostRecentItemDisposable = controller.getMostRecentMediaItem()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(item -> presentRecentItemThumbnail(item.orElse(null)));
|
||||||
|
|
||||||
selfieFlash = requireView().findViewById(R.id.camera_selfie_flash);
|
selfieFlash = requireView().findViewById(R.id.camera_selfie_flash);
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,9 @@ import android.widget.Toast
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import app.cash.exhaustive.Exhaustive
|
import app.cash.exhaustive.Exhaustive
|
||||||
|
import io.reactivex.rxjava3.core.Flowable
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.mediasend.CameraFragment
|
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()
|
return viewModel.getMostRecentMedia()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
package org.thoughtcrime.securesms.mediasend.v2.capture
|
package org.thoughtcrime.securesms.mediasend.v2.capture
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Transformations
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import io.reactivex.rxjava3.core.Flowable
|
||||||
import org.thoughtcrime.securesms.mediasend.Media
|
import org.thoughtcrime.securesms.mediasend.Media
|
||||||
import org.thoughtcrime.securesms.util.SingleLiveEvent
|
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.io.FileDescriptor
|
||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
|
|
||||||
class MediaCaptureViewModel(private val repository: MediaCaptureRepository) : ViewModel() {
|
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()
|
private val internalEvents: SingleLiveEvent<MediaCaptureEvent> = SingleLiveEvent()
|
||||||
|
|
||||||
|
@ -34,8 +34,8 @@ class MediaCaptureViewModel(private val repository: MediaCaptureRepository) : Vi
|
||||||
repository.renderVideoToMedia(fd, this::onMediaRendered, this::onMediaRenderFailed)
|
repository.renderVideoToMedia(fd, this::onMediaRendered, this::onMediaRenderFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMostRecentMedia(): LiveData<Optional<Media>> {
|
fun getMostRecentMedia(): Flowable<Optional<Media>> {
|
||||||
return Transformations.map(store.stateLiveData) { Optional.ofNullable(it.mostRecentMedia) }
|
return store.stateFlowable.map { Optional.ofNullable(it.mostRecentMedia) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onMediaRendered(media: Media) {
|
private fun onMediaRendered(media: Media) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue