mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 20:48:43 +00:00
Add SavedStateHandle support to LinkPreviewViewModelV2.
This commit is contained in:
@@ -1,17 +1,23 @@
|
||||
package org.thoughtcrime.securesms.attachments;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.os.ParcelCompat;
|
||||
|
||||
import org.thoughtcrime.securesms.audio.AudioHash;
|
||||
import org.thoughtcrime.securesms.blurhash.BlurHash;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable.TransformProperties;
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||
import org.thoughtcrime.securesms.util.ParcelUtil;
|
||||
|
||||
public abstract class Attachment {
|
||||
import java.util.Objects;
|
||||
|
||||
public abstract class Attachment implements Parcelable {
|
||||
|
||||
@NonNull
|
||||
private final String contentType;
|
||||
@@ -116,6 +122,69 @@ public abstract class Attachment {
|
||||
this.transformProperties = transformProperties != null ? transformProperties : TransformProperties.empty();
|
||||
}
|
||||
|
||||
protected Attachment(Parcel in) {
|
||||
this.contentType = Objects.requireNonNull(in.readString());
|
||||
this.transferState = in.readInt();
|
||||
this.size = in.readLong();
|
||||
this.fileName = in.readString();
|
||||
this.cdnNumber = in.readInt();
|
||||
this.location = in.readString();
|
||||
this.key = in.readString();
|
||||
this.relay = in.readString();
|
||||
this.digest = ParcelUtil.readByteArray(in);
|
||||
this.incrementalDigest = ParcelUtil.readByteArray(in);
|
||||
this.fastPreflightId = in.readString();
|
||||
this.voiceNote = ParcelUtil.readBoolean(in);
|
||||
this.borderless = ParcelUtil.readBoolean(in);
|
||||
this.videoGif = ParcelUtil.readBoolean(in);
|
||||
this.width = in.readInt();
|
||||
this.height = in.readInt();
|
||||
this.incrementalMacChunkSize = in.readInt();
|
||||
this.quote = ParcelUtil.readBoolean(in);
|
||||
this.uploadTimestamp = in.readLong();
|
||||
this.stickerLocator = ParcelCompat.readParcelable(in, StickerLocator.class.getClassLoader(), StickerLocator.class);
|
||||
this.caption = in.readString();
|
||||
this.blurHash = ParcelCompat.readParcelable(in, BlurHash.class.getClassLoader(), BlurHash.class);
|
||||
this.audioHash = ParcelCompat.readParcelable(in, AudioHash.class.getClassLoader(), AudioHash.class);
|
||||
this.transformProperties = Objects.requireNonNull(ParcelCompat.readParcelable(in, TransformProperties.class.getClassLoader(), TransformProperties.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
AttachmentCreator.writeSubclass(dest, this);
|
||||
dest.writeString(contentType);
|
||||
dest.writeInt(transferState);
|
||||
dest.writeLong(size);
|
||||
dest.writeString(fileName);
|
||||
dest.writeInt(cdnNumber);
|
||||
dest.writeString(location);
|
||||
dest.writeString(key);
|
||||
dest.writeString(relay);
|
||||
ParcelUtil.writeByteArray(dest, digest);
|
||||
ParcelUtil.writeByteArray(dest, incrementalDigest);
|
||||
dest.writeString(fastPreflightId);
|
||||
ParcelUtil.writeBoolean(dest, voiceNote);
|
||||
ParcelUtil.writeBoolean(dest, borderless);
|
||||
ParcelUtil.writeBoolean(dest, videoGif);
|
||||
dest.writeInt(width);
|
||||
dest.writeInt(height);
|
||||
dest.writeInt(incrementalMacChunkSize);
|
||||
ParcelUtil.writeBoolean(dest, quote);
|
||||
dest.writeLong(uploadTimestamp);
|
||||
dest.writeParcelable(stickerLocator, 0);
|
||||
dest.writeString(caption);
|
||||
dest.writeParcelable(blurHash, 0);
|
||||
dest.writeParcelable(audioHash, 0);
|
||||
dest.writeParcelable(transformProperties, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Creator<Attachment> CREATOR = AttachmentCreator.INSTANCE;
|
||||
|
||||
@Nullable
|
||||
public abstract Uri getUri();
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.attachments
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
/**
|
||||
* Parcelable Creator for Attachments. Encapsulates the logic around dealing with
|
||||
* subclasses, since Attachment is abstract and has several children.
|
||||
*/
|
||||
object AttachmentCreator : Parcelable.Creator<Attachment> {
|
||||
enum class Subclass(val clazz: Class<out Attachment>, val code: String) {
|
||||
DATABASE(DatabaseAttachment::class.java, "database"),
|
||||
MMS_NOTIFICATION(MmsNotificationAttachment::class.java, "mms_notification"),
|
||||
POINTER(PointerAttachment::class.java, "pointer"),
|
||||
TOMBSTONE(TombstoneAttachment::class.java, "tombstone"),
|
||||
URI(UriAttachment::class.java, "uri")
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun writeSubclass(dest: Parcel, instance: Attachment) {
|
||||
val subclass = Subclass.values().firstOrNull { it.clazz == instance::class.java } ?: error("Unexpected subtype ${instance::class.java.simpleName}")
|
||||
dest.writeString(subclass.code)
|
||||
}
|
||||
|
||||
override fun createFromParcel(source: Parcel): Attachment {
|
||||
val rawCode = source.readString()!!
|
||||
|
||||
return when (Subclass.values().first { rawCode == it.code }) {
|
||||
Subclass.DATABASE -> DatabaseAttachment(source)
|
||||
Subclass.MMS_NOTIFICATION -> MmsNotificationAttachment(source)
|
||||
Subclass.POINTER -> PointerAttachment(source)
|
||||
Subclass.TOMBSTONE -> TombstoneAttachment(source)
|
||||
Subclass.URI -> UriAttachment(source)
|
||||
}
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<Attachment?> = arrayOfNulls(size)
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
package org.thoughtcrime.securesms.attachments;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.os.ParcelCompat;
|
||||
|
||||
import org.thoughtcrime.securesms.audio.AudioHash;
|
||||
import org.thoughtcrime.securesms.blurhash.BlurHash;
|
||||
@@ -10,6 +13,7 @@ import org.thoughtcrime.securesms.database.AttachmentTable.TransformProperties;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.ParcelUtil;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
@@ -59,6 +63,25 @@ public class DatabaseAttachment extends Attachment {
|
||||
this.displayOrder = displayOrder;
|
||||
}
|
||||
|
||||
protected DatabaseAttachment(Parcel in) {
|
||||
super(in);
|
||||
this.attachmentId = ParcelCompat.readParcelable(in, AttachmentId.class.getClassLoader(), AttachmentId.class);
|
||||
this.hasData = ParcelUtil.readBoolean(in);
|
||||
this.hasThumbnail = ParcelUtil.readBoolean(in);
|
||||
this.mmsId = in.readLong();
|
||||
this.displayOrder = in.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeParcelable(attachmentId, 0);
|
||||
ParcelUtil.writeBoolean(dest, hasData);
|
||||
ParcelUtil.writeBoolean(dest, hasThumbnail);
|
||||
dest.writeLong(mmsId);
|
||||
dest.writeInt(displayOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Uri getUri() {
|
||||
|
||||
@@ -2,7 +2,9 @@ package org.thoughtcrime.securesms.attachments;
|
||||
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
@@ -14,6 +16,10 @@ public class MmsNotificationAttachment extends Attachment {
|
||||
super("application/mms", getTransferStateFromStatus(status), size, null, 0, null, null, null, null, null, null, false, false, false, 0, 0, 0, false, 0, null, null, null, null, null);
|
||||
}
|
||||
|
||||
protected MmsNotificationAttachment(Parcel in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Uri getUri() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.attachments;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -46,6 +47,10 @@ public class PointerAttachment extends Attachment {
|
||||
super(contentType, transferState, size, fileName, cdnNumber, location, key, relay, digest, incrementalDigest, fastPreflightId, voiceNote, borderless, videoGif, width, height, incrementalMacChunkSize, false, uploadTimestamp, caption, stickerLocator, blurHash, null, null);
|
||||
}
|
||||
|
||||
protected PointerAttachment(Parcel in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Uri getUri() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.attachments;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -19,6 +20,10 @@ public class TombstoneAttachment extends Attachment {
|
||||
super(contentType, AttachmentTable.TRANSFER_PROGRESS_DONE, 0, null, 0, null, null, null, null, null, null, false, false, false, 0, 0, 0, quote, 0, null, null, null, null, null);
|
||||
}
|
||||
|
||||
protected TombstoneAttachment(Parcel in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Uri getUri() {
|
||||
return null;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package org.thoughtcrime.securesms.attachments;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.os.ParcelCompat;
|
||||
|
||||
import org.thoughtcrime.securesms.audio.AudioHash;
|
||||
import org.thoughtcrime.securesms.blurhash.BlurHash;
|
||||
@@ -56,6 +58,17 @@ public class UriAttachment extends Attachment {
|
||||
this.dataUri = Objects.requireNonNull(dataUri);
|
||||
}
|
||||
|
||||
protected UriAttachment(Parcel in) {
|
||||
super(in);
|
||||
this.dataUri = Objects.requireNonNull(ParcelCompat.readParcelable(in, Uri.class.getClassLoader(), Uri.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeParcelable(dataUri, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Uri getUri() {
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
package org.thoughtcrime.securesms.audio;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.AudioWaveFormData;
|
||||
import org.thoughtcrime.securesms.util.ParcelUtil;
|
||||
import org.whispersystems.util.Base64;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An AudioHash is a compact string representation of the wave form and duration for an audio file.
|
||||
*/
|
||||
public final class AudioHash {
|
||||
public final class AudioHash implements Parcelable {
|
||||
|
||||
@NonNull private final String hash;
|
||||
@NonNull private final AudioWaveFormData audioWaveForm;
|
||||
@@ -25,6 +30,39 @@ public final class AudioHash {
|
||||
this(Base64.encodeBytes(audioWaveForm.encode()), audioWaveForm);
|
||||
}
|
||||
|
||||
protected AudioHash(Parcel in) {
|
||||
hash = Objects.requireNonNull(in.readString());
|
||||
|
||||
try {
|
||||
audioWaveForm = AudioWaveFormData.ADAPTER.decode(Objects.requireNonNull(ParcelUtil.readByteArray(in)));
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(hash);
|
||||
ParcelUtil.writeByteArray(dest, audioWaveForm.encode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Creator<AudioHash> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public AudioHash createFromParcel(Parcel in) {
|
||||
return new AudioHash(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AudioHash[] newArray(int size) {
|
||||
return new AudioHash[size];
|
||||
}
|
||||
};
|
||||
|
||||
public static @Nullable AudioHash parseOrNull(@Nullable String hash) {
|
||||
if (hash == null) return null;
|
||||
try {
|
||||
|
||||
@@ -314,6 +314,7 @@ import org.thoughtcrime.securesms.util.getRecordQuoteType
|
||||
import org.thoughtcrime.securesms.util.hasAudio
|
||||
import org.thoughtcrime.securesms.util.hasGiftBadge
|
||||
import org.thoughtcrime.securesms.util.isValidReactionTarget
|
||||
import org.thoughtcrime.securesms.util.savedStateViewModel
|
||||
import org.thoughtcrime.securesms.util.viewModel
|
||||
import org.thoughtcrime.securesms.util.views.Stub
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
@@ -405,10 +406,8 @@ class ConversationFragment :
|
||||
)
|
||||
}
|
||||
|
||||
private val linkPreviewViewModel: LinkPreviewViewModelV2 by viewModel {
|
||||
LinkPreviewViewModelV2(
|
||||
enablePlaceholder = false
|
||||
)
|
||||
private val linkPreviewViewModel: LinkPreviewViewModelV2 by savedStateViewModel {
|
||||
LinkPreviewViewModelV2(it, enablePlaceholder = false)
|
||||
}
|
||||
|
||||
private val groupCallViewModel: ConversationGroupCallViewModel by viewModel {
|
||||
|
||||
@@ -20,6 +20,8 @@ import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.media.MediaDataSource;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
|
||||
@@ -1628,7 +1630,7 @@ public class AttachmentTable extends DatabaseTable {
|
||||
}
|
||||
}
|
||||
|
||||
public static final class TransformProperties {
|
||||
public static final class TransformProperties implements Parcelable {
|
||||
|
||||
private static final int DEFAULT_MEDIA_QUALITY = SentMediaQuality.STANDARD.getCode();
|
||||
|
||||
@@ -1652,6 +1654,40 @@ public class AttachmentTable extends DatabaseTable {
|
||||
this.sentMediaQuality = sentMediaQuality;
|
||||
}
|
||||
|
||||
protected TransformProperties(Parcel in) {
|
||||
skipTransform = in.readByte() != 0;
|
||||
videoTrim = in.readByte() != 0;
|
||||
videoTrimStartTimeUs = in.readLong();
|
||||
videoTrimEndTimeUs = in.readLong();
|
||||
sentMediaQuality = in.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeByte((byte) (skipTransform ? 1 : 0));
|
||||
dest.writeByte((byte) (videoTrim ? 1 : 0));
|
||||
dest.writeLong(videoTrimStartTimeUs);
|
||||
dest.writeLong(videoTrimEndTimeUs);
|
||||
dest.writeInt(sentMediaQuality);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Creator<TransformProperties> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public TransformProperties createFromParcel(Parcel in) {
|
||||
return new TransformProperties(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformProperties[] newArray(int size) {
|
||||
return new TransformProperties[size];
|
||||
}
|
||||
};
|
||||
|
||||
public static @NonNull TransformProperties empty() {
|
||||
return new TransformProperties(false, false, 0, 0, DEFAULT_MEDIA_QUALITY);
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
|
||||
@@ -14,15 +14,16 @@ import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs
|
||||
import org.thoughtcrime.securesms.databinding.StoriesTextPostCreationFragmentBinding
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewState
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModelV2
|
||||
import org.thoughtcrime.securesms.mediasend.CameraDisplay
|
||||
import org.thoughtcrime.securesms.mediasend.v2.HudCommand
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel
|
||||
@@ -31,7 +32,8 @@ import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendReposi
|
||||
import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendResult
|
||||
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil
|
||||
import org.thoughtcrime.securesms.util.activitySavedStateViewModel
|
||||
import org.thoughtcrime.securesms.util.viewModel
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
import java.util.Optional
|
||||
|
||||
@@ -55,14 +57,9 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
|
||||
}
|
||||
)
|
||||
|
||||
private val linkPreviewViewModel: LinkPreviewViewModel by viewModels(
|
||||
ownerProducer = {
|
||||
requireActivity()
|
||||
},
|
||||
factoryProducer = {
|
||||
LinkPreviewViewModel.Factory(LinkPreviewRepository(), true)
|
||||
}
|
||||
)
|
||||
private val linkPreviewViewModel: LinkPreviewViewModelV2 by activitySavedStateViewModel { handle ->
|
||||
LinkPreviewViewModelV2(handle, enablePlaceholder = true)
|
||||
}
|
||||
|
||||
private val lifecycleDisposable = LifecycleDisposable()
|
||||
|
||||
@@ -80,16 +77,16 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.typeface.observe(viewLifecycleOwner) { typeface ->
|
||||
lifecycleDisposable += viewModel.typeface.subscribeBy { typeface ->
|
||||
binding.storyTextPost.setTypeface(typeface)
|
||||
}
|
||||
|
||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||
lifecycleDisposable += viewModel.state.subscribeBy { state ->
|
||||
binding.backgroundSelector.background = state.backgroundColor.chatBubbleMask
|
||||
binding.storyTextPost.bindFromCreationState(state)
|
||||
|
||||
if (state.linkPreviewUri != null) {
|
||||
linkPreviewViewModel.onTextChanged(requireContext(), state.linkPreviewUri, 0, state.linkPreviewUri.lastIndex)
|
||||
linkPreviewViewModel.onTextChanged(state.linkPreviewUri, 0, state.linkPreviewUri.lastIndex)
|
||||
} else {
|
||||
linkPreviewViewModel.onSend()
|
||||
}
|
||||
@@ -99,9 +96,9 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
|
||||
binding.send.isEnabled = canSend
|
||||
}
|
||||
|
||||
LiveDataUtil.combineLatest(viewModel.state, linkPreviewViewModel.linkPreviewState) { viewState, linkState ->
|
||||
lifecycleDisposable += Flowable.combineLatest(viewModel.state, linkPreviewViewModel.linkPreviewState) { viewState, linkState ->
|
||||
Pair(viewState.body.isBlank(), linkState)
|
||||
}.observe(viewLifecycleOwner) { (useLargeThumb, linkState) ->
|
||||
}.subscribeBy { (useLargeThumb, linkState) ->
|
||||
binding.storyTextPost.bindLinkPreviewState(linkState, View.GONE, useLargeThumb)
|
||||
binding.storyTextPost.postAdjustLinkPreviewTranslationY()
|
||||
}
|
||||
@@ -145,7 +142,7 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
|
||||
|
||||
if (contacts.isEmpty()) {
|
||||
val bitmap = binding.storyTextPost.drawToBitmap()
|
||||
viewModel.compressToBlob(bitmap).observeOn(AndroidSchedulers.mainThread()).subscribe { uri ->
|
||||
lifecycleDisposable += viewModel.compressToBlob(bitmap).observeOn(AndroidSchedulers.mainThread()).subscribe { uri ->
|
||||
launcher.launch(
|
||||
StoriesMultiselectForwardActivity.Args(
|
||||
MultiselectForwardFragmentArgs(
|
||||
@@ -246,7 +243,7 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
|
||||
}
|
||||
|
||||
private fun getLinkPreview(): LinkPreview? {
|
||||
val linkPreviewState: LinkPreviewState = linkPreviewViewModel.linkPreviewState.value ?: return null
|
||||
val linkPreviewState: LinkPreviewState = linkPreviewViewModel.linkPreviewStateSnapshot
|
||||
|
||||
return if (linkPreviewState.linkPreview.isPresent) {
|
||||
linkPreviewState.linkPreview.get()
|
||||
|
||||
@@ -5,13 +5,15 @@ import android.graphics.Typeface
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import io.reactivex.rxjava3.processors.BehaviorProcessor
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject
|
||||
import io.reactivex.rxjava3.subjects.Subject
|
||||
@@ -25,32 +27,32 @@ import org.thoughtcrime.securesms.fonts.TypefaceCache
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||
import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendRepository
|
||||
import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendResult
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
import org.thoughtcrime.securesms.util.rx.RxStore
|
||||
|
||||
class TextStoryPostCreationViewModel(private val repository: TextStoryPostSendRepository, private val identityChangesSince: Long = System.currentTimeMillis()) : ViewModel() {
|
||||
|
||||
private val store = Store(TextStoryPostCreationState())
|
||||
private val store = RxStore(TextStoryPostCreationState())
|
||||
private val textFontSubject: Subject<TextFont> = BehaviorSubject.create()
|
||||
private val temporaryBodySubject: Subject<String> = BehaviorSubject.createDefault("")
|
||||
private val disposables = CompositeDisposable()
|
||||
|
||||
private val internalTypeface = MutableLiveData<Typeface>()
|
||||
private val internalTypeface = BehaviorProcessor.create<Typeface>()
|
||||
|
||||
val state: LiveData<TextStoryPostCreationState> = store.stateLiveData
|
||||
val typeface: LiveData<Typeface> = internalTypeface
|
||||
val state: Flowable<TextStoryPostCreationState> = store.stateFlowable.observeOn(AndroidSchedulers.mainThread())
|
||||
val typeface: Flowable<Typeface> = internalTypeface.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
init {
|
||||
textFontSubject.onNext(store.state.textFont)
|
||||
|
||||
val scriptGuess = temporaryBodySubject.observeOn(Schedulers.io()).map { TextToScript.guessScript(it) }
|
||||
|
||||
Observable.combineLatest(textFontSubject, scriptGuess, ::Pair)
|
||||
disposables += Observable.combineLatest(textFontSubject, scriptGuess, ::Pair)
|
||||
.observeOn(Schedulers.io())
|
||||
.distinctUntilChanged()
|
||||
.switchMapSingle { (textFont, script) -> TypefaceCache.get(ApplicationDependencies.getApplication(), textFont, script) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe {
|
||||
internalTypeface.postValue(it)
|
||||
internalTypeface.onNext(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.view.View
|
||||
import android.widget.EditText
|
||||
import androidx.constraintlayout.widget.Group
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.thoughtcrime.securesms.R
|
||||
@@ -30,11 +31,7 @@ class TextStoryPostLinkEntryFragment : KeyboardEntryDialogFragment(
|
||||
factoryProducer = { LinkPreviewViewModel.Factory(LinkPreviewRepository(), true) }
|
||||
)
|
||||
|
||||
private val viewModel: TextStoryPostCreationViewModel by viewModels(
|
||||
ownerProducer = {
|
||||
requireActivity()
|
||||
}
|
||||
)
|
||||
private val viewModel: TextStoryPostCreationViewModel by activityViewModels()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
input = view.findViewById(R.id.input)
|
||||
|
||||
@@ -25,6 +25,8 @@ import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.transition.TransitionManager
|
||||
import com.airbnb.lottie.SimpleColorFilter
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.KeyboardEntryDialogFragment
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaAnimations
|
||||
@@ -52,6 +54,8 @@ class TextStoryPostTextEntryFragment : KeyboardEntryDialogFragment(
|
||||
}
|
||||
)
|
||||
|
||||
private val lifecycleDisposable = LifecycleDisposable()
|
||||
|
||||
private lateinit var scene: ConstraintLayout
|
||||
private lateinit var input: EditText
|
||||
private lateinit var confirmButton: View
|
||||
@@ -213,11 +217,13 @@ class TextStoryPostTextEntryFragment : KeyboardEntryDialogFragment(
|
||||
}
|
||||
|
||||
private fun initializeViewModel() {
|
||||
viewModel.typeface.observe(viewLifecycleOwner) { typeface ->
|
||||
lifecycleDisposable.bindTo(viewLifecycleOwner)
|
||||
|
||||
lifecycleDisposable += viewModel.typeface.subscribeBy { typeface ->
|
||||
input.typeface = typeface
|
||||
}
|
||||
|
||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||
lifecycleDisposable += viewModel.state.subscribeBy { state ->
|
||||
input.setTextColor(state.textForegroundColor)
|
||||
input.setHintTextColor(state.textForegroundColor)
|
||||
|
||||
|
||||
@@ -149,15 +149,15 @@ public class ShareInterstitialActivity extends PassphraseRequiredActivity {
|
||||
|
||||
linkPreviewViewModel.getLinkPreviewState().observe(this, linkPreviewState -> {
|
||||
preview.setVisibility(View.VISIBLE);
|
||||
if (linkPreviewState.getError() != null) {
|
||||
preview.setNoPreview(linkPreviewState.getError());
|
||||
if (linkPreviewState.error != null) {
|
||||
preview.setNoPreview(linkPreviewState.error);
|
||||
viewModel.onLinkPreviewChanged(null);
|
||||
} else if (linkPreviewState.isLoading()) {
|
||||
} else if (linkPreviewState.isLoading) {
|
||||
preview.setLoading();
|
||||
viewModel.onLinkPreviewChanged(null);
|
||||
} else if (linkPreviewState.getLinkPreview().isPresent()) {
|
||||
preview.setLinkPreview(GlideApp.with(this), linkPreviewState.getLinkPreview().get(), true);
|
||||
viewModel.onLinkPreviewChanged(linkPreviewState.getLinkPreview().get());
|
||||
} else if (linkPreviewState.linkPreview.isPresent()) {
|
||||
preview.setLinkPreview(GlideApp.with(this), linkPreviewState.linkPreview.get(), true);
|
||||
viewModel.onLinkPreviewChanged(linkPreviewState.linkPreview.get());
|
||||
} else if (!linkPreviewState.hasLinks()) {
|
||||
preview.setVisibility(View.GONE);
|
||||
viewModel.onLinkPreviewChanged(null);
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.AbstractSavedStateViewModelFactory
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.savedstate.SavedStateRegistryOwner
|
||||
|
||||
/**
|
||||
* Simplifies [ViewModel] creation by providing default implementations of [ViewModelProvider.Factory]
|
||||
@@ -28,6 +33,25 @@ class ViewModelFactory<MODEL : ViewModel>(private val create: () -> MODEL) : Vie
|
||||
}
|
||||
}
|
||||
|
||||
class SavedStateViewModelFactory<MODEL : ViewModel>(
|
||||
private val create: (SavedStateHandle) -> MODEL,
|
||||
registryOwner: SavedStateRegistryOwner
|
||||
) : AbstractSavedStateViewModelFactory(registryOwner, null) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T {
|
||||
return create(handle) as T
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun <MODEL : ViewModel> factoryProducer(
|
||||
create: (SavedStateHandle) -> MODEL,
|
||||
registryOwnerProducer: () -> SavedStateRegistryOwner
|
||||
): () -> ViewModelProvider.Factory {
|
||||
return { SavedStateViewModelFactory(create, registryOwnerProducer()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
inline fun <reified VM : ViewModel> Fragment.viewModel(
|
||||
noinline create: () -> VM
|
||||
@@ -37,6 +61,33 @@ inline fun <reified VM : ViewModel> Fragment.viewModel(
|
||||
)
|
||||
}
|
||||
|
||||
@MainThread
|
||||
inline fun <reified VM : ViewModel> Fragment.savedStateViewModel(
|
||||
noinline create: (SavedStateHandle) -> VM
|
||||
): Lazy<VM> {
|
||||
return viewModels(
|
||||
factoryProducer = SavedStateViewModelFactory.factoryProducer(create) { this }
|
||||
)
|
||||
}
|
||||
|
||||
@MainThread
|
||||
inline fun <reified VM : ViewModel> Fragment.activitySavedStateViewModel(
|
||||
noinline create: (SavedStateHandle) -> VM
|
||||
): Lazy<VM> {
|
||||
return viewModels(
|
||||
factoryProducer = SavedStateViewModelFactory.factoryProducer(create) { requireActivity() }
|
||||
)
|
||||
}
|
||||
|
||||
@MainThread
|
||||
inline fun <reified VM : ViewModel> ComponentActivity.savedStateViewModel(
|
||||
noinline create: (SavedStateHandle) -> VM
|
||||
): Lazy<VM> {
|
||||
return viewModels(
|
||||
factoryProducer = SavedStateViewModelFactory.factoryProducer(create) { this }
|
||||
)
|
||||
}
|
||||
|
||||
@MainThread
|
||||
inline fun <reified VM : ViewModel> Fragment.activityViewModel(
|
||||
noinline create: () -> VM
|
||||
|
||||
Reference in New Issue
Block a user