That fuck shit the fascists are using
at master 277 lines 8.8 kB view raw
1package org.tm.archive.linkpreview; 2 3import android.content.Context; 4import android.text.TextUtils; 5 6import androidx.annotation.NonNull; 7import androidx.annotation.Nullable; 8import androidx.lifecycle.LiveData; 9import androidx.lifecycle.MutableLiveData; 10import androidx.lifecycle.Transformations; 11import androidx.lifecycle.ViewModel; 12import androidx.lifecycle.ViewModelProvider; 13 14import org.signal.core.util.ThreadUtil; 15import org.tm.archive.attachments.AttachmentId; 16import org.tm.archive.dependencies.ApplicationDependencies; 17import org.tm.archive.keyvalue.SignalStore; 18import org.tm.archive.net.RequestController; 19import org.tm.archive.util.Debouncer; 20 21import java.util.Collections; 22import java.util.List; 23import java.util.Optional; 24 25 26public class LinkPreviewViewModel extends ViewModel { 27 28 private final LinkPreviewRepository repository; 29 private final MutableLiveData<LinkPreviewState> linkPreviewState; 30 private final LiveData<LinkPreviewState> linkPreviewSafeState; 31 32 private String activeUrl; 33 private RequestController activeRequest; 34 private boolean userCanceled; 35 private Debouncer debouncer; 36 private boolean enabled; 37 38 private final boolean enablePlaceholder; 39 40 private LinkPreviewViewModel(@NonNull LinkPreviewRepository repository, boolean enablePlaceholder) { 41 this.repository = repository; 42 this.enablePlaceholder = enablePlaceholder; 43 this.linkPreviewState = new MutableLiveData<>(); 44 this.debouncer = new Debouncer(250); 45 this.enabled = SignalStore.settings().isLinkPreviewsEnabled(); 46 this.linkPreviewSafeState = Transformations.map(linkPreviewState, state -> cleanseState(state)); 47 } 48 49 public LiveData<LinkPreviewState> getLinkPreviewState() { 50 return linkPreviewSafeState; 51 } 52 53 /** 54 * Gets the current state for use in the UI, then resets local state to prepare for the next message send. 55 */ 56 public @NonNull List<LinkPreview> onSend() { 57 final LinkPreviewState currentState = linkPreviewSafeState.getValue(); 58 59 if (activeRequest != null) { 60 activeRequest.cancel(); 61 activeRequest = null; 62 } 63 64 userCanceled = false; 65 activeUrl = null; 66 67 debouncer.clear(); 68 linkPreviewState.setValue(LinkPreviewState.forNoLinks()); 69 70 if (currentState == null || !currentState.linkPreview.isPresent()) { 71 return Collections.emptyList(); 72 } else { 73 return Collections.singletonList(currentState.linkPreview.get()); 74 } 75 } 76 77 /** 78 * Gets the current state for use in the UI, then resets local state to prepare for the next message send. 79 */ 80 public @NonNull List<LinkPreview> onSendWithErrorUrl() { 81 final LinkPreviewState currentState = linkPreviewSafeState.getValue(); 82 83 if (activeRequest != null) { 84 activeRequest.cancel(); 85 activeRequest = null; 86 } 87 88 userCanceled = false; 89 activeUrl = null; 90 91 debouncer.clear(); 92 linkPreviewState.setValue(LinkPreviewState.forNoLinks()); 93 94 if (currentState == null) { 95 return Collections.emptyList(); 96 } else if (currentState.linkPreview.isPresent()) { 97 return Collections.singletonList(currentState.linkPreview.get()); 98 } else if (currentState.activeUrlForError != null) { 99 String topLevelDomain = LinkPreviewUtil.getTopLevelDomain(currentState.activeUrlForError); 100 AttachmentId attachmentId = null; 101 102 return Collections.singletonList(new LinkPreview(currentState.activeUrlForError, 103 topLevelDomain != null ? topLevelDomain : currentState.activeUrlForError, 104 null, 105 -1L, 106 attachmentId)); 107 } else { 108 return Collections.emptyList(); 109 } 110 } 111 112 public void onTextChanged(@NonNull Context context, @NonNull String text, int cursorStart, int cursorEnd) { 113 if (!enabled && !enablePlaceholder) return; 114 115 debouncer.publish(() -> { 116 if (TextUtils.isEmpty(text)) { 117 userCanceled = false; 118 } 119 120 if (userCanceled) { 121 return; 122 } 123 124 Optional<Link> link = LinkPreviewUtil.findValidPreviewUrls(text) 125 .findFirst(); 126 127 if (link.isPresent() && link.get().getUrl().equals(activeUrl)) { 128 return; 129 } 130 131 if (activeRequest != null) { 132 activeRequest.cancel(); 133 activeRequest = null; 134 } 135 136 if (!link.isPresent() || !isCursorPositionValid(text, link.get(), cursorStart, cursorEnd)) { 137 activeUrl = null; 138 linkPreviewState.setValue(LinkPreviewState.forNoLinks()); 139 return; 140 } 141 142 linkPreviewState.setValue(LinkPreviewState.forLoading()); 143 144 activeUrl = link.get().getUrl(); 145 activeRequest = enabled ? performRequest(activeUrl) : createPlaceholder(activeUrl); 146 }); 147 } 148 149 public void onUserCancel() { 150 if (activeRequest != null) { 151 activeRequest.cancel(); 152 activeRequest = null; 153 } 154 155 userCanceled = true; 156 activeUrl = null; 157 158 debouncer.clear(); 159 linkPreviewState.setValue(LinkPreviewState.forNoLinks()); 160 } 161 162 public void onTransportChanged(boolean isSms) { 163 enabled = SignalStore.settings().isLinkPreviewsEnabled() && !isSms; 164 165 if (!enabled) { 166 onUserCancel(); 167 } 168 } 169 170 public void onEnabled() { 171 userCanceled = false; 172 enabled = SignalStore.settings().isLinkPreviewsEnabled(); 173 } 174 175 @Override 176 protected void onCleared() { 177 if (activeRequest != null) { 178 activeRequest.cancel(); 179 } 180 181 debouncer.clear(); 182 } 183 184 private boolean isCursorPositionValid(@NonNull String text, @NonNull Link link, int cursorStart, int cursorEnd) { 185 if (cursorStart != cursorEnd) { 186 return true; 187 } 188 189 if (text.endsWith(link.getUrl()) && cursorStart == link.getPosition() + link.getUrl().length()) { 190 return true; 191 } 192 193 return cursorStart < link.getPosition() || cursorStart > link.getPosition() + link.getUrl().length(); 194 } 195 196 private @Nullable RequestController createPlaceholder(String url) { 197 ThreadUtil.runOnMain(() -> { 198 if (!userCanceled) { 199 if (activeUrl != null && activeUrl.equals(url)) { 200 linkPreviewState.setValue(LinkPreviewState.forLinksWithNoPreview(url, LinkPreviewRepository.Error.PREVIEW_NOT_AVAILABLE)); 201 } else { 202 linkPreviewState.setValue(LinkPreviewState.forNoLinks()); 203 } 204 } 205 206 activeRequest = null; 207 }); 208 209 return null; 210 } 211 212 private @Nullable RequestController performRequest(String url) { 213 return repository.getLinkPreview(ApplicationDependencies.getApplication(), url, new LinkPreviewRepository.Callback() { 214 @Override 215 public void onSuccess(@NonNull LinkPreview linkPreview) { 216 ThreadUtil.runOnMain(() -> { 217 if (!userCanceled) { 218 if (activeUrl != null && activeUrl.equals(linkPreview.getUrl())) { 219 linkPreviewState.setValue(LinkPreviewState.forPreview(linkPreview)); 220 } else { 221 linkPreviewState.setValue(LinkPreviewState.forNoLinks()); 222 } 223 } 224 activeRequest = null; 225 }); 226 } 227 228 @Override 229 public void onError(@NonNull LinkPreviewRepository.Error error) { 230 ThreadUtil.runOnMain(() -> { 231 if (!userCanceled) { 232 if (activeUrl != null) { 233 linkPreviewState.setValue(LinkPreviewState.forLinksWithNoPreview(activeUrl, error)); 234 } else { 235 linkPreviewState.setValue(LinkPreviewState.forNoLinks()); 236 } 237 } 238 activeRequest = null; 239 }); 240 } 241 }); 242 } 243 244 private @NonNull LinkPreviewState cleanseState(@NonNull LinkPreviewState state) { 245 if (enabled) { 246 return state; 247 } 248 249 if (enablePlaceholder) { 250 return state.linkPreview 251 .map(linkPreview -> LinkPreviewState.forLinksWithNoPreview(linkPreview.getUrl(), LinkPreviewRepository.Error.PREVIEW_NOT_AVAILABLE)) 252 .orElse(state); 253 } 254 255 return LinkPreviewState.forNoLinks(); 256 } 257 258 public static class Factory extends ViewModelProvider.NewInstanceFactory { 259 260 private final LinkPreviewRepository repository; 261 private final boolean enablePlaceholder; 262 263 public Factory(@NonNull LinkPreviewRepository repository) { 264 this(repository, false); 265 } 266 267 public Factory(@NonNull LinkPreviewRepository repository, boolean enablePlaceholder) { 268 this.repository = repository; 269 this.enablePlaceholder = enablePlaceholder; 270 } 271 272 @Override 273 public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) { 274 return modelClass.cast(new LinkPreviewViewModel(repository, enablePlaceholder)); 275 } 276 } 277}