Bläddra i källkod

Merge pull request #1285 from nextcloud/bug/992/fixDesignWhenKeyboardShown

fix design when keyboard is shown
Marcel Hibbe 3 år sedan
förälder
incheckning
db1071d2ee
34 ändrade filer med 1362 tillägg och 1225 borttagningar
  1. 0 1
      app/build.gradle
  2. 3 1
      app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt
  3. 3 1
      app/src/main/AndroidManifest.xml
  4. 18 6
      app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt
  5. 12 7
      app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt
  6. 264 303
      app/src/main/java/com/nextcloud/talk/controllers/ContactsController.java
  7. 22 2
      app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt
  8. 33 88
      app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java
  9. 3 1
      app/src/main/java/com/nextcloud/talk/controllers/base/ButterKnifeController.kt
  10. 0 356
      app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/CallMenuController.java
  11. 37 0
      app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/ConversationOperationEnum.kt
  12. 41 30
      app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/EntryMenuController.java
  13. 35 42
      app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java
  14. 0 116
      app/src/main/java/com/nextcloud/talk/events/BottomSheetLockEvent.java
  15. 23 0
      app/src/main/java/com/nextcloud/talk/events/ConversationsListFetchDataEvent.kt
  16. 52 0
      app/src/main/java/com/nextcloud/talk/events/OpenConversationEvent.kt
  17. 5 1
      app/src/main/java/com/nextcloud/talk/jobs/LeaveConversationWorker.java
  18. 4 15
      app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.java
  19. 2 0
      app/src/main/java/com/nextcloud/talk/ui/bottom/sheet/ProfileBottomSheet.kt
  20. 82 0
      app/src/main/java/com/nextcloud/talk/ui/dialog/ContactsBottomDialog.kt
  21. 318 0
      app/src/main/java/com/nextcloud/talk/ui/dialog/ConversationsListBottomDialog.kt
  22. 0 92
      app/src/main/java/com/nextcloud/talk/utils/KeyboardUtils.java
  23. 2 57
      app/src/main/java/com/nextcloud/talk/utils/ShareUtils.java
  24. 0 49
      app/src/main/res/drawable/ic_mimetype_folder_public.xml
  25. 0 34
      app/src/main/res/drawable/ic_mimetype_folder_starred.xml
  26. 3 1
      app/src/main/res/layout/controller_entry_menu.xml
  27. 2 2
      app/src/main/res/layout/controller_operations_menu.xml
  28. 13 14
      app/src/main/res/layout/dialog_bottom_contacts.xml
  29. 376 0
      app/src/main/res/layout/dialog_conversation_operations.xml
  30. 1 0
      app/src/main/res/values/dimens.xml
  31. 0 3
      app/src/main/res/values/strings.xml
  32. 6 1
      app/src/main/res/values/styles.xml
  33. 1 1
      scripts/analysis/findbugs-results.txt
  34. 1 1
      scripts/analysis/lint-results.txt

+ 0 - 1
app/build.gradle

@@ -266,7 +266,6 @@ dependencies {
 
     implementation 'com.novoda:merlin:1.2.1'
 
-    implementation 'com.github.Kennyc1012:BottomSheet:2.4.1'
     implementation 'com.github.nextcloud:PopupBubble:1.0.6'
     implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
 

+ 3 - 1
app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt

@@ -304,7 +304,9 @@ class MagicFirebaseMessagingService : FirebaseMessagingService() {
                     }
                 }
 
-                override fun onError(e: Throwable) {}
+                override fun onError(e: Throwable) {
+                    // unused atm
+                }
                 override fun onComplete() {
                     stopForeground(true)
                     handler.removeCallbacksAndMessages(null)

+ 3 - 1
app/src/main/AndroidManifest.xml

@@ -2,7 +2,9 @@
   ~ Nextcloud Talk application
   ~
   ~ @author Mario Danic
+  ~ @author Marcel Hibbe
   ~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
+  ~ Copyright (C) 2021-2022 Marcel Hibbe <dev@mhibbe.de>
   ~
   ~ 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
@@ -97,7 +99,7 @@
         <activity
             android:name=".activities.MainActivity"
             android:label="@string/nc_app_name"
-            android:windowSoftInputMode="adjustPan">
+            android:windowSoftInputMode="adjustResize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />

+ 18 - 6
app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt

@@ -246,7 +246,9 @@ class MainActivity : BaseActivity(), ActionBarProvider {
             .subscribeOn(Schedulers.io())
             .observeOn(AndroidSchedulers.mainThread())
             .subscribe(object : Observer<RoomOverall> {
-                override fun onSubscribe(d: Disposable) {}
+                override fun onSubscribe(d: Disposable) {
+                    // unused atm
+                }
                 override fun onNext(roomOverall: RoomOverall) {
                     val bundle = Bundle()
                     bundle.putParcelable(KEY_USER_ENTITY, currentUser)
@@ -265,7 +267,9 @@ class MainActivity : BaseActivity(), ActionBarProvider {
                         .subscribeOn(Schedulers.io())
                         .observeOn(AndroidSchedulers.mainThread())
                         .subscribe(object : Observer<RoomOverall> {
-                            override fun onSubscribe(d: Disposable) {}
+                            override fun onSubscribe(d: Disposable) {
+                                // unused atm
+                            }
                             override fun onNext(roomOverall: RoomOverall) {
                                 bundle.putParcelable(
                                     KEY_ACTIVE_CONVERSATION,
@@ -277,13 +281,21 @@ class MainActivity : BaseActivity(), ActionBarProvider {
                                 )
                             }
 
-                            override fun onError(e: Throwable) {}
-                            override fun onComplete() {}
+                            override fun onError(e: Throwable) {
+                                // unused atm
+                            }
+                            override fun onComplete() {
+                                // unused atm
+                            }
                         })
                 }
 
-                override fun onError(e: Throwable) {}
-                override fun onComplete() {}
+                override fun onError(e: Throwable) {
+                    // unused atm
+                }
+                override fun onComplete() {
+                    // unused atm
+                }
             })
     }
 

+ 12 - 7
app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt

@@ -149,7 +149,6 @@ import com.nextcloud.talk.utils.ContactUtils
 import com.nextcloud.talk.utils.DateUtils
 import com.nextcloud.talk.utils.DisplayUtils
 import com.nextcloud.talk.utils.ImageEmojiEditText
-import com.nextcloud.talk.utils.KeyboardUtils
 import com.nextcloud.talk.utils.MagicCharPolicy
 import com.nextcloud.talk.utils.NotificationUtils
 import com.nextcloud.talk.utils.UriUtils
@@ -347,6 +346,7 @@ class ChatController(args: Bundle) :
                     }
 
                     override fun onError(e: Throwable) {
+                        // unused atm
                     }
 
                     override fun onComplete() {
@@ -389,9 +389,11 @@ class ChatController(args: Bundle) :
                 }
 
                 override fun onError(e: Throwable) {
+                    // unused atm
                 }
 
                 override fun onComplete() {
+                    // unused atm
                 }
             })
     }
@@ -666,6 +668,7 @@ class ChatController(args: Bundle) :
             }
 
             override fun afterTextChanged(s: Editable) {
+                // unused atm
             }
         })
 
@@ -1262,7 +1265,9 @@ class ChatController(args: Bundle) :
                             UploadAndShareFilesWorker.requestStoragePermission(this)
                         }
                     }
-                    .setNegativeButton(R.string.nc_no) {}
+                    .setNegativeButton(R.string.nc_no) {
+                        // unused atm
+                    }
                     .show()
             } catch (e: IllegalStateException) {
                 Toast.makeText(context, context?.resources?.getString(R.string.nc_upload_failed), Toast.LENGTH_LONG)
@@ -1541,10 +1546,6 @@ class ChatController(args: Bundle) :
             cancelReply()
         }
 
-        if (activity != null) {
-            KeyboardUtils(activity, getView(), false)
-        }
-
         cancelNotificationsForCurrentConversation()
 
         if (inConversation) {
@@ -1698,9 +1699,11 @@ class ChatController(args: Bundle) :
                     }
 
                     override fun onError(e: Throwable) {
+                        // unused atm
                     }
 
                     override fun onComplete() {
+                        // unused atm
                     }
                 })
         } else {
@@ -1764,7 +1767,9 @@ class ChatController(args: Bundle) :
                     }
                 }
 
-                override fun onError(e: Throwable) {}
+                override fun onError(e: Throwable) {
+                    // unused atm
+                }
 
                 override fun onComplete() {
                     dispose()

+ 264 - 303
app/src/main/java/com/nextcloud/talk/controllers/ContactsController.java

@@ -2,7 +2,9 @@
  * Nextcloud Talk application
  *
  * @author Mario Danic
- * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
+ * @author Marcel Hibbe
+ * Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
+ * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
  *
  * 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
@@ -20,12 +22,14 @@
 
 package com.nextcloud.talk.controllers;
 
+import static com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_INVITE_USERS;
+import static com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_GET_AND_JOIN_ROOM;
+
 import android.app.SearchManager;
 import android.content.Context;
 import android.graphics.PorterDuff;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Handler;
 import android.text.InputType;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -39,19 +43,14 @@ import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
 
-import com.bluelinelabs.conductor.RouterTransaction;
-import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler;
 import com.bluelinelabs.logansquare.LoganSquare;
-import com.kennyc.bottomsheet.BottomSheet;
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.adapters.items.GenericTextHeaderItem;
 import com.nextcloud.talk.adapters.items.UserItem;
 import com.nextcloud.talk.api.NcApi;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.controllers.base.BaseController;
-import com.nextcloud.talk.controllers.bottomsheet.EntryMenuController;
-import com.nextcloud.talk.controllers.bottomsheet.OperationsMenuController;
-import com.nextcloud.talk.events.BottomSheetLockEvent;
+import com.nextcloud.talk.events.OpenConversationEvent;
 import com.nextcloud.talk.jobs.AddParticipantsToConversation;
 import com.nextcloud.talk.models.RetrofitBucket;
 import com.nextcloud.talk.models.database.CapabilitiesUtil;
@@ -62,9 +61,9 @@ import com.nextcloud.talk.models.json.conversations.Conversation;
 import com.nextcloud.talk.models.json.conversations.RoomOverall;
 import com.nextcloud.talk.models.json.converters.EnumActorTypeConverter;
 import com.nextcloud.talk.models.json.participants.Participant;
+import com.nextcloud.talk.ui.dialog.ContactsBottomDialog;
 import com.nextcloud.talk.utils.ApiUtils;
 import com.nextcloud.talk.utils.ConductorRemapping;
-import com.nextcloud.talk.utils.KeyboardUtils;
 import com.nextcloud.talk.utils.bundle.BundleKeys;
 import com.nextcloud.talk.utils.database.user.UserUtils;
 import com.nextcloud.talk.utils.preferences.AppPreferences;
@@ -113,7 +112,7 @@ import okhttp3.ResponseBody;
 
 @AutoInjector(NextcloudTalkApplication.class)
 public class ContactsController extends BaseController implements SearchView.OnQueryTextListener,
-        FlexibleAdapter.OnItemClickListener {
+    FlexibleAdapter.OnItemClickListener {
 
     public static final String TAG = "ContactsController";
 
@@ -167,8 +166,6 @@ public class ContactsController extends BaseController implements SearchView.OnQ
     private Disposable cacheQueryDisposable;
     private FlexibleAdapter adapter;
     private List<AbstractFlexibleItem> contactItems;
-    private BottomSheet bottomSheet;
-    private View view;
 
     private SmoothScrollLinearLayoutManager layoutManager;
 
@@ -192,6 +189,8 @@ public class ContactsController extends BaseController implements SearchView.OnQ
     private boolean isAddingParticipantsView;
     private String conversationToken;
 
+    private ContactsBottomDialog contactsBottomDialog;
+
     public ContactsController() {
         super();
         setHasOptionsMenu(true);
@@ -266,12 +265,12 @@ public class ContactsController extends BaseController implements SearchView.OnQ
 
     private void setupAdapter() {
         adapter.setNotifyChangeOfUnfilteredItems(true)
-                .setMode(SelectableAdapter.Mode.MULTI);
+            .setMode(SelectableAdapter.Mode.MULTI);
 
         adapter.setStickyHeaderElevation(5)
-                .setUnlinkAllItemsOnRemoveHeaders(true)
-                .setDisplayHeadersAtStartUp(true)
-                .setStickyHeaders(true);
+            .setUnlinkAllItemsOnRemoveHeaders(true)
+            .setDisplayHeadersAtStartUp(true)
+            .setStickyHeaders(true);
 
         adapter.addListener(this);
     }
@@ -294,7 +293,7 @@ public class ContactsController extends BaseController implements SearchView.OnQ
                     userId = selectedUserIds.iterator().next();
                 }
 
-                int apiVersion = ApiUtils.getConversationApiVersion(currentUser, new int[] {ApiUtils.APIv4, 1});
+                int apiVersion = ApiUtils.getConversationApiVersion(currentUser, new int[]{ApiUtils.APIv4, 1});
                 RetrofitBucket retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(apiVersion,
                                                                                         currentUser.getBaseUrl(),
                                                                                         roomType,
@@ -302,65 +301,65 @@ public class ContactsController extends BaseController implements SearchView.OnQ
                                                                                         userId,
                                                                                         null);
                 ncApi.createRoom(credentials,
-                        retrofitBucket.getUrl(), retrofitBucket.getQueryMap())
-                        .subscribeOn(Schedulers.io())
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .subscribe(new Observer<RoomOverall>() {
-                            @Override
-                            public void onSubscribe(Disposable d) {
+                                 retrofitBucket.getUrl(), retrofitBucket.getQueryMap())
+                    .subscribeOn(Schedulers.io())
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .subscribe(new Observer<RoomOverall>() {
+                        @Override
+                        public void onSubscribe(Disposable d) {
 
-                            }
+                        }
 
-                            @Override
-                            public void onNext(RoomOverall roomOverall) {
-                                Bundle bundle = new Bundle();
-                                bundle.putParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY(), currentUser);
-                                bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), roomOverall.getOcs().getData().getToken());
-                                bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), roomOverall.getOcs().getData().getRoomId());
+                        @Override
+                        public void onNext(RoomOverall roomOverall) {
+                            Bundle bundle = new Bundle();
+                            bundle.putParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY(), currentUser);
+                            bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), roomOverall.getOcs().getData().getToken());
+                            bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), roomOverall.getOcs().getData().getRoomId());
 
-                                // FIXME once APIv2 or later is used only, the createRoom already returns all the data
-                                ncApi.getRoom(credentials,
-                                              ApiUtils.getUrlForRoom(apiVersion, currentUser.getBaseUrl(),
-                                                                     roomOverall.getOcs().getData().getToken()))
-                                        .subscribeOn(Schedulers.io())
-                                        .observeOn(AndroidSchedulers.mainThread())
-                                        .subscribe(new Observer<RoomOverall>() {
+                            // FIXME once APIv2 or later is used only, the createRoom already returns all the data
+                            ncApi.getRoom(credentials,
+                                          ApiUtils.getUrlForRoom(apiVersion, currentUser.getBaseUrl(),
+                                                                 roomOverall.getOcs().getData().getToken()))
+                                .subscribeOn(Schedulers.io())
+                                .observeOn(AndroidSchedulers.mainThread())
+                                .subscribe(new Observer<RoomOverall>() {
 
-                                            @Override
-                                            public void onSubscribe(Disposable d) {
+                                    @Override
+                                    public void onSubscribe(Disposable d) {
 
-                                            }
+                                    }
 
-                                            @Override
-                                            public void onNext(RoomOverall roomOverall) {
-                                                bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ACTIVE_CONVERSATION(),
-                                                                     Parcels.wrap(roomOverall.getOcs().getData()));
+                                    @Override
+                                    public void onNext(RoomOverall roomOverall) {
+                                        bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ACTIVE_CONVERSATION(),
+                                                             Parcels.wrap(roomOverall.getOcs().getData()));
 
-                                                ConductorRemapping.INSTANCE.remapChatController(getRouter(), currentUser.getId(),
-                                                                                                roomOverall.getOcs().getData().getToken(), bundle, true);
-                                            }
+                                        ConductorRemapping.INSTANCE.remapChatController(getRouter(), currentUser.getId(),
+                                                                                        roomOverall.getOcs().getData().getToken(), bundle, true);
+                                    }
 
-                                            @Override
-                                            public void onError(Throwable e) {
+                                    @Override
+                                    public void onError(Throwable e) {
 
-                                            }
+                                    }
 
-                                            @Override
-                                            public void onComplete() {
+                                    @Override
+                                    public void onComplete() {
 
-                                            }
-                                        });
-                            }
+                                    }
+                                });
+                        }
 
-                            @Override
-                            public void onError(Throwable e) {
+                        @Override
+                        public void onError(Throwable e) {
 
-                            }
+                        }
 
