浏览代码

Merge pull request #2313 from nextcloud/chore/noid/conversationslistcontroller-kotlin

Convert ConversationsListController to Kotlin, and get rid of ButterKnife
Andy Scherzinger 2 年之前
父节点
当前提交
5c6d001750
共有 24 个文件被更改,包括 1346 次插入1766 次删除
  1. 0 3
      app/build.gradle
  2. 0 1
      app/lint.xml
  3. 2 2
      app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.kt
  4. 2 2
      app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt
  5. 2 2
      app/src/main/java/com/nextcloud/talk/controllers/ContactsController.kt
  6. 2 2
      app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt
  7. 0 1386
      app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java
  8. 1298 0
      app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt
  9. 2 2
      app/src/main/java/com/nextcloud/talk/controllers/GeocodingController.kt
  10. 2 2
      app/src/main/java/com/nextcloud/talk/controllers/LocationPickerController.kt
  11. 2 2
      app/src/main/java/com/nextcloud/talk/controllers/LockedController.kt
  12. 2 2
      app/src/main/java/com/nextcloud/talk/controllers/ProfileController.kt
  13. 2 2
      app/src/main/java/com/nextcloud/talk/controllers/RingtoneSelectionController.kt
  14. 2 2
      app/src/main/java/com/nextcloud/talk/controllers/ServerSelectionController.kt
  15. 2 2
      app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt
  16. 2 2
      app/src/main/java/com/nextcloud/talk/controllers/SwitchAccountController.kt
  17. 2 2
      app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.kt
  18. 0 271
      app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.java
  19. 17 12
      app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.kt
  20. 0 58
      app/src/main/java/com/nextcloud/talk/controllers/base/ButterKnifeController.kt
  21. 2 2
      app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/EntryMenuController.kt
  22. 2 2
      app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.kt
  23. 0 4
      app/src/main/java/com/nextcloud/talk/ui/dialog/ConversationsListBottomDialog.kt
  24. 1 1
      detekt.yml

+ 0 - 3
app/build.gradle

