Improve conversation open speed.

Co-authored-by: Cody Henthorne <cody@signal.org>
This commit is contained in:
Greyson Parrelli
2022-03-16 10:10:01 -04:00
committed by Cody Henthorne
parent d3049a3433
commit 666218773c
27 changed files with 462 additions and 395 deletions

View File

@@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import org.signal.paging.LivePagedData
import org.signal.paging.PagedData
import org.signal.paging.PagingConfig
import org.signal.paging.PagingController
@@ -30,7 +31,7 @@ class ContactSearchViewModel(
.setStartIndex(0)
.build()
private val pagedData = MutableLiveData<PagedData<ContactSearchKey, ContactSearchData>>()
private val pagedData = MutableLiveData<LivePagedData<ContactSearchKey, ContactSearchData>>()
private val configurationStore = Store(ContactSearchState())
private val selectionStore = Store<Set<ContactSearchKey>>(emptySet())
@@ -45,7 +46,7 @@ class ContactSearchViewModel(
fun setConfiguration(contactSearchConfiguration: ContactSearchConfiguration) {
val pagedDataSource = ContactSearchPagedDataSource(contactSearchConfiguration)
pagedData.value = PagedData.create(pagedDataSource, pagingConfig)
pagedData.value = PagedData.createForLiveData(pagedDataSource, pagingConfig)
}
fun setQuery(query: String?) {

View File

@@ -1,112 +0,0 @@
package org.thoughtcrime.securesms.conversation;
import androidx.annotation.NonNull;
/**
* Represents metadata about a conversation.
*/
public final class ConversationData {
private final long threadId;
private final long lastSeen;
private final int lastSeenPosition;
private final int lastScrolledPosition;
private final boolean hasSent;
private final int jumpToPosition;
private final int threadSize;
private final MessageRequestData messageRequestData;
private final boolean showUniversalExpireTimerMessage;
ConversationData(long threadId,
long lastSeen,
int lastSeenPosition,
int lastScrolledPosition,
boolean hasSent,
int jumpToPosition,
int threadSize,
@NonNull MessageRequestData messageRequestData,
boolean showUniversalExpireTimerMessage)
{
this.threadId = threadId;
this.lastSeen = lastSeen;
this.lastSeenPosition = lastSeenPosition;
this.lastScrolledPosition = lastScrolledPosition;
this.hasSent = hasSent;
this.jumpToPosition = jumpToPosition;
this.threadSize = threadSize;
this.messageRequestData = messageRequestData;
this.showUniversalExpireTimerMessage = showUniversalExpireTimerMessage;
}
public long getThreadId() {
return threadId;
}
long getLastSeen() {
return lastSeen;
}
int getLastSeenPosition() {
return lastSeenPosition;
}
int getLastScrolledPosition() {
return lastScrolledPosition;
}
boolean hasSent() {
return hasSent;
}
boolean shouldJumpToMessage() {
return jumpToPosition >= 0;
}
boolean shouldScrollToLastSeen() {
return lastSeenPosition > 0;
}
int getJumpToPosition() {
return jumpToPosition;
}
int getThreadSize() {
return threadSize;
}
@NonNull MessageRequestData getMessageRequestData() {
return messageRequestData;
}
public boolean showUniversalExpireTimerMessage() {
return showUniversalExpireTimerMessage;
}
static final class MessageRequestData {
private final boolean messageRequestAccepted;
private final boolean groupsInCommon;
private final boolean isGroup;
public MessageRequestData(boolean messageRequestAccepted) {
this(messageRequestAccepted, false, false);
}
public MessageRequestData(boolean messageRequestAccepted, boolean groupsInCommon, boolean isGroup) {
this.messageRequestAccepted = messageRequestAccepted;
this.groupsInCommon = groupsInCommon;
this.isGroup = isGroup;
}
public boolean isMessageRequestAccepted() {
return messageRequestAccepted;
}
public boolean includeWarningUpdateMessage() {
return !messageRequestAccepted && !groupsInCommon;
}
public boolean isGroup() {
return isGroup;
}
}
}

View File

@@ -0,0 +1,35 @@
package org.thoughtcrime.securesms.conversation
/**
* Represents metadata about a conversation.
*/
data class ConversationData(
val threadId: Long,
val lastSeen: Long,
val lastSeenPosition: Int,
val lastScrolledPosition: Int,
val jumpToPosition: Int,
val threadSize: Int,
val messageRequestData: MessageRequestData,
@get:JvmName("showUniversalExpireTimerMessage") val showUniversalExpireTimerMessage: Boolean
) {
fun shouldJumpToMessage(): Boolean {
return jumpToPosition >= 0
}
fun shouldScrollToLastSeen(): Boolean {
return lastSeenPosition > 0
}
data class MessageRequestData @JvmOverloads constructor(
val isMessageRequestAccepted: Boolean,
private val groupsInCommon: Boolean = false,
val isGroup: Boolean = false
) {
fun includeWarningUpdateMessage(): Boolean {
return !isMessageRequestAccepted && !groupsInCommon
}
}
}

View File

@@ -47,25 +47,41 @@ class ConversationDataSource implements PagedDataSource<MessageId, ConversationM
private final MessageRequestData messageRequestData;
private final boolean showUniversalExpireTimerUpdate;
ConversationDataSource(@NonNull Context context, long threadId, @NonNull MessageRequestData messageRequestData, boolean showUniversalExpireTimerUpdate) {
/** Used once for the initial fetch, then cleared. */
private int baseSize;
ConversationDataSource(@NonNull Context context, long threadId, @NonNull MessageRequestData messageRequestData, boolean showUniversalExpireTimerUpdate, int baseSize) {
this.context = context;
this.threadId = threadId;
this.messageRequestData = messageRequestData;
this.showUniversalExpireTimerUpdate = showUniversalExpireTimerUpdate;
this.baseSize = baseSize;
}
@Override
public int size() {
long startTime = System.currentTimeMillis();
int size = SignalDatabase.mmsSms().getConversationCount(threadId) +
int size = getSizeInternal() +
(messageRequestData.includeWarningUpdateMessage() ? 1 : 0) +
(showUniversalExpireTimerUpdate ? 1 : 0);
Log.d(TAG, "size() for thread " + threadId + ": " + (System.currentTimeMillis() - startTime) + " ms");
Log.d(TAG, "[size(), thread " + threadId + "] " + (System.currentTimeMillis() - startTime) + " ms");
return size;
}
private int getSizeInternal() {
synchronized (this) {
if (baseSize != -1) {
int size = baseSize;
baseSize = -1;
return size;
}
}
return SignalDatabase.mmsSms().getConversationCount(threadId);
}
@Override
public @NonNull List<ConversationMessage> load(int start, int length, @NonNull CancellationSignal cancellationSignal) {
Stopwatch stopwatch = new Stopwatch("load(" + start + ", " + length + "), thread " + threadId);

View File

@@ -195,8 +195,9 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
private static final int SCROLL_ANIMATION_THRESHOLD = 50;
private static final int CODE_ADD_EDIT_CONTACT = 77;
private final ActionModeCallback actionModeCallback = new ActionModeCallback();
private final ItemClickListener selectionClickListener = new ConversationFragmentItemClickListener();
private final ActionModeCallback actionModeCallback = new ActionModeCallback();
private final ItemClickListener selectionClickListener = new ConversationFragmentItemClickListener();
private final LifecycleDisposable disposables = new LifecycleDisposable();
private ConversationFragmentListener listener;
@@ -241,6 +242,9 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
private MultiselectItemDecoration multiselectItemDecoration;
private LifecycleDisposable lifecycleDisposable;
private @Nullable ConversationData conversationData;
private @Nullable ChatWallpaper chatWallpaper;
public static void prepare(@NonNull Context context) {
FrameLayout parent = new FrameLayout(context);
parent.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT));
@@ -263,6 +267,8 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle bundle) {
disposables.bindTo(getViewLifecycleOwner());
final View view = inflater.inflate(R.layout.conversation_fragment, container, false);
videoContainer = view.findViewById(R.id.video_container);
list = view.findViewById(android.R.id.list);
@@ -291,8 +297,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
() -> conversationViewModel.shouldPlayMessageAnimations() && list.getScrollState() == RecyclerView.SCROLL_STATE_IDLE,
() -> list.canScrollVertically(1) || list.canScrollVertically(-1));
multiselectItemDecoration = new MultiselectItemDecoration(requireContext(),
() -> conversationViewModel.getWallpaper().getValue());
multiselectItemDecoration = new MultiselectItemDecoration(requireContext(), () -> chatWallpaper);
list.setHasFixedSize(false);
list.setLayoutManager(layoutManager);
@@ -333,17 +338,22 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
this.messageCountsViewModel = new ViewModelProvider(getParentFragment()).get(MessageCountsViewModel.class);
this.conversationViewModel = new ViewModelProvider(getParentFragment(), new ConversationViewModel.Factory()).get(ConversationViewModel.class);
conversationViewModel.getChatColors().observe(getViewLifecycleOwner(), recyclerViewColorizer::setChatColors);
conversationViewModel.getMessages().observe(getViewLifecycleOwner(), messages -> {
disposables.add(conversationViewModel.getChatColors().subscribe(recyclerViewColorizer::setChatColors));
disposables.add(conversationViewModel.getMessageData().subscribe(messageData -> {
ConversationAdapter adapter = getListAdapter();
if (adapter != null) {
List<ConversationMessage> messages = messageData.getMessages();
getListAdapter().submitList(messages, () -> {
list.post(() -> conversationViewModel.onMessagesCommitted(messages));
list.post(() -> {
conversationViewModel.onMessagesCommitted(messages);
});
});
}
});
conversationViewModel.getConversationMetadata().observe(getViewLifecycleOwner(), this::presentConversationMetadata);
presentConversationMetadata(messageData.getMetadata());
}));
disposables.add(conversationViewModel.getWallpaper().subscribe(w -> chatWallpaper = w.orElse(null)));
conversationViewModel.getShowMentionsButton().observe(getViewLifecycleOwner(), shouldShow -> {
if (shouldShow) {
@@ -367,14 +377,14 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
updateToolbarDependentMargins();
colorizer = new Colorizer();
conversationViewModel.getNameColorsMap().observe(getViewLifecycleOwner(), nameColorsMap -> {
disposables.add(conversationViewModel.getNameColorsMap().subscribe(nameColorsMap -> {
colorizer.onNameColorsChanged(nameColorsMap);
ConversationAdapter adapter = getListAdapter();
if (adapter != null) {
adapter.notifyItemRangeChanged(0, adapter.getItemCount(), ConversationAdapter.PAYLOAD_NAME_COLORS);
}
});
}));
conversationUpdateTick = new ConversationUpdateTick(this::updateConversationItemTimestamps);
getViewLifecycleOwner().getLifecycle().addObserver(conversationUpdateTick);
@@ -491,7 +501,8 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
}
public void moveToLastSeen() {
if (conversationViewModel.getLastSeenPosition() <= 0) {
int lastSeenPosition = conversationData != null ? conversationData.getLastSeenPosition() : 0;
if (lastSeenPosition <= 0) {
Log.i(TAG, "No need to move to last seen.");
return;
}
@@ -501,7 +512,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
return;
}
int position = getListAdapter().getAdapterPositionForMessagePosition(conversationViewModel.getLastSeenPosition());
int position = getListAdapter().getAdapterPositionForMessagePosition(lastSeenPosition);
snapToTopDataObserver.requestScrollPosition(position);
}
@@ -631,9 +642,8 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
}
private void initializeResources() {
long oldThreadId = threadId;
int startingPosition = getStartPosition();
long oldThreadId = threadId;
int startingPosition = getStartPosition();
this.recipient = Recipient.live(conversationViewModel.getArgs().getRecipientId());
this.threadId = conversationViewModel.getArgs().getThreadId();
@@ -678,14 +688,14 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
adapter.registerAdapterDataObserver(snapToTopDataObserver);
adapter.registerAdapterDataObserver(new CheckExpirationDataObserver());
setLastSeen(conversationViewModel.getLastSeen());
setLastSeen(conversationData != null ? conversationData.getLastSeen() : 0);
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
adapter.unregisterAdapterDataObserver(this);
startupStopwatch.split("data-set");
SignalLocalMetrics.ConversationOpen.onDataLoaded();
adapter.unregisterAdapterDataObserver(this);
list.post(() -> {
startupStopwatch.split("first-render");
startupStopwatch.stop(TAG);
@@ -1100,6 +1110,13 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
}
private void presentConversationMetadata(@NonNull ConversationData conversation) {
if (conversationData != null && conversationData.getThreadId() == conversation.getThreadId()) {
Log.d(TAG, "Already presented conversation data for thread " + threadId);
return;
}
conversationData = conversation;
ConversationAdapter adapter = getListAdapter();
if (adapter == null) {
return;

View File

@@ -108,6 +108,9 @@ import org.thoughtcrime.securesms.PromptMmsActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.ShortcutLauncherActivity;
import org.thoughtcrime.securesms.TransportOption;
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel;
import org.thoughtcrime.securesms.util.LifecycleDisposable;
import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.TombstoneAttachment;
import org.thoughtcrime.securesms.audio.AudioRecorder;
@@ -439,6 +442,8 @@ public class ConversationParentFragment extends Fragment
private boolean isSecurityInitialized = false;
private boolean isSearchRequested = false;
private final LifecycleDisposable disposables = new LifecycleDisposable();
private volatile boolean screenInitialized = false;
private IdentityRecordList identityRecords = new IdentityRecordList(Collections.emptyList());
@@ -452,6 +457,8 @@ public class ConversationParentFragment extends Fragment
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
disposables.bindTo(getViewLifecycleOwner());
if (requireActivity() instanceof Callback) {
callback = (Callback) requireActivity();
} else if (getParentFragment() instanceof Callback) {
@@ -552,7 +559,7 @@ public class ConversationParentFragment extends Fragment
initializeInsightObserver();
initializeActionBar();
viewModel.getStoryViewState(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), titleView::setStoryRingFromState);
disposables.add(viewModel.getStoryViewState().subscribe(titleView::setStoryRingFromState));
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
@Override
@@ -1026,13 +1033,13 @@ public class ConversationParentFragment extends Fragment
}
hideMenuItem(menu, R.id.menu_create_bubble);
viewModel.canShowAsBubble().observe(getViewLifecycleOwner(), canShowAsBubble -> {
disposables.add(viewModel.canShowAsBubble().subscribe(canShowAsBubble -> {
MenuItem item = menu.findItem(R.id.menu_create_bubble);
if (item != null) {
item.setVisible(canShowAsBubble && !isInBubble());
}
});
}));
if (threadId == -1L) {
hideMenuItem(menu, R.id.menu_view_media);
@@ -2300,8 +2307,8 @@ public class ConversationParentFragment extends Fragment
this.viewModel = new ViewModelProvider(this, new ConversationViewModel.Factory()).get(ConversationViewModel.class);
this.viewModel.setArgs(args);
this.viewModel.getWallpaper().observe(getViewLifecycleOwner(), this::updateWallpaper);
this.viewModel.getEvents().observe(getViewLifecycleOwner(), this::onViewModelEvent);
disposables.add(this.viewModel.getWallpaper().subscribe(w -> updateWallpaper(w.orElse(null))));
}
private void initializeGroupViewModel() {

View File

@@ -5,10 +5,9 @@ import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
@@ -21,26 +20,15 @@ import org.thoughtcrime.securesms.util.ConversationUtil;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executor;
class ConversationRepository {
private static final String TAG = Log.tag(ConversationRepository.class);
private final Context context;
private final Executor executor;
ConversationRepository() {
this.context = ApplicationDependencies.getApplication();
this.executor = SignalExecutors.BOUNDED;
}
LiveData<ConversationData> getConversationData(long threadId, @NonNull Recipient recipient, int jumpToPosition) {
MutableLiveData<ConversationData> liveData = new MutableLiveData<>();
executor.execute(() -> {
liveData.postValue(getConversationDataInternal(threadId, recipient, jumpToPosition));
});
return liveData;
this.context = ApplicationDependencies.getApplication();
}
@WorkerThread
@@ -54,11 +42,11 @@ class ConversationRepository {
}
}
private @NonNull ConversationData getConversationDataInternal(long threadId, @NonNull Recipient conversationRecipient, int jumpToPosition) {
@WorkerThread
public @NonNull ConversationData getConversationData(long threadId, @NonNull Recipient conversationRecipient, int jumpToPosition) {
ThreadDatabase.ConversationMetadata metadata = SignalDatabase.threads().getConversationMetadata(threadId);
int threadSize = SignalDatabase.mmsSms().getConversationCount(threadId);
long lastSeen = metadata.getLastSeen();
boolean hasSent = metadata.hasSent();
int lastSeenPosition = 0;
long lastScrolled = metadata.getLastScrolled();
int lastScrolledPosition = 0;
@@ -108,6 +96,6 @@ class ConversationRepository {
showUniversalExpireTimerUpdate = true;
}
return new ConversationData(threadId, lastSeen, lastSeenPosition, lastScrolledPosition, hasSent, jumpToPosition, threadSize, messageRequestData, showUniversalExpireTimerUpdate);
return new ConversationData(threadId, lastSeen, lastSeenPosition, lastScrolledPosition, jumpToPosition, threadSize, messageRequestData, showUniversalExpireTimerUpdate);
}
}

View File

@@ -5,7 +5,6 @@ import android.app.Application;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.LiveDataReactiveStreams;
import androidx.lifecycle.MutableLiveData;
@@ -19,9 +18,9 @@ import com.annimon.stream.Stream;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.reactivestreams.Publisher;
import org.signal.core.util.MapUtil;
import org.signal.core.util.logging.Log;
import org.signal.paging.ObservablePagedData;
import org.signal.paging.PagedData;
import org.signal.paging.PagingConfig;
import org.signal.paging.PagingController;
@@ -31,12 +30,12 @@ import org.thoughtcrime.securesms.conversation.colors.ChatColors;
import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette;
import org.thoughtcrime.securesms.conversation.colors.NameColor;
import org.thoughtcrime.securesms.database.DatabaseObserver;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.database.model.StoryViewState;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.LiveGroup;
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.mediasend.MediaRepository;
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile;
@@ -44,7 +43,7 @@ import org.thoughtcrime.securesms.notifications.profiles.NotificationProfiles;
import org.thoughtcrime.securesms.ratelimit.RecaptchaRequiredEvent;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -63,39 +62,41 @@ import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.BackpressureStrategy;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subjects.BehaviorSubject;
public class ConversationViewModel extends ViewModel {
private static final String TAG = Log.tag(ConversationViewModel.class);
private final Application context;
private final MediaRepository mediaRepository;
private final ConversationRepository conversationRepository;
private final MutableLiveData<List<Media>> recentMedia;
private final MutableLiveData<Long> threadId;
private final LiveData<List<ConversationMessage>> messages;
private final LiveData<ConversationData> conversationMetadata;
private final MutableLiveData<Boolean> showScrollButtons;
private final MutableLiveData<Boolean> hasUnreadMentions;
private final LiveData<Boolean> canShowAsBubble;
private final ProxyPagingController<MessageId> pagingController;
private final DatabaseObserver.Observer conversationObserver;
private final DatabaseObserver.MessageObserver messageUpdateObserver;
private final DatabaseObserver.MessageObserver messageInsertObserver;
private final MutableLiveData<RecipientId> recipientId;
private final LiveData<ChatWallpaper> wallpaper;
private final SingleLiveEvent<Event> events;
private final LiveData<ChatColors> chatColors;
private final MutableLiveData<Integer> toolbarBottom;
private final MutableLiveData<Integer> inlinePlayerHeight;
private final LiveData<Integer> conversationTopMargin;
private final Store<ThreadAnimationState> threadAnimationStateStore;
private final Observer<ThreadAnimationState> threadAnimationStateStoreDriver;
private final NotificationProfilesRepository notificationProfilesRepository;
private final MutableLiveData<String> searchQuery;
private final Application context;
private final MediaRepository mediaRepository;
private final ConversationRepository conversationRepository;
private final MutableLiveData<List<Media>> recentMedia;
private final BehaviorSubject<Long> threadId;
private final Observable<MessageData> messageData;
private final MutableLiveData<Boolean> showScrollButtons;
private final MutableLiveData<Boolean> hasUnreadMentions;
private final Observable<Boolean> canShowAsBubble;
private final ProxyPagingController<MessageId> pagingController;
private final DatabaseObserver.Observer conversationObserver;
private final DatabaseObserver.MessageObserver messageUpdateObserver;
private final DatabaseObserver.MessageObserver messageInsertObserver;
private final BehaviorSubject<RecipientId> recipientId;
private final Observable<Optional<ChatWallpaper>> wallpaper;
private final SingleLiveEvent<Event> events;
private final Observable<ChatColors> chatColors;
private final MutableLiveData<Integer> toolbarBottom;
private final MutableLiveData<Integer> inlinePlayerHeight;
private final LiveData<Integer> conversationTopMargin;
private final Store<ThreadAnimationState> threadAnimationStateStore;
private final Observer<ThreadAnimationState> threadAnimationStateStoreDriver;
private final NotificationProfilesRepository notificationProfilesRepository;
private final MutableLiveData<String> searchQuery;
private final Map<GroupId, Set<Recipient>> sessionMemberCache = new HashMap<>();
@@ -107,10 +108,8 @@ public class ConversationViewModel extends ViewModel {
this.mediaRepository = new MediaRepository();
this.conversationRepository = new ConversationRepository();
this.recentMedia = new MutableLiveData<>();
this.threadId = new MutableLiveData<>();
this.showScrollButtons = new MutableLiveData<>(false);
this.hasUnreadMentions = new MutableLiveData<>(false);
this.recipientId = new MutableLiveData<>();
this.events = new SingleLiveEvent<>();
this.pagingController = new ProxyPagingController<>();
this.conversationObserver = pagingController::onDataInvalidated;
@@ -122,65 +121,75 @@ public class ConversationViewModel extends ViewModel {
this.threadAnimationStateStore = new Store<>(new ThreadAnimationState(-1L, null, false));
this.notificationProfilesRepository = new NotificationProfilesRepository();
this.searchQuery = new MutableLiveData<>();
this.recipientId = BehaviorSubject.create();
this.threadId = BehaviorSubject.create();
LiveData<Recipient> recipientLiveData = LiveDataUtil.mapAsync(recipientId, Recipient::resolved);
LiveData<ThreadAndRecipient> threadAndRecipient = LiveDataUtil.combineLatest(threadId, recipientLiveData, ThreadAndRecipient::new);
BehaviorSubject<Recipient> recipientCache = BehaviorSubject.create();
LiveData<ConversationData> metadata = Transformations.switchMap(threadAndRecipient, d -> {
LiveData<ConversationData> conversationData = conversationRepository.getConversationData(d.threadId, d.recipient, jumpToPosition);
recipientId
.observeOn(Schedulers.io())
.distinctUntilChanged()
.map(Recipient::resolved)
.subscribe(recipientCache);
jumpToPosition = -1;
BehaviorSubject<ConversationData> conversationMetadata = BehaviorSubject.create();
return conversationData;
});
Observable.combineLatest(threadId, recipientCache, Pair::new)
.observeOn(Schedulers.io())
.distinctUntilChanged()
.map(threadIdAndRecipient -> {
SignalLocalMetrics.ConversationOpen.onMetadataLoadStarted();
ConversationData conversationData = conversationRepository.getConversationData(threadIdAndRecipient.first(), threadIdAndRecipient.second(), jumpToPosition);
SignalLocalMetrics.ConversationOpen.onMetadataLoaded();
jumpToPosition = -1;
return conversationData;
})
.subscribe(conversationMetadata);
ApplicationDependencies.getDatabaseObserver().registerMessageUpdateObserver(messageUpdateObserver);
LiveData<Pair<Long, PagedData<MessageId, ConversationMessage>>> pagedDataForThreadId = Transformations.map(metadata, data -> {
int startPosition;
ConversationData.MessageRequestData messageRequestData = data.getMessageRequestData();
messageData = conversationMetadata
.observeOn(Schedulers.io())
.switchMap(data -> {
int startPosition;
if (data.shouldJumpToMessage()) {
startPosition = data.getJumpToPosition();
} else if (messageRequestData.isMessageRequestAccepted() && data.shouldScrollToLastSeen()) {
startPosition = data.getLastSeenPosition();
} else if (messageRequestData.isMessageRequestAccepted()) {
startPosition = data.getLastScrolledPosition();
} else {
startPosition = data.getThreadSize();
}
ConversationData.MessageRequestData messageRequestData = data.getMessageRequestData();
ApplicationDependencies.getDatabaseObserver().unregisterObserver(conversationObserver);
ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageInsertObserver);
ApplicationDependencies.getDatabaseObserver().registerConversationObserver(data.getThreadId(), conversationObserver);
ApplicationDependencies.getDatabaseObserver().registerMessageInsertObserver(data.getThreadId(), messageInsertObserver);
if (data.shouldJumpToMessage()) {
startPosition = data.getJumpToPosition();
} else if (messageRequestData.isMessageRequestAccepted() && data.shouldScrollToLastSeen()) {
startPosition = data.getLastSeenPosition();
} else if (messageRequestData.isMessageRequestAccepted()) {
startPosition = data.getLastScrolledPosition();
} else {
startPosition = data.getThreadSize();
}
ConversationDataSource dataSource = new ConversationDataSource(context, data.getThreadId(), messageRequestData, data.showUniversalExpireTimerMessage());
PagingConfig config = new PagingConfig.Builder().setPageSize(25)
.setBufferPages(3)
.setStartIndex(Math.max(startPosition, 0))
.build();
ApplicationDependencies.getDatabaseObserver().unregisterObserver(conversationObserver);
ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageInsertObserver);
ApplicationDependencies.getDatabaseObserver().registerConversationObserver(data.getThreadId(), conversationObserver);
ApplicationDependencies.getDatabaseObserver().registerMessageInsertObserver(data.getThreadId(), messageInsertObserver);
Log.d(TAG, "Starting at position: " + startPosition + " || jumpToPosition: " + data.getJumpToPosition() + ", lastSeenPosition: " + data.getLastSeenPosition() + ", lastScrolledPosition: " + data.getLastScrolledPosition());
return new Pair<>(data.getThreadId(), PagedData.create(dataSource, config));
});
ConversationDataSource dataSource = new ConversationDataSource(context, data.getThreadId(), messageRequestData, data.showUniversalExpireTimerMessage(), data.getThreadSize());
PagingConfig config = new PagingConfig.Builder().setPageSize(25)
.setBufferPages(2)
.setStartIndex(Math.max(startPosition, 0))
.build();
this.messages = Transformations.switchMap(pagedDataForThreadId, pair -> {
pagingController.set(pair.second().getController());
return pair.second().getData();
});
Log.d(TAG, "Starting at position: " + startPosition + " || jumpToPosition: " + data.getJumpToPosition() + ", lastSeenPosition: " + data.getLastSeenPosition() + ", lastScrolledPosition: " + data.getLastScrolledPosition());
ObservablePagedData<MessageId, ConversationMessage> pagedData = PagedData.createForObservable(dataSource, config);
conversationMetadata = Transformations.switchMap(messages, m -> metadata);
canShowAsBubble = LiveDataUtil.mapAsync(threadId, conversationRepository::canShowAsBubble);
wallpaper = LiveDataUtil.mapDistinct(Transformations.switchMap(recipientId,
id -> Recipient.live(id).getLiveData()),
Recipient::getWallpaper);
pagingController.set(pagedData.getController());
return pagedData.getData();
})
.observeOn(Schedulers.io())
.withLatestFrom(conversationMetadata, (messages, metadata) -> new MessageData(metadata, messages));
EventBus.getDefault().register(this);
chatColors = LiveDataUtil.mapDistinct(Transformations.switchMap(recipientId,
id -> Recipient.live(id).getLiveData()),
Recipient::getChatColors);
canShowAsBubble = threadId.observeOn(Schedulers.io()).map(conversationRepository::canShowAsBubble);
wallpaper = recipientCache.map(r -> Optional.ofNullable(r.getWallpaper())).distinctUntilChanged();
chatColors = recipientCache.map(Recipient::getChatColors).distinctUntilChanged();
threadAnimationStateStore.update(threadId, (id, state) -> {
if (state.getThreadId() == id) {
@@ -190,7 +199,7 @@ public class ConversationViewModel extends ViewModel {
}
});
threadAnimationStateStore.update(metadata, (m, state) -> {
threadAnimationStateStore.update(conversationMetadata, (m, state) -> {
if (state.getThreadId() == m.getThreadId()) {
return state.copy(state.getThreadId(), m, state.getHasCommittedNonEmptyMessageList());
} else {
@@ -200,14 +209,16 @@ public class ConversationViewModel extends ViewModel {
this.threadAnimationStateStoreDriver = state -> {};
threadAnimationStateStore.getStateLiveData().observeForever(threadAnimationStateStoreDriver);
EventBus.getDefault().register(this);
}
LiveData<StoryViewState> getStoryViewState(@NonNull LifecycleOwner lifecycle) {
Publisher<RecipientId> recipientIdPublisher = LiveDataReactiveStreams.toPublisher(lifecycle, recipientId);
Flowable<StoryViewState> storyViewState = Flowable.fromPublisher(recipientIdPublisher)
.flatMap(id -> StoryViewState.getForRecipientId(id).toFlowable(BackpressureStrategy.LATEST));
return LiveDataReactiveStreams.fromPublisher(storyViewState);
Observable<StoryViewState> getStoryViewState() {
return recipientId
.subscribeOn(Schedulers.io())
.switchMap(StoryViewState::getForRecipientId)
.distinctUntilChanged()
.observeOn(AndroidSchedulers.mainThread());
}
void onMessagesCommitted(@NonNull List<ConversationMessage> conversationMessages) {
@@ -249,13 +260,13 @@ public class ConversationViewModel extends ViewModel {
Log.d(TAG, "[onConversationDataAvailable] recipientId: " + recipientId + ", threadId: " + threadId + ", startingPosition: " + startingPosition);
this.jumpToPosition = startingPosition;
this.threadId.setValue(threadId);
this.recipientId.setValue(recipientId);
this.threadId.onNext(threadId);
this.recipientId.onNext(recipientId);
}
void clearThreadId() {
this.jumpToPosition = -1;
this.threadId.postValue(-1L);
this.threadId.onNext(-1L);
}
void setSearchQuery(@Nullable String query) {
@@ -270,8 +281,9 @@ public class ConversationViewModel extends ViewModel {
return conversationTopMargin;
}
@NonNull LiveData<Boolean> canShowAsBubble() {
return canShowAsBubble;
@NonNull Observable<Boolean> canShowAsBubble() {
return canShowAsBubble
.observeOn(AndroidSchedulers.mainThread());
}
@NonNull LiveData<Boolean> getShowScrollToBottom() {
@@ -282,16 +294,18 @@ public class ConversationViewModel extends ViewModel {
return Transformations.distinctUntilChanged(LiveDataUtil.combineLatest(showScrollButtons, hasUnreadMentions, (a, b) -> a && b));
}
@NonNull LiveData<ChatWallpaper> getWallpaper() {
return wallpaper;
@NonNull Observable<Optional<ChatWallpaper>> getWallpaper() {
return wallpaper
.observeOn(AndroidSchedulers.mainThread());
}
@NonNull LiveData<Event> getEvents() {
return events;
}
@NonNull LiveData<ChatColors> getChatColors() {
return chatColors;
@NonNull Observable<ChatColors> getChatColors() {
return chatColors
.observeOn(AndroidSchedulers.mainThread());
}
void setHasUnreadMentions(boolean hasUnreadMentions) {
@@ -310,55 +324,45 @@ public class ConversationViewModel extends ViewModel {
return recentMedia;
}
@NonNull LiveData<ConversationData> getConversationMetadata() {
return conversationMetadata;
@NonNull Observable<MessageData> getMessageData() {
return messageData
.observeOn(AndroidSchedulers.mainThread());
}
@NonNull LiveData<List<ConversationMessage>> getMessages() {
return messages;
}
@NonNull PagingController getPagingController() {
@NonNull PagingController<MessageId> getPagingController() {
return pagingController;
}
@NonNull LiveData<Map<RecipientId, NameColor>> getNameColorsMap() {
LiveData<Recipient> recipient = Transformations.switchMap(recipientId, r -> Recipient.live(r).getLiveData());
LiveData<Optional<GroupId>> group = Transformations.map(recipient, Recipient::getGroupId);
LiveData<Set<Recipient>> groupMembers = Transformations.switchMap(group, g -> {
//noinspection CodeBlock2Expr
return g.map(this::getSessionGroupRecipients)
.orElseGet(() -> new DefaultValueLiveData<>(Collections.emptySet()));
});
@NonNull Observable<Map<RecipientId, NameColor>> getNameColorsMap() {
return recipientId.map(Recipient::resolved)
.map(Recipient::getGroupId)
.map(groupId -> {
if (groupId.isPresent()) {
List<Recipient> fullMembers = SignalDatabase.groups().getGroupMembers(groupId.get(), GroupDatabase.MemberSet.FULL_MEMBERS_INCLUDING_SELF);
Set<Recipient> cachedMembers = MapUtil.getOrDefault(sessionMemberCache, groupId.get(), new HashSet<>());
cachedMembers.addAll(fullMembers);
sessionMemberCache.put(groupId.get(), cachedMembers);
return cachedMembers;
} else {
return Collections.<Recipient>emptySet();
}
})
.map(members -> {
List<Recipient> sorted = Stream.of(members)
.filter(member -> !Objects.equals(member, Recipient.self()))
.sortBy(Recipient::requireStringId)
.toList();
return Transformations.map(groupMembers, members -> {
List<Recipient> sorted = Stream.of(members)
.filter(member -> !Objects.equals(member, Recipient.self()))
.sortBy(Recipient::requireStringId)
.toList();
List<NameColor> names = ChatColorsPalette.Names.getAll();
Map<RecipientId, NameColor> colors = new HashMap<>();
for (int i = 0; i < sorted.size(); i++) {
colors.put(sorted.get(i).getId(), names.get(i % names.size()));
}
List<NameColor> names = ChatColorsPalette.Names.getAll();
Map<RecipientId, NameColor> colors = new HashMap<>();
for (int i = 0; i < sorted.size(); i++) {
colors.put(sorted.get(i).getId(), names.get(i % names.size()));
}
return colors;
});
}
private @NonNull LiveData<Set<Recipient>> getSessionGroupRecipients(@NonNull GroupId groupId) {
LiveData<List<Recipient>> fullMembers = Transformations.map(new LiveGroup(groupId).getFullMembers(),
members -> Stream.of(members)
.map(GroupMemberEntry.FullMember::getMember)
.toList());
return Transformations.map(fullMembers, currentMembership -> {
Set<Recipient> cachedMembers = MapUtil.getOrDefault(sessionMemberCache, groupId, new HashSet<>());
cachedMembers.addAll(currentMembership);
sessionMemberCache.put(groupId, cachedMembers);
return cachedMembers;
});
return colors;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
@NonNull LiveData<Optional<NotificationProfile>> getActiveNotificationProfile() {
@@ -368,14 +372,6 @@ public class ConversationViewModel extends ViewModel {
return LiveDataReactiveStreams.fromPublisher(activeProfile.toFlowable(BackpressureStrategy.LATEST));
}
long getLastSeen() {
return conversationMetadata.getValue() != null ? conversationMetadata.getValue().getLastSeen() : 0;
}
int getLastSeenPosition() {
return conversationMetadata.getValue() != null ? conversationMetadata.getValue().getLastSeenPosition() : 0;
}
void setArgs(@NonNull ConversationIntents.Args args) {
this.args = args;
}
@@ -403,14 +399,21 @@ public class ConversationViewModel extends ViewModel {
SHOW_RECAPTCHA
}
private static class ThreadAndRecipient {
static class MessageData {
private final List<ConversationMessage> messages;
private final ConversationData metadata;
private final long threadId;
private final Recipient recipient;
MessageData(@NonNull ConversationData metadata, @NonNull List<ConversationMessage> messages) {
this.metadata = metadata;
this.messages = messages;
}
public ThreadAndRecipient(long threadId, Recipient recipient) {
this.threadId = threadId;
this.recipient = recipient;
public @NonNull List<ConversationMessage> getMessages() {
return messages;
}
public @NonNull ConversationData getMetadata() {
return metadata;
}
}

View File

@@ -11,6 +11,7 @@ import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.signal.core.util.logging.Log;
import org.signal.paging.LivePagedData;
import org.signal.paging.PagedData;
import org.signal.paging.PagingConfig;
import org.signal.paging.PagingController;
@@ -56,24 +57,24 @@ class ConversationListViewModel extends ViewModel {
private static boolean coldStart = true;
private final MutableLiveData<Megaphone> megaphone;
private final MutableLiveData<SearchResult> searchResult;
private final MutableLiveData<ConversationSet> selectedConversations;
private final Set<Conversation> internalSelection;
private final ConversationListDataSource conversationListDataSource;
private final PagedData<Long, Conversation> pagedData;
private final LiveData<Boolean> hasNoConversations;
private final SearchRepository searchRepository;
private final MegaphoneRepository megaphoneRepository;
private final Debouncer messageSearchDebouncer;
private final Debouncer contactSearchDebouncer;
private final ThrottledDebouncer updateDebouncer;
private final DatabaseObserver.Observer observer;
private final Invalidator invalidator;
private final CompositeDisposable disposables;
private final UnreadPaymentsLiveData unreadPaymentsLiveData;
private final UnreadPaymentsRepository unreadPaymentsRepository;
private final NotificationProfilesRepository notificationProfilesRepository;
private final MutableLiveData<Megaphone> megaphone;
private final MutableLiveData<SearchResult> searchResult;
private final MutableLiveData<ConversationSet> selectedConversations;
private final Set<Conversation> internalSelection;
private final ConversationListDataSource conversationListDataSource;
private final LivePagedData<Long, Conversation> pagedData;
private final LiveData<Boolean> hasNoConversations;
private final SearchRepository searchRepository;
private final MegaphoneRepository megaphoneRepository;
private final Debouncer messageSearchDebouncer;
private final Debouncer contactSearchDebouncer;
private final ThrottledDebouncer updateDebouncer;
private final DatabaseObserver.Observer observer;
private final Invalidator invalidator;
private final CompositeDisposable disposables;
private final UnreadPaymentsLiveData unreadPaymentsLiveData;
private final UnreadPaymentsRepository unreadPaymentsRepository;
private final NotificationProfilesRepository notificationProfilesRepository;
private String activeQuery;
private SearchResult activeSearchResult;
@@ -95,8 +96,8 @@ class ConversationListViewModel extends ViewModel {
this.invalidator = new Invalidator();
this.disposables = new CompositeDisposable();
this.conversationListDataSource = ConversationListDataSource.create(application, isArchived);
this.pagedData = PagedData.create(conversationListDataSource,
new PagingConfig.Builder()
this.pagedData = PagedData.createForLiveData(conversationListDataSource,
new PagingConfig.Builder()
.setPageSize(15)
.setBufferPages(2)
.build());

View File

@@ -1379,10 +1379,10 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
return DeviceLastResetTime.newBuilder().build()
}
fun setBadges(id: RecipientId, badges: List<Badge?>) {
fun setBadges(id: RecipientId, badges: List<Badge>) {
val badgeListBuilder = BadgeList.newBuilder()
for (badge in badges) {
badgeListBuilder.addBadges(toDatabaseBadge(badge!!))
badgeListBuilder.addBadges(toDatabaseBadge(badge))
}
val values = ContentValues(1).apply {
@@ -1390,7 +1390,6 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
}
if (update(id, values)) {
ApplicationDependencies.getDatabaseObserver().notifyNotificationProfileObservers()
ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id)
}
}

View File

@@ -12,6 +12,7 @@ import androidx.lifecycle.ViewModelProvider;
import com.annimon.stream.Stream;
import org.signal.paging.LivePagedData;
import org.signal.paging.PagedData;
import org.signal.paging.PagingConfig;
import org.signal.paging.PagingController;
@@ -28,12 +29,12 @@ import java.util.Objects;
*/
public final class GiphyMp4ViewModel extends ViewModel {
private final GiphyMp4Repository repository;
private final MutableLiveData<PagedData<String, GiphyImage>> pagedData;
private final LiveData<MappingModelList> images;
private final LiveData<PagingController<String>> pagingController;
private final SingleLiveEvent<GiphyMp4SaveResult> saveResultEvents;
private final boolean isForMms;
private final GiphyMp4Repository repository;
private final MutableLiveData<LivePagedData<String, GiphyImage>> pagedData;
private final LiveData<MappingModelList> images;
private final LiveData<PagingController<String>> pagingController;
private final SingleLiveEvent<GiphyMp4SaveResult> saveResultEvents;
private final boolean isForMms;
private String query;
@@ -52,7 +53,7 @@ public final class GiphyMp4ViewModel extends ViewModel {
.collect(MappingModelList.toMappingModelList())));
}
LiveData<PagedData<String, GiphyImage>> getPagedData() {
LiveData<LivePagedData<String, GiphyImage>> getPagedData() {
return pagedData;
}
@@ -81,9 +82,9 @@ public final class GiphyMp4ViewModel extends ViewModel {
return pagingController;
}
private PagedData<String, GiphyImage> getGiphyImagePagedData(@Nullable String query) {
return PagedData.create(new GiphyMp4PagedDataSource(query),
new PagingConfig.Builder().setPageSize(20)
private LivePagedData<String, GiphyImage> getGiphyImagePagedData(@Nullable String query) {
return PagedData.createForLiveData(new GiphyMp4PagedDataSource(query),
new PagingConfig.Builder().setPageSize(20)
.setBufferPages(1)
.build());
}

View File

@@ -10,6 +10,7 @@ import androidx.lifecycle.ViewModelProvider;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
import org.signal.core.util.tracing.Tracer;
import org.signal.paging.LivePagedData;
import org.signal.paging.PagedData;
import org.signal.paging.PagingConfig;
import org.signal.paging.PagingController;
@@ -53,7 +54,7 @@ public class SubmitDebugLogViewModel extends ViewModel {
.setStartIndex(0)
.build();
PagedData<Long, LogLine> pagedData = PagedData.create(dataSource, config);
LivePagedData<Long, LogLine> pagedData = PagedData.createForLiveData(dataSource, config);
ThreadUtil.runOnMain(() -> {
pagingController.set(pagedData.getController());

View File

@@ -155,7 +155,6 @@ public class MessageRequestViewModel extends ViewModel {
private void loadRecipient() {
liveRecipient.observeForever(recipientObserver);
SignalExecutors.BOUNDED.execute(() -> {
liveRecipient.refresh();
recipient.postValue(liveRecipient.get());
});
}

View File

@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.stories.viewer.reply.group
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.signal.paging.LivePagedData
import org.signal.paging.PagedData
import org.signal.paging.PagingConfig
import org.thoughtcrime.securesms.database.DatabaseObserver
@@ -12,10 +13,10 @@ import org.thoughtcrime.securesms.recipients.RecipientId
class StoryGroupReplyRepository {
fun getPagedReplies(parentStoryId: Long): Observable<PagedData<StoryGroupReplyItemData.Key, StoryGroupReplyItemData>> {
return Observable.create<PagedData<StoryGroupReplyItemData.Key, StoryGroupReplyItemData>> { emitter ->
fun getPagedReplies(parentStoryId: Long): Observable<LivePagedData<StoryGroupReplyItemData.Key, StoryGroupReplyItemData>> {
return Observable.create<LivePagedData<StoryGroupReplyItemData.Key, StoryGroupReplyItemData>> { emitter ->
fun refresh() {
emitter.onNext(PagedData.create(StoryGroupReplyDataSource(parentStoryId), PagingConfig.Builder().build()))
emitter.onNext(PagedData.createForLiveData(StoryGroupReplyDataSource(parentStoryId), PagingConfig.Builder().build()))
}
val observer = DatabaseObserver.Observer {

View File

@@ -8,7 +8,7 @@ import androidx.lifecycle.ViewModelProvider
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import org.signal.paging.PagedData
import org.signal.paging.LivePagedData
import org.signal.paging.PagingController
import org.thoughtcrime.securesms.conversation.colors.NameColors
import org.thoughtcrime.securesms.groups.GroupId
@@ -23,7 +23,7 @@ class StoryGroupReplyViewModel(storyId: Long, repository: StoryGroupReplyReposit
val state: LiveData<StoryGroupReplyState> = store.stateLiveData
private val pagedData: MutableLiveData<PagedData<StoryGroupReplyItemData.Key, StoryGroupReplyItemData>> = MutableLiveData()
private val pagedData: MutableLiveData<LivePagedData<StoryGroupReplyItemData.Key, StoryGroupReplyItemData>> = MutableLiveData()
val pagingController: LiveData<PagingController<StoryGroupReplyItemData.Key>>
val pageData: LiveData<List<StoryGroupReplyItemData>>

View File

@@ -68,8 +68,8 @@ object LocalMetrics {
executor.execute {
val lastTime: Long? = lastSplitTimeById[id]
if (lastTime != null) {
val splitDoesNotExist: Boolean = eventsById[id]?.splits?.none { it.name == split } ?: true
if (lastTime != null && splitDoesNotExist) {
eventsById[id]?.splits?.add(LocalMetricsSplit(split, time - lastTime))
lastSplitTimeById[id] = time
}

View File

@@ -72,8 +72,10 @@ public final class SignalLocalMetrics {
public static final class ConversationOpen {
private static final String NAME = "conversation-open";
private static final String SPLIT_DATA_LOADED = "data-loaded";
private static final String SPLIT_RENDER = "render";
private static final String SPLIT_VIEWMODEL_INIT = "viewmodel-init";
private static final String SPLIT_METADATA_LOADED = "metadata-loaded";
private static final String SPLIT_DATA_LOADED = "data-loaded";
private static final String SPLIT_RENDER = "render";
private static String id;
@@ -82,6 +84,14 @@ public final class SignalLocalMetrics {
LocalMetrics.getInstance().start(id, NAME);
}
public static void onMetadataLoadStarted() {
LocalMetrics.getInstance().split(id, SPLIT_VIEWMODEL_INIT);
}
public static void onMetadataLoaded() {
LocalMetrics.getInstance().split(id, SPLIT_METADATA_LOADED);
}
public static void onDataLoaded() {
LocalMetrics.getInstance().split(id, SPLIT_DATA_LOADED);
}

View File

@@ -4,6 +4,7 @@ import androidx.annotation.AnyThread;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.LiveDataReactiveStreams;
import androidx.lifecycle.MediatorLiveData;
import com.annimon.stream.function.Function;
@@ -15,6 +16,9 @@ import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executor;
import io.reactivex.rxjava3.core.BackpressureStrategy;
import io.reactivex.rxjava3.core.Observable;
/**
* Manages a state to be updated from a view model and provide direct and live access. Updates
* occur serially on the same executor to allow updating in a thread safe way. While not
@@ -46,6 +50,11 @@ public class Store<State> {
liveStore.update(source, action);
}
@MainThread
public <Input> void update(@NonNull Observable<Input> source, @NonNull Action<Input, State> action) {
liveStore.update(LiveDataReactiveStreams.fromPublisher(source.toFlowable(BackpressureStrategy.LATEST)), action);
}
@MainThread
public void clear() {
liveStore.clear();