session-android/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewViewModel.java

179 lines
4.9 KiB
Java

package org.thoughtcrime.securesms.linkpreview;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import android.content.Context;
import androidx.annotation.NonNull;
import android.text.TextUtils;
import org.thoughtcrime.securesms.net.RequestController;
import org.session.libsession.utilities.Debouncer;
import org.session.libsession.utilities.Util;
import org.session.libsignal.utilities.guava.Optional;
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
import java.util.List;
public class LinkPreviewViewModel extends ViewModel {
private final LinkPreviewRepository repository;
private final MutableLiveData<LinkPreviewState> linkPreviewState;
private String activeUrl;
private RequestController activeRequest;
private boolean userCanceled;
private Debouncer debouncer;
private LinkPreviewViewModel(@NonNull LinkPreviewRepository repository) {
this.repository = repository;
this.linkPreviewState = new MutableLiveData<>();
this.debouncer = new Debouncer(250);
}
public LiveData<LinkPreviewState> getLinkPreviewState() {
return linkPreviewState;
}
public boolean hasLinkPreview() {
return linkPreviewState.getValue() != null && linkPreviewState.getValue().getLinkPreview().isPresent();
}
public Optional<LinkPreview> getActiveLinkPreview() {
final LinkPreviewState state = linkPreviewState.getValue();
if (state == null || !state.getLinkPreview().isPresent()) {
return Optional.absent();
} else {
return state.getLinkPreview();
}
}
public void onTextChanged(@NonNull Context context, @NonNull String text, int cursorStart, int cursorEnd) {
debouncer.publish(() -> {
if (TextUtils.isEmpty(text)) {
userCanceled = false;
}
if (userCanceled) {
return;
}
List<Link> links = LinkPreviewUtil.findWhitelistedUrls(text);
Optional<Link> link = links.isEmpty() ? Optional.absent() : Optional.of(links.get(0));
if (link.isPresent() && link.get().getUrl().equals(activeUrl)) {
return;
}
if (activeRequest != null) {
activeRequest.cancel();
activeRequest = null;
}
if (!link.isPresent()) {
activeUrl = null;
linkPreviewState.setValue(LinkPreviewState.forEmpty());
return;
}
linkPreviewState.setValue(LinkPreviewState.forLoading());
activeUrl = link.get().getUrl();
activeRequest = repository.getLinkPreview(context, link.get().getUrl(), lp -> {
Util.runOnMain(() -> {
if (!userCanceled) {
linkPreviewState.setValue(LinkPreviewState.forPreview(lp));
}
activeRequest = null;
});
});
});
}
public void onUserCancel() {
if (activeRequest != null) {
activeRequest.cancel();
activeRequest = null;
}
userCanceled = true;
activeUrl = null;
debouncer.clear();
linkPreviewState.setValue(LinkPreviewState.forEmpty());
}
public void onEnabled() {
userCanceled = false;
}
@Override
protected void onCleared() {
if (activeRequest != null) {
activeRequest.cancel();
}
debouncer.clear();
}
private boolean isCursorPositionValid(@NonNull String text, @NonNull Link link, int cursorStart, int cursorEnd) {
if (cursorStart != cursorEnd) {
return true;
}
if (text.endsWith(link.getUrl()) && cursorStart == link.getPosition() + link.getUrl().length()) {
return true;
}
return cursorStart < link.getPosition() || cursorStart > link.getPosition() + link.getUrl().length();
}
public static class LinkPreviewState {
private final boolean isLoading;
private final Optional<LinkPreview> linkPreview;
private LinkPreviewState(boolean isLoading, Optional<LinkPreview> linkPreview) {
this.isLoading = isLoading;
this.linkPreview = linkPreview;
}
private static LinkPreviewState forLoading() {
return new LinkPreviewState(true, Optional.absent());
}
private static LinkPreviewState forPreview(@NonNull Optional<LinkPreview> linkPreview) {
return new LinkPreviewState(false, linkPreview);
}
private static LinkPreviewState forEmpty() {
return new LinkPreviewState(false, Optional.absent());
}
public boolean isLoading() {
return isLoading;
}
public Optional<LinkPreview> getLinkPreview() {
return linkPreview;
}
}
public static class Factory extends ViewModelProvider.NewInstanceFactory {
private final LinkPreviewRepository repository;
public Factory(@NonNull LinkPreviewRepository repository) {
this.repository = repository;
}
@Override
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return modelClass.cast(new LinkPreviewViewModel(repository));
}
}
}