@@ -158,7 +158,6 @@ android {
 
 ext {
     androidxCameraVersion = "1.1.0"
-    butterknifeVersion = "10.2.3"
     coilKtVersion = "2.1.0"
     daggerVersion = "2.43.2"
     lifecycleVersion = '2.5.1'
@@ -256,8 +255,6 @@ dependencies {
 
     implementation "org.parceler:parceler-api:$parcelerVersion"
     implementation 'net.orange-box.storebox:storebox-lib:1.4.0'
-    implementation "com.jakewharton:butterknife:${butterknifeVersion}"
-    kapt "com.jakewharton:butterknife-compiler:${butterknifeVersion}"
     implementation 'eu.davidea:flexible-adapter:5.1.0'
     implementation 'eu.davidea:flexible-adapter-ui:1.0.0'
     implementation fileTree(downloadWebRtc.libFile.path)

+ 0 - 1
app/lint.xml

@@ -32,7 +32,6 @@
 
     <issue id="ObsoleteLintCustomCheck" severity="warning">
         <ignore path="**/jetified-annotation-experimental-1.**/**/lint.jar" />
-        <ignore path="**/butterknife-runtime-10.2.**/**/lint.jar" />
         <ignore path="**/jetified-conductor-2.**/**/lint.jar" />
     </issue>
 </lint>

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

@@ -38,7 +38,7 @@ import com.nextcloud.talk.R
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
-import com.nextcloud.talk.controllers.base.NewBaseController
+import com.nextcloud.talk.controllers.base.BaseController
 import com.nextcloud.talk.controllers.util.viewBinding
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.databinding.ControllerAccountVerificationBinding
@@ -73,7 +73,7 @@ import javax.inject.Inject
 
 @AutoInjector(NextcloudTalkApplication::class)
 class AccountVerificationController(args: Bundle? = null) :
-    NewBaseController(
+    BaseController(
         R.layout.controller_account_verification,
         args
     ) {

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

@@ -125,7 +125,7 @@ import com.nextcloud.talk.adapters.messages.VoiceMessageInterface
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.callbacks.MentionAutocompleteCallback
-import com.nextcloud.talk.controllers.base.NewBaseController
+import com.nextcloud.talk.controllers.base.BaseController
 import com.nextcloud.talk.controllers.util.viewBinding
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.databinding.ControllerChatBinding
@@ -211,7 +211,7 @@ import kotlin.math.roundToInt
 
 @AutoInjector(NextcloudTalkApplication::class)
 class ChatController(args: Bundle) :
-    NewBaseController(
+    BaseController(
         R.layout.controller_chat,
         args
     ),

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

@@ -49,7 +49,7 @@ import com.nextcloud.talk.adapters.items.GenericTextHeaderItem
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
-import com.nextcloud.talk.controllers.base.NewBaseController
+import com.nextcloud.talk.controllers.base.BaseController
 import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum
 import com.nextcloud.talk.controllers.util.viewBinding
 import com.nextcloud.talk.data.user.model.User
@@ -89,7 +89,7 @@ import javax.inject.Inject
 
 @AutoInjector(NextcloudTalkApplication::class)
 class ContactsController(args: Bundle) :
-    NewBaseController(R.layout.controller_contacts_rv),
+    BaseController(R.layout.controller_contacts_rv),
     SearchView.OnQueryTextListener,
     FlexibleAdapter.OnItemClickListener {
     private val binding: ControllerContactsRvBinding by viewBinding(ControllerContactsRvBinding::bind)

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

@@ -55,7 +55,7 @@ import com.nextcloud.talk.R
 import com.nextcloud.talk.adapters.items.ParticipantItem
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
-import com.nextcloud.talk.controllers.base.NewBaseController
+import com.nextcloud.talk.controllers.base.BaseController
 import com.nextcloud.talk.controllers.bottomsheet.items.BasicListItemWithImage
 import com.nextcloud.talk.controllers.bottomsheet.items.listItemsWithImage
 import com.nextcloud.talk.controllers.util.viewBinding
@@ -99,7 +99,7 @@ import javax.inject.Inject
 
 @AutoInjector(NextcloudTalkApplication::class)
 class ConversationInfoController(args: Bundle) :
-    NewBaseController(
+    BaseController(
         R.layout.controller_conversation_info,
         args
     ),

+ 0 - 1386
app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java

@@ -1,1386 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @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
- * 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;
-
-import android.animation.AnimatorInflater;
-import android.annotation.SuppressLint;
-import android.app.SearchManager;
-import android.content.ClipData;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
-import android.widget.Toast;
-
-import com.bluelinelabs.conductor.RouterTransaction;
-import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
-import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler;
-import com.facebook.common.executors.UiThreadImmediateExecutorService;
-import com.facebook.common.references.CloseableReference;
-import com.facebook.datasource.DataSource;
-import com.facebook.drawee.backends.pipeline.Fresco;
-import com.facebook.imagepipeline.core.ImagePipeline;
-import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
-import com.facebook.imagepipeline.image.CloseableImage;
-import com.facebook.imagepipeline.request.ImageRequest;
-import com.google.android.material.button.MaterialButton;
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.google.android.material.floatingactionbutton.FloatingActionButton;
-import com.nextcloud.talk.R;
-import com.nextcloud.talk.activities.MainActivity;
-import com.nextcloud.talk.adapters.items.ConversationItem;
-import com.nextcloud.talk.adapters.items.GenericTextHeaderItem;
-import com.nextcloud.talk.adapters.items.LoadMoreResultsItem;
-import com.nextcloud.talk.adapters.items.MessageResultItem;
-import com.nextcloud.talk.adapters.items.MessagesTextHeaderItem;
-import com.nextcloud.talk.api.NcApi;
-import com.nextcloud.talk.application.NextcloudTalkApplication;
-import com.nextcloud.talk.controllers.base.BaseController;
-import com.nextcloud.talk.data.user.model.User;
-import com.nextcloud.talk.events.ConversationsListFetchDataEvent;
-import com.nextcloud.talk.events.EventStatus;
-import com.nextcloud.talk.interfaces.ConversationMenuInterface;
-import com.nextcloud.talk.jobs.AccountRemovalWorker;
-import com.nextcloud.talk.jobs.ContactAddressBookWorker;
-import com.nextcloud.talk.jobs.DeleteConversationWorker;
-import com.nextcloud.talk.jobs.UploadAndShareFilesWorker;
-import com.nextcloud.talk.messagesearch.MessageSearchHelper;
-import com.nextcloud.talk.models.domain.SearchMessageEntry;
-import com.nextcloud.talk.models.json.conversations.Conversation;
-import com.nextcloud.talk.models.json.status.Status;
-import com.nextcloud.talk.models.json.statuses.StatusesOverall;
-import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository;
-import com.nextcloud.talk.ui.dialog.ChooseAccountDialogFragment;
-import com.nextcloud.talk.ui.dialog.ConversationsListBottomDialog;
-import com.nextcloud.talk.ui.theme.ViewThemeUtils;
-import com.nextcloud.talk.users.UserManager;
-import com.nextcloud.talk.utils.ApiUtils;
-import com.nextcloud.talk.utils.AttendeePermissionsUtil;
-import com.nextcloud.talk.utils.ClosedInterfaceImpl;
-import com.nextcloud.talk.utils.ConductorRemapping;
-import com.nextcloud.talk.utils.DisplayUtils;
-import com.nextcloud.talk.utils.UriUtils;
-import com.nextcloud.talk.utils.bundle.BundleKeys;
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew;
-import com.nextcloud.talk.utils.preferences.AppPreferences;
-import com.nextcloud.talk.utils.rx.SearchViewObservable;
-import com.nextcloud.ui.popupbubble.PopupBubble;
-
-import org.apache.commons.lang3.builder.CompareToBuilder;
-import org.greenrobot.eventbus.EventBus;
-import org.greenrobot.eventbus.Subscribe;
-import org.greenrobot.eventbus.ThreadMode;
-import org.jetbrains.annotations.NotNull;
-import org.parceler.Parcels;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.TimeUnit;
-
-import javax.inject.Inject;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.widget.SearchView;
-import androidx.core.content.res.ResourcesCompat;
-import androidx.core.graphics.drawable.RoundedBitmapDrawable;
-import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
-import androidx.core.view.MenuItemCompat;
-import androidx.fragment.app.DialogFragment;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
-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;
-import eu.davidea.flexibleadapter.items.IHeader;
-import io.reactivex.Observable;
-import io.reactivex.Observer;
-import io.reactivex.android.schedulers.AndroidSchedulers;
-import io.reactivex.disposables.Disposable;
-import io.reactivex.schedulers.Schedulers;
-import retrofit2.HttpException;
-
-import static com.nextcloud.talk.utils.Mimetype.TEXT_PLAIN;
-
-@AutoInjector(NextcloudTalkApplication.class)
-public class ConversationsListController extends BaseController implements FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, ConversationMenuInterface {
-
-    public static final String TAG = "ConvListController";
-    public static final int UNREAD_BUBBLE_DELAY = 2500;
-    private static final String KEY_SEARCH_QUERY = "ContactsController.searchQuery";
-
-    public static final int SEARCH_DEBOUNCE_INTERVAL_MS = 300;
-    public static final int SEARCH_MIN_CHARS = 2;
-
-    private final Bundle bundle;
-    @Inject
-    UserManager userManager;
-
-    @Inject
-    EventBus eventBus;
-
-    @Inject
-    NcApi ncApi;
-
-    @Inject
-    Context context;
-
-    @Inject
-    AppPreferences appPreferences;
-
-    @Inject
-    UnifiedSearchRepository unifiedSearchRepository;
-
-    @Inject
-    ViewThemeUtils viewThemeUtils;
-
-    @BindView(R.id.recycler_view)
-    RecyclerView recyclerView;
-
-    @BindView(R.id.swipeRefreshLayoutView)
-    SwipeRefreshLayout swipeRefreshLayout;
-
-    @BindView(R.id.loading_content)
-    LinearLayout loadingContent;
-
-    @BindView(R.id.emptyLayout)
-    RelativeLayout emptyLayoutView;
-
-    @BindView(R.id.floatingActionButton)
-    FloatingActionButton floatingActionButton;
-
-    @BindView(R.id.newMentionPopupBubble)
-    PopupBubble newMentionPopupBubble;
-
-    private User currentUser;
-    private Disposable roomsQueryDisposable;
-    private Disposable openConversationsQueryDisposable;
-    private FlexibleAdapter<AbstractFlexibleItem> adapter;
-    private List<AbstractFlexibleItem> conversationItems = new ArrayList<>();
-    private List<AbstractFlexibleItem> conversationItemsWithHeader = new ArrayList<>();
-    private final List<AbstractFlexibleItem> searchableConversationItems = new ArrayList<>();
-
-    private MenuItem searchItem;
-    private SearchView searchView;
-    private String searchQuery;
-
-    private String credentials;
-
-    private boolean adapterWasNull = true;
-
-    private boolean isRefreshing;
-
-    private Bundle conversationMenuBundle = null;
-
-    private boolean showShareToScreen = false;
-
-    private ArrayList<String> filesToShare;
-    private Conversation selectedConversation;
-
-    private String textToPaste = "";
-    private String selectedMessageId = null;
-
-    private boolean forwardMessage;
-
-    private int nextUnreadConversationScrollPosition = 0;
-
-    private SmoothScrollLinearLayoutManager layoutManager;
-
-    private HashMap<String, GenericTextHeaderItem> callHeaderItems = new HashMap<>();
-
-    private ConversationsListBottomDialog conversationsListBottomDialog;
-
-    private HashMap<String, Status> userStatuses = new HashMap<>();
-
-    private MessageSearchHelper searchHelper;
-    private Disposable searchViewDisposable;
-
-    public ConversationsListController(Bundle bundle) {
-        super();
-        setHasOptionsMenu(true);
-        forwardMessage = bundle.getBoolean(BundleKeys.INSTANCE.getKEY_FORWARD_MSG_FLAG());
-        this.bundle = bundle;
-    }
-
-    @Override
-    protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
-        return inflater.inflate(R.layout.controller_conversations_rv, container, false);
-    }
-
-    @Override
-    protected void onViewBound(@NonNull View view) {
-        super.onViewBound(view);
-        NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
-
-        if (getActionBar() != null) {
-            getActionBar().show();
-        }
-
-        if (adapter == null) {
-            adapter = new FlexibleAdapter<>(conversationItems, getActivity(), true);
-        } else {
-            loadingContent.setVisibility(View.GONE);
-        }
-
-        adapter.addListener(this);
-        prepareViews();
-    }
-
-    private void loadUserAvatar(MaterialButton button) {
-        if (getActivity() != null) {
-            ImageRequest imageRequest = DisplayUtils.getImageRequestForUrl(
-                ApiUtils.getUrlForAvatar(
-                    currentUser.getBaseUrl(),
-                    currentUser.getUserId(),
-                    true),
-                currentUser);
-
-            ImagePipeline imagePipeline = Fresco.getImagePipeline();
-            DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline.fetchDecodedImage(imageRequest, null);
-            dataSource.subscribe(new BaseBitmapDataSubscriber() {
-                @Override
-                protected void onNewResultImpl(Bitmap bitmap) {
-                    if (bitmap != null && getResources() != null) {
-                        RoundedBitmapDrawable roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(getResources(), bitmap);
-                        roundedBitmapDrawable.setCircular(true);
-                        roundedBitmapDrawable.setAntiAlias(true);
-                        button.setIcon(roundedBitmapDrawable);
-                    }
-                }
-
-                @Override
-                protected void onFailureImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
-                    if (getResources() != null) {
-                        button.setIcon(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_user, null));
-                    }
-                }
-            }, UiThreadImmediateExecutorService.getInstance());
-        }
-    }
-
-    @Override
-    protected void onAttach(@NonNull View view) {
-        Log.d(TAG, "onAttach: Controller: " + System.identityHashCode(this) +
-            " Activity: " + System.identityHashCode(getActivity()));
-        super.onAttach(view);
-
-        new ClosedInterfaceImpl().setUpPushTokenRegistration();
-
-        if (!eventBus.isRegistered(this)) {
-            eventBus.register(this);
-        }
-        currentUser = userManager.getCurrentUser().blockingGet();
-
-        if (currentUser != null) {
-            if (CapabilitiesUtilNew.isServerEOL(currentUser)) {
-                showServerEOLDialog();
-                return;
-            }
-
-            if (CapabilitiesUtilNew.isUnifiedSearchAvailable(currentUser)) {
-                searchHelper = new MessageSearchHelper(unifiedSearchRepository);
-            }
-
-            credentials = ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken());
-            if (getActivity() != null && getActivity() instanceof MainActivity) {
-                loadUserAvatar(((MainActivity) getActivity()).binding.switchAccountButton);
-                viewThemeUtils.colorMaterialTextButton(((MainActivity) getActivity()).binding.switchAccountButton);
-            }
-            fetchData();
-        }
-    }
-
-    @Override
-    protected void onDetach(@NonNull View view) {
-        Log.d(TAG, "onDetach: Controller: " + System.identityHashCode(this) +
-            " Activity: " + System.identityHashCode(getActivity()));
-        super.onDetach(view);
-        eventBus.unregister(this);
-    }
-
-    private void initSearchView() {
-        if (getActivity() != null) {
-            SearchManager searchManager = (SearchManager) getActivity().getSystemService(Context.SEARCH_SERVICE);
-            if (searchItem != null) {
-                searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
-                viewThemeUtils.themeSearchView(searchView);
-                searchView.setMaxWidth(Integer.MAX_VALUE);
-                searchView.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER);
-                int imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_FULLSCREEN;
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences.getIsKeyboardIncognito()) {
-                    imeOptions |= EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING;
-                }
-                searchView.setImeOptions(imeOptions);
-                searchView.setQueryHint(getSearchHint());
-                if (searchManager != null) {
-                    searchView.setSearchableInfo(searchManager.getSearchableInfo(getActivity().getComponentName()));
-                }
-                searchViewDisposable = SearchViewObservable.observeSearchView(searchView)
-                    .debounce(query -> {
-                        if (TextUtils.isEmpty(query)) {
-                            return Observable.empty();
-                        } else {
-                            return Observable.timer(SEARCH_DEBOUNCE_INTERVAL_MS, TimeUnit.MILLISECONDS);
-                        }
-                    })
-                    .distinctUntilChanged()
-                    .subscribeOn(Schedulers.io())
-                    .observeOn(AndroidSchedulers.mainThread())
-                    .subscribe(this::onQueryTextChange);
-            }
-        }
-    }
-
-    @Override
-    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
-        super.onCreateOptionsMenu(menu, inflater);
-        inflater.inflate(R.menu.menu_conversation_plus_filter, menu);
-        searchItem = menu.findItem(R.id.action_search);
-        initSearchView();
-    }
-
-    @Override
-    public void onPrepareOptionsMenu(@NonNull Menu menu) {
-        super.onPrepareOptionsMenu(menu);
-
-        searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
-
-        showShareToScreen = !showShareToScreen && hasActivityActionSendIntent();
-
-        if (showShareToScreen) {
-            hideSearchBar();
-            getActionBar().setTitle(R.string.send_to_three_dots);
-        } else if (forwardMessage) {
-            hideSearchBar();
-            getActionBar().setTitle(R.string.nc_forward_to_three_dots);
-        } else {
-            MainActivity activity = (MainActivity) getActivity();
-
-            searchItem.setVisible(conversationItems.size() > 0);
-            if (activity != null) {
-                if (adapter.hasFilter()) {
-                    showSearchView(activity, searchView, searchItem);
-                    searchView.setQuery(adapter.getFilter(String.class), false);
-                }
-
-                activity.binding.searchText.setOnClickListener(v -> {
-                    showSearchView(activity, searchView, searchItem);
-                    viewThemeUtils.themeStatusBar(activity, searchView);
-                });
-            }
-
-            searchView.setOnCloseListener(() -> {
-                if (TextUtils.isEmpty(searchView.getQuery().toString())) {
-                    searchView.onActionViewCollapsed();
-                    if (activity != null) {
-                        viewThemeUtils.resetStatusBar(activity, searchView);
-                    }
-                } else {
-                    searchView.post(() -> searchView.setQuery(TAG, true));
-                }
-                return true;
-            });
-
-            searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
-                @Override
-                public boolean onMenuItemActionExpand(MenuItem item) {
-                    adapter.setHeadersShown(true);
-                    adapter.updateDataSet(searchableConversationItems, false);
-                    adapter.showAllHeaders();
-                    if (swipeRefreshLayout != null) {
-                        swipeRefreshLayout.setEnabled(false);
-                    }
-                    return true;
-                }
-
-                @Override
-                public boolean onMenuItemActionCollapse(MenuItem item) {
-                    adapter.setHeadersShown(false);
-                    adapter.updateDataSet(conversationItems, false);
-                    adapter.hideAllHeaders();
-                    if (searchHelper != null) {
-                        // cancel any pending searches
-                        searchHelper.cancelSearch();
-                        swipeRefreshLayout.setRefreshing(false);
-                    }
-                    if (swipeRefreshLayout != null) {
-                        swipeRefreshLayout.setEnabled(true);
-                    }
-
-                    searchView.onActionViewCollapsed();
-                    MainActivity activity = (MainActivity) getActivity();
-                    if (activity != null) {
-                        activity.binding.appBar.setStateListAnimator(AnimatorInflater.loadStateListAnimator(
-                                                                         activity.binding.appBar.getContext(),
-                                                                         R.animator.appbar_elevation_off)
-                                                                    );
-                        activity.binding.toolbar.setVisibility(View.GONE);
-                        activity.binding.searchToolbar.setVisibility(View.VISIBLE);
-                        if (getResources() != null) {
-                            viewThemeUtils.resetStatusBar(activity, activity.binding.searchToolbar);
-                        }
-                    }
-                    SmoothScrollLinearLayoutManager layoutManager =
-                        (SmoothScrollLinearLayoutManager) recyclerView.getLayoutManager();
-                    if (layoutManager != null) {
-                        layoutManager.scrollToPositionWithOffset(0, 0);
-                    }
-                    return true;
-                }
-            });
-        }
-    }
-
-    private boolean hasActivityActionSendIntent() {
-        if (getActivity() != null) {
-            return Intent.ACTION_SEND.equals(getActivity().getIntent().getAction())
-                || Intent.ACTION_SEND_MULTIPLE.equals(getActivity().getIntent().getAction());
-        }
-        return false;
-    }
-
-    @Override
-    protected void showSearchOrToolbar() {
-        if (TextUtils.isEmpty(searchQuery)) {
-            super.showSearchOrToolbar();
-        }
-    }
-
-    public void showSearchView(MainActivity activity, SearchView searchView, MenuItem searchItem) {
-        activity.binding.appBar.setStateListAnimator(AnimatorInflater.loadStateListAnimator(
-            activity.binding.appBar.getContext(),
-            R.animator.appbar_elevation_on));
-        activity.binding.toolbar.setVisibility(View.VISIBLE);
-        activity.binding.searchToolbar.setVisibility(View.GONE);
-        searchItem.expandActionView();
-    }
-
-    @SuppressLint("LongLogTag")
-    public void fetchData() {
-        if (CapabilitiesUtilNew.isUserStatusAvailable(userManager.getCurrentUser().blockingGet())) {
-            fetchUserStatusesAndRooms();
-        } else {
-            fetchRooms();
-        }
-    }
-
-    private void fetchUserStatusesAndRooms() {
-        ncApi.getUserStatuses(credentials, ApiUtils.getUrlForUserStatuses(currentUser.getBaseUrl()))
-            .subscribe(new Observer<StatusesOverall>() {
-                @Override
-                public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
-                }
-
-                @Override
-                public void onNext(@NonNull StatusesOverall statusesOverall) {
-                    for (Status status : statusesOverall.getOcs().getData()) {
-                        userStatuses.put(status.getUserId(), status);
-                    }
-                    fetchRooms();
-                }
-
-                @Override
-                public void onError(@io.reactivex.annotations.NonNull Throwable e) {
-                    Log.e(TAG, "failed to fetch user statuses", e);
-                    fetchRooms();
-                }
-
-                @Override
-                public void onComplete() {
-                }
-            });
-    }
-
-    private void fetchRooms() {
-        dispose(null);
-
-        isRefreshing = true;
-
-        conversationItems = new ArrayList<>();
-        conversationItemsWithHeader = new ArrayList<>();
-
-        int apiVersion = ApiUtils.getConversationApiVersion(currentUser, new int[]{ApiUtils.APIv4, ApiUtils.APIv3, 1});
-
-        long startNanoTime = System.nanoTime();
-        Log.d(TAG, "fetchData - getRooms - calling: " + startNanoTime);
-        roomsQueryDisposable = ncApi.getRooms(credentials, ApiUtils.getUrlForRooms(apiVersion,
-                                                                                   currentUser.getBaseUrl()))
-            .subscribeOn(Schedulers.io())
-            .observeOn(AndroidSchedulers.mainThread())
-            .subscribe(roomsOverall -> {
-                Log.d(TAG, "fetchData - getRooms - got response: " + startNanoTime);
-
-                // This is invoked asynchronously, when server returns a response the view might have been
-                // unbound in the meantime. Check if the view is still there.
-                // FIXME - does it make sense to update internal data structures even when view has been unbound?
-                if (getView() == null) {
-                    Log.d(TAG, "fetchData - getRooms - view is not bound: " + startNanoTime);
-                    return;
-                }
-
-                if (adapterWasNull) {
-                    adapterWasNull = false;
-                    loadingContent.setVisibility(View.GONE);
-                }
-
-                if (roomsOverall.getOcs().getData().size() > 0) {
-                    if (emptyLayoutView.getVisibility() != View.GONE) {
-                        emptyLayoutView.setVisibility(View.GONE);
-                    }
-
-                    if (swipeRefreshLayout.getVisibility() != View.VISIBLE) {
-                        swipeRefreshLayout.setVisibility(View.VISIBLE);
-                    }
-                } else {
-                    if (emptyLayoutView.getVisibility() != View.VISIBLE) {
-                        emptyLayoutView.setVisibility(View.VISIBLE);
-                    }
-
-                    if (swipeRefreshLayout.getVisibility() != View.GONE) {
-                        swipeRefreshLayout.setVisibility(View.GONE);
-                    }
-                }
-
-                for (Conversation conversation : roomsOverall.getOcs().getData()) {
-                    if (bundle.containsKey(BundleKeys.INSTANCE.getKEY_FORWARD_HIDE_SOURCE_ROOM()) &&
-                        conversation.getRoomId().equals(bundle.getString(
-                            BundleKeys.INSTANCE.getKEY_FORWARD_HIDE_SOURCE_ROOM()))
-                    ) {
-                        continue;
-                    }
-
-                    String headerTitle;
-
-                    headerTitle = getResources().getString(R.string.conversations);
-
-                    GenericTextHeaderItem genericTextHeaderItem;
-                    if (!callHeaderItems.containsKey(headerTitle)) {
-                        genericTextHeaderItem = new GenericTextHeaderItem(headerTitle, viewThemeUtils);
-                        callHeaderItems.put(headerTitle, genericTextHeaderItem);
-                    }
-
-                    if (getActivity() != null) {
-                        ConversationItem conversationItem = new ConversationItem(
-                            conversation,
-                            currentUser,
-                            getActivity(),
-                            userStatuses.get(conversation.getName()),
-                            viewThemeUtils);
-                        conversationItems.add(conversationItem);
-
-                        ConversationItem conversationItemWithHeader = new ConversationItem(
-                            conversation,
-                            currentUser,
-                            getActivity(),
-                            callHeaderItems.get(headerTitle),
-                            userStatuses.get(conversation.getName()),
-                            viewThemeUtils);
-                        conversationItemsWithHeader.add(conversationItemWithHeader);
-                    }
-                }
-
-                sortConversations(conversationItems);
-                sortConversations(conversationItemsWithHeader);
-
-                adapter.updateDataSet(conversationItems, false);
-
-                new Handler().postDelayed(this::checkToShowUnreadBubble, UNREAD_BUBBLE_DELAY);
-
-                fetchOpenConversations(apiVersion);
-
-                if (swipeRefreshLayout != null) {
-                    swipeRefreshLayout.setRefreshing(false);
-                }
-
-            }, throwable -> {
-                handleHttpExceptions(throwable);
-                if (swipeRefreshLayout != null) {
-                    swipeRefreshLayout.setRefreshing(false);
-                }
-                dispose(roomsQueryDisposable);
-            }, () -> {
-                dispose(roomsQueryDisposable);
-                if (swipeRefreshLayout != null) {
-                    swipeRefreshLayout.setRefreshing(false);
-                }
-
-                isRefreshing = false;
-            });
-    }
-
-    private void sortConversations(List<AbstractFlexibleItem> conversationItems) {
-        Collections.sort(conversationItems, (o1, o2) -> {
-            Conversation conversation1 = ((ConversationItem) o1).getModel();
-            Conversation conversation2 = ((ConversationItem) o2).getModel();
-            return new CompareToBuilder()
-                .append(conversation2.getFavorite(), conversation1.getFavorite())
-                .append(conversation2.getLastActivity(), conversation1.getLastActivity())
-                .toComparison();
-        });
-    }
-
-    private void fetchOpenConversations(int apiVersion) {
-        searchableConversationItems.clear();
-        searchableConversationItems.addAll(conversationItemsWithHeader);
-
-        if (CapabilitiesUtilNew.hasSpreedFeatureCapability(currentUser, "listable-rooms")) {
-            List<AbstractFlexibleItem> openConversationItems = new ArrayList<>();
-
-            openConversationsQueryDisposable = ncApi.getOpenConversations(
-                credentials,
-                ApiUtils.getUrlForOpenConversations(apiVersion, currentUser.getBaseUrl()))
-                .subscribeOn(Schedulers.io())
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(roomsOverall -> {
-
-                    for (Conversation conversation : roomsOverall.getOcs().getData()) {
-                        String headerTitle = getResources().getString(R.string.openConversations);
-
-                        GenericTextHeaderItem genericTextHeaderItem;
-                        if (!callHeaderItems.containsKey(headerTitle)) {
-                            genericTextHeaderItem = new GenericTextHeaderItem(headerTitle, viewThemeUtils);
-                            callHeaderItems.put(headerTitle, genericTextHeaderItem);
-                        }
-
-                        ConversationItem conversationItem = new ConversationItem(
-                            conversation,
-                            currentUser,
-                            getActivity(),
-                            callHeaderItems.get(headerTitle),
-                            userStatuses.get(conversation.getName()),
-                            viewThemeUtils);
-
-                        openConversationItems.add(conversationItem);
-                    }
-                    searchableConversationItems.addAll(openConversationItems);
-
-                }, throwable -> {
-                    Log.e(TAG, "fetchData - getRooms - ERROR", throwable);
-                    handleHttpExceptions(throwable);
-                    dispose(openConversationsQueryDisposable);
-                }, () -> {
-                    dispose(openConversationsQueryDisposable);
-                });
-        } else {
-            Log.d(TAG, "no open conversations fetched because of missing capability");
-        }
-    }
-
-    private void handleHttpExceptions(Throwable throwable) {
-        if (throwable instanceof HttpException) {
-            HttpException exception = (HttpException) throwable;
-            switch (exception.code()) {
-                case 401:
-                    if (getParentController() != null && getParentController().getRouter() != null) {
-                        Log.d(TAG, "Starting reauth webview via getParentController()");
-                        getParentController().getRouter().pushController((RouterTransaction.with
-                            (new WebViewLoginController(currentUser.getBaseUrl(), true))
-                            .pushChangeHandler(new VerticalChangeHandler())
-                            .popChangeHandler(new VerticalChangeHandler())));
-                    } else {
-                        Log.d(TAG, "Starting reauth webview via ConversationsListController");
-                        showUnauthorizedDialog();
-                    }
-                    break;
-                default:
-                    break;
-            }
-        }
-    }
-
-    @SuppressLint("ClickableViewAccessibility")
-    private void prepareViews() {
-        layoutManager = new SmoothScrollLinearLayoutManager(Objects.requireNonNull(getActivity()));
-        recyclerView.setLayoutManager(layoutManager);
-        recyclerView.setHasFixedSize(true);
-        recyclerView.setAdapter(adapter);
-        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
-            @Override
-            public void onScrollStateChanged(@NotNull RecyclerView recyclerView, int newState) {
-                super.onScrollStateChanged(recyclerView, newState);
-                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
-                    checkToShowUnreadBubble();
-                }
-            }
-        });
-
-        recyclerView.setOnTouchListener((v, event) -> {
-            if (isAttached() && (!isBeingDestroyed() || !isDestroyed())) {
-                InputMethodManager imm =
-                    (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
-                if (imm != null) {
-                    imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
-                }
-            }
-            return false;
-        });
-
-        swipeRefreshLayout.setOnRefreshListener(() -> fetchData());
-        viewThemeUtils.themeSwipeRefreshLayout(swipeRefreshLayout);
-
-        emptyLayoutView.setOnClickListener(v -> showNewConversationsScreen());
-        floatingActionButton.setOnClickListener(v -> {
-            ContactAddressBookWorker.Companion.run(context);
-            showNewConversationsScreen();
-        });
-
-        viewThemeUtils.themeFAB(floatingActionButton);
-
-        if (getActivity() != null && getActivity() instanceof MainActivity) {
-            MainActivity activity = (MainActivity) getActivity();
-
-            activity.binding.switchAccountButton.setOnClickListener(v -> {
-                if (getResources() != null && getResources().getBoolean(R.bool.multiaccount_support)) {
-                    DialogFragment newFragment = ChooseAccountDialogFragment.newInstance();
-                    newFragment.show(((MainActivity) getActivity()).getSupportFragmentManager(),
-                                     "ChooseAccountDialogFragment");
-                } else {
-                    getRouter().pushController((RouterTransaction.with(new SettingsController())
-                        .pushChangeHandler(new HorizontalChangeHandler())
-                        .popChangeHandler(new HorizontalChangeHandler())));
-                }
-            });
-        }
-
-        newMentionPopupBubble.hide();
-        newMentionPopupBubble.setPopupBubbleListener(new PopupBubble.PopupBubbleClickListener() {
-            @Override
-            public void bubbleClicked(Context context) {
-                recyclerView.smoothScrollToPosition(nextUnreadConversationScrollPosition);
-            }
-        });
-        viewThemeUtils.colorMaterialButtonPrimaryFilled(newMentionPopupBubble);
-    }
-
-    private void checkToShowUnreadBubble() {
-        try {
-            int lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition();
-            for (AbstractFlexibleItem flexItem : conversationItems) {
-                Conversation conversationItem = ((ConversationItem) flexItem).getModel();
-                int position = adapter.getGlobalPositionOf(flexItem);
-                if ((conversationItem.getUnreadMention() ||
-                    (conversationItem.getUnreadMessages() > 0 &&
-                        conversationItem.getType() == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL)) &&
-                    position > lastVisibleItem) {
-                    nextUnreadConversationScrollPosition = position;
-                    if (!newMentionPopupBubble.isShown()) {
-                        newMentionPopupBubble.show();
-                    }
-                    return;
-                }
-            }
-            nextUnreadConversationScrollPosition = 0;
-            newMentionPopupBubble.hide();
-        } catch (NullPointerException e) {
-            Log.d(TAG, "A NPE was caught when trying to show the unread popup bubble. This might happen when the " +
-                "user already left the conversations-list screen so the popup bubble is not available anymore.", e);
-        }
-    }
-
-    private void showNewConversationsScreen() {
-        Bundle bundle = new Bundle();
-        bundle.putBoolean(BundleKeys.INSTANCE.getKEY_NEW_CONVERSATION(), true);
-        getRouter().pushController((RouterTransaction.with(new ContactsController(bundle))
-            .pushChangeHandler(new HorizontalChangeHandler())
-            .popChangeHandler(new HorizontalChangeHandler())));
-    }
-
-    private void dispose(@Nullable Disposable disposable) {
-        if (disposable != null && !disposable.isDisposed()) {
-            disposable.dispose();
-            disposable = null;
-        } else if (disposable == null &&
-            roomsQueryDisposable != null && !roomsQueryDisposable.isDisposed()) {
-            roomsQueryDisposable.dispose();
-            roomsQueryDisposable = null;
-        } else if (disposable == null &&
-            openConversationsQueryDisposable != null && !openConversationsQueryDisposable.isDisposed()) {
-            openConversationsQueryDisposable.dispose();
-            openConversationsQueryDisposable = null;
-        }
-    }
-
-    @Override
-    public void onSaveViewState(@NonNull View view, @NonNull Bundle outState) {
-
-        if (searchView != null && !TextUtils.isEmpty(searchView.getQuery())) {
-            outState.putString(KEY_SEARCH_QUERY, searchView.getQuery().toString());
-        }
-
-        super.onSaveViewState(view, outState);
-    }
-
-    @Override
-    public void onRestoreViewState(@NonNull View view, @NonNull Bundle savedViewState) {
-        super.onRestoreViewState(view, savedViewState);
-        if (savedViewState.containsKey(KEY_SEARCH_QUERY)) {
-            searchQuery = savedViewState.getString(KEY_SEARCH_QUERY, "");
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        dispose(null);
-        if (searchViewDisposable != null && !searchViewDisposable.isDisposed()) {
-            searchViewDisposable.dispose();
-        }
-    }
-
-    public void onQueryTextChange(final String newText) {
-        if (!TextUtils.isEmpty(searchQuery)) {
-            final String filter = searchQuery;
-            searchQuery = "";
-            performFilterAndSearch(filter);
-        } else if (adapter.hasNewFilter(newText)) {
-            performFilterAndSearch(newText);
-        }
-    }
-
-    private void performFilterAndSearch(String filter) {
-        if (filter.length() >= SEARCH_MIN_CHARS) {
-            clearMessageSearchResults();
-            adapter.setFilter(filter);
-            adapter.filterItems();
-            if (CapabilitiesUtilNew.isUnifiedSearchAvailable(currentUser)) {
-                startMessageSearch(filter);
-            }
-        } else {
-            resetSearchResults();
-        }
-    }
-
-    private void resetSearchResults() {
-        clearMessageSearchResults();
-        adapter.setFilter("");
-        adapter.filterItems();
-    }
-
-    private void clearMessageSearchResults() {
-        final IHeader firstHeader = adapter.getSectionHeader(0);
-        if (firstHeader != null && firstHeader.getItemViewType() == MessagesTextHeaderItem.VIEW_TYPE) {
-            adapter.removeSection(firstHeader);
-        } else {
-            adapter.removeItemsOfType(MessageResultItem.VIEW_TYPE);
-        }
-        adapter.removeItemsOfType(LoadMoreResultsItem.VIEW_TYPE);
-    }
-
-    @SuppressLint("CheckResult") // handled by helper
-    private void startMessageSearch(final String search) {
-        if (swipeRefreshLayout != null) {
-            swipeRefreshLayout.setRefreshing(true);
-        }
-        searchHelper
-            .startMessageSearch(search)
-            .subscribeOn(Schedulers.io())
-            .observeOn(AndroidSchedulers.mainThread())
-            .subscribe(
-                this::onMessageSearchResult,
-                this::onMessageSearchError);
-    }
-
-    @SuppressLint("CheckResult") // handled by helper
-    private void loadMoreMessages() {
-        swipeRefreshLayout.setRefreshing(true);
-        final Observable<MessageSearchHelper.MessageSearchResults> observable = searchHelper.loadMore();
-        if (observable != null) {
-            observable
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(
-                    this::onMessageSearchResult,
-                    this::onMessageSearchError);
-        }
-    }
-
-
-    @Override
-    protected String getTitle() {
-        return getResources().getString(R.string.nc_app_product_name);
-    }
-
-    @Override
-    public boolean onItemClick(View view, int position) {
-        final AbstractFlexibleItem item = adapter.getItem(position);
-        if (item != null) {
-            final int viewType = item.getItemViewType();
-            if (viewType == MessageResultItem.VIEW_TYPE) {
-                MessageResultItem messageItem = (MessageResultItem) item;
-                String conversationToken = messageItem.getMessageEntry().getConversationToken();
-                selectedMessageId = messageItem.getMessageEntry().getMessageId();
-                showConversationByToken(conversationToken);
-            } else if (viewType == LoadMoreResultsItem.VIEW_TYPE) {
-                loadMoreMessages();
-            } else if (viewType == ConversationItem.VIEW_TYPE) {
-                showConversation(((ConversationItem) Objects.requireNonNull(item)).getModel());
-            }
-        }
-        return true;
-    }
-
-    private void showConversationByToken(String conversationToken) {
-        for (AbstractFlexibleItem absItem : conversationItems) {
-            ConversationItem conversationItem = ((ConversationItem) absItem);
-            if (conversationItem.getModel().getToken().equals(conversationToken)) {
-                final Conversation conversation = conversationItem.getModel();
-                showConversation(conversation);
-            }
-        }
-    }
-
-    private void showConversation(@Nullable final Conversation conversation) {
-        selectedConversation = conversation;
-        if (selectedConversation != null && getActivity() != null) {
-            boolean hasChatPermission =
-                new AttendeePermissionsUtil(selectedConversation.getPermissions()).hasChatPermission(currentUser);
-
-            if (showShareToScreen) {
-                if (hasChatPermission && !isReadOnlyConversation(selectedConversation)) {
-                    handleSharedData();
-                    showShareToScreen = false;
-                } else {
-                    Toast.makeText(context, R.string.send_to_forbidden, Toast.LENGTH_LONG).show();
-                }
-            } else if (forwardMessage) {
-                if (hasChatPermission && !isReadOnlyConversation(selectedConversation)) {
-                    openConversation(bundle.getString(BundleKeys.INSTANCE.getKEY_FORWARD_MSG_TEXT()));
-                    forwardMessage = false;
-                } else {
-                    Toast.makeText(context, R.string.send_to_forbidden, Toast.LENGTH_LONG).show();
-                }
-            } else {
-                openConversation();
-            }
-        }
-    }
-
-    private Boolean isReadOnlyConversation(Conversation conversation) {
-        return conversation.getConversationReadOnlyState() ==
-            Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY;
-    }
-
-    private void handleSharedData() {
-        collectDataFromIntent();
-        if (!textToPaste.isEmpty()) {
-            openConversation(textToPaste);
-        } else if (filesToShare != null && !filesToShare.isEmpty()) {
-            showSendFilesConfirmDialog();
-        } else {
-            Toast.makeText(context, context.getResources().getString(R.string.nc_common_error_sorry), Toast.LENGTH_LONG).show();
-        }
-    }
-
-    private void showSendFilesConfirmDialog() {
-        if (UploadAndShareFilesWorker.Companion.isStoragePermissionGranted(context)) {
-            StringBuilder fileNamesWithLineBreaks = new StringBuilder("\n");
-
-            for (String file : filesToShare) {
-                String filename = UriUtils.Companion.getFileName(Uri.parse(file), context);
-                fileNamesWithLineBreaks.append(filename).append("\n");
-            }
-
-            String confirmationQuestion;
-            if (filesToShare.size() == 1) {
-                confirmationQuestion =
-                    String.format(getResources().getString(R.string.nc_upload_confirm_send_single),
-                                  selectedConversation.getDisplayName());
-            } else {
-                confirmationQuestion =
-                    String.format(getResources().getString(R.string.nc_upload_confirm_send_multiple),
-                                  selectedConversation.getDisplayName());
-            }
-
-            MaterialAlertDialogBuilder dialogBuilder = new MaterialAlertDialogBuilder(floatingActionButton.getContext())
-                .setIcon(viewThemeUtils.colorMaterialAlertDialogIcon(context, R.drawable.upload))
-                .setTitle(confirmationQuestion)
-                .setMessage(fileNamesWithLineBreaks.toString())
-                .setPositiveButton(R.string.nc_yes, (dialog, which) -> {
-                    upload();
-                    openConversation();
-                })
-                .setNegativeButton(R.string.nc_no, (dialog, which) -> {
-                    Log.d(TAG, "sharing files aborted, going back to share-to screen");
-                    showShareToScreen = true;
-                });
-
-            viewThemeUtils.colorMaterialAlertDialogBackground(floatingActionButton.getContext(), dialogBuilder);
-
-            AlertDialog dialog = dialogBuilder.show();
-
-            viewThemeUtils.colorTextButtons(
-                dialog.getButton(AlertDialog.BUTTON_POSITIVE),
-                dialog.getButton(AlertDialog.BUTTON_NEGATIVE));
-        } else {
-            UploadAndShareFilesWorker.Companion.requestStoragePermission(ConversationsListController.this);
-        }
-    }
-
-    @Override
-    public void onItemLongClick(int position) {
-        if (showShareToScreen) {
-            Log.d(TAG, "sharing to multiple rooms not yet implemented. onItemLongClick is ignored.");
-
-        } else {
-            Object clickedItem = adapter.getItem(position);
-            if (clickedItem != null) {
-                Conversation conversation = ((ConversationItem) clickedItem).getModel();
-                conversationsListBottomDialog = new ConversationsListBottomDialog(
-                    getActivity(),
-                    this,
-                    userManager.getCurrentUser().blockingGet(),
-                    conversation);
-                conversationsListBottomDialog.show();
-            }
-        }
-    }
-
-    private void collectDataFromIntent() {
-        filesToShare = new ArrayList<>();
-        if (getActivity() != null && getActivity().getIntent() != null) {
-            Intent intent = getActivity().getIntent();
-            if (Intent.ACTION_SEND.equals(intent.getAction())
-                || Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
-                try {
-                    String mimeType = intent.getType();
-                    if (TEXT_PLAIN.equals(mimeType) && (intent.getStringExtra(Intent.EXTRA_TEXT) != null)) {
-                        // Share from Google Chrome sets text/plain MIME type, but also provides a content:// URI
-                        // with a *screenshot* of the current page in getClipData().
-                        // Here we assume that when sharing a web page the user would prefer to send the URL
-                        // of the current page rather than a screenshot.
-                        textToPaste = intent.getStringExtra(Intent.EXTRA_TEXT);
-                    } else {
-                        if (intent.getClipData() != null) {
-                            for (int i = 0; i < intent.getClipData().getItemCount(); i++) {
-                                ClipData.Item item = intent.getClipData().getItemAt(i);
-                                if (item.getUri() != null) {
-                                    filesToShare.add(item.getUri().toString());
-                                } else if (item.getText() != null) {
-                                    textToPaste = item.getText().toString();
-                                    break;
-                                } else {
-                                    Log.w(TAG, "datatype not yet implemented for share-to");
-                                }
-                            }
-                        } else {
-                            filesToShare.add(intent.getData().toString());
-                        }
-                    }
-                    if (filesToShare.isEmpty() && textToPaste.isEmpty()) {
-                        Toast.makeText(context, context.getResources().getString(R.string.nc_common_error_sorry),
-                                       Toast.LENGTH_LONG).show();
-                        Log.e(TAG, "failed to get data from intent");
-                    }
-                } catch (Exception e) {
-                    Toast.makeText(context, context.getResources().getString(R.string.nc_common_error_sorry),
-                                   Toast.LENGTH_LONG).show();
-                    Log.e(TAG, "Something went wrong when extracting data from intent");
-                }
-            }
-        }
-    }
-
-    private void upload() {
-        if (selectedConversation == null) {
-            Toast.makeText(context, context.getResources().getString(R.string.nc_common_error_sorry),
-                           Toast.LENGTH_LONG).show();
-            Log.e(TAG, "not able to upload any files because conversation was null.");
-            return;
-        }
-
-        try {
-            String[] filesToShareArray = new String[filesToShare.size()];
-            filesToShareArray = filesToShare.toArray(filesToShareArray);
-
-            Data data = new Data.Builder()
-                .putStringArray(UploadAndShareFilesWorker.DEVICE_SOURCEFILES, filesToShareArray)
-                .putString(
-                    UploadAndShareFilesWorker.NC_TARGETPATH,
-                    CapabilitiesUtilNew.getAttachmentFolder(currentUser))
-                .putString(UploadAndShareFilesWorker.ROOM_TOKEN, selectedConversation.getToken())
-                .build();
-            OneTimeWorkRequest uploadWorker = new OneTimeWorkRequest.Builder(UploadAndShareFilesWorker.class)
-                .setInputData(data)
-                .build();
-            WorkManager.getInstance().enqueue(uploadWorker);
-
-            Toast.makeText(
-                context, context.getResources().getString(R.string.nc_upload_in_progess),
-                Toast.LENGTH_LONG
-                          ).show();
-
-        } catch (IllegalArgumentException e) {
-            Toast.makeText(context, context.getResources().getString(R.string.nc_upload_failed), Toast.LENGTH_LONG).show();
-            Log.e(TAG, "Something went wrong when trying to upload file", e);
-        }
-    }
-
-    @Override
-    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
-        if (requestCode == UploadAndShareFilesWorker.REQUEST_PERMISSION &&
-            grantResults.length > 0 &&
-            grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-            Log.d(TAG, "upload starting after permissions were granted");
-            showSendFilesConfirmDialog();
-        } else {
-            Toast.makeText(context, context.getString(R.string.read_storage_no_permission), Toast.LENGTH_LONG).show();
-        }
-    }
-
-    private void openConversation() {
-        openConversation("");
-    }
-
-    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 (selectedMessageId != null) {
-            bundle.putString(BundleKeys.KEY_MESSAGE_ID, selectedMessageId);
-            selectedMessageId = null;
-        }
-
-        ConductorRemapping.INSTANCE.remapChatController(getRouter(), currentUser.getId(),
-                                                        selectedConversation.getToken(), bundle, false);
-    }
-
-    @Subscribe(sticky = true, threadMode = ThreadMode.BACKGROUND)
-    public void onMessageEvent(EventStatus eventStatus) {
-        if (currentUser != null && eventStatus.getUserId() == currentUser.getId()) {
-            switch (eventStatus.getEventType()) {
-                case CONVERSATION_UPDATE:
-                    if (eventStatus.isAllGood() && !isRefreshing) {
-                        fetchData();
-                    }
-                    break;
-                default:
-                    break;
-            }
-        }
-    }
-
-    @Subscribe(threadMode = ThreadMode.MAIN)
-    public void onMessageEvent(ConversationsListFetchDataEvent conversationsListFetchDataEvent) {
-        fetchData();
-
-        new Handler().postDelayed(() -> {
-            if (conversationsListBottomDialog.isShowing()) {
-                conversationsListBottomDialog.dismiss();
-            }
-        }, 2500);
-    }
-
-    @Override
-    public void showDeleteConversationDialog(@NonNull Bundle bundle) {
-        conversationMenuBundle = bundle;
-        if (getActivity() != null &&
-            conversationMenuBundle != null &&
-            currentUser != null &&
-            conversationMenuBundle.getLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID()) == currentUser.getId()) {
-
-            Conversation conversation =
-                Parcels.unwrap(conversationMenuBundle.getParcelable(BundleKeys.INSTANCE.getKEY_ROOM()));
-
-            if (conversation != null) {
-                MaterialAlertDialogBuilder dialogBuilder =
-                    new MaterialAlertDialogBuilder(floatingActionButton.getContext())
-                        .setIcon(viewThemeUtils.colorMaterialAlertDialogIcon(context, R.drawable.ic_delete_black_24dp))
-                        .setTitle(R.string.nc_delete_call)
-                        .setMessage(R.string.nc_delete_conversation_more)
-                        .setPositiveButton(R.string.nc_delete, (dialog, which) -> {
-                            Data.Builder data = new Data.Builder();
-                            data.putLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(),
-                                         conversationMenuBundle.getLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID()));
-                            data.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), conversation.getToken());
-                            conversationMenuBundle = null;
-                            deleteConversation(data.build());
-                        })
-                        .setNegativeButton(R.string.nc_cancel, (dialog, which) -> {
-                            conversationMenuBundle = null;
-                        });
-
-                viewThemeUtils.colorMaterialAlertDialogBackground(floatingActionButton.getContext(), dialogBuilder);
-
-                AlertDialog dialog = dialogBuilder.show();
-
-                viewThemeUtils.colorTextButtons(
-                    dialog.getButton(AlertDialog.BUTTON_POSITIVE),
-                    dialog.getButton(AlertDialog.BUTTON_NEGATIVE));
-            }
-        }
-    }
-
-    private void showUnauthorizedDialog() {
-        if (getActivity() != null) {
-            MaterialAlertDialogBuilder dialogBuilder = new MaterialAlertDialogBuilder(floatingActionButton.getContext())
-                .setIcon(viewThemeUtils.colorMaterialAlertDialogIcon(context, R.drawable.ic_delete_black_24dp))
-                .setTitle(R.string.nc_dialog_invalid_password)
-                .setMessage(R.string.nc_dialog_reauth_or_delete)
-                .setCancelable(false)
-                .setPositiveButton(R.string.nc_delete, (dialog, which) -> {
-                    boolean otherUserExists = userManager
-                        .scheduleUserForDeletionWithId(currentUser.getId())
-                        .blockingGet();
-
-                    OneTimeWorkRequest accountRemovalWork = new OneTimeWorkRequest.Builder(AccountRemovalWorker.class).build();
-                    WorkManager.getInstance().enqueue(accountRemovalWork);
-
-                    if (otherUserExists && getView() != null) {
-                        onViewBound(getView());
-                        onAttach(getView());
-                    } else if (!otherUserExists) {
-                        getRouter().setRoot(RouterTransaction.with(
-                                new ServerSelectionController())
-                                                .pushChangeHandler(new VerticalChangeHandler())
-                                                .popChangeHandler(new VerticalChangeHandler()));
-                    }
-                })
-                .setNegativeButton(R.string.nc_settings_reauthorize, (dialog, which) -> {
-                    getRouter().pushController(RouterTransaction.with(
-                            new WebViewLoginController(currentUser.getBaseUrl(), true))
-                                                   .pushChangeHandler(new VerticalChangeHandler())
-                                                   .popChangeHandler(new VerticalChangeHandler()));
-                });
-
-            viewThemeUtils.colorMaterialAlertDialogBackground(floatingActionButton.getContext(), dialogBuilder);
-
-            AlertDialog dialog = dialogBuilder.show();
-
-            viewThemeUtils.colorTextButtons(
-                dialog.getButton(AlertDialog.BUTTON_POSITIVE),
-                dialog.getButton(AlertDialog.BUTTON_NEGATIVE));
-        }
-    }
-
-    private void showServerEOLDialog() {
-        MaterialAlertDialogBuilder dialogBuilder = new MaterialAlertDialogBuilder(floatingActionButton.getContext())
-            .setIcon(viewThemeUtils.colorMaterialAlertDialogIcon(context, R.drawable.ic_warning_white))
-            .setTitle(R.string.nc_settings_server_eol_title)
-            .setMessage(R.string.nc_settings_server_eol)
-            .setCancelable(false)
-            .setPositiveButton(R.string.nc_settings_remove_account, (dialog, which) -> {
-                boolean otherUserExists = userManager
-                    .scheduleUserForDeletionWithId(currentUser.getId())
-                    .blockingGet();
-
-                OneTimeWorkRequest accountRemovalWork = new OneTimeWorkRequest.Builder(AccountRemovalWorker.class).build();
-                WorkManager.getInstance().enqueue(accountRemovalWork);
-
-                if (otherUserExists && getView() != null) {
-                    onViewBound(getView());
-                    onAttach(getView());
-                } else if (!otherUserExists) {
-                    getRouter().setRoot(RouterTransaction.with(
-                            new ServerSelectionController())
-                                            .pushChangeHandler(new VerticalChangeHandler())
-                                            .popChangeHandler(new VerticalChangeHandler()));
-                }
-            })
-            .setNegativeButton(R.string.nc_cancel, (dialog, which) -> {
-                if (userManager.getUsers().blockingGet().size() > 0) {
-                    getRouter().pushController(RouterTransaction.with(new SwitchAccountController()));
-                } else {
-                    getActivity().finishAffinity();
-                    getActivity().finish();
-                }
-            });
-
-        viewThemeUtils.colorMaterialAlertDialogBackground(floatingActionButton.getContext(), dialogBuilder);
-
-        AlertDialog dialog = dialogBuilder.show();
-
-        viewThemeUtils.colorTextButtons(
-            dialog.getButton(AlertDialog.BUTTON_POSITIVE),
-            dialog.getButton(AlertDialog.BUTTON_NEGATIVE));
-    }
-
-    private void deleteConversation(Data data) {
-        OneTimeWorkRequest deleteConversationWorker =
-            new OneTimeWorkRequest.Builder(DeleteConversationWorker.class).setInputData(data).build();
-        WorkManager.getInstance().enqueue(deleteConversationWorker);
-    }
-
-    @Override
-    public AppBarLayoutType getAppBarLayoutType() {
-        return AppBarLayoutType.SEARCH_BAR;
-    }
-
-    public void onMessageSearchResult(@NonNull MessageSearchHelper.MessageSearchResults results) {
-        if (searchView.getQuery().length() > 0) {
-            clearMessageSearchResults();
-            final List<SearchMessageEntry> entries = results.getMessages();
-            if (entries.size() > 0) {
-                List<AbstractFlexibleItem> adapterItems = new ArrayList<>(entries.size() + 1);
-                for (int i = 0; i < entries.size(); i++) {
-                    final boolean showHeader = i == 0;
-                    adapterItems.add(new MessageResultItem(context, currentUser, entries.get(i), showHeader, viewThemeUtils));
-                }
-                if (results.getHasMore()) {
-                    adapterItems.add(LoadMoreResultsItem.INSTANCE);
-                }
-                adapter.addItems(0, adapterItems);
-                recyclerView.scrollToPosition(0);
-            }
-        }
-        if (swipeRefreshLayout != null) {
-            swipeRefreshLayout.setRefreshing(false);
-        }
-    }
-
-    public void onMessageSearchError(@NonNull Throwable throwable) {
-        handleHttpExceptions(throwable);
-        if (swipeRefreshLayout != null) {
-            swipeRefreshLayout.setRefreshing(false);
-        }
-    }
-}

