Browse Source

Merge pull request #1782 from nextcloud/feature/1625/joinOpenConversations

add openConversations to search
Marcel Hibbe 3 years ago
parent
commit
a05a931749

+ 18 - 13
app/src/gplay/java/com/nextcloud/talk/jobs/GetFirebasePushTokenWorker.kt

@@ -42,22 +42,27 @@ class GetFirebasePushTokenWorker(val context: Context, workerParameters: WorkerP
 
     @SuppressLint("LongLogTag")
     override fun doWork(): Result {
-        FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
-            if (!task.isSuccessful) {
-                Log.w(TAG, "Fetching FCM registration token failed", task.exception)
-                return@OnCompleteListener
-            }
+        FirebaseMessaging.getInstance().token.addOnCompleteListener(
+            OnCompleteListener { task ->
+                if (!task.isSuccessful) {
+                    Log.w(TAG, "Fetching FCM registration token failed", task.exception)
+                    return@OnCompleteListener
+                }
 
-            val token = task.result
+                val token = task.result
 
-            appPreferences?.pushToken = token
+                appPreferences?.pushToken = token
 
-            val data: Data = Data.Builder().putString(PushRegistrationWorker.ORIGIN, "GetFirebasePushTokenWorker").build()
-            val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java)
-                .setInputData(data)
-                .build()
-            WorkManager.getInstance(context).enqueue(pushRegistrationWork)
-        })
+                val data: Data =
+                    Data.Builder()
+                        .putString(PushRegistrationWorker.ORIGIN, "GetFirebasePushTokenWorker")
+                        .build()
+                val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java)
+                    .setInputData(data)
+                    .build()
+                WorkManager.getInstance(context).enqueue(pushRegistrationWork)
+            }
+        )
 
         return Result.success()
     }

+ 10 - 2
app/src/gplay/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt

@@ -75,7 +75,11 @@ class ClosedInterfaceImpl : ClosedInterface, ProviderInstaller.ProviderInstallLi
     }
 
     private fun registerLocalToken() {
-        val data: Data = Data.Builder().putString(PushRegistrationWorker.ORIGIN, "ClosedInterfaceImpl#registerLocalToken").build()
+        val data: Data = Data.Builder().putString(
+            PushRegistrationWorker.ORIGIN,
+            "ClosedInterfaceImpl#registerLocalToken"
+        )
+            .build()
         val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java)
             .setInputData(data)
             .build()
@@ -83,7 +87,11 @@ class ClosedInterfaceImpl : ClosedInterface, ProviderInstaller.ProviderInstallLi
     }
 
     private fun setUpPeriodicLocalTokenRegistration() {
-        val data: Data = Data.Builder().putString(PushRegistrationWorker.ORIGIN, "ClosedInterfaceImpl#setUpPeriodicLocalTokenRegistration").build()
+        val data: Data = Data.Builder().putString(
+            PushRegistrationWorker.ORIGIN,
+            "ClosedInterfaceImpl#setUpPeriodicLocalTokenRegistration"
+        )
+            .build()
 
         val periodicTokenRegistration = PeriodicWorkRequest.Builder(
             PushRegistrationWorker::class.java,

+ 4 - 17
app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.java

@@ -100,7 +100,7 @@ public class AdvancedUserItem extends AbstractFlexibleItem<AdvancedUserItem.User
 
     @Override
     public int getLayoutRes() {
-        return R.layout.rv_item_conversation;
+        return R.layout.account_item;
     }
 
     @Override
@@ -145,11 +145,6 @@ public class AdvancedUserItem extends AbstractFlexibleItem<AdvancedUserItem.User
             holder.avatarImageView.setController(draweeController);
         } else {
             holder.avatarImageView.setVisibility(View.GONE);
-            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) holder.linearLayout.getLayoutParams();
-            layoutParams.setMarginStart((int) NextcloudTalkApplication.Companion.getSharedApplication().getApplicationContext()
-                    .getResources().getDimension(R.dimen.activity_horizontal_margin));
-            layoutParams.addRule(RelativeLayout.ALIGN_PARENT_START);
-            holder.linearLayout.setLayoutParams(layoutParams);
         }
     }
 
