Move reactions into their own table.

This commit is contained in:
Greyson Parrelli
2021-11-11 13:12:51 -05:00
committed by Cody Henthorne
parent 3a1f06f510
commit ab55fec6bd
32 changed files with 650 additions and 567 deletions

View File

@@ -1,35 +0,0 @@
package org.thoughtcrime.securesms.reactions;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.recipients.Recipient;
public class ReactionDetails {
private final Recipient sender;
private final String baseEmoji;
private final String displayEmoji;
private final long timestamp;
ReactionDetails(@NonNull Recipient sender, @NonNull String baseEmoji, @NonNull String displayEmoji, long timestamp) {
this.sender = sender;
this.baseEmoji = baseEmoji;
this.displayEmoji = displayEmoji;
this.timestamp = timestamp;
}
public @NonNull Recipient getSender() {
return sender;
}
public @NonNull String getBaseEmoji() {
return baseEmoji;
}
public @NonNull String getDisplayEmoji() {
return displayEmoji;
}
public long getTimestamp() {
return timestamp;
}
}

View File

@@ -0,0 +1,13 @@
package org.thoughtcrime.securesms.reactions
import org.thoughtcrime.securesms.recipients.Recipient
/**
* A UI model for a reaction in the [ReactionsBottomSheetDialogFragment]
*/
data class ReactionDetails(
val sender: Recipient,
val baseEmoji: String,
val displayEmoji: String,
val timestamp: Long
)

View File

