mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 00:59:49 +01:00
Add SavedStateHandle support to LinkPreviewViewModelV2.
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
package org.thoughtcrime.securesms.linkpreview;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.os.ParcelCompat;
|
||||
import androidx.core.text.HtmlCompat;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
@@ -15,7 +19,7 @@ import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
public class LinkPreview {
|
||||
public class LinkPreview implements Parcelable {
|
||||
|
||||
@JsonProperty
|
||||
private final String url;
|
||||
@@ -67,6 +71,42 @@ public class LinkPreview {
|
||||
this.thumbnail = Optional.empty();
|
||||
}
|
||||
|
||||
protected LinkPreview(Parcel in) {
|
||||
url = in.readString();
|
||||
title = in.readString();
|
||||
description = in.readString();
|
||||
date = in.readLong();
|
||||
attachmentId = ParcelCompat.readParcelable(in, AttachmentId.class.getClassLoader(), AttachmentId.class);
|
||||
thumbnail = Optional.ofNullable(ParcelCompat.readParcelable(in, Attachment.class.getClassLoader(), Attachment.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(url);
|
||||
dest.writeString(title);
|
||||
dest.writeString(description);
|
||||
dest.writeLong(date);
|
||||
dest.writeParcelable(attachmentId, flags);
|
||||
dest.writeParcelable(thumbnail.orElse(null), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Creator<LinkPreview> CREATOR = new Creator<LinkPreview>() {
|
||||
@Override
|
||||
public LinkPreview createFromParcel(Parcel in) {
|
||||
return new LinkPreview(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinkPreview[] newArray(int size) {
|
||||
return new LinkPreview[size];
|
||||
}
|
||||
};
|
||||
|
||||
public @NonNull String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.linkpreview;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class LinkPreviewState {
|
||||
private final String activeUrlForError;
|
||||
private final boolean isLoading;
|
||||
private final boolean hasLinks;
|
||||
private final Optional<LinkPreview> linkPreview;
|
||||
private final LinkPreviewRepository.Error error;
|
||||
|
||||
private LinkPreviewState(@Nullable String activeUrlForError,
|
||||
boolean isLoading,
|
||||
boolean hasLinks,
|
||||
Optional<LinkPreview> linkPreview,
|
||||
@Nullable LinkPreviewRepository.Error error)
|
||||
{
|
||||
this.activeUrlForError = activeUrlForError;
|
||||
this.isLoading = isLoading;
|
||||
this.hasLinks = hasLinks;
|
||||
this.linkPreview = linkPreview;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public static LinkPreviewState forLoading() {
|
||||
return new LinkPreviewState(null, true, false, Optional.empty(), null);
|
||||
}
|
||||
|
||||
public static LinkPreviewState forPreview(@NonNull LinkPreview linkPreview) {
|
||||
return new LinkPreviewState(null, false, true, Optional.of(linkPreview), null);
|
||||
}
|
||||
|
||||
public static LinkPreviewState forLinksWithNoPreview(@Nullable String activeUrlForError, @NonNull LinkPreviewRepository.Error error) {
|
||||
return new LinkPreviewState(activeUrlForError, false, true, Optional.empty(), error);
|
||||
}
|
||||
|
||||
public static LinkPreviewState forNoLinks() {
|
||||
return new LinkPreviewState(null, false, false, Optional.empty(), null);
|
||||
}
|
||||
|
||||
public @Nullable String getActiveUrlForError() {
|
||||
return activeUrlForError;
|
||||
}
|
||||
|
||||
public boolean isLoading() {
|
||||
return isLoading;
|
||||
}
|
||||
|
||||
public boolean hasLinks() {
|
||||
return hasLinks;
|
||||
}
|
||||
|
||||
public Optional<LinkPreview> getLinkPreview() {
|
||||
return linkPreview;
|
||||
}
|
||||
|
||||
public @Nullable LinkPreviewRepository.Error getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public boolean hasContent() {
|
||||
return isLoading || hasLinks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.thoughtcrime.securesms.linkpreview
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.Optional
|
||||
|
||||
@Parcelize
|
||||
class LinkPreviewState private constructor(
|
||||
@JvmField val activeUrlForError: String?,
|
||||
@JvmField val isLoading: Boolean,
|
||||
private val hasLinks: Boolean,
|
||||
private val preview: LinkPreview?,
|
||||
@JvmField val error: LinkPreviewRepository.Error?
|
||||
) : Parcelable {
|
||||
|
||||
@IgnoredOnParcel
|
||||
@JvmField
|
||||
val linkPreview: Optional<LinkPreview> = Optional.ofNullable(preview)
|
||||
|
||||
fun hasLinks(): Boolean {
|
||||
return hasLinks
|
||||
}
|
||||
|
||||
fun hasContent(): Boolean {
|
||||
return isLoading || hasLinks
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun forLoading(): LinkPreviewState {
|
||||
return LinkPreviewState(
|
||||
activeUrlForError = null,
|
||||
isLoading = true,
|
||||
hasLinks = false,
|
||||
preview = null,
|
||||
error = null
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun forPreview(linkPreview: LinkPreview): LinkPreviewState {
|
||||
return LinkPreviewState(
|
||||
activeUrlForError = null,
|
||||
isLoading = false,
|
||||
hasLinks = true,
|
||||
preview = linkPreview,
|
||||
error = null
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun forLinksWithNoPreview(activeUrlForError: String?, error: LinkPreviewRepository.Error): LinkPreviewState {
|
||||
return LinkPreviewState(
|
||||
activeUrlForError = activeUrlForError,
|
||||
isLoading = false,
|
||||
hasLinks = true,
|
||||
preview = null,
|
||||
error = error
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun forNoLinks(): LinkPreviewState {
|
||||
return LinkPreviewState(
|
||||
activeUrlForError = null,
|
||||
isLoading = false,
|
||||
hasLinks = false,
|
||||
preview = null,
|
||||
error = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,14 +50,6 @@ public class LinkPreviewViewModel extends ViewModel {
|
||||
return linkPreviewSafeState;
|
||||
}
|
||||
|
||||
public boolean hasLinkPreview() {
|
||||
return linkPreviewSafeState.getValue() != null && linkPreviewSafeState.getValue().getLinkPreview().isPresent();
|
||||
}
|
||||
|
||||
public boolean hasLinkPreviewUi() {
|
||||
return linkPreviewSafeState.getValue() != null && linkPreviewSafeState.getValue().hasContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current state for use in the UI, then resets local state to prepare for the next message send.
|
||||
*/
|
||||
@@ -75,10 +67,10 @@ public class LinkPreviewViewModel extends ViewModel {
|
||||
debouncer.clear();
|
||||
linkPreviewState.setValue(LinkPreviewState.forNoLinks());
|
||||
|
||||
if (currentState == null || !currentState.getLinkPreview().isPresent()) {
|
||||
if (currentState == null || !currentState.linkPreview.isPresent()) {
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
return Collections.singletonList(currentState.getLinkPreview().get());
|
||||
return Collections.singletonList(currentState.linkPreview.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,14 +93,14 @@ public class LinkPreviewViewModel extends ViewModel {
|
||||
|
||||
if (currentState == null) {
|
||||
return Collections.emptyList();
|
||||
} else if (currentState.getLinkPreview().isPresent()) {
|
||||
return Collections.singletonList(currentState.getLinkPreview().get());
|
||||
} else if (currentState.getActiveUrlForError() != null) {
|
||||
String topLevelDomain = LinkPreviewUtil.getTopLevelDomain(currentState.getActiveUrlForError());
|
||||
} else if (currentState.linkPreview.isPresent()) {
|
||||
return Collections.singletonList(currentState.linkPreview.get());
|
||||
} else if (currentState.activeUrlForError != null) {
|
||||
String topLevelDomain = LinkPreviewUtil.getTopLevelDomain(currentState.activeUrlForError);
|
||||
AttachmentId attachmentId = null;
|
||||
|
||||
return Collections.singletonList(new LinkPreview(currentState.getActiveUrlForError(),
|
||||
topLevelDomain != null ? topLevelDomain : currentState.getActiveUrlForError(),
|
||||
return Collections.singletonList(new LinkPreview(currentState.activeUrlForError,
|
||||
topLevelDomain != null ? topLevelDomain : currentState.activeUrlForError,
|
||||
null,
|
||||
-1L,
|
||||
attachmentId));
|
||||
@@ -255,7 +247,7 @@ public class LinkPreviewViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
if (enablePlaceholder) {
|
||||
return state.getLinkPreview()
|
||||
return state.linkPreview
|
||||
.map(linkPreview -> LinkPreviewState.forLinksWithNoPreview(linkPreview.getUrl(), LinkPreviewRepository.Error.PREVIEW_NOT_AVAILABLE))
|
||||
.orElse(state);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.thoughtcrime.securesms.linkpreview
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
@@ -22,23 +23,50 @@ import java.util.Optional
|
||||
* Rewrite of [LinkPreviewViewModel] preferring Rx and Kotlin
|
||||
*/
|
||||
class LinkPreviewViewModelV2(
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
private val linkPreviewRepository: LinkPreviewRepository = LinkPreviewRepository(),
|
||||
private val enablePlaceholder: Boolean
|
||||
) : ViewModel() {
|
||||
private var enabled = SignalStore.settings().isLinkPreviewsEnabled
|
||||
private val linkPreviewStateStore = RxStore<LinkPreviewState>(LinkPreviewState.forNoLinks())
|
||||
|
||||
val linkPreviewState: Flowable<LinkPreviewState> = linkPreviewStateStore.stateFlowable
|
||||
companion object {
|
||||
private const val ACTIVE_URL = "active_url"
|
||||
private const val USER_CANCELLED = "user_cancelled"
|
||||
private const val LINK_PREVIEW_STATE = "link_preview_state"
|
||||
}
|
||||
|
||||
private var enabled = SignalStore.settings().isLinkPreviewsEnabled
|
||||
private val linkPreviewStateStore = RxStore(savedStateHandle[LINK_PREVIEW_STATE] ?: LinkPreviewState.forNoLinks())
|
||||
|
||||
val linkPreviewState: Flowable<LinkPreviewState> = linkPreviewStateStore.stateFlowable.observeOn(AndroidSchedulers.mainThread())
|
||||
val linkPreviewStateSnapshot: LinkPreviewState = linkPreviewStateStore.state
|
||||
|
||||
val hasLinkPreview: Boolean = linkPreviewStateStore.state.linkPreview.isPresent
|
||||
val hasLinkPreviewUi: Boolean = linkPreviewStateStore.state.hasContent()
|
||||
|
||||
private var activeUrl: String? = null
|
||||
private var activeUrl: String?
|
||||
get() = savedStateHandle[ACTIVE_URL]
|
||||
set(value) {
|
||||
savedStateHandle[ACTIVE_URL] = value
|
||||
}
|
||||
private var userCancelled: Boolean
|
||||
get() = savedStateHandle[USER_CANCELLED] ?: false
|
||||
set(value) {
|
||||
savedStateHandle[USER_CANCELLED] = value
|
||||
}
|
||||
|
||||
private var activeRequest: Disposable = Disposable.disposed()
|
||||
private var userCancelled: Boolean = false
|
||||
private val debouncer: Debouncer = Debouncer(250)
|
||||
|
||||
private var savedStateDisposable: Disposable = linkPreviewStateStore
|
||||
.stateFlowable
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeBy {
|
||||
savedStateHandle[LINK_PREVIEW_STATE] = it
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
activeRequest.dispose()
|
||||
savedStateDisposable.dispose()
|
||||
debouncer.clear()
|
||||
}
|
||||
|
||||
@@ -46,6 +74,7 @@ class LinkPreviewViewModelV2(
|
||||
val currentState = linkPreviewStateStore.state
|
||||
|
||||
onUserCancel()
|
||||
userCancelled = false
|
||||
|
||||
return currentState.linkPreview.map { listOf(it) }.orElse(emptyList())
|
||||
}
|
||||
@@ -143,7 +172,7 @@ class LinkPreviewViewModelV2(
|
||||
}
|
||||
}
|
||||
|
||||
private fun setLinkPreviewState(linkPreviewState: LinkPreviewState) {
|
||||
fun setLinkPreviewState(linkPreviewState: LinkPreviewState) {
|
||||
linkPreviewStateStore.update { cleanseState(linkPreviewState) }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user