add open conversations to search

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2022-01-21 13:53:39 +01:00
parent 8969f4b11f
commit 1de2261426
No known key found for this signature in database
GPG key ID: C793F8B59F43CE7B
6 changed files with 203 additions and 49 deletions

View file

@ -49,19 +49,27 @@ import butterknife.ButterKnife;
import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
import eu.davidea.flexibleadapter.items.IFilterable; import eu.davidea.flexibleadapter.items.IFilterable;
import eu.davidea.flexibleadapter.items.ISectionable;
import eu.davidea.flexibleadapter.utils.FlexibleUtils; import eu.davidea.flexibleadapter.utils.FlexibleUtils;
import eu.davidea.viewholders.FlexibleViewHolder; import eu.davidea.viewholders.FlexibleViewHolder;
public class CallItem extends AbstractFlexibleItem<CallItem.RoomItemViewHolder> implements IFilterable<String> { public class CallItem extends AbstractFlexibleItem<CallItem.RoomItemViewHolder> implements ISectionable<CallItem.RoomItemViewHolder, GenericTextHeaderItem>, IFilterable<String> {
private Conversation conversation; private Conversation conversation;
private UserEntity userEntity; private UserEntity userEntity;
private GenericTextHeaderItem header;
public CallItem(Conversation conversation, UserEntity userEntity) { public CallItem(Conversation conversation, UserEntity userEntity) {
this.conversation = conversation; this.conversation = conversation;
this.userEntity = userEntity; this.userEntity = userEntity;
} }
public CallItem(Conversation conversation, UserEntity userEntity, GenericTextHeaderItem genericTextHeaderItem) {
this.conversation = conversation;
this.userEntity = userEntity;
this.header = genericTextHeaderItem;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (o instanceof CallItem) { if (o instanceof CallItem) {
@ -168,6 +176,16 @@ public class CallItem extends AbstractFlexibleItem<CallItem.RoomItemViewHolder>
Pattern.compile(constraint, Pattern.CASE_INSENSITIVE | Pattern.LITERAL).matcher(conversation.getDisplayName().trim()).find(); Pattern.compile(constraint, Pattern.CASE_INSENSITIVE | Pattern.LITERAL).matcher(conversation.getDisplayName().trim()).find();
} }
@Override
public GenericTextHeaderItem getHeader() {
return header;
}
@Override
public void setHeader(GenericTextHeaderItem header) {
this.header = header;
}
static class RoomItemViewHolder extends FlexibleViewHolder { static class RoomItemViewHolder extends FlexibleViewHolder {
@BindView(R.id.name_text) @BindView(R.id.name_text)

View file

@ -61,24 +61,33 @@ import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
import eu.davidea.flexibleadapter.items.IFilterable; import eu.davidea.flexibleadapter.items.IFilterable;
import eu.davidea.flexibleadapter.items.IFlexible; import eu.davidea.flexibleadapter.items.IFlexible;
import eu.davidea.flexibleadapter.items.ISectionable;
import eu.davidea.flexibleadapter.utils.FlexibleUtils; import eu.davidea.flexibleadapter.utils.FlexibleUtils;
import eu.davidea.viewholders.FlexibleViewHolder; import eu.davidea.viewholders.FlexibleViewHolder;
public class ConversationItem extends AbstractFlexibleItem<ConversationItem.ConversationItemViewHolder> implements public class ConversationItem extends AbstractFlexibleItem<ConversationItem.ConversationItemViewHolder> implements ISectionable<ConversationItem.ConversationItemViewHolder, GenericTextHeaderItem>,
IFilterable<String> { IFilterable<String> {
private Conversation conversation; private Conversation conversation;
private UserEntity userEntity; private UserEntity userEntity;
private Context context; private Context context;
private GenericTextHeaderItem header;
public ConversationItem(Conversation conversation, UserEntity userEntity, public ConversationItem(Conversation conversation, UserEntity userEntity, Context activityContext) {
Context activityContext) {
this.conversation = conversation; this.conversation = conversation;
this.userEntity = userEntity; this.userEntity = userEntity;
this.context = activityContext; this.context = activityContext;
} }
public ConversationItem(Conversation conversation, UserEntity userEntity,
Context activityContext, GenericTextHeaderItem genericTextHeaderItem) {
this.conversation = conversation;
this.userEntity = userEntity;
this.context = activityContext;
this.header = genericTextHeaderItem;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (o instanceof ConversationItem) { if (o instanceof ConversationItem) {
@ -286,6 +295,16 @@ public class ConversationItem extends AbstractFlexibleItem<ConversationItem.Conv
Pattern.compile(constraint, Pattern.CASE_INSENSITIVE | Pattern.LITERAL).matcher(conversation.getDisplayName().trim()).find(); Pattern.compile(constraint, Pattern.CASE_INSENSITIVE | Pattern.LITERAL).matcher(conversation.getDisplayName().trim()).find();
} }
@Override
public GenericTextHeaderItem getHeader() {
return header;
}
@Override
public void setHeader(GenericTextHeaderItem header) {
this.header = header;
}
static class ConversationItemViewHolder extends FlexibleViewHolder { static class ConversationItemViewHolder extends FlexibleViewHolder {
@BindView(R.id.dialogAvatar) @BindView(R.id.dialogAvatar)
SimpleDraweeView dialogAvatar; SimpleDraweeView dialogAvatar;

View file

@ -436,4 +436,11 @@ public interface NcApi {
Observable<GenericOverall> setChatReadMarker(@Header("Authorization") String authorization, Observable<GenericOverall> setChatReadMarker(@Header("Authorization") String authorization,
@Url String url, @Url String url,
@Field("lastReadMessage") int lastReadMessage); @Field("lastReadMessage") int lastReadMessage);
/*
Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /listed-room
*/
@GET
Observable<RoomsOverall> getOpenConversations(@Header("Authorization") String authorization, @Url String url);
} }

View file

@ -66,6 +66,7 @@ import com.nextcloud.talk.R;
import com.nextcloud.talk.activities.MainActivity; import com.nextcloud.talk.activities.MainActivity;
import com.nextcloud.talk.adapters.items.CallItem; import com.nextcloud.talk.adapters.items.CallItem;
import com.nextcloud.talk.adapters.items.ConversationItem; import com.nextcloud.talk.adapters.items.ConversationItem;
import com.nextcloud.talk.adapters.items.GenericTextHeaderItem;
import com.nextcloud.talk.api.NcApi; import com.nextcloud.talk.api.NcApi;
import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.controllers.base.BaseController; import com.nextcloud.talk.controllers.base.BaseController;
@ -106,6 +107,7 @@ import org.parceler.Parcels;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -178,8 +180,11 @@ public class ConversationsListController extends BaseController implements Searc
private UserEntity currentUser; private UserEntity currentUser;
private Disposable roomsQueryDisposable; private Disposable roomsQueryDisposable;
private Disposable openConversationsQueryDisposable;
private FlexibleAdapter<AbstractFlexibleItem> adapter; private FlexibleAdapter<AbstractFlexibleItem> adapter;
private List<AbstractFlexibleItem> callItems = new ArrayList<>(); private List<AbstractFlexibleItem> callItems = new ArrayList<>();
private List<AbstractFlexibleItem> callItemsWithHeader = new ArrayList<>();
private List<AbstractFlexibleItem> searchableCallItems = new ArrayList<>();
private BottomSheet bottomSheet; private BottomSheet bottomSheet;
private MenuItem searchItem; private MenuItem searchItem;
@ -212,6 +217,8 @@ public class ConversationsListController extends BaseController implements Searc
private SmoothScrollLinearLayoutManager layoutManager; private SmoothScrollLinearLayoutManager layoutManager;
private HashMap<String, GenericTextHeaderItem> callHeaderItems = new HashMap<>();
public ConversationsListController(Bundle bundle) { public ConversationsListController(Bundle bundle) {
super(); super();
setHasOptionsMenu(true); setHasOptionsMenu(true);
@ -400,11 +407,20 @@ public class ConversationsListController extends BaseController implements Searc
searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
@Override @Override
public boolean onMenuItemActionExpand(MenuItem item) { public boolean onMenuItemActionExpand(MenuItem item) {
adapter.setHeadersShown(true);
adapter.updateDataSet(searchableCallItems, false);
adapter.showAllHeaders();
swipeRefreshLayout.setEnabled(false);
return true; return true;
} }
@Override @Override
public boolean onMenuItemActionCollapse(MenuItem item) { public boolean onMenuItemActionCollapse(MenuItem item) {
adapter.setHeadersShown(false);
adapter.updateDataSet(callItems, false);
adapter.hideAllHeaders();
swipeRefreshLayout.setEnabled(true);
searchView.onActionViewCollapsed(); searchView.onActionViewCollapsed();
MainActivity activity = (MainActivity) getActivity(); MainActivity activity = (MainActivity) getActivity();
if (activity != null) { if (activity != null) {
@ -462,6 +478,7 @@ public class ConversationsListController extends BaseController implements Searc
isRefreshing = true; isRefreshing = true;
callItems = new ArrayList<>(); callItems = new ArrayList<>();
callItemsWithHeader = new ArrayList<>();
int apiVersion = ApiUtils.getConversationApiVersion(currentUser, new int[]{ApiUtils.APIv4, ApiUtils.APIv3, 1}); int apiVersion = ApiUtils.getConversationApiVersion(currentUser, new int[]{ApiUtils.APIv4, ApiUtils.APIv3, 1});
@ -494,69 +511,68 @@ public class ConversationsListController extends BaseController implements Searc
} }
} }
Conversation conversation; for (Conversation conversation : roomsOverall.getOcs().getData()) {
for (int i = 0; i < roomsOverall.getOcs().getData().size(); i++) {
conversation = roomsOverall.getOcs().getData().get(i);
if (bundle.containsKey(BundleKeys.INSTANCE.getKEY_FORWARD_HIDE_SOURCE_ROOM()) && conversation.roomId.equals(bundle.getString( if (bundle.containsKey(BundleKeys.INSTANCE.getKEY_FORWARD_HIDE_SOURCE_ROOM()) && conversation.roomId.equals(bundle.getString(
BundleKeys.INSTANCE.getKEY_FORWARD_HIDE_SOURCE_ROOM()))) { BundleKeys.INSTANCE.getKEY_FORWARD_HIDE_SOURCE_ROOM()))) {
continue; continue;
} }
String headerTitle;
headerTitle = getResources().getString(R.string.conversations);
GenericTextHeaderItem genericTextHeaderItem;
if (!callHeaderItems.containsKey(headerTitle)) {
genericTextHeaderItem = new GenericTextHeaderItem(headerTitle);
callHeaderItems.put(headerTitle, genericTextHeaderItem);
}
if (shouldUseLastMessageLayout) { if (shouldUseLastMessageLayout) {
if (getActivity() != null) { if (getActivity() != null) {
ConversationItem conversationItem = new ConversationItem(conversation ConversationItem conversationItem = new ConversationItem(
, currentUser, getActivity()); conversation,
currentUser,
getActivity());
callItems.add(conversationItem); callItems.add(conversationItem);
ConversationItem conversationItemWithHeader = new ConversationItem(
conversation,
currentUser,
getActivity(),
callHeaderItems.get(headerTitle));
callItemsWithHeader.add(conversationItemWithHeader);
} }
} else { } else {
CallItem callItem = new CallItem(conversation, currentUser); CallItem callItem = new CallItem(
conversation,
currentUser);
callItems.add(callItem); callItems.add(callItem);
CallItem callItemWithHeader = new CallItem(
conversation,
currentUser,
callHeaderItems.get(headerTitle));
callItemsWithHeader.add(callItemWithHeader);
} }
} }
if (CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "last-room-activity")) { sortConversations(callItems);
Collections.sort(callItems, (o1, o2) -> { sortConversations(callItemsWithHeader);
Conversation conversation1 = ((ConversationItem) o1).getModel();
Conversation conversation2 = ((ConversationItem) o2).getModel();
return new CompareToBuilder()
.append(conversation2.isFavorite(), conversation1.isFavorite())
.append(conversation2.getLastActivity(), conversation1.getLastActivity())
.toComparison();
});
} else {
Collections.sort(callItems, (callItem, t1) ->
Long.compare(((CallItem) t1).getModel().getLastPing(),
((CallItem) callItem).getModel().getLastPing()));
}
adapter.updateDataSet(callItems, false); adapter.updateDataSet(callItems, false);
new Handler().postDelayed(this::checkToShowUnreadBubble, UNREAD_BUBBLE_DELAY); new Handler().postDelayed(this::checkToShowUnreadBubble, UNREAD_BUBBLE_DELAY);
fetchOpenConversations(apiVersion);
if (swipeRefreshLayout != null) { if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(false); swipeRefreshLayout.setRefreshing(false);
} }
}, throwable -> { }, throwable -> {
if (throwable instanceof HttpException) { handleHttpExceptions(throwable);
HttpException exception = (HttpException) throwable;
switch (exception.code()) {
case 401:
if (getParentController() != null && getParentController().getRouter() != null) {
Log.d(TAG, "Starting reauth webview via getParentController()");
getParentController().getRouter().pushController((RouterTransaction.with
(new WebViewLoginController(currentUser.getBaseUrl(), true))
.pushChangeHandler(new VerticalChangeHandler())
.popChangeHandler(new VerticalChangeHandler())));
} else {
Log.d(TAG, "Starting reauth webview via ConversationsListController");
showUnauthorizedDialog();
}
break;
default:
break;
}
}
if (swipeRefreshLayout != null) { if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(false); swipeRefreshLayout.setRefreshing(false);
} }
@ -580,6 +596,94 @@ public class ConversationsListController extends BaseController implements Searc
}); });
} }
private void sortConversations(List<AbstractFlexibleItem> callItems) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "last-room-activity")) {
Collections.sort(callItems, (o1, o2) -> {
Conversation conversation1 = ((ConversationItem) o1).getModel();
Conversation conversation2 = ((ConversationItem) o2).getModel();
return new CompareToBuilder()
.append(conversation2.isFavorite(), conversation1.isFavorite())
.append(conversation2.getLastActivity(), conversation1.getLastActivity())
.toComparison();
});
} else {
Collections.sort(callItems, (callItem, t1) ->
Long.compare(((CallItem) t1).getModel().getLastPing(),
((CallItem) callItem).getModel().getLastPing()));
}
}
private void fetchOpenConversations(int apiVersion){
searchableCallItems.clear();
searchableCallItems.addAll(callItemsWithHeader);
if (CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "listable-rooms")) {
List<AbstractFlexibleItem> openConversationItems = new ArrayList<>();
openConversationsQueryDisposable = ncApi.getOpenConversations(
credentials,
ApiUtils.getUrlForOpenConversations(apiVersion, currentUser.getBaseUrl()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(roomsOverall -> {
for (Conversation conversation : roomsOverall.getOcs().getData()) {
String headerTitle = getResources().getString(R.string.openConversations);
GenericTextHeaderItem genericTextHeaderItem;
if (!callHeaderItems.containsKey(headerTitle)) {
genericTextHeaderItem = new GenericTextHeaderItem(headerTitle);
callHeaderItems.put(headerTitle, genericTextHeaderItem);
}
if (shouldUseLastMessageLayout) {
if (getActivity() != null) {
ConversationItem conversationItem = new ConversationItem(conversation
, currentUser, getActivity(), callHeaderItems.get(headerTitle));
openConversationItems.add(conversationItem);
}
} else {
CallItem callItem = new CallItem(conversation, currentUser, callHeaderItems.get(headerTitle));
openConversationItems.add(callItem);
}
}
searchableCallItems.addAll(openConversationItems);
}, throwable -> {
handleHttpExceptions(throwable);
dispose(openConversationsQueryDisposable);
}, () -> {
dispose(openConversationsQueryDisposable);
});
} else {
Log.d(TAG, "no open conversations fetched because of missing capability");
}
}
private void handleHttpExceptions(Throwable throwable) {
if (throwable instanceof HttpException) {
HttpException exception = (HttpException) throwable;
switch (exception.code()) {
case 401:
if (getParentController() != null && getParentController().getRouter() != null) {
Log.d(TAG, "Starting reauth webview via getParentController()");
getParentController().getRouter().pushController((RouterTransaction.with
(new WebViewLoginController(currentUser.getBaseUrl(), true))
.pushChangeHandler(new VerticalChangeHandler())
.popChangeHandler(new VerticalChangeHandler())));
} else {
Log.d(TAG, "Starting reauth webview via ConversationsListController");
showUnauthorizedDialog();
}
break;
default:
break;
}
}
}
private void prepareViews() { private void prepareViews() {
layoutManager = new SmoothScrollLinearLayoutManager(Objects.requireNonNull(getActivity())); layoutManager = new SmoothScrollLinearLayoutManager(Objects.requireNonNull(getActivity()));
recyclerView.setLayoutManager(layoutManager); recyclerView.setLayoutManager(layoutManager);
@ -671,6 +775,10 @@ public class ConversationsListController extends BaseController implements Searc
roomsQueryDisposable != null && !roomsQueryDisposable.isDisposed()) { roomsQueryDisposable != null && !roomsQueryDisposable.isDisposed()) {
roomsQueryDisposable.dispose(); roomsQueryDisposable.dispose();
roomsQueryDisposable = null; roomsQueryDisposable = null;
} else if (disposable == null &&
openConversationsQueryDisposable != null && !openConversationsQueryDisposable.isDisposed()) {
openConversationsQueryDisposable.dispose();
openConversationsQueryDisposable = null;
} }
} }
@ -716,11 +824,6 @@ public class ConversationsListController extends BaseController implements Searc
adapter.filterItems(300); adapter.filterItems(300);
} }
} }
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setEnabled(!adapter.hasFilter());
}
return true; return true;
} }