@@ -13,8 +13,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProviders;
import androidx.loader.app.LoaderManager;
import androidx.lifecycle.ViewModelProvider;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
@@ -23,6 +22,8 @@ import com.google.android.material.tabs.TabLayoutMediator;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.util.LifecycleDisposable;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -33,13 +34,13 @@ public final class ReactionsBottomSheetDialogFragment extends BottomSheetDialogF
private static final String ARGS_MESSAGE_ID = "reactions.args.message.id";
private static final String ARGS_IS_MMS = "reactions.args.is.mms";
private long messageId;
private ViewPager2 recipientPagerView;
private ReactionsLoader reactionsLoader;
private ReactionViewPagerAdapter recipientsAdapter;
private ReactionsViewModel viewModel;
private Callback callback;
private final LifecycleDisposable disposables = new LifecycleDisposable();
public static DialogFragment create(long messageId, boolean isMms) {
Bundle args = new Bundle();
DialogFragment fragment = new ReactionsBottomSheetDialogFragment();
@@ -61,7 +62,6 @@ public final class ReactionsBottomSheetDialogFragment extends BottomSheetDialogF
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
if (ThemeUtil.isDarkTheme(requireContext())) {
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Signal_BottomSheetDialog_Fixed_ReactWithAny);
} else {
@@ -82,24 +82,14 @@ public final class ReactionsBottomSheetDialogFragment extends BottomSheetDialogF
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
recipientPagerView = view.findViewById(R.id.reactions_bottom_view_recipient_pager);
messageId = requireArguments().getLong(ARGS_MESSAGE_ID);
disposables.bindTo(getViewLifecycleOwner());
setUpRecipientsRecyclerView();
setUpTabMediator(savedInstanceState);
reactionsLoader = new ReactionsLoader(requireContext(),
requireArguments().getLong(ARGS_MESSAGE_ID),
requireArguments().getBoolean(ARGS_IS_MMS));
LoaderManager.getInstance(requireActivity()).initLoader((int) messageId, null, reactionsLoader);
setUpViewModel();
}
@Override
public void onDestroyView() {
LoaderManager.getInstance(requireActivity()).destroyLoader((int) messageId);
super.onDestroyView();
MessageId messageId = new MessageId(requireArguments().getLong(ARGS_MESSAGE_ID), requireArguments().getBoolean(ARGS_IS_MMS));
setUpViewModel(messageId);
}
@Override
@@ -167,16 +157,16 @@ public final class ReactionsBottomSheetDialogFragment extends BottomSheetDialogF
recipientPagerView.setAdapter(recipientsAdapter);
}
private void setUpViewModel() {
ReactionsViewModel.Factory factory = new ReactionsViewModel.Factory(reactionsLoader);
private void setUpViewModel(@NonNull MessageId messageId) {
ReactionsViewModel.Factory factory = new ReactionsViewModel.Factory(messageId);
viewModel = ViewModelProviders.of(this, factory).get(ReactionsViewModel.class);
viewModel = new ViewModelProvider(this, factory).get(ReactionsViewModel.class);
viewModel.getEmojiCounts().observe(getViewLifecycleOwner(), emojiCounts -> {
disposables.add(viewModel.getEmojiCounts().subscribe(emojiCounts -> {
if (emojiCounts.size() <= 1) dismiss();
recipientsAdapter.submitList(emojiCounts);
});
}));
}
public interface Callback {

View File

@@ -1,110 +0,0 @@
package org.thoughtcrime.securesms.reactions;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import com.annimon.stream.Stream;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.AbstractCursorLoader;
import java.util.Collections;
import java.util.List;
public class ReactionsLoader implements ReactionsViewModel.Repository, LoaderManager.LoaderCallbacks<Cursor> {
private final long messageId;
private final boolean isMms;
private final Context appContext;
private MutableLiveData<List<ReactionDetails>> internalLiveData = new MutableLiveData<>();
public ReactionsLoader(@NonNull Context context, long messageId, boolean isMms)
{
this.messageId = messageId;
this.isMms = isMms;
this.appContext = context.getApplicationContext();
}
@Override
public @NonNull Loader<Cursor> onCreateLoader(int id, @Nullable Bundle args) {
return isMms ? new MmsMessageRecordCursorLoader(appContext, messageId)
: new SmsMessageRecordCursorLoader(appContext, messageId);
}
@Override
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor data) {
SignalExecutors.BOUNDED.execute(() -> {
data.moveToPosition(-1);
MessageRecord record = isMms ? MmsDatabase.readerFor(data).getNext()
: SmsDatabase.readerFor(data).getNext();
if (record == null) {
internalLiveData.postValue(Collections.emptyList());
} else {
internalLiveData.postValue(Stream.of(record.getReactions())
.map(reactionRecord -> new ReactionDetails(Recipient.resolved(reactionRecord.getAuthor()),
EmojiUtil.getCanonicalRepresentation(reactionRecord.getEmoji()),
reactionRecord.getEmoji(),
reactionRecord.getDateReceived()))
.toList());
}
});
}
@Override
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
// Do nothing?
}
@Override
public LiveData<List<ReactionDetails>> getReactions() {
return internalLiveData;
}
private static final class MmsMessageRecordCursorLoader extends AbstractCursorLoader {
private final long messageId;
public MmsMessageRecordCursorLoader(@NonNull Context context, long messageId) {
super(context);
this.messageId = messageId;
}
@Override
public Cursor getCursor() {
return DatabaseFactory.getMmsDatabase(context).getMessageCursor(messageId);
}
}
private static final class SmsMessageRecordCursorLoader extends AbstractCursorLoader {
private final long messageId;
public SmsMessageRecordCursorLoader(@NonNull Context context, long messageId) {
super(context);
this.messageId = messageId;
}
@Override
public Cursor getCursor() {
return DatabaseFactory.getSmsDatabase(context).getMessageCursor(messageId);
}
}
}

View File

