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

fix design when keyboard is shown
This commit is contained in:
Marcel Hibbe 2022-02-16 14:56:15 +01:00 committed by GitHub
commit db1071d2ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1365 additions and 1228 deletions

View File

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

View File

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

View File

@ -2,7 +2,9 @@
~ Nextcloud Talk application
~
~ @author Mario Danic
~ @author Marcel Hibbe
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
~ Copyright (C) 2021-2022 Marcel Hibbe <dev@mhibbe.de>
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
@ -97,7 +99,7 @@
<activity
android:name=".activities.MainActivity"
android:label="@string/nc_app_name"
android:windowSoftInputMode="adjustPan">
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,356 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.controllers.bottomsheet;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.kennyc.bottomsheet.adapters.AppAdapter;
import com.nextcloud.talk.R;
import com.nextcloud.talk.adapters.items.AppItem;
import com.nextcloud.talk.adapters.items.MenuItem;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.controllers.ConversationsListController;
import com.nextcloud.talk.controllers.base.BaseController;
import com.nextcloud.talk.events.BottomSheetLockEvent;
import com.nextcloud.talk.interfaces.ConversationMenuInterface;
import com.nextcloud.talk.jobs.LeaveConversationWorker;
import com.nextcloud.talk.models.database.CapabilitiesUtil;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.models.json.conversations.Conversation;
import com.nextcloud.talk.utils.DisplayUtils;
import com.nextcloud.talk.utils.ShareUtils;
import com.nextcloud.talk.utils.bundle.BundleKeys;
import com.nextcloud.talk.utils.database.user.UserUtils;
import org.greenrobot.eventbus.EventBus;
import org.parceler.Parcel;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.work.Data;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import autodagger.AutoInjector;
import butterknife.BindView;
import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager;
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
@AutoInjector(NextcloudTalkApplication.class)
public class CallMenuController extends BaseController implements FlexibleAdapter.OnItemClickListener {
public static final int ALL_MESSAGES_READ = 0;
@BindView(R.id.recycler_view)
RecyclerView recyclerView;
@Inject
EventBus eventBus;
@Inject
UserUtils userUtils;
@Inject
Context context;
private Conversation conversation;
private List<AbstractFlexibleItem> menuItems;
private FlexibleAdapter<AbstractFlexibleItem> adapter;
private MenuType menuType;
private Intent shareIntent;
private UserEntity currentUser;
private ConversationMenuInterface conversationMenuInterface;
public CallMenuController(Bundle args) {
super(args);
this.conversation = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_ROOM()));
if (args.containsKey(BundleKeys.INSTANCE.getKEY_MENU_TYPE())) {
this.menuType = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_MENU_TYPE()));
}
}
public CallMenuController(Bundle args, @Nullable ConversationMenuInterface conversationMenuInterface) {
super(args);
this.conversation = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_ROOM()));
if (args.containsKey(BundleKeys.INSTANCE.getKEY_MENU_TYPE())) {
this.menuType = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_MENU_TYPE()));
}
this.conversationMenuInterface = conversationMenuInterface;
}
@Override
@NonNull
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_call_menu, container, false);
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
prepareViews();
}
private void prepareViews() {
LinearLayoutManager layoutManager = new SmoothScrollLinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
prepareMenu();
if (adapter == null) {
adapter = new FlexibleAdapter<>(menuItems, getActivity(), false);
}
adapter.addListener(this);
recyclerView.setAdapter(adapter);
}
private void prepareIntent() {
shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_SUBJECT, String.format(getResources().getString(R.string.nc_share_subject),
getResources().getString(R.string.nc_app_product_name)));
}
private void prepareMenu() {
menuItems = new ArrayList<>();
if (menuType.equals(MenuType.REGULAR)) {
if (!TextUtils.isEmpty(conversation.getDisplayName())) {
menuItems.add(new MenuItem(conversation.getDisplayName(), 0, null));
} else if (!TextUtils.isEmpty(conversation.getName())) {
menuItems.add(new MenuItem(conversation.getName(), 0, null));
} else {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_configure_room), 0, null));
}
currentUser = userUtils.getCurrentUser();
if (conversation.isFavorite()) {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_remove_from_favorites), 97, DisplayUtils.getTintedDrawable(getResources(), R.drawable.ic_star_border_black_24dp, R.color.grey_600)));
} else if (CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "favorites")) {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_add_to_favorites),
98,
DisplayUtils.getTintedDrawable(getResources(),
R.drawable.ic_star_black_24dp,
R.color.grey_600)));
}
if(conversation.unreadMessages > ALL_MESSAGES_READ && CapabilitiesUtil.canSetChatReadMarker(currentUser)) {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_mark_as_read),
96,
ContextCompat.getDrawable(context, R.drawable.ic_eye)));
}
if (conversation.isNameEditable(currentUser)) {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_rename),
2,
ContextCompat.getDrawable(context,
R.drawable.ic_pencil_grey600_24dp)));
}
if (conversation.canModerate(currentUser)) {
if (!conversation.isPublic()) {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_make_call_public),
3, ContextCompat.getDrawable(context,
R.drawable.ic_link_grey600_24px)));
} else {
if (conversation.isHasPassword()) {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_change_password),
4, ContextCompat.getDrawable(context,
R.drawable.ic_lock_grey600_24px)));
menuItems.add(new MenuItem(getResources().getString(R.string.nc_clear_password),
5,
ContextCompat.getDrawable(context,
R.drawable.ic_lock_open_grey600_24dp)));
} else {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_set_password),
6, ContextCompat.getDrawable(context,
R.drawable.ic_lock_plus_grey600_24dp)));
}
}
menuItems.add(new MenuItem(getResources().getString(R.string.nc_delete_call),
9, ContextCompat.getDrawable(context,
R.drawable.ic_delete_grey600_24dp)));
}
if (conversation.isPublic()) {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_share_link),
7, ContextCompat.getDrawable(context,
R.drawable.ic_link_grey600_24px)));
if (conversation.canModerate(currentUser)) {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_make_call_private),
8, ContextCompat.getDrawable(context,
R.drawable.ic_group_grey600_24px)));
}
}
if (conversation.canLeave(currentUser)) {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_leave), 1,
DisplayUtils.getTintedDrawable(getResources(),
R.drawable.ic_exit_to_app_black_24dp, R.color.grey_600)
));
}
} else if (menuType.equals(MenuType.SHARE)) {
prepareIntent();
List<AppAdapter.AppInfo> appInfoList = ShareUtils.getShareApps(getActivity(), shareIntent, null,
null);
menuItems.add(new AppItem(getResources().getString(R.string.nc_share_link_via),
"",
"",
ContextCompat.getDrawable(context, R.drawable.ic_link_grey600_24px)));
if (appInfoList != null) {
for (AppAdapter.AppInfo appInfo : appInfoList) {
menuItems.add(new AppItem(appInfo.title, appInfo.packageName, appInfo.name, appInfo.drawable));
}
}
} else {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_start_conversation), 0, null));
menuItems.add(new MenuItem(getResources().getString(R.string.nc_new_conversation),
1, ContextCompat.getDrawable(context,
R.drawable.ic_add_grey600_24px)));
menuItems.add(new MenuItem(getResources().getString(R.string.nc_join_via_link),
2, ContextCompat.getDrawable(context,
R.drawable.ic_link_grey600_24px)));
}
}
@Override
public boolean onItemClick(View view, int position) {
Bundle bundle = new Bundle();
bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ROOM(), Parcels.wrap(conversation));
if (menuType.equals(MenuType.REGULAR)) {
MenuItem menuItem = (MenuItem) adapter.getItem(position);
if (menuItem != null) {
int tag = menuItem.getTag();
if (tag == 5) {
conversation.setPassword("");
}
if (tag > 0) {
if (tag == 1 || tag == 9) {
if (tag == 1) {
Data data;
if ((data = getWorkerData()) != null) {
OneTimeWorkRequest leaveConversationWorker =
new OneTimeWorkRequest.Builder(LeaveConversationWorker.class).setInputData(data).build();
WorkManager.getInstance().enqueue(leaveConversationWorker);
}
} else {
Bundle deleteConversationBundle;
if ((deleteConversationBundle = getDeleteConversationBundle()) != null) {
conversationMenuInterface.openLovelyDialogWithIdAndBundle(ConversationsListController.ID_DELETE_CONVERSATION_DIALOG, deleteConversationBundle);
}
}
eventBus.post(new BottomSheetLockEvent(true, 0, false, true));
} else {
bundle.putInt(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), tag);
if (tag != 2 && tag != 4 && tag != 6 && tag != 7) {
eventBus.post(new BottomSheetLockEvent(false, 0, false, false));
getRouter().pushController(RouterTransaction.with(new OperationsMenuController(bundle))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
} else if (tag != 7) {
getRouter().pushController(RouterTransaction.with(new EntryMenuController(bundle))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
} else {
bundle.putParcelable(BundleKeys.INSTANCE.getKEY_MENU_TYPE(), Parcels.wrap(MenuType.SHARE));
getRouter().pushController(RouterTransaction.with(new CallMenuController(bundle, null))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
}
}
}
}
} else if (menuType.equals(MenuType.SHARE) && position != 0) {
AppItem appItem = (AppItem) adapter.getItem(position);
if (appItem != null && getActivity() != null) {
if (!conversation.hasPassword) {
shareIntent.putExtra(Intent.EXTRA_TEXT, ShareUtils.getStringForIntent(getActivity(), null,
userUtils, conversation));
Intent intent = new Intent(shareIntent);
intent.setComponent(new ComponentName(appItem.getPackageName(), appItem.getName()));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
eventBus.post(new BottomSheetLockEvent(true, 0, false, true));
getActivity().startActivity(intent);
} else {
bundle.putInt(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), 7);
bundle.putParcelable(BundleKeys.INSTANCE.getKEY_SHARE_INTENT(), Parcels.wrap(shareIntent));
bundle.putString(BundleKeys.INSTANCE.getKEY_APP_ITEM_PACKAGE_NAME(), appItem.getPackageName());
bundle.putString(BundleKeys.INSTANCE.getKEY_APP_ITEM_NAME(), appItem.getName());
getRouter().pushController(RouterTransaction.with(new EntryMenuController(bundle))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
}
}
}
return true;
}
private Data getWorkerData() {
if (!TextUtils.isEmpty(conversation.getToken())) {
Data.Builder data = new Data.Builder();
data.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), conversation.getToken());
data.putLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), currentUser.getId());
return data.build();
}
return null;
}
private Bundle getDeleteConversationBundle() {
if (!TextUtils.isEmpty(conversation.getToken())) {
Bundle bundle = new Bundle();
bundle.putLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), currentUser.getId());
bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ROOM(), Parcels.wrap(conversation));
return bundle;
}
return null;
}
@Parcel
public enum MenuType {
REGULAR, SHARE
}
}

