Add 'Add to a group' button to bottom sheet.
This commit is contained in:
parent
7e934eff5d
commit
e1bb773d85
17 changed files with 505 additions and 37 deletions
|
@ -492,6 +492,9 @@
|
||||||
<activity android:name=".groups.ui.creategroup.CreateGroupActivity"
|
<activity android:name=".groups.ui.creategroup.CreateGroupActivity"
|
||||||
android:theme="@style/TextSecure.LightNoActionBar" />
|
android:theme="@style/TextSecure.LightNoActionBar" />
|
||||||
|
|
||||||
|
<activity android:name=".groups.ui.addtogroup.AddToGroupsActivity"
|
||||||
|
android:theme="@style/TextSecure.LightNoActionBar" />
|
||||||
|
|
||||||
<activity android:name=".groups.ui.addmembers.AddMembersActivity"
|
<activity android:name=".groups.ui.addmembers.AddMembersActivity"
|
||||||
android:theme="@style/TextSecure.LightNoActionBar" />
|
android:theme="@style/TextSecure.LightNoActionBar" />
|
||||||
|
|
||||||
|
|
|
@ -255,7 +255,7 @@ public final class ContactSelectionListFragment extends Fragment
|
||||||
: Collections.unmodifiableSet(Stream.of(currentSelection).collect(Collectors.toSet()));
|
: Collections.unmodifiableSet(Stream.of(currentSelection).collect(Collectors.toSet()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isMulti() {
|
public boolean isMulti() {
|
||||||
return requireActivity().getIntent().getBooleanExtra(MULTI_SELECT, false);
|
return requireActivity().getIntent().getBooleanExtra(MULTI_SELECT, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import android.util.AttributeSet;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.widget.AppCompatImageView;
|
import androidx.appcompat.widget.AppCompatImageView;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
|
|
||||||
|
@ -24,6 +25,7 @@ import org.thoughtcrime.securesms.groups.ui.managegroup.ManageGroupActivity;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment;
|
||||||
import org.thoughtcrime.securesms.util.AvatarUtil;
|
import org.thoughtcrime.securesms.util.AvatarUtil;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
|
@ -162,11 +164,17 @@ public final class AvatarImageView extends AppCompatImageView {
|
||||||
private void setAvatarClickHandler(@NonNull final Recipient recipient, boolean quickContactEnabled) {
|
private void setAvatarClickHandler(@NonNull final Recipient recipient, boolean quickContactEnabled) {
|
||||||
if (quickContactEnabled) {
|
if (quickContactEnabled) {
|
||||||
super.setOnClickListener(v -> {
|
super.setOnClickListener(v -> {
|
||||||
|
Context context = getContext();
|
||||||
if (FeatureFlags.newGroupUI() && recipient.isPushGroup()) {
|
if (FeatureFlags.newGroupUI() && recipient.isPushGroup()) {
|
||||||
getContext().startActivity(ManageGroupActivity.newIntent(getContext(), recipient.requireGroupId().requirePush()),
|
context.startActivity(ManageGroupActivity.newIntent(context, recipient.requireGroupId().requirePush()),
|
||||||
ManageGroupActivity.createTransitionBundle(getContext(), this));
|
ManageGroupActivity.createTransitionBundle(context, this));
|
||||||
} else {
|
} else {
|
||||||
getContext().startActivity(RecipientPreferenceActivity.getLaunchIntent(getContext(), recipient.getId()));
|
if (context instanceof FragmentActivity) {
|
||||||
|
RecipientBottomSheetDialogFragment.create(recipient.getId(), null)
|
||||||
|
.show(((FragmentActivity) context).getSupportFragmentManager(), "BOTTOM");
|
||||||
|
} else {
|
||||||
|
context.startActivity(RecipientPreferenceActivity.getLaunchIntent(context, recipient.getId()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -3,11 +3,6 @@ package org.thoughtcrime.securesms.components;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
import androidx.core.widget.TextViewCompat;
|
|
||||||
|
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
@ -18,20 +13,23 @@ import android.widget.EditText;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
import androidx.core.widget.TextViewCompat;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.views.DarkOverflowToolbar;
|
import org.thoughtcrime.securesms.util.views.DarkOverflowToolbar;
|
||||||
|
|
||||||
public class ContactFilterToolbar extends DarkOverflowToolbar {
|
public final class ContactFilterToolbar extends DarkOverflowToolbar {
|
||||||
private OnFilterChangedListener listener;
|
private OnFilterChangedListener listener;
|
||||||
|
|
||||||
private EditText searchText;
|
private final EditText searchText;
|
||||||
private AnimatingToggle toggle;
|
private final AnimatingToggle toggle;
|
||||||
private ImageView keyboardToggle;
|
private final ImageView keyboardToggle;
|
||||||
private ImageView dialpadToggle;
|
private final ImageView dialpadToggle;
|
||||||
private ImageView clearToggle;
|
private final ImageView clearToggle;
|
||||||
private LinearLayout toggleContainer;
|
private final LinearLayout toggleContainer;
|
||||||
|
|
||||||
public ContactFilterToolbar(Context context) {
|
public ContactFilterToolbar(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
|
@ -45,12 +43,12 @@ public class ContactFilterToolbar extends DarkOverflowToolbar {
|
||||||
super(context, attrs, defStyleAttr);
|
super(context, attrs, defStyleAttr);
|
||||||
inflate(context, R.layout.contact_filter_toolbar, this);
|
inflate(context, R.layout.contact_filter_toolbar, this);
|
||||||
|
|
||||||
this.searchText = ViewUtil.findById(this, R.id.search_view);
|
this.searchText = findViewById(R.id.search_view);
|
||||||
this.toggle = ViewUtil.findById(this, R.id.button_toggle);
|
this.toggle = findViewById(R.id.button_toggle);
|
||||||
this.keyboardToggle = ViewUtil.findById(this, R.id.search_keyboard);
|
this.keyboardToggle = findViewById(R.id.search_keyboard);
|
||||||
this.dialpadToggle = ViewUtil.findById(this, R.id.search_dialpad);
|
this.dialpadToggle = findViewById(R.id.search_dialpad);
|
||||||
this.clearToggle = ViewUtil.findById(this, R.id.search_clear);
|
this.clearToggle = findViewById(R.id.search_clear);
|
||||||
this.toggleContainer = ViewUtil.findById(this, R.id.toggle_container);
|
this.toggleContainer = findViewById(R.id.toggle_container);
|
||||||
|
|
||||||
this.keyboardToggle.setOnClickListener(new View.OnClickListener() {
|
this.keyboardToggle.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -103,11 +101,11 @@ public class ContactFilterToolbar extends DarkOverflowToolbar {
|
||||||
setLogo(null);
|
setLogo(null);
|
||||||
setContentInsetStartWithNavigation(0);
|
setContentInsetStartWithNavigation(0);
|
||||||
expandTapArea(toggleContainer, dialpadToggle);
|
expandTapArea(toggleContainer, dialpadToggle);
|
||||||
styleSearchText(searchText, context, attrs, defStyleAttr);
|
applyAttributes(searchText, context, attrs, defStyleAttr);
|
||||||
searchText.requestFocus();
|
searchText.requestFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void styleSearchText(@NonNull EditText searchText,
|
private void applyAttributes(@NonNull EditText searchText,
|
||||||
@NonNull Context context,
|
@NonNull Context context,
|
||||||
@NonNull AttributeSet attrs,
|
@NonNull AttributeSet attrs,
|
||||||
int defStyle)
|
int defStyle)
|
||||||
|
@ -121,6 +119,9 @@ public class ContactFilterToolbar extends DarkOverflowToolbar {
|
||||||
if (styleResource != -1) {
|
if (styleResource != -1) {
|
||||||
TextViewCompat.setTextAppearance(searchText, styleResource);
|
TextViewCompat.setTextAppearance(searchText, styleResource);
|
||||||
}
|
}
|
||||||
|
if (!attributes.getBoolean(R.styleable.ContactFilterToolbar_showDialpad, true)) {
|
||||||
|
dialpadToggle.setVisibility(GONE);
|
||||||
|
}
|
||||||
attributes.recycle();
|
attributes.recycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,6 +134,10 @@ public class ContactFilterToolbar extends DarkOverflowToolbar {
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setHint(@StringRes int hint) {
|
||||||
|
searchText.setHint(hint);
|
||||||
|
}
|
||||||
|
|
||||||
private void notifyListener() {
|
private void notifyListener() {
|
||||||
if (listener != null) listener.onFilterChanged(searchText.getText().toString());
|
if (listener != null) listener.onFilterChanged(searchText.getText().toString());
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,7 +101,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||||
super(itemView);
|
super(itemView);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, int color, boolean multiSelect);
|
public abstract void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, int color, boolean checkboxVisible);
|
||||||
public abstract void unbind(@NonNull GlideRequests glideRequests);
|
public abstract void unbind(@NonNull GlideRequests glideRequests);
|
||||||
public abstract void setChecked(boolean checked);
|
public abstract void setChecked(boolean checked);
|
||||||
public abstract void setEnabled(boolean enabled);
|
public abstract void setEnabled(boolean enabled);
|
||||||
|
@ -121,8 +121,8 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||||
return (ContactSelectionListItem) itemView;
|
return (ContactSelectionListItem) itemView;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, int color, boolean multiSelect) {
|
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, int color, boolean checkBoxVisible) {
|
||||||
getView().set(glideRequests, recipientId, type, name, number, label, color, multiSelect);
|
getView().set(glideRequests, recipientId, type, name, number, label, color, checkBoxVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -151,7 +151,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, int color, boolean multiSelect) {
|
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, int color, boolean checkboxVisible) {
|
||||||
this.label.setText(name);
|
this.label.setText(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,11 +222,13 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||||
int color = (contactType == ContactRepository.PUSH_TYPE) ? drawables.getColor(0, 0xa0000000) :
|
int color = (contactType == ContactRepository.PUSH_TYPE) ? drawables.getColor(0, 0xa0000000) :
|
||||||
drawables.getColor(1, 0xff000000);
|
drawables.getColor(1, 0xff000000);
|
||||||
|
|
||||||
|
boolean currentContact = currentContacts.contains(id);
|
||||||
|
|
||||||
viewHolder.unbind(glideRequests);
|
viewHolder.unbind(glideRequests);
|
||||||
viewHolder.bind(glideRequests, id, contactType, name, number, labelText, color, multiSelect);
|
viewHolder.bind(glideRequests, id, contactType, name, number, labelText, color, multiSelect || currentContact);
|
||||||
viewHolder.setEnabled(true);
|
viewHolder.setEnabled(true);
|
||||||
|
|
||||||
if (currentContacts.contains(id)) {
|
if (currentContact) {
|
||||||
viewHolder.setChecked(true);
|
viewHolder.setChecked(true);
|
||||||
viewHolder.setEnabled(false);
|
viewHolder.setEnabled(false);
|
||||||
} else if (numberType == ContactRepository.NEW_USERNAME_TYPE) {
|
} else if (numberType == ContactRepository.NEW_USERNAME_TYPE) {
|
||||||
|
|
|
@ -67,7 +67,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||||
String number,
|
String number,
|
||||||
String label,
|
String label,
|
||||||
int color,
|
int color,
|
||||||
boolean multiSelect)
|
boolean checkboxVisible)
|
||||||
{
|
{
|
||||||
this.glideRequests = glideRequests;
|
this.glideRequests = glideRequests;
|
||||||
this.number = number;
|
this.number = number;
|
||||||
|
@ -90,8 +90,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||||
|
|
||||||
setText(recipientSnapshot, type, name, number, label);
|
setText(recipientSnapshot, type, name, number, label);
|
||||||
|
|
||||||
if (multiSelect) this.checkBox.setVisibility(View.VISIBLE);
|
this.checkBox.setVisibility(checkboxVisible ? View.VISIBLE : View.GONE);
|
||||||
else this.checkBox.setVisibility(View.GONE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setChecked(boolean selected) {
|
public void setChecked(boolean selected) {
|
||||||
|
|
|
@ -115,8 +115,12 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||||
private List<Cursor> getUnfilteredResults() {
|
private List<Cursor> getUnfilteredResults() {
|
||||||
ArrayList<Cursor> cursorList = new ArrayList<>();
|
ArrayList<Cursor> cursorList = new ArrayList<>();
|
||||||
|
|
||||||
addRecentsSection(cursorList);
|
if (groupsOnly(mode)) {
|
||||||
addContactsSection(cursorList);
|
addGroupsSection(cursorList);
|
||||||
|
} else {
|
||||||
|
addRecentsSection(cursorList);
|
||||||
|
addContactsSection(cursorList);
|
||||||
|
}
|
||||||
|
|
||||||
return cursorList;
|
return cursorList;
|
||||||
}
|
}
|
||||||
|
@ -376,6 +380,10 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||||
return flagSet(mode, DisplayMode.FLAG_ACTIVE_GROUPS);
|
return flagSet(mode, DisplayMode.FLAG_ACTIVE_GROUPS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean groupsOnly(int mode) {
|
||||||
|
return mode == DisplayMode.FLAG_ACTIVE_GROUPS;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean flagSet(int mode, int flag) {
|
private static boolean flagSet(int mode, int flag) {
|
||||||
return (mode & flag) > 0;
|
return (mode & flag) > 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
package org.thoughtcrime.securesms.groups.ui.addtogroup;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupChangeFailedException;
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupId;
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupInsufficientRightsException;
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupManager;
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupNotAMemberException;
|
||||||
|
import org.thoughtcrime.securesms.groups.MembershipNotSuitableForV2Exception;
|
||||||
|
import org.thoughtcrime.securesms.groups.ui.GroupChangeErrorCallback;
|
||||||
|
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
final class AddToGroupRepository {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(AddToGroupRepository.class);
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
AddToGroupRepository() {
|
||||||
|
this.context = ApplicationDependencies.getApplication();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(@NonNull RecipientId recipientId,
|
||||||
|
@NonNull Recipient groupRecipient,
|
||||||
|
@NonNull GroupChangeErrorCallback error,
|
||||||
|
@NonNull Runnable success)
|
||||||
|
{
|
||||||
|
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||||
|
try {
|
||||||
|
GroupId.Push pushGroupId = groupRecipient.requireGroupId().requirePush();
|
||||||
|
|
||||||
|
GroupManager.addMembers(context, pushGroupId, Collections.singletonList(recipientId));
|
||||||
|
|
||||||
|
success.run();
|
||||||
|
} catch (GroupInsufficientRightsException | GroupNotAMemberException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
error.onError(GroupChangeFailureReason.NO_RIGHTS);
|
||||||
|
} catch (GroupChangeFailedException | GroupChangeBusyException | IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
error.onError(GroupChangeFailureReason.OTHER);
|
||||||
|
} catch (MembershipNotSuitableForV2Exception e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
error.onError(GroupChangeFailureReason.NOT_CAPABLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
package org.thoughtcrime.securesms.groups.ui.addtogroup;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.groups.ui.GroupErrors;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public final class AddToGroupViewModel extends ViewModel {
|
||||||
|
|
||||||
|
private final Application context;
|
||||||
|
private final AddToGroupRepository repository;
|
||||||
|
private final RecipientId recipientId;
|
||||||
|
private final SingleLiveEvent<Event> events = new SingleLiveEvent<>();
|
||||||
|
|
||||||
|
private AddToGroupViewModel(@NonNull RecipientId recipientId) {
|
||||||
|
this.context = ApplicationDependencies.getApplication();
|
||||||
|
this.recipientId = recipientId;
|
||||||
|
this.repository = new AddToGroupRepository();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SingleLiveEvent<Event> getEvents() {
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onContinueWithSelection(@NonNull List<RecipientId> groupRecipientIds) {
|
||||||
|
if (groupRecipientIds.isEmpty()) {
|
||||||
|
events.postValue(new Event.CloseEvent());
|
||||||
|
} else if (groupRecipientIds.size() == 1) {
|
||||||
|
SignalExecutors.BOUNDED.execute(() -> {
|
||||||
|
Recipient groupRecipient = Recipient.resolved(groupRecipientIds.get(0));
|
||||||
|
String recipientName = Recipient.resolved(recipientId).getDisplayName(context);
|
||||||
|
String groupName = groupRecipient.getDisplayName(context);
|
||||||
|
|
||||||
|
events.postValue(new Event.AddToSingleGroupConfirmationEvent(context.getResources().getString(R.string.AddToGroupActivity_add_member),
|
||||||
|
context.getResources().getString(R.string.AddToGroupActivity_add_s_to_s, recipientName, groupName),
|
||||||
|
groupRecipient, recipientName, groupName));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new AssertionError("Does not support multi-select");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAddToGroupsConfirmed(@NonNull Event.AddToSingleGroupConfirmationEvent event) {
|
||||||
|
repository.add(recipientId,
|
||||||
|
event.groupRecipient,
|
||||||
|
error -> events.postValue(new Event.ToastEvent(context.getResources().getString(GroupErrors.getUserDisplayMessage(error)))),
|
||||||
|
() -> {
|
||||||
|
events.postValue(new Event.ToastEvent(context.getResources().getString(R.string.AddToGroupActivity_s_added_to_s, event.recipientName, event.groupName)));
|
||||||
|
events.postValue(new Event.CloseEvent());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static abstract class Event {
|
||||||
|
|
||||||
|
static class CloseEvent extends Event {
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ToastEvent extends Event {
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
ToastEvent(@NonNull String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class AddToSingleGroupConfirmationEvent extends Event {
|
||||||
|
private final String title;
|
||||||
|
private final String message;
|
||||||
|
private final Recipient groupRecipient;
|
||||||
|
private final String recipientName;
|
||||||
|
private final String groupName;
|
||||||
|
|
||||||
|
AddToSingleGroupConfirmationEvent(@NonNull String title,
|
||||||
|
@NonNull String message,
|
||||||
|
@NonNull Recipient groupRecipient,
|
||||||
|
@NonNull String recipientName,
|
||||||
|
@NonNull String groupName)
|
||||||
|
{
|
||||||
|
this.title = title;
|
||||||
|
this.message = message;
|
||||||
|
this.groupRecipient = groupRecipient;
|
||||||
|
this.recipientName = recipientName;
|
||||||
|
this.groupName = groupName;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Factory implements ViewModelProvider.Factory {
|
||||||
|
|
||||||
|
private final RecipientId recipientId;
|
||||||
|
|
||||||
|
public Factory(@NonNull RecipientId recipientId) {
|
||||||
|
this.recipientId = recipientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||||
|
return Objects.requireNonNull(modelClass.cast(new AddToGroupViewModel(recipientId)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
package org.thoughtcrime.securesms.groups.ui.addtogroup;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.lifecycle.ViewModelProviders;
|
||||||
|
|
||||||
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.ContactSelectionActivity;
|
||||||
|
import org.thoughtcrime.securesms.ContactSelectionListFragment;
|
||||||
|
import org.thoughtcrime.securesms.GroupCreateActivity;
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
|
||||||
|
import org.thoughtcrime.securesms.groups.ui.addtogroup.AddToGroupViewModel.Event;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group selection activity, will add a single member to selected groups.
|
||||||
|
*/
|
||||||
|
public final class AddToGroupsActivity extends ContactSelectionActivity {
|
||||||
|
|
||||||
|
private static final int MINIMUM_GROUP_SELECT_SIZE = 1;
|
||||||
|
|
||||||
|
private static final String EXTRA_RECIPIENT_ID = "RECIPIENT_ID";
|
||||||
|
|
||||||
|
private View next;
|
||||||
|
private AddToGroupViewModel viewModel;
|
||||||
|
|
||||||
|
public static Intent newIntent(@NonNull Context context,
|
||||||
|
@NonNull RecipientId recipientId,
|
||||||
|
@NonNull List<RecipientId> currentGroupsMemberOf)
|
||||||
|
{
|
||||||
|
if (!FeatureFlags.newGroupUI()) {
|
||||||
|
return new Intent(context, GroupCreateActivity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent(context, AddToGroupsActivity.class);
|
||||||
|
|
||||||
|
intent.putExtra(ContactSelectionListFragment.MULTI_SELECT, false);
|
||||||
|
intent.putExtra(ContactSelectionListFragment.REFRESHABLE, false);
|
||||||
|
intent.putExtra(ContactSelectionActivity.EXTRA_LAYOUT_RES_ID, R.layout.add_to_group_activity);
|
||||||
|
intent.putExtra(EXTRA_RECIPIENT_ID, recipientId);
|
||||||
|
|
||||||
|
intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, ContactsCursorLoader.DisplayMode.FLAG_ACTIVE_GROUPS);
|
||||||
|
intent.putExtra(ContactSelectionListFragment.TOTAL_CAPACITY, ContactSelectionListFragment.NO_LIMIT);
|
||||||
|
|
||||||
|
intent.putParcelableArrayListExtra(ContactSelectionListFragment.CURRENT_SELECTION, new ArrayList<>(currentGroupsMemberOf));
|
||||||
|
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle bundle, boolean ready) {
|
||||||
|
super.onCreate(bundle, ready);
|
||||||
|
|
||||||
|
Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
||||||
|
next = findViewById(R.id.next);
|
||||||
|
|
||||||
|
getToolbar().setHint(contactsFragment.isMulti() ? R.string.AddToGroupActivity_add_to_groups : R.string.AddToGroupActivity_add_to_group);
|
||||||
|
|
||||||
|
next.setVisibility(contactsFragment.isMulti() ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
disableNext();
|
||||||
|
next.setOnClickListener(v -> handleNextPressed());
|
||||||
|
|
||||||
|
AddToGroupViewModel.Factory factory = new AddToGroupViewModel.Factory(getRecipientId());
|
||||||
|
viewModel = ViewModelProviders.of(this, factory)
|
||||||
|
.get(AddToGroupViewModel.class);
|
||||||
|
|
||||||
|
|
||||||
|
viewModel.getEvents().observe(this, event -> {
|
||||||
|
if (event instanceof Event.CloseEvent) {
|
||||||
|
finish();
|
||||||
|
} else if (event instanceof Event.ToastEvent) {
|
||||||
|
Toast.makeText(this, ((Event.ToastEvent) event).getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
|
} else if (event instanceof Event.AddToSingleGroupConfirmationEvent) {
|
||||||
|
Event.AddToSingleGroupConfirmationEvent addEvent = (Event.AddToSingleGroupConfirmationEvent) event;
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setTitle(addEvent.getTitle())
|
||||||
|
.setMessage(addEvent.getMessage())
|
||||||
|
.setPositiveButton(android.R.string.ok, (dialog, which) -> viewModel.onAddToGroupsConfirmed(addEvent))
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show();
|
||||||
|
} else {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NonNull RecipientId getRecipientId() {
|
||||||
|
return getIntent().getParcelableExtra(EXTRA_RECIPIENT_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (item.getItemId() == android.R.id.home) {
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onContactSelected(Optional<RecipientId> recipientId, String number) {
|
||||||
|
if (contactsFragment.isMulti()) {
|
||||||
|
if (contactsFragment.hasQueryFilter()) {
|
||||||
|
getToolbar().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contactsFragment.getSelectedContactsCount() >= MINIMUM_GROUP_SELECT_SIZE) {
|
||||||
|
enableNext();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (recipientId.isPresent()) {
|
||||||
|
viewModel.onContinueWithSelection(Collections.singletonList(recipientId.get()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onContactDeselected(Optional<RecipientId> recipientId, String number) {
|
||||||
|
if (contactsFragment.hasQueryFilter()) {
|
||||||
|
getToolbar().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contactsFragment.getSelectedContactsCount() < MINIMUM_GROUP_SELECT_SIZE) {
|
||||||
|
disableNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableNext() {
|
||||||
|
next.setEnabled(true);
|
||||||
|
next.animate().alpha(1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disableNext() {
|
||||||
|
next.setEnabled(false);
|
||||||
|
next.animate().alpha(0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleNextPressed() {
|
||||||
|
List<RecipientId> groupsRecipientIds = Stream.of(contactsFragment.getSelectedContacts())
|
||||||
|
.map(selectedContact -> selectedContact.getOrCreateRecipientId(this))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
viewModel.onContinueWithSelection(groupsRecipientIds);
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,6 +50,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
|
||||||
private Button blockButton;
|
private Button blockButton;
|
||||||
private Button unblockButton;
|
private Button unblockButton;
|
||||||
private Button addContactButton;
|
private Button addContactButton;
|
||||||
|
private Button addToGroupButton;
|
||||||
private Button viewSafetyNumberButton;
|
private Button viewSafetyNumberButton;
|
||||||
private Button makeGroupAdminButton;
|
private Button makeGroupAdminButton;
|
||||||
private Button removeAdminButton;
|
private Button removeAdminButton;
|
||||||
|
@ -93,6 +94,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
|
||||||
blockButton = view.findViewById(R.id.rbs_block_button);
|
blockButton = view.findViewById(R.id.rbs_block_button);
|
||||||
unblockButton = view.findViewById(R.id.rbs_unblock_button);
|
unblockButton = view.findViewById(R.id.rbs_unblock_button);
|
||||||
addContactButton = view.findViewById(R.id.rbs_add_contact_button);
|
addContactButton = view.findViewById(R.id.rbs_add_contact_button);
|
||||||
|
addToGroupButton = view.findViewById(R.id.rbs_add_to_group_button);
|
||||||
viewSafetyNumberButton = view.findViewById(R.id.rbs_view_safety_number_button);
|
viewSafetyNumberButton = view.findViewById(R.id.rbs_view_safety_number_button);
|
||||||
makeGroupAdminButton = view.findViewById(R.id.rbs_make_group_admin_button);
|
makeGroupAdminButton = view.findViewById(R.id.rbs_make_group_admin_button);
|
||||||
removeAdminButton = view.findViewById(R.id.rbs_remove_group_admin_button);
|
removeAdminButton = view.findViewById(R.id.rbs_remove_group_admin_button);
|
||||||
|
@ -188,6 +190,11 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
|
||||||
|
|
||||||
removeFromGroupButton.setOnClickListener(view -> viewModel.onRemoveFromGroupClicked(requireActivity(), this::dismiss));
|
removeFromGroupButton.setOnClickListener(view -> viewModel.onRemoveFromGroupClicked(requireActivity(), this::dismiss));
|
||||||
|
|
||||||
|
addToGroupButton.setOnClickListener(view -> {
|
||||||
|
dismiss();
|
||||||
|
viewModel.onAddToGroupButton(requireActivity());
|
||||||
|
});
|
||||||
|
|
||||||
viewModel.getAdminActionBusy().observe(getViewLifecycleOwner(), busy -> {
|
viewModel.getAdminActionBusy().observe(getViewLifecycleOwner(), busy -> {
|
||||||
adminActionBusy.setVisibility(busy ? View.VISIBLE : View.GONE);
|
adminActionBusy.setVisibility(busy ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import androidx.core.util.Consumer;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||||
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
|
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
|
||||||
import org.thoughtcrime.securesms.groups.GroupChangeFailedException;
|
import org.thoughtcrime.securesms.groups.GroupChangeFailedException;
|
||||||
|
@ -24,6 +25,8 @@ import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
final class RecipientDialogRepository {
|
final class RecipientDialogRepository {
|
||||||
|
@ -117,6 +120,22 @@ final class RecipientDialogRepository {
|
||||||
onComplete::accept);
|
onComplete::accept);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void getGroupMembership(@NonNull Consumer<List<RecipientId>> onComplete) {
|
||||||
|
SimpleTask.run(SignalExecutors.UNBOUNDED,
|
||||||
|
() -> {
|
||||||
|
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||||
|
List<GroupDatabase.GroupRecord> groupRecords = groupDatabase.getPushGroupsContainingMember(recipientId);
|
||||||
|
ArrayList<RecipientId> groupRecipients = new ArrayList<>(groupRecords.size());
|
||||||
|
|
||||||
|
for (GroupDatabase.GroupRecord groupRecord : groupRecords) {
|
||||||
|
groupRecipients.add(groupRecord.getRecipientId());
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupRecipients;
|
||||||
|
},
|
||||||
|
onComplete::accept);
|
||||||
|
}
|
||||||
|
|
||||||
interface IdentityCallback {
|
interface IdentityCallback {
|
||||||
void remoteIdentity(@Nullable IdentityDatabase.IdentityRecord identityRecord);
|
void remoteIdentity(@Nullable IdentityDatabase.IdentityRecord identityRecord);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.lifecycle.Transformations;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.ViewModel;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
@ -24,6 +23,7 @@ import org.thoughtcrime.securesms.groups.GroupId;
|
||||||
import org.thoughtcrime.securesms.groups.LiveGroup;
|
import org.thoughtcrime.securesms.groups.LiveGroup;
|
||||||
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
|
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
|
||||||
import org.thoughtcrime.securesms.groups.ui.GroupErrors;
|
import org.thoughtcrime.securesms.groups.ui.GroupErrors;
|
||||||
|
import org.thoughtcrime.securesms.groups.ui.addtogroup.AddToGroupsActivity;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||||
|
@ -171,6 +171,10 @@ final class RecipientDialogViewModel extends ViewModel {
|
||||||
recipientDialogRepository.refreshRecipient();
|
recipientDialogRepository.refreshRecipient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onAddToGroupButton(@NonNull Activity activity) {
|
||||||
|
recipientDialogRepository.getGroupMembership(existingGroups -> activity.startActivity(AddToGroupsActivity.newIntent(activity, recipientDialogRepository.getRecipientId(), existingGroups)));
|
||||||
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private void showErrorToast(@NonNull GroupChangeFailureReason e) {
|
private void showErrorToast(@NonNull GroupChangeFailureReason e) {
|
||||||
Util.runOnMain(() -> Toast.makeText(context, GroupErrors.getUserDisplayMessage(e), Toast.LENGTH_LONG).show());
|
Util.runOnMain(() -> Toast.makeText(context, GroupErrors.getUserDisplayMessage(e), Toast.LENGTH_LONG).show());
|
||||||
|
|
46
app/src/main/res/layout/add_to_group_activity.xml
Normal file
46
app/src/main/res/layout/add_to_group_activity.xml
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout 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="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.ContactFilterToolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:elevation="4dp"
|
||||||
|
android:minHeight="?attr/actionBarSize"
|
||||||
|
android:theme="?attr/actionBarStyle"
|
||||||
|
app:contentInsetStartWithNavigation="0dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:navigationIcon="@drawable/ic_arrow_left_24"
|
||||||
|
app:showDialpad="false" />
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/contact_selection_list_fragment"
|
||||||
|
android:name="org.thoughtcrime.securesms.ContactSelectionListFragment"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/toolbar" />
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/next"
|
||||||
|
android:layout_width="56dp"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:tint="@color/core_white"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:backgroundTint="@color/core_ultramarine"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:srcCompat="@drawable/ic_arrow_end_24"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -119,6 +119,16 @@
|
||||||
app:drawableStartCompat="?attr/recipient_add_contact_icon"
|
app:drawableStartCompat="?attr/recipient_add_contact_icon"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/rbs_add_to_group_button"
|
||||||
|
style="@style/Widget.Signal.Button.TextButton.Drawable"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:paddingStart="20dp"
|
||||||
|
android:paddingEnd="20dp"
|
||||||
|
android:text="@string/RecipientBottomSheet_add_to_a_group"
|
||||||
|
app:drawableStartCompat="?attr/recipient_make_admin_icon" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/rbs_view_safety_number_button"
|
android:id="@+id/rbs_view_safety_number_button"
|
||||||
style="@style/Widget.Signal.Button.TextButton.Drawable"
|
style="@style/Widget.Signal.Button.TextButton.Drawable"
|
||||||
|
|
|
@ -484,6 +484,7 @@
|
||||||
|
|
||||||
<declare-styleable name="ContactFilterToolbar">
|
<declare-styleable name="ContactFilterToolbar">
|
||||||
<attr name="searchTextStyle" format="reference" />
|
<attr name="searchTextStyle" format="reference" />
|
||||||
|
<attr name="showDialpad" format="boolean" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
<declare-styleable name="SquareImageView">
|
<declare-styleable name="SquareImageView">
|
||||||
|
|
|
@ -449,6 +449,13 @@
|
||||||
<string name="GroupCreateActivity_youre_already_in_the_group">You\'re already in the group.</string>
|
<string name="GroupCreateActivity_youre_already_in_the_group">You\'re already in the group.</string>
|
||||||
<string name="GroupCreateActivity_remove_member_description">Remove member</string>
|
<string name="GroupCreateActivity_remove_member_description">Remove member</string>
|
||||||
|
|
||||||
|
<!-- AddToGroupActivity -->
|
||||||
|
<string name="AddToGroupActivity_add_member">Add member?</string>
|
||||||
|
<string name="AddToGroupActivity_add_s_to_s">Add \"%1$s\" to \"%2$s\"?</string>
|
||||||
|
<string name="AddToGroupActivity_s_added_to_s">\"%1$s\" added to \"%2$s\".</string>
|
||||||
|
<string name="AddToGroupActivity_add_to_group">Add to group</string>
|
||||||
|
<string name="AddToGroupActivity_add_to_groups">Add to groups</string>
|
||||||
|
|
||||||
<!-- GroupShareProfileView -->
|
<!-- GroupShareProfileView -->
|
||||||
<string name="GroupShareProfileView_share_your_profile_name_and_photo_with_this_group">Share your profile name and photo with this group?</string>
|
<string name="GroupShareProfileView_share_your_profile_name_and_photo_with_this_group">Share your profile name and photo with this group?</string>
|
||||||
<string name="GroupShareProfileView_do_you_want_to_make_your_profile_name_and_photo_visible_to_all_current_and_future_members_of_this_group">Do you want to make your profile name and photo visible to all current and future members of this group?</string>
|
<string name="GroupShareProfileView_do_you_want_to_make_your_profile_name_and_photo_visible_to_all_current_and_future_members_of_this_group">Do you want to make your profile name and photo visible to all current and future members of this group?</string>
|
||||||
|
@ -2303,6 +2310,7 @@
|
||||||
<string name="RecipientBottomSheet_block">Block</string>
|
<string name="RecipientBottomSheet_block">Block</string>
|
||||||
<string name="RecipientBottomSheet_unblock">Unblock</string>
|
<string name="RecipientBottomSheet_unblock">Unblock</string>
|
||||||
<string name="RecipientBottomSheet_add_to_contacts">Add to contacts</string>
|
<string name="RecipientBottomSheet_add_to_contacts">Add to contacts</string>
|
||||||
|
<string name="RecipientBottomSheet_add_to_a_group">Add to a group</string>
|
||||||
<string name="RecipientBottomSheet_view_safety_number">View safety number</string>
|
<string name="RecipientBottomSheet_view_safety_number">View safety number</string>
|
||||||
<string name="RecipientBottomSheet_make_group_admin">Make group admin</string>
|
<string name="RecipientBottomSheet_make_group_admin">Make group admin</string>
|
||||||
<string name="RecipientBottomSheet_remove_as_admin">Remove as admin</string>
|
<string name="RecipientBottomSheet_remove_as_admin">Remove as admin</string>
|
||||||
|
|
Loading…
Add table
Reference in a new issue