-                            @Override
-                            public void onComplete() {
-                            }
-                        });
+                        @Override
+                        public void onComplete() {
+                        }
+                    });
             } else {
 
                 Bundle bundle = new Bundle();
@@ -382,8 +381,8 @@ public class ContactsController extends BaseController implements SearchView.OnQ
                 bundle.putStringArrayList(BundleKeys.INSTANCE.getKEY_INVITED_GROUP(), groupIdsArray);
                 bundle.putStringArrayList(BundleKeys.INSTANCE.getKEY_INVITED_EMAIL(), emailsArray);
                 bundle.putStringArrayList(BundleKeys.INSTANCE.getKEY_INVITED_CIRCLE(), circleIdsArray);
-                bundle.putInt(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), 11);
-                prepareAndShowBottomSheetWithBundle(bundle, true);
+                bundle.putSerializable(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), OPS_CODE_INVITE_USERS);
+                prepareAndShowBottomSheetWithBundle(bundle);
             }
         } else {
             String[] userIdsArray = selectedUserIds.toArray(new String[selectedUserIds.size()]);
@@ -400,7 +399,7 @@ public class ContactsController extends BaseController implements SearchView.OnQ
             data.putStringArray(BundleKeys.INSTANCE.getKEY_SELECTED_CIRCLES(), circleIdsArray);
 
             OneTimeWorkRequest addParticipantsToConversationWorker =
-                    new OneTimeWorkRequest.Builder(AddParticipantsToConversation.class).setInputData(data.build()).build();
+                new OneTimeWorkRequest.Builder(AddParticipantsToConversation.class).setInputData(data.build()).build();
             WorkManager.getInstance().enqueue(addParticipantsToConversationWorker);
 
             getRouter().popCurrentController();
@@ -500,186 +499,186 @@ public class ContactsController extends BaseController implements SearchView.OnQ
         modifiedQueryMap.put("shareTypes[]", shareTypesList);
 
         ncApi.getContactsWithSearchParam(
-                credentials,
-                retrofitBucket.getUrl(), shareTypesList, modifiedQueryMap)
-                .subscribeOn(Schedulers.io())
-                .observeOn(AndroidSchedulers.mainThread())
-                .retry(3)
-                .subscribe(new Observer<ResponseBody>() {
-                    @Override
-                    public void onSubscribe(Disposable d) {
-                        contactsQueryDisposable = d;
-                    }
+            credentials,
+            retrofitBucket.getUrl(), shareTypesList, modifiedQueryMap)
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+            .retry(3)
+            .subscribe(new Observer<ResponseBody>() {
+                @Override
+                public void onSubscribe(Disposable d) {
+                    contactsQueryDisposable = d;
+                }
 
-                    @Override
-                    public void onNext(ResponseBody responseBody) {
-                        if (responseBody != null) {
-                            Participant participant;
-
-                            List<AbstractFlexibleItem> newUserItemList = new ArrayList<>();
-                            EnumActorTypeConverter actorTypeConverter = new EnumActorTypeConverter();
-
-                            try {
-                                AutocompleteOverall autocompleteOverall = LoganSquare.parse(
-                                        responseBody.string(),
-                                        AutocompleteOverall.class);
-                                autocompleteUsersHashSet.addAll(autocompleteOverall.getOcs().getData());
-
-                                for (AutocompleteUser autocompleteUser : autocompleteUsersHashSet) {
-                                    if (!autocompleteUser.getId().equals(currentUser.getUserId())
-                                            && !existingParticipants.contains(autocompleteUser.getId())) {
-                                        participant = new Participant();
-                                        participant.setActorId(autocompleteUser.getId());
-                                        participant.setActorType(actorTypeConverter.getFromString(autocompleteUser.getSource()));
-                                        participant.setDisplayName(autocompleteUser.getLabel());
-                                        participant.setSource(autocompleteUser.getSource());
-
-                                        String headerTitle;
-
-                                        if (participant.getActorType() == Participant.ActorType.GROUPS) {
-                                            headerTitle = getResources().getString(R.string.nc_groups);
-                                        } else if (participant.getActorType() == Participant.ActorType.CIRCLES) {
-                                            headerTitle = getResources().getString(R.string.nc_circles);
-                                        } else {
-                                            headerTitle =
-                                                participant.getDisplayName().substring(0, 1).toUpperCase(Locale.getDefault());
-                                        }
-
-                                        GenericTextHeaderItem genericTextHeaderItem;
-                                        if (!userHeaderItems.containsKey(headerTitle)) {
-                                            genericTextHeaderItem = new GenericTextHeaderItem(headerTitle);
-                                            userHeaderItems.put(headerTitle, genericTextHeaderItem);
-                                        }
-
-                                        UserItem newContactItem = new UserItem(
-                                                participant,
-                                                currentUser,
-                                                userHeaderItems.get(headerTitle)
-                                        );
-
-                                        if (!contactItems.contains(newContactItem)) {
-                                            newUserItemList.add(newContactItem);
-                                        }
+                @Override
+                public void onNext(ResponseBody responseBody) {
+                    if (responseBody != null) {
+                        Participant participant;
+
+                        List<AbstractFlexibleItem> newUserItemList = new ArrayList<>();
+                        EnumActorTypeConverter actorTypeConverter = new EnumActorTypeConverter();
+
+                        try {
+                            AutocompleteOverall autocompleteOverall = LoganSquare.parse(
+                                responseBody.string(),
+                                AutocompleteOverall.class);
+                            autocompleteUsersHashSet.addAll(autocompleteOverall.getOcs().getData());
+
+                            for (AutocompleteUser autocompleteUser : autocompleteUsersHashSet) {
+                                if (!autocompleteUser.getId().equals(currentUser.getUserId())
+                                    && !existingParticipants.contains(autocompleteUser.getId())) {
+                                    participant = new Participant();
+                                    participant.setActorId(autocompleteUser.getId());
+                                    participant.setActorType(actorTypeConverter.getFromString(autocompleteUser.getSource()));
+                                    participant.setDisplayName(autocompleteUser.getLabel());
+                                    participant.setSource(autocompleteUser.getSource());
+
+                                    String headerTitle;
+
+                                    if (participant.getActorType() == Participant.ActorType.GROUPS) {
+                                        headerTitle = getResources().getString(R.string.nc_groups);
+                                    } else if (participant.getActorType() == Participant.ActorType.CIRCLES) {
+                                        headerTitle = getResources().getString(R.string.nc_circles);
+                                    } else {
+                                        headerTitle =
+                                            participant.getDisplayName().substring(0, 1).toUpperCase(Locale.getDefault());
                                     }
-                                }
-                            } catch (IOException ioe) {
-                                Log.e(TAG, "Parsing response body failed while getting contacts", ioe);
-                            }
 
-                            userHeaderItems = new HashMap<>();
-                            contactItems.addAll(newUserItemList);
+                                    GenericTextHeaderItem genericTextHeaderItem;
+                                    if (!userHeaderItems.containsKey(headerTitle)) {
+                                        genericTextHeaderItem = new GenericTextHeaderItem(headerTitle);
+                                        userHeaderItems.put(headerTitle, genericTextHeaderItem);
+                                    }
 
-                            Collections.sort(newUserItemList, (o1, o2) -> {
-                                String firstName;
-                                String secondName;
+                                    UserItem newContactItem = new UserItem(
+                                        participant,
+                                        currentUser,
+                                        userHeaderItems.get(headerTitle)
+                                    );
 
-                                if (o1 instanceof UserItem) {
-                                    firstName = ((UserItem) o1).getModel().getDisplayName();
-                                } else {
-                                    firstName = ((GenericTextHeaderItem) o1).getModel();
-                                }
-
-                                if (o2 instanceof UserItem) {
-                                    secondName = ((UserItem) o2).getModel().getDisplayName();
-                                } else {
-                                    secondName = ((GenericTextHeaderItem) o2).getModel();
+                                    if (!contactItems.contains(newContactItem)) {
+                                        newUserItemList.add(newContactItem);
+                                    }
                                 }
+                            }
+                        } catch (IOException ioe) {
+                            Log.e(TAG, "Parsing response body failed while getting contacts", ioe);
+                        }
 
-                                if (o1 instanceof UserItem && o2 instanceof UserItem) {
-                                    String firstSource = ((UserItem) o1).getModel().getSource();
-                                    String secondSource = ((UserItem) o2).getModel().getSource();
-                                    if (firstSource.equals(secondSource)) {
-                                        return firstName.compareToIgnoreCase(secondName);
-                                    }
+                        userHeaderItems = new HashMap<>();
+                        contactItems.addAll(newUserItemList);
 
-                                    // First users
-                                    if ("users".equals(firstSource)) {
-                                        return -1;
-                                    } else if ("users".equals(secondSource)) {
-                                        return 1;
-                                    }
+                        Collections.sort(newUserItemList, (o1, o2) -> {
+                            String firstName;
+                            String secondName;
 
-                                    // Then groups
-                                    if ("groups".equals(firstSource)) {
-                                        return -1;
-                                    } else if ("groups".equals(secondSource)) {
-                                        return 1;
-                                    }
+                            if (o1 instanceof UserItem) {
+                                firstName = ((UserItem) o1).getModel().getDisplayName();
+                            } else {
+                                firstName = ((GenericTextHeaderItem) o1).getModel();
+                            }
 
-                                    // Then circles
-                                    if ("circles".equals(firstSource)) {
-                                        return -1;
-                                    } else if ("circles".equals(secondSource)) {
-                                        return 1;
-                                    }
+                            if (o2 instanceof UserItem) {
+                                secondName = ((UserItem) o2).getModel().getDisplayName();
+                            } else {
+                                secondName = ((GenericTextHeaderItem) o2).getModel();
+                            }
 
-                                    // Otherwise fall back to name sorting
+                            if (o1 instanceof UserItem && o2 instanceof UserItem) {
+                                String firstSource = ((UserItem) o1).getModel().getSource();
+                                String secondSource = ((UserItem) o2).getModel().getSource();
+                                if (firstSource.equals(secondSource)) {
                                     return firstName.compareToIgnoreCase(secondName);
                                 }
 
-                                return firstName.compareToIgnoreCase(secondName);
-                            });
-
-                            Collections.sort(contactItems, (o1, o2) -> {
-                                String firstName;
-                                String secondName;
-
-                                if (o1 instanceof UserItem) {
-                                    firstName = ((UserItem) o1).getModel().getDisplayName();
-                                } else {
-                                    firstName = ((GenericTextHeaderItem) o1).getModel();
+                                // First users
+                                if ("users".equals(firstSource)) {
+                                    return -1;
+                                } else if ("users".equals(secondSource)) {
+                                    return 1;
                                 }
 
-                                if (o2 instanceof UserItem) {
-                                    secondName = ((UserItem) o2).getModel().getDisplayName();
-                                } else {
-                                    secondName = ((GenericTextHeaderItem) o2).getModel();
+                                // Then groups
+                                if ("groups".equals(firstSource)) {
+                                    return -1;
+                                } else if ("groups".equals(secondSource)) {
+                                    return 1;
                                 }
 
-                                if (o1 instanceof UserItem && o2 instanceof UserItem) {
-                                    if ("groups".equals(((UserItem) o1).getModel().getSource()) && "groups".equals(((UserItem) o2).getModel().getSource())) {
-                                        return firstName.compareToIgnoreCase(secondName);
-                                    } else if ("groups".equals(((UserItem) o1).getModel().getSource())) {
-                                        return -1;
-                                    } else if ("groups".equals(((UserItem) o2).getModel().getSource())) {
-                                        return 1;
-                                    }
+                                // Then circles
+                                if ("circles".equals(firstSource)) {
+                                    return -1;
+                                } else if ("circles".equals(secondSource)) {
+                                    return 1;
                                 }
 
+                                // Otherwise fall back to name sorting
                                 return firstName.compareToIgnoreCase(secondName);
-                            });
+                            }
+
+                            return firstName.compareToIgnoreCase(secondName);
+                        });
+
+                        Collections.sort(contactItems, (o1, o2) -> {
+                            String firstName;
+                            String secondName;
 
-                            if (newUserItemList.size() > 0) {
-                                adapter.updateDataSet(newUserItemList);
+                            if (o1 instanceof UserItem) {
+                                firstName = ((UserItem) o1).getModel().getDisplayName();
                             } else {
-                                adapter.filterItems();
+                                firstName = ((GenericTextHeaderItem) o1).getModel();
                             }
 
-                            if (swipeRefreshLayout != null) {
-                                swipeRefreshLayout.setRefreshing(false);
+                            if (o2 instanceof UserItem) {
+                                secondName = ((UserItem) o2).getModel().getDisplayName();
+                            } else {
+                                secondName = ((GenericTextHeaderItem) o2).getModel();
                             }
+
+                            if (o1 instanceof UserItem && o2 instanceof UserItem) {
+                                if ("groups".equals(((UserItem) o1).getModel().getSource()) && "groups".equals(((UserItem) o2).getModel().getSource())) {
+                                    return firstName.compareToIgnoreCase(secondName);
+                                } else if ("groups".equals(((UserItem) o1).getModel().getSource())) {
+                                    return -1;
+                                } else if ("groups".equals(((UserItem) o2).getModel().getSource())) {
+                                    return 1;
+                                }
+                            }
+
+                            return firstName.compareToIgnoreCase(secondName);
+                        });
+
+                        if (newUserItemList.size() > 0) {
+                            adapter.updateDataSet(newUserItemList);
+                        } else {
+                            adapter.filterItems();
                         }
-                    }
 
-                    @Override
-                    public void onError(Throwable e) {
                         if (swipeRefreshLayout != null) {
                             swipeRefreshLayout.setRefreshing(false);
                         }
-                        dispose(contactsQueryDisposable);
                     }
+                }
 
-                    @Override
-                    public void onComplete() {
-                        if (swipeRefreshLayout != null) {
-                            swipeRefreshLayout.setRefreshing(false);
-                        }
-                        dispose(contactsQueryDisposable);
-                        alreadyFetching = false;
+                @Override
+                public void onError(Throwable e) {
+                    if (swipeRefreshLayout != null) {
+                        swipeRefreshLayout.setRefreshing(false);
+                    }
+                    dispose(contactsQueryDisposable);
+                }
 
-                        disengageProgressBar();
+                @Override
+                public void onComplete() {
+                    if (swipeRefreshLayout != null) {
+                        swipeRefreshLayout.setRefreshing(false);
                     }
-                });
+                    dispose(contactsQueryDisposable);
+                    alreadyFetching = false;
+
+                    disengageProgressBar();
+                }
+            });
 
     }
 
@@ -694,14 +693,14 @@ public class ContactsController extends BaseController implements SearchView.OnQ
         swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.refresh_spinner_background);
 
         joinConversationViaLinkImageView
-                .getBackground()
-                .setColorFilter(ResourcesCompat.getColor(getResources(), R.color.colorBackgroundDarker, null),
-                                PorterDuff.Mode.SRC_IN);
+            .getBackground()
+            .setColorFilter(ResourcesCompat.getColor(getResources(), R.color.colorBackgroundDarker, null),
+                            PorterDuff.Mode.SRC_IN);
 
         publicCallLinkImageView
-                .getBackground()
-                .setColorFilter(ResourcesCompat.getColor(getResources(), R.color.colorPrimary, null),
-                                PorterDuff.Mode.SRC_IN);
+            .getBackground()
+            .setColorFilter(ResourcesCompat.getColor(getResources(), R.color.colorPrimary, null),
+                            PorterDuff.Mode.SRC_IN);
 
         disengageProgressBar();
     }
@@ -801,59 +800,21 @@ public class ContactsController extends BaseController implements SearchView.OnQ
         }
     }
 
-
-
-    private void prepareAndShowBottomSheetWithBundle(Bundle bundle, boolean showEntrySheet) {
-        if (view == null) {
-            view = getActivity().getLayoutInflater().inflate(R.layout.bottom_sheet, null, false);
-        }
-
-        if (bottomSheet == null) {
-            bottomSheet = new BottomSheet.Builder(getActivity()).setView(view).create();
-        }
-
-        if (showEntrySheet) {
-            getChildRouter((ViewGroup) view).setRoot(
-                    RouterTransaction.with(new EntryMenuController(bundle))
-                            .popChangeHandler(new VerticalChangeHandler())
-                            .pushChangeHandler(new VerticalChangeHandler()));
-        } else {
-            getChildRouter((ViewGroup) view).setRoot(
-                    RouterTransaction.with(new OperationsMenuController(bundle))
-                            .popChangeHandler(new VerticalChangeHandler())
-                            .pushChangeHandler(new VerticalChangeHandler()));
-        }
-
-        bottomSheet.setOnShowListener(dialog -> {
-            if (showEntrySheet) {
-                new KeyboardUtils(getActivity(), bottomSheet.getLayout(), true);
-            } else {
-                eventBus.post(new BottomSheetLockEvent(false, 0,
-                        false, false));
-            }
-        });
-
-        bottomSheet.setOnDismissListener(dialog -> getActionBar().setDisplayHomeAsUpEnabled(getRouter().getBackstackSize() > 1));
-
-        bottomSheet.show();
+    private void prepareAndShowBottomSheetWithBundle(Bundle bundle) {
+        // 11: create conversation-enter name for new conversation
+        // 10: get&join room when enter link
+        contactsBottomDialog = new ContactsBottomDialog(getActivity(), bundle);
+        contactsBottomDialog.show();
     }
 
-    @Subscribe(threadMode = ThreadMode.MAIN)
-    public void onMessageEvent(BottomSheetLockEvent bottomSheetLockEvent) {
-
-        if (bottomSheet != null) {
-            if (!bottomSheetLockEvent.isCancelable()) {
-                bottomSheet.setCancelable(bottomSheetLockEvent.isCancelable());
-            } else {
-                bottomSheet.setCancelable(bottomSheetLockEvent.isCancelable());
-                if (bottomSheet.isShowing() && bottomSheetLockEvent.isCancel()) {
-                    new Handler().postDelayed(() -> {
-                        bottomSheet.setOnCancelListener(null);
-                        bottomSheet.cancel();
 
-                    }, bottomSheetLockEvent.getDelay());
-                }
-            }
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    public void onMessageEvent(OpenConversationEvent openConversationEvent) {
+        ConductorRemapping.INSTANCE.remapChatController(getRouter(), currentUser.getId(),
+                                                        openConversationEvent.getConversation().getToken(),
+                                                        openConversationEvent.getBundle(), true);
+        if (contactsBottomDialog != null) {
+            contactsBottomDialog.dismiss();
         }
     }
 
@@ -874,7 +835,7 @@ public class ContactsController extends BaseController implements SearchView.OnQ
                     roomType = "2";
                 }
 
-                int apiVersion = ApiUtils.getConversationApiVersion(currentUser, new int[] {ApiUtils.APIv4, 1});
+                int apiVersion = ApiUtils.getConversationApiVersion(currentUser, new int[]{ApiUtils.APIv4, 1});
 
                 RetrofitBucket retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(apiVersion,
                                                                                         currentUser.getBaseUrl(),
@@ -884,40 +845,40 @@ public class ContactsController extends BaseController implements SearchView.OnQ
                                                                                         null);
 
                 ncApi.createRoom(credentials,
-                        retrofitBucket.getUrl(), retrofitBucket.getQueryMap())
-                        .subscribeOn(Schedulers.io())
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .subscribe(new Observer<RoomOverall>() {
-                            @Override
-                            public void onSubscribe(Disposable d) {
+                                 retrofitBucket.getUrl(), retrofitBucket.getQueryMap())
+                    .subscribeOn(Schedulers.io())
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .subscribe(new Observer<RoomOverall>() {
+                        @Override
+                        public void onSubscribe(Disposable d) {
 
-                            }
+                        }
 
-                            @Override
-                            public void onNext(RoomOverall roomOverall) {
-                                if (getActivity() != null) {
-                                    Bundle bundle = new Bundle();
-                                    bundle.putParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY(), currentUser);
-                                    bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), roomOverall.getOcs().getData().getToken());
-                                    bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), roomOverall.getOcs().getData().getRoomId());
-                                    bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ACTIVE_CONVERSATION(),
-                                                         Parcels.wrap(roomOverall.getOcs().getData()));
-
-                                    ConductorRemapping.INSTANCE.remapChatController(getRouter(), currentUser.getId(),
-                                                                                    roomOverall.getOcs().getData().getToken(), bundle, true);
-                                }
+                        @Override
+                        public void onNext(RoomOverall roomOverall) {
+                            if (getActivity() != null) {
+                                Bundle bundle = new Bundle();
+                                bundle.putParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY(), currentUser);
+                                bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), roomOverall.getOcs().getData().getToken());
+                                bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), roomOverall.getOcs().getData().getRoomId());
+                                bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ACTIVE_CONVERSATION(),
+                                                     Parcels.wrap(roomOverall.getOcs().getData()));
+
+                                ConductorRemapping.INSTANCE.remapChatController(getRouter(), currentUser.getId(),
+                                                                                roomOverall.getOcs().getData().getToken(), bundle, true);
                             }
+                        }
 