View File

@ -0,0 +1,37 @@
/*
* Nextcloud Talk application
*
* @author Marcel Hibbe
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.controllers.bottomsheet
enum class ConversationOperationEnum {
OPS_CODE_RENAME_ROOM,
OPS_CODE_MAKE_PUBLIC,
OPS_CODE_CHANGE_PASSWORD,
OPS_CODE_CLEAR_PASSWORD,
OPS_CODE_SET_PASSWORD,
OPS_CODE_SHARE_LINK,
OPS_CODE_MAKE_PRIVATE,
OPS_CODE_GET_AND_JOIN_ROOM,
OPS_CODE_INVITE_USERS,
OPS_CODE_MARK_AS_READ,
OPS_CODE_REMOVE_FAVORITE,
OPS_CODE_ADD_FAVORITE,
OPS_CODE_JOIN_ROOM
}

View File

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

View File

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

View File

@ -1,116 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.events;
public class BottomSheetLockEvent {
private final boolean cancelable;
private final int delay;
private final boolean shouldRefreshData;
private final boolean cancel;
private boolean dismissView;
public BottomSheetLockEvent(boolean cancelable, int delay, boolean shouldRefreshData, boolean cancel) {
this.cancelable = cancelable;
this.delay = delay;
this.shouldRefreshData = shouldRefreshData;
this.cancel = cancel;
this.dismissView = true;
}
public BottomSheetLockEvent(boolean cancelable, int delay, boolean shouldRefreshData, boolean cancel, boolean
dismissView) {
this.cancelable = cancelable;
this.delay = delay;
this.shouldRefreshData = shouldRefreshData;
this.cancel = cancel;
this.dismissView = dismissView;
}
public boolean isCancelable() {
return this.cancelable;
}
public int getDelay() {
return this.delay;
}
public boolean isShouldRefreshData() {
return this.shouldRefreshData;
}
public boolean isCancel() {
return this.cancel;
}
public boolean isDismissView() {
return this.dismissView;
}
public void setDismissView(boolean dismissView) {
this.dismissView = dismissView;
}
public boolean equals(final Object o) {
if (o == this) {
return true;
}
if (!(o instanceof BottomSheetLockEvent)) {
return false;
}
final BottomSheetLockEvent other = (BottomSheetLockEvent) o;
if (!other.canEqual((Object) this)) {
return false;
}
if (this.isCancelable() != other.isCancelable()) {
return false;
}
if (this.getDelay() != other.getDelay()) {
return false;
}
if (this.isShouldRefreshData() != other.isShouldRefreshData()) {
return false;
}
if (this.isCancel() != other.isCancel()) {
return false;
}
return this.isDismissView() == other.isDismissView();
}
protected boolean canEqual(final Object other) {
return other instanceof BottomSheetLockEvent;
}
public int hashCode() {
final int PRIME = 59;
int result = 1;
result = result * PRIME + (this.isCancelable() ? 79 : 97);
result = result * PRIME + this.getDelay();
result = result * PRIME + (this.isShouldRefreshData() ? 79 : 97);
result = result * PRIME + (this.isCancel() ? 79 : 97);
result = result * PRIME + (this.isDismissView() ? 79 : 97);
return result;
}
public String toString() {
return "BottomSheetLockEvent(cancelable=" + this.isCancelable() + ", delay=" + this.getDelay() + ", shouldRefreshData=" + this.isShouldRefreshData() + ", cancel=" + this.isCancel() + ", dismissView=" + this.isDismissView() + ")";
}
}

View File

@ -0,0 +1,23 @@
/*
* Nextcloud Talk application
*
* @author Marcel Hibbe
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.events
class ConversationsListFetchDataEvent

View File

@ -0,0 +1,52 @@
/*
* Nextcloud Talk application
*
* @author Marcel Hibbe
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.events
import android.os.Bundle
import com.nextcloud.talk.models.json.conversations.Conversation
class OpenConversationEvent {
var conversation: Conversation? = null
var bundle: Bundle? = null
constructor(conversation: Conversation?, bundle: Bundle?) {
this.conversation = conversation
this.bundle = bundle
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as OpenConversationEvent
if (conversation != other.conversation) return false
if (bundle != other.bundle) return false
return true
}
override fun hashCode(): Int {
var result = conversation?.hashCode() ?: 0
result = 31 * result + (bundle?.hashCode() ?: 0)
return result
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,82 @@
/*
* Nextcloud Talk application
*
* @author Marcel Hibbe
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.ui.dialog
import android.app.Activity
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import autodagger.AutoInjector
import com.bluelinelabs.conductor.Conductor
import com.bluelinelabs.conductor.Router
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.controllers.bottomsheet.EntryMenuController
import com.nextcloud.talk.databinding.DialogBottomContactsBinding
@AutoInjector(NextcloudTalkApplication::class)
class ContactsBottomDialog(
val activity: Activity,
val bundle: Bundle
) : BottomSheetDialog(activity, R.style.BottomSheetDialogThemeNoFloating) {
private var dialogRouter: Router? = null
private lateinit var binding: DialogBottomContactsBinding
init {
NextcloudTalkApplication.sharedApplication?.componentApplication?.inject(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DialogBottomContactsBinding.inflate(layoutInflater)
setContentView(binding.root)
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
executeEntryMenuController(bundle)
}
private fun executeEntryMenuController(bundle: Bundle) {
dialogRouter = Conductor.attachRouter(activity, binding.root, null)
dialogRouter!!.pushController(
RouterTransaction.with(EntryMenuController(bundle))
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
}
override fun onStart() {
super.onStart()
val bottomSheet = findViewById<View>(R.id.design_bottom_sheet)
val behavior = BottomSheetBehavior.from(bottomSheet as View)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
companion object {
private const val TAG = "ContactsBottomDialog"
}
}

View File

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

View File

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

View File

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

View File

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Talk application
~
~ @author Mario Danic
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="#0082c9"
android:pathData="M1.5,2 C1.25,2,1,2.25,1,2.5 L1,13.5 C1,13.76,1.24,14,1.5,14 L14.5,14
C14.76,14,15,13.759,15,13.5 L15,4.5 C15,4.25,14.75,4,14.5,4 L8,4 L6,2 L1.5,2 Z
M9.834,5.5 C10.268,5.4981,10.7,5.6545,11.02,5.9746
C11.66,6.6153,11.66,7.7166,11.02,8.3574 L10.17,9.207
C10.197,8.7574,10.151,8.4573,9.8633,8.0508 L10.287,7.625
C10.562,7.3498,10.539,6.9967,10.289,6.709
C10.019,6.4391,9.645,6.4388,9.373,6.7109 L7.7266,8.3574
C7.4437,8.6404,7.431,8.9797,7.7246,9.2734 L6.9941,10.006
C6.7069,9.7186,6.5083,9.3329,6.4824,8.8984
C6.4568,8.464,6.6281,7.9913,6.9941,7.625 L8.6406,5.9766
C8.9651,5.6619,9.4004,5.5019,9.834,5.5 Z M9.0078,7.9902
C9.296,8.2773,9.4947,8.6645,9.5195,9.0996
C9.5449,9.5348,9.3746,10.006,9.0078,10.373 L7.3594,12.02
C6.7198,12.659,5.6199,12.659,4.9805,12.02
C4.3409,11.38,4.339,10.282,4.9805,9.6406 L5.8418,8.7773
C5.8155,9.2266,5.8645,9.5278,6.1523,9.9336 L5.7129,10.373
C5.4541,10.632,5.3887,10.963,5.7129,11.287
C5.9567,11.531,6.2686,11.594,6.6289,11.287 L8.2754,9.6406
C8.5401,9.3758,8.562,9.0075,8.2773,8.7227 L9.0078,7.9902 Z" />
</vector>

View File

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Talk application
~
~ @author Mario Danic
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="#0082c9"
android:pathData="M1.5,2 C1.25,2,1,2.25,1,2.5 L1,13.5 C1,13.76,1.24,14,1.5,14 L14.5,14
C14.76,14,15,13.759,15,13.5 L15,4.5 C15,4.25,14.75,4,14.5,4 L8,4 L6,2 L1.5,2 Z
M8,5.25 L9.1,7.9004 L12,8.125 L9.75,10 L10.5,12.75 L8,11.199 L5.5,12.75 L6.25,10
L4,8.125 L6.9,7.9004 L8,5.25 Z" />
</vector>

View File

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

View File

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

View File

@ -1,8 +1,11 @@
<?xml version="1.0" encoding="utf-8"?><!--
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Talk application
~
~ @author Mario Danic
~ Copyright (C) 2017 Mario Danic
~ @author Marcel Hibbe
~ @author Andy Scherzinger
~ Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
~ Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
@ -17,17 +20,13 @@
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/bg_default">
android:background="@color/bg_bottom_sheet"
android:orientation="vertical"
android:paddingStart="@dimen/standard_padding"
android:paddingEnd="@dimen/standard_padding"
android:paddingBottom="@dimen/standard_half_padding">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
</LinearLayout>

View File

@ -0,0 +1,376 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Talk application
~
~ @author Marcel Hibbe
~ @author Andy Scherzinger
~ Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
~ Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/bg_bottom_sheet"
android:orientation="vertical"
android:paddingStart="@dimen/standard_padding"
android:paddingEnd="@dimen/standard_padding"
android:paddingBottom="@dimen/standard_half_padding">
<LinearLayout
android:id="@+id/operation_items_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:ignore="UseCompoundDrawables">
<TextView
android:id="@+id/conversation_operation_header"
android:layout_width="wrap_content"
android:layout_height="@dimen/bottom_sheet_item_height"
android:gravity="start|center_vertical"
android:textColor="@color/medium_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size"
tools:text="conversation name" />
<LinearLayout
android:id="@+id/conversation_operation_remove_favorite"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_star_border_black_24dp"
app:tint="@color/grey_600" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_remove_from_favorites"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_add_favorite"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_star_black_24dp"
app:tint="@color/grey_600" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_add_to_favorites"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_mark_as_read"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_eye"
app:tint="@color/grey_600" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_mark_as_read"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_rename"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_pencil_grey600_24dp"
app:tint="@color/grey_600" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_rename"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_make_public"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_link_grey600_24px"
app:tint="@color/grey_600" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_make_call_public"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_change_password"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_lock_grey600_24px"
app:tint="@color/grey_600" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_change_password"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_clear_password"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_lock_open_grey600_24dp"
app:tint="@color/grey_600" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_clear_password"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_set_password"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_lock_plus_grey600_24dp"
app:tint="@color/grey_600" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_set_password"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_delete"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_delete_grey600_24dp"
app:tint="@color/grey_600" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_delete_call"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_share_link"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_link_grey600_24px"
app:tint="@color/grey_600" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_share_link"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_make_private"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_group_grey600_24px"
app:tint="@color/grey_600" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_make_call_private"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_leave"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_exit_to_app_black_24dp"
app:tint="@color/grey_600" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_leave"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

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

View File

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

View File

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

View File

@ -1 +1 @@
552
542

View File

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