diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/CallItem.java b/app/src/main/java/com/nextcloud/talk/adapters/items/CallItem.java index 4958ee569..5818e83dc 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/CallItem.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/CallItem.java @@ -49,19 +49,27 @@ import butterknife.ButterKnife; import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; import eu.davidea.flexibleadapter.items.IFilterable; +import eu.davidea.flexibleadapter.items.ISectionable; import eu.davidea.flexibleadapter.utils.FlexibleUtils; import eu.davidea.viewholders.FlexibleViewHolder; -public class CallItem extends AbstractFlexibleItem implements IFilterable { +public class CallItem extends AbstractFlexibleItem implements ISectionable, IFilterable { private Conversation conversation; private UserEntity userEntity; + private GenericTextHeaderItem header; public CallItem(Conversation conversation, UserEntity userEntity) { this.conversation = conversation; this.userEntity = userEntity; } + public CallItem(Conversation conversation, UserEntity userEntity, GenericTextHeaderItem genericTextHeaderItem) { + this.conversation = conversation; + this.userEntity = userEntity; + this.header = genericTextHeaderItem; + } + @Override public boolean equals(Object o) { if (o instanceof CallItem) { @@ -168,6 +176,16 @@ public class CallItem extends AbstractFlexibleItem 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 { @BindView(R.id.name_text) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.java b/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.java index 861528357..f63b1f3cf 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.java @@ -61,24 +61,33 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; import eu.davidea.flexibleadapter.items.IFilterable; import eu.davidea.flexibleadapter.items.IFlexible; +import eu.davidea.flexibleadapter.items.ISectionable; import eu.davidea.flexibleadapter.utils.FlexibleUtils; import eu.davidea.viewholders.FlexibleViewHolder; -public class ConversationItem extends AbstractFlexibleItem implements +public class ConversationItem extends AbstractFlexibleItem implements ISectionable, IFilterable { private Conversation conversation; private UserEntity userEntity; private Context context; + private GenericTextHeaderItem header; - public ConversationItem(Conversation conversation, UserEntity userEntity, - Context activityContext) { + public ConversationItem(Conversation conversation, UserEntity userEntity, Context activityContext) { this.conversation = conversation; this.userEntity = userEntity; 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 public boolean equals(Object o) { if (o instanceof ConversationItem) { @@ -286,6 +295,16 @@ public class ConversationItem extends AbstractFlexibleItem setChatReadMarker(@Header("Authorization") String authorization, @Url String url, @Field("lastReadMessage") int lastReadMessage); + + /* + Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /listed-room + */ + @GET + Observable getOpenConversations(@Header("Authorization") String authorization, @Url String url); + } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java index 76b3867ba..1becbac2c 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java @@ -66,6 +66,7 @@ import com.nextcloud.talk.R; import com.nextcloud.talk.activities.MainActivity; import com.nextcloud.talk.adapters.items.CallItem; import com.nextcloud.talk.adapters.items.ConversationItem; +import com.nextcloud.talk.adapters.items.GenericTextHeaderItem; import com.nextcloud.talk.api.NcApi; import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.controllers.base.BaseController; @@ -106,6 +107,7 @@ import org.parceler.Parcels; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Objects; @@ -178,8 +180,11 @@ public class ConversationsListController extends BaseController implements Searc private UserEntity currentUser; private Disposable roomsQueryDisposable; + private Disposable openConversationsQueryDisposable; private FlexibleAdapter adapter; private List callItems = new ArrayList<>(); + private List callItemsWithHeader = new ArrayList<>(); + private List searchableCallItems = new ArrayList<>(); private BottomSheet bottomSheet; private MenuItem searchItem; @@ -212,6 +217,8 @@ public class ConversationsListController extends BaseController implements Searc private SmoothScrollLinearLayoutManager layoutManager; + private HashMap callHeaderItems = new HashMap<>(); + public ConversationsListController(Bundle bundle) { super(); setHasOptionsMenu(true); @@ -400,11 +407,20 @@ public class ConversationsListController extends BaseController implements Searc searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { @Override public boolean onMenuItemActionExpand(MenuItem item) { + adapter.setHeadersShown(true); + adapter.updateDataSet(searchableCallItems, false); + adapter.showAllHeaders(); + swipeRefreshLayout.setEnabled(false); return true; } @Override public boolean onMenuItemActionCollapse(MenuItem item) { + adapter.setHeadersShown(false); + adapter.updateDataSet(callItems, false); + adapter.hideAllHeaders(); + swipeRefreshLayout.setEnabled(true); + searchView.onActionViewCollapsed(); MainActivity activity = (MainActivity) getActivity(); if (activity != null) { @@ -462,6 +478,7 @@ public class ConversationsListController extends BaseController implements Searc isRefreshing = true; callItems = new ArrayList<>(); + callItemsWithHeader = new ArrayList<>(); 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 (int i = 0; i < roomsOverall.getOcs().getData().size(); i++) { - conversation = roomsOverall.getOcs().getData().get(i); - + for (Conversation conversation : roomsOverall.getOcs().getData()) { if (bundle.containsKey(BundleKeys.INSTANCE.getKEY_FORWARD_HIDE_SOURCE_ROOM()) && conversation.roomId.equals(bundle.getString( BundleKeys.INSTANCE.getKEY_FORWARD_HIDE_SOURCE_ROOM()))) { 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 (getActivity() != null) { - ConversationItem conversationItem = new ConversationItem(conversation - , currentUser, getActivity()); + ConversationItem conversationItem = new ConversationItem( + conversation, + currentUser, + getActivity()); callItems.add(conversationItem); + + ConversationItem conversationItemWithHeader = new ConversationItem( + conversation, + currentUser, + getActivity(), + callHeaderItems.get(headerTitle)); + + callItemsWithHeader.add(conversationItemWithHeader); } } else { - CallItem callItem = new CallItem(conversation, currentUser); + CallItem callItem = new CallItem( + conversation, + currentUser); callItems.add(callItem); + + CallItem callItemWithHeader = new CallItem( + conversation, + currentUser, + callHeaderItems.get(headerTitle)); + + callItemsWithHeader.add(callItemWithHeader); } } - 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())); - } + sortConversations(callItems); + sortConversations(callItemsWithHeader); adapter.updateDataSet(callItems, false); + new Handler().postDelayed(this::checkToShowUnreadBubble, UNREAD_BUBBLE_DELAY); + fetchOpenConversations(apiVersion); + if (swipeRefreshLayout != null) { swipeRefreshLayout.setRefreshing(false); } }, 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; - } - } + handleHttpExceptions(throwable); if (swipeRefreshLayout != null) { swipeRefreshLayout.setRefreshing(false); } @@ -580,6 +596,94 @@ public class ConversationsListController extends BaseController implements Searc }); } + private void sortConversations(List 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 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() { layoutManager = new SmoothScrollLinearLayoutManager(Objects.requireNonNull(getActivity())); recyclerView.setLayoutManager(layoutManager); @@ -671,6 +775,10 @@ public class ConversationsListController extends BaseController implements Searc roomsQueryDisposable != null && !roomsQueryDisposable.isDisposed()) { roomsQueryDisposable.dispose(); 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); } } - - if (swipeRefreshLayout != null) { - swipeRefreshLayout.setEnabled(!adapter.hasFilter()); - } - return true; } diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java index 6b0be29d0..01c5c7f74 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java @@ -275,6 +275,10 @@ public class ApiUtils { 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, @Nullable String source, @Nullable String invite, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8eee9721f..94ca857b3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -273,6 +273,8 @@ Unread mentions + Conversations + Open conversations Enter a messageā€¦ @@ -492,4 +494,5 @@ Send Error taking picture Taking a photo is not possible without permissions +