-                            @Override
-                            public void onError(Throwable e) {
+                        @Override
+                        public void onError(Throwable e) {
 
-                            }
+                        }
 
-                            @Override
-                            public void onComplete() {
+                        @Override
+                        public void onComplete() {
 
-                            }
-                        });
+                        }
+                    });
             } else {
                 Participant participant = ((UserItem) adapter.getItem(position)).getModel();
                 participant.setSelected(!participant.isSelected());
@@ -949,17 +910,17 @@ public class ContactsController extends BaseController implements SearchView.OnQ
                 }
 
                 if (CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "last-room-activity")
-                        && !CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "invite-groups-and-mails") &&
-                        "groups".equals(((UserItem) adapter.getItem(position)).getModel().getSource()) &&
-                        participant.isSelected() &&
-                        adapter.getSelectedItemCount() > 1) {
+                    && !CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "invite-groups-and-mails") &&
+                    "groups".equals(((UserItem) adapter.getItem(position)).getModel().getSource()) &&
+                    participant.isSelected() &&
+                    adapter.getSelectedItemCount() > 1) {
                     List<UserItem> currentItems = adapter.getCurrentItems();
                     Participant internalParticipant;
                     for (int i = 0; i < currentItems.size(); i++) {
                         internalParticipant = currentItems.get(i).getModel();
                         if (internalParticipant.getActorId().equals(participant.getActorId()) &&
-                                internalParticipant.getActorType() == Participant.ActorType.GROUPS &&
-                                internalParticipant.isSelected()) {
+                            internalParticipant.getActorType() == Participant.ActorType.GROUPS &&
+                            internalParticipant.isSelected()) {
                             internalParticipant.setSelected(false);
                             selectedGroupIds.remove(internalParticipant.getActorId());
                         }
@@ -978,9 +939,9 @@ public class ContactsController extends BaseController implements SearchView.OnQ
     @OnClick(R.id.joinConversationViaLinkRelativeLayout)
     void joinConversationViaLink() {
         Bundle bundle = new Bundle();
-        bundle.putInt(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), 10);
+        bundle.putSerializable(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), OPS_CODE_GET_AND_JOIN_ROOM);
 
-        prepareAndShowBottomSheetWithBundle(bundle, true);
+        prepareAndShowBottomSheetWithBundle(bundle);
     }
 
     @Optional
@@ -1002,7 +963,7 @@ public class ContactsController extends BaseController implements SearchView.OnQ
                 if (currentItems.get(i) instanceof UserItem) {
                     internalParticipant = ((UserItem) currentItems.get(i)).getModel();
                     if (internalParticipant.getActorType() == Participant.ActorType.GROUPS &&
-                            internalParticipant.isSelected()) {
+                        internalParticipant.isSelected()) {
                         internalParticipant.setSelected(false);
                         selectedGroupIds.remove(internalParticipant.getActorId());
                     }

+ 22 - 2
app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt

@@ -287,15 +287,19 @@ class ConversationInfoController(args: Bundle) :
             ?.observeOn(AndroidSchedulers.mainThread())
             ?.subscribe(object : Observer<GenericOverall> {
                 override fun onComplete() {
+                    // unused atm
                 }
 
                 override fun onSubscribe(d: Disposable) {
+                    // unused atm
                 }
 
                 override fun onNext(t: GenericOverall) {
+                    // unused atm
                 }
 
                 override fun onError(e: Throwable) {
+                    // unused atm
                 }
             })
     }
@@ -305,6 +309,7 @@ class ConversationInfoController(args: Bundle) :
             ID_DELETE_CONVERSATION_DIALOG -> showDeleteConversationDialog(savedInstanceState)
             ID_CLEAR_CHAT_DIALOG -> showClearHistoryDialog(savedInstanceState)
             else -> {
+                // unused atm
             }
         }
     }
@@ -444,6 +449,7 @@ class ConversationInfoController(args: Bundle) :
                 }
 
                 override fun onError(e: Throwable) {
+                    // unused atm
                 }
 
                 override fun onComplete() {
@@ -525,6 +531,7 @@ class ConversationInfoController(args: Bundle) :
             ?.observeOn(AndroidSchedulers.mainThread())
             ?.subscribe(object : Observer<GenericOverall> {
                 override fun onSubscribe(d: Disposable) {
+                    // unused atm
                 }
 
                 override fun onNext(genericOverall: GenericOverall) {
@@ -538,6 +545,7 @@ class ConversationInfoController(args: Bundle) :
                 }
 
                 override fun onComplete() {
+                    // unused atm
                 }
             })
     }
@@ -598,13 +606,13 @@ class ConversationInfoController(args: Bundle) :
 
                             setupWebinaryView()
 
-                            if (!conversation!!.canLeave(conversationUser)) {
+                            if (!conversation!!.canLeave()) {
                                 binding.leaveConversationAction.visibility = View.GONE
                             } else {
                                 binding.leaveConversationAction.visibility = View.VISIBLE
                             }
 
-                            if (!conversation!!.canDelete(conversationUser)) {
+                            if (!conversation!!.canDelete()) {
                                 binding.deleteConversationAction.visibility = View.GONE
                             } else {
                                 binding.deleteConversationAction.visibility = View.VISIBLE
@@ -647,6 +655,7 @@ class ConversationInfoController(args: Bundle) :
                 }
 
                 override fun onError(e: Throwable) {
+                    // unused atm
                 }
 
                 override fun onComplete() {
@@ -731,6 +740,7 @@ class ConversationInfoController(args: Bundle) :
             }
 
             else -> {
+                // unused atm
             }
         }
     }
@@ -738,6 +748,7 @@ class ConversationInfoController(args: Bundle) :
     private fun toggleModeratorStatus(apiVersion: Int, participant: Participant) {
         val subscriber = object : Observer<GenericOverall> {
             override fun onSubscribe(d: Disposable) {
+                // unused atm
             }
 
             override fun onNext(genericOverall: GenericOverall) {
@@ -750,6 +761,7 @@ class ConversationInfoController(args: Bundle) :
             }
 
             override fun onComplete() {
+                // unused atm
             }
         }
 
@@ -789,6 +801,7 @@ class ConversationInfoController(args: Bundle) :
     private fun toggleModeratorStatusLegacy(apiVersion: Int, participant: Participant) {
         val subscriber = object : Observer<GenericOverall> {
             override fun onSubscribe(d: Disposable) {
+                // unused atm
             }
 
             override fun onNext(genericOverall: GenericOverall) {
@@ -801,6 +814,7 @@ class ConversationInfoController(args: Bundle) :
             }
 
             override fun onComplete() {
+                // unused atm
             }
         }
 
@@ -848,6 +862,7 @@ class ConversationInfoController(args: Bundle) :
                 ?.observeOn(AndroidSchedulers.mainThread())
                 ?.subscribe(object : Observer<GenericOverall> {
                     override fun onSubscribe(d: Disposable) {
+                        // unused atm
                     }
 
                     override fun onNext(genericOverall: GenericOverall) {
@@ -860,6 +875,7 @@ class ConversationInfoController(args: Bundle) :
                     }
 
                     override fun onComplete() {
+                        // unused atm
                     }
                 })
         } else {
@@ -879,6 +895,7 @@ class ConversationInfoController(args: Bundle) :
                     ?.observeOn(AndroidSchedulers.mainThread())
                     ?.subscribe(object : Observer<GenericOverall> {
                         override fun onSubscribe(d: Disposable) {
+                            // unused atm
                         }
 
                         override fun onNext(genericOverall: GenericOverall) {
@@ -891,6 +908,7 @@ class ConversationInfoController(args: Bundle) :
                         }
 
                         override fun onComplete() {
+                            // unused atm
                         }
                     })
             } else {
@@ -907,6 +925,7 @@ class ConversationInfoController(args: Bundle) :
                     ?.observeOn(AndroidSchedulers.mainThread())
                     ?.subscribe(object : Observer<GenericOverall> {
                         override fun onSubscribe(d: Disposable) {
+                            // unused atm
                         }
 
                         override fun onNext(genericOverall: GenericOverall) {
@@ -919,6 +938,7 @@ class ConversationInfoController(args: Bundle) :
                         }
 
                         override fun onComplete() {
+                            // unused atm
                         }
                     })
             }

+ 33 - 88
app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java

@@ -3,8 +3,10 @@
  *
  * @author Mario Danic
  * @author Andy Scherzinger
+ * @author Marcel Hibbe
  * Copyright (C) 2021 Andy Scherzinger (info@andy-scherzinger.de)
  * Copyright (C) 2017-2020 Mario Danic (mario@lovelyhq.com)
+ * Copyright (C) 2022 Marcel Hibbe (dev@mhibbe.de)
  *
  * 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
@@ -62,7 +64,7 @@ import com.facebook.imagepipeline.image.CloseableImage;
 import com.facebook.imagepipeline.request.ImageRequest;
 import com.google.android.material.button.MaterialButton;
 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.ConversationItem;
@@ -70,11 +72,10 @@ 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;
-import com.nextcloud.talk.controllers.bottomsheet.CallMenuController;
+import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum;
 import com.nextcloud.talk.controllers.bottomsheet.EntryMenuController;
-import com.nextcloud.talk.events.BottomSheetLockEvent;
+import com.nextcloud.talk.events.ConversationsListFetchDataEvent;
 import com.nextcloud.talk.events.EventStatus;
-import com.nextcloud.talk.events.MoreMenuClickEvent;
 import com.nextcloud.talk.interfaces.ConversationMenuInterface;
 import com.nextcloud.talk.jobs.AccountRemovalWorker;
 import com.nextcloud.talk.jobs.ContactAddressBookWorker;
@@ -85,11 +86,11 @@ import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.json.conversations.Conversation;
 import com.nextcloud.talk.models.json.participants.Participant;
 import com.nextcloud.talk.ui.dialog.ChooseAccountDialogFragment;
+import com.nextcloud.talk.ui.dialog.ConversationsListBottomDialog;
 import com.nextcloud.talk.utils.ApiUtils;
 import com.nextcloud.talk.utils.ClosedInterfaceImpl;
 import com.nextcloud.talk.utils.ConductorRemapping;
 import com.nextcloud.talk.utils.DisplayUtils;
-import com.nextcloud.talk.utils.KeyboardUtils;
 import com.nextcloud.talk.utils.UriUtils;
 import com.nextcloud.talk.utils.bundle.BundleKeys;
 import com.nextcloud.talk.utils.database.user.UserUtils;
@@ -186,7 +187,6 @@ public class ConversationsListController extends BaseController implements Searc
     private List<AbstractFlexibleItem> conversationItemsWithHeader = new ArrayList<>();
     private final List<AbstractFlexibleItem> searchableConversationItems = new ArrayList<>();
 
-    private BottomSheet bottomSheet;
     private MenuItem searchItem;
     private SearchView searchView;
     private String searchQuery;
@@ -218,6 +218,8 @@ public class ConversationsListController extends BaseController implements Searc
 
     private HashMap<String, GenericTextHeaderItem> callHeaderItems = new HashMap<>();
 
+    private ConversationsListBottomDialog conversationsListBottomDialog;
+
     public ConversationsListController(Bundle bundle) {
         super();
         setHasOptionsMenu(true);
@@ -310,7 +312,7 @@ public class ConversationsListController extends BaseController implements Searc
             if (getActivity() != null && getActivity() instanceof MainActivity) {
                 loadUserAvatar(((MainActivity) getActivity()).binding.switchAccountButton);
             }
-            fetchData(false);
+            fetchData();
         }
     }
 
@@ -453,6 +455,7 @@ public class ConversationsListController extends BaseController implements Searc
         return false;
     }
 
+    @Override
     protected void showSearchOrToolbar() {
         if (TextUtils.isEmpty(searchQuery)) {
             super.showSearchOrToolbar();
@@ -469,7 +472,7 @@ public class ConversationsListController extends BaseController implements Searc
     }
 
     @SuppressLint("LongLogTag")
-    private void fetchData(boolean fromBottomSheet) {
+    public void fetchData() {
         dispose(null);
 
         isRefreshing = true;
@@ -565,15 +568,6 @@ public class ConversationsListController extends BaseController implements Searc
                         swipeRefreshLayout.setRefreshing(false);
                     }
 
-                    if (fromBottomSheet) {
-                        new Handler().postDelayed(() -> {
-                            bottomSheet.setCancelable(true);
-                            if (bottomSheet.isShowing()) {
-                                bottomSheet.cancel();
-                            }
-                        }, 2500);
-                    }
-
                     isRefreshing = false;
                 });
     }