View file

@ -275,6 +275,10 @@ public class ApiUtils {
return getUrlForSignaling(version, baseUrl) + "/" + token; return getUrlForSignaling(version, baseUrl) + "/" + token;
} }
public static String getUrlForOpenConversations(int version, String baseUrl) {
return getUrlForApi(version, baseUrl) + "/listed-room";
}
public static RetrofitBucket getRetrofitBucketForCreateRoom(int version, String baseUrl, String roomType, public static RetrofitBucket getRetrofitBucketForCreateRoom(int version, String baseUrl, String roomType,
@Nullable String source, @Nullable String source,
@Nullable String invite, @Nullable String invite,

View file

@ -273,6 +273,8 @@
<!-- Conversations List--> <!-- Conversations List-->
<string name="nc_new_mention">Unread mentions</string> <string name="nc_new_mention">Unread mentions</string>
<string name="conversations">Conversations</string>
<string name="openConversations">Open conversations</string>
<!-- Chat --> <!-- Chat -->
<string name="nc_hint_enter_a_message">Enter a message…</string> <string name="nc_hint_enter_a_message">Enter a message…</string>
@ -492,4 +494,5 @@
<string name="take_photo_send">Send</string> <string name="take_photo_send">Send</string>
<string name="take_photo_error_deleting_picture">Error taking picture</string> <string name="take_photo_error_deleting_picture">Error taking picture</string>
<string name="take_photo_permission">Taking a photo is not possible without permissions</string> <string name="take_photo_permission">Taking a photo is not possible without permissions</string>
</resources> </resources>