+ 1298 - 0
app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt

@@ -0,0 +1,1298 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Álvaro Brey
+ * @author Andy Scherzinger
+ * @author Marcel Hibbe
+ * @author Mario Danic
+ * Copyright (C) 2022 Álvaro Brey <alvaro.brey@nextcloud.com>
+ * Copyright (C) 2022 Andy Scherzinger (info@andy-scherzinger.de)
+ * Copyright (C) 2022 Marcel Hibbe (dev@mhibbe.de)
+ * Copyright (C) 2017-2020 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
+
+import android.animation.AnimatorInflater
+import android.annotation.SuppressLint
+import android.app.SearchManager
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.os.Handler
+import android.text.InputType
+import android.text.TextUtils
+import android.util.Log
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.MotionEvent
+import android.view.View
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputMethodManager
+import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.widget.SearchView
+import androidx.core.content.res.ResourcesCompat
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
+import androidx.core.view.MenuItemCompat
+import androidx.fragment.app.DialogFragment
+import androidx.recyclerview.widget.RecyclerView
+import androidx.work.Data
+import androidx.work.OneTimeWorkRequest
+import androidx.work.WorkManager
+import autodagger.AutoInjector
+import com.bluelinelabs.conductor.RouterTransaction
+import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
+import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
+import com.facebook.common.executors.UiThreadImmediateExecutorService
+import com.facebook.common.references.CloseableReference
+import com.facebook.datasource.DataSource
+import com.facebook.drawee.backends.pipeline.Fresco
+import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber
+import com.facebook.imagepipeline.image.CloseableImage
+import com.google.android.material.button.MaterialButton
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.nextcloud.talk.R
+import com.nextcloud.talk.activities.MainActivity
+import com.nextcloud.talk.adapters.items.ConversationItem
+import com.nextcloud.talk.adapters.items.GenericTextHeaderItem
+import com.nextcloud.talk.adapters.items.LoadMoreResultsItem
+import com.nextcloud.talk.adapters.items.MessageResultItem
+import com.nextcloud.talk.adapters.items.MessagesTextHeaderItem
+import com.nextcloud.talk.api.NcApi
+import com.nextcloud.talk.application.NextcloudTalkApplication
+import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
+import com.nextcloud.talk.controllers.base.BaseController
+import com.nextcloud.talk.controllers.util.viewBinding
+import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.databinding.ControllerConversationsRvBinding
+import com.nextcloud.talk.events.ConversationsListFetchDataEvent
+import com.nextcloud.talk.events.EventStatus
+import com.nextcloud.talk.interfaces.ConversationMenuInterface
+import com.nextcloud.talk.jobs.AccountRemovalWorker
+import com.nextcloud.talk.jobs.ContactAddressBookWorker.Companion.run
+import com.nextcloud.talk.jobs.DeleteConversationWorker
+import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
+import com.nextcloud.talk.jobs.UploadAndShareFilesWorker.Companion.isStoragePermissionGranted
+import com.nextcloud.talk.jobs.UploadAndShareFilesWorker.Companion.requestStoragePermission
+import com.nextcloud.talk.messagesearch.MessageSearchHelper
+import com.nextcloud.talk.messagesearch.MessageSearchHelper.MessageSearchResults
+import com.nextcloud.talk.models.json.conversations.Conversation
+import com.nextcloud.talk.models.json.conversations.RoomsOverall
+import com.nextcloud.talk.models.json.status.Status
+import com.nextcloud.talk.models.json.statuses.StatusesOverall
+import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
+import com.nextcloud.talk.ui.dialog.ChooseAccountDialogFragment
+import com.nextcloud.talk.ui.dialog.ConversationsListBottomDialog
+import com.nextcloud.talk.users.UserManager
+import com.nextcloud.talk.utils.ApiUtils
+import com.nextcloud.talk.utils.AttendeePermissionsUtil
+import com.nextcloud.talk.utils.ClosedInterfaceImpl
+import com.nextcloud.talk.utils.ConductorRemapping.remapChatController
+import com.nextcloud.talk.utils.DisplayUtils
+import com.nextcloud.talk.utils.Mimetype
+import com.nextcloud.talk.utils.UriUtils.Companion.getFileName
+import com.nextcloud.talk.utils.bundle.BundleKeys
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_HIDE_SOURCE_ROOM
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_MSG_FLAG
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_MSG_TEXT
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NEW_CONVERSATION
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SHARED_TEXT
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
+import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.getAttachmentFolder
+import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability
+import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isServerEOL
+import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isUnifiedSearchAvailable
+import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isUserStatusAvailable
+import com.nextcloud.talk.utils.rx.SearchViewObservable.Companion.observeSearchView
+import eu.davidea.flexibleadapter.FlexibleAdapter
+import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
+import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
+import io.reactivex.Observable
+import io.reactivex.Observer
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import io.reactivex.schedulers.Schedulers
+import org.apache.commons.lang3.builder.CompareToBuilder
+import org.greenrobot.eventbus.EventBus
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+import org.parceler.Parcels
+import retrofit2.HttpException
+import java.util.Objects
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+@AutoInjector(NextcloudTalkApplication::class)
+class ConversationsListController(bundle: Bundle) :
+    BaseController(R.layout.controller_conversations_rv, bundle),
+    FlexibleAdapter.OnItemClickListener,
+    FlexibleAdapter.OnItemLongClickListener,
+    ConversationMenuInterface {
+    private val bundle: Bundle
+
+    @Inject
+    lateinit var userManager: UserManager
+
+    @Inject
+    lateinit var eventBus: EventBus
+
+    @Inject
+    lateinit var ncApi: NcApi
+
+    @Inject
+    lateinit var unifiedSearchRepository: UnifiedSearchRepository
+
+    private val binding: ControllerConversationsRvBinding by viewBinding(ControllerConversationsRvBinding::bind)
+
+    override val title: String
+        get() = resources!!.getString(R.string.nc_app_product_name)
+
+    override val appBarLayoutType: AppBarLayoutType
+        get() = AppBarLayoutType.SEARCH_BAR
+
+    private var currentUser: User? = null
+    private var roomsQueryDisposable: Disposable? = null
+    private var openConversationsQueryDisposable: Disposable? = null
+    private var adapter: FlexibleAdapter<AbstractFlexibleItem<*>>? = null
+    private var conversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
+    private var conversationItemsWithHeader: MutableList<AbstractFlexibleItem<*>> = ArrayList()
+    private val searchableConversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
+    private var searchItem: MenuItem? = null
+    private var searchView: SearchView? = null
+    private var searchQuery: String? = null
+    private var credentials: String? = null
+    private var adapterWasNull = true
+    private var isRefreshing = false
+    private var conversationMenuBundle: Bundle? = null
+    private var showShareToScreen = false
+    private var filesToShare: ArrayList<String>? = null
+    private var selectedConversation: Conversation? = null
+    private var textToPaste: String? = ""
+    private var selectedMessageId: String? = null
+    private var forwardMessage: Boolean
+    private var nextUnreadConversationScrollPosition = 0
+    private var layoutManager: SmoothScrollLinearLayoutManager? = null
+    private val callHeaderItems = HashMap<String, GenericTextHeaderItem>()
+    private var conversationsListBottomDialog: ConversationsListBottomDialog? = null
+    private val userStatuses = HashMap<String?, Status>()
+    private var searchHelper: MessageSearchHelper? = null
+    private var searchViewDisposable: Disposable? = null
+
+    override fun onViewBound(view: View) {
+        super.onViewBound(view)
+        sharedApplication!!.componentApplication.inject(this)
+        actionBar?.show()
+        if (adapter == null) {
+            adapter = FlexibleAdapter(conversationItems, activity, true)
+        } else {
+            binding.loadingContent.visibility = View.GONE
+        }
+        adapter!!.addListener(this)
+        prepareViews()
+    }
+
+    private fun loadUserAvatar(button: MaterialButton) {
+        if (activity != null) {
+            val imageRequest = DisplayUtils.getImageRequestForUrl(
+                ApiUtils.getUrlForAvatar(
+                    currentUser!!.baseUrl,
+                    currentUser!!.userId,
+                    true
+                ),
+                currentUser
+            )
+            val imagePipeline = Fresco.getImagePipeline()
+            val dataSource = imagePipeline.fetchDecodedImage(imageRequest, null)
+            dataSource.subscribe(
+                object : BaseBitmapDataSubscriber() {
+                    override fun onNewResultImpl(bitmap: Bitmap?) {
+                        if (bitmap != null && resources != null) {
+                            val roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(
+                                resources!!,
+                                bitmap
+                            )
+                            roundedBitmapDrawable.isCircular = true
+                            roundedBitmapDrawable.setAntiAlias(true)
+                            button.icon = roundedBitmapDrawable
+                        }
+                    }
+
+                    override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage?>>) {
+                        if (resources != null) {
+                            button.icon = ResourcesCompat.getDrawable(resources!!, R.drawable.ic_user, null)
+                        }
+                    }
+                },
+                UiThreadImmediateExecutorService.getInstance()
+            )
+        }
+    }
+
+    override fun onAttach(view: View) {
+        Log.d(
+            TAG,
+            "onAttach: Controller: " + System.identityHashCode(this) +
+                " Activity: " + System.identityHashCode(activity)
+        )
+        super.onAttach(view)
+        ClosedInterfaceImpl().setUpPushTokenRegistration()
+        if (!eventBus.isRegistered(this)) {
+            eventBus.register(this)
+        }
+        currentUser = userManager.currentUser.blockingGet()
+        if (currentUser != null) {
+            if (isServerEOL(currentUser!!)) {
+                showServerEOLDialog()
+                return
+            }
+            if (isUnifiedSearchAvailable(currentUser!!)) {
+                searchHelper = MessageSearchHelper(unifiedSearchRepository)
+            }
+            credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
+            if (activity != null && activity is MainActivity) {
+                loadUserAvatar((activity as MainActivity?)!!.binding.switchAccountButton)
+                viewThemeUtils.colorMaterialTextButton((activity as MainActivity?)!!.binding.switchAccountButton)
+            }
+            fetchData()
+        }
+    }
+
+    override fun onDetach(view: View) {
+        Log.d(
+            TAG,
+            "onDetach: Controller: " + System.identityHashCode(this) +
+                " Activity: " + System.identityHashCode(activity)
+        )
+        super.onDetach(view)
+        eventBus.unregister(this)
+    }
+
+    private fun initSearchView() {
+        if (activity != null) {
+            val searchManager = activity!!.getSystemService(Context.SEARCH_SERVICE) as SearchManager?
+            if (searchItem != null) {
+                searchView = MenuItemCompat.getActionView(searchItem) as SearchView
+                viewThemeUtils.themeSearchView(searchView!!)
+                searchView!!.maxWidth = Int.MAX_VALUE
+                searchView!!.inputType = InputType.TYPE_TEXT_VARIATION_FILTER
+                var imeOptions = EditorInfo.IME_ACTION_DONE or EditorInfo.IME_FLAG_NO_FULLSCREEN
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences.isKeyboardIncognito) {
+                    imeOptions = imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
+                }
+                searchView!!.imeOptions = imeOptions
+                searchView!!.queryHint = searchHint
+                if (searchManager != null) {
+                    searchView!!.setSearchableInfo(searchManager.getSearchableInfo(activity!!.componentName))
+                }
+                searchViewDisposable = observeSearchView(searchView!!)
+                    .debounce { query: String? ->
+                        if (TextUtils.isEmpty(query)) {
+                            return@debounce Observable.empty<Long>()
+                        } else {
+                            return@debounce Observable.timer(
+                                SEARCH_DEBOUNCE_INTERVAL_MS.toLong(),
+                                TimeUnit.MILLISECONDS
+                            )
+                        }
+                    }
+                    .distinctUntilChanged()
+                    .subscribeOn(Schedulers.io())
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .subscribe { newText: String? -> onQueryTextChange(newText) }
+            }
+        }
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+        super.onCreateOptionsMenu(menu, inflater)
+        inflater.inflate(R.menu.menu_conversation_plus_filter, menu)
+        searchItem = menu.findItem(R.id.action_search)
+        initSearchView()
+    }
+
+    override fun onPrepareOptionsMenu(menu: Menu) {
+        super.onPrepareOptionsMenu(menu)
+        searchView = MenuItemCompat.getActionView(searchItem) as SearchView
+        showShareToScreen = !showShareToScreen && hasActivityActionSendIntent()
+        if (showShareToScreen) {
+            hideSearchBar()
+            actionBar?.setTitle(R.string.send_to_three_dots)
+        } else if (forwardMessage) {
+            hideSearchBar()
+            actionBar?.setTitle(R.string.nc_forward_to_three_dots)
+        } else {
+            val activity = activity as MainActivity?
+            searchItem!!.isVisible = conversationItems.size > 0
+            if (activity != null) {
+                if (adapter!!.hasFilter()) {
+                    showSearchView(activity, searchView, searchItem)
+                    searchView!!.setQuery(adapter!!.getFilter(String::class.java), false)
+                }
+                activity.binding.searchText.setOnClickListener {
+                    showSearchView(activity, searchView, searchItem)
+                    viewThemeUtils.themeStatusBar(activity, searchView!!)
+                }
+            }
+            searchView!!.setOnCloseListener {
+                if (TextUtils.isEmpty(searchView!!.query.toString())) {
+                    searchView!!.onActionViewCollapsed()
+                    if (activity != null) {
+                        viewThemeUtils.resetStatusBar(activity, searchView!!)
+                    }
+                } else {
+                    searchView!!.post { searchView!!.setQuery(TAG, true) }
+                }
+                true
+            }
+            searchItem!!.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
+                override fun onMenuItemActionExpand(item: MenuItem): Boolean {
+                    adapter!!.setHeadersShown(true)
+                    adapter!!.updateDataSet(searchableConversationItems, false)
+                    adapter!!.showAllHeaders()
+                    withNullableControllerViewBinding {
+                        binding.swipeRefreshLayoutView.isEnabled = false
+                    }
+                    return true
+                }
+
+                override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
+                    adapter!!.setHeadersShown(false)
+                    adapter!!.updateDataSet(conversationItems, false)
+                    adapter!!.hideAllHeaders()
+                    if (searchHelper != null) {
+                        // cancel any pending searches
+                        searchHelper!!.cancelSearch()
+                        binding.swipeRefreshLayoutView.isRefreshing = false
+                    }
+                    withNullableControllerViewBinding {
+                        binding.swipeRefreshLayoutView.isEnabled = true
+                    }
+                    searchView!!.onActionViewCollapsed()
+                    val mainActivity = getActivity() as MainActivity?
+                    if (mainActivity != null) {
+                        mainActivity.binding.appBar.stateListAnimator = AnimatorInflater.loadStateListAnimator(
+                            mainActivity.binding.appBar.context,
+                            R.animator.appbar_elevation_off
+                        )
+                        mainActivity.binding.toolbar.visibility = View.GONE
+                        mainActivity.binding.searchToolbar.visibility = View.VISIBLE
+                        if (resources != null) {
+                            viewThemeUtils.resetStatusBar(mainActivity, mainActivity.binding.searchToolbar)
+                        }
+                    }
+                    val layoutManager = binding.recyclerView.layoutManager as SmoothScrollLinearLayoutManager?
+                    layoutManager?.scrollToPositionWithOffset(0, 0)
+                    return true
+                }
+            })
+        }
+    }
+
+    private fun hasActivityActionSendIntent(): Boolean {
+        return if (activity != null) {
+            Intent.ACTION_SEND == activity!!.intent.action || Intent.ACTION_SEND_MULTIPLE == activity!!.intent.action
+        } else false
+    }
+
+    override fun showSearchOrToolbar() {
+        if (TextUtils.isEmpty(searchQuery)) {
+            super.showSearchOrToolbar()
+        }
+    }
+
+    private fun showSearchView(activity: MainActivity, searchView: SearchView?, searchItem: MenuItem?) {
+        activity.binding.appBar.stateListAnimator = AnimatorInflater.loadStateListAnimator(
+            activity.binding.appBar.context,
+            R.animator.appbar_elevation_on
+        )
+        activity.binding.toolbar.visibility = View.VISIBLE
+        activity.binding.searchToolbar.visibility = View.GONE
+        searchItem!!.expandActionView()
+    }
+
+    fun fetchData() {
+        if (isUserStatusAvailable(userManager.currentUser.blockingGet())) {
+            fetchUserStatusesAndRooms()
+        } else {
+            fetchRooms()
+        }
+    }
+
+    private fun fetchUserStatusesAndRooms() {
+        ncApi.getUserStatuses(credentials, ApiUtils.getUrlForUserStatuses(currentUser!!.baseUrl))
+            .subscribe(object : Observer<StatusesOverall> {
+                override fun onSubscribe(d: Disposable) {
+                    // unused atm
+                }
+                override fun onNext(statusesOverall: StatusesOverall) {
+                    for (status in statusesOverall.ocs!!.data!!) {
+                        userStatuses[status.userId] = status
+                    }
+                    fetchRooms()
+                }
+
+                override fun onError(e: Throwable) {
+                    Log.e(TAG, "failed to fetch user statuses", e)
+                    fetchRooms()
+                }
+
+                override fun onComplete() {
+                    // unused atm
+                }
+            })
+    }
+
+    private fun fetchRooms() {
+        dispose(null)
+        isRefreshing = true
+        conversationItems = ArrayList()
+        conversationItemsWithHeader = ArrayList()
+        val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv3, 1))
+        val startNanoTime = System.nanoTime()
+        Log.d(TAG, "fetchData - getRooms - calling: $startNanoTime")
+        roomsQueryDisposable = ncApi.getRooms(
+            credentials,
+            ApiUtils.getUrlForRooms(
+                apiVersion,
+                currentUser!!.baseUrl
+            )
+        )
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribe({ (ocs): RoomsOverall ->
+                Log.d(TAG, "fetchData - getRooms - got response: $startNanoTime")
+
+                // This is invoked asynchronously, when server returns a response the view might have been
+                // unbound in the meantime. Check if the view is still there.
+                // FIXME - does it make sense to update internal data structures even when view has been unbound?
+                if (view == null) {
+                    Log.d(TAG, "fetchData - getRooms - view is not bound: $startNanoTime")
+                    return@subscribe
+                }
+                if (adapterWasNull) {
+                    adapterWasNull = false
+                    binding.loadingContent.visibility = View.GONE
+                }
+                if (ocs!!.data!!.isNotEmpty()) {
+                    if (binding.emptyLayout.visibility != View.GONE) {
+                        binding.emptyLayout.visibility = View.GONE
+                    }
+                    if (binding.swipeRefreshLayoutView.visibility != View.VISIBLE) {
+                        binding.swipeRefreshLayoutView.visibility = View.VISIBLE
+                    }
+                } else {
+                    if (binding.emptyLayout.visibility != View.VISIBLE) {
+                        binding.emptyLayout.visibility = View.VISIBLE
+                    }
+                    if (binding.swipeRefreshLayoutView.visibility != View.GONE) {
+                        binding.swipeRefreshLayoutView.visibility = View.GONE
+                    }
+                }
+                for (conversation in ocs.data!!) {
+                    if (bundle.containsKey(KEY_FORWARD_HIDE_SOURCE_ROOM) && conversation.roomId == bundle.getString(
+                            KEY_FORWARD_HIDE_SOURCE_ROOM
+                        )
+                    ) {
+                        continue
+                    }
+                    val headerTitle: String = resources!!.getString(R.string.conversations)
+                    var genericTextHeaderItem: GenericTextHeaderItem
+                    if (!callHeaderItems.containsKey(headerTitle)) {
+                        genericTextHeaderItem = GenericTextHeaderItem(headerTitle, viewThemeUtils)
+                        callHeaderItems[headerTitle] = genericTextHeaderItem
+                    }
+                    if (activity != null) {
+                        val conversationItem = ConversationItem(
+                            conversation,
+                            currentUser,
+                            activity,
+                            userStatuses[conversation.name],
+                            viewThemeUtils
+                        )
+                        conversationItems.add(conversationItem)
+                        val conversationItemWithHeader = ConversationItem(
+                            conversation,
+                            currentUser,
+                            activity,
+                            callHeaderItems[headerTitle],
+                            userStatuses[conversation.name],
+                            viewThemeUtils
+                        )
+                        conversationItemsWithHeader.add(conversationItemWithHeader)
+                    }
+                }
+                sortConversations(conversationItems)
+                sortConversations(conversationItemsWithHeader)
+                adapter!!.updateDataSet(conversationItems, false)
+                Handler().postDelayed({ checkToShowUnreadBubble() }, UNREAD_BUBBLE_DELAY.toLong())
+                fetchOpenConversations(apiVersion)
+                withNullableControllerViewBinding {
+                    binding.swipeRefreshLayoutView.isRefreshing = false
+                }
+            }, { throwable: Throwable ->
+                handleHttpExceptions(throwable)
+                withNullableControllerViewBinding {
+                    binding.swipeRefreshLayoutView.isRefreshing = false
+                }
+                dispose(roomsQueryDisposable)
+            }) {
+                dispose(roomsQueryDisposable)
+                withNullableControllerViewBinding {
+                    binding.swipeRefreshLayoutView.isRefreshing = false
+                }
+                isRefreshing = false
+            }
+    }
+
+    private fun sortConversations(conversationItems: MutableList<AbstractFlexibleItem<*>>) {
+        conversationItems.sortWith { o1: AbstractFlexibleItem<*>, o2: AbstractFlexibleItem<*> ->
+            val conversation1 = (o1 as ConversationItem).model
+            val conversation2 = (o2 as ConversationItem).model
+            CompareToBuilder()
+                .append(conversation2.favorite, conversation1.favorite)
+                .append(conversation2.lastActivity, conversation1.lastActivity)
+                .toComparison()
+        }
+    }
+
+    private fun fetchOpenConversations(apiVersion: Int) {
+        searchableConversationItems.clear()
+        searchableConversationItems.addAll(conversationItemsWithHeader)
+        if (hasSpreedFeatureCapability(currentUser, "listable-rooms")) {
+            val openConversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
+            openConversationsQueryDisposable = ncApi.getOpenConversations(
+                credentials,
+                ApiUtils.getUrlForOpenConversations(apiVersion, currentUser!!.baseUrl)
+            )
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe({ (ocs): RoomsOverall ->
+                    for (conversation in ocs!!.data!!) {
+                        val headerTitle = resources!!.getString(R.string.openConversations)
+                        var genericTextHeaderItem: GenericTextHeaderItem
+                        if (!callHeaderItems.containsKey(headerTitle)) {
+                            genericTextHeaderItem = GenericTextHeaderItem(headerTitle, viewThemeUtils)
+                            callHeaderItems[headerTitle] = genericTextHeaderItem
+                        }
+                        val conversationItem = ConversationItem(
+                            conversation,
+                            currentUser,
+                            activity,
+                            callHeaderItems[headerTitle],
+                            userStatuses[conversation.name],
+                            viewThemeUtils
+                        )
+                        openConversationItems.add(conversationItem)
+                    }
+                    searchableConversationItems.addAll(openConversationItems)
+                }, { throwable: Throwable ->
+                    Log.e(TAG, "fetchData - getRooms - ERROR", throwable)
+                    handleHttpExceptions(throwable)
+                    dispose(openConversationsQueryDisposable)
+                }) { dispose(openConversationsQueryDisposable) }
+        } else {
+            Log.d(TAG, "no open conversations fetched because of missing capability")
+        }
+    }
+
+    private fun handleHttpExceptions(throwable: Throwable) {
+        if (throwable is HttpException) {
+            when (throwable.code()) {
+                HTTP_UNAUTHORIZED -> if (parentController != null && parentController!!.router != null) {
+                    Log.d(TAG, "Starting reauth webview via getParentController()")
+                    parentController!!.router.pushController(
+                        RouterTransaction.with(
+                            WebViewLoginController(
+                                currentUser!!.baseUrl,
+                                true
+                            )
+                        )
+                            .pushChangeHandler(VerticalChangeHandler())
+                            .popChangeHandler(VerticalChangeHandler())
+                    )
+                } else {
+                    Log.d(TAG, "Starting reauth webview via ConversationsListController")
+                    showUnauthorizedDialog()
+                }
+                else -> {}
+            }
+        }
+    }
+
+    @SuppressLint("ClickableViewAccessibility")
+    private fun prepareViews() {
+        layoutManager = SmoothScrollLinearLayoutManager(Objects.requireNonNull(activity))
+        binding.recyclerView.layoutManager = layoutManager
+        binding.recyclerView.setHasFixedSize(true)
+        binding.recyclerView.adapter = adapter
+        binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
+            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
+                super.onScrollStateChanged(recyclerView, newState)
+                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+                    checkToShowUnreadBubble()
+                }
+            }
+        })
+        binding.recyclerView.setOnTouchListener { v: View, _: MotionEvent? ->
+            if (isAttached && (!isBeingDestroyed || !isDestroyed)) {
+                val imm = activity!!.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+                imm.hideSoftInputFromWindow(v.windowToken, 0)
+            }
+            false
+        }
+        binding.swipeRefreshLayoutView.setOnRefreshListener { fetchData() }
+        viewThemeUtils.themeSwipeRefreshLayout(binding.swipeRefreshLayoutView)
+        binding.emptyLayout.setOnClickListener { showNewConversationsScreen() }
+        binding.floatingActionButton.setOnClickListener {
+            run(context)
+            showNewConversationsScreen()
+        }
+        viewThemeUtils.themeFAB(binding.floatingActionButton)
+        if (activity != null && activity is MainActivity) {
+            val activity = activity as MainActivity?
+            activity!!.binding.switchAccountButton.setOnClickListener {
+                if (resources != null && resources!!.getBoolean(R.bool.multiaccount_support)) {
+                    val newFragment: DialogFragment = ChooseAccountDialogFragment.newInstance()
+                    newFragment.show(
+                        (getActivity() as MainActivity?)!!.supportFragmentManager,
+                        "ChooseAccountDialogFragment"
+                    )
+                } else {
+                    router.pushController(
+                        RouterTransaction.with(SettingsController())
+                            .pushChangeHandler(HorizontalChangeHandler())
+                            .popChangeHandler(HorizontalChangeHandler())
+                    )
+                }
+            }
+        }
+        binding.newMentionPopupBubble.hide()
+        binding.newMentionPopupBubble.setPopupBubbleListener {
+            binding.recyclerView.smoothScrollToPosition(
+                nextUnreadConversationScrollPosition
+            )
+        }
+        viewThemeUtils.colorMaterialButtonPrimaryFilled(binding.newMentionPopupBubble)
+    }
+
+    @Suppress("Detekt.TooGenericExceptionCaught")
+    private fun checkToShowUnreadBubble() {
+        try {
+            val lastVisibleItem = layoutManager!!.findLastCompletelyVisibleItemPosition()
+            for (flexItem in conversationItems) {
+                val conversation: Conversation = (flexItem as ConversationItem).model
+                val position = adapter!!.getGlobalPositionOf(flexItem)
+                if ((
+                    conversation.unreadMention ||
+                        conversation.unreadMessages > 0 &&
+                        conversation.type === Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
+                    ) && position > lastVisibleItem
+                ) {
+                    nextUnreadConversationScrollPosition = position
+                    if (!binding.newMentionPopupBubble.isShown) {
+                        binding.newMentionPopupBubble.show()
+                    }
+                    return
+                }
+            }
+            nextUnreadConversationScrollPosition = 0
+            binding.newMentionPopupBubble.hide()
+        } catch (e: NullPointerException) {
+            Log.d(
+                TAG,
+                "A NPE was caught when trying to show the unread popup bubble. This might happen when the " +
+                    "user already left the conversations-list screen so the popup bubble is not available anymore.",
+                e
+            )
+        }
+    }
+
+    private fun showNewConversationsScreen() {
+        val bundle = Bundle()
+        bundle.putBoolean(KEY_NEW_CONVERSATION, true)
+        router.pushController(
+            RouterTransaction.with(ContactsController(bundle))
+                .pushChangeHandler(HorizontalChangeHandler())
+                .popChangeHandler(HorizontalChangeHandler())
+        )
+    }
+
+    private fun dispose(disposable: Disposable?) {
+        if (disposable != null && !disposable.isDisposed) {
+            disposable.dispose()
+        } else if (disposable == null && roomsQueryDisposable != null && !roomsQueryDisposable!!.isDisposed) {
+            roomsQueryDisposable!!.dispose()
+            roomsQueryDisposable = null
+        } else if (disposable == null &&
+            openConversationsQueryDisposable != null &&
+            !openConversationsQueryDisposable!!.isDisposed
+        ) {
+            openConversationsQueryDisposable!!.dispose()
+            openConversationsQueryDisposable = null
+        }
+    }
+
+    public override fun onSaveViewState(view: View, outState: Bundle) {
+        if (searchView != null && !TextUtils.isEmpty(searchView!!.query)) {
+            outState.putString(KEY_SEARCH_QUERY, searchView!!.query.toString())
+        }
+        super.onSaveViewState(view, outState)
+    }
+
+    public override fun onRestoreViewState(view: View, savedViewState: Bundle) {
+        super.onRestoreViewState(view, savedViewState)
+        if (savedViewState.containsKey(KEY_SEARCH_QUERY)) {
+            searchQuery = savedViewState.getString(KEY_SEARCH_QUERY, "")
+        }
+    }
+
+    public override fun onDestroy() {
+        super.onDestroy()
+        dispose(null)
+        if (searchViewDisposable != null && !searchViewDisposable!!.isDisposed) {
+            searchViewDisposable!!.dispose()
+        }
+    }
+
+    private fun onQueryTextChange(newText: String?) {
+        if (!TextUtils.isEmpty(searchQuery)) {
+            val filter = searchQuery
+            searchQuery = ""
+            performFilterAndSearch(filter)
+        } else if (adapter!!.hasNewFilter(newText)) {
+            performFilterAndSearch(newText)
+        }
+    }
+
+    private fun performFilterAndSearch(filter: String?) {
+        if (filter!!.length >= SEARCH_MIN_CHARS) {
+            clearMessageSearchResults()
+            adapter!!.setFilter(filter)
+            adapter!!.filterItems()
+            if (isUnifiedSearchAvailable(currentUser!!)) {
+                startMessageSearch(filter)
+            }
+        } else {
+            resetSearchResults()
+        }
+    }
+
+    private fun resetSearchResults() {
+        clearMessageSearchResults()
+        adapter!!.setFilter("")
+        adapter!!.filterItems()
+    }
+
+    private fun clearMessageSearchResults() {
+        val firstHeader = adapter!!.getSectionHeader(0)
+        if (firstHeader != null && firstHeader.itemViewType == MessagesTextHeaderItem.VIEW_TYPE) {
+            adapter!!.removeSection(firstHeader)
+        } else {
+            adapter!!.removeItemsOfType(MessageResultItem.VIEW_TYPE)
+        }
+        adapter!!.removeItemsOfType(LoadMoreResultsItem.VIEW_TYPE)
+    }
+
+    @SuppressLint("CheckResult") // handled by helper
+    private fun startMessageSearch(search: String?) {
+        withNullableControllerViewBinding {
+            binding.swipeRefreshLayoutView.isRefreshing = true
+        }
+        searchHelper?.startMessageSearch(search!!)
+            ?.subscribeOn(Schedulers.io())
+            ?.observeOn(AndroidSchedulers.mainThread())
+            ?.subscribe({ results: MessageSearchResults -> onMessageSearchResult(results) }) { throwable: Throwable ->
+                onMessageSearchError(
+                    throwable
+                )
+            }
+    }
+
+    @SuppressLint("CheckResult") // handled by helper
+    private fun loadMoreMessages() {
+        binding.swipeRefreshLayoutView.isRefreshing = true
+        val observable = searchHelper!!.loadMore()
+        observable?.observeOn(AndroidSchedulers.mainThread())
+            ?.subscribe({ results: MessageSearchResults -> onMessageSearchResult(results) }) { throwable: Throwable ->
+                onMessageSearchError(
+                    throwable
+                )
+            }
+    }
+
+    override fun onItemClick(view: View, position: Int): Boolean {
+        val item = adapter!!.getItem(position)
+        if (item != null) {
+            when (item.itemViewType) {
+                MessageResultItem.VIEW_TYPE -> {
+                    val messageItem: MessageResultItem = item as MessageResultItem
+                    val conversationToken = messageItem.messageEntry.conversationToken
+                    selectedMessageId = messageItem.messageEntry.messageId
+                    showConversationByToken(conversationToken)
+                }
+                LoadMoreResultsItem.VIEW_TYPE -> {
+                    loadMoreMessages()
+                }
+                ConversationItem.VIEW_TYPE -> {
+                    showConversation((Objects.requireNonNull(item) as ConversationItem).model)
+                }
+            }
+        }
+        return true
+    }
+
+    private fun showConversationByToken(conversationToken: String) {
+        for (absItem in conversationItems) {
+            val conversationItem = absItem as ConversationItem
+            if (conversationItem.model.token == conversationToken) {
+                val conversation = conversationItem.model
+                showConversation(conversation)
+            }
+        }
+    }
+
+    private fun showConversation(conversation: Conversation?) {
+        selectedConversation = conversation
+        if (selectedConversation != null && activity != null) {
+            val hasChatPermission = AttendeePermissionsUtil(selectedConversation!!.permissions).hasChatPermission(
+                currentUser!!
+            )
+            if (showShareToScreen) {
+                if (hasChatPermission && !isReadOnlyConversation(selectedConversation!!)) {
+                    handleSharedData()
+                    showShareToScreen = false
+                } else {
+                    Toast.makeText(context, R.string.send_to_forbidden, Toast.LENGTH_LONG).show()
+                }
+            } else if (forwardMessage) {
+                if (hasChatPermission && !isReadOnlyConversation(selectedConversation!!)) {
+                    openConversation(bundle.getString(KEY_FORWARD_MSG_TEXT))
+                    forwardMessage = false
+                } else {
+                    Toast.makeText(context, R.string.send_to_forbidden, Toast.LENGTH_LONG).show()
+                }
+            } else {
+                openConversation()
+            }
+        }
+    }
+
+    private fun isReadOnlyConversation(conversation: Conversation): Boolean {
+        return conversation.conversationReadOnlyState ===
+            Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY
+    }
+
+    private fun handleSharedData() {
+        collectDataFromIntent()
+        if (textToPaste!!.isNotEmpty()) {
+            openConversation(textToPaste)
+        } else if (filesToShare != null && filesToShare!!.isNotEmpty()) {
+            showSendFilesConfirmDialog()
+        } else {
+            Toast
+                .makeText(context, context.resources.getString(R.string.nc_common_error_sorry), Toast.LENGTH_LONG)
+                .show()
+        }
+    }
+
+    private fun showSendFilesConfirmDialog() {
+        if (isStoragePermissionGranted(context)) {
+            val fileNamesWithLineBreaks = StringBuilder("\n")
+            for (file in filesToShare!!) {
+                val filename = getFileName(Uri.parse(file), context)
+                fileNamesWithLineBreaks.append(filename).append("\n")
+            }
+            val confirmationQuestion: String = if (filesToShare!!.size == 1) {
+                String.format(
+                    resources!!.getString(R.string.nc_upload_confirm_send_single),
+                    selectedConversation!!.displayName
+                )
+            } else {
+                String.format(
+                    resources!!.getString(R.string.nc_upload_confirm_send_multiple),
+                    selectedConversation!!.displayName
+                )
+            }
+            val dialogBuilder = MaterialAlertDialogBuilder(binding.floatingActionButton.context)
+                .setIcon(viewThemeUtils.colorMaterialAlertDialogIcon(context, R.drawable.upload))
+                .setTitle(confirmationQuestion)
+                .setMessage(fileNamesWithLineBreaks.toString())
+                .setPositiveButton(R.string.nc_yes) { _, _ ->
+                    upload()
+                    openConversation()
+                }
+                .setNegativeButton(R.string.nc_no) { _, _ ->
+                    Log.d(TAG, "sharing files aborted, going back to share-to screen")
+                    showShareToScreen = true
+                }
+            viewThemeUtils.colorMaterialAlertDialogBackground(binding.floatingActionButton.context, dialogBuilder)
+            val dialog = dialogBuilder.show()
+            viewThemeUtils.colorTextButtons(
+                dialog.getButton(AlertDialog.BUTTON_POSITIVE),
+                dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
+            )
+        } else {
+            requestStoragePermission(this@ConversationsListController)
+        }
+    }
+
+    override fun onItemLongClick(position: Int) {
+        if (showShareToScreen) {
+            Log.d(TAG, "sharing to multiple rooms not yet implemented. onItemLongClick is ignored.")
+        } else {
+            val clickedItem: Any? = adapter!!.getItem(position)
+            if (clickedItem != null) {
+                val conversation = (clickedItem as ConversationItem).model
+                conversationsListBottomDialog = ConversationsListBottomDialog(
+                    activity!!,
+                    this,
+                    userManager.currentUser.blockingGet(),
+                    conversation
+                )
+                conversationsListBottomDialog!!.show()
+            }
+        }
+    }
+
+    @Suppress("Detekt.TooGenericExceptionCaught")
+    private fun collectDataFromIntent() {
+        filesToShare = ArrayList()
+        if (activity != null && activity!!.intent != null) {
+            val intent = activity!!.intent
+            if (Intent.ACTION_SEND == intent.action || Intent.ACTION_SEND_MULTIPLE == intent.action) {
+                try {
+                    val mimeType = intent.type
+                    if (Mimetype.TEXT_PLAIN == mimeType && intent.getStringExtra(Intent.EXTRA_TEXT) != null) {
+                        // Share from Google Chrome sets text/plain MIME type, but also provides a content:// URI
+                        // with a *screenshot* of the current page in getClipData().
+                        // Here we assume that when sharing a web page the user would prefer to send the URL
+                        // of the current page rather than a screenshot.
+                        textToPaste = intent.getStringExtra(Intent.EXTRA_TEXT)
+                    } else {
+                        if (intent.clipData != null) {
+                            for (i in 0 until intent.clipData!!.itemCount) {
+                                val item = intent.clipData!!.getItemAt(i)
+                                if (item.uri != null) {
+                                    filesToShare!!.add(item.uri.toString())
+                                } else if (item.text != null) {
+                                    textToPaste = item.text.toString()
+                                    break
+                                } else {
+                                    Log.w(TAG, "datatype not yet implemented for share-to")
+                                }
+                            }
+                        } else {
+                            filesToShare!!.add(intent.data.toString())
+                        }
+                    }
+                    if (filesToShare!!.isEmpty() && textToPaste!!.isEmpty()) {
+                        Toast.makeText(
+                            context,
+                            context.resources.getString(R.string.nc_common_error_sorry),
+                            Toast.LENGTH_LONG
+                        ).show()
+                        Log.e(TAG, "failed to get data from intent")
+                    }
+                } catch (e: Exception) {
+                    Toast.makeText(
+                        context,
+                        context.resources.getString(R.string.nc_common_error_sorry),
+                        Toast.LENGTH_LONG
+                    ).show()
+                    Log.e(TAG, "Something went wrong when extracting data from intent")
+                }
+            }
+        }
+    }
+
+    private fun upload() {
+        if (selectedConversation == null) {
+            Toast.makeText(
+                context,
+                context.resources.getString(R.string.nc_common_error_sorry),
+                Toast.LENGTH_LONG
+            ).show()
+            Log.e(TAG, "not able to upload any files because conversation was null.")
+            return
+        }
+        try {
+            var filesToShareArray: Array<String?> = arrayOfNulls(filesToShare!!.size)
+            filesToShareArray = filesToShare!!.toArray(filesToShareArray)
+            val data = Data.Builder()
+                .putStringArray(UploadAndShareFilesWorker.DEVICE_SOURCEFILES, filesToShareArray)
+                .putString(
+                    UploadAndShareFilesWorker.NC_TARGETPATH,
+                    getAttachmentFolder(currentUser!!)
+                )
+                .putString(UploadAndShareFilesWorker.ROOM_TOKEN, selectedConversation!!.token)
+                .build()
+            val uploadWorker = OneTimeWorkRequest.Builder(UploadAndShareFilesWorker::class.java)
+                .setInputData(data)
+                .build()
+            WorkManager.getInstance().enqueue(uploadWorker)
+            Toast.makeText(
+                context,
+                context.resources.getString(R.string.nc_upload_in_progess),
+                Toast.LENGTH_LONG
+            ).show()
+        } catch (e: IllegalArgumentException) {
+            Toast.makeText(context, context.resources.getString(R.string.nc_upload_failed), Toast.LENGTH_LONG).show()
+            Log.e(TAG, "Something went wrong when trying to upload file", e)
+        }
+    }
+
+    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
+        if (requestCode == UploadAndShareFilesWorker.REQUEST_PERMISSION &&
+            grantResults.isNotEmpty() &&
+            grantResults[0] == PackageManager.PERMISSION_GRANTED
+        ) {
+            Log.d(TAG, "upload starting after permissions were granted")
+            showSendFilesConfirmDialog()
+        } else {
+            Toast.makeText(context, context.getString(R.string.read_storage_no_permission), Toast.LENGTH_LONG).show()
+        }
+    }
+
+    private fun openConversation(textToPaste: String? = "") {
+        val bundle = Bundle()
+        bundle.putParcelable(KEY_USER_ENTITY, currentUser)
+        bundle.putParcelable(KEY_ACTIVE_CONVERSATION, Parcels.wrap(selectedConversation))
+        bundle.putString(KEY_ROOM_TOKEN, selectedConversation!!.token)
+        bundle.putString(KEY_ROOM_ID, selectedConversation!!.roomId)
+        bundle.putString(KEY_SHARED_TEXT, textToPaste)
+        if (selectedMessageId != null) {
+            bundle.putString(BundleKeys.KEY_MESSAGE_ID, selectedMessageId)
+            selectedMessageId = null
+        }
+        remapChatController(
+            router,
+            currentUser!!.id!!,
+            selectedConversation!!.token!!,
+            bundle,
+            false
+        )
+    }
+
+    @Subscribe(sticky = true, threadMode = ThreadMode.BACKGROUND)
+    fun onMessageEvent(eventStatus: EventStatus) {
+        if (currentUser != null && eventStatus.userId == currentUser!!.id) {
+            when (eventStatus.eventType) {
+                EventStatus.EventType.CONVERSATION_UPDATE -> if (eventStatus.isAllGood && !isRefreshing) {
+                    fetchData()
+                }
+                else -> {}
+            }
+        }
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    fun onMessageEvent(conversationsListFetchDataEvent: ConversationsListFetchDataEvent?) {
+        fetchData()
+        Handler().postDelayed({
+            if (conversationsListBottomDialog!!.isShowing) {
+                conversationsListBottomDialog!!.dismiss()
+            }
+        }, BOTTOM_SHEET_DELAY)
+    }
+
+    override fun showDeleteConversationDialog(bundle: Bundle) {
+        conversationMenuBundle = bundle
+        if (activity != null &&
+            conversationMenuBundle != null &&
+            currentUser != null &&
+            conversationMenuBundle!!.getLong(KEY_INTERNAL_USER_ID) == currentUser!!.id
+        ) {
+            val conversation = Parcels.unwrap<Conversation>(conversationMenuBundle!!.getParcelable(KEY_ROOM))
+            if (conversation != null) {
+                val dialogBuilder = MaterialAlertDialogBuilder(binding.floatingActionButton.context)
+                    .setIcon(viewThemeUtils.colorMaterialAlertDialogIcon(context, R.drawable.ic_delete_black_24dp))
+                    .setTitle(R.string.nc_delete_call)
+                    .setMessage(R.string.nc_delete_conversation_more)
+                    .setPositiveButton(R.string.nc_delete) { _, _ ->
+                        val data = Data.Builder()
+                        data.putLong(
+                            KEY_INTERNAL_USER_ID,
+                            conversationMenuBundle!!.getLong(KEY_INTERNAL_USER_ID)
+                        )
+                        data.putString(KEY_ROOM_TOKEN, conversation.token)
+                        conversationMenuBundle = null
+                        deleteConversation(data.build())
+                    }
+                    .setNegativeButton(R.string.nc_cancel) { _, _ ->
+                        conversationMenuBundle = null
+                    }
+                viewThemeUtils.colorMaterialAlertDialogBackground(binding.floatingActionButton.context, dialogBuilder)
+                val dialog = dialogBuilder.show()
+                viewThemeUtils.colorTextButtons(
+                    dialog.getButton(AlertDialog.BUTTON_POSITIVE),
+                    dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
+                )
+            }
+        }
+    }
+
+    private fun showUnauthorizedDialog() {
+        if (activity != null) {
+            val dialogBuilder = MaterialAlertDialogBuilder(binding.floatingActionButton.context)
+                .setIcon(viewThemeUtils.colorMaterialAlertDialogIcon(context, R.drawable.ic_delete_black_24dp))
+                .setTitle(R.string.nc_dialog_invalid_password)
+                .setMessage(R.string.nc_dialog_reauth_or_delete)
+                .setCancelable(false)
+                .setPositiveButton(R.string.nc_delete) { _, _ ->
+                    val otherUserExists = userManager
+                        .scheduleUserForDeletionWithId(currentUser!!.id!!)
+                        .blockingGet()
+                    val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
+                    WorkManager.getInstance().enqueue(accountRemovalWork)
+                    if (otherUserExists && view != null) {
+                        onViewBound(view!!)
+                        onAttach(view!!)
+                    } else if (!otherUserExists) {
+                        router.setRoot(
+                            RouterTransaction.with(ServerSelectionController())
+                                .pushChangeHandler(VerticalChangeHandler())
+                                .popChangeHandler(VerticalChangeHandler())
+                        )
+                    }
+                }
+                .setNegativeButton(R.string.nc_settings_reauthorize) { _, _ ->
+                    router.pushController(
+                        RouterTransaction.with(
+                            WebViewLoginController(currentUser!!.baseUrl, true)
+                        )
+                            .pushChangeHandler(VerticalChangeHandler())
+                            .popChangeHandler(VerticalChangeHandler())
+                    )
+                }
+            viewThemeUtils.colorMaterialAlertDialogBackground(binding.floatingActionButton.context, dialogBuilder)
+            val dialog = dialogBuilder.show()
+            viewThemeUtils.colorTextButtons(
+                dialog.getButton(AlertDialog.BUTTON_POSITIVE),
+                dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
+            )
+        }
+    }
+
+    private fun showServerEOLDialog() {
+        val dialogBuilder = MaterialAlertDialogBuilder(binding.floatingActionButton.context)
+            .setIcon(viewThemeUtils.colorMaterialAlertDialogIcon(context, R.drawable.ic_warning_white))
+            .setTitle(R.string.nc_settings_server_eol_title)
+            .setMessage(R.string.nc_settings_server_eol)
+            .setCancelable(false)
+            .setPositiveButton(R.string.nc_settings_remove_account) { _, _ ->
+                val otherUserExists = userManager
+                    .scheduleUserForDeletionWithId(currentUser!!.id!!)
+                    .blockingGet()
+                val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
+                WorkManager.getInstance().enqueue(accountRemovalWork)
+                if (otherUserExists && view != null) {
+                    onViewBound(view!!)
+                    onAttach(view!!)
+                } else if (!otherUserExists) {
+                    router.setRoot(
+                        RouterTransaction.with(
+                            ServerSelectionController()
+                        )
+                            .pushChangeHandler(VerticalChangeHandler())
+                            .popChangeHandler(VerticalChangeHandler())
+                    )
+                }
+            }
+            .setNegativeButton(R.string.nc_cancel) { _, _ ->
+                if (userManager.users.blockingGet().isNotEmpty()) {
+                    router.pushController(RouterTransaction.with(SwitchAccountController()))
+                } else {
+                    activity!!.finishAffinity()
+                    activity!!.finish()
+                }
+            }
+        viewThemeUtils.colorMaterialAlertDialogBackground(binding.floatingActionButton.context, dialogBuilder)
+        val dialog = dialogBuilder.show()
+        viewThemeUtils.colorTextButtons(
+            dialog.getButton(AlertDialog.BUTTON_POSITIVE),
+            dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
+        )
+    }
+
+    private fun deleteConversation(data: Data) {
+        val deleteConversationWorker =
+            OneTimeWorkRequest.Builder(DeleteConversationWorker::class.java).setInputData(data).build()
+        WorkManager.getInstance().enqueue(deleteConversationWorker)
+    }
+
+    private fun onMessageSearchResult(results: MessageSearchResults) {
+        if (searchView!!.query.isNotEmpty()) {
+            clearMessageSearchResults()
+            val entries = results.messages
+            if (entries.isNotEmpty()) {
+                val adapterItems: MutableList<AbstractFlexibleItem<*>> = ArrayList(entries.size + 1)
+                for (i in entries.indices) {
+                    val showHeader = i == 0
+                    adapterItems.add(
+                        MessageResultItem(
+                            context,
+                            currentUser!!,
+                            entries[i],
+                            showHeader,
+                            viewThemeUtils
+                        )
+                    )
+                }
+                if (results.hasMore) {
+                    adapterItems.add(LoadMoreResultsItem)
+                }
+                adapter!!.addItems(0, adapterItems)
+                binding.recyclerView.scrollToPosition(0)
+            }
+        }
+        withNullableControllerViewBinding {
+            binding.swipeRefreshLayoutView.isRefreshing = false
+        }
+    }
+
+    private fun onMessageSearchError(throwable: Throwable) {
+        handleHttpExceptions(throwable)
+        withNullableControllerViewBinding {
+            binding.swipeRefreshLayoutView.isRefreshing = false
+        }
+    }
+
+    companion object {
+        const val TAG = "ConvListController"
+        const val UNREAD_BUBBLE_DELAY = 2500
+        const val BOTTOM_SHEET_DELAY: Long = 2500
+        private const val KEY_SEARCH_QUERY = "ContactsController.searchQuery"
+        const val SEARCH_DEBOUNCE_INTERVAL_MS = 300
+        const val SEARCH_MIN_CHARS = 2
+        const val HTTP_UNAUTHORIZED = 401
+    }
+
+    init {
+        setHasOptionsMenu(true)
+        forwardMessage = bundle.getBoolean(KEY_FORWARD_MSG_FLAG)
+        this.bundle = bundle
+    }
+}

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

