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; ...@@ -13,6 +13,7 @@ import android.support.v13.view.inputmethod.InputConnectionCompat;
import android.support.v13.view.inputmethod.InputContentInfoCompat; import android.support.v13.view.inputmethod.InputContentInfoCompat;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v4.os.BuildCompat; import android.support.v4.os.BuildCompat;
import android.support.v4.util.Pair;
import android.support.v4.view.GravityCompat; import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.SlidingPaneLayout; import android.support.v4.widget.SlidingPaneLayout;
...@@ -22,8 +23,10 @@ import android.support.v7.widget.RecyclerView; ...@@ -22,8 +23,10 @@ import android.support.v7.widget.RecyclerView;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.fernandocejas.arrow.optional.Optional;
import com.jakewharton.rxbinding2.support.v4.widget.RxDrawerLayout; import com.jakewharton.rxbinding2.support.v4.widget.RxDrawerLayout;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
...@@ -57,10 +60,12 @@ import chat.rocket.android.layouthelper.extra_action.upload.VideoUploadActionIte ...@@ -57,10 +60,12 @@ import chat.rocket.android.layouthelper.extra_action.upload.VideoUploadActionIte
import chat.rocket.android.log.RCLog; import chat.rocket.android.log.RCLog;
import chat.rocket.android.renderer.RocketChatUserStatusProvider; import chat.rocket.android.renderer.RocketChatUserStatusProvider;
import chat.rocket.android.service.temp.DeafultTempSpotlightRoomCaller; 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.AutocompleteManager;
import chat.rocket.android.widget.message.autocomplete.channel.ChannelSource; import chat.rocket.android.widget.message.autocomplete.channel.ChannelSource;
import chat.rocket.android.widget.message.autocomplete.user.UserSource; import chat.rocket.android.widget.message.autocomplete.user.UserSource;
import chat.rocket.core.interactors.AutocompleteChannelInteractor; import chat.rocket.core.interactors.AutocompleteChannelInteractor;
import chat.rocket.core.interactors.AutocompleteUserInteractor;
import chat.rocket.core.interactors.MessageInteractor; import chat.rocket.core.interactors.MessageInteractor;
import chat.rocket.core.interactors.RoomInteractor; import chat.rocket.core.interactors.RoomInteractor;
import chat.rocket.core.interactors.SessionInteractor; import chat.rocket.core.interactors.SessionInteractor;
...@@ -73,6 +78,7 @@ import chat.rocket.persistence.realm.repositories.RealmRoomRepository; ...@@ -73,6 +78,7 @@ import chat.rocket.persistence.realm.repositories.RealmRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmServerInfoRepository; import chat.rocket.persistence.realm.repositories.RealmServerInfoRepository;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository; import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
import chat.rocket.persistence.realm.repositories.RealmSpotlightRoomRepository; import chat.rocket.persistence.realm.repositories.RealmSpotlightRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmSpotlightUserRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository; import chat.rocket.persistence.realm.repositories.RealmUserRepository;
import chat.rocket.android.layouthelper.chatroom.ModelListAdapter; import chat.rocket.android.layouthelper.chatroom.ModelListAdapter;
import chat.rocket.persistence.realm.RealmStore; import chat.rocket.persistence.realm.RealmStore;
...@@ -111,6 +117,8 @@ public class RoomFragment extends AbstractChatRoomFragment ...@@ -111,6 +117,8 @@ public class RoomFragment extends AbstractChatRoomFragment
protected RoomContract.Presenter presenter; protected RoomContract.Presenter presenter;
private RealmRoomRepository roomRepository;
public RoomFragment() { public RoomFragment() {
} }
...@@ -136,7 +144,7 @@ public class RoomFragment extends AbstractChatRoomFragment ...@@ -136,7 +144,7 @@ public class RoomFragment extends AbstractChatRoomFragment
hostname = args.getString(HOSTNAME); hostname = args.getString(HOSTNAME);
roomId = args.getString(ROOM_ID); roomId = args.getString(ROOM_ID);
RealmRoomRepository roomRepository = new RealmRoomRepository(hostname); roomRepository = new RealmRoomRepository(hostname);
MessageInteractor messageInteractor = new MessageInteractor( MessageInteractor messageInteractor = new MessageInteractor(
new RealmMessageRepository(hostname), new RealmMessageRepository(hostname),
...@@ -322,12 +330,14 @@ public class RoomFragment extends AbstractChatRoomFragment ...@@ -322,12 +330,14 @@ public class RoomFragment extends AbstractChatRoomFragment
autocompleteManager = autocompleteManager =
new AutocompleteManager((ViewGroup) rootView.findViewById(R.id.message_list_root)); new AutocompleteManager((ViewGroup) rootView.findViewById(R.id.message_list_root));
final MethodCallHelper methodCallHelper = new MethodCallHelper(getContext(), hostname);
autocompleteManager.registerSource( autocompleteManager.registerSource(
new ChannelSource( new ChannelSource(
new AutocompleteChannelInteractor( new AutocompleteChannelInteractor(
new RealmRoomRepository(hostname), new RealmRoomRepository(hostname),
new RealmSpotlightRoomRepository(hostname), new RealmSpotlightRoomRepository(hostname),
new DeafultTempSpotlightRoomCaller(new MethodCallHelper(getContext(), hostname)) new DeafultTempSpotlightRoomCaller(methodCallHelper)
), ),
AndroidSchedulers.from(BackgroundLooper.get()), AndroidSchedulers.from(BackgroundLooper.get()),
AndroidSchedulers.mainThread() AndroidSchedulers.mainThread()
...@@ -343,14 +353,24 @@ public class RoomFragment extends AbstractChatRoomFragment ...@@ -343,14 +353,24 @@ public class RoomFragment extends AbstractChatRoomFragment
new SessionInteractor(new RealmSessionRepository(hostname)) new SessionInteractor(new RealmSessionRepository(hostname))
); );
Disposable disposable = absoluteUrlHelper.getRocketChatAbsoluteUrl() Disposable disposable = Single.zip(
absoluteUrlHelper.getRocketChatAbsoluteUrl(),
roomRepository.getById(roomId).first(Optional.absent()),
Pair::create
)
.subscribe( .subscribe(
optional -> { pair -> {
if (optional.isPresent()) { if (pair.first.isPresent() && pair.second.isPresent()) {
autocompleteManager.registerSource( autocompleteManager.registerSource(
new UserSource( new UserSource(
new UserInteractor(userRepository), new AutocompleteUserInteractor(
optional.get(), pair.second.get(),
userRepository,
new RealmMessageRepository(hostname),
new RealmSpotlightUserRepository(hostname),
new DefaultTempSpotlightUserCaller(methodCallHelper)
),
pair.first.get(),
RocketChatUserStatusProvider.getInstance(), RocketChatUserStatusProvider.getInstance(),
AndroidSchedulers.from(BackgroundLooper.get()), AndroidSchedulers.from(BackgroundLooper.get()),
AndroidSchedulers.mainThread() 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; ...@@ -6,15 +6,15 @@ import android.support.annotation.NonNull;
import chat.rocket.android.widget.AbsoluteUrl; import chat.rocket.android.widget.AbsoluteUrl;
import chat.rocket.android.widget.helper.UserStatusProvider; import chat.rocket.android.widget.helper.UserStatusProvider;
import chat.rocket.android.widget.message.autocomplete.AutocompleteItem; 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 { public class UserItem implements AutocompleteItem {
private final User user; private final SpotlightUser user;
private final AbsoluteUrl absoluteUrl; private final AbsoluteUrl absoluteUrl;
private final UserStatusProvider userStatusProvider; private final UserStatusProvider userStatusProvider;
public UserItem(@NonNull User user, AbsoluteUrl absoluteUrl, public UserItem(@NonNull SpotlightUser user, AbsoluteUrl absoluteUrl,
UserStatusProvider userStatusProvider) { UserStatusProvider userStatusProvider) {
this.user = user; this.user = user;
this.absoluteUrl = absoluteUrl; this.absoluteUrl = absoluteUrl;
......
...@@ -14,21 +14,21 @@ import java.util.List; ...@@ -14,21 +14,21 @@ import java.util.List;
import chat.rocket.android.widget.AbsoluteUrl; import chat.rocket.android.widget.AbsoluteUrl;
import chat.rocket.android.widget.helper.UserStatusProvider; import chat.rocket.android.widget.helper.UserStatusProvider;
import chat.rocket.android.widget.message.autocomplete.AutocompleteSource; import chat.rocket.android.widget.message.autocomplete.AutocompleteSource;
import chat.rocket.core.interactors.UserInteractor; import chat.rocket.core.interactors.AutocompleteUserInteractor;
import chat.rocket.core.models.User; import chat.rocket.core.models.SpotlightUser;
public class UserSource extends AutocompleteSource<UserAdapter, UserItem> { public class UserSource extends AutocompleteSource<UserAdapter, UserItem> {
private final UserInteractor userInteractor; private final AutocompleteUserInteractor autocompleteUserInteractor;
private final AbsoluteUrl absoluteUrl; private final AbsoluteUrl absoluteUrl;
private final UserStatusProvider userStatusProvider; private final UserStatusProvider userStatusProvider;
private final Scheduler bgScheduler; private final Scheduler bgScheduler;
private final Scheduler fgScheduler; private final Scheduler fgScheduler;
public UserSource(UserInteractor userInteractor, AbsoluteUrl absoluteUrl, public UserSource(AutocompleteUserInteractor autocompleteUserInteractor,
UserStatusProvider userStatusProvider, AbsoluteUrl absoluteUrl, UserStatusProvider userStatusProvider,
Scheduler bgScheduler, Scheduler fgScheduler) { Scheduler bgScheduler, Scheduler fgScheduler) {
this.userInteractor = userInteractor; this.autocompleteUserInteractor = autocompleteUserInteractor;
this.absoluteUrl = absoluteUrl; this.absoluteUrl = absoluteUrl;
this.userStatusProvider = userStatusProvider; this.userStatusProvider = userStatusProvider;
this.bgScheduler = bgScheduler; this.bgScheduler = bgScheduler;
...@@ -51,17 +51,17 @@ public class UserSource extends AutocompleteSource<UserAdapter, UserItem> { ...@@ -51,17 +51,17 @@ public class UserSource extends AutocompleteSource<UserAdapter, UserItem> {
return s.substring(1); return s.substring(1);
} }
}) })
.flatMap(new Function<String, Publisher<List<User>>>() { .flatMap(new Function<String, Publisher<List<SpotlightUser>>>() {
@Override @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 { throws Exception {
return userInteractor.getUserAutocompleteSuggestions(s); return autocompleteUserInteractor.getSuggestionsFor(s);
} }
}) })
.distinctUntilChanged() .distinctUntilChanged()
.map(new Function<List<User>, List<UserItem>>() { .map(new Function<List<SpotlightUser>, List<UserItem>>() {
@Override @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 { throws Exception {
return toUserItemList(users); return toUserItemList(users);
} }
...@@ -102,7 +102,7 @@ public class UserSource extends AutocompleteSource<UserAdapter, UserItem> { ...@@ -102,7 +102,7 @@ public class UserSource extends AutocompleteSource<UserAdapter, UserItem> {
return getTrigger() + autocompleteItem.getSuggestion(); return getTrigger() + autocompleteItem.getSuggestion();
} }
private List<UserItem> toUserItemList(List<User> users) { private List<UserItem> toUserItemList(List<SpotlightUser> users) {
int size = users.size(); int size = users.size();
List<UserItem> userItems = new ArrayList<>(size); List<UserItem> userItems = new ArrayList<>(size);
......
package chat.rocket.core.interactors 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.SpotlightUser
import chat.rocket.core.models.User import chat.rocket.core.models.User
import chat.rocket.core.repositories.MessageRepository
import chat.rocket.core.repositories.SpotlightUserRepository import chat.rocket.core.repositories.SpotlightUserRepository
import chat.rocket.core.repositories.UserRepository import chat.rocket.core.repositories.UserRepository
import chat.rocket.core.temp.TempSpotlightUserCaller import chat.rocket.core.temp.TempSpotlightUserCaller
import chat.rocket.core.utils.Triple import chat.rocket.core.utils.Triple
import io.reactivex.Flowable import io.reactivex.Flowable
import io.reactivex.functions.Function3
import java.util.ArrayList 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 spotlightUserRepository: SpotlightUserRepository,
private val tempSpotlightUserCaller: TempSpotlightUserCaller) { 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>> { 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), 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> { tempSpotlightUserCaller.search(triple.first)
val size = users.size
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) val spotlightUsers = ArrayList<SpotlightUser>(size)
for (i in 0..size - 1) { for (i in 0..size - 1) {
val user = users[i] val user = this[i]
spotlightUsers.add(SpotlightUser.builder() spotlightUsers.add(SpotlightUser.builder()
.setId(user.id) .setId(user.id)
.setUsername(user.username) .setUsername(user.username)
...@@ -34,5 +75,19 @@ class AutocompleteUserInteractor(private val userRepository: UserRepository, ...@@ -34,5 +75,19 @@ class AutocompleteUserInteractor(private val userRepository: UserRepository,
} }
return spotlightUsers 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