@@ -162,18 +157,12 @@ public class AdvancedUserItem extends AbstractFlexibleItem<AdvancedUserItem.User
 
     static class UserItemViewHolder extends FlexibleViewHolder {
 
-        @BindView(R.id.name_text)
+        @BindView(R.id.user_name)
         public EmojiTextView contactDisplayName;
-        @BindView(R.id.secondary_text)
+        @BindView(R.id.account)
         public TextView serverUrl;
-        @BindView(R.id.avatar_image)
+        @BindView(R.id.user_icon)
         public SimpleDraweeView avatarImageView;
-        @BindView(R.id.linear_layout)
-        LinearLayout linearLayout;
-        @BindView(R.id.more_menu)
-        ImageButton moreMenuButton;
-        @BindView(R.id.password_protected_image_view)
-        ImageView passwordProtectedImageView;
 
         /**
          * Default constructor.
@@ -181,8 +170,6 @@ public class AdvancedUserItem extends AbstractFlexibleItem<AdvancedUserItem.User
         UserItemViewHolder(View view, FlexibleAdapter adapter) {
             super(view, adapter);
             ButterKnife.bind(this, view);
-            moreMenuButton.setVisibility(View.GONE);
-            passwordProtectedImageView.setVisibility(View.GONE);
         }
     }
 }

+ 0 - 189
app/src/main/java/com/nextcloud/talk/adapters/items/CallItem.java

@@ -1,189 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Mario Danic
- * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.nextcloud.talk.adapters.items;
-
-import android.content.res.Resources;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-
-import com.facebook.drawee.backends.pipeline.Fresco;
-import com.facebook.drawee.interfaces.DraweeController;
-import com.facebook.drawee.view.SimpleDraweeView;
-import com.nextcloud.talk.R;
-import com.nextcloud.talk.application.NextcloudTalkApplication;
-import com.nextcloud.talk.events.MoreMenuClickEvent;
-import com.nextcloud.talk.models.database.UserEntity;
-import com.nextcloud.talk.models.json.conversations.Conversation;
-import com.nextcloud.talk.utils.ApiUtils;
-import com.nextcloud.talk.utils.DisplayUtils;
-
-import org.greenrobot.eventbus.EventBus;
-
-import java.util.List;
-import java.util.regex.Pattern;
-
-import androidx.emoji.widget.EmojiTextView;
-import butterknife.BindView;
-import butterknife.ButterKnife;
-import eu.davidea.flexibleadapter.FlexibleAdapter;
-import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
-import eu.davidea.flexibleadapter.items.IFilterable;
-import eu.davidea.flexibleadapter.utils.FlexibleUtils;
-import eu.davidea.viewholders.FlexibleViewHolder;
-
-public class CallItem extends AbstractFlexibleItem<CallItem.RoomItemViewHolder> implements IFilterable<String> {
-
-    private Conversation conversation;
-    private UserEntity userEntity;
-
-    public CallItem(Conversation conversation, UserEntity userEntity) {
-        this.conversation = conversation;
-        this.userEntity = userEntity;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o instanceof CallItem) {
-            CallItem inItem = (CallItem) o;
-            return conversation.equals(inItem.getModel());
-        }
-        return false;
-    }
-
-    @Override
-    public int hashCode() {
-        return conversation.hashCode();
-    }
-
-    /**
-     * @return the model object
-     */
-
-    public Conversation getModel() {
-        return conversation;
-    }
-
-    /**
-     * Filter is applied to the model fields.
-     */
-
-    @Override
-    public int getLayoutRes() {
-        return R.layout.rv_item_conversation;
-    }
-
-    @Override
-    public RoomItemViewHolder createViewHolder(View view, FlexibleAdapter adapter) {
-        return new RoomItemViewHolder(view, adapter);
-    }
-
-    @Override
-    public void bindViewHolder(final FlexibleAdapter adapter, RoomItemViewHolder holder, int position, List payloads) {
-        if (adapter.hasFilter()) {
-            FlexibleUtils.highlightText(holder.roomDisplayName, conversation.getDisplayName(),
-                    String.valueOf(adapter.getFilter(String.class)), NextcloudTalkApplication.Companion.getSharedApplication()
-                            .getResources().getColor(R.color.colorPrimary));
-        } else {
-            holder.roomDisplayName.setText(conversation.getDisplayName());
-        }
-
-        if (conversation.getLastPing() == 0) {
-            holder.roomLastPing.setText(R.string.nc_never);
-        } else {
-            holder.roomLastPing.setText(DateUtils.getRelativeTimeSpanString(conversation.getLastPing() * 1000L,
-                    System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE));
-        }
-
-        if (conversation.hasPassword) {
-            holder.passwordProtectedImageView.setVisibility(View.VISIBLE);
-        } else {
-            holder.passwordProtectedImageView.setVisibility(View.GONE);
-        }
-
-        Resources resources = NextcloudTalkApplication.Companion.getSharedApplication().getResources();
-        switch (conversation.getType()) {
-            case ROOM_TYPE_ONE_TO_ONE_CALL:
-                holder.avatarImageView.setVisibility(View.VISIBLE);
-
-                holder.moreMenuButton.setContentDescription(String.format(resources.getString(R.string
-                        .nc_description_more_menu_one_to_one), conversation.getDisplayName()));
-
-                if (!TextUtils.isEmpty(conversation.getName())) {
-                    DraweeController draweeController = Fresco.newDraweeControllerBuilder()
-                            .setOldController(holder.avatarImageView.getController())
-                            .setAutoPlayAnimations(true)
-                            .setImageRequest(DisplayUtils.getImageRequestForUrl(ApiUtils.getUrlForAvatarWithName(userEntity.getBaseUrl(),
-                                    conversation.getName(),
-                                    R.dimen.avatar_size), null))
-                            .build();
-                    holder.avatarImageView.setController(draweeController);
-                } else {
-                    holder.avatarImageView.setVisibility(View.GONE);
-                }
-                break;
-            case ROOM_GROUP_CALL:
-                holder.moreMenuButton.setContentDescription(String.format(resources.getString(R.string
-                        .nc_description_more_menu_group), conversation.getDisplayName()));
-                holder.avatarImageView.setActualImageResource(R.drawable.ic_circular_group);
-                holder.avatarImageView.setVisibility(View.VISIBLE);
-                break;
-            case ROOM_PUBLIC_CALL:
-                holder.moreMenuButton.setContentDescription(String.format(resources.getString(R.string
-                        .nc_description_more_menu_public), conversation.getDisplayName()));
-                holder.avatarImageView.setActualImageResource(R.drawable.ic_circular_link);
-                holder.avatarImageView.setVisibility(View.VISIBLE);
-                break;
-            default:
-                holder.avatarImageView.setVisibility(View.GONE);
-
-        }
-
-        holder.moreMenuButton.setOnClickListener(view -> EventBus.getDefault().post(new MoreMenuClickEvent(conversation)));
-    }
-
-    @Override
-    public boolean filter(String constraint) {
-        return conversation.getDisplayName() != null &&
-                Pattern.compile(constraint, Pattern.CASE_INSENSITIVE | Pattern.LITERAL).matcher(conversation.getDisplayName().trim()).find();
-    }
-
-    static class RoomItemViewHolder extends FlexibleViewHolder {
-
-        @BindView(R.id.name_text)
-        public EmojiTextView roomDisplayName;
-        @BindView(R.id.secondary_text)
-        public EmojiTextView roomLastPing;
-        @BindView(R.id.avatar_image)
-        public SimpleDraweeView avatarImageView;
-        @BindView(R.id.more_menu)
-        public ImageButton moreMenuButton;
-        @BindView(R.id.password_protected_image_view)
-        ImageView passwordProtectedImageView;
-
-        RoomItemViewHolder(View view, FlexibleAdapter adapter) {
-            super(view, adapter);
-            ButterKnife.bind(this, view);
-        }
-    }
-}