@@ -0,0 +1,48 @@
package org.thoughtcrime.securesms.reactions
import android.content.Context
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.ObservableEmitter
import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.components.emoji.EmojiUtil
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.DatabaseObserver
import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.ReactionRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.recipients.Recipient
class ReactionsRepository {
fun getReactions(messageId: MessageId): Observable<List<ReactionDetails>> {
return Observable.create { emitter: ObservableEmitter<List<ReactionDetails>> ->
val databaseObserver: DatabaseObserver = ApplicationDependencies.getDatabaseObserver()
val messageObserver = DatabaseObserver.MessageObserver { messageId ->
emitter.onNext(fetchReactionDetails(messageId))
}
databaseObserver.registerMessageUpdateObserver(messageObserver)
emitter.setCancellable {
databaseObserver.unregisterObserver(messageObserver)
}
emitter.onNext(fetchReactionDetails(messageId))
}.subscribeOn(Schedulers.io())
}
private fun fetchReactionDetails(messageId: MessageId): List<ReactionDetails> {
val context: Context = ApplicationDependencies.getApplication()
val reactions: List<ReactionRecord> = DatabaseFactory.getReactionDatabase(context).getReactions(messageId)
return reactions.map { reaction ->
ReactionDetails(
sender = Recipient.resolved(reaction.author),
baseEmoji = EmojiUtil.getCanonicalRepresentation(reaction.emoji),
displayEmoji = reaction.emoji,
timestamp = reaction.dateReceived
)
}
}
}

View File