@@ -678,7 +672,7 @@ public class ConversationsListController extends BaseController implements Searc
             return false;
         });
 
-        swipeRefreshLayout.setOnRefreshListener(() -> fetchData(false));
+        swipeRefreshLayout.setOnRefreshListener(() -> fetchData());
         swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary);
         swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.refresh_spinner_background);
 
@@ -811,60 +805,6 @@ public class ConversationsListController extends BaseController implements Searc
         return onQueryTextChange(query);
     }
 
-    @Subscribe(threadMode = ThreadMode.MAIN)
-    public void onMessageEvent(BottomSheetLockEvent bottomSheetLockEvent) {
-        if (bottomSheet != null) {
-            if (!bottomSheetLockEvent.isCancelable()) {
-                bottomSheet.setCancelable(bottomSheetLockEvent.isCancelable());
-            } else {
-                if (bottomSheetLockEvent.getDelay() != 0 && bottomSheetLockEvent.isShouldRefreshData()) {
-                    fetchData(true);
-                } else {
-                    bottomSheet.setCancelable(bottomSheetLockEvent.isCancelable());
-                    if (bottomSheet.isShowing() && bottomSheetLockEvent.isCancel()) {
-                        bottomSheet.cancel();
-                    }
-                }
-            }
-        }
-    }
-
-    @Subscribe(threadMode = ThreadMode.MAIN)
-    public void onMessageEvent(MoreMenuClickEvent moreMenuClickEvent) {
-        Bundle bundle = new Bundle();
-        Conversation conversation = moreMenuClickEvent.getConversation();
-        bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ROOM(), Parcels.wrap(conversation));
-        bundle.putParcelable(BundleKeys.INSTANCE.getKEY_MENU_TYPE(), Parcels.wrap(CallMenuController.MenuType.REGULAR));
-
-        prepareAndShowBottomSheetWithBundle(bundle, true);
-    }
-
-    private void prepareAndShowBottomSheetWithBundle(Bundle bundle, boolean shouldShowCallMenuController) {
-        if (view == null) {
-            view = getActivity().getLayoutInflater().inflate(R.layout.bottom_sheet, null, false);
-        }
-
-        if (shouldShowCallMenuController) {
-            getChildRouter((ViewGroup) view).setRoot(
-                    RouterTransaction.with(new CallMenuController(bundle, this))
-                            .popChangeHandler(new VerticalChangeHandler())
-                            .pushChangeHandler(new VerticalChangeHandler()));
-        } else {
-            getChildRouter((ViewGroup) view).setRoot(
-                    RouterTransaction.with(new EntryMenuController(bundle))
-                            .popChangeHandler(new VerticalChangeHandler())
-                            .pushChangeHandler(new VerticalChangeHandler()));
-        }
-
-        if (bottomSheet == null) {
-            bottomSheet = new BottomSheet.Builder(getActivity()).setView(view).create();
-        }
-
-        bottomSheet.setOnShowListener(dialog -> new KeyboardUtils(getActivity(), bottomSheet.getLayout(), true));
-        bottomSheet.setOnDismissListener(dialog -> showSearchOrToolbar());
-        bottomSheet.show();
-    }
-
     @Override
     protected String getTitle() {
         return getResources().getString(R.string.nc_app_product_name);
@@ -950,8 +890,12 @@ public class ConversationsListController extends BaseController implements Searc
             Object clickedItem = adapter.getItem(position);
             if (clickedItem != null) {
                 Conversation conversation = ((ConversationItem) clickedItem).getModel();
-                MoreMenuClickEvent moreMenuClickEvent = new MoreMenuClickEvent(conversation);
-                onMessageEvent(moreMenuClickEvent);
+                conversationsListBottomDialog = new ConversationsListBottomDialog(
+                    getActivity(),
+                    this,
+                    userUtils.getCurrentUser(),
+                    conversation);
+                conversationsListBottomDialog.show();
             }
         }
     }
@@ -1055,22 +999,13 @@ public class ConversationsListController extends BaseController implements Searc
     private void openConversation(String textToPaste) {
         Bundle bundle = new Bundle();
         bundle.putParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY(), currentUser);
+        bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ACTIVE_CONVERSATION(), Parcels.wrap(selectedConversation));
         bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), selectedConversation.getToken());
         bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), selectedConversation.getRoomId());
         bundle.putString(BundleKeys.INSTANCE.getKEY_SHARED_TEXT(), textToPaste);
 
-        if (selectedConversation.hasPassword && selectedConversation.participantType ==
-                Participant.ParticipantType.GUEST ||
-                selectedConversation.participantType == Participant.ParticipantType.USER_FOLLOWING_LINK) {
-            bundle.putInt(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), 99);
-            prepareAndShowBottomSheetWithBundle(bundle, false);
-        } else {
-            currentUser = userUtils.getCurrentUser();
-
-            bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ACTIVE_CONVERSATION(), Parcels.wrap(selectedConversation));
-            ConductorRemapping.INSTANCE.remapChatController(getRouter(), currentUser.getId(),
-                                                            selectedConversation.getToken(), bundle, false);
-        }
+        ConductorRemapping.INSTANCE.remapChatController(getRouter(), currentUser.getId(),
+                                                        selectedConversation.getToken(), bundle, false);
     }
 
     @Subscribe(sticky = true, threadMode = ThreadMode.BACKGROUND)
@@ -1079,7 +1014,7 @@ public class ConversationsListController extends BaseController implements Searc
             switch (eventStatus.getEventType()) {
                 case CONVERSATION_UPDATE:
                     if (eventStatus.isAllGood() && !isRefreshing) {
-                        fetchData(false);
+                        fetchData();
                     }
                     break;
                 default:
@@ -1088,6 +1023,17 @@ public class ConversationsListController extends BaseController implements Searc
         }
     }
 
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    public void onMessageEvent(ConversationsListFetchDataEvent conversationsListFetchDataEvent) {
+        fetchData();
+
+        new Handler().postDelayed(() -> {
+            if (conversationsListBottomDialog.isShowing()) {
+                conversationsListBottomDialog.dismiss();
+            }
+        }, 2500);
+    }
+
     private void showDeleteConversationDialog(Bundle savedInstanceState) {
         if (getActivity() != null && conversationMenuBundle != null && currentUser != null && conversationMenuBundle.getLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID()) == currentUser.getId()) {
 
@@ -1240,7 +1186,6 @@ public class ConversationsListController extends BaseController implements Searc
             default:
                 break;
         }
-
     }
 
     @Override

+ 3 - 1
app/src/main/java/com/nextcloud/talk/controllers/base/ButterKnifeController.kt

@@ -46,7 +46,9 @@ abstract class ButterKnifeController : Controller {
         return view
     }
 
-    protected open fun onViewBound(view: View) {}
+    protected open fun onViewBound(view: View) {
+        // unused atm
+    }
 
     override fun onDestroyView(view: View) {
         super.onDestroyView(view)

+ 0 - 356
app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/CallMenuController.java

@@ -1,356 +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.controllers.bottomsheet;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.bluelinelabs.conductor.RouterTransaction;
-import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
-import com.kennyc.bottomsheet.adapters.AppAdapter;
-import com.nextcloud.talk.R;
-import com.nextcloud.talk.adapters.items.AppItem;
-import com.nextcloud.talk.adapters.items.MenuItem;
-import com.nextcloud.talk.application.NextcloudTalkApplication;
-import com.nextcloud.talk.controllers.ConversationsListController;
-import com.nextcloud.talk.controllers.base.BaseController;
-import com.nextcloud.talk.events.BottomSheetLockEvent;
-import com.nextcloud.talk.interfaces.ConversationMenuInterface;
-import com.nextcloud.talk.jobs.LeaveConversationWorker;
-import com.nextcloud.talk.models.database.CapabilitiesUtil;
-import com.nextcloud.talk.models.database.UserEntity;
-import com.nextcloud.talk.models.json.conversations.Conversation;
-import com.nextcloud.talk.utils.DisplayUtils;
-import com.nextcloud.talk.utils.ShareUtils;
-import com.nextcloud.talk.utils.bundle.BundleKeys;
-import com.nextcloud.talk.utils.database.user.UserUtils;
-
-import org.greenrobot.eventbus.EventBus;
-import org.parceler.Parcel;
-import org.parceler.Parcels;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.work.Data;
-import androidx.work.OneTimeWorkRequest;
-import androidx.work.WorkManager;
-import autodagger.AutoInjector;
-import butterknife.BindView;
-import eu.davidea.flexibleadapter.FlexibleAdapter;
-import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager;
-import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
-
-@AutoInjector(NextcloudTalkApplication.class)
-public class CallMenuController extends BaseController implements FlexibleAdapter.OnItemClickListener {
-    public static final int ALL_MESSAGES_READ = 0;
-    @BindView(R.id.recycler_view)
-    RecyclerView recyclerView;
-
-    @Inject
-    EventBus eventBus;
-
-    @Inject
-    UserUtils userUtils;
-
-    @Inject
-    Context context;
-
-    private Conversation conversation;
-    private List<AbstractFlexibleItem> menuItems;
-    private FlexibleAdapter<AbstractFlexibleItem> adapter;
-    private MenuType menuType;
-    private Intent shareIntent;
-
-    private UserEntity currentUser;
-    private ConversationMenuInterface conversationMenuInterface;
-
-    public CallMenuController(Bundle args) {
-        super(args);
-        this.conversation = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_ROOM()));
-        if (args.containsKey(BundleKeys.INSTANCE.getKEY_MENU_TYPE())) {
-            this.menuType = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_MENU_TYPE()));
-        }
-    }
-
-    public CallMenuController(Bundle args, @Nullable ConversationMenuInterface conversationMenuInterface) {
-        super(args);
-        this.conversation = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_ROOM()));
-        if (args.containsKey(BundleKeys.INSTANCE.getKEY_MENU_TYPE())) {
-            this.menuType = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_MENU_TYPE()));
-        }
-        this.conversationMenuInterface = conversationMenuInterface;
-    }
-
-    @Override
-    @NonNull
-    protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
-        return inflater.inflate(R.layout.controller_call_menu, container, false);
-    }
-
-    @Override
-    protected void onViewBound(@NonNull View view) {
-        super.onViewBound(view);
-        NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
-        prepareViews();
-    }
-
-    private void prepareViews() {
-        LinearLayoutManager layoutManager = new SmoothScrollLinearLayoutManager(getActivity());
-        recyclerView.setLayoutManager(layoutManager);
-        recyclerView.setHasFixedSize(true);
-
-        prepareMenu();
-        if (adapter == null) {
-            adapter = new FlexibleAdapter<>(menuItems, getActivity(), false);
-        }
-
-        adapter.addListener(this);
-        recyclerView.setAdapter(adapter);
-    }
-
-    private void prepareIntent() {
-        shareIntent = new Intent(Intent.ACTION_SEND);
-        shareIntent.setType("text/plain");
-        shareIntent.putExtra(Intent.EXTRA_SUBJECT, String.format(getResources().getString(R.string.nc_share_subject),
-                getResources().getString(R.string.nc_app_product_name)));
-    }
-
-    private void prepareMenu() {
-        menuItems = new ArrayList<>();
-
-        if (menuType.equals(MenuType.REGULAR)) {
-            if (!TextUtils.isEmpty(conversation.getDisplayName())) {
-                menuItems.add(new MenuItem(conversation.getDisplayName(), 0, null));
-            } else if (!TextUtils.isEmpty(conversation.getName())) {
-                menuItems.add(new MenuItem(conversation.getName(), 0, null));
-            } else {
-                menuItems.add(new MenuItem(getResources().getString(R.string.nc_configure_room), 0, null));
-            }
-
-            currentUser = userUtils.getCurrentUser();
-
-            if (conversation.isFavorite()) {
-                menuItems.add(new MenuItem(getResources().getString(R.string.nc_remove_from_favorites), 97, DisplayUtils.getTintedDrawable(getResources(), R.drawable.ic_star_border_black_24dp, R.color.grey_600)));
-            } else if (CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "favorites")) {
-                menuItems.add(new MenuItem(getResources().getString(R.string.nc_add_to_favorites),
-                                           98,
-                                           DisplayUtils.getTintedDrawable(getResources(),
-                                                                          R.drawable.ic_star_black_24dp,
-                                                                          R.color.grey_600)));
-            }
-
-            if(conversation.unreadMessages > ALL_MESSAGES_READ && CapabilitiesUtil.canSetChatReadMarker(currentUser)) {
-                menuItems.add(new MenuItem(getResources().getString(R.string.nc_mark_as_read),
-                                           96,
-                                           ContextCompat.getDrawable(context, R.drawable.ic_eye)));
-            }
-
-            if (conversation.isNameEditable(currentUser)) {
-                menuItems.add(new MenuItem(getResources().getString(R.string.nc_rename),
-                                           2,
-                                           ContextCompat.getDrawable(context,
-                                                                     R.drawable.ic_pencil_grey600_24dp)));
-            }
-
-            if (conversation.canModerate(currentUser)) {
-                if (!conversation.isPublic()) {
-                    menuItems.add(new MenuItem(getResources().getString(R.string.nc_make_call_public),
-                                               3, ContextCompat.getDrawable(context,
-                                                                            R.drawable.ic_link_grey600_24px)));
-                } else {
-                    if (conversation.isHasPassword()) {
-                        menuItems.add(new MenuItem(getResources().getString(R.string.nc_change_password),
-                                                   4, ContextCompat.getDrawable(context,
-                                                                                R.drawable.ic_lock_grey600_24px)));
-                        menuItems.add(new MenuItem(getResources().getString(R.string.nc_clear_password),
-                                                   5,
-                                                   ContextCompat.getDrawable(context,
-                                                                             R.drawable.ic_lock_open_grey600_24dp)));
-                    } else {
-                        menuItems.add(new MenuItem(getResources().getString(R.string.nc_set_password),
-                                                   6, ContextCompat.getDrawable(context,
-                                                                                R.drawable.ic_lock_plus_grey600_24dp)));
-                    }
-                }
-
-                menuItems.add(new MenuItem(getResources().getString(R.string.nc_delete_call),
-                                           9, ContextCompat.getDrawable(context,
-                                                                        R.drawable.ic_delete_grey600_24dp)));
-            }
-
-            if (conversation.isPublic()) {
-                menuItems.add(new MenuItem(getResources().getString(R.string.nc_share_link),
-                                           7, ContextCompat.getDrawable(context,
-                                                                        R.drawable.ic_link_grey600_24px)));
-                if (conversation.canModerate(currentUser)) {
-                    menuItems.add(new MenuItem(getResources().getString(R.string.nc_make_call_private),
-                                               8, ContextCompat.getDrawable(context,
-                                                                            R.drawable.ic_group_grey600_24px)));
-                }
-            }
-
-            if (conversation.canLeave(currentUser)) {
-                menuItems.add(new MenuItem(getResources().getString(R.string.nc_leave), 1,
-                        DisplayUtils.getTintedDrawable(getResources(),
-                                R.drawable.ic_exit_to_app_black_24dp, R.color.grey_600)
-                ));
-            }
-        } else if (menuType.equals(MenuType.SHARE)) {
-            prepareIntent();
-            List<AppAdapter.AppInfo> appInfoList = ShareUtils.getShareApps(getActivity(), shareIntent, null,
-                    null);
-            menuItems.add(new AppItem(getResources().getString(R.string.nc_share_link_via),
-                                      "",
-                                      "",
-                                      ContextCompat.getDrawable(context, R.drawable.ic_link_grey600_24px)));
-            if (appInfoList != null) {
-                for (AppAdapter.AppInfo appInfo : appInfoList) {
-                    menuItems.add(new AppItem(appInfo.title, appInfo.packageName, appInfo.name, appInfo.drawable));
-                }
-            }
-        } else {
-            menuItems.add(new MenuItem(getResources().getString(R.string.nc_start_conversation), 0, null));
-            menuItems.add(new MenuItem(getResources().getString(R.string.nc_new_conversation),
-                                       1, ContextCompat.getDrawable(context,
-                                                                    R.drawable.ic_add_grey600_24px)));
-            menuItems.add(new MenuItem(getResources().getString(R.string.nc_join_via_link),
-                                       2, ContextCompat.getDrawable(context,
-                                                                    R.drawable.ic_link_grey600_24px)));
-        }
-    }
-
-    @Override
-    public boolean onItemClick(View view, int position) {
-        Bundle bundle = new Bundle();
-        bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ROOM(), Parcels.wrap(conversation));
-
-        if (menuType.equals(MenuType.REGULAR)) {
-            MenuItem menuItem = (MenuItem) adapter.getItem(position);
-            if (menuItem != null) {
-
-                int tag = menuItem.getTag();
-                if (tag == 5) {
-                    conversation.setPassword("");
-                }
-
-                if (tag > 0) {
-                    if (tag == 1 || tag == 9) {
-                        if (tag == 1) {
-                            Data data;
-                            if ((data = getWorkerData()) != null) {
-                                OneTimeWorkRequest leaveConversationWorker =
-                                        new OneTimeWorkRequest.Builder(LeaveConversationWorker.class).setInputData(data).build();
-                                WorkManager.getInstance().enqueue(leaveConversationWorker);
-                            }
-                        } else {
-                            Bundle deleteConversationBundle;
-                            if ((deleteConversationBundle = getDeleteConversationBundle()) != null) {
-                                conversationMenuInterface.openLovelyDialogWithIdAndBundle(ConversationsListController.ID_DELETE_CONVERSATION_DIALOG, deleteConversationBundle);
-                            }
-                        }
-                        eventBus.post(new BottomSheetLockEvent(true, 0, false, true));
-                    } else {
-                        bundle.putInt(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), tag);
-                        if (tag != 2 && tag != 4 && tag != 6 && tag != 7) {
-                            eventBus.post(new BottomSheetLockEvent(false, 0, false, false));
-                            getRouter().pushController(RouterTransaction.with(new OperationsMenuController(bundle))
-                                    .pushChangeHandler(new HorizontalChangeHandler())
-                                    .popChangeHandler(new HorizontalChangeHandler()));
-                        } else if (tag != 7) {
-                            getRouter().pushController(RouterTransaction.with(new EntryMenuController(bundle))
-                                    .pushChangeHandler(new HorizontalChangeHandler())
-                                    .popChangeHandler(new HorizontalChangeHandler()));
-                        } else {
-                            bundle.putParcelable(BundleKeys.INSTANCE.getKEY_MENU_TYPE(), Parcels.wrap(MenuType.SHARE));
-                            getRouter().pushController(RouterTransaction.with(new CallMenuController(bundle, null))
-                                    .pushChangeHandler(new HorizontalChangeHandler())
-                                    .popChangeHandler(new HorizontalChangeHandler()));
-                        }
-                    }
-                }
-            }
-        } else if (menuType.equals(MenuType.SHARE) && position != 0) {
-            AppItem appItem = (AppItem) adapter.getItem(position);
-            if (appItem != null && getActivity() != null) {
-                if (!conversation.hasPassword) {
-                    shareIntent.putExtra(Intent.EXTRA_TEXT, ShareUtils.getStringForIntent(getActivity(), null,
-                            userUtils, conversation));
-                    Intent intent = new Intent(shareIntent);
-                    intent.setComponent(new ComponentName(appItem.getPackageName(), appItem.getName()));
-                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                    eventBus.post(new BottomSheetLockEvent(true, 0, false, true));
-                    getActivity().startActivity(intent);
-                } else {
-                    bundle.putInt(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), 7);
-                    bundle.putParcelable(BundleKeys.INSTANCE.getKEY_SHARE_INTENT(), Parcels.wrap(shareIntent));
-                    bundle.putString(BundleKeys.INSTANCE.getKEY_APP_ITEM_PACKAGE_NAME(), appItem.getPackageName());
-                    bundle.putString(BundleKeys.INSTANCE.getKEY_APP_ITEM_NAME(), appItem.getName());
-                    getRouter().pushController(RouterTransaction.with(new EntryMenuController(bundle))
-                            .pushChangeHandler(new HorizontalChangeHandler())
-                            .popChangeHandler(new HorizontalChangeHandler()));
-                }
-            }
-        }
-
-        return true;
-    }
-
-    private Data getWorkerData() {
-        if (!TextUtils.isEmpty(conversation.getToken())) {
-            Data.Builder data = new Data.Builder();
-            data.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), conversation.getToken());
-            data.putLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), currentUser.getId());
-            return data.build();
-        }
-
-        return null;
-    }
-
-    private Bundle getDeleteConversationBundle() {
-        if (!TextUtils.isEmpty(conversation.getToken())) {
-            Bundle bundle = new Bundle();
-            bundle.putLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), currentUser.getId());
-            bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ROOM(), Parcels.wrap(conversation));
-            return bundle;
-        }
-
-        return null;
-    }
-
-    @Parcel
-    public enum MenuType {
-        REGULAR, SHARE
-    }
-}

