Commit 1d1b6004 authored by Tiago Cunha's avatar Tiago Cunha

WIP: autocompleting users

parent 5e6756b0
......@@ -13,6 +13,7 @@ import android.support.v13.view.inputmethod.InputConnectionCompat;
import android.support.v13.view.inputmethod.InputContentInfoCompat;
import android.support.v4.app.DialogFragment;
import android.support.v4.os.BuildCompat;
import android.support.v4.util.Pair;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.SlidingPaneLayout;
......@@ -22,8 +23,10 @@ import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import com.fernandocejas.arrow.optional.Optional;
import com.jakewharton.rxbinding2.support.v4.widget.RxDrawerLayout;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
......@@ -57,10 +60,12 @@ import chat.rocket.android.layouthelper.extra_action.upload.VideoUploadActionIte
import chat.rocket.android.log.RCLog;
import chat.rocket.android.renderer.RocketChatUserStatusProvider;
import chat.rocket.android.service.temp.DeafultTempSpotlightRoomCaller;
import chat.rocket.android.service.temp.DefaultTempSpotlightUserCaller;
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.AutocompleteChannelInteractor;
import chat.rocket.core.interactors.AutocompleteUserInteractor;
import chat.rocket.core.interactors.MessageInteractor;
import chat.rocket.core.interactors.RoomInteractor;
import chat.rocket.core.interactors.SessionInteractor;
......@@ -73,6 +78,7 @@ import chat.rocket.persistence.realm.repositories.RealmRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmServerInfoRepository;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
import chat.rocket.persistence.realm.repositories.RealmSpotlightRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmSpotlightUserRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository;
import chat.rocket.android.layouthelper.chatroom.ModelListAdapter;
import chat.rocket.persistence.realm.RealmStore;
......@@ -111,6 +117,8 @@ public class RoomFragment extends AbstractChatRoomFragment
protected RoomContract.Presenter presenter;
private RealmRoomRepository roomRepository;
public RoomFragment() {
}
......@@ -136,7 +144,7 @@ public class RoomFragment extends AbstractChatRoomFragment
hostname = args.getString(HOSTNAME);
roomId = args.getString(ROOM_ID);
RealmRoomRepository roomRepository = new RealmRoomRepository(hostname);
roomRepository = new RealmRoomRepository(hostname);
MessageInteractor messageInteractor = new MessageInteractor(
new RealmMessageRepository(hostname),
......@@ -322,12 +330,14 @@ public class RoomFragment extends AbstractChatRoomFragment
autocompleteManager =
new AutocompleteManager((ViewGroup) rootView.findViewById(R.id.message_list_root));
final MethodCallHelper methodCallHelper = new MethodCallHelper(getContext(), hostname);
autocompleteManager.registerSource(
new ChannelSource(
new AutocompleteChannelInteractor(
new RealmRoomRepository(hostname),
new RealmSpotlightRoomRepository(hostname),
new DeafultTempSpotlightRoomCaller(new MethodCallHelper(getContext(), hostname))
new DeafultTempSpotlightRoomCaller(methodCallHelper)
),
AndroidSchedulers.from(BackgroundLooper.get()),
AndroidSchedulers.mainThread()
......@@ -343,14 +353,24 @@ public class RoomFragment extends AbstractChatRoomFragment
new SessionInteractor(new RealmSessionRepository(hostname))
);
Disposable disposable = absoluteUrlHelper.getRocketChatAbsoluteUrl()
Disposable disposable = Single.zip(
absoluteUrlHelper.getRocketChatAbsoluteUrl(),
roomRepository.getById(roomId).first(Optional.absent()),
Pair::create
)
.subscribe(
optional -> {
if (optional.isPresent()) {
pair -> {
if (pair.first.isPresent() && pair.second.isPresent()) {
autocompleteManager.registerSource(
new UserSource(
new UserInteractor(userRepository),
optional.get(),
new AutocompleteUserInteractor(
pair.second.get(),
userRepository,
new RealmMessageRepository(hostname),
new RealmSpotlightUserRepository(hostname),
new DefaultTempSpotlightUserCaller(methodCallHelper)
),
pair.first.get(),
RocketChatUserStatusProvider.getInstance(),
AndroidSchedulers.from(BackgroundLooper.get()),
AndroidSchedulers.mainThread()
......
package chat.rocket.android.service.temp;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.core.temp.TempSpotlightUserCaller;
public class DefaultTempSpotlightUserCaller implements TempSpotlightUserCaller {
private final MethodCallHelper methodCallHelper;
public DefaultTempSpotlightUserCaller(MethodCallHelper methodCallHelper) {
this.methodCallHelper = methodCallHelper;
}
@Override
public void search(String term) {
methodCallHelper.searchSpotlightUsers(term);
}
}
package chat.rocket.persistence.realm.repositories;
import android.os.Looper;
import android.support.v4.util.Pair;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.Sort;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.core.SortDirection;
import chat.rocket.core.models.SpotlightUser;
import chat.rocket.core.repositories.SpotlightUserRepository;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.ddp.RealmSpotlightUser;
import hu.akarnokd.rxjava.interop.RxJavaInterop;
public class RealmSpotlightUserRepository extends RealmRepository
implements SpotlightUserRepository {
private final String hostname;
public RealmSpotlightUserRepository(String hostname) {
this.hostname = hostname;
}
@Override
public Flowable<List<SpotlightUser>> getSuggestionsFor(String name, SortDirection direction,
int limit) {
return Flowable.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
pair.first.where(RealmSpotlightUser.class)
.like(RealmSpotlightUser.Columns.USERNAME, "*" + name + "*")
.findAllSorted(RealmSpotlightUser.Columns.USERNAME,
direction.equals(SortDirection.ASC) ? Sort.ASCENDING : Sort.DESCENDING)
.asObservable()),
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
.filter(it -> it != null && it.isLoaded() && it.isValid())
.map(realmSpotlightUsers -> toList(safeSubList(realmSpotlightUsers, 0, limit))));
}
private List<SpotlightUser> toList(List<RealmSpotlightUser> realmSpotlightUsers) {
int total = realmSpotlightUsers.size();
final List<SpotlightUser> spotlightUsers = new ArrayList<>(total);
for (int i = 0; i < total; i++) {
spotlightUsers.add(realmSpotlightUsers.get(i).asSpotlightUser());
}
return spotlightUsers;
}
}
......@@ -6,15 +6,15 @@ import android.support.annotation.NonNull;
import chat.rocket.android.widget.AbsoluteUrl;
import chat.rocket.android.widget.helper.UserStatusProvider;
import chat.rocket.android.widget.message.autocomplete.AutocompleteItem;
import chat.rocket.core.models.User;
import chat.rocket.core.models.SpotlightUser;
public class UserItem implements AutocompleteItem {
private final User user;
private final SpotlightUser user;
private final AbsoluteUrl absoluteUrl;
private final UserStatusProvider userStatusProvider;
public UserItem(@NonNull User user, AbsoluteUrl absoluteUrl,
public UserItem(@NonNull SpotlightUser user, AbsoluteUrl absoluteUrl,
UserStatusProvider userStatusProvider) {
this.user = user;
this.absoluteUrl = absoluteUrl;
......
......@@ -14,21 +14,21 @@ import java.util.List;
import chat.rocket.android.widget.AbsoluteUrl;
import chat.rocket.android.widget.helper.UserStatusProvider;
import chat.rocket.android.widget.message.autocomplete.AutocompleteSource;
import chat.rocket.core.interactors.UserInteractor;
import chat.rocket.core.models.User;
import chat.rocket.core.interactors.AutocompleteUserInteractor;
import chat.rocket.core.models.SpotlightUser;
public class UserSource extends AutocompleteSource<UserAdapter, UserItem> {
private final UserInteractor userInteractor;
private final AutocompleteUserInteractor autocompleteUserInteractor;
private final AbsoluteUrl absoluteUrl;
private final UserStatusProvider userStatusProvider;
private final Scheduler bgScheduler;
private final Scheduler fgScheduler;
public UserSource(UserInteractor userInteractor, AbsoluteUrl absoluteUrl,
UserStatusProvider userStatusProvider,
public UserSource(AutocompleteUserInteractor autocompleteUserInteractor,
AbsoluteUrl absoluteUrl, UserStatusProvider userStatusProvider,
Scheduler bgScheduler, Scheduler fgScheduler) {
this.userInteractor = userInteractor;
this.autocompleteUserInteractor = autocompleteUserInteractor;
this.absoluteUrl = absoluteUrl;
this.userStatusProvider = userStatusProvider;
this.bgScheduler = bgScheduler;
......@@ -51,17 +51,17 @@ public class UserSource extends AutocompleteSource<UserAdapter, UserItem> {
return s.substring(1);
}
})
.flatMap(new Function<String, Publisher<List<User>>>() {
.flatMap(new Function<String, Publisher<List<SpotlightUser>>>() {
@Override
public Publisher<List<User>> apply(@io.reactivex.annotations.NonNull String s)
public Publisher<List<SpotlightUser>> apply(@io.reactivex.annotations.NonNull String s)
throws Exception {
return userInteractor.getUserAutocompleteSuggestions(s);
return autocompleteUserInteractor.getSuggestionsFor(s);
}
})
.distinctUntilChanged()
.map(new Function<List<User>, List<UserItem>>() {
.map(new Function<List<SpotlightUser>, List<UserItem>>() {
@Override
public List<UserItem> apply(@io.reactivex.annotations.NonNull List<User> users)
public List<UserItem> apply(@io.reactivex.annotations.NonNull List<SpotlightUser> users)
throws Exception {
return toUserItemList(users);
}
......@@ -102,7 +102,7 @@ public class UserSource extends AutocompleteSource<UserAdapter, UserItem> {
return getTrigger() + autocompleteItem.getSuggestion();
}
private List<UserItem> toUserItemList(List<User> users) {
private List<UserItem> toUserItemList(List<SpotlightUser> users) {
int size = users.size();
List<UserItem> userItems = new ArrayList<>(size);
......
package chat.rocket.core.interactors
import chat.rocket.core.SortDirection
import chat.rocket.core.models.Message
import chat.rocket.core.models.Room
import chat.rocket.core.models.SpotlightUser
import chat.rocket.core.models.User
import chat.rocket.core.repositories.MessageRepository
import chat.rocket.core.repositories.SpotlightUserRepository
import chat.rocket.core.repositories.UserRepository
import chat.rocket.core.temp.TempSpotlightUserCaller
import chat.rocket.core.utils.Triple
import io.reactivex.Flowable
import io.reactivex.functions.Function3
import java.util.ArrayList
class AutocompleteUserInteractor(private val userRepository: UserRepository,
class AutocompleteUserInteractor(private val room: Room,
private val userRepository: UserRepository,
private val messageRepository: MessageRepository,
private val spotlightUserRepository: SpotlightUserRepository,
private val tempSpotlightUserCaller: TempSpotlightUserCaller) {
private val groupMentions = listOf(
SpotlightUser.builder().setId("all").setUsername("all").setStatus("online").build(),
SpotlightUser.builder().setId("here").setUsername("here").setStatus("online").build()
)
fun getSuggestionsFor(name: String): Flowable<List<SpotlightUser>> {
return Flowable.zip<String, List<SpotlightUser>, List<SpotlightUser>, Triple<String, List<SpotlightUser>, List<SpotlightUser>>>(
return Flowable.zip<String, List<Message>, List<SpotlightUser>, Triple<String, List<Message>, List<SpotlightUser>>>(
Flowable.just(name),
userRepository.getSortedLikeName(name)
messageRepository.getAllFrom(room),
userRepository.getSortedLikeName(name, SortDirection.DESC, 5).map { it.toSpotlightUsers() },
Function3 { a, b, c -> Triple.create(a, b, c) }
)
.flatMap { triple ->
val recentUsers = triple.second.takeUsers(5).toSpotlightUsers()
if (triple.first.isEmpty()) {
return@flatMap Flowable.just(recentUsers + groupMentions)
}
val workedUsers = (recentUsers.filter { it.username.contains(triple.first, true) } + triple.third).distinct().take(5)
if (workedUsers.size == 5) {
return@flatMap Flowable.just(workedUsers + groupMentions.filter { it.username.contains(triple.first, true) })
}
private fun toSpotlightRooms(users: List<User>): List<SpotlightUser> {
val size = users.size
tempSpotlightUserCaller.search(triple.first)
spotlightUserRepository.getSuggestionsFor(triple.first, SortDirection.DESC, 5)
.withLatestFrom<List<SpotlightUser>, List<SpotlightUser>, Triple<List<SpotlightUser>, List<SpotlightUser>, List<SpotlightUser>>>(
Flowable.just(workedUsers),
Flowable.just(groupMentions.filter { it.username.contains(triple.first, true) }),
Function3 { a, b, c -> Triple.create(a, b, c) }
)
.map { triple ->
val spotlightUsers = triple.first + triple.second
return@map spotlightUsers.distinct().take(5) + triple.third
}
}
}
}
fun List<User>.toSpotlightUsers(): List<SpotlightUser> {
val size = this.size
val spotlightUsers = ArrayList<SpotlightUser>(size)
for (i in 0..size - 1) {
val user = users[i]
val user = this[i]
spotlightUsers.add(SpotlightUser.builder()
.setId(user.id)
.setUsername(user.username)
......@@ -34,5 +75,19 @@ class AutocompleteUserInteractor(private val userRepository: UserRepository,
}
return spotlightUsers
}
fun List<Message>.takeUsers(n: Int): List<User> {
val users = ArrayList<User>()
this.forEach {
if (it.user != null && !users.contains(it.user!!)) {
users.add(it.user!!)
if (users.size == n) {
return@forEach
}
}
}
return users
}
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