瀏覽代碼

Merge pull request #1922 from nextcloud/bugfix/noid/kotlinConversion5

Migrate OperationsMenuController to kotlin
Andy Scherzinger 3 年之前
父節點
當前提交
4c199b1369

+ 0 - 808
app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java

@@ -1,808 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Mario Danic
- * Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.nextcloud.talk.controllers.bottomsheet;
-
-import static com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_JOIN_ROOM;
-import static com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_REMOVE_FAVORITE;
-
-import android.annotation.SuppressLint;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-
-import com.bluelinelabs.conductor.RouterTransaction;
-import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
-import com.bluelinelabs.logansquare.LoganSquare;
-import com.nextcloud.talk.R;
-import com.nextcloud.talk.api.NcApi;
-import com.nextcloud.talk.application.NextcloudTalkApplication;
-import com.nextcloud.talk.controllers.base.BaseController;
-import com.nextcloud.talk.events.ConversationsListFetchDataEvent;
-import com.nextcloud.talk.events.OpenConversationEvent;
-import com.nextcloud.talk.models.RetrofitBucket;
-import com.nextcloud.talk.models.database.CapabilitiesUtil;
-import com.nextcloud.talk.models.database.UserEntity;
-import com.nextcloud.talk.models.json.capabilities.Capabilities;
-import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall;
-import com.nextcloud.talk.models.json.conversations.Conversation;
-import com.nextcloud.talk.models.json.conversations.RoomOverall;
-import com.nextcloud.talk.models.json.generic.GenericOverall;
-import com.nextcloud.talk.models.json.participants.AddParticipantOverall;
-import com.nextcloud.talk.utils.ApiUtils;
-import com.nextcloud.talk.utils.DisplayUtils;
-import com.nextcloud.talk.utils.NoSupportedApiException;
-import com.nextcloud.talk.utils.bundle.BundleKeys;
-import com.nextcloud.talk.utils.database.user.UserUtils;
-import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder;
-
-import org.greenrobot.eventbus.EventBus;
-import org.parceler.Parcels;
-
-import java.io.IOException;
-import java.util.ArrayList;
-
-import javax.inject.Inject;
-
-import androidx.annotation.NonNull;
-import autodagger.AutoInjector;
-import butterknife.BindView;
-import io.reactivex.Observer;
-import io.reactivex.android.schedulers.AndroidSchedulers;
-import io.reactivex.disposables.Disposable;
-import io.reactivex.schedulers.Schedulers;
-import retrofit2.HttpException;
-import retrofit2.Response;
-
-@AutoInjector(NextcloudTalkApplication.class)
-public class OperationsMenuController extends BaseController {
-
-    private static final String TAG = "OperationsMenuController";
-
-    @BindView(R.id.progress_bar)
-    ProgressBar progressBar;
-
-    @BindView(R.id.result_image_view)
-    ImageView resultImageView;
-
-    @BindView(R.id.result_text_view)
-    TextView resultsTextView;
-
-    @BindView(R.id.ok_button)
-    Button okButton;
-
-    @BindView(R.id.web_button)
-    Button webButton;
-
-    @Inject
-    NcApi ncApi;
-
-    @Inject
-    UserUtils userUtils;
-
-    @Inject
-    EventBus eventBus;
-
-    private ConversationOperationEnum operation;
-    private Conversation conversation;
-
-    private UserEntity currentUser;
-    private String callPassword;
-    private String callUrl;
-
-    private String baseUrl;
-    private String conversationToken;
-
-    private Disposable disposable;
-
-    private Conversation.ConversationType conversationType;
-    private ArrayList<String> invitedUsers = new ArrayList<>();
-    private ArrayList<String> invitedGroups = new ArrayList<>();
-
-    private Capabilities serverCapabilities;
-    private String credentials;
-    private String conversationName;
-
-    public OperationsMenuController(Bundle args) {
-        super(args);
-        this.operation = (ConversationOperationEnum) args.getSerializable(BundleKeys.INSTANCE.getKEY_OPERATION_CODE());
-        if (args.containsKey(BundleKeys.INSTANCE.getKEY_ROOM())) {
-            this.conversation = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_ROOM()));
-        }
-
-        this.callPassword = args.getString(BundleKeys.INSTANCE.getKEY_CONVERSATION_PASSWORD(), "");
-        this.callUrl = args.getString(BundleKeys.INSTANCE.getKEY_CALL_URL(), "");
-
-        if (args.containsKey(BundleKeys.INSTANCE.getKEY_INVITED_PARTICIPANTS())) {
-            this.invitedUsers = args.getStringArrayList(BundleKeys.INSTANCE.getKEY_INVITED_PARTICIPANTS());
-        }
-
-        if (args.containsKey(BundleKeys.INSTANCE.getKEY_INVITED_GROUP())) {
-            this.invitedGroups = args.getStringArrayList(BundleKeys.INSTANCE.getKEY_INVITED_GROUP());
-        }
-
-        if (args.containsKey(BundleKeys.INSTANCE.getKEY_CONVERSATION_TYPE())) {
-            this.conversationType = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_CONVERSATION_TYPE()));
-        }
-
-        if (args.containsKey(BundleKeys.INSTANCE.getKEY_SERVER_CAPABILITIES())) {
-            this.serverCapabilities = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_SERVER_CAPABILITIES()));
-        }
-
-        this.conversationName = args.getString(BundleKeys.INSTANCE.getKEY_CONVERSATION_NAME(), "");
-
-    }
-
-    @NonNull
-    @Override
-    protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
-        return inflater.inflate(R.layout.controller_operations_menu, container, false);
-    }
-
-    @Override
-    protected void onViewBound(@NonNull View view) {
-        super.onViewBound(view);
-        NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
-
-        currentUser = userUtils.getCurrentUser();
-        if (!TextUtils.isEmpty(callUrl) && callUrl.contains("/call")) {
-            conversationToken = callUrl.substring(callUrl.lastIndexOf("/") + 1);
-            if (callUrl.contains("/index.php")) {
-                baseUrl = callUrl.substring(0, callUrl.indexOf("/index.php"));
-            } else {
-                baseUrl = callUrl.substring(0, callUrl.indexOf("/call"));
-            }
-        }
-
-        if (!TextUtils.isEmpty(baseUrl) && !baseUrl.equals(currentUser.getBaseUrl())) {
-            if (serverCapabilities != null) {
-                try {
-                    useBundledCapabilitiesForGuest();
-                } catch (IOException e) {
-                    // Fall back to fetching capabilities again
-                    fetchCapabilitiesForGuest();
-                }
-            } else {
-                fetchCapabilitiesForGuest();
-            }
-        } else {
-            processOperation();
-        }
-    }
-
-
-    @SuppressLint("LongLogTag")
-    private void useBundledCapabilitiesForGuest() throws IOException {
-        currentUser = new UserEntity();
-        currentUser.setBaseUrl(baseUrl);
-        currentUser.setUserId("?");
-        try {
-            currentUser.setCapabilities(LoganSquare.serialize(serverCapabilities));
-        } catch (IOException e) {
-            Log.e("OperationsMenu", "Failed to serialize capabilities");
-            throw e;
-        }
-
-        try {
-            checkCapabilities(currentUser);
-            processOperation();
-        } catch (NoSupportedApiException e) {
-            showResultImage(false, false);
-            Log.d(TAG, "No supported server version found", e);
-        }
-    }
-
-    @SuppressLint("LongLogTag")
-    private void fetchCapabilitiesForGuest() {
-        ncApi.getCapabilities(null, ApiUtils.getUrlForCapabilities(baseUrl))
-                .subscribeOn(Schedulers.io())
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(new Observer<CapabilitiesOverall>() {
-                    @Override
-                    public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
-                    }
-
-                    @SuppressLint("LongLogTag")
-                    @Override
-                    public void onNext(@io.reactivex.annotations.NonNull CapabilitiesOverall capabilitiesOverall) {
-                        currentUser = new UserEntity();
-                        currentUser.setBaseUrl(baseUrl);
-                        currentUser.setUserId("?");
-                        try {
-                            currentUser.setCapabilities(
-                                    LoganSquare
-                                            .serialize(
-                                                    capabilitiesOverall
-                                                            .getOcs()
-                                                            .getData()
-                                                            .getCapabilities()));
-                        } catch (IOException e) {
-                            Log.e("OperationsMenu", "Failed to serialize capabilities");
-                        }
-
-                        try {
-                            checkCapabilities(currentUser);
-                            processOperation();
-                        } catch (NoSupportedApiException e) {
-                            showResultImage(false, false);
-                            Log.d(TAG, "No supported server version found", e);
-                        }
-                    }
-
-                    @SuppressLint("LongLogTag")
-                    @Override
-                    public void onError(@io.reactivex.annotations.NonNull Throwable e) {
-                        showResultImage(false, false);
-                        Log.e(TAG, "Error fetching capabilities for guest", e);
-                    }
-
-                    @Override
-                    public void onComplete() {
-
-                    }
-                });
-    }
-
-    @SuppressLint("LongLogTag")
-    private void processOperation() {
-        RoomOperationsObserver roomOperationsObserver = new RoomOperationsObserver();
-        GenericOperationsObserver genericOperationsObserver = new GenericOperationsObserver();
-
-        if (currentUser == null) {
-            showResultImage(false, true);
-            Log.e(TAG, "Ended up in processOperation without a valid currentUser");
-            return;
-        }
-
-        credentials = ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken());
-        int apiVersion = ApiUtils.getConversationApiVersion(currentUser, new int[] {ApiUtils.APIv4, ApiUtils.APIv1});
-        int chatApiVersion = ApiUtils.getChatApiVersion(currentUser, new int[] {ApiUtils.APIv1});
-
-        switch (operation) {
-            case OPS_CODE_RENAME_ROOM:
-                ncApi.renameRoom(credentials, ApiUtils.getUrlForRoom(apiVersion, currentUser.getBaseUrl(),
-                                                                     conversation.getToken()),
-                        conversation.getName())
-                        .subscribeOn(Schedulers.io())
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .retry(1)
-                        .subscribe(genericOperationsObserver);
-                break;
-            case OPS_CODE_MAKE_PUBLIC:
-                ncApi.makeRoomPublic(credentials, ApiUtils.getUrlForRoomPublic(apiVersion, currentUser.getBaseUrl(),
-                                                                               conversation.getToken()))
-                        .subscribeOn(Schedulers.io())
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .retry(1)
-                        .subscribe(genericOperationsObserver);
-                break;
-            case OPS_CODE_CHANGE_PASSWORD:
-            case OPS_CODE_CLEAR_PASSWORD:
-            case OPS_CODE_SET_PASSWORD:
-                String pass = "";
-                if (conversation.getPassword() != null) {
-                    pass = conversation.getPassword();
-                }
-                ncApi.setPassword(credentials, ApiUtils.getUrlForRoomPassword(apiVersion, currentUser.getBaseUrl(),
-                                                                              conversation.getToken()), pass)
-                        .subscribeOn(Schedulers.io())
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .retry(1)
-                        .subscribe(genericOperationsObserver);
-                break;
-            case OPS_CODE_MAKE_PRIVATE:
-                ncApi.makeRoomPrivate(credentials, ApiUtils.getUrlForRoomPublic(apiVersion,
-                                                                                currentUser.getBaseUrl(),
-                                                                                conversation.getToken()))
-                        .subscribeOn(Schedulers.io())
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .retry(1)
-                        .subscribe(genericOperationsObserver);
-                break;
-            case OPS_CODE_GET_AND_JOIN_ROOM:
-                ncApi.getRoom(credentials, ApiUtils.getUrlForRoom(apiVersion, baseUrl, conversationToken))
-                        .subscribeOn(Schedulers.io())
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .retry(1)
-                        .subscribe(new Observer<RoomOverall>() {
-                            @Override
-                            public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
-                                disposable = d;
-                            }
-
-                            @Override
-                            public void onNext(@io.reactivex.annotations.NonNull RoomOverall roomOverall) {
-                                conversation = roomOverall.getOcs().getData();
-                                if (conversation.isHasPassword() && conversation.isGuest()) {
-                                    eventBus.post(new ConversationsListFetchDataEvent());
-                                    Bundle bundle = new Bundle();
-                                    bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ROOM(), Parcels.wrap(conversation));
-                                    bundle.putString(BundleKeys.INSTANCE.getKEY_CALL_URL(), callUrl);
-                                    try {
-                                        bundle.putParcelable(BundleKeys.INSTANCE.getKEY_SERVER_CAPABILITIES(),
-                                                             Parcels.wrap(LoganSquare.parse(currentUser.getCapabilities(),
-                                                                                            Capabilities.class)));
-                                    } catch (IOException e) {
-                                        Log.e(TAG, "Failed to parse capabilities for guest");
-                                        showResultImage(false, false);
-                                    }
-                                    bundle.putSerializable(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), OPS_CODE_JOIN_ROOM);
-                                    getRouter().pushController(RouterTransaction.with(new EntryMenuController(bundle))
-                                                                       .pushChangeHandler(new HorizontalChangeHandler())
-                                                                       .popChangeHandler(new HorizontalChangeHandler()));
-                                } else if (conversation.isGuest()) {
-                                    ncApi.joinRoom(credentials, ApiUtils.getUrlForParticipantsActive(apiVersion,
-                                                                                                     baseUrl,
-                                                                                                     conversationToken), null)
-                                            .subscribeOn(Schedulers.io())
-                                            .observeOn(AndroidSchedulers.mainThread())
-                                            .subscribe(new Observer<RoomOverall>() {
-                                                @Override
-                                                public void onSubscribe(
-                                                        @io.reactivex.annotations.NonNull Disposable d
-                                                                       ) {
-                                                }
-
-                                                @Override
-                                                public void onNext(
-                                                        @io.reactivex.annotations.NonNull RoomOverall roomOverall
-                                                                  ) {
-                                                    conversation = roomOverall.getOcs().getData();
-                                                    initiateConversation();
-                                                }
-
-                                                @Override
-                                                public void onError(@io.reactivex.annotations.NonNull Throwable e) {
-                                                    showResultImage(false, false);
-                                                    dispose();
-                                                }
-
-                                                @Override
-                                                public void onComplete() {
-                                                }
-                                            });
-                                } else {
-                                    initiateConversation();
-                                }
-                            }
-
-                            @Override
-                            public void onError(@io.reactivex.annotations.NonNull Throwable e) {
-                                showResultImage(false, false);
-                                dispose();
-                            }
-
-                            @Override
-                            public void onComplete() {
-                                dispose();
-                            }
-                        });
-                break;
-            case OPS_CODE_INVITE_USERS:
-                RetrofitBucket retrofitBucket;
-                String invite = null;
-
-                if (invitedGroups.size() > 0) {
-                    invite = invitedGroups.get(0);
-                }
-
-                if (conversationType.equals(Conversation.ConversationType.ROOM_PUBLIC_CALL)) {
-                    retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(apiVersion, currentUser.getBaseUrl(),
-                                                                             "3", null, invite, conversationName);
-                } else {
-                    retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(apiVersion, currentUser.getBaseUrl(),
-                                                                             "2", null, invite, conversationName);
-                }
-
-                ncApi.createRoom(credentials, retrofitBucket.getUrl(), retrofitBucket.getQueryMap())
-                        .subscribeOn(Schedulers.io())
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .retry(1)
-                        .subscribe(new Observer<RoomOverall>() {
-                            @Override
-                            public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
-
-                            }
-
-                            @Override
-                            public void onNext(@io.reactivex.annotations.NonNull RoomOverall roomOverall) {
-                                conversation = roomOverall.getOcs().getData();
-
-                                ncApi.getRoom(credentials,
-                                        ApiUtils.getUrlForRoom(apiVersion, currentUser.getBaseUrl(),
-                                                         conversation.getToken()))
-                                        .subscribeOn(Schedulers.io())
-                                        .observeOn(AndroidSchedulers.mainThread())
-                                        .subscribe(new Observer<RoomOverall>() {
-                                            @Override
-                                            public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
-
-                                            }
-
-                                            @Override
-                                            public void onNext(
-                                                    @io.reactivex.annotations.NonNull RoomOverall roomOverall
-                                                              ) {
-                                                conversation = roomOverall.getOcs().getData();
-                                                inviteUsersToAConversation();
-                                            }
-
-                                            @Override
-                                            public void onError(@io.reactivex.annotations.NonNull Throwable e) {
-                                                showResultImage(false, false);
-                                                dispose();
-                                            }
-
-                                            @Override
-                                            public void onComplete() {
-
-                                            }
-                                        });
-
-                            }
-
-                            @Override
-                            public void onError(@io.reactivex.annotations.NonNull Throwable e) {
-                                showResultImage(false, false);
-                                dispose();
-                            }
-
-                            @Override
-                            public void onComplete() {
-                                dispose();
-                            }
-                        });
-
-                break;
-            case OPS_CODE_MARK_AS_READ:
-                ncApi.setChatReadMarker(credentials,
-                                        ApiUtils.getUrlForSetChatReadMarker(chatApiVersion,
-                                                                            currentUser.getBaseUrl(),
-                                                                            conversation.getToken()),
-                                        conversation.lastMessage.jsonMessageId)
-                    .subscribeOn(Schedulers.io())
-                    .observeOn(AndroidSchedulers.mainThread())
-                    .retry(1)
-                    .subscribe(genericOperationsObserver);
-                break;
-            case OPS_CODE_REMOVE_FAVORITE:
-            case OPS_CODE_ADD_FAVORITE:
-                if (operation == OPS_CODE_REMOVE_FAVORITE) {
-                    ncApi.removeConversationFromFavorites(credentials,
-                                                          ApiUtils.getUrlForRoomFavorite(apiVersion,
-                                                                                         currentUser.getBaseUrl(),
-                                                                                         conversation.getToken()))
-                            .subscribeOn(Schedulers.io())
-                            .observeOn(AndroidSchedulers.mainThread())
-                            .retry(1)
-                            .subscribe(genericOperationsObserver);
-                } else {
-                    ncApi.addConversationToFavorites(credentials,
-                                                     ApiUtils.getUrlForRoomFavorite(apiVersion,
-                                                                                    currentUser.getBaseUrl(),
-                                                                                    conversation.getToken()))
-                            .subscribeOn(Schedulers.io())
-                            .observeOn(AndroidSchedulers.mainThread())
-                            .retry(1)
-                            .subscribe(genericOperationsObserver);
-                }
-                break;
-            case OPS_CODE_JOIN_ROOM:
-                ncApi.joinRoom(credentials, ApiUtils.getUrlForParticipantsActive(apiVersion,
-                                                                                 baseUrl,
-                                                                                 conversationToken),
-                        callPassword)
-                        .subscribeOn(Schedulers.io())
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .retry(1)
-                        .subscribe(roomOperationsObserver);
-                break;
-            default:
-                break;
-        }
-    }
-
-    private void showResultImage(boolean everythingOK, boolean isGuestSupportError) {
-        progressBar.setVisibility(View.GONE);
-
-        if (getResources() != null) {
-            if (everythingOK) {
-                resultImageView.setImageDrawable(DisplayUtils.getTintedDrawable(getResources(),
-                                                                                R.drawable.ic_check_circle_black_24dp,
-                                                                                R.color.nc_darkGreen));
-            } else {
-                resultImageView.setImageDrawable(DisplayUtils.getTintedDrawable(getResources(),
-                                                                                R.drawable.ic_cancel_black_24dp,
-                                                                                R.color.nc_darkRed));
-            }
-        }
-
-        resultImageView.setVisibility(View.VISIBLE);
-
-        if (everythingOK) {
-            resultsTextView.setText(R.string.nc_all_ok_operation);
-        } else {
-            resultsTextView.setTextColor(getResources().getColor(R.color.nc_darkRed));
-            if (!isGuestSupportError) {
-                resultsTextView.setText(R.string.nc_failed_to_perform_operation);
-            } else {
-                resultsTextView.setText(R.string.nc_failed_signaling_settings);
-                webButton.setOnClickListener(v -> {
-                    Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(callUrl));
-                    startActivity(browserIntent);
-                });
-                webButton.setVisibility(View.VISIBLE);
-            }
-        }
-
-        resultsTextView.setVisibility(View.VISIBLE);
-        if (everythingOK) {
-            eventBus.post(new ConversationsListFetchDataEvent());
-        } else {
-            resultImageView.setImageDrawable(DisplayUtils.getTintedDrawable(getResources(), R.drawable
-                    .ic_cancel_black_24dp, R.color.nc_darkRed));
-            okButton.setOnClickListener(v -> eventBus.post(new ConversationsListFetchDataEvent()));
-            okButton.setVisibility(View.VISIBLE);
-        }
-    }
-
-    private void dispose() {
-        if (disposable != null && !disposable.isDisposed()) {
-            disposable.dispose();
-        }
-
-        disposable = null;
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        dispose();
-    }
-
-    private void checkCapabilities(UserEntity currentUser) throws NoSupportedApiException {
-        ApiUtils.getConversationApiVersion(currentUser, new int[] {ApiUtils.APIv4, 1});
-        ApiUtils.getCallApiVersion(currentUser, new int[] {ApiUtils.APIv4, 1});
-        ApiUtils.getChatApiVersion(currentUser, new int[] {1});
-        ApiUtils.getSignalingApiVersion(currentUser, new int[] {ApiUtils.APIv3, 2, 1});
-    }
-
-    private void inviteUsersToAConversation() {
-        final ArrayList<String> localInvitedUsers = invitedUsers;
-        final ArrayList<String> localInvitedGroups = invitedGroups;
-        if (localInvitedGroups.size() > 0) {
-            localInvitedGroups.remove(0);
-        }
-
-        int apiVersion = ApiUtils.getConversationApiVersion(currentUser, new int[] {4, 1});
-
-        if (localInvitedUsers.size() > 0 || (localInvitedGroups.size() > 0 &&
-                CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "invite-groups-and-mails"))) {
-            addGroupsToConversation(localInvitedUsers, localInvitedGroups, apiVersion);
-            addUsersToConversation(localInvitedUsers, localInvitedGroups, apiVersion);
-        } else {
-            initiateConversation();
-        }
-    }
-
-    private void addUsersToConversation(
-            ArrayList<String> localInvitedUsers,
-            ArrayList<String> localInvitedGroups,
-            int apiVersion)
-    {
-        RetrofitBucket retrofitBucket;
-        for (int i = 0; i < localInvitedUsers.size(); i++) {
-            final String userId = invitedUsers.get(i);
-            retrofitBucket = ApiUtils.getRetrofitBucketForAddParticipant(apiVersion,
-                                                                         currentUser.getBaseUrl(),
-                                                                         conversation.getToken(),
-                                                                         userId);
-
-            ncApi.addParticipant(credentials, retrofitBucket.getUrl(), retrofitBucket.getQueryMap())
-                    .subscribeOn(Schedulers.io())
-                    .observeOn(AndroidSchedulers.mainThread())
-                    .retry(1)
-                    .subscribe(new Observer<AddParticipantOverall>() {
-                        @Override
-                        public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
-
-                        }
-
-                        @Override
-                        public void onNext(
-                                @io.reactivex.annotations.NonNull AddParticipantOverall addParticipantOverall
-                                          ) {
-                        }
-
-                        @Override
-                        public void onError(@io.reactivex.annotations.NonNull Throwable e) {
-                            dispose();
-                        }
-
-                        @Override
-                        public void onComplete() {
-                            synchronized (localInvitedUsers) {
-                                localInvitedUsers.remove(userId);
-                            }
-
-                            if (localInvitedGroups.size() == 0 && localInvitedUsers.size() == 0) {
-                                initiateConversation();
-                            }
-                            dispose();
-                        }
-                    });
-        }
-    }
-
-    private void addGroupsToConversation(
-            ArrayList<String> localInvitedUsers,
-            ArrayList<String> localInvitedGroups,
-            int apiVersion)
-    {
-        RetrofitBucket retrofitBucket;
-        if ((localInvitedGroups.size() > 0 &&
-                CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "invite-groups-and-mails"))) {
-            for (int i = 0; i < localInvitedGroups.size(); i++) {
-                final String groupId = localInvitedGroups.get(i);
-                retrofitBucket = ApiUtils.getRetrofitBucketForAddParticipantWithSource(
-                        apiVersion,
-                        currentUser.getBaseUrl(),
-                        conversation.getToken(),
-                        "groups",
-                        groupId
-                                                                                      );
-
-                ncApi.addParticipant(credentials, retrofitBucket.getUrl(), retrofitBucket.getQueryMap())
-                        .subscribeOn(Schedulers.io())
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .retry(1)
-                        .subscribe(new Observer<AddParticipantOverall>() {
-                            @Override
-                            public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
-
-                            }
-
-                            @Override
-                            public void onNext(
-                                    @io.reactivex.annotations.NonNull AddParticipantOverall addParticipantOverall
-                                              ) {
-                            }
-
-                            @Override
-                            public void onError(@io.reactivex.annotations.NonNull Throwable e) {
-                                dispose();
-                            }
-
-                            @Override
-                            public void onComplete() {
-                                synchronized (localInvitedGroups) {
-                                    localInvitedGroups.remove(groupId);
-                                }
-
-                                if (localInvitedGroups.size() == 0 && localInvitedUsers.size() == 0) {
-                                    initiateConversation();
-                                }
-                                dispose();
-                            }
-                        });
-
-            }
-        }
-    }
-
-    private void initiateConversation() {
-        eventBus.post(new ConversationsListFetchDataEvent());
-
-        Bundle bundle = new Bundle();
-        bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), conversation.getToken());
-        bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), conversation.getRoomId());
-        bundle.putString(BundleKeys.INSTANCE.getKEY_CONVERSATION_NAME(), conversation.getDisplayName());
-        bundle.putParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY(), currentUser);
-        bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ACTIVE_CONVERSATION(), Parcels.wrap(conversation));
-        bundle.putString(BundleKeys.INSTANCE.getKEY_CONVERSATION_PASSWORD(), callPassword);
-
-        eventBus.post(new OpenConversationEvent(conversation, bundle));
-    }
-
-    private void handleObserverError(@io.reactivex.annotations.NonNull Throwable e) {
-        if (operation != OPS_CODE_JOIN_ROOM || !(e instanceof HttpException)) {
-            showResultImage(false, false);
-        } else {
-            Response<?> response = ((HttpException) e).response();
-            if (response != null && response.code() == 403) {
-                ApplicationWideMessageHolder.getInstance().setMessageType(ApplicationWideMessageHolder.MessageType.CALL_PASSWORD_WRONG);
-                getRouter().popCurrentController();
-            } else {
-                showResultImage(false, false);
-            }
-        }
-        dispose();
-    }
-
-    private class GenericOperationsObserver implements Observer<GenericOverall> {
-
-        @Override
-        public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
-            disposable = d;
-        }
-
-        @Override
-        public void onNext(@io.reactivex.annotations.NonNull GenericOverall genericOverall) {
-            if (operation != OPS_CODE_JOIN_ROOM) {
-                showResultImage(true, false);
-            } else {
-                throw new IllegalArgumentException("Unsupported operation code observed!");
-            }
-        }
-
-        @Override
-        public void onError(@io.reactivex.annotations.NonNull Throwable e) {
-            handleObserverError(e);
-        }
-
-        @Override
-        public void onComplete() {
-            dispose();
-        }
-    }
-
-    private class RoomOperationsObserver implements Observer<RoomOverall> {
-
-        @Override
-        public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
-            disposable = d;
-        }
-
-        @Override
-        public void onNext(@io.reactivex.annotations.NonNull RoomOverall roomOverall) {
-            conversation = roomOverall.getOcs().getData();
-            if (operation != OPS_CODE_JOIN_ROOM) {
-                showResultImage(true, false);
-            } else {
-                conversation = roomOverall.getOcs().getData();
-                initiateConversation();
-            }
-        }
-
-        @Override
-        public void onError(@io.reactivex.annotations.NonNull Throwable e) {
-            handleObserverError(e);
-        }
-
-        @Override
-        public void onComplete() {
-            dispose();
-        }
-    }
-
-    @Override
-    public AppBarLayoutType getAppBarLayoutType() {
-        return AppBarLayoutType.SEARCH_BAR;
-    }
-}

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