+ 37 - 0
app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/ConversationOperationEnum.kt

@@ -0,0 +1,37 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
+ *
+ * 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.controllers.bottomsheet
+
+enum class ConversationOperationEnum {
+    OPS_CODE_RENAME_ROOM,
+    OPS_CODE_MAKE_PUBLIC,
+    OPS_CODE_CHANGE_PASSWORD,
+    OPS_CODE_CLEAR_PASSWORD,
+    OPS_CODE_SET_PASSWORD,
+    OPS_CODE_SHARE_LINK,
+    OPS_CODE_MAKE_PRIVATE,
+    OPS_CODE_GET_AND_JOIN_ROOM,
+    OPS_CODE_INVITE_USERS,
+    OPS_CODE_MARK_AS_READ,
+    OPS_CODE_REMOVE_FAVORITE,
+    OPS_CODE_ADD_FAVORITE,
+    OPS_CODE_JOIN_ROOM
+}

+ 41 - 30
app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/EntryMenuController.java

@@ -2,7 +2,9 @@
  * Nextcloud Talk application
  *
  * @author Mario Danic
+ * @author Marcel Hibbe
  * Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
+ * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
  *
  * 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
@@ -20,6 +22,14 @@
 
 package com.nextcloud.talk.controllers.bottomsheet;
 
+import static com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_JOIN_ROOM;
+import static com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_GET_AND_JOIN_ROOM;
+import static com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_INVITE_USERS;
+import static com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_CHANGE_PASSWORD;
+import static com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_SET_PASSWORD;
+import static com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_SHARE_LINK;
+import static com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_RENAME_ROOM;
+
 import android.content.ComponentName;
 import android.content.Intent;
 import android.graphics.PorterDuff;
@@ -41,7 +51,6 @@ import com.google.android.material.textfield.TextInputLayout;
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.controllers.base.BaseController;
-import com.nextcloud.talk.events.BottomSheetLockEvent;
 import com.nextcloud.talk.models.json.conversations.Conversation;
 import com.nextcloud.talk.utils.EmojiTextInputEditText;
 import com.nextcloud.talk.utils.ShareUtils;
@@ -86,7 +95,7 @@ public class EntryMenuController extends BaseController {
     @Inject
     UserUtils userUtils;
 
-    private int operationCode;
+    private ConversationOperationEnum operation;
     private Conversation conversation;
     private Intent shareIntent;
     private String packageName;
@@ -101,7 +110,7 @@ public class EntryMenuController extends BaseController {
         super(args);
         originalBundle = args;
 
-        this.operationCode = args.getInt(BundleKeys.INSTANCE.getKEY_OPERATION_CODE());
+        this.operation = (ConversationOperationEnum) args.getSerializable(BundleKeys.INSTANCE.getKEY_OPERATION_CODE());
         if (args.containsKey(BundleKeys.INSTANCE.getKEY_ROOM())) {
             this.conversation = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_ROOM()));
         }
@@ -143,13 +152,12 @@ public class EntryMenuController extends BaseController {
     @OnClick(R.id.ok_button)
     public void onProceedButtonClick() {
         Bundle bundle;
-        if (operationCode == 99) {
-            eventBus.post(new BottomSheetLockEvent(false, 0, false, false));
+        if (operation == OPS_CODE_JOIN_ROOM) {
             bundle = new Bundle();
             bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ROOM(), Parcels.wrap(conversation));
             bundle.putString(BundleKeys.INSTANCE.getKEY_CALL_URL(), callUrl);
             bundle.putString(BundleKeys.INSTANCE.getKEY_CONVERSATION_PASSWORD(), editText.getText().toString());
-            bundle.putInt(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), operationCode);
+            bundle.putSerializable(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), operation);
             if (originalBundle.containsKey(BundleKeys.INSTANCE.getKEY_SERVER_CAPABILITIES())) {
                 bundle.putParcelable(BundleKeys.INSTANCE.getKEY_SERVER_CAPABILITIES(), originalBundle.getParcelable(BundleKeys.INSTANCE.getKEY_SERVER_CAPABILITIES()));
             }
@@ -157,20 +165,19 @@ public class EntryMenuController extends BaseController {
             getRouter().pushController(RouterTransaction.with(new OperationsMenuController(bundle))
                     .pushChangeHandler(new HorizontalChangeHandler())
                     .popChangeHandler(new HorizontalChangeHandler()));
-        } else if (operationCode != 7 && operationCode != 10 && operationCode != 11) {
-            eventBus.post(new BottomSheetLockEvent(false, 0, false, false));
+        } else if (operation != OPS_CODE_SHARE_LINK && operation != OPS_CODE_GET_AND_JOIN_ROOM && operation != OPS_CODE_INVITE_USERS) {
             bundle = new Bundle();
-            if (operationCode == 4 || operationCode == 6) {
+            if (operation == OPS_CODE_CHANGE_PASSWORD || operation == OPS_CODE_SET_PASSWORD) {
                 conversation.setPassword(editText.getText().toString());
             } else {
                 conversation.setName(editText.getText().toString());
             }
             bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ROOM(), Parcels.wrap(conversation));
-            bundle.putInt(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), operationCode);
+            bundle.putSerializable(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), operation);
             getRouter().pushController(RouterTransaction.with(new OperationsMenuController(bundle))
                     .pushChangeHandler(new HorizontalChangeHandler())
                     .popChangeHandler(new HorizontalChangeHandler()));
-        } else if (operationCode == 7) {
+        } else if (operation == OPS_CODE_SHARE_LINK) {
             if (getActivity() != null) {
                 shareIntent.putExtra(Intent.EXTRA_TEXT, ShareUtils.getStringForIntent(getActivity(),
                         editText.getText().toString(), userUtils, conversation));
@@ -178,19 +185,16 @@ public class EntryMenuController extends BaseController {
                 intent.setComponent(new ComponentName(packageName, name));
                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                 getActivity().startActivity(intent);
-                eventBus.post(new BottomSheetLockEvent(true, 0, false, true));
             }
-        } else if (operationCode != 11) {
-            eventBus.post(new BottomSheetLockEvent(false, 0, false, false));
+        } else if (operation != OPS_CODE_INVITE_USERS) {
             bundle = new Bundle();
-            bundle.putInt(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), operationCode);
+            bundle.putSerializable(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), operation);
             bundle.putString(BundleKeys.INSTANCE.getKEY_CALL_URL(), editText.getText().toString());
             getRouter().pushController(RouterTransaction.with(new OperationsMenuController(bundle))
                     .pushChangeHandler(new HorizontalChangeHandler())
                     .popChangeHandler(new HorizontalChangeHandler()));
 