+ 21 - 2
app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.java

@@ -61,22 +61,31 @@ 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<ConversationItem.ConversationItemViewHolder> implements
+public class ConversationItem extends AbstractFlexibleItem<ConversationItem.ConversationItemViewHolder> implements ISectionable<ConversationItem.ConversationItemViewHolder, GenericTextHeaderItem>,
         IFilterable<String> {
 
 
     private Conversation conversation;
     private UserEntity userEntity;
     private Context context;
+    private GenericTextHeaderItem header;
+
+    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) {
+                            Context activityContext, GenericTextHeaderItem genericTextHeaderItem) {
         this.conversation = conversation;
         this.userEntity = userEntity;
         this.context = activityContext;
+        this.header = genericTextHeaderItem;
     }
 
     @Override
@@ -286,6 +295,16 @@ public class ConversationItem extends AbstractFlexibleItem<ConversationItem.Conv
                 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 {
         @BindView(R.id.dialogAvatar)
         SimpleDraweeView dialogAvatar;

+ 7 - 0
app/src/main/java/com/nextcloud/talk/api/NcApi.java

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

+ 146 - 82
app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java

@@ -44,6 +44,7 @@ import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
 import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
 import android.widget.Toast;
@@ -64,8 +65,8 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
 import com.kennyc.bottomsheet.BottomSheet;
 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<AbstractFlexibleItem> adapter;
-    private List<AbstractFlexibleItem> callItems = new ArrayList<>();
+    private List<AbstractFlexibleItem> conversationItems = new ArrayList<>();
+    private List<AbstractFlexibleItem> conversationItemsWithHeader = new ArrayList<>();
+    private final List<AbstractFlexibleItem> searchableConversationItems = new ArrayList<>();
 
     private BottomSheet bottomSheet;
     private MenuItem searchItem;
@@ -187,7 +192,6 @@ public class ConversationsListController extends BaseController implements Searc
     private String searchQuery;
 
     private View view;
-    private boolean shouldUseLastMessageLayout;
 
     private String credentials;
 
@@ -212,6 +216,8 @@ public class ConversationsListController extends BaseController implements Searc
 
     private SmoothScrollLinearLayoutManager layoutManager;
 
+    private HashMap<String, GenericTextHeaderItem> callHeaderItems = new HashMap<>();
+
     public ConversationsListController(Bundle bundle) {
         super();
         setHasOptionsMenu(true);
@@ -238,7 +244,7 @@ public class ConversationsListController extends BaseController implements Searc
         }
 
         if (adapter == null) {
-            adapter = new FlexibleAdapter<>(callItems, getActivity(), true);
+            adapter = new FlexibleAdapter<>(conversationItems, getActivity(), true);
         } else {
             loadingContent.setVisibility(View.GONE);
         }
@@ -301,8 +307,6 @@ public class ConversationsListController extends BaseController implements Searc
             }
 
             credentials = ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken());