@@ -8,32 +8,45 @@ import androidx.lifecycle.ViewModelProvider;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.database.DatabaseObserver;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.subjects.PublishSubject;
public class ReactionsViewModel extends ViewModel {
private final Repository repository;
private final MessageId messageId;
private final ReactionsRepository repository;
public ReactionsViewModel(@NonNull Repository repository) {
this.repository = repository;
public ReactionsViewModel(@NonNull MessageId messageId) {
this.messageId = messageId;
this.repository = new ReactionsRepository();
}
public @NonNull LiveData<List<EmojiCount>> getEmojiCounts() {
return Transformations.map(repository.getReactions(),
reactionList -> {
List<EmojiCount> emojiCounts = Stream.of(reactionList)
.groupBy(ReactionDetails::getBaseEmoji)
.sorted(this::compareReactions)
.map(entry -> new EmojiCount(entry.getKey(),
getCountDisplayEmoji(entry.getValue()),
entry.getValue()))
.toList();
public @NonNull Observable<List<EmojiCount>> getEmojiCounts() {
return repository.getReactions(messageId)
.map(reactionList -> {
List<EmojiCount> emojiCounts = Stream.of(reactionList)
.groupBy(ReactionDetails::getBaseEmoji)
.sorted(this::compareReactions)
.map(entry -> new EmojiCount(entry.getKey(),
getCountDisplayEmoji(entry.getValue()),
entry.getValue()))
.toList();
emojiCounts.add(0, EmojiCount.all(reactionList));
emojiCounts.add(0, EmojiCount.all(reactionList));
return emojiCounts;
});
return emojiCounts;
})
.observeOn(AndroidSchedulers.mainThread());
}
private int compareReactions(@NonNull Map.Entry<String, List<ReactionDetails>> lhs, @NonNull Map.Entry<String, List<ReactionDetails>> rhs) {
@@ -48,7 +61,7 @@ public class ReactionsViewModel extends ViewModel {
private long getLatestTimestamp(List<ReactionDetails> reactions) {
return Stream.of(reactions)
.max((a, b) -> Long.compare(a.getTimestamp(), b.getTimestamp()))
.max(Comparator.comparingLong(ReactionDetails::getTimestamp))
.map(ReactionDetails::getTimestamp)
.orElse(-1L);
}
@@ -63,21 +76,17 @@ public class ReactionsViewModel extends ViewModel {
return reactions.get(reactions.size() - 1).getDisplayEmoji();
}
interface Repository {
LiveData<List<ReactionDetails>> getReactions();
}
static final class Factory implements ViewModelProvider.Factory {
private final Repository repository;
private final MessageId messageId;
Factory(@NonNull Repository repository) {
this.repository = repository;
Factory(@NonNull MessageId messageId) {
this.messageId = messageId;
}
@Override
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new ReactionsViewModel(repository);
return modelClass.cast(new ReactionsViewModel(messageId));
}
}
}

View File

@@ -39,8 +39,8 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageCategoriesAdapter;
import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageCategoryMappingModel;
import org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView;
import org.thoughtcrime.securesms.reactions.ReactionsLoader;
import org.thoughtcrime.securesms.reactions.edit.EditReactionsActivity;
import org.thoughtcrime.securesms.util.LifecycleDisposable;
import org.thoughtcrime.securesms.util.MappingModel;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -65,11 +65,12 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
private ReactWithAnyEmojiViewModel viewModel;
private Callback callback;
private ReactionsLoader reactionsLoader;
private EmojiPageView emojiPageView;
private KeyboardPageSearchView search;
private View tabBar;
private final LifecycleDisposable disposables = new LifecycleDisposable();
private final UpdateCategorySelectionOnScroll categoryUpdateOnScroll = new UpdateCategorySelectionOnScroll();
public static DialogFragment createForMessageRecord(@NonNull MessageRecord messageRecord, int startingPage) {
@@ -174,11 +175,7 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
reactionsLoader = new ReactionsLoader(requireContext(),
requireArguments().getLong(ARG_MESSAGE_ID),
requireArguments().getBoolean(ARG_IS_MMS));
LoaderManager.getInstance(requireActivity()).initLoader((int) requireArguments().getLong(ARG_MESSAGE_ID), null, reactionsLoader);
disposables.bindTo(getViewLifecycleOwner());
emojiPageView = view.findViewById(R.id.react_with_any_emoji_page_view);
emojiPageView.initialize(this, this, true);
@@ -219,15 +216,15 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
emojiPageView.addOnScrollListener(new TopAndBottomShadowHelper(requireView().findViewById(R.id.react_with_any_emoji_top_shadow),
tabBar.findViewById(R.id.react_with_any_emoji_bottom_shadow)));
viewModel.getEmojiList().observe(getViewLifecycleOwner(), pages -> emojiPageView.setList(pages, null));
viewModel.getCategories().observe(getViewLifecycleOwner(), categoriesAdapter::submitList);
viewModel.getSelectedKey().observe(getViewLifecycleOwner(), key -> categoriesRecycler.post(() -> {
disposables.add(viewModel.getEmojiList().subscribe(pages -> emojiPageView.setList(pages, null)));
disposables.add(viewModel.getCategories().subscribe(categoriesAdapter::submitList));
disposables.add(viewModel.getSelectedKey().subscribe(key -> categoriesRecycler.post(() -> {
int index = categoriesAdapter.indexOfFirst(EmojiKeyboardPageCategoryMappingModel.class, m -> m.getKey().equals(key));
if (index != -1) {
categoriesRecycler.smoothScrollToPosition(index);
}
}));
})));
}
}
@@ -259,7 +256,7 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomShee
private void initializeViewModel() {
Bundle args = requireArguments();
ReactWithAnyEmojiRepository repository = new ReactWithAnyEmojiRepository(requireContext(), args.getString(ARG_RECENT_KEY, REACTION_STORAGE_KEY));
ReactWithAnyEmojiViewModel.Factory factory = new ReactWithAnyEmojiViewModel.Factory(reactionsLoader, repository, args.getLong(ARG_MESSAGE_ID), args.getBoolean(ARG_IS_MMS));
ReactWithAnyEmojiViewModel.Factory factory = new ReactWithAnyEmojiViewModel.Factory(repository, args.getLong(ARG_MESSAGE_ID), args.getBoolean(ARG_IS_MMS));
viewModel = ViewModelProviders.of(this, factory).get(ReactWithAnyEmojiViewModel.class);
}

View File

@@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessageDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.ReactionRecord;
import org.thoughtcrime.securesms.emoji.EmojiCategory;
@@ -68,24 +69,18 @@ final class ReactWithAnyEmojiRepository {
return pages;
}
void addEmojiToMessage(@NonNull String emoji, long messageId, boolean isMms) {
void addEmojiToMessage(@NonNull String emoji, @NonNull MessageId messageId) {
SignalExecutors.BOUNDED.execute(() -> {
try {
MessageDatabase db = isMms ? DatabaseFactory.getMmsDatabase(context) : DatabaseFactory.getSmsDatabase(context);
MessageRecord messageRecord = db.getMessageRecord(messageId);
ReactionRecord oldRecord = Stream.of(messageRecord.getReactions())
.filter(record -> record.getAuthor().equals(Recipient.self().getId()))
.findFirst()
.orElse(null);
ReactionRecord oldRecord = Stream.of(DatabaseFactory.getReactionDatabase(context).getReactions(messageId))
.filter(record -> record.getAuthor().equals(Recipient.self().getId()))
.findFirst()
.orElse(null);
if (oldRecord != null && oldRecord.getEmoji().equals(emoji)) {
MessageSender.sendReactionRemoval(context, messageRecord.getId(), messageRecord.isMms(), oldRecord);
} else {
MessageSender.sendNewReaction(context, messageRecord.getId(), messageRecord.isMms(), emoji);
ThreadUtil.runOnMain(() -> recentEmojiPageModel.onCodePointSelected(emoji));
}
} catch (NoSuchMessageException e) {
Log.w(TAG, "Message not found! Ignoring.");
if (oldRecord != null && oldRecord.getEmoji().equals(emoji)) {
MessageSender.sendReactionRemoval(context, messageId, oldRecord);
} else {
MessageSender.sendNewReaction(context, messageId, emoji);
ThreadUtil.runOnMain(() -> recentEmojiPageModel.onCodePointSelected(emoji));
}
});
}

View File

@@ -4,28 +4,29 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel;
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter;
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.emoji.EmojiCategory;
import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageCategoryMappingModel;
import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchRepository;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.reactions.ReactionsLoader;
import org.thoughtcrime.securesms.reactions.ReactionsRepository;
import org.thoughtcrime.securesms.util.MappingModelList;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import java.util.List;
import java.util.stream.Collectors;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.subjects.BehaviorSubject;
public final class ReactWithAnyEmojiViewModel extends ViewModel {
private static final int SEARCH_LIMIT = 40;
@@ -35,13 +36,12 @@ public final class ReactWithAnyEmojiViewModel extends ViewModel {
private final boolean isMms;
private final EmojiSearchRepository emojiSearchRepository;
private final LiveData<MappingModelList> categories;
private final LiveData<MappingModelList> emojiList;
private final MutableLiveData<EmojiSearchResult> searchResults;
private final MutableLiveData<String> selectedKey;
private final Observable<MappingModelList> categories;
private final Observable<MappingModelList> emojiList;
private final BehaviorSubject<EmojiSearchResult> searchResults;
private final BehaviorSubject<String> selectedKey;
private ReactWithAnyEmojiViewModel(@NonNull ReactionsLoader reactionsLoader,
@NonNull ReactWithAnyEmojiRepository repository,
private ReactWithAnyEmojiViewModel(@NonNull ReactWithAnyEmojiRepository repository,
long messageId,
boolean isMms,
@NonNull EmojiSearchRepository emojiSearchRepository)
@@ -50,12 +50,13 @@ public final class ReactWithAnyEmojiViewModel extends ViewModel {
this.messageId = messageId;
this.isMms = isMms;
this.emojiSearchRepository = emojiSearchRepository;
this.searchResults = new MutableLiveData<>(new EmojiSearchResult());
this.selectedKey = new MutableLiveData<>(getStartingKey());
this.searchResults = BehaviorSubject.createDefault(new EmojiSearchResult());
this.selectedKey = BehaviorSubject.createDefault(getStartingKey());
LiveData<List<ReactWithAnyEmojiPage>> emojiPages = Transformations.map(reactionsLoader.getReactions(), repository::getEmojiPageModels);
Observable<List<ReactWithAnyEmojiPage>> emojiPages = new ReactionsRepository().getReactions(new MessageId(messageId, isMms))
.map(repository::getEmojiPageModels);
LiveData<MappingModelList> emojiList = Transformations.map(emojiPages, (pages) -> {
Observable<MappingModelList> emojiList = emojiPages.map(pages -> {
MappingModelList list = new MappingModelList();
for (ReactWithAnyEmojiPage page : pages) {
@@ -69,7 +70,7 @@ public final class ReactWithAnyEmojiViewModel extends ViewModel {
return list;
});
this.categories = LiveDataUtil.combineLatest(emojiPages, this.selectedKey, (pages, selectedKey) -> {
this.categories = Observable.combineLatest(emojiPages, this.selectedKey.distinctUntilChanged(), (pages, selectedKey) -> {
MappingModelList list = new MappingModelList();
list.add(new EmojiKeyboardPageCategoryMappingModel.RecentsMappingModel(RecentEmojiPageModel.KEY.equals(selectedKey)));
list.addAll(pages.stream()
@@ -82,7 +83,7 @@ public final class ReactWithAnyEmojiViewModel extends ViewModel {
return list;
});
this.emojiList = LiveDataUtil.combineLatest(emojiList, searchResults, (all, search) -> {
this.emojiList = Observable.combineLatest(emojiList, searchResults.distinctUntilChanged(), (all, search) -> {
if (TextUtils.isEmpty(search.query)) {
return all;
} else {
@@ -94,35 +95,31 @@ public final class ReactWithAnyEmojiViewModel extends ViewModel {
});
}
LiveData<MappingModelList> getCategories() {
return categories;
@NonNull Observable<MappingModelList> getCategories() {
return categories.observeOn(AndroidSchedulers.mainThread());
}
LiveData<String> getSelectedKey() {
return selectedKey;
@NonNull Observable<String> getSelectedKey() {
return selectedKey.observeOn(AndroidSchedulers.mainThread());
}
@NonNull Observable<MappingModelList> getEmojiList() {
return emojiList.observeOn(AndroidSchedulers.mainThread());
}
void onEmojiSelected(@NonNull String emoji) {
if (messageId > 0) {
SignalStore.emojiValues().setPreferredVariation(emoji);
repository.addEmojiToMessage(emoji, messageId, isMms);
repository.addEmojiToMessage(emoji, new MessageId(messageId, isMms));
}
}
LiveData<MappingModelList> getEmojiList() {
return emojiList;
}
public void onQueryChanged(String query) {
emojiSearchRepository.submitQuery(query, false, SEARCH_LIMIT, m -> searchResults.postValue(new EmojiSearchResult(query, m)));
emojiSearchRepository.submitQuery(query, false, SEARCH_LIMIT, m -> searchResults.onNext(new EmojiSearchResult(query, m)));
}
public void selectPage(@NonNull String key) {
if (key.equals(selectedKey.getValue())) {
return;
}
selectedKey.setValue(key);
selectedKey.onNext(key);
}
private static @NonNull MappingModelList toMappingModels(@NonNull EmojiPageModel model) {
@@ -156,22 +153,20 @@ public final class ReactWithAnyEmojiViewModel extends ViewModel {
static class Factory implements ViewModelProvider.Factory {
private final ReactionsLoader reactionsLoader;
private final ReactWithAnyEmojiRepository repository;
private final long messageId;
private final boolean isMms;
Factory(@NonNull ReactionsLoader reactionsLoader, @NonNull ReactWithAnyEmojiRepository repository, long messageId, boolean isMms) {
this.reactionsLoader = reactionsLoader;
this.repository = repository;
this.messageId = messageId;
this.isMms = isMms;
Factory(@NonNull ReactWithAnyEmojiRepository repository, long messageId, boolean isMms) {
this.repository = repository;
this.messageId = messageId;
this.isMms = isMms;
}
@Override
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection ConstantConditions
return modelClass.cast(new ReactWithAnyEmojiViewModel(reactionsLoader, repository, messageId, isMms, new EmojiSearchRepository(ApplicationDependencies.getApplication())));
return modelClass.cast(new ReactWithAnyEmojiViewModel(repository, messageId, isMms, new EmojiSearchRepository(ApplicationDependencies.getApplication())));
}
}