Konverting the MediaSendViewModel (#922)
* Konverting the MediaSendViewModel * PR Feedbackpull/1710/head
parent
b134b82daa
commit
14948df4b5
@ -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