-            shouldUseLastMessageLayout = CapabilitiesUtil.hasSpreedFeatureCapability(currentUser,
-                                                                                     "last-room-activity");
             if (getActivity() != null && getActivity() instanceof MainActivity) {
                 loadUserAvatar(((MainActivity) getActivity()).binding.switchAccountButton);
             }
@@ -364,7 +368,7 @@ public class ConversationsListController extends BaseController implements Searc
         } else {
             MainActivity activity = (MainActivity) getActivity();
 
-            searchItem.setVisible(callItems.size() > 0);
+            searchItem.setVisible(conversationItems.size() > 0);
             if (activity != null) {
                 if (adapter.hasFilter()) {
                     showSearchView(activity, searchView, searchItem);
@@ -400,11 +404,20 @@ public class ConversationsListController extends BaseController implements Searc
             searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
                 @Override
                 public boolean onMenuItemActionExpand(MenuItem item) {
+                    adapter.setHeadersShown(true);
+                    adapter.updateDataSet(searchableConversationItems, false);
+                    adapter.showAllHeaders();
+                    swipeRefreshLayout.setEnabled(false);
                     return true;
                 }
 
                 @Override
                 public boolean onMenuItemActionCollapse(MenuItem item) {
+                    adapter.setHeadersShown(false);
+                    adapter.updateDataSet(conversationItems, false);
+                    adapter.hideAllHeaders();
+                    swipeRefreshLayout.setEnabled(true);
+
                     searchView.onActionViewCollapsed();
                     MainActivity activity = (MainActivity) getActivity();
                     if (activity != null) {
@@ -461,7 +474,8 @@ public class ConversationsListController extends BaseController implements Searc
 
         isRefreshing = true;
 
-        callItems = new ArrayList<>();
+        conversationItems = new ArrayList<>();
+        conversationItemsWithHeader = new ArrayList<>();
 
         int apiVersion = ApiUtils.getConversationApiVersion(currentUser, new int[]{ApiUtils.APIv4, ApiUtils.APIv3, 1});
 
@@ -494,69 +508,53 @@ 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;
                         }
 
-                        if (shouldUseLastMessageLayout) {
-                            if (getActivity() != null) {
-                                ConversationItem conversationItem = new ConversationItem(conversation
-                                        , currentUser, getActivity());
-                                callItems.add(conversationItem);
-                            }
-                        } else {
-                            CallItem callItem = new CallItem(conversation, currentUser);
-                            callItems.add(callItem);
+                        String headerTitle;
+
+                        headerTitle = getResources().getString(R.string.conversations);
+
+                        GenericTextHeaderItem genericTextHeaderItem;
+                        if (!callHeaderItems.containsKey(headerTitle)) {
+                            genericTextHeaderItem = new GenericTextHeaderItem(headerTitle);
+                            callHeaderItems.put(headerTitle, genericTextHeaderItem);
                         }
-                    }
 
-                    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()));
+                        if (getActivity() != null) {
+                            ConversationItem conversationItem = new ConversationItem(
+                                conversation,
+                                currentUser,
+                                getActivity());
+                            conversationItems.add(conversationItem);
+
+                            ConversationItem conversationItemWithHeader = new ConversationItem(
+                                conversation,
+                                currentUser,
+                                getActivity(),
+                                callHeaderItems.get(headerTitle));
+                            conversationItemsWithHeader.add(conversationItemWithHeader);
+                        }
                     }
 
-                    adapter.updateDataSet(callItems, false);
+                    sortConversations(conversationItems);
+                    sortConversations(conversationItemsWithHeader);
+
+                    adapter.updateDataSet(conversationItems, 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 +578,84 @@ public class ConversationsListController extends BaseController implements Searc
                 });
     }
 