@@ -0,0 +1,819 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * @author Andy Scherzinger
+ * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
+ * Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.nextcloud.talk.controllers.bottomsheet
+
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.text.TextUtils
+import android.util.Log
+import android.view.View
+import autodagger.AutoInjector
+import com.bluelinelabs.conductor.RouterTransaction
+import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
+import com.bluelinelabs.logansquare.LoganSquare
+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.util.viewBinding
+import com.nextcloud.talk.databinding.ControllerOperationsMenuBinding
+import com.nextcloud.talk.events.ConversationsListFetchDataEvent
+import com.nextcloud.talk.events.OpenConversationEvent
+import com.nextcloud.talk.models.RetrofitBucket
+import com.nextcloud.talk.models.database.CapabilitiesUtil
+import com.nextcloud.talk.models.database.UserEntity
+import com.nextcloud.talk.models.json.capabilities.Capabilities
+import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall
+import com.nextcloud.talk.models.json.conversations.Conversation
+import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType
+import com.nextcloud.talk.models.json.conversations.RoomOverall
+import com.nextcloud.talk.models.json.generic.GenericOverall
+import com.nextcloud.talk.models.json.participants.AddParticipantOverall
+import com.nextcloud.talk.utils.ApiUtils
+import com.nextcloud.talk.utils.DisplayUtils
+import com.nextcloud.talk.utils.NoSupportedApiException
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CALL_URL
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_PASSWORD
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_TYPE
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INVITED_GROUP
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INVITED_PARTICIPANTS
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_OPERATION_CODE
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SERVER_CAPABILITIES
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
+import com.nextcloud.talk.utils.database.user.UserUtils
+import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
+import io.reactivex.Observer
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import io.reactivex.schedulers.Schedulers
+import org.greenrobot.eventbus.EventBus
+import org.parceler.Parcels
+import retrofit2.HttpException
+import java.io.IOException
+import java.util.Collections
+import javax.inject.Inject
+
+@AutoInjector(NextcloudTalkApplication::class)
+class OperationsMenuController(args: Bundle) : NewBaseController(
+    R.layout.controller_operations_menu,
+    args
+) {
+    private val binding: ControllerOperationsMenuBinding by viewBinding(ControllerOperationsMenuBinding::bind)
+
+    @Inject
+    lateinit var ncApi: NcApi
+
+    @Inject
+    lateinit var userUtils: UserUtils
+
+    @Inject
+    lateinit var eventBus: EventBus
+
+    private val operation: ConversationOperationEnum?
+    private var conversation: Conversation? = null
+    private var currentUser: UserEntity? = null
+    private val callPassword: String
+    private val callUrl: String
+    private var baseUrl: String? = null
+    private var conversationToken: String? = null
+    private var disposable: Disposable? = null
+    private var conversationType: ConversationType? = null
+    private var invitedUsers: ArrayList<String>? = ArrayList()
+    private var invitedGroups: ArrayList<String>? = ArrayList()
+    private var serverCapabilities: Capabilities? = null
+    private var credentials: String? = null
+    private val conversationName: String
+
+    override val appBarLayoutType: AppBarLayoutType
+        get() = AppBarLayoutType.SEARCH_BAR
+
+    override fun onViewBound(view: View) {
+        super.onViewBound(view)
+        sharedApplication!!.componentApplication.inject(this)
+        currentUser = userUtils.currentUser
+
+        if (!TextUtils.isEmpty(callUrl) && callUrl.contains("/call")) {
+            conversationToken = callUrl.substring(callUrl.lastIndexOf("/") + 1)
+            if (callUrl.contains("/index.php")) {
+                baseUrl = callUrl.substring(0, callUrl.indexOf("/index.php"))
+            } else {
+                baseUrl = callUrl.substring(0, callUrl.indexOf("/call"))
+            }
+        }
+        if (!TextUtils.isEmpty(baseUrl) && baseUrl != currentUser!!.baseUrl) {
+            if (serverCapabilities != null) {
+                try {
+                    useBundledCapabilitiesForGuest()
+                } catch (e: IOException) {
+                    // Fall back to fetching capabilities again
+                    fetchCapabilitiesForGuest()
+                }
+            } else {
+                fetchCapabilitiesForGuest()
+            }
+        } else {
+            processOperation()
+        }
+    }
+
+    @Throws(IOException::class)
+    private fun useBundledCapabilitiesForGuest() {
+        currentUser = UserEntity()
+        currentUser!!.baseUrl = baseUrl
+        currentUser!!.userId = "?"
+        try {
+            currentUser!!.capabilities = LoganSquare.serialize(serverCapabilities)
+        } catch (e: IOException) {
+            Log.e("OperationsMenu", "Failed to serialize capabilities")
+            throw e
+        }
+        try {
+            checkCapabilities(currentUser!!)
+            processOperation()
+        } catch (e: NoSupportedApiException) {
+            showResultImage(everythingOK = false, isGuestSupportError = false)
+            Log.d(TAG, "No supported server version found", e)
+        }
+    }
+
+    private fun fetchCapabilitiesForGuest() {
+        ncApi.getCapabilities(null, ApiUtils.getUrlForCapabilities(baseUrl))
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribe(object : Observer<CapabilitiesOverall> {
+                override fun onSubscribe(d: Disposable) {
+                    // unused atm
+                }
+
+                override fun onNext(capabilitiesOverall: CapabilitiesOverall) {
+                    currentUser = UserEntity()
+                    currentUser!!.baseUrl = baseUrl
+                    currentUser!!.userId = "?"
+                    try {
+                        currentUser!!.capabilities = LoganSquare
+                            .serialize(capabilitiesOverall.ocs!!.data!!.capabilities)
+                    } catch (e: IOException) {
+                        Log.e("OperationsMenu", "Failed to serialize capabilities")
+                    }
+                    try {
+                        checkCapabilities(currentUser!!)
+                        processOperation()
+                    } catch (e: NoSupportedApiException) {
+                        showResultImage(everythingOK = false, isGuestSupportError = false)
+                        Log.d(TAG, "No supported server version found", e)
+                    }
+                }
+
+                override fun onError(e: Throwable) {
+                    showResultImage(everythingOK = false, isGuestSupportError = false)
+                    Log.e(TAG, "Error fetching capabilities for guest", e)
+                }
+
+                override fun onComplete() {
+                    // unused atm
+                }
+            })
+    }
+
+    @Suppress("Detekt.ComplexMethod")
+    private fun processOperation() {
+        if (currentUser == null) {
+            showResultImage(everythingOK = false, isGuestSupportError = true)
+            Log.e(TAG, "Ended up in processOperation without a valid currentUser")
+            return
+        }
+        credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
+        when (operation) {
+            ConversationOperationEnum.OPS_CODE_RENAME_ROOM -> operationRenameRoom()
+            ConversationOperationEnum.OPS_CODE_MAKE_PUBLIC -> operationMakePublic()
+            ConversationOperationEnum.OPS_CODE_CHANGE_PASSWORD,
+            ConversationOperationEnum.OPS_CODE_CLEAR_PASSWORD,
+            ConversationOperationEnum.OPS_CODE_SET_PASSWORD -> operationChangePassword()
+            ConversationOperationEnum.OPS_CODE_MAKE_PRIVATE -> operationMakePrivate()
+            ConversationOperationEnum.OPS_CODE_GET_AND_JOIN_ROOM -> operationGetAndJoinRoom()
+            ConversationOperationEnum.OPS_CODE_INVITE_USERS -> operationInviteUsers()
+            ConversationOperationEnum.OPS_CODE_MARK_AS_READ -> operationMarkAsRead()
+            ConversationOperationEnum.OPS_CODE_REMOVE_FAVORITE,
+            ConversationOperationEnum.OPS_CODE_ADD_FAVORITE -> operationToggleFavorite()
+            ConversationOperationEnum.OPS_CODE_JOIN_ROOM -> operationJoinRoom()
+            else -> {
+            }
+        }
+    }
+
+    private fun apiVersion(): Int {
+        return ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv1))
+    }
+
+    private fun chatApiVersion(): Int {
+        return ApiUtils.getChatApiVersion(currentUser, intArrayOf(ApiUtils.APIv1))
+    }
+
+    private fun operationJoinRoom() {
+        ncApi.joinRoom(
+            credentials,
+            ApiUtils.getUrlForParticipantsActive(
+                apiVersion(),
+                baseUrl,
+                conversationToken
+            ),
+            callPassword
+        )
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+            .retry(1)
+            .subscribe(RoomOperationsObserver())
+    }
+
+    private fun operationMarkAsRead() {
+        ncApi.setChatReadMarker(
+            credentials,
+            ApiUtils.getUrlForSetChatReadMarker(
+                chatApiVersion(),
+                currentUser!!.baseUrl,
+                conversation!!.getToken()
+            ),
+            conversation!!.lastMessage.jsonMessageId
+        )
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+            .retry(1)
+            .subscribe(GenericOperationsObserver())
+    }
+
+    private fun operationMakePrivate() {
+        ncApi.makeRoomPrivate(
+            credentials,
+            ApiUtils.getUrlForRoomPublic(
+                apiVersion(),
+                currentUser!!.baseUrl,
+                conversation!!.getToken()
+            )
+        )
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+            .retry(1)
+            .subscribe(GenericOperationsObserver())
+    }
+
+    private fun operationChangePassword() {
+        var pass: String? = ""
+        if (conversation!!.getPassword() != null) {
+            pass = conversation!!.getPassword()
+        }
+        ncApi.setPassword(
+            credentials,
+            ApiUtils.getUrlForRoomPassword(
+                apiVersion(),
+                currentUser!!.baseUrl,
+                conversation!!.getToken()
+            ),
+            pass
+        )
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+            .retry(1)
+            .subscribe(GenericOperationsObserver())
+    }
+
+    private fun operationMakePublic() {
+        ncApi.makeRoomPublic(
+            credentials,
+            ApiUtils.getUrlForRoomPublic(
+                apiVersion(),
+                currentUser!!.baseUrl,
+                conversation!!.getToken()
+            )
+        )
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+            .retry(1)
+            .subscribe(GenericOperationsObserver())
+    }
+
+    private fun operationRenameRoom() {
+        ncApi.renameRoom(
+            credentials,
+            ApiUtils.getUrlForRoom(
+                apiVersion(),
+                currentUser!!.baseUrl,
+                conversation!!.getToken()
+            ),
+            conversation!!.getName()
+        )
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+            .retry(1)
+            .subscribe(GenericOperationsObserver())
+    }
+
+    private fun operationToggleFavorite() {
+        val genericOperationsObserver = GenericOperationsObserver()
+        val apiVersion = apiVersion()
+        if (operation === ConversationOperationEnum.OPS_CODE_REMOVE_FAVORITE) {
+            ncApi.removeConversationFromFavorites(
+                credentials,
+                ApiUtils.getUrlForRoomFavorite(
+                    apiVersion,
+                    currentUser!!.baseUrl,
+                    conversation!!.getToken()
+                )
+            )
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .retry(1)
+                .subscribe(genericOperationsObserver)
+        } else {
+            ncApi.addConversationToFavorites(
+                credentials,
+                ApiUtils.getUrlForRoomFavorite(
+                    apiVersion,
+                    currentUser!!.baseUrl,
+                    conversation!!.getToken()
+                )
+            )
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .retry(1)
+                .subscribe(genericOperationsObserver)
+        }
+    }
+
+    private fun operationInviteUsers() {
+        val retrofitBucket: RetrofitBucket
+        val apiVersion = apiVersion()
+        var invite: String? = null
+        if (invitedGroups!!.size > 0) {
+            invite = invitedGroups!![0]
+        }
+        retrofitBucket = if (conversationType == ConversationType.ROOM_PUBLIC_CALL) {
+            ApiUtils.getRetrofitBucketForCreateRoom(
+                apiVersion,
+                currentUser!!.baseUrl,
+                "3",
+                null,
+                invite,
+                conversationName
+            )
+        } else {
+            ApiUtils.getRetrofitBucketForCreateRoom(
+                apiVersion,
+                currentUser!!.baseUrl,
+                "2",
+                null,
+                invite,
+                conversationName
+            )
+        }
+        ncApi.createRoom(credentials, retrofitBucket.getUrl(), retrofitBucket.getQueryMap())
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+            .retry(1)
+            .subscribe(object : Observer<RoomOverall> {
+                override fun onSubscribe(d: Disposable) {
+                    // unused atm
+                }
+
+                override fun onNext(roomOverall: RoomOverall) {
+                    conversation = roomOverall.getOcs().getData()
+                    ncApi.getRoom(
+                        credentials,
+                        ApiUtils.getUrlForRoom(
+                            apiVersion, currentUser!!.baseUrl,
+                            conversation!!.getToken()
+                        )
+                    )
+                        .subscribeOn(Schedulers.io())
+                        .observeOn(AndroidSchedulers.mainThread())
+                        .subscribe(object : Observer<RoomOverall> {
+                            override fun onSubscribe(d: Disposable) {
+                                // unused atm
+                            }
+
+                            override fun onNext(
+                                roomOverall: RoomOverall
+                            ) {
+                                conversation = roomOverall.getOcs().getData()
+                                inviteUsersToAConversation()
+                            }
+
+                            override fun onError(e: Throwable) {
+                                showResultImage(everythingOK = false, isGuestSupportError = false)
+                                dispose()
+                            }
+
+                            override fun onComplete() {
+                                // unused atm
+                            }
+                        })
+                }
+
+                override fun onError(e: Throwable) {
+                    showResultImage(everythingOK = false, isGuestSupportError = false)
+                    dispose()
+                }
+
+                override fun onComplete() {
+                    dispose()
+                }
+            })
+    }
+
+    private fun operationGetAndJoinRoom() {
+        val apiVersion = apiVersion()
+        ncApi.getRoom(
+            credentials,
+            ApiUtils.getUrlForRoom(apiVersion, baseUrl, conversationToken)
+        )
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+            .retry(1)
+            .subscribe(object : Observer<RoomOverall> {
+                override fun onSubscribe(d: Disposable) {
+                    disposable = d
+                }
+
+                override fun onNext(roomOverall: RoomOverall) {
+                    conversation = roomOverall.getOcs().getData()
+                    if (conversation!!.isHasPassword && conversation!!.isGuest) {
+                        eventBus.post(ConversationsListFetchDataEvent())
+                        val bundle = Bundle()
+                        bundle.putParcelable(KEY_ROOM, Parcels.wrap(conversation))
+                        bundle.putString(KEY_CALL_URL, callUrl)
+                        try {
+                            bundle.putParcelable(
+                                KEY_SERVER_CAPABILITIES,
+                                Parcels.wrap<Capabilities>(
+                                    LoganSquare.parse<Capabilities>(
+                                        currentUser!!.capabilities,
+                                        Capabilities::class.java
+                                    )
+                                )
+                            )
+                        } catch (e: IOException) {
+                            Log.e(TAG, "Failed to parse capabilities for guest")
+                            showResultImage(everythingOK = false, isGuestSupportError = false)
+                        }
+                        bundle.putSerializable(KEY_OPERATION_CODE, ConversationOperationEnum.OPS_CODE_JOIN_ROOM)
+                        router.pushController(
+                            RouterTransaction.with(EntryMenuController(bundle))
+                                .pushChangeHandler(HorizontalChangeHandler())
+                                .popChangeHandler(HorizontalChangeHandler())
+                        )
+                    } else if (conversation!!.isGuest) {
+                        ncApi.joinRoom(
+                            credentials,
+                            ApiUtils.getUrlForParticipantsActive(
+                                apiVersion,
+                                baseUrl,
+                                conversationToken
+                            ),
+                            null
+                        )
+                            .subscribeOn(Schedulers.io())
+                            .observeOn(AndroidSchedulers.mainThread())
+                            .subscribe(object : Observer<RoomOverall> {
+                                override fun onSubscribe(d: Disposable) {
+                                    // unused atm
+                                }
+
+                                override fun onNext(roomOverall: RoomOverall) {
+                                    conversation = roomOverall.getOcs().getData()
+                                    initiateConversation()
+                                }
+
+                                override fun onError(e: Throwable) {
+                                    showResultImage(everythingOK = false, isGuestSupportError = false)
+                                    dispose()
+                                }
+
+                                override fun onComplete() {
+                                    // unused atm
+                                }
+                            })
+                    } else {
+                        initiateConversation()
+                    }
+                }
+
+                override fun onError(e: Throwable) {
+                    showResultImage(everythingOK = false, isGuestSupportError = false)
+                    dispose()
+                }
+
+                override fun onComplete() {
+                    dispose()
+                }
+            })
+    }
+
+    @Suppress("Detekt.TooGenericExceptionCaught")
+    private fun showResultImage(everythingOK: Boolean, isGuestSupportError: Boolean) {
+        try {
+            binding.progressBar.visibility = View.GONE
+            if (resources != null) {
+                if (everythingOK) {
+                    binding.resultImageView.setImageDrawable(
+                        DisplayUtils.getTintedDrawable(
+                            resources,
+                            R.drawable.ic_check_circle_black_24dp,
+                            R.color.nc_darkGreen
+                        )
+                    )
+                } else {
+                    binding.resultImageView.setImageDrawable(
+                        DisplayUtils.getTintedDrawable(
+                            resources,
+                            R.drawable.ic_cancel_black_24dp,
+                            R.color.nc_darkRed
+                        )
+                    )
+                }
+            }
+            binding.resultImageView.visibility = View.VISIBLE
+            if (everythingOK) {
+                binding.resultTextView.setText(R.string.nc_all_ok_operation)
+            } else {
+                binding.resultTextView.setTextColor(resources!!.getColor(R.color.nc_darkRed))
+                if (!isGuestSupportError) {
+                    binding.resultTextView.setText(R.string.nc_failed_to_perform_operation)
+                } else {
+                    binding.resultTextView.setText(R.string.nc_failed_signaling_settings)
+                    binding.webButton.setOnClickListener {
+                        val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(callUrl))
+                        startActivity(browserIntent)
+                    }
+                    binding.webButton.visibility = View.VISIBLE
+                }
+            }
+            binding.resultTextView.visibility = View.VISIBLE
+            if (everythingOK) {
+                eventBus!!.post(ConversationsListFetchDataEvent())
+            } else {
+                binding.resultImageView.setImageDrawable(
+                    DisplayUtils.getTintedDrawable(
+                        resources,
+                        R.drawable.ic_cancel_black_24dp,
+                        R.color.nc_darkRed
+                    )
+                )
+                binding.okButton.setOnClickListener { v: View? -> eventBus!!.post(ConversationsListFetchDataEvent()) }
+                binding.okButton.visibility = View.VISIBLE
+            }
+        } catch (npe: NullPointerException) {
+            Log.i(TAG, "Controller already closed", npe)
+        }
+    }
+
+    private fun dispose() {
+        if (disposable != null && !disposable!!.isDisposed) {
+            disposable!!.dispose()
+        }
+        disposable = null
+    }
+
+    public override fun onDestroy() {
+        super.onDestroy()
+        dispose()
+    }
+
+    @kotlin.Throws(NoSupportedApiException::class)
+    private fun checkCapabilities(currentUser: UserEntity) {
+        ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, 1))
+        ApiUtils.getCallApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, 1))
+        ApiUtils.getChatApiVersion(currentUser, intArrayOf(1))
+        ApiUtils.getSignalingApiVersion(currentUser, intArrayOf(ApiUtils.APIv3, 2, 1))
+    }
+
+    private fun inviteUsersToAConversation() {
+        val localInvitedUsers = invitedUsers
+        val localInvitedGroups = invitedGroups
+        if (localInvitedGroups!!.size > 0) {
+            localInvitedGroups.removeAt(0)
+        }
+        val apiVersion = ApiUtils.getConversationApiVersion(currentUser, API_CONVERSATION_VERSIONS)
+        if (localInvitedUsers!!.size > 0 || localInvitedGroups.size > 0 &&
+            CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "invite-groups-and-mails")
+        ) {
+            addGroupsToConversation(localInvitedUsers, localInvitedGroups, apiVersion)
+            addUsersToConversation(localInvitedUsers, localInvitedGroups, apiVersion)
+        } else {
+            initiateConversation()
+        }
+    }
+
+    private fun addUsersToConversation(
+        localInvitedUsers: ArrayList<String>?,
+        localInvitedGroups: ArrayList<String>?,
+        apiVersion: Int
+    ) {
+        var retrofitBucket: RetrofitBucket
+        for (i in localInvitedUsers!!.indices) {
+            val userId = invitedUsers!![i]
+            retrofitBucket = ApiUtils.getRetrofitBucketForAddParticipant(
+                apiVersion,
+                currentUser!!.baseUrl,
+                conversation!!.getToken(),
+                userId
+            )
+            ncApi.addParticipant(credentials, retrofitBucket.getUrl(), retrofitBucket.getQueryMap())
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .retry(1)
+                .subscribe(object : Observer<AddParticipantOverall> {
+                    override fun onSubscribe(d: Disposable) {
+                        // unused atm
+                    }
+                    override fun onNext(addParticipantOverall: AddParticipantOverall) {
+                        // unused atm
+                    }
+
+                    override fun onError(e: Throwable) {
+                        dispose()
+                    }
+
+                    override fun onComplete() {
+                        Collections.synchronizedList(localInvitedUsers).remove(userId)
+                        if (localInvitedGroups!!.size == 0 && localInvitedUsers.size == 0) {
+                            initiateConversation()
+                        }
+                        dispose()
+                    }
+                })
+        }
+    }
+
+    private fun addGroupsToConversation(
+        localInvitedUsers: ArrayList<String>?,
+        localInvitedGroups: ArrayList<String>?,
+        apiVersion: Int
+    ) {
+        var retrofitBucket: RetrofitBucket
+        if (localInvitedGroups!!.size > 0 &&
+            CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "invite-groups-and-mails")
+        ) {
+            for (i in localInvitedGroups.indices) {
+                val groupId = localInvitedGroups[i]
+                retrofitBucket = ApiUtils.getRetrofitBucketForAddParticipantWithSource(
+                    apiVersion,
+                    currentUser!!.baseUrl,
+                    conversation!!.getToken(),
+                    "groups",
+                    groupId
+                )
+                ncApi.addParticipant(credentials, retrofitBucket.getUrl(), retrofitBucket.getQueryMap())
+                    .subscribeOn(Schedulers.io())
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .retry(1)
+                    .subscribe(object : Observer<AddParticipantOverall> {
+                        override fun onSubscribe(d: Disposable) {
+                            // unused atm
+                        }
+                        override fun onNext(addParticipantOverall: AddParticipantOverall) {
+                            // unused atm
+                        }
+
+                        override fun onError(e: Throwable) {
+                            dispose()
+                        }
+
+                        override fun onComplete() {
+                            Collections.synchronizedList(localInvitedGroups).remove(groupId)
+                            if (localInvitedGroups.size == 0 && localInvitedUsers!!.size == 0) {
+                                initiateConversation()
+                            }
+                            dispose()
+                        }
+                    })
+            }
+        }
+    }
+
+    private fun initiateConversation() {
+        eventBus.post(ConversationsListFetchDataEvent())
+        val bundle = Bundle()
+        bundle.putString(KEY_ROOM_TOKEN, conversation!!.getToken())
+        bundle.putString(KEY_ROOM_ID, conversation!!.getRoomId())
+        bundle.putString(KEY_CONVERSATION_NAME, conversation!!.getDisplayName())
+        bundle.putParcelable(KEY_USER_ENTITY, currentUser)
+        bundle.putParcelable(KEY_ACTIVE_CONVERSATION, Parcels.wrap(conversation))
+        bundle.putString(KEY_CONVERSATION_PASSWORD, callPassword)
+        eventBus.post(OpenConversationEvent(conversation, bundle))
+    }
+
+    private fun handleObserverError(e: Throwable) {
+        if (operation !== ConversationOperationEnum.OPS_CODE_JOIN_ROOM || e !is HttpException) {
+            showResultImage(everythingOK = false, isGuestSupportError = false)
+        } else {
+            val response = e.response()
+            if (response != null && response.code() == FORBIDDEN) {
+                ApplicationWideMessageHolder.getInstance()
+                    .setMessageType(ApplicationWideMessageHolder.MessageType.CALL_PASSWORD_WRONG)
+                router.popCurrentController()
+            } else {
+                showResultImage(everythingOK = false, isGuestSupportError = false)
+            }
+        }
+        dispose()
+    }
+
+    private inner class GenericOperationsObserver : Observer<GenericOverall> {
+        override fun onSubscribe(d: Disposable) {
+            disposable = d
+        }
+
+        override fun onNext(genericOverall: GenericOverall) {
+            if (operation !== ConversationOperationEnum.OPS_CODE_JOIN_ROOM) {
+                showResultImage(everythingOK = true, isGuestSupportError = false)
+            } else {
+                throw IllegalArgumentException("Unsupported operation code observed!")
+            }
+        }
+
+        override fun onError(e: Throwable) {
+            handleObserverError(e)
+        }
+
+        override fun onComplete() {
+            dispose()
+        }
+    }
+
+    private inner class RoomOperationsObserver : Observer<RoomOverall> {
+        override fun onSubscribe(d: Disposable) {
+            disposable = d
+        }
+
+        override fun onNext(roomOverall: RoomOverall) {
+            conversation = roomOverall.getOcs().getData()
+            if (operation !== ConversationOperationEnum.OPS_CODE_JOIN_ROOM) {
+                showResultImage(everythingOK = true, isGuestSupportError = false)
+            } else {
+                conversation = roomOverall.getOcs().getData()
+                initiateConversation()
+            }
+        }
+
+        override fun onError(e: Throwable) {
+            handleObserverError(e)
+        }
+
+        override fun onComplete() {
+            dispose()
+        }
+    }
+
+    companion object {
+        private const val TAG = "OperationsMenu"
+        private const val FORBIDDEN = 403
+        private val API_CONVERSATION_VERSIONS = intArrayOf(4, 1)
+    }
+
+    init {
+        operation = args.getSerializable(KEY_OPERATION_CODE) as ConversationOperationEnum?
+        if (args.containsKey(KEY_ROOM)) {
+            conversation = Parcels.unwrap(args.getParcelable(KEY_ROOM))
+        }
+        callPassword = args.getString(KEY_CONVERSATION_PASSWORD, "")
+        callUrl = args.getString(KEY_CALL_URL, "")
+        if (args.containsKey(KEY_INVITED_PARTICIPANTS)) {
+            invitedUsers = args.getStringArrayList(KEY_INVITED_PARTICIPANTS)
+        }
+        if (args.containsKey(KEY_INVITED_GROUP)) {
+            invitedGroups = args.getStringArrayList(KEY_INVITED_GROUP)
+        }
+        if (args.containsKey(KEY_CONVERSATION_TYPE)) {
+            conversationType = Parcels.unwrap(args.getParcelable(KEY_CONVERSATION_TYPE))
+        }
+        if (args.containsKey(KEY_SERVER_CAPABILITIES)) {
+            serverCapabilities = Parcels.unwrap(args.getParcelable(KEY_SERVER_CAPABILITIES))
+        }
+        conversationName = args.getString(KEY_CONVERSATION_NAME, "")
+    }
+}

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

@@ -1 +1 @@
-411
+406

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

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