301 lines
10 KiB
Java
Raw Normal View History

package org.thoughtcrime.securesms.mediasend;
2019-03-13 16:05:25 -07:00
import android.app.Application;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.ViewModel;
import android.arch.lifecycle.ViewModelProvider;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.mms.MediaConstraints;
2019-03-13 16:05:25 -07:00
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import org.thoughtcrime.securesms.util.Util;
2019-03-13 16:05:25 -07:00
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.Collections;
import java.util.HashMap;
2019-03-13 16:05:25 -07:00
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Manages the observable datasets available in {@link MediaSendActivity}.
*/
class MediaSendViewModel extends ViewModel {
2019-03-13 16:05:25 -07:00
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;
2019-03-01 10:50:48 -08:00
private final MutableLiveData<String> bucketId;
private final MutableLiveData<List<MediaFolder>> folders;
2019-03-01 10:50:48 -08:00
private final MutableLiveData<CountButtonState> countButtonState;
private final SingleLiveEvent<Error> error;
private final Map<Uri, Object> savedDrawState;
2019-03-01 10:50:48 -08:00
private MediaConstraints mediaConstraints;
private CharSequence body;
private CountButtonState.Visibility countButtonVisibility;
2019-03-13 16:05:25 -07:00
private boolean sentMedia;
private Optional<Media> lastImageCapture;
2019-03-13 16:05:25 -07:00
private MediaSendViewModel(@NonNull Application application, @NonNull MediaRepository repository) {
this.application = application;
2019-03-01 10:50:48 -08:00
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.error = new SingleLiveEvent<>();
this.savedDrawState = new HashMap<>();
this.countButtonVisibility = CountButtonState.Visibility.CONDITIONAL;
2019-03-13 16:05:25 -07:00
this.lastImageCapture = Optional.absent();
position.setValue(-1);
2019-03-01 10:50:48 -08:00
countButtonState.setValue(new CountButtonState(0, CountButtonState.Visibility.CONDITIONAL));
}
void setMediaConstraints(@NonNull MediaConstraints mediaConstraints) {
this.mediaConstraints = mediaConstraints;
}
2019-03-01 10:50:48 -08:00
void onSelectedMediaChanged(@NonNull Context context, @NonNull List<Media> newMedia) {
repository.getPopulatedMedia(context, newMedia, populatedMedia -> {
List<Media> filteredMedia = getFilteredMedia(context, populatedMedia, mediaConstraints);
if (filteredMedia.size() != newMedia.size()) {
error.postValue(Error.ITEM_TOO_LARGE);
}
2019-03-01 10:50:48 -08:00
if (filteredMedia.size() > 0) {
String computedId = Stream.of(filteredMedia)
.skip(1)
.reduce(filteredMedia.get(0).getBucketId().orNull(), (id, m) -> {
if (Util.equals(id, m.getBucketId().orNull())) {
return id;
} else {
return Media.ALL_MEDIA_BUCKET_ID;
}
});
bucketId.postValue(computedId);
} else {
bucketId.postValue(Media.ALL_MEDIA_BUCKET_ID);
countButtonVisibility = CountButtonState.Visibility.CONDITIONAL;
}
selectedMedia.postValue(filteredMedia);
2019-03-01 10:50:48 -08:00
countButtonState.postValue(new CountButtonState(filteredMedia.size(), countButtonVisibility));
});
}
2019-03-01 10:50:48 -08:00
void onMultiSelectStarted() {
countButtonVisibility = CountButtonState.Visibility.FORCED_ON;
2019-03-13 16:05:25 -07:00
countButtonState.setValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
2019-03-01 10:50:48 -08:00
}
2019-03-01 10:50:48 -08:00
void onImageEditorStarted() {
countButtonVisibility = CountButtonState.Visibility.FORCED_OFF;
2019-03-13 16:05:25 -07:00
countButtonState.setValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
2019-03-01 10:50:48 -08:00
}
void onImageEditorEnded() {
countButtonVisibility = CountButtonState.Visibility.CONDITIONAL;
2019-03-13 16:05:25 -07:00
countButtonState.setValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
2019-03-01 10:50:48 -08:00
}
2019-03-01 10:50:48 -08:00
void onBodyChanged(@NonNull CharSequence body) {
this.body = body;
}
void onFolderSelected(@NonNull String bucketId) {
2019-03-01 10:50:48 -08:00
this.bucketId.setValue(bucketId);
bucketMedia.setValue(Collections.emptyList());
}
void onPageChanged(int position) {
this.position.setValue(position);
}
2019-03-13 16:05:25 -07:00
void onMediaItemRemoved(@NonNull Context context, int position) {
Media removed = getSelectedMediaOrDefault().remove(position);
if (removed != null && BlobProvider.isAuthority(removed.getUri())) {
BlobProvider.getInstance().delete(context, removed.getUri());
}
selectedMedia.setValue(selectedMedia.getValue());
}
2019-03-13 16:05:25 -07:00
void onImageCaptured(@NonNull Media media) {
List<Media> selected = selectedMedia.getValue();
if (selected == null) {
selected = new LinkedList<>();
}
lastImageCapture = Optional.of(media);
selected.add(media);
selectedMedia.setValue(selected);
position.setValue(selected.size() - 1);
bucketId.setValue(Media.ALL_MEDIA_BUCKET_ID);
if (selected.size() == 1) {
countButtonVisibility = CountButtonState.Visibility.FORCED_OFF;
} else {
countButtonVisibility = CountButtonState.Visibility.CONDITIONAL;
}
countButtonState.setValue(new CountButtonState(selected.size(), countButtonVisibility));
}
void onImageCaptureUndo(@NonNull Context context) {
List<Media> selected = getSelectedMediaOrDefault();
if (lastImageCapture.isPresent() && selected.contains(lastImageCapture.get()) && selected.size() == 1) {
selected.remove(lastImageCapture.get());
selectedMedia.setValue(selected);
countButtonState.setValue(new CountButtonState(selected.size(), countButtonVisibility));
BlobProvider.getInstance().delete(context, lastImageCapture.get().getUri());
}
}
void onCaptionChanged(@NonNull String newCaption) {
if (position.getValue() >= 0 && !Util.isEmpty(selectedMedia.getValue())) {
selectedMedia.getValue().get(position.getValue()).setCaption(TextUtils.isEmpty(newCaption) ? null : newCaption);
}
}
void saveDrawState(@NonNull Map<Uri, Object> state) {
savedDrawState.clear();
savedDrawState.putAll(state);
}
2019-03-13 16:05:25 -07:00
void onSendClicked() {
sentMedia = true;
}
@NonNull Map<Uri, Object> getDrawState() {
return savedDrawState;
}
2019-03-01 10:50:48 -08:00
@NonNull LiveData<List<Media>> getSelectedMedia() {
return selectedMedia;
}
2019-03-01 10:50:48 -08:00
@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;
}
2019-03-01 10:50:48 -08:00
@NonNull LiveData<CountButtonState> getCountButtonState() {
return countButtonState;
}
CharSequence getBody() {
return body;
}
LiveData<Integer> getPosition() {
return position;
}
2019-03-01 10:50:48 -08:00
LiveData<String> getBucketId() {
return bucketId;
}
LiveData<Error> getError() {
return error;
}
2019-03-01 10:50:48 -08:00
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();
}
2019-03-13 16:05:25 -07:00
@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
}
2019-03-01 10:50:48 -08:00
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 getVisibility() {
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 {
2019-03-13 16:05:25 -07:00
private final Application application;
private final MediaRepository repository;
2019-03-13 16:05:25 -07:00
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) {
2019-03-13 16:05:25 -07:00
return modelClass.cast(new MediaSendViewModel(application, repository));
}
}
}