-        } else if (operationCode == 11) {
-            eventBus.post(new BottomSheetLockEvent(false, 0, false, false));
+        } else if (operation == OPS_CODE_INVITE_USERS) {
             originalBundle.putString(BundleKeys.INSTANCE.getKEY_CONVERSATION_NAME(), editText.getText().toString());
             getRouter().pushController(RouterTransaction.with(new OperationsMenuController(originalBundle))
                     .pushChangeHandler(new HorizontalChangeHandler())
@@ -204,7 +208,7 @@ public class EntryMenuController extends BaseController {
         super.onViewBound(view);
         NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
 
-        if (conversation != null && operationCode == 2) {
+        if (conversation != null && operation == OPS_CODE_RENAME_ROOM) {
             editText.setText(conversation.getName());
         }
 
@@ -230,7 +234,7 @@ public class EntryMenuController extends BaseController {
             @Override
             public void afterTextChanged(Editable s) {
                 if (!TextUtils.isEmpty(s)) {
-                    if (operationCode == 2) {
+                    if (operation == OPS_CODE_RENAME_ROOM) {
                         if (conversation.getName() == null || !conversation.getName().equals(s.toString())) {
                             if (!proceedButton.isEnabled()) {
                                 proceedButton.setEnabled(true);
@@ -244,7 +248,7 @@ public class EntryMenuController extends BaseController {
                             }
                             textInputLayout.setError(getResources().getString(R.string.nc_call_name_is_same));
                         }
-                    } else if (operationCode != 10) {
+                    } else if (operation != OPS_CODE_GET_AND_JOIN_ROOM) {
                         if (!proceedButton.isEnabled()) {
                             proceedButton.setEnabled(true);
                             proceedButton.setAlpha(1.0f);
@@ -253,7 +257,6 @@ public class EntryMenuController extends BaseController {
                     } else if ((editText.getText().toString().startsWith("http://") ||
                             editText.getText().toString().startsWith("https://")) &&
                                     editText.getText().toString().contains("/call/")) {
-                        // operation code 10
                         if (!proceedButton.isEnabled()) {
                             proceedButton.setEnabled(true);
                             proceedButton.setAlpha(1.0f);
@@ -277,9 +280,9 @@ public class EntryMenuController extends BaseController {
         });
 
         String labelText = "";
-        switch (operationCode) {
-            case 11:
-            case 2:
+        switch (operation) {
+            case OPS_CODE_INVITE_USERS:
+            case OPS_CODE_RENAME_ROOM:
                 labelText = getResources().getString(R.string.nc_call_name);
                 editText.setInputType(InputType.TYPE_CLASS_TEXT);
                 smileyButton.setVisibility(View.VISIBLE);
@@ -307,18 +310,18 @@ public class EntryMenuController extends BaseController {
                 }).build(editText);
 
                 break;
-            case 4:
+            case OPS_CODE_CHANGE_PASSWORD:
                 labelText = getResources().getString(R.string.nc_new_password);
                 editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
                 break;
-            case 6:
-            case 7:
-            case 99:
+            case OPS_CODE_SET_PASSWORD:
+            case OPS_CODE_SHARE_LINK:
+            case OPS_CODE_JOIN_ROOM:
                 // 99 is joining a conversation via password
                 labelText = getResources().getString(R.string.nc_password);
                 editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
                 break;
-            case 10:
+            case OPS_CODE_GET_AND_JOIN_ROOM:
                 labelText = getResources().getString(R.string.nc_conversation_link);
                 editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
                 break;
@@ -326,7 +329,10 @@ public class EntryMenuController extends BaseController {
                 break;
         }
 
-        if (operationCode == 99 || operationCode == 4 || operationCode == 6 || operationCode == 7) {
+        if (operation == OPS_CODE_JOIN_ROOM
+            || operation == OPS_CODE_CHANGE_PASSWORD
+            || operation == OPS_CODE_SET_PASSWORD
+            || operation == OPS_CODE_SHARE_LINK) {
             textInputLayout.setEndIconMode(TextInputLayout.END_ICON_PASSWORD_TOGGLE);
         } else {
             textInputLayout.setEndIconMode(TextInputLayout.END_ICON_NONE);
@@ -335,4 +341,9 @@ public class EntryMenuController extends BaseController {
         textInputLayout.setHint(labelText);
         textInputLayout.requestFocus();
     }
+
+    @Override
+    public AppBarLayoutType getAppBarLayoutType() {
+        return AppBarLayoutType.SEARCH_BAR;
+    }
 }

+ 35 - 42
app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java

@@ -20,6 +20,9 @@
 
 package com.nextcloud.talk.controllers.bottomsheet;
 
+import static com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_JOIN_ROOM;
+import static com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_REMOVE_FAVORITE;
+
 import android.annotation.SuppressLint;
 import android.content.Intent;
 import android.net.Uri;
@@ -33,7 +36,6 @@ import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.ProgressBar;
 import android.widget.TextView;
-import android.widget.Toast;
 
 import com.bluelinelabs.conductor.RouterTransaction;
 import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
@@ -42,7 +44,8 @@ import com.nextcloud.talk.R;
 import com.nextcloud.talk.api.NcApi;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.controllers.base.BaseController;
-import com.nextcloud.talk.events.BottomSheetLockEvent;
+import com.nextcloud.talk.events.ConversationsListFetchDataEvent;
+import com.nextcloud.talk.events.OpenConversationEvent;
 import com.nextcloud.talk.models.RetrofitBucket;
 import com.nextcloud.talk.models.database.CapabilitiesUtil;
 import com.nextcloud.talk.models.database.UserEntity;
@@ -53,7 +56,6 @@ import com.nextcloud.talk.models.json.conversations.RoomOverall;
 import com.nextcloud.talk.models.json.generic.GenericOverall;
 import com.nextcloud.talk.models.json.participants.AddParticipantOverall;
 import com.nextcloud.talk.utils.ApiUtils;
-import com.nextcloud.talk.utils.ConductorRemapping;
 import com.nextcloud.talk.utils.DisplayUtils;
 import com.nextcloud.talk.utils.NoSupportedApiException;
 import com.nextcloud.talk.utils.bundle.BundleKeys;
@@ -75,7 +77,6 @@ import io.reactivex.Observer;
 import io.reactivex.android.schedulers.AndroidSchedulers;
 import io.reactivex.disposables.Disposable;
 import io.reactivex.schedulers.Schedulers;
-import okhttp3.ResponseBody;
 import retrofit2.HttpException;
 import retrofit2.Response;
 
@@ -108,7 +109,7 @@ public class OperationsMenuController extends BaseController {
     @Inject
     EventBus eventBus;
 
-    private int operationCode;
+    private ConversationOperationEnum operation;
     private Conversation conversation;
 
     private UserEntity currentUser;
@@ -130,7 +131,7 @@ public class OperationsMenuController extends BaseController {
 
     public OperationsMenuController(Bundle args) {
         super(args);
-        this.operationCode = args.getInt(BundleKeys.INSTANCE.getKEY_OPERATION_CODE());
+        this.operation = (ConversationOperationEnum) args.getSerializable(BundleKeys.INSTANCE.getKEY_OPERATION_CODE());
         if (args.containsKey(BundleKeys.INSTANCE.getKEY_ROOM())) {
             this.conversation = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_ROOM()));
         }
@@ -283,8 +284,8 @@ public class OperationsMenuController extends BaseController {
         int apiVersion = ApiUtils.getConversationApiVersion(currentUser, new int[] {ApiUtils.APIv4, ApiUtils.APIv1});
         int chatApiVersion = ApiUtils.getChatApiVersion(currentUser, new int[] {ApiUtils.APIv1});
 
-        switch (operationCode) {
-            case 2:
+        switch (operation) {
+            case OPS_CODE_RENAME_ROOM:
                 ncApi.renameRoom(credentials, ApiUtils.getUrlForRoom(apiVersion, currentUser.getBaseUrl(),
                                                                      conversation.getToken()),
                         conversation.getName())
@@ -293,7 +294,7 @@ public class OperationsMenuController extends BaseController {
                         .retry(1)
                         .subscribe(genericOperationsObserver);
                 break;
-            case 3:
+            case OPS_CODE_MAKE_PUBLIC:
                 ncApi.makeRoomPublic(credentials, ApiUtils.getUrlForRoomPublic(apiVersion, currentUser.getBaseUrl(),
                                                                                conversation.getToken()))
                         .subscribeOn(Schedulers.io())
@@ -301,9 +302,9 @@ public class OperationsMenuController extends BaseController {
                         .retry(1)
                         .subscribe(genericOperationsObserver);
                 break;
-            case 4:
-            case 5:
-            case 6:
+            case OPS_CODE_CHANGE_PASSWORD:
+            case OPS_CODE_CLEAR_PASSWORD:
+            case OPS_CODE_SET_PASSWORD:
                 String pass = "";
                 if (conversation.getPassword() != null) {
                     pass = conversation.getPassword();
@@ -315,10 +316,7 @@ public class OperationsMenuController extends BaseController {
                         .retry(1)
                         .subscribe(genericOperationsObserver);
                 break;
-            case 7:
-                // Operation 7 is sharing, so we handle this differently
-                break;
-            case 8:
+            case OPS_CODE_MAKE_PRIVATE:
                 ncApi.makeRoomPrivate(credentials, ApiUtils.getUrlForRoomPublic(apiVersion,
                                                                                 currentUser.getBaseUrl(),
                                                                                 conversation.getToken()))
@@ -327,7 +325,7 @@ public class OperationsMenuController extends BaseController {
                         .retry(1)
                         .subscribe(genericOperationsObserver);
                 break;
-            case 10:
+            case OPS_CODE_GET_AND_JOIN_ROOM:
                 ncApi.getRoom(credentials, ApiUtils.getUrlForRoom(apiVersion, baseUrl, conversationToken))
                         .subscribeOn(Schedulers.io())
                         .observeOn(AndroidSchedulers.mainThread())
@@ -342,8 +340,7 @@ public class OperationsMenuController extends BaseController {
                             public void onNext(@io.reactivex.annotations.NonNull RoomOverall roomOverall) {
                                 conversation = roomOverall.getOcs().getData();
                                 if (conversation.isHasPassword() && conversation.isGuest()) {
-                                    eventBus.post(new BottomSheetLockEvent(true, 0,
-                                                                           true, false));
+                                    eventBus.post(new ConversationsListFetchDataEvent());
                                     Bundle bundle = new Bundle();
                                     bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ROOM(), Parcels.wrap(conversation));
                                     bundle.putString(BundleKeys.INSTANCE.getKEY_CALL_URL(), callUrl);
@@ -355,7 +352,7 @@ public class OperationsMenuController extends BaseController {
                                         Log.e(TAG, "Failed to parse capabilities for guest");
                                         showResultImage(false, false);
                                     }
-                                    bundle.putInt(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), 99);
+                                    bundle.putSerializable(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), OPS_CODE_JOIN_ROOM);
                                     getRouter().pushController(RouterTransaction.with(new EntryMenuController(bundle))
                                                                        .pushChangeHandler(new HorizontalChangeHandler())
                                                                        .popChangeHandler(new HorizontalChangeHandler()));
@@ -407,7 +404,7 @@ public class OperationsMenuController extends BaseController {
                             }
                         });
                 break;
-            case 11:
+            case OPS_CODE_INVITE_USERS:
                 RetrofitBucket retrofitBucket;
                 String invite = null;
 
@@ -483,7 +480,7 @@ public class OperationsMenuController extends BaseController {
                         });
 
                 break;
-            case 96:
+            case OPS_CODE_MARK_AS_READ:
                 ncApi.setChatReadMarker(credentials,
                                         ApiUtils.getUrlForSetChatReadMarker(chatApiVersion,
                                                                             currentUser.getBaseUrl(),
@@ -494,9 +491,9 @@ public class OperationsMenuController extends BaseController {
                     .retry(1)
                     .subscribe(genericOperationsObserver);
                 break;
-            case 97:
-            case 98:
-                if (operationCode == 97) {
+            case OPS_CODE_REMOVE_FAVORITE:
+            case OPS_CODE_ADD_FAVORITE:
+                if (operation == OPS_CODE_REMOVE_FAVORITE) {
                     ncApi.removeConversationFromFavorites(credentials,
                                                           ApiUtils.getUrlForRoomFavorite(apiVersion,
                                                                                          currentUser.getBaseUrl(),
@@ -516,7 +513,7 @@ public class OperationsMenuController extends BaseController {
                             .subscribe(genericOperationsObserver);
                 }
                 break;
-            case 99:
+            case OPS_CODE_JOIN_ROOM:
                 ncApi.joinRoom(credentials, ApiUtils.getUrlForParticipantsActive(apiVersion,
                                                                                  baseUrl,
                                                                                  conversationToken),
@@ -557,10 +554,8 @@ public class OperationsMenuController extends BaseController {
             } else {
                 resultsTextView.setText(R.string.nc_failed_signaling_settings);
                 webButton.setOnClickListener(v -> {
-                    eventBus.post(new BottomSheetLockEvent(true, 0, false, true));
                     Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(callUrl));
                     startActivity(browserIntent);
-                    new BottomSheetLockEvent(true, 0, false, true);
                 });
                 webButton.setVisibility(View.VISIBLE);
             }
@@ -568,12 +563,11 @@ public class OperationsMenuController extends BaseController {
 
         resultsTextView.setVisibility(View.VISIBLE);
         if (everythingOK) {
-            eventBus.post(new BottomSheetLockEvent(true, 2500, true, true));
+            eventBus.post(new ConversationsListFetchDataEvent());
         } else {
             resultImageView.setImageDrawable(DisplayUtils.getTintedDrawable(getResources(), R.drawable
                     .ic_cancel_black_24dp, R.color.nc_darkRed));
-            okButton.setOnClickListener(v -> eventBus.post(new BottomSheetLockEvent(true, 0, operationCode != 99
-                    && operationCode != 10, true)));
+            okButton.setOnClickListener(v -> eventBus.post(new ConversationsListFetchDataEvent()));
             okButton.setVisibility(View.VISIBLE);
         }
     }
@@ -724,8 +718,7 @@ public class OperationsMenuController extends BaseController {
     }
 
     private void initiateConversation(boolean dismissView) {
-        eventBus.post(new BottomSheetLockEvent(true, 0,
-                                               true, true, dismissView));
+        eventBus.post(new ConversationsListFetchDataEvent());
 
         Bundle bundle = new Bundle();
         bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), conversation.getToken());
@@ -735,20 +728,15 @@ public class OperationsMenuController extends BaseController {
         bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ACTIVE_CONVERSATION(), Parcels.wrap(conversation));
         bundle.putString(BundleKeys.INSTANCE.getKEY_CONVERSATION_PASSWORD(), callPassword);
 
-        if (getParentController() != null) {
-            ConductorRemapping.INSTANCE.remapChatController(getParentController().getRouter(), currentUser.getId(),
-                                                            conversation.getToken(), bundle, true);
-        }
+        eventBus.post(new OpenConversationEvent(conversation, bundle));
     }
 
     private void handleObserverError(@io.reactivex.annotations.NonNull Throwable e) {
-        if (operationCode != 99 || !(e instanceof HttpException)) {
+        if (operation != OPS_CODE_JOIN_ROOM || !(e instanceof HttpException)) {
             showResultImage(false, false);
         } else {
             Response<?> response = ((HttpException) e).response();
             if (response != null && response.code() == 403) {
-                eventBus.post(new BottomSheetLockEvent(true, 0, false,
-                                                       false));
                 ApplicationWideMessageHolder.getInstance().setMessageType(ApplicationWideMessageHolder.MessageType.CALL_PASSWORD_WRONG);
                 getRouter().popCurrentController();
             } else {
@@ -767,7 +755,7 @@ public class OperationsMenuController extends BaseController {
 
         @Override
         public void onNext(@io.reactivex.annotations.NonNull GenericOverall genericOverall) {
-            if (operationCode != 99) {
+            if (operation != OPS_CODE_JOIN_ROOM) {
                 showResultImage(true, false);
             } else {
                 throw new IllegalArgumentException("Unsupported operation code observed!");
@@ -795,7 +783,7 @@ public class OperationsMenuController extends BaseController {
         @Override
         public void onNext(@io.reactivex.annotations.NonNull RoomOverall roomOverall) {
             conversation = roomOverall.getOcs().getData();
-            if (operationCode != 99) {
+            if (operation != OPS_CODE_JOIN_ROOM) {
                 showResultImage(true, false);
             } else {
                 conversation = roomOverall.getOcs().getData();
@@ -813,4 +801,9 @@ public class OperationsMenuController extends BaseController {
             dispose();
         }
     }
+
+    @Override
+    public AppBarLayoutType getAppBarLayoutType() {
+        return AppBarLayoutType.SEARCH_BAR;
+    }
 }

+ 0 - 116
app/src/main/java/com/nextcloud/talk/events/BottomSheetLockEvent.java

@@ -1,116 +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.events;
-
-public class BottomSheetLockEvent {
-    private final boolean cancelable;
-    private final int delay;
-    private final boolean shouldRefreshData;
-    private final boolean cancel;
-    private boolean dismissView;
-
-    public BottomSheetLockEvent(boolean cancelable, int delay, boolean shouldRefreshData, boolean cancel) {
-        this.cancelable = cancelable;
-        this.delay = delay;
-        this.shouldRefreshData = shouldRefreshData;
-        this.cancel = cancel;
-        this.dismissView = true;
-    }
-
-    public BottomSheetLockEvent(boolean cancelable, int delay, boolean shouldRefreshData, boolean cancel, boolean
-            dismissView) {
-        this.cancelable = cancelable;
-        this.delay = delay;
-        this.shouldRefreshData = shouldRefreshData;
-        this.cancel = cancel;
-        this.dismissView = dismissView;
-    }
-
-    public boolean isCancelable() {
-        return this.cancelable;
-    }
-
-    public int getDelay() {
-        return this.delay;
-    }
-
-    public boolean isShouldRefreshData() {
-        return this.shouldRefreshData;
-    }
-
-    public boolean isCancel() {
-        return this.cancel;
-    }
-
-    public boolean isDismissView() {
-        return this.dismissView;
-    }
-
-    public void setDismissView(boolean dismissView) {
-        this.dismissView = dismissView;
-    }
-
-    public boolean equals(final Object o) {
-        if (o == this) {
-            return true;
-        }
-        if (!(o instanceof BottomSheetLockEvent)) {
-            return false;
-        }
-        final BottomSheetLockEvent other = (BottomSheetLockEvent) o;
-        if (!other.canEqual((Object) this)) {
-            return false;
-        }
-        if (this.isCancelable() != other.isCancelable()) {
-            return false;
-        }
-        if (this.getDelay() != other.getDelay()) {
-            return false;
-        }
-        if (this.isShouldRefreshData() != other.isShouldRefreshData()) {
-            return false;
-        }
-        if (this.isCancel() != other.isCancel()) {
-            return false;
-        }
-
-        return this.isDismissView() == other.isDismissView();
-    }
-
-    protected boolean canEqual(final Object other) {
-        return other instanceof BottomSheetLockEvent;
-    }
-
-    public int hashCode() {
-        final int PRIME = 59;
-        int result = 1;
-        result = result * PRIME + (this.isCancelable() ? 79 : 97);
-        result = result * PRIME + this.getDelay();
-        result = result * PRIME + (this.isShouldRefreshData() ? 79 : 97);
-        result = result * PRIME + (this.isCancel() ? 79 : 97);
-        result = result * PRIME + (this.isDismissView() ? 79 : 97);
-        return result;
-    }
-
-    public String toString() {
-        return "BottomSheetLockEvent(cancelable=" + this.isCancelable() + ", delay=" + this.getDelay() + ", shouldRefreshData=" + this.isShouldRefreshData() + ", cancel=" + this.isCancel() + ", dismissView=" + this.isDismissView() + ")";
-    }
-}

+ 23 - 0
app/src/main/java/com/nextcloud/talk/events/ConversationsListFetchDataEvent.kt

@@ -0,0 +1,23 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
+ *
+ * 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.events
+
+class ConversationsListFetchDataEvent

+ 52 - 0
app/src/main/java/com/nextcloud/talk/events/OpenConversationEvent.kt

@@ -0,0 +1,52 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
+ *
+ * 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.events
+
+import android.os.Bundle
+import com.nextcloud.talk.models.json.conversations.Conversation
+
+class OpenConversationEvent {
+    var conversation: Conversation? = null
+    var bundle: Bundle? = null
+
+    constructor(conversation: Conversation?, bundle: Bundle?) {
+        this.conversation = conversation
+        this.bundle = bundle
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+
+        other as OpenConversationEvent
+
+        if (conversation != other.conversation) return false
+        if (bundle != other.bundle) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = conversation?.hashCode() ?: 0
+        result = 31 * result + (bundle?.hashCode() ?: 0)
+        return result
+    }
+}

+ 5 - 1
app/src/main/java/com/nextcloud/talk/jobs/LeaveConversationWorker.java

@@ -21,6 +21,8 @@
 package com.nextcloud.talk.jobs;
 
 import android.content.Context;
+import android.util.Log;
+
 import androidx.annotation.NonNull;
 import androidx.work.Data;
 import androidx.work.Worker;
@@ -48,6 +50,8 @@ import java.net.CookieManager;
 @AutoInjector(NextcloudTalkApplication.class)
 public class LeaveConversationWorker extends Worker {
 
+    private static String TAG = "LeaveConversationWorker";
+
     @Inject
     Retrofit retrofit;
 
@@ -106,7 +110,7 @@ public class LeaveConversationWorker extends Worker {
 
                         @Override
                         public void onError(Throwable e) {
-
+                            Log.e(TAG, "failed to remove self from room", e);
                         }
 
                         @Override

+ 4 - 15
app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.java

@@ -148,23 +148,12 @@ public class Conversation {
         return (canModerate(conversationUser) && !ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL.equals(type));
     }
 
-    public boolean canLeave(UserEntity conversationUser) {
-        if (canLeaveConversation != null) {
-            // Available since APIv2
-            return canLeaveConversation;
-        }
-        // Fallback for APIv1
-        return !canModerate(conversationUser) ||
-                (getType() != ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL && this.participants.size() > 1);
+    public boolean canLeave() {
+        return canLeaveConversation;
     }
 
-    public boolean canDelete(UserEntity conversationUser) {
-        if (canDeleteConversation != null) {
-            // Available since APIv2
-            return canDeleteConversation;
-        }
-        // Fallback for APIv1
-        return canModerate(conversationUser);
+    public boolean canDelete() {
+        return canDeleteConversation;
     }
 
     public String getRoomId() {

+ 2 - 0
app/src/main/java/com/nextcloud/talk/ui/bottom/sheet/ProfileBottomSheet.kt

@@ -63,6 +63,7 @@ class ProfileBottomSheet(val ncApi: NcApi, val userEntity: UserEntity, val route
         ).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
             .subscribe(object : io.reactivex.Observer<HoverCardOverall> {
                 override fun onSubscribe(d: Disposable) {
+                    // unused atm
                 }
 
                 override fun onNext(hoverCardOverall: HoverCardOverall) {
@@ -74,6 +75,7 @@ class ProfileBottomSheet(val ncApi: NcApi, val userEntity: UserEntity, val route
                 }
 
                 override fun onComplete() {
+                    // unused atm
                 }
             })
     }

+ 82 - 0
app/src/main/java/com/nextcloud/talk/ui/dialog/ContactsBottomDialog.kt

@@ -0,0 +1,82 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
+ *
+ * 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.ui.dialog
+
+import android.app.Activity
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import autodagger.AutoInjector
+import com.bluelinelabs.conductor.Conductor
+import com.bluelinelabs.conductor.Router
+import com.bluelinelabs.conductor.RouterTransaction
+import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import com.nextcloud.talk.R
+import com.nextcloud.talk.application.NextcloudTalkApplication
+import com.nextcloud.talk.controllers.bottomsheet.EntryMenuController
+import com.nextcloud.talk.databinding.DialogBottomContactsBinding
+
+@AutoInjector(NextcloudTalkApplication::class)
+class ContactsBottomDialog(
+    val activity: Activity,
+    val bundle: Bundle
+) : BottomSheetDialog(activity, R.style.BottomSheetDialogThemeNoFloating) {
+
+    private var dialogRouter: Router? = null
+
+    private lateinit var binding: DialogBottomContactsBinding
+
+    init {
+        NextcloudTalkApplication.sharedApplication?.componentApplication?.inject(this)
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        binding = DialogBottomContactsBinding.inflate(layoutInflater)
+        setContentView(binding.root)
+        window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+
+        executeEntryMenuController(bundle)
+    }
+
+    private fun executeEntryMenuController(bundle: Bundle) {
+        dialogRouter = Conductor.attachRouter(activity, binding.root, null)
+
+        dialogRouter!!.pushController(
+            RouterTransaction.with(EntryMenuController(bundle))
+                .pushChangeHandler(HorizontalChangeHandler())
+                .popChangeHandler(HorizontalChangeHandler())
+        )
+    }
+
+    override fun onStart() {
+        super.onStart()
+        val bottomSheet = findViewById<View>(R.id.design_bottom_sheet)
+        val behavior = BottomSheetBehavior.from(bottomSheet as View)
+        behavior.state = BottomSheetBehavior.STATE_EXPANDED
+    }
+
+    companion object {
+        private const val TAG = "ContactsBottomDialog"
+    }
+}

+ 318 - 0
app/src/main/java/com/nextcloud/talk/ui/dialog/ConversationsListBottomDialog.kt

@@ -0,0 +1,318 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
+ *
+ * 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.ui.dialog
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.text.TextUtils
+import android.view.View
+import android.view.ViewGroup
+import androidx.work.Data
+import androidx.work.OneTimeWorkRequest
+import androidx.work.WorkManager
+import autodagger.AutoInjector
+import com.bluelinelabs.conductor.Conductor
+import com.bluelinelabs.conductor.Router
+import com.bluelinelabs.conductor.RouterTransaction
+import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import com.nextcloud.talk.R
+import com.nextcloud.talk.api.NcApi
+import com.nextcloud.talk.application.NextcloudTalkApplication
+import com.nextcloud.talk.controllers.ConversationsListController
+import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum
+import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_ADD_FAVORITE
+import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_REMOVE_FAVORITE
+import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_CHANGE_PASSWORD
+import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_SET_PASSWORD
+import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_CLEAR_PASSWORD
+import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_MAKE_PRIVATE
+import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_MAKE_PUBLIC
+import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_RENAME_ROOM
+import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_MARK_AS_READ
+import com.nextcloud.talk.controllers.bottomsheet.EntryMenuController
+import com.nextcloud.talk.controllers.bottomsheet.OperationsMenuController
+import com.nextcloud.talk.databinding.DialogConversationOperationsBinding
+import com.nextcloud.talk.jobs.LeaveConversationWorker
+import com.nextcloud.talk.models.database.CapabilitiesUtil
+import com.nextcloud.talk.models.database.UserEntity
+import com.nextcloud.talk.models.json.conversations.Conversation
+import com.nextcloud.talk.utils.ShareUtils
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_OPERATION_CODE
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
+import com.nextcloud.talk.utils.database.user.UserUtils
+import org.parceler.Parcels
+import javax.inject.Inject
+
+@AutoInjector(NextcloudTalkApplication::class)
+class ConversationsListBottomDialog(
+    val activity: Activity,
+    val controller: ConversationsListController,
+    val currentUser: UserEntity,
+    val conversation: Conversation
+) : BottomSheetDialog(activity, R.style.BottomSheetDialogThemeNoFloating) {
+
+    private var dialogRouter: Router? = null
+
+    private lateinit var binding: DialogConversationOperationsBinding
+
+    @Inject
+    @JvmField
+    var ncApi: NcApi? = null
+
+    @Inject
+    @JvmField
+    var userUtils: UserUtils? = null
+
+    init {
+        NextcloudTalkApplication.sharedApplication?.componentApplication?.inject(this)
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        binding = DialogConversationOperationsBinding.inflate(layoutInflater)
+        setContentView(binding.root)
+        window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+
+        initHeaderDescription()
+        initItemsVisibility()
+        initClickListeners()
+    }
+
+    private fun initHeaderDescription() {
+        if (!TextUtils.isEmpty(conversation.getDisplayName())) {
+            binding.conversationOperationHeader.text = conversation.getDisplayName()
+        } else if (!TextUtils.isEmpty(conversation.getName())) {
+            binding.conversationOperationHeader.text = conversation.getName()
+        }
+    }
+
+    private fun initItemsVisibility() {
+        val hasFavoritesCapability = CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "favorites")
+        val canModerate = conversation.canModerate(currentUser)
+
+        binding.conversationOperationRemoveFavorite.visibility = setVisibleIf(
+            hasFavoritesCapability && conversation.isFavorite()
+        )
+        binding.conversationOperationAddFavorite.visibility = setVisibleIf(
+            hasFavoritesCapability && !conversation.isFavorite()
+        )
+
+        binding.conversationOperationMarkAsRead.visibility = setVisibleIf(
+            conversation.unreadMessages > 0 && CapabilitiesUtil.canSetChatReadMarker(currentUser)
+        )
+
+        binding.conversationOperationRename.visibility = setVisibleIf(
+            conversation.isNameEditable(currentUser)
+        )
+
+        binding.conversationOperationMakePublic.visibility = setVisibleIf(
+            canModerate && !conversation.isPublic
+        )
+
+        binding.conversationOperationChangePassword.visibility = setVisibleIf(
+            canModerate && conversation.isHasPassword && conversation.isPublic
+        )
+
+        binding.conversationOperationClearPassword.visibility = setVisibleIf(
+            canModerate && conversation.isHasPassword && conversation.isPublic
+        )
+
+        binding.conversationOperationSetPassword.visibility = setVisibleIf(
+            canModerate && !conversation.isHasPassword && conversation.isPublic
+        )
+
+        binding.conversationOperationDelete.visibility = setVisibleIf(
+            canModerate
+        )
+
+        binding.conversationOperationShareLink.visibility = setVisibleIf(
+            conversation.isPublic
+        )
+
+        binding.conversationOperationMakePrivate.visibility = setVisibleIf(
+            conversation.isPublic && canModerate
+        )
+
+        binding.conversationOperationLeave.visibility = setVisibleIf(
+            conversation.canLeave() &&
+                // leaving is by api not possible for the last user with moderator permissions.
+                // for now, hide this option for all moderators.
+                !conversation.canModerate(currentUser)
+        )
+    }
+
+    private fun setVisibleIf(boolean: Boolean): Int {
+        return if (boolean) {
+            View.VISIBLE
+        } else {
+            View.GONE
+        }
+    }
+
+    private fun initClickListeners() {
+        binding.conversationOperationAddFavorite.setOnClickListener {
+            executeOperationsMenuController(OPS_CODE_ADD_FAVORITE)
+        }
+
+        binding.conversationOperationRemoveFavorite.setOnClickListener {
+            executeOperationsMenuController(OPS_CODE_REMOVE_FAVORITE)
+        }
+
+        binding.conversationOperationLeave.setOnClickListener {
+            val dataBuilder = Data.Builder()
+            dataBuilder.putString(KEY_ROOM_TOKEN, conversation.getToken())
+            dataBuilder.putLong(KEY_INTERNAL_USER_ID, currentUser.id)
+            val data = dataBuilder.build()
+
+            val leaveConversationWorker =
+                OneTimeWorkRequest.Builder(LeaveConversationWorker::class.java).setInputData(
+                    data
+                ).build()
+            WorkManager.getInstance().enqueue(leaveConversationWorker)
+
+            dismiss()
+        }
+
+        binding.conversationOperationDelete.setOnClickListener {
+            if (!TextUtils.isEmpty(conversation.getToken())) {
+                val bundle = Bundle()
+                bundle.putLong(KEY_INTERNAL_USER_ID, currentUser.id)
+                bundle.putParcelable(KEY_ROOM, Parcels.wrap(conversation))
+
+                controller.openLovelyDialogWithIdAndBundle(
+                    ConversationsListController.ID_DELETE_CONVERSATION_DIALOG,
+                    bundle
+                )
+            }
+
+            dismiss()
+        }
+
+        binding.conversationOperationMakePublic.setOnClickListener {
+            executeOperationsMenuController(OPS_CODE_MAKE_PUBLIC)
+        }
+
+        binding.conversationOperationMakePrivate.setOnClickListener {
+            executeOperationsMenuController(OPS_CODE_MAKE_PRIVATE)
+        }
+
+        binding.conversationOperationChangePassword.setOnClickListener {
+            executeEntryMenuController(OPS_CODE_CHANGE_PASSWORD)
+        }
+
+        binding.conversationOperationClearPassword.setOnClickListener {
+            executeOperationsMenuController(OPS_CODE_CLEAR_PASSWORD)
+        }
+
+        binding.conversationOperationSetPassword.setOnClickListener {
+            executeEntryMenuController(OPS_CODE_SET_PASSWORD)
+        }
+
+        binding.conversationOperationRename.setOnClickListener {
+            executeEntryMenuController(OPS_CODE_RENAME_ROOM)
+        }
+
+        binding.conversationOperationMarkAsRead.setOnClickListener {
+            executeOperationsMenuController(OPS_CODE_MARK_AS_READ)
+        }
+
+        binding.conversationOperationShareLink.setOnClickListener {
+            val sendIntent: Intent = Intent().apply {
+                action = Intent.ACTION_SEND
+                type = "text/plain"
+                putExtra(
+                    Intent.EXTRA_SUBJECT,
+                    String.format(
+                        activity.resources.getString(R.string.nc_share_subject),
+                        activity.resources.getString(R.string.nc_app_product_name)
+                    )
+                )
+                // password should not be shared!!
+                putExtra(
+                    Intent.EXTRA_TEXT,
+                    ShareUtils.getStringForIntent(activity, null, userUtils, conversation)
+                )
+            }
+
+            val shareIntent = Intent.createChooser(sendIntent, null)
+            activity.startActivity(shareIntent)
+
+            dismiss()
+        }
+    }
+
+    private fun executeOperationsMenuController(operation: ConversationOperationEnum) {
+        val bundle = Bundle()
+        bundle.putParcelable(KEY_ROOM, Parcels.wrap(conversation))
+        bundle.putSerializable(KEY_OPERATION_CODE, operation)
+
+        binding.operationItemsLayout.visibility = View.GONE
+
+        dialogRouter = Conductor.attachRouter(activity, binding.root, null)
+
+        dialogRouter!!.pushController(
+            RouterTransaction.with(OperationsMenuController(bundle))
+                .pushChangeHandler(HorizontalChangeHandler())
+                .popChangeHandler(HorizontalChangeHandler())
+        )
+
+        controller.fetchData()
+    }
+
+    private fun executeEntryMenuController(operation: ConversationOperationEnum) {
+        val bundle = Bundle()
+        bundle.putParcelable(KEY_ROOM, Parcels.wrap(conversation))
+        bundle.putSerializable(KEY_OPERATION_CODE, operation)
+
+        binding.operationItemsLayout.visibility = View.GONE
+
+        dialogRouter = Conductor.attachRouter(activity, binding.root, null)
+
+        dialogRouter!!.pushController(
+
+            // TODO: refresh conversation list after EntryMenuController finished (throw event? / pass controller
+            //  into EntryMenuController to execute fetch data... ?!)
+            // for example if you set a password, the dialog items should be refreshed for the next time you open it
+            // without to manually have to refresh the conversations list
+            // also see BottomSheetLockEvent ??
+
+            RouterTransaction.with(EntryMenuController(bundle))
+                .pushChangeHandler(HorizontalChangeHandler())
+                .popChangeHandler(HorizontalChangeHandler())
+        )
+    }
+
+    override fun onStart() {
+        super.onStart()
+        val bottomSheet = findViewById<View>(R.id.design_bottom_sheet)
+        val behavior = BottomSheetBehavior.from(bottomSheet as View)
+        behavior.state = BottomSheetBehavior.STATE_EXPANDED
+    }
+
+    companion object {
+        private const val TAG = "ConversationOperationDialog"
+    }
+}

+ 0 - 92
app/src/main/java/com/nextcloud/talk/utils/KeyboardUtils.java

@@ -1,92 +0,0 @@
-/*
- * Copyright 2015 Mike Penz All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.nextcloud.talk.utils;
-
-import android.app.Activity;
-import android.graphics.Rect;
-import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.inputmethod.InputMethodManager;
-
-/**
- * Created by mikepenz on 14.03.15.
- * This class implements a hack to change the layout padding on bottom if the keyboard is shown
- * to allow long lists with editTextViews
- * Basic idea for this solution found here: http://stackoverflow.com/a/9108219/325479
- */
-public class KeyboardUtils {
-    private View decorView;
-    private View contentView;
-    private boolean isUsedInBottomSheet;
-    //a small helper to allow showing the editText focus
-    ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
-        @Override
-        public void onGlobalLayout() {
-            Rect r = new Rect();
-            //r will be populated with the coordinates of your view that area still visible.
-            decorView.getWindowVisibleDisplayFrame(r);
-
-            //get screen height and calculate the difference with the useable area from the r
-            int height = decorView.getContext().getResources().getDisplayMetrics().heightPixels;
-
-            int diff = height - r.bottom;
-
-            boolean shouldSetBottomPadding = (isUsedInBottomSheet && diff != 0) || (diff > 0);
-
-            if (shouldSetBottomPadding) {
-                if (contentView.getPaddingBottom() != diff) {
-                    //set the padding of the contentView for the keyboard
-                    contentView.setPadding(0, 0, 0, diff);
-                }
-            } else {
-                //check if the padding is != initialBottomPadding (if yes reset the padding)
-                if (contentView.getPaddingBottom() != 0) {
-                    //reset the padding of the contentView
-                    contentView.setPadding(0, 0, 0, 0);
-                }
-            }
-        }
-    };
-
-    public KeyboardUtils(Activity act, View contentView, boolean isUsedInBottomSheet) {
-        this.decorView = act.getWindow().getDecorView();
-        this.contentView = contentView;
-        this.isUsedInBottomSheet = isUsedInBottomSheet;
-
-        decorView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
-    }
-
-    /**
-     * Helper to hide the keyboard
-     *
-     * @param act
-     */
-    public static void hideKeyboard(Activity act) {
-        if (act != null && act.getCurrentFocus() != null) {
-            InputMethodManager inputMethodManager = (InputMethodManager) act.getSystemService(Activity.INPUT_METHOD_SERVICE);
-            inputMethodManager.hideSoftInputFromWindow(act.getCurrentFocus().getWindowToken(), 0);
-        }
-    }
-
-    public void enable() {
-        decorView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
-    }
-
-    public void disable() {
-        decorView.getViewTreeObserver().removeOnGlobalLayoutListener(onGlobalLayoutListener);
-    }
-}

+ 2 - 57
app/src/main/java/com/nextcloud/talk/utils/ShareUtils.java

@@ -16,29 +16,19 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * Part of the code in ShareUtils was inspired by BottomSheet under the Apache licence
- * located here: https://github.com/Kennyc1012/BottomSheet/blob/master/library/src/main/java/com/kennyc/bottomsheet/BottomSheet.java#L425
  */
 
 package com.nextcloud.talk.utils;
 
 import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
-import androidx.annotation.Nullable;
-import com.kennyc.bottomsheet.adapters.AppAdapter;
+
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.json.conversations.Conversation;
 import com.nextcloud.talk.utils.database.user.UserUtils;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
+import androidx.annotation.Nullable;
 
 public class ShareUtils {
 
@@ -58,49 +48,4 @@ public class ShareUtils {
 
         return shareString;
     }
-
-    public static List<AppAdapter.AppInfo> getShareApps(Context context, Intent intent,
-                                                        @Nullable Set<String> appsFilter, @Nullable Set<String> toExclude) {
-
-        if (context == null || intent == null) return null;
-
-        PackageManager manager = context.getPackageManager();
-        List<ResolveInfo> apps = manager.queryIntentActivities(intent, 0);
-
-        if (apps != null && !apps.isEmpty()) {
-            List<AppAdapter.AppInfo> appResources = new ArrayList<>(apps.size());
-            boolean shouldCheckPackages = appsFilter != null && !appsFilter.isEmpty();
-
-            for (ResolveInfo resolveInfo : apps) {
-                String packageName = resolveInfo.activityInfo.packageName;
-
-                if (shouldCheckPackages && !appsFilter.contains(packageName)) {
-                    continue;
-                }
-
-                String title = resolveInfo.loadLabel(manager).toString();
-                String name = resolveInfo.activityInfo.name;
-                Drawable drawable = resolveInfo.loadIcon(manager);
-                appResources.add(new AppAdapter.AppInfo(title, packageName, name, drawable));
-            }
-
-            if (toExclude != null && !toExclude.isEmpty()) {
-                List<AppAdapter.AppInfo> toRemove = new ArrayList<>();
-
-                for (AppAdapter.AppInfo appInfo : appResources) {
-                    if (toExclude.contains(appInfo.packageName)) {
-                        toRemove.add(appInfo);
-                    }
-                }
-
-                if (!toRemove.isEmpty()) appResources.removeAll(toRemove);
-            }
-
-            return appResources;
-
-        }
-
-        return null;
-    }
-
 }

+ 0 - 49
app/src/main/res/drawable/ic_mimetype_folder_public.xml

@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Nextcloud Talk application
-  ~
-  ~ @author Mario Danic
-  ~ Copyright (C) 2017-2019 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/>.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="16dp"
-    android:height="16dp"
-    android:viewportWidth="16"
-    android:viewportHeight="16">
-
-    <path
-        android:fillColor="#0082c9"
-        android:pathData="M1.5,2 C1.25,2,1,2.25,1,2.5 L1,13.5 C1,13.76,1.24,14,1.5,14 L14.5,14
-C14.76,14,15,13.759,15,13.5 L15,4.5 C15,4.25,14.75,4,14.5,4 L8,4 L6,2 L1.5,2 Z
-M9.834,5.5 C10.268,5.4981,10.7,5.6545,11.02,5.9746
-C11.66,6.6153,11.66,7.7166,11.02,8.3574 L10.17,9.207
-C10.197,8.7574,10.151,8.4573,9.8633,8.0508 L10.287,7.625
-C10.562,7.3498,10.539,6.9967,10.289,6.709
-C10.019,6.4391,9.645,6.4388,9.373,6.7109 L7.7266,8.3574
-C7.4437,8.6404,7.431,8.9797,7.7246,9.2734 L6.9941,10.006
-C6.7069,9.7186,6.5083,9.3329,6.4824,8.8984
-C6.4568,8.464,6.6281,7.9913,6.9941,7.625 L8.6406,5.9766
-C8.9651,5.6619,9.4004,5.5019,9.834,5.5 Z M9.0078,7.9902
-C9.296,8.2773,9.4947,8.6645,9.5195,9.0996
-C9.5449,9.5348,9.3746,10.006,9.0078,10.373 L7.3594,12.02
-C6.7198,12.659,5.6199,12.659,4.9805,12.02
-C4.3409,11.38,4.339,10.282,4.9805,9.6406 L5.8418,8.7773
-C5.8155,9.2266,5.8645,9.5278,6.1523,9.9336 L5.7129,10.373
-C5.4541,10.632,5.3887,10.963,5.7129,11.287
-C5.9567,11.531,6.2686,11.594,6.6289,11.287 L8.2754,9.6406
-C8.5401,9.3758,8.562,9.0075,8.2773,8.7227 L9.0078,7.9902 Z" />
-</vector>

+ 0 - 34
app/src/main/res/drawable/ic_mimetype_folder_starred.xml

@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Nextcloud Talk application
-  ~
-  ~ @author Mario Danic
-  ~ Copyright (C) 2017-2019 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/>.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="16dp"
-    android:height="16dp"
-    android:viewportWidth="16"
-    android:viewportHeight="16">
-
-    <path
-        android:fillColor="#0082c9"
-        android:pathData="M1.5,2 C1.25,2,1,2.25,1,2.5 L1,13.5 C1,13.76,1.24,14,1.5,14 L14.5,14
-C14.76,14,15,13.759,15,13.5 L15,4.5 C15,4.25,14.75,4,14.5,4 L8,4 L6,2 L1.5,2 Z
-M8,5.25 L9.1,7.9004 L12,8.125 L9.75,10 L10.5,12.75 L8,11.199 L5.5,12.75 L6.25,10
-L4,8.125 L6.9,7.9004 L8,5.25 Z" />
-</vector>

+ 3 - 1
app/src/main/res/layout/controller_entry_menu.xml

@@ -52,7 +52,9 @@
         android:layout_marginEnd="@dimen/standard_half_margin"
         android:layout_toStartOf="@id/smileyButton"
         app:errorTextAppearance="@style/ErrorAppearance"
-        app:passwordToggleTint="@color/grey_600">
+        app:passwordToggleTint="@color/grey_600"
+        app:boxStrokeColor="@color/colorPrimary"
+        app:hintTextColor="@color/colorPrimary">
 
         <com.nextcloud.talk.utils.EmojiTextInputEditText
             android:id="@+id/text_edit"

+ 2 - 2
app/src/main/res/layout/controller_operations_menu.xml

@@ -19,9 +19,9 @@
   -->
 
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/controller_operations_view"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:background="@color/bg_default">
+    android:layout_height="wrap_content">
 
     <ImageView
         android:id="@+id/result_image_view"

+ 13 - 14
app/src/main/res/layout/controller_call_menu.xml → app/src/main/res/layout/dialog_bottom_contacts.xml

@@ -1,8 +1,11 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="utf-8"?>
+<!--
   ~ Nextcloud Talk application
   ~
-  ~ @author Mario Danic
-  ~ Copyright (C) 2017 Mario Danic
+  ~ @author Marcel Hibbe
+  ~ @author Andy Scherzinger
+  ~ Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
+  ~ Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
   ~
   ~ 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
@@ -17,17 +20,13 @@
   ~ 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:tools="http://schemas.android.com/tools"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:background="@color/bg_default">
-
-    <androidx.recyclerview.widget.RecyclerView
-        android:id="@+id/recycler_view"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
-
+    android:background="@color/bg_bottom_sheet"
+    android:orientation="vertical"
+    android:paddingStart="@dimen/standard_padding"
+    android:paddingEnd="@dimen/standard_padding"
+    android:paddingBottom="@dimen/standard_half_padding">
 
-</RelativeLayout>
+</LinearLayout>

+ 376 - 0
app/src/main/res/layout/dialog_conversation_operations.xml

@@ -0,0 +1,376 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Nextcloud Talk application
+  ~
+  ~ @author Marcel Hibbe
+  ~ @author Andy Scherzinger
+  ~ Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
+  ~ Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
+  ~
+  ~ 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/>.
+  -->
+<LinearLayout 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:background="@color/bg_bottom_sheet"
+    android:orientation="vertical"
+    android:paddingStart="@dimen/standard_padding"
+    android:paddingEnd="@dimen/standard_padding"
+    android:paddingBottom="@dimen/standard_half_padding">
+
+    <LinearLayout
+        android:id="@+id/operation_items_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        tools:ignore="UseCompoundDrawables">
+
+        <TextView
+            android:id="@+id/conversation_operation_header"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/bottom_sheet_item_height"
+            android:gravity="start|center_vertical"
+            android:textColor="@color/medium_emphasis_text"
+            android:textSize="@dimen/bottom_sheet_text_size"
+            tools:text="conversation name" />
+
+        <LinearLayout
+            android:id="@+id/conversation_operation_remove_favorite"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/bottom_sheet_item_height"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            tools:ignore="UseCompoundDrawables">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@null"
+                android:src="@drawable/ic_star_border_black_24dp"
+                app:tint="@color/grey_600" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="start|center_vertical"
+                android:paddingStart="40dp"
+                android:paddingEnd="@dimen/zero"
+                android:text="@string/nc_remove_from_favorites"
+                android:textAlignment="viewStart"
+                android:textColor="@color/high_emphasis_text"
+                android:textSize="@dimen/bottom_sheet_text_size" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/conversation_operation_add_favorite"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/bottom_sheet_item_height"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            tools:ignore="UseCompoundDrawables">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@null"
+                android:src="@drawable/ic_star_black_24dp"
+                app:tint="@color/grey_600" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="start|center_vertical"
+                android:paddingStart="40dp"
+                android:paddingEnd="@dimen/zero"
+                android:text="@string/nc_add_to_favorites"
+                android:textAlignment="viewStart"
+                android:textColor="@color/high_emphasis_text"
+                android:textSize="@dimen/bottom_sheet_text_size" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/conversation_operation_mark_as_read"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/bottom_sheet_item_height"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            tools:ignore="UseCompoundDrawables">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@null"
+                android:src="@drawable/ic_eye"
+                app:tint="@color/grey_600" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="start|center_vertical"
+                android:paddingStart="40dp"
+                android:paddingEnd="@dimen/zero"
+                android:text="@string/nc_mark_as_read"
+                android:textAlignment="viewStart"
+                android:textColor="@color/high_emphasis_text"
+                android:textSize="@dimen/bottom_sheet_text_size" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/conversation_operation_rename"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/bottom_sheet_item_height"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            tools:ignore="UseCompoundDrawables">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@null"
+                android:src="@drawable/ic_pencil_grey600_24dp"
+                app:tint="@color/grey_600" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="start|center_vertical"
+                android:paddingStart="40dp"
+                android:paddingEnd="@dimen/zero"
+                android:text="@string/nc_rename"
+                android:textAlignment="viewStart"
+                android:textColor="@color/high_emphasis_text"
+                android:textSize="@dimen/bottom_sheet_text_size" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/conversation_operation_make_public"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/bottom_sheet_item_height"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            tools:ignore="UseCompoundDrawables">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@null"
+                android:src="@drawable/ic_link_grey600_24px"
+                app:tint="@color/grey_600" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="start|center_vertical"
+                android:paddingStart="40dp"
+                android:paddingEnd="@dimen/zero"
+                android:text="@string/nc_make_call_public"
+                android:textAlignment="viewStart"
+                android:textColor="@color/high_emphasis_text"
+                android:textSize="@dimen/bottom_sheet_text_size" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/conversation_operation_change_password"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/bottom_sheet_item_height"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            tools:ignore="UseCompoundDrawables">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@null"
+                android:src="@drawable/ic_lock_grey600_24px"
+                app:tint="@color/grey_600" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="start|center_vertical"
+                android:paddingStart="40dp"
+                android:paddingEnd="@dimen/zero"
+                android:text="@string/nc_change_password"
+                android:textAlignment="viewStart"
+                android:textColor="@color/high_emphasis_text"
+                android:textSize="@dimen/bottom_sheet_text_size" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/conversation_operation_clear_password"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/bottom_sheet_item_height"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            tools:ignore="UseCompoundDrawables">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@null"
+                android:src="@drawable/ic_lock_open_grey600_24dp"
+                app:tint="@color/grey_600" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="start|center_vertical"
+                android:paddingStart="40dp"
+                android:paddingEnd="@dimen/zero"
+                android:text="@string/nc_clear_password"
+                android:textAlignment="viewStart"
+                android:textColor="@color/high_emphasis_text"
+                android:textSize="@dimen/bottom_sheet_text_size" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/conversation_operation_set_password"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/bottom_sheet_item_height"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            tools:ignore="UseCompoundDrawables">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@null"
+                android:src="@drawable/ic_lock_plus_grey600_24dp"
+                app:tint="@color/grey_600" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="start|center_vertical"
+                android:paddingStart="40dp"
+                android:paddingEnd="@dimen/zero"
+                android:text="@string/nc_set_password"
+                android:textAlignment="viewStart"
+                android:textColor="@color/high_emphasis_text"
+                android:textSize="@dimen/bottom_sheet_text_size" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/conversation_operation_delete"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/bottom_sheet_item_height"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            tools:ignore="UseCompoundDrawables">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@null"
+                android:src="@drawable/ic_delete_grey600_24dp"
+                app:tint="@color/grey_600" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="start|center_vertical"
+                android:paddingStart="40dp"
+                android:paddingEnd="@dimen/zero"
+                android:text="@string/nc_delete_call"
+                android:textAlignment="viewStart"
+                android:textColor="@color/high_emphasis_text"
+                android:textSize="@dimen/bottom_sheet_text_size" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/conversation_operation_share_link"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/bottom_sheet_item_height"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            tools:ignore="UseCompoundDrawables">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@null"
+                android:src="@drawable/ic_link_grey600_24px"
+                app:tint="@color/grey_600" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="start|center_vertical"
+                android:paddingStart="40dp"
+                android:paddingEnd="@dimen/zero"
+                android:text="@string/nc_share_link"
+                android:textAlignment="viewStart"
+                android:textColor="@color/high_emphasis_text"
+                android:textSize="@dimen/bottom_sheet_text_size" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/conversation_operation_make_private"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/bottom_sheet_item_height"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            tools:ignore="UseCompoundDrawables">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@null"
+                android:src="@drawable/ic_group_grey600_24px"
+                app:tint="@color/grey_600" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="start|center_vertical"
+                android:paddingStart="40dp"
+                android:paddingEnd="@dimen/zero"
+                android:text="@string/nc_make_call_private"
+                android:textAlignment="viewStart"
+                android:textColor="@color/high_emphasis_text"
+                android:textSize="@dimen/bottom_sheet_text_size" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/conversation_operation_leave"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/bottom_sheet_item_height"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            tools:ignore="UseCompoundDrawables">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@null"
+                android:src="@drawable/ic_exit_to_app_black_24dp"
+                app:tint="@color/grey_600" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="start|center_vertical"
+                android:paddingStart="40dp"
+                android:paddingEnd="@dimen/zero"
+                android:text="@string/nc_leave"
+                android:textAlignment="viewStart"
+                android:textColor="@color/high_emphasis_text"
+                android:textSize="@dimen/bottom_sheet_text_size" />
+        </LinearLayout>
+
+    </LinearLayout>
+
+</LinearLayout>

+ 1 - 0
app/src/main/res/values/dimens.xml

@@ -24,6 +24,7 @@
 
     <dimen name="item_height">72dp</dimen>
     <dimen name="bottom_sheet_item_height">56dp</dimen>
+    <dimen name="bottom_sheet_text_size">16sp</dimen>
     <dimen name="small_item_height">48dp</dimen>
 
     <dimen name="min_size_clickable_area">48dp</dimen>

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

@@ -165,8 +165,6 @@
     <string name="nc_profile_personal_info_title">Personal Info</string>
 
     <!-- Conversation menu -->
-    <string name="nc_start_conversation">Start a conversation</string>
-    <string name="nc_configure_room">Configure conversation</string>
     <string name="nc_leave">Leave conversation</string>
     <string name="nc_clear_history">Delete all messages</string>
     <string name="nc_clear_history_warning">Do you really want to delete all messages in this conversation?</string>
@@ -176,7 +174,6 @@
     <string name="nc_change_password">Change password</string>
     <string name="nc_clear_password">Clear password</string>
     <string name="nc_share_link">Share link</string>
-    <string name="nc_share_link_via">Share link via</string>
     <string name="nc_make_call_public">Make conversation public</string>
     <string name="nc_make_call_private">Make conversation private</string>
     <string name="nc_delete_call">Delete conversation</string>

+ 6 - 1
app/src/main/res/values/styles.xml

@@ -72,7 +72,7 @@
         <item name="android:textSize">12sp</item>
     </style>
 
-    <style name="ListItem" parent="BottomSheet.ListItem.TextAppearance">
+    <style name="ListItem">
         <item name="android:textColor">@color/high_emphasis_text</item>
         <item name="android:textSize">16sp</item>
     </style>
@@ -235,4 +235,9 @@
         <item name="android:colorControlNormal">#ffffff</item>
     </style>
 
+    <style name="BottomSheetDialogThemeNoFloating" parent="Theme.Design.Light.BottomSheetDialog">
+        <item name="android:windowIsFloating">false</item>
+        <item name="android:windowSoftInputMode">adjustResize</item>
+    </style>
+
 </resources>

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

@@ -1 +1 @@
-552
+542

+ 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 221 warnings</span>
+      <span class="mdl-layout-title">Lint Report: 1 error and 218 warnings</span>