+    private void sortConversations(List<AbstractFlexibleItem> conversationItems) {
+        Collections.sort(conversationItems, (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();
+        });
+    }
+
+    private void fetchOpenConversations(int apiVersion){
+        searchableConversationItems.clear();
+        searchableConversationItems.addAll(conversationItemsWithHeader);
+
+        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);
+                        }
+
+                        ConversationItem conversationItem = new ConversationItem(
+                            conversation,
+                            currentUser,
+                            getActivity(),
+                            callHeaderItems.get(headerTitle));
+
+                        openConversationItems.add(conversationItem);
+                    }
+                    searchableConversationItems.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;
+            }
+        }
+    }
+
+    @SuppressLint("ClickableViewAccessibility")
     private void prepareViews() {
         layoutManager = new SmoothScrollLinearLayoutManager(Objects.requireNonNull(getActivity()));
         recyclerView.setLayoutManager(layoutManager);
@@ -595,6 +671,13 @@ public class ConversationsListController extends BaseController implements Searc
             }
         });
 
+        recyclerView.setOnTouchListener((v, event) -> {
+            InputMethodManager imm =
+                (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+            imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
+            return false;
+        });
+
         swipeRefreshLayout.setOnRefreshListener(() -> fetchData(false));
         swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary);
         swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.refresh_spinner_background);