@@ -41,7 +41,7 @@ import com.nextcloud.talk.R
 import com.nextcloud.talk.adapters.GeocodingAdapter
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
-import com.nextcloud.talk.controllers.base.NewBaseController
+import com.nextcloud.talk.controllers.base.BaseController
 import com.nextcloud.talk.controllers.util.viewBinding
 import com.nextcloud.talk.databinding.ControllerGeocodingBinding
 import com.nextcloud.talk.utils.bundle.BundleKeys
@@ -58,7 +58,7 @@ import javax.inject.Inject
 
 @AutoInjector(NextcloudTalkApplication::class)
 class GeocodingController(args: Bundle) :
-    NewBaseController(
+    BaseController(
         R.layout.controller_geocoding,
         args
     ),

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

@@ -49,7 +49,7 @@ import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
 import com.nextcloud.talk.R
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
-import com.nextcloud.talk.controllers.base.NewBaseController
+import com.nextcloud.talk.controllers.base.BaseController
 import com.nextcloud.talk.controllers.util.viewBinding
 import com.nextcloud.talk.databinding.ControllerLocationBinding
 import com.nextcloud.talk.models.json.generic.GenericOverall
@@ -83,7 +83,7 @@ import javax.inject.Inject
 
 @AutoInjector(NextcloudTalkApplication::class)
 class LocationPickerController(args: Bundle) :
-    NewBaseController(
+    BaseController(
         R.layout.controller_location,
         args
     ),

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

@@ -39,7 +39,7 @@ import autodagger.AutoInjector
 import com.nextcloud.talk.R
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
-import com.nextcloud.talk.controllers.base.NewBaseController
+import com.nextcloud.talk.controllers.base.BaseController
 import com.nextcloud.talk.controllers.util.viewBinding
 import com.nextcloud.talk.databinding.ControllerLockedBinding
 import com.nextcloud.talk.utils.DisplayUtils
@@ -48,7 +48,7 @@ import java.util.concurrent.Executor
 import java.util.concurrent.Executors
 
 @AutoInjector(NextcloudTalkApplication::class)
-class LockedController : NewBaseController(R.layout.controller_locked) {
+class LockedController : BaseController(R.layout.controller_locked) {
     private val binding: ControllerLockedBinding by viewBinding(ControllerLockedBinding::bind)
 
     override val appBarLayoutType: AppBarLayoutType

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

@@ -52,7 +52,7 @@ import com.nextcloud.talk.activities.TakePhotoActivity
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
-import com.nextcloud.talk.controllers.base.NewBaseController
+import com.nextcloud.talk.controllers.base.BaseController
 import com.nextcloud.talk.controllers.util.viewBinding
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.databinding.ControllerProfileBinding
@@ -94,7 +94,7 @@ import javax.inject.Inject
 
 @AutoInjector(NextcloudTalkApplication::class)
 @Suppress("Detekt.TooManyFunctions")
-class ProfileController : NewBaseController(R.layout.controller_profile) {
+class ProfileController : BaseController(R.layout.controller_profile) {
     private val binding: ControllerProfileBinding by viewBinding(ControllerProfileBinding::bind)
 
     @Inject

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

@@ -38,7 +38,7 @@ import com.nextcloud.talk.R
 import com.nextcloud.talk.adapters.items.NotificationSoundItem
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
-import com.nextcloud.talk.controllers.base.NewBaseController
+import com.nextcloud.talk.controllers.base.BaseController
 import com.nextcloud.talk.controllers.util.viewBinding
 import com.nextcloud.talk.databinding.ControllerGenericRvBinding
 import com.nextcloud.talk.models.RingtoneSettings
@@ -52,7 +52,7 @@ import java.io.IOException
 
 @AutoInjector(NextcloudTalkApplication::class)
 class RingtoneSelectionController(args: Bundle) :
-    NewBaseController(
+    BaseController(
         R.layout.controller_generic_rv,
         args
     ),

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

@@ -41,7 +41,7 @@ import com.nextcloud.talk.R
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
-import com.nextcloud.talk.controllers.base.NewBaseController
+import com.nextcloud.talk.controllers.base.BaseController
 import com.nextcloud.talk.controllers.util.viewBinding
 import com.nextcloud.talk.databinding.ControllerServerSelectionBinding
 import com.nextcloud.talk.models.json.generic.Status
@@ -61,7 +61,7 @@ import javax.inject.Inject
 
 @AutoInjector(NextcloudTalkApplication::class)
 class ServerSelectionController :
-    NewBaseController(R.layout.controller_server_selection) {
+    BaseController(R.layout.controller_server_selection) {
 
     private val binding: ControllerServerSelectionBinding by viewBinding(ControllerServerSelectionBinding::bind)
 

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

@@ -68,7 +68,7 @@ import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.setAppTheme
 import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
-import com.nextcloud.talk.controllers.base.NewBaseController
+import com.nextcloud.talk.controllers.base.BaseController
 import com.nextcloud.talk.controllers.util.viewBinding
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.databinding.ControllerSettingsBinding
@@ -105,7 +105,7 @@ import java.util.Locale
 import javax.inject.Inject
 
 @AutoInjector(NextcloudTalkApplication::class)
-class SettingsController : NewBaseController(R.layout.controller_settings) {
+class SettingsController : BaseController(R.layout.controller_settings) {
     private val binding: ControllerSettingsBinding by viewBinding(ControllerSettingsBinding::bind)
 
     @Inject

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

@@ -37,7 +37,7 @@ import com.nextcloud.talk.R
 import com.nextcloud.talk.adapters.items.AdvancedUserItem
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
-import com.nextcloud.talk.controllers.base.NewBaseController
+import com.nextcloud.talk.controllers.base.BaseController
 import com.nextcloud.talk.controllers.util.viewBinding
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.databinding.ControllerGenericRvBinding
@@ -59,7 +59,7 @@ import javax.inject.Inject
 
 @AutoInjector(NextcloudTalkApplication::class)
 class SwitchAccountController(args: Bundle? = null) :
-    NewBaseController(
+    BaseController(
         R.layout.controller_generic_rv,
         args
     ) {

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

@@ -51,7 +51,7 @@ import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
 import com.nextcloud.talk.R
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
-import com.nextcloud.talk.controllers.base.NewBaseController
+import com.nextcloud.talk.controllers.base.BaseController
 import com.nextcloud.talk.controllers.util.viewBinding
 import com.nextcloud.talk.databinding.ControllerWebViewLoginBinding
 import com.nextcloud.talk.events.CertificateEvent
@@ -78,7 +78,7 @@ import java.util.Locale
 import javax.inject.Inject
 
 @AutoInjector(NextcloudTalkApplication::class)
-class WebViewLoginController(args: Bundle? = null) : NewBaseController(
+class WebViewLoginController(args: Bundle? = null) : BaseController(
     R.layout.controller_web_view_login,
     args
 ) {

+ 0 - 271
app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.java

@@ -1,271 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Andy Scherzinger
- * @author BlueLine Labs, Inc.
- * @author Mario Danic
- * Copyright (C) 2021 Andy Scherzinger (info@andy-scherzinger.de)
- * Copyright (C) 2020 Mario Danic (mario@lovelyhq.com)
- * Copyright (C) 2016 BlueLine Labs, Inc.
- * <p>
- * 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
- * <p>
- * http://www.apache.org/licenses/LICENSE-2.0
- * <p>
- * 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.controllers.base;
-
-import android.animation.AnimatorInflater;
-import android.content.Context;
-import android.os.Build;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-import androidx.appcompat.app.ActionBar;
-import androidx.core.content.res.ResourcesCompat;
-
-import com.bluelinelabs.conductor.Controller;
-import com.google.android.material.appbar.AppBarLayout;
-import com.nextcloud.talk.R;
-import com.nextcloud.talk.activities.MainActivity;
-import com.nextcloud.talk.application.NextcloudTalkApplication;
-import com.nextcloud.talk.controllers.AccountVerificationController;
-import com.nextcloud.talk.controllers.ServerSelectionController;
-import com.nextcloud.talk.controllers.SwitchAccountController;
-import com.nextcloud.talk.controllers.WebViewLoginController;
-import com.nextcloud.talk.controllers.base.providers.ActionBarProvider;
-import com.nextcloud.talk.ui.theme.ViewThemeUtils;
-import com.nextcloud.talk.utils.DisplayUtils;
-import com.nextcloud.talk.utils.preferences.AppPreferences;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
-import autodagger.AutoInjector;
-
-@AutoInjector(NextcloudTalkApplication.class)
-public abstract class BaseController extends ButterKnifeController {
-    public enum AppBarLayoutType {
-        TOOLBAR,
-        SEARCH_BAR,
-        EMPTY
-    }
-
-    private static final String TAG = "BaseController";
-    @Inject
-    AppPreferences appPreferences;
-
-    @Inject
-    Context context;
-
-    @Inject
-    ViewThemeUtils viewThemeUtils;
-
-    protected BaseController() {
-        cleanTempCertPreference();
-    }
-
-    protected BaseController(Bundle args) {
-        super(args);
-        cleanTempCertPreference();
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
-        if (item.getItemId() == android.R.id.home) {
-            getRouter().popCurrentController();
-            return true;
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    private void cleanTempCertPreference() {
-        NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
-
-        List<String> temporaryClassNames = new ArrayList<>();
-        temporaryClassNames.add(ServerSelectionController.class.getName());
-        temporaryClassNames.add(AccountVerificationController.class.getName());
-        temporaryClassNames.add(WebViewLoginController.class.getName());
-        temporaryClassNames.add(SwitchAccountController.class.getName());
-
-        if (!temporaryClassNames.contains(getClass().getName())) {
-            appPreferences.removeTemporaryClientCertAlias();
-        }
-
-    }
-
-    @Override
-    protected void onViewBound(@NonNull View view) {
-        super.onViewBound(view);
-        MainActivity activity = null;
-
-        if (getActivity() != null && getActivity() instanceof MainActivity) {
-            activity = (MainActivity) getActivity();
-            viewThemeUtils.themeCardView(activity.binding.searchToolbar);
-            viewThemeUtils.themeToolbar(activity.binding.toolbar);
-            viewThemeUtils.themeSearchBarText(activity.binding.searchText);
-        }
-
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences.getIsKeyboardIncognito()) {
-            disableKeyboardPersonalisedLearning((ViewGroup) view);
-
-            if (activity != null) {
-                disableKeyboardPersonalisedLearning(activity.binding.appBar);
-            }
-        }
-    }
-
-    // Note: This is just a quick demo of how an ActionBar *can* be accessed, not necessarily how it *should*
-    // be accessed. In a production app, this would use Dagger instead.
-    protected ActionBar getActionBar() {
-        ActionBarProvider actionBarProvider = null;
-        try {
-            actionBarProvider = ((ActionBarProvider) getActivity());
-        } catch (Exception exception) {
-            Log.d(TAG, "Failed to fetch the action bar provider");
-        }
-        return actionBarProvider != null ? actionBarProvider.getSupportActionBar() : null;
-    }
-
-    @Override
-    protected void onAttach(@NonNull View view) {
-        showSearchOrToolbar();
-
-        setTitle();
-        if (getActionBar() != null) {
-            getActionBar().setDisplayHomeAsUpEnabled(getParentController() != null || getRouter().getBackstackSize() > 1);
-        }
-
-        super.onAttach(view);
-    }
-
-    protected void showSearchOrToolbar() {
-        if (getActivity() != null && getActivity() instanceof MainActivity) {
-            boolean showSearchBar = getAppBarLayoutType() == AppBarLayoutType.SEARCH_BAR;
-            MainActivity activity = (MainActivity) getActivity();
-
-            if (getAppBarLayoutType() == AppBarLayoutType.EMPTY) {
-                activity.binding.toolbar.setVisibility(View.GONE);
-                activity.binding.searchToolbar.setVisibility(View.GONE);
-            } else {
-                AppBarLayout.LayoutParams layoutParams =
-                        (AppBarLayout.LayoutParams) activity.binding.searchToolbar.getLayoutParams();
-
-                if (showSearchBar) {
-                    activity.binding.searchToolbar.setVisibility(View.VISIBLE);
-                    activity.binding.searchText.setHint(getSearchHint());
-                    activity.binding.toolbar.setVisibility(View.GONE);
-                    //layoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);
-                    layoutParams.setScrollFlags(0);
-                    activity.binding.appBar.setStateListAnimator(AnimatorInflater.loadStateListAnimator(
-                            activity.binding.appBar.getContext(),
-                            R.animator.appbar_elevation_off)
-                    );
-                } else {
-                    hideSearchBar();
-                }
-
-                activity.binding.searchToolbar.setLayoutParams(layoutParams);
-
-                if ((getResources() != null)) {
-                    if (showSearchBar) {
-                        viewThemeUtils.resetStatusBar(activity, activity.binding.searchToolbar);
-                    } else {
-                        viewThemeUtils.themeStatusBar(activity, activity.binding.searchToolbar);
-                    }
-                }
-            }
-
-            if ((getResources() != null)) {
-                DisplayUtils.applyColorToNavigationBar(
-                        activity.getWindow(),
-                        ResourcesCompat.getColor(getResources(), R.color.bg_default, null)
-                );
-            }
-        }
-    }
-
-    protected void hideSearchBar() {
-        MainActivity activity = (MainActivity) getActivity();
-        AppBarLayout.LayoutParams layoutParams =
-                (AppBarLayout.LayoutParams) activity.binding.searchToolbar.getLayoutParams();
-
-        activity.binding.searchToolbar.setVisibility(View.GONE);
-        activity.binding.toolbar.setVisibility(View.VISIBLE);
-        layoutParams.setScrollFlags(0);
-        activity.binding.appBar.setStateListAnimator(AnimatorInflater.loadStateListAnimator(
-                activity.binding.appBar.getContext(),
-                R.animator.appbar_elevation_on)
-                                                    );
-    }
-
-    @Override
-    protected void onDetach(@NonNull View view) {
-        super.onDetach(view);
-        InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
-        if (imm != null) {
-            imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
-        }
-    }
-
-    protected void setTitle() {
-        Controller parentController = getParentController();
-        while (parentController != null) {
-            if (parentController instanceof BaseController && ((BaseController) parentController).getTitle() != null) {
-                return;
-            }
-            parentController = parentController.getParentController();
-        }
-
-        String title = getTitle();
-        ActionBar actionBar = getActionBar();
-        if (title != null && actionBar != null) {
-            actionBar.setTitle(title);
-        }
-    }
-
-    protected String getTitle() {
-        return null;
-    }
-
-    @RequiresApi(api = Build.VERSION_CODES.O)
-    private void disableKeyboardPersonalisedLearning(final ViewGroup viewGroup) {
-        View view;
-        EditText editText;
-
-        for (int i = 0; i < viewGroup.getChildCount(); i++) {
-            view = viewGroup.getChildAt(i);
-            if (view instanceof EditText) {
-                editText = (EditText) view;
-                editText.setImeOptions(editText.getImeOptions() | EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING);
-            } else if (view instanceof ViewGroup) {
-                disableKeyboardPersonalisedLearning((ViewGroup) view);
-            }
-        }
-    }
-
-    public AppBarLayoutType getAppBarLayoutType() {
-        return AppBarLayoutType.TOOLBAR;
-    }
-
-    public String getSearchHint() {
-        return context.getString(R.string.appbar_search_in, context.getString(R.string.nc_app_product_name));
-    }
-}

+ 17 - 12
app/src/main/java/com/nextcloud/talk/controllers/base/NewBaseController.kt → app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.kt

@@ -63,7 +63,7 @@ import javax.inject.Inject
 import kotlin.jvm.internal.Intrinsics
 
 @AutoInjector(NextcloudTalkApplication::class)
-abstract class NewBaseController(@LayoutRes var layoutRes: Int, args: Bundle? = null) : Controller(args) {
+abstract class BaseController(@LayoutRes var layoutRes: Int, args: Bundle? = null) : Controller(args) {
     enum class AppBarLayoutType {
         TOOLBAR, SEARCH_BAR, EMPTY
     }
@@ -145,7 +145,7 @@ abstract class NewBaseController(@LayoutRes var layoutRes: Int, args: Bundle? =
         super.onAttach(view)
     }
 
-    protected fun showSearchOrToolbar() {
+    open fun showSearchOrToolbar() {
         if (isValidActivity(activity)) {
             val showSearchBar = appBarLayoutType == AppBarLayoutType.SEARCH_BAR
             val activity = activity as MainActivity
@@ -202,6 +202,18 @@ abstract class NewBaseController(@LayoutRes var layoutRes: Int, args: Bundle? =
         binding.searchToolbar.visibility = View.GONE
     }
 
+    fun hideSearchBar() {
+        val activity = activity as MainActivity?
+        val layoutParams = activity!!.binding.searchToolbar.layoutParams as AppBarLayout.LayoutParams
+        activity.binding.searchToolbar.visibility = View.GONE
+        activity.binding.toolbar.visibility = View.VISIBLE
+        layoutParams.scrollFlags = 0
+        activity.binding.appBar.stateListAnimator = AnimatorInflater.loadStateListAnimator(
+            activity.binding.appBar.context,
+            R.animator.appbar_elevation_on
+        )
+    }
+
     private fun colorizeStatusBar(showSearchBar: Boolean, activity: Activity?, resources: Resources?) {
         if (activity != null && resources != null) {
             if (showSearchBar) {
@@ -223,7 +235,7 @@ abstract class NewBaseController(@LayoutRes var layoutRes: Int, args: Bundle? =
 
     override fun onDetach(view: View) {
         super.onDetach(view)
-        val imm = context!!.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+        val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
         imm.hideSoftInputFromWindow(view.windowToken, 0)
     }
 
@@ -239,17 +251,10 @@ abstract class NewBaseController(@LayoutRes var layoutRes: Int, args: Bundle? =
     private fun calculateValidParentController() {
         var parentController = parentController
         while (parentController != null) {
-            if (isValidController(parentController)) {
-                return
-            }
             parentController = parentController.parentController
         }
     }
 
-    private fun isValidController(parentController: Controller): Boolean {
-        return parentController is BaseController && parentController.title != null
-    }
-
     private fun isTitleSetable(): Boolean {
         return title != null && actionBar != null
     }
@@ -280,7 +285,7 @@ abstract class NewBaseController(@LayoutRes var layoutRes: Int, args: Bundle? =
         temporaryClassNames.add(WebViewLoginController::class.java.name)
         temporaryClassNames.add(SwitchAccountController::class.java.name)
         if (!temporaryClassNames.contains(javaClass.name)) {
-            appPreferences!!.removeTemporaryClientCertAlias()
+            appPreferences.removeTemporaryClientCertAlias()
         }
     }
 
@@ -323,7 +328,7 @@ abstract class NewBaseController(@LayoutRes var layoutRes: Int, args: Bundle? =
     open val appBarLayoutType: AppBarLayoutType
         get() = AppBarLayoutType.TOOLBAR
     val searchHint: String
-        get() = context!!.getString(R.string.appbar_search_in, context!!.getString(R.string.nc_app_product_name))
+        get() = context.getString(R.string.appbar_search_in, context.getString(R.string.nc_app_product_name))
 
     companion object {
         private val TAG = BaseController::class.java.simpleName

+ 0 - 58
app/src/main/java/com/nextcloud/talk/controllers/base/ButterKnifeController.kt

@@ -1,58 +0,0 @@
-/**
- * Nextcloud Talk application
- *
- * @author BlueLine Labs, Inc.
- * Copyright (C) 2016 BlueLine Labs, Inc.
- *
- *
- * 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.controllers.base
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import butterknife.ButterKnife
-import butterknife.Unbinder
-import com.bluelinelabs.conductor.Controller
-
-abstract class ButterKnifeController : Controller {
-
-    private var unbinder: Unbinder? = null
-
-    constructor()
-
-    constructor(args: Bundle) : super(args)
-
-    protected abstract fun inflateView(inflater: LayoutInflater, container: ViewGroup): View
-
-    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View {
-        val view = inflateView(inflater, container)
-        unbinder = ButterKnife.bind(this, view)
-        onViewBound(view)
-        return view
-    }
-
-    protected open fun onViewBound(view: View) {
-        // unused atm
-    }
-
-    override fun onDestroyView(view: View) {
-        super.onDestroyView(view)
-        unbinder!!.unbind()
-        unbinder = null
-    }
-}

+ 2 - 2
app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/EntryMenuController.kt

@@ -42,7 +42,7 @@ import com.google.android.material.textfield.TextInputLayout
 import com.nextcloud.talk.R
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
-import com.nextcloud.talk.controllers.base.NewBaseController
+import com.nextcloud.talk.controllers.base.BaseController
 import com.nextcloud.talk.controllers.util.viewBinding
 import com.nextcloud.talk.databinding.ControllerEntryMenuBinding
 import com.nextcloud.talk.models.json.conversations.Conversation
@@ -60,7 +60,7 @@ import javax.inject.Inject
 
 @AutoInjector(NextcloudTalkApplication::class)
 class EntryMenuController(args: Bundle) :
-    NewBaseController(
+    BaseController(
         R.layout.controller_entry_menu,
         args
     ) {

+ 2 - 2
app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.kt

@@ -34,7 +34,7 @@ import com.nextcloud.talk.R
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
-import com.nextcloud.talk.controllers.base.NewBaseController
+import com.nextcloud.talk.controllers.base.BaseController
 import com.nextcloud.talk.controllers.util.viewBinding
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.databinding.ControllerOperationsMenuBinding
@@ -79,7 +79,7 @@ import java.util.Collections
 import javax.inject.Inject
 
 @AutoInjector(NextcloudTalkApplication::class)
-class OperationsMenuController(args: Bundle) : NewBaseController(
+class OperationsMenuController(args: Bundle) : BaseController(
     R.layout.controller_operations_menu,
     args
 ) {

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

@@ -310,8 +310,4 @@ class ConversationsListBottomDialog(
         val behavior = BottomSheetBehavior.from(bottomSheet as View)
         behavior.state = BottomSheetBehavior.STATE_COLLAPSED
     }
-
-    companion object {
-        private const val TAG = "ConversationOperationDialog"
-    }
 }

+ 1 - 1
detekt.yml

@@ -1,5 +1,5 @@
 build:
-  maxIssues: 77
+  maxIssues: 94
   weights:
     # complexity: 2
     # LongParameterList: 1