mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-20 03:59:35 +01:00
Merge pull request #1922 from nextcloud/bugfix/noid/kotlinConversion5
Migrate OperationsMenuController to kotlin
This commit is contained in:
commit
4c199b1369
@ -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;
|
||||
}
|
||||
}
|
@ -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 @@
|
||||
411
|
||||
406
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user