@@ -633,7 +716,7 @@ public class ConversationsListController extends BaseController implements Searc
     private void checkToShowUnreadBubble() {
         try {
             int lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition();
-            for (AbstractFlexibleItem flexItem : callItems) {
+            for (AbstractFlexibleItem flexItem : conversationItems) {
                 Conversation conversationItem = ((ConversationItem) flexItem).getModel();
                 int position = adapter.getGlobalPositionOf(flexItem);
                 if ((conversationItem.unreadMention ||
@@ -671,6 +754,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 +803,6 @@ public class ConversationsListController extends BaseController implements Searc
                 adapter.filterItems(300);
             }
         }
-
-        if (swipeRefreshLayout != null) {
-            swipeRefreshLayout.setEnabled(!adapter.hasFilter());
-        }
-
         return true;
     }
 
@@ -790,7 +872,7 @@ public class ConversationsListController extends BaseController implements Searc
 
     @Override
     public boolean onItemClick(View view, int position) {
-        selectedConversation = getConversation(position);
+        selectedConversation = ((ConversationItem) Objects.requireNonNull(adapter.getItem(position))).getModel();
         if (selectedConversation != null && getActivity() != null) {
             if (showShareToScreen) {
                 handleSharedData();
@@ -861,20 +943,13 @@ public class ConversationsListController extends BaseController implements Searc
 
     @Override
     public void onItemLongClick(int position) {
-
         if (showShareToScreen) {
             Log.d(TAG, "sharing to multiple rooms not yet implemented. onItemLongClick is ignored.");
 
-        } else if (CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "last-room-activity")) {
+        } else {
             Object clickedItem = adapter.getItem(position);
             if (clickedItem != null) {
-                Conversation conversation;
-                if (shouldUseLastMessageLayout) {
-                    conversation = ((ConversationItem) clickedItem).getModel();
-                } else {
-                    conversation = ((CallItem) clickedItem).getModel();
-                }
-
+                Conversation conversation = ((ConversationItem) clickedItem).getModel();
                 MoreMenuClickEvent moreMenuClickEvent = new MoreMenuClickEvent(conversation);
                 onMessageEvent(moreMenuClickEvent);
             }
@@ -998,17 +1073,6 @@ public class ConversationsListController extends BaseController implements Searc
         }
     }
 
-    private Conversation getConversation(int position) {
-        Object clickedItem = adapter.getItem(position);
-        Conversation conversation;
-        if (shouldUseLastMessageLayout) {
-            conversation = ((ConversationItem) clickedItem).getModel();
-        } else {
-            conversation = ((CallItem) clickedItem).getModel();
-        }
-        return conversation;
-    }
-
     @Subscribe(sticky = true, threadMode = ThreadMode.BACKGROUND)
     public void onMessageEvent(EventStatus eventStatus) {
         if (currentUser != null && eventStatus.getUserId() == currentUser.getId()) {

+ 4 - 0
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,

+ 1 - 39
app/src/main/res/layout/account_item.xml

@@ -57,16 +57,6 @@
                 fresco:placeholderImage="@drawable/account_circle_48dp"
                 fresco:failureImage="@drawable/account_circle_48dp"
                 app:roundAsCircle="true"/>
-
-            <ImageView
-                android:id="@+id/ticker"
-                android:layout_width="18dp"
-                android:layout_height="18dp"
-                android:layout_gravity="bottom|end"
-                android:background="@drawable/round_bgnd"
-                android:contentDescription="@string/nc_account_chooser_active_user"
-                android:src="@drawable/ic_check_circle"
-                tools:visibility="gone" />
         </FrameLayout>
 
 
@@ -80,7 +70,7 @@
             android:paddingStart="3dp"
             android:paddingEnd="0dp">
 
-            <TextView
+            <androidx.emoji.widget.EmojiTextView
                 android:id="@+id/user_name"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
@@ -95,20 +85,6 @@
                 android:textSize="@dimen/two_line_primary_text_size"
                 tools:text="Firstname Lastname" />
 
-            <TextView
-                android:id="@+id/status"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginStart="@dimen/standard_half_margin"
-                android:layout_marginEnd="@dimen/standard_double_margin"
-                android:layout_marginBottom="4dp"
-                android:ellipsize="end"
-                android:gravity="top"
-                android:maxLines="1"
-                android:textColor="?android:attr/textColorSecondary"
-                android:visibility="gone"
-                tools:text="☁️ My custom status" />
-
             <TextView
                 android:id="@+id/account"
                 android:layout_width="match_parent"
@@ -126,19 +102,5 @@
 
         </LinearLayout>
 
-        <ImageView
-            android:id="@+id/account_menu"
-            android:layout_width="48dp"
-            android:layout_height="match_parent"
-            android:layout_alignParentEnd="true"
-            android:layout_centerVertical="true"
-            android:clickable="true"
-            android:contentDescription="@string/nc_account_chooser_active_user"
-            android:focusable="true"
-            android:paddingStart="@dimen/standard_half_padding"
-            android:paddingEnd="10dp"
-            android:src="@drawable/ic_check_circle"
-            app:tint="@color/colorPrimary" />
-
     </RelativeLayout>
 </com.google.android.material.card.MaterialCardView>

+ 1 - 2
app/src/main/res/layout/controller_call_menu.xml

@@ -27,8 +27,7 @@
     <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/recycler_view"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        tools:listitem="@layout/rv_item_conversation" />
+        android:layout_height="wrap_content" />
 
 
 </RelativeLayout>

+ 1 - 2
app/src/main/res/layout/controller_conversations_rv.xml

@@ -103,8 +103,7 @@
             <androidx.recyclerview.widget.RecyclerView
                 android:id="@+id/recycler_view"
                 android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                tools:listitem="@layout/rv_item_conversation" />
+                android:layout_height="match_parent" />
 
         </FrameLayout>
 

+ 1 - 2
app/src/main/res/layout/controller_generic_rv.xml

@@ -43,6 +43,5 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         app:layout_anchor="@+id/swipe_refresh_layout"
-        app:layout_anchorGravity="center"
-        tools:listitem="@layout/rv_item_conversation" />
+        app:layout_anchorGravity="center" />
 </androidx.coordinatorlayout.widget.CoordinatorLayout>

+ 144 - 0
app/src/main/res/layout/current_account_item.xml

@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Nextcloud Talk application
+
+  Copyright (C) 2016 Andy Scherzinger
+  Copyright (C) 2016 Nextcloud
+  Copyright (C) 2016 ownCloud
+  Copyright (C) 2020 Infomaniak Network SA
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+  License as published by the Free Software Foundation; either
+  version 3 of the License, or any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+
+  You should have received a copy of the GNU Affero General Public
+  License along with this program. If not, see <http://www.gnu.org/licenses/>.
+-->
+<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:fresco="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="72dp"
+    android:layout_margin="4dp"
+    android:orientation="horizontal"
+    app:cardBackgroundColor="@color/transparent"
+    app:cardElevation="0dp">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="horizontal"
+        tools:ignore="UnusedAttribute">
+
+        <FrameLayout
+            android:id="@+id/avatar_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentStart="true"
+            android:layout_centerVertical="true">
+
+            <com.facebook.drawee.view.SimpleDraweeView
+                android:id="@+id/user_icon"
+                android:layout_width="@dimen/small_item_height"
+                android:layout_height="@dimen/small_item_height"
+                android:layout_gravity="top|start"
+                android:layout_marginStart="12dp"
+                android:layout_marginTop="1dp"
+                android:layout_marginEnd="1dp"
+                android:layout_marginBottom="1dp"
+                android:contentDescription="@string/avatar"
+                android:src="@drawable/account_circle_48dp"
+                fresco:placeholderImage="@drawable/account_circle_48dp"
+                fresco:failureImage="@drawable/account_circle_48dp"
+                app:roundAsCircle="true"/>
+
+            <ImageView
+                android:id="@+id/ticker"
+                android:layout_width="18dp"
+                android:layout_height="18dp"
+                android:layout_gravity="bottom|end"
+                android:background="@drawable/round_bgnd"
+                android:contentDescription="@string/nc_account_chooser_active_user"
+                android:src="@drawable/ic_check_circle"
+                tools:visibility="gone" />
+        </FrameLayout>
+
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true"
+            android:layout_marginEnd="25dp"
+            android:layout_toEndOf="@id/avatar_container"
+            android:orientation="vertical"
+            android:paddingStart="3dp"
+            android:paddingEnd="0dp">
+
+            <androidx.emoji.widget.EmojiTextView
+                android:id="@+id/user_name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="@dimen/standard_half_margin"
+                android:layout_marginTop="4dp"
+                android:layout_marginEnd="@dimen/standard_double_margin"
+                android:ellipsize="end"
+                android:gravity="start|bottom"
+                android:maxLines="1"
+                android:textAlignment="viewStart"
+                android:textColor="@color/conversation_item_header"
+                android:textSize="@dimen/two_line_primary_text_size"
+                tools:text="Firstname Lastname" />
+
+            <TextView
+                android:id="@+id/status"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="@dimen/standard_half_margin"
+                android:layout_marginEnd="@dimen/standard_double_margin"
+                android:layout_marginBottom="4dp"
+                android:ellipsize="end"
+                android:gravity="top"
+                android:maxLines="1"
+                android:textColor="?android:attr/textColorSecondary"
+                android:visibility="gone"
+                tools:text="☁️ My custom status" />
+
+            <TextView
+                android:id="@+id/account"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="@dimen/standard_half_margin"
+                android:layout_marginEnd="@dimen/standard_double_margin"
+                android:layout_marginBottom="4dp"
+                android:ellipsize="end"
+                android:gravity="start|top"
+                android:maxLines="1"
+                android:textAlignment="viewStart"
+                android:textColor="@color/textColorMaxContrast"
+                android:textSize="14sp"
+                tools:text="https://server.com/nextcloud" />
+
+        </LinearLayout>
+
+        <ImageView
+            android:id="@+id/account_menu"
+            android:layout_width="48dp"
+            android:layout_height="match_parent"
+            android:layout_alignParentEnd="true"
+            android:layout_centerVertical="true"
+            android:clickable="true"
+            android:contentDescription="@string/nc_account_chooser_active_user"
+            android:focusable="true"
+            android:paddingStart="@dimen/standard_half_padding"
+            android:paddingEnd="10dp"
+            android:src="@drawable/ic_check_circle"
+            app:tint="@color/colorPrimary" />
+
+    </RelativeLayout>
+</com.google.android.material.card.MaterialCardView>

+ 1 - 1
app/src/main/res/layout/dialog_choose_account.xml

@@ -23,7 +23,7 @@
 
     <include
         android:id="@+id/current_account"
-        layout="@layout/account_item"
+        layout="@layout/current_account_item"
         android:layout_width="0dp"
         android:layout_height="72dp"
         android:layout_margin="4dp"

+ 0 - 98
app/src/main/res/layout/rv_item_conversation.xml

@@ -1,98 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  ~ Nextcloud Talk application
-  ~
-  ~ @author Mario Danic
-  ~ @author Andy Scherzinger
-  ~ Copyright (C) 2017 Mario Danic
-  ~ Copyright (C) 2017-2021 Andy Scherzinger
-  ~
-  ~ This program is free software: you can redistribute it and/or modify
-  ~ it under the terms of the GNU General Public License as published by
-  ~ the Free Software Foundation, either version 3 of the License, or
-  ~ at your option) any later version.
-  ~
-  ~ This program is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  ~ GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License
-  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.
-  -->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:layout_margin="@dimen/standard_margin">
-
-    <FrameLayout
-        android:id="@+id/frame_layout"
-        android:layout_width="@dimen/small_item_height"
-        android:layout_height="@dimen/small_item_height"
-        android:layout_centerVertical="true"
-        android:layout_marginEnd="@dimen/margin_between_elements">
-
-        <ImageView
-            android:id="@+id/password_protected_image_view"
-            android:layout_width="16dp"
-            android:layout_height="16dp"
-            android:layout_gravity="bottom|end"
-            android:contentDescription="@string/password_protected"
-            android:src="@drawable/ic_lock_white_24px"
-            android:visibility="visible" />
-
-        <com.facebook.drawee.view.SimpleDraweeView
-            android:id="@+id/avatar_image"
-            android:layout_width="@dimen/small_item_height"
-            android:layout_height="@dimen/small_item_height"
-            app:roundAsCircle="true" />
-
-    </FrameLayout>
-
-    <ImageButton
-        android:id="@+id/more_menu"
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/small_item_height"
-        android:layout_alignParentEnd="true"
-        android:layout_centerVertical="true"
-        android:layout_marginStart="@dimen/standard_margin"
-        android:background="?android:attr/selectableItemBackground"
-        android:contentDescription="@null"
-        android:scaleType="center"
-        android:src="@drawable/ic_more_horiz_black_24dp" />
-
-    <LinearLayout
-        android:id="@+id/linear_layout"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_centerInParent="true"
-        android:layout_toStartOf="@+id/more_menu"
-        android:layout_toEndOf="@id/frame_layout"
-        android:orientation="vertical">
-
-        <androidx.emoji.widget.EmojiTextView
-            android:id="@+id/name_text"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:ellipsize="middle"
-            android:singleLine="true"
-            android:textAlignment="viewStart"
-            android:textColor="@color/conversation_item_header"
-            android:textSize="@dimen/two_line_primary_text_size"
-            tools:text="Call item text" />
-
-        <androidx.emoji.widget.EmojiTextView
-            android:id="@+id/secondary_text"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:singleLine="true"
-            android:textAlignment="viewStart"
-            android:textColor="@color/textColorMaxContrast"
-            android:textSize="14sp"
-            tools:text="A week ago" />
-
-    </LinearLayout>
-
-</RelativeLayout>

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -273,6 +273,8 @@
 
     <!-- Conversations List-->
     <string name="nc_new_mention">Unread mentions</string>
+    <string name="conversations">Conversations</string>
+    <string name="openConversations">Open conversations</string>
 
     <!-- Chat -->
     <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_error_deleting_picture">Error taking picture</string>
     <string name="take_photo_permission">Taking a photo is not possible without permissions</string>
+
 </resources>

+ 1 - 1
scripts/analysis/findbugs-results.txt

@@ -1 +1 @@
-558
+554

+ 1 - 1
scripts/analysis/lint-results.txt

@@ -1,2 +1,2 @@
 DO NOT TOUCH; GENERATED BY DRONE
-      <span class="mdl-layout-title">Lint Report: 1 error and 224 warnings</span>
+      <span class="mdl-layout-title">Lint Report: 1 error and 222 warnings</span>