Commit 608df437 authored by Tiago Cunha's avatar Tiago Cunha

WIP - May change a LOT

parent c68d0a66
package chat.rocket.android;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
......@@ -11,8 +10,8 @@ public class BackgroundLooper {
public static Looper get() {
if (handlerThread == null) {
handlerThread =
new HandlerThread("BackgroundHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
handlerThread = new HandlerThread(
"BackgroundHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
handlerThread.start();
}
......
......@@ -20,12 +20,16 @@ import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import com.jakewharton.rxbinding2.support.v4.widget.RxDrawerLayout;
import io.reactivex.android.schedulers.AndroidSchedulers;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.R;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.fragment.chatroom.dialog.FileUploadProgressDialogFragment;
......@@ -49,8 +53,13 @@ import chat.rocket.android.layouthelper.extra_action.upload.AudioUploadActionIte
import chat.rocket.android.layouthelper.extra_action.upload.ImageUploadActionItem;
import chat.rocket.android.layouthelper.extra_action.upload.VideoUploadActionItem;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.widget.message.autocomplete.AutocompleteManager;
import chat.rocket.android.widget.message.autocomplete.channel.ChannelSource;
import chat.rocket.android.widget.message.autocomplete.user.UserSource;
import chat.rocket.core.interactors.MessageInteractor;
import chat.rocket.core.interactors.RoomInteractor;
import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.core.interactors.UserInteractor;
import chat.rocket.core.models.Message;
import chat.rocket.core.models.Room;
import chat.rocket.persistence.realm.repositories.RealmMessageRepository;
......@@ -87,6 +96,7 @@ public class RoomFragment extends AbstractChatRoomFragment
protected Snackbar unreadIndicator;
private boolean previousUnreadMessageExists;
private MessageListAdapter adapter;
private AutocompleteManager autocompleteManager;
private List<AbstractExtraActionItem> extraActionItems;
......@@ -238,6 +248,12 @@ public class RoomFragment extends AbstractChatRoomFragment
adapter.unregisterAdapterDataObserver(autoScrollManager);
}
}
if (autocompleteManager != null) {
autocompleteManager.dispose();
autocompleteManager = null;
}
super.onDestroyView();
}
......@@ -290,7 +306,31 @@ public class RoomFragment extends AbstractChatRoomFragment
messageFormManager =
new MessageFormManager(messageFormLayout, this::showExtraActionSelectionDialog);
messageFormManager.setSendMessageCallback(this::sendMessage);
messageFormLayout.setEditTextContentListener(this::onCommitContent);
messageFormLayout.setEditTextCommitContentListener(this::onCommitContent);
autocompleteManager =
new AutocompleteManager((ViewGroup) rootView.findViewById(R.id.message_list_root));
autocompleteManager.registerSource(
new ChannelSource(
new RoomInteractor(new RealmRoomRepository(hostname)),
AndroidSchedulers.from(BackgroundLooper.get()),
AndroidSchedulers.mainThread()
)
);
autocompleteManager.registerSource(
new UserSource(
new UserInteractor(new RealmUserRepository(hostname)),
AndroidSchedulers.from(BackgroundLooper.get()),
AndroidSchedulers.mainThread()
)
);
autocompleteManager.bindTo(
messageFormLayout.getEditText(),
messageFormLayout
);
}
@Override
......
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/message_list_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
......
......@@ -8,9 +8,11 @@ import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.Realm;
import io.realm.RealmResults;
import io.realm.Sort;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.core.SortDirection;
import chat.rocket.core.models.Room;
import chat.rocket.core.models.RoomHistoryState;
import chat.rocket.core.repositories.RoomRepository;
......@@ -141,6 +143,24 @@ public class RealmRoomRepository extends RealmRepository implements RoomReposito
});
}
@Override
public Flowable<List<Room>> getSortedLikeName(String name, SortDirection direction, int limit) {
return Flowable.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
pair.first.where(RealmRoom.class)
.like(RealmRoom.NAME, "*" + name + "*")
.findAllSorted(RealmRoom.NAME,
direction.equals(SortDirection.ASC) ? Sort.ASCENDING : Sort.DESCENDING)
.asObservable()),
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
.filter(roomSubscriptions -> roomSubscriptions != null && roomSubscriptions.isLoaded()
&& roomSubscriptions.isValid())
.map(realmRooms -> toList(safeSubList(realmRooms, 0, limit))));
}
private List<Room> toList(RealmResults<RealmRoom> realmRooms) {
int total = realmRooms.size();
......@@ -152,4 +172,22 @@ public class RealmRoomRepository extends RealmRepository implements RoomReposito
return roomList;
}
private List<Room> toList(List<RealmRoom> realmRooms) {
int total = realmRooms.size();
final List<Room> roomList = new ArrayList<>(total);
for (int i = 0; i < total; i++) {
roomList.add(realmRooms.get(i).asRoom());
}
return roomList;
}
private List<RealmRoom> safeSubList(RealmResults<RealmRoom> realmRooms,
int fromIndex,
int toIndex) {
return realmRooms.subList(Math.max(0, fromIndex), Math.min(realmRooms.size(), toIndex));
}
}
......@@ -6,7 +6,11 @@ import com.fernandocejas.arrow.optional.Optional;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.RealmResults;
import io.realm.Sort;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.core.SortDirection;
import chat.rocket.core.models.User;
import chat.rocket.core.repositories.UserRepository;
import chat.rocket.persistence.realm.RealmStore;
......@@ -43,4 +47,52 @@ public class RealmUserRepository extends RealmRepository implements UserReposito
return Optional.<User>absent();
}));
}
@Override
public Flowable<List<User>> getSortedLikeName(String name, SortDirection direction, int limit) {
return Flowable.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
pair.first.where(RealmUser.class)
.like(RealmUser.USERNAME, "*" + name + "*")
.findAllSorted(RealmUser.USERNAME,
direction.equals(SortDirection.ASC) ? Sort.ASCENDING : Sort.DESCENDING)
.asObservable()),
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
.filter(realmUsers -> realmUsers != null && realmUsers.isLoaded()
&& realmUsers.isValid())
.map(realmUsers -> toList(safeSubList(realmUsers, 0, limit))));
}
private List<User> toList(RealmResults<RealmUser> realmUsers) {
int total = realmUsers.size();
final List<User> userList = new ArrayList<>(total);
for (int i = 0; i < total; i++) {
userList.add(realmUsers.get(i).asUser());
}
return userList;
}
private List<User> toList(List<RealmUser> realmUsers) {
int total = realmUsers.size();
final List<User> userList = new ArrayList<>(total);
for (int i = 0; i < total; i++) {
userList.add(realmUsers.get(i).asUser());
}
return userList;
}
private List<RealmUser> safeSubList(RealmResults<RealmUser> realmUsers,
int fromIndex,
int toIndex) {
return realmUsers.subList(Math.max(0, fromIndex), Math.min(realmUsers.size(), toIndex));
}
}
......@@ -32,6 +32,7 @@ android {
ext {
supportVersion = '25.2.0'
frescoVersion = '1.1.0'
rxbindingVersion = '2.0.0'
}
dependencies {
......@@ -39,6 +40,8 @@ dependencies {
compile "com.android.support:support-annotations:$supportVersion"
compile "com.android.support:appcompat-v7:$supportVersion"
compile "com.android.support:recyclerview-v7:$supportVersion"
compile "com.android.support:cardview-v7:$supportVersion"
compile "com.android.support:support-v13:$supportVersion"
compile "com.android.support:design:$supportVersion"
......@@ -58,5 +61,9 @@ dependencies {
compile 'com.caverock:androidsvg:1.2.1'
compile "com.jakewharton.rxbinding2:rxbinding:$rxbindingVersion"
compile "com.jakewharton.rxbinding2:rxbinding-support-v4:$rxbindingVersion"
testCompile 'junit:junit:4.12'
testCompile "org.mockito:mockito-core:2.7.19"
}
package chat.rocket.android.widget.message;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.v13.view.inputmethod.EditorInfoCompat;
import android.support.v13.view.inputmethod.InputConnectionCompat;
import android.support.v13.view.inputmethod.InputContentInfoCompat;
import android.support.v7.widget.AppCompatEditText;
import android.util.AttributeSet;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.EditText;
public class ImageKeyboardEditText extends EditText {
public class ImageKeyboardEditText extends AppCompatEditText {
private final String[] mimeTypes = {"image/gif"};
......@@ -43,12 +41,6 @@ public class ImageKeyboardEditText extends EditText {
super(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ImageKeyboardEditText(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
final InputConnection inputConnection = super.onCreateInputConnection(editorInfo);
......
......@@ -12,6 +12,7 @@ import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
......@@ -115,6 +116,10 @@ public class MessageFormLayout extends LinearLayout {
addView(composer);
}
public EditText getEditText() {
return (EditText) composer.findViewById(R.id.editor);
}
public void setExtraActionSelectionClickListener(
ExtraActionSelectionClickListener extraActionSelectionClickListener) {
this.extraActionSelectionClickListener = extraActionSelectionClickListener;
......@@ -153,7 +158,8 @@ public class MessageFormLayout extends LinearLayout {
composer.findViewById(R.id.btn_submit).setEnabled(enabled);
}
public void setEditTextContentListener(ImageKeyboardEditText.OnCommitContentListener listener) {
public void setEditTextCommitContentListener(
ImageKeyboardEditText.OnCommitContentListener listener) {
this.listener = listener;
}
......
package chat.rocket.android.widget.message.autocomplete;
import android.support.v7.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public abstract class AutocompleteAdapter<I extends AutocompleteItem, H extends AutocompleteViewHolder<I>>
extends RecyclerView.Adapter<H> {
private List<I> autocompleteItems = new ArrayList<>();
protected AutocompleteViewHolder.OnClickListener<I> onClickListener;
public void setAutocompleteItems(List<I> autocompleteItems) {
this.autocompleteItems.clear();
this.autocompleteItems.addAll(autocompleteItems);
notifyDataSetChanged();
}
@Override
public void onBindViewHolder(H holder, int position) {
holder.bind(autocompleteItems.get(position));
}
@Override
public int getItemCount() {
return autocompleteItems.size();
}
public void setOnClickListener(AutocompleteViewHolder.OnClickListener<I> onClickListener) {
this.onClickListener = onClickListener;
}
}
package chat.rocket.android.widget.message.autocomplete;
public interface AutocompleteItem {
}
package chat.rocket.android.widget.message.autocomplete;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.RelativeLayout;
import com.jakewharton.rxbinding2.widget.RxTextView;
import com.jakewharton.rxbinding2.widget.TextViewAfterTextChangeEvent;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.internal.util.AppendOnlyLinkedArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import chat.rocket.android.widget.R;
public class AutocompleteManager {
private final Map<String, AutocompleteSource> autocompleteSourceMap = new HashMap<>();
private AutocompleteSource currentSource;
private Disposable afterTextChangeDisposable;
private CompositeDisposable sourceDisposable = new CompositeDisposable();
private EditText editText;
private String text;
private int fromIndex;
private int toIndex;
private final View contentHolder;
private final RecyclerView recyclerView;
private final AutocompleteSource.OnAutocompleteSelected onAutocompleteSelected =
new AutocompleteSource.OnAutocompleteSelected() {
@Override
public void onSelected(String autocompleteSuggestion) {
replaceSelected(autocompleteSuggestion);
}
};
public AutocompleteManager(ViewGroup parent) {
contentHolder =
LayoutInflater.from(parent.getContext()).inflate(R.layout.autocomplete_box, parent, false);
contentHolder.setVisibility(View.GONE);
recyclerView = (RecyclerView) contentHolder.findViewById(R.id.autocomplete_list);
recyclerView.setLayoutManager(new LinearLayoutManager(parent.getContext()));
parent.addView(contentHolder);
}
public void registerSource(AutocompleteSource autocompleteSource) {
autocompleteSourceMap.put(autocompleteSource.getTrigger(), autocompleteSource);
}
public void bindTo(EditText editText, View anchor) {
this.editText = editText;
if (contentHolder.getLayoutParams() instanceof RelativeLayout.LayoutParams) {
RelativeLayout.LayoutParams layoutParams =
(RelativeLayout.LayoutParams) contentHolder.getLayoutParams();
layoutParams.addRule(RelativeLayout.ABOVE, anchor.getId());
}
contentHolder.setVisibility(View.GONE);
afterTextChangeDisposable = RxTextView.afterTextChangeEvents(editText)
.debounce(300, TimeUnit.MILLISECONDS)
.filter(new AppendOnlyLinkedArrayList.NonThrowingPredicate<TextViewAfterTextChangeEvent>() {
@Override
public boolean test(TextViewAfterTextChangeEvent textViewAfterTextChangeEvent) {
return textViewAfterTextChangeEvent.editable() != null;
}
})
.map(new Function<TextViewAfterTextChangeEvent, String>() {
@Override
public String apply(@NonNull TextViewAfterTextChangeEvent textViewAfterTextChangeEvent)
throws Exception {
//noinspection ConstantConditions
return textViewAfterTextChangeEvent.editable().toString();
}
})
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(new Consumer<String>() {
@Override
public void accept(@NonNull String s) throws Exception {
// let's do stateful things
tryToAutocomplete(s);
}
})
.subscribe();
}
public void dispose() {
if (afterTextChangeDisposable != null) {
afterTextChangeDisposable.dispose();
afterTextChangeDisposable = null;
}
editText = null;
cleanState();
}
private void tryToAutocomplete(String text) {
if (editText == null) {
cleanState();
return;
}
final int selectionStart = editText.getSelectionStart();
final int selectionEnd = editText.getSelectionEnd();
if (selectionStart != selectionEnd) {
// selecting text
cleanState();
return;
}
final String toCompleteText = getToCompleteText(text, selectionStart);
// trigger plus 2 letters
if (toCompleteText.length() < 3) {
cleanState();
return;
}
final AutocompleteSource source = getSource(toCompleteText);
if (source == null) {
cleanState();
return;
}
// render and stuff
if (source != currentSource) {
cleanState();
currentSource = source;
// set adapter on something
recyclerView.setAdapter(currentSource.getAdapter());
currentSource.setOnAutocompleteSelected(onAutocompleteSelected);
}
this.text = text;
contentHolder.setVisibility(View.VISIBLE);
sourceDisposable.clear();
sourceDisposable.add(currentSource.loadList(toCompleteText));
}
private void cleanState() {
contentHolder.setVisibility(View.GONE);
sourceDisposable.clear();
text = null;
if (currentSource != null) {
currentSource.dispose();
currentSource = null;
}
}
private String getToCompleteText(String text, int cursorPosition) {
if (text == null || text.length() == 0 || cursorPosition < 0
|| cursorPosition > text.length()) {
return "";
}
final String[] textParts = text.split(" ");
int currentPos = 0;
for (String textPart : textParts) {
int currentLength = currentPos + textPart.length();
if (cursorPosition >= currentPos && cursorPosition <= currentLength) {
fromIndex = currentPos;
toIndex = cursorPosition;
return textPart.substring(0, cursorPosition - currentPos);
}
currentPos = currentLength + 1;
}
return "";
}
private AutocompleteSource getSource(String toCompleteText) {
if (toCompleteText == null || toCompleteText.length() == 0) {
return null;
}
final String trigger = toCompleteText.substring(0, 1);
return autocompleteSourceMap.get(trigger);
}
private void replaceSelected(String autocompleteSuggestion) {
final String preText = text.substring(0, fromIndex);
final String postText = text.substring(toIndex);
StringBuilder stringBuilder =
new StringBuilder(text.length() + autocompleteSuggestion.length());
stringBuilder.append(preText)
.append(autocompleteSuggestion)
.append(' ');
final int selectionPos = stringBuilder.length();
stringBuilder.append(postText);
editText.setText(stringBuilder.toString());
editText.setSelection(selectionPos);
}
}
package chat.rocket.android.widget.message.autocomplete;
import android.support.annotation.NonNull;
import io.reactivex.disposables.Disposable;
public abstract class AutocompleteSource<A extends AutocompleteAdapter, I extends AutocompleteItem> {
protected A adapter;
private AutocompleteSource.OnAutocompleteSelected autocompleteSelected;
private AutocompleteViewHolder.OnClickListener autocompleteListener =
new AutocompleteViewHolder.OnClickListener<I>() {
@Override
public void onClick(I autocompleteItem) {
if (autocompleteSelected != null) {
autocompleteSelected.onSelected(getAutocompleteSuggestion(autocompleteItem));
}
}
};
@NonNull
public abstract String getTrigger();
@NonNull
public A getAdapter() {
if (adapter == null) {
adapter = createAdapter();
adapter.setOnClickListener(autocompleteListener);
}
return adapter;
}
@NonNull
public abstract Disposable loadList(String text);
public abstract void dispose();
public void setOnAutocompleteSelected(
AutocompleteSource.OnAutocompleteSelected autocompleteSelected) {
this.autocompleteSelected = autocompleteSelected;
}
protected abstract A createAdapter();
protected abstract String getAutocompleteSuggestion(I autocompleteItem);
public interface OnAutocompleteSelected {
void onSelected(String autocompleteSuggestion);
}
}
package chat.rocket.android.widget.message.autocomplete;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public abstract class AutocompleteViewHolder<I extends AutocompleteItem>
extends RecyclerView.ViewHolder {
public AutocompleteViewHolder(View itemView) {
super(itemView);
}
public abstract void bind(I autocompleteItem);
public interface OnClickListener<I extends AutocompleteItem> {
void onClick(I autocompleteItem);
}
}
package chat.rocket.android.widget.message.autocomplete.channel;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import chat.rocket.android.widget.R;
import chat.rocket.android.widget.message.autocomplete.AutocompleteAdapter;
public class ChannelAdapter extends AutocompleteAdapter<ChannelItem, ChannelViewHolder> {
@Override
public ChannelViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.autocomplete_channel_view, parent, false);
return new ChannelViewHolder(view, onClickListener);
}
}
package chat.rocket.android.widget.message.autocomplete.channel;
import android.support.annotation.NonNull;
import chat.rocket.android.widget.message.autocomplete.AutocompleteItem;
import chat.rocket.core.models.Room;
public class ChannelItem implements AutocompleteItem {
private final Room room;
public ChannelItem(@NonNull Room room) {
this.room = room;
}
public String getTitle() {
return room.getName();
}
}
package chat.rocket.android.widget.message.autocomplete.channel;
import android.support.annotation.NonNull;
import io.reactivex.Flowable;
import io.reactivex.Scheduler;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import org.reactivestreams.Publisher;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.android.widget.message.autocomplete.AutocompleteSource;
import chat.rocket.core.interactors.RoomInteractor;
import chat.rocket.core.models.Room;
public class ChannelSource extends AutocompleteSource<ChannelAdapter, ChannelItem> {
private final RoomInteractor roomInteractor;
private final Scheduler bgScheduler;
private final Scheduler fgScheduler;
public ChannelSource(RoomInteractor roomInteractor,
Scheduler bgScheduler,
Scheduler fgScheduler) {
this.roomInteractor = roomInteractor;
this.bgScheduler = bgScheduler;
this.fgScheduler = fgScheduler;
}
@NonNull
@Override
public String getTrigger() {
return "#";
}
@NonNull
@Override
public Disposable loadList(String text) {
return Flowable.just(text)
.map(new Function<String, String>() {
@Override
public String apply(@io.reactivex.annotations.NonNull String s) throws Exception {
return s.substring(1);
}
})
.flatMap(new Function<String, Publisher<List<Room>>>() {
@Override
public Publisher<List<Room>> apply(@io.reactivex.annotations.NonNull String s)
throws Exception {
return roomInteractor.getRoomsWithNameLike(s);
}
})
.map(new Function<List<Room>, List<ChannelItem>>() {
@Override
public List<ChannelItem> apply(@io.reactivex.annotations.NonNull List<Room> rooms)
throws Exception {
return toChannelItemList(rooms);
}
})
.subscribeOn(bgScheduler)
.observeOn(fgScheduler)
.subscribe(
new Consumer<List<ChannelItem>>() {
@Override
public void accept(@io.reactivex.annotations.NonNull List<ChannelItem> channelItems)
throws Exception {
if (adapter != null) {
adapter.setAutocompleteItems(channelItems);
}
}
},
new Consumer<Throwable>() {
@Override
public void accept(@io.reactivex.annotations.NonNull Throwable throwable)
throws Exception {
}
}
);
}
@Override
public void dispose() {
adapter = null;
}
@Override
protected ChannelAdapter createAdapter() {
return new ChannelAdapter();
}
@Override
protected String getAutocompleteSuggestion(ChannelItem autocompleteItem) {
return getTrigger() + autocompleteItem.getTitle();
}
private List<ChannelItem> toChannelItemList(List<Room> rooms) {
int size = rooms.size();
List<ChannelItem> channelItems = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
channelItems.add(new ChannelItem(rooms.get(i)));
}
return channelItems;
}
}
package chat.rocket.android.widget.message.autocomplete.channel;
import android.view.View;
import android.widget.TextView;
import chat.rocket.android.widget.R;
import chat.rocket.android.widget.message.autocomplete.AutocompleteItem;
import chat.rocket.android.widget.message.autocomplete.AutocompleteViewHolder;
public class ChannelViewHolder extends AutocompleteViewHolder<ChannelItem> {
private final TextView titleTextView;
public ChannelViewHolder(View itemView,
final AutocompleteViewHolder.OnClickListener<ChannelItem> onClickListener) {
super(itemView);
titleTextView = (TextView) itemView.findViewById(R.id.title);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onClickListener != null) {
onClickListener.onClick((ChannelItem) v.getTag());
}
}
});
}
@Override
public void bind(ChannelItem channelItem) {
itemView.setTag(channelItem);
if (titleTextView != null) {
titleTextView.setText(channelItem.getTitle());
}
}
}
package chat.rocket.android.widget.message.autocomplete.user;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import chat.rocket.android.widget.R;
import chat.rocket.android.widget.message.autocomplete.AutocompleteAdapter;
public class UserAdapter extends AutocompleteAdapter<UserItem, UserViewHolder> {
@Override
public UserViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.autocomplete_channel_view, parent, false);
return new UserViewHolder(view, onClickListener);
}
}
package chat.rocket.android.widget.message.autocomplete.user;
import android.support.annotation.NonNull;
import chat.rocket.android.widget.message.autocomplete.AutocompleteItem;
import chat.rocket.core.models.User;
public class UserItem implements AutocompleteItem {
private final User user;
public UserItem(@NonNull User user) {
this.user = user;
}
public String getTitle() {
return user.getUsername();
}
}
package chat.rocket.android.widget.message.autocomplete.user;
import android.support.annotation.NonNull;
import io.reactivex.Flowable;
import io.reactivex.Scheduler;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import org.reactivestreams.Publisher;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.android.widget.message.autocomplete.AutocompleteSource;
import chat.rocket.core.interactors.UserInteractor;
import chat.rocket.core.models.User;
public class UserSource extends AutocompleteSource<UserAdapter, UserItem> {
private final UserInteractor userInteractor;
private final Scheduler bgScheduler;
private final Scheduler fgScheduler;
public UserSource(UserInteractor userInteractor, Scheduler bgScheduler, Scheduler fgScheduler) {
this.userInteractor = userInteractor;
this.bgScheduler = bgScheduler;
this.fgScheduler = fgScheduler;
}
@NonNull
@Override
public String getTrigger() {
return "@";
}
@NonNull
@Override
public Disposable loadList(String text) {
return Flowable.just(text)
.map(new Function<String, String>() {
@Override
public String apply(@io.reactivex.annotations.NonNull String s) throws Exception {
return s.substring(1);
}
})
.flatMap(new Function<String, Publisher<List<User>>>() {
@Override
public Publisher<List<User>> apply(@io.reactivex.annotations.NonNull String s)
throws Exception {
return userInteractor.getUserAutocompleteSuggestions(s);
}
})
.map(new Function<List<User>, List<UserItem>>() {
@Override
public List<UserItem> apply(@io.reactivex.annotations.NonNull List<User> users)
throws Exception {
return toUserItemList(users);
}
})
.subscribeOn(bgScheduler)
.observeOn(fgScheduler)
.subscribe(
new Consumer<List<UserItem>>() {
@Override
public void accept(@io.reactivex.annotations.NonNull List<UserItem> userItems)
throws Exception {
if (adapter != null) {
adapter.setAutocompleteItems(userItems);
}
}
},
new Consumer<Throwable>() {
@Override
public void accept(@io.reactivex.annotations.NonNull Throwable throwable)
throws Exception {
}
}
);
}
@Override
public void dispose() {
adapter = null;
}
@Override
protected UserAdapter createAdapter() {
return new UserAdapter();
}
@Override
protected String getAutocompleteSuggestion(UserItem autocompleteItem) {
return getTrigger() + autocompleteItem.getTitle();
}
private List<UserItem> toUserItemList(List<User> users) {
int size = users.size();
List<UserItem> userItems = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
userItems.add(new UserItem(users.get(i)));
}
return userItems;
}
}
package chat.rocket.android.widget.message.autocomplete.user;
import android.view.View;
import android.widget.TextView;
import chat.rocket.android.widget.R;
import chat.rocket.android.widget.message.autocomplete.AutocompleteViewHolder;
public class UserViewHolder extends AutocompleteViewHolder<UserItem> {
private final TextView titleTextView;
public UserViewHolder(View itemView,
final AutocompleteViewHolder.OnClickListener<UserItem> onClickListener) {
super(itemView);
titleTextView = (TextView) itemView.findViewById(R.id.title);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onClickListener != null) {
onClickListener.onClick((UserItem) v.getTag());
}
}
});
}
@Override
public void bind(UserItem userItem) {
itemView.setTag(userItem);
if (titleTextView != null) {
titleTextView.setText(userItem.getTitle());
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/darker_gray" />
</shape>
</item>
<item android:bottom="1dp">
<shape android:shape="rectangle">
<solid android:color="@android:color/white" />
</shape>
</item>
</layer-list>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_marginLeft="8dp"
android:layout_marginBottom="-8dp"
app:cardCornerRadius="4dp"
app:contentPadding="4dp">
<android.support.v7.widget.RecyclerView
android:id="@+id/autocomplete_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.v7.widget.CardView>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="YOLO" />
</LinearLayout>
\ No newline at end of file
package chat.rocket.core;
public enum SortDirection {
ASC,
DESC
}
......@@ -3,6 +3,7 @@ package chat.rocket.core.interactors;
import io.reactivex.Flowable;
import java.util.List;
import chat.rocket.core.SortDirection;
import chat.rocket.core.models.Room;
import chat.rocket.core.repositories.RoomRepository;
......@@ -39,4 +40,8 @@ public class RoomInteractor {
.toList()
.toFlowable());
}
public Flowable<List<Room>> getRoomsWithNameLike(String name) {
return roomRepository.getSortedLikeName(name, SortDirection.DESC, 5);
}
}
package chat.rocket.core.interactors;
import io.reactivex.Flowable;
import java.util.List;
import chat.rocket.core.SortDirection;
import chat.rocket.core.models.User;
import chat.rocket.core.repositories.UserRepository;
public class UserInteractor {
private final UserRepository userRepository;
public UserInteractor(UserRepository userRepository) {
this.userRepository = userRepository;
}
public Flowable<List<User>> getUserAutocompleteSuggestions(String name) {
return userRepository.getSortedLikeName(name, SortDirection.DESC, 5);
}
}
......@@ -5,6 +5,7 @@ import io.reactivex.Flowable;
import io.reactivex.Single;
import java.util.List;
import chat.rocket.core.SortDirection;
import chat.rocket.core.models.Room;
import chat.rocket.core.models.RoomHistoryState;
......@@ -17,4 +18,6 @@ public interface RoomRepository {
Flowable<Optional<RoomHistoryState>> getHistoryStateByRoomId(String roomId);
Single<Boolean> setHistoryState(RoomHistoryState roomHistoryState);
Flowable<List<Room>> getSortedLikeName(String name, SortDirection direction, int limit);
}
......@@ -3,9 +3,13 @@ package chat.rocket.core.repositories;
import com.fernandocejas.arrow.optional.Optional;
import io.reactivex.Flowable;
import java.util.List;
import chat.rocket.core.SortDirection;
import chat.rocket.core.models.User;
public interface UserRepository {
Flowable<Optional<User>> getCurrent();
Flowable<List<User>> getSortedLikeName(String name, SortDirection direction, int limit);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment