Show channels in a proper "Channel" chat (#3783)

* Rename "broadcast list" to "channel"/"broadcast channel" both in UI and code

* feat: Add new channel types

* Update CHANGELOG.md

* adb's review

* refactor: Rename BroadcastChannel to Broadcast

* Revert accidental change

* Make it possible to leave channels

- In a chat, if the chat is an InBroadcast, and it's not a contact
  request, then the `Leave` menu option is shown with the translated
  stock string `menu_leave_channel` as its label.
- If the user clicks on it, the confirmation dialog has
  `menu_leave_channel` (rather than `menu_leave_group`) as its positive
  option.

Counterpart of https://github.com/chatmail/core/pull/6984.

---------

Co-authored-by: adbenitez <asieldbenitez@gmail.com>
Co-authored-by: adb <adb@merlinux.eu>
This commit is contained in:
Hocuri 2025-07-10 10:32:04 +02:00 committed by GitHub
parent 23d521beed
commit 717777f628
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 102 additions and 55 deletions

View file

@ -10,6 +10,8 @@
* Improve hint for app drafts * Improve hint for app drafts
* Add Text-To-Speech (TTS) support for in-chat apps * Add Text-To-Speech (TTS) support for in-chat apps
* New icon for the QR icon * New icon for the QR icon
* Start rebuilding the experimental broadcast lists
into proper channels - note that this is work-in-progress
* Improved separation between unencryted chats/contacts and encrypted ones, avoiding mixing of encrypted and unencrypted messages in the same chat * Improved separation between unencryted chats/contacts and encrypted ones, avoiding mixing of encrypted and unencrypted messages in the same chat
* Removed padlocks, as encrypted is the default "normal" state. Instead, unencrypted email is marked with a small email / letter (✉️) icon * Removed padlocks, as encrypted is the default "normal" state. Instead, unencrypted email is marked with a small email / letter (✉️) icon
* Classic email chats/threads get a big email / letter icon making it easy to recognize * Classic email chats/threads get a big email / letter icon making it easy to recognize

View file

@ -6,7 +6,8 @@ public class DcChat {
public static final int DC_CHAT_TYPE_SINGLE = 100; public static final int DC_CHAT_TYPE_SINGLE = 100;
public static final int DC_CHAT_TYPE_GROUP = 120; public static final int DC_CHAT_TYPE_GROUP = 120;
public static final int DC_CHAT_TYPE_MAILINGLIST = 140; public static final int DC_CHAT_TYPE_MAILINGLIST = 140;
public static final int DC_CHAT_TYPE_BROADCAST = 160; public static final int DC_CHAT_TYPE_OUT_BROADCAST = 160;
public static final int DC_CHAT_TYPE_IN_BROADCAST = 165;
public static final int DC_CHAT_NO_CHAT = 0; public static final int DC_CHAT_NO_CHAT = 0;
public final static int DC_CHAT_ID_ARCHIVED_LINK = 6; public final static int DC_CHAT_ID_ARCHIVED_LINK = 6;
@ -54,15 +55,18 @@ public class DcChat {
public boolean isMultiUser() { public boolean isMultiUser() {
int type = getType(); int type = getType();
return type == DC_CHAT_TYPE_GROUP || type == DC_CHAT_TYPE_MAILINGLIST || type == DC_CHAT_TYPE_BROADCAST; return type != DC_CHAT_TYPE_SINGLE;
} }
public boolean isMailingList() { public boolean isMailingList() {
return getType() == DC_CHAT_TYPE_MAILINGLIST; return getType() == DC_CHAT_TYPE_MAILINGLIST;
} }
public boolean isBroadcast() { public boolean isInBroadcast() {
return getType() == DC_CHAT_TYPE_BROADCAST; return getType() == DC_CHAT_TYPE_IN_BROADCAST;
}
public boolean isOutBroadcast() {
return getType() == DC_CHAT_TYPE_OUT_BROADCAST;
} }
public boolean isHalfBlocked() { public boolean isHalfBlocked() {

View file

@ -10,7 +10,7 @@ public class DcContact {
public final static int DC_CONTACT_ID_NEW_GROUP = -2; // - " - public final static int DC_CONTACT_ID_NEW_GROUP = -2; // - " -
public final static int DC_CONTACT_ID_ADD_MEMBER = -3; // - " - public final static int DC_CONTACT_ID_ADD_MEMBER = -3; // - " -
public final static int DC_CONTACT_ID_QR_INVITE = -4; // - " - public final static int DC_CONTACT_ID_QR_INVITE = -4; // - " -
public final static int DC_CONTACT_ID_NEW_BROADCAST_LIST = -5; // - " - public final static int DC_CONTACT_ID_NEW_BROADCAST = -5; // - " -
public final static int DC_CONTACT_ID_ADD_ACCOUNT = -6; // - " - public final static int DC_CONTACT_ID_ADD_ACCOUNT = -6; // - " -
public DcContact(long contactCPtr) { public DcContact(long contactCPtr) {

View file

@ -160,6 +160,10 @@ public class Rpc {
getResult("add_or_update_transport", accountId, param); getResult("add_or_update_transport", accountId, param);
} }
public int createBroadcast(int accountId, String chatName) throws RpcException {
return gson.fromJson(getResult("create_broadcast", accountId, chatName), Integer.class);
}
private static class Request { private static class Request {
private final String jsonrpc = "2.0"; private final String jsonrpc = "2.0";
public final String method; public final String method;

View file

@ -59,6 +59,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
@ -431,7 +432,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
getMenuInflater().inflate(R.menu.conversation, menu); getMenuInflater().inflate(R.menu.conversation, menu);
if (dcChat.isSelfTalk() || dcChat.isBroadcast()) { if (dcChat.isSelfTalk() || dcChat.isOutBroadcast()) {
menu.findItem(R.id.menu_mute_notifications).setVisible(false); menu.findItem(R.id.menu_mute_notifications).setVisible(false);
} else if(dcChat.isMuted()) { } else if(dcChat.isMuted()) {
menu.findItem(R.id.menu_mute_notifications).setTitle(R.string.menu_unmute); menu.findItem(R.id.menu_mute_notifications).setTitle(R.string.menu_unmute);
@ -441,14 +442,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
menu.findItem(R.id.menu_show_map).setVisible(false); menu.findItem(R.id.menu_show_map).setVisible(false);
} }
if (!dcChat.canSend() || dcChat.isBroadcast() || dcChat.isMailingList()) { if (!dcChat.canSend() || dcChat.isMailingList() ) {
menu.findItem(R.id.menu_ephemeral_messages).setVisible(false); menu.findItem(R.id.menu_ephemeral_messages).setVisible(false);
} }
if (isMultiUser()) { if (isMultiUser()) {
if (dcChat.isEncrypted() if (dcChat.isInBroadcast() && !dcChat.isContactRequest()) {
menu.findItem(R.id.menu_leave).setTitle(R.string.menu_leave_channel).setVisible(true);
} else if (dcChat.isEncrypted()
&& dcChat.canSend() && dcChat.canSend()
&& !dcChat.isBroadcast() && !dcChat.isOutBroadcast()
&& !dcChat.isMailingList()) { && !dcChat.isMailingList()) {
menu.findItem(R.id.menu_leave).setVisible(true); menu.findItem(R.id.menu_leave).setVisible(true);
} }
@ -625,9 +628,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
private void handleLeaveGroup() { private void handleLeaveGroup() {
@StringRes int leaveLabel;
if (dcChat.isInBroadcast()) {
leaveLabel = R.string.menu_leave_channel;
} else {
leaveLabel = R.string.menu_leave_group;
}
AlertDialog dialog = new AlertDialog.Builder(this) AlertDialog dialog = new AlertDialog.Builder(this)
.setMessage(getString(R.string.ask_leave_group)) .setMessage(getString(R.string.ask_leave_group))
.setPositiveButton(R.string.menu_leave_group, (d, which) -> { .setPositiveButton(leaveLabel, (d, which) -> {
dcContext.removeContactFromChat(chatId, DcContact.DC_CONTACT_ID_SELF); dcContext.removeContactFromChat(chatId, DcContact.DC_CONTACT_ID_SELF);
Toast.makeText(this, getString(R.string.done), Toast.LENGTH_SHORT).show(); Toast.makeText(this, getString(R.string.done), Toast.LENGTH_SHORT).show();
}) })

View file

@ -168,8 +168,8 @@ public class ConversationFragment extends MessageSelectorFragment
private void setNoMessageText() { private void setNoMessageText() {
DcChat dcChat = getListAdapter().getChat(); DcChat dcChat = getListAdapter().getChat();
if(dcChat.isMultiUser()){ if(dcChat.isMultiUser()){
if (dcChat.isBroadcast()) { if (dcChat.isInBroadcast() || dcChat.isOutBroadcast()) {
noMessageTextView.setText(R.string.chat_new_broadcast_hint); noMessageTextView.setText(R.string.chat_new_channel_hint);
} else if (dcChat.isUnpromoted()) { } else if (dcChat.isUnpromoted()) {
noMessageTextView.setText(R.string.chat_new_group_hint); noMessageTextView.setText(R.string.chat_new_group_hint);
} }

View file

@ -87,7 +87,9 @@ public class ConversationTitleView extends RelativeLayout {
} else { } else {
subtitleStr = context.getString(R.string.mailing_list); subtitleStr = context.getString(R.string.mailing_list);
} }
} else if (dcChat.isBroadcast()) { } else if (dcChat.isInBroadcast()) {
subtitleStr = context.getString(R.string.channel);
} else if (dcChat.isOutBroadcast()) {
if (!profileView) { if (!profileView) {
subtitleStr = context.getResources().getQuantityString(R.plurals.n_recipients, chatContacts.length, chatContacts.length); subtitleStr = context.getResources().getQuantityString(R.plurals.n_recipients, chatContacts.length, chatContacts.length);
} }

View file

@ -23,6 +23,7 @@ import androidx.loader.app.LoaderManager;
import com.b44t.messenger.DcChat; import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcContact; import com.b44t.messenger.DcContact;
import com.b44t.messenger.DcContext; import com.b44t.messenger.DcContext;
import com.b44t.messenger.rpc.RpcException;
import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.target.CustomTarget; import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition; import com.bumptech.glide.request.transition.Transition;
@ -48,7 +49,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
{ {
public static final String EDIT_GROUP_CHAT_ID = "edit_group_chat_id"; public static final String EDIT_GROUP_CHAT_ID = "edit_group_chat_id";
public static final String CREATE_BROADCAST = "group_create_broadcast"; public static final String CREATE_BROADCAST = "create_broadcast";
public static final String CLONE_CHAT_EXTRA = "clone_chat"; public static final String CLONE_CHAT_EXTRA = "clone_chat";
private static final int PICK_CONTACT = 1; private static final int PICK_CONTACT = 1;
@ -88,12 +89,12 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
isEdit = true; isEdit = true;
DcChat dcChat = dcContext.getChat(groupChatId); DcChat dcChat = dcContext.getChat(groupChatId);
verified = dcChat.isProtected(); verified = dcChat.isProtected();
broadcast = dcChat.isBroadcast(); broadcast = dcChat.isOutBroadcast();
} }
int chatId = getIntent().getIntExtra(CLONE_CHAT_EXTRA, 0); int chatId = getIntent().getIntExtra(CLONE_CHAT_EXTRA, 0);
if (chatId != 0) { if (chatId != 0) {
broadcast = dcContext.getChat(chatId).isBroadcast(); broadcast = dcContext.getChat(chatId).isOutBroadcast();
} }
initializeResources(); initializeResources();
@ -120,7 +121,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
title = getString(R.string.global_menu_edit_desktop); title = getString(R.string.global_menu_edit_desktop);
} }
else if(broadcast) { else if(broadcast) {
title = getString(R.string.new_broadcast_list); title = getString(R.string.new_channel);
} }
else { else {
title = getString(R.string.menu_new_group); title = getString(R.string.menu_new_group);
@ -160,9 +161,8 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
} }
if (broadcast) { if (broadcast) {
avatar.setVisibility(View.GONE); groupName.setHint(R.string.channel_name);
groupName.setHint(R.string.broadcast_list_name); chatHints.setVisibility(View.VISIBLE);
chatHints.setVisibility(isEdit()? View.GONE : View.VISIBLE);
} else { } else {
chatHints.setVisibility(View.GONE); chatHints.setVisibility(View.GONE);
} }
@ -257,8 +257,12 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
private void createGroup(String groupName) { private void createGroup(String groupName) {
if (broadcast) { if (broadcast) {
groupChatId = dcContext.createBroadcastList(); try {
dcContext.setChatName(groupChatId, groupName); groupChatId = DcHelper.getRpc(this).createBroadcast(dcContext.getAccountId(), groupName);
} catch (RpcException e) {
e.printStackTrace();
return;
}
} else { } else {
groupChatId = dcContext.createGroupChat(verified, groupName); groupChatId = dcContext.createGroupChat(verified, groupName);
} }

View file

@ -102,7 +102,7 @@ public class NewConversationActivity extends ContactSelectionActivity {
public void onContactSelected(int contactId) { public void onContactSelected(int contactId) {
if(contactId == DcContact.DC_CONTACT_ID_NEW_GROUP) { if(contactId == DcContact.DC_CONTACT_ID_NEW_GROUP) {
startActivity(new Intent(this, GroupCreateActivity.class)); startActivity(new Intent(this, GroupCreateActivity.class));
} else if(contactId == DcContact.DC_CONTACT_ID_NEW_BROADCAST_LIST) { } else if(contactId == DcContact.DC_CONTACT_ID_NEW_BROADCAST) {
Intent intent = new Intent(this, GroupCreateActivity.class); Intent intent = new Intent(this, GroupCreateActivity.class);
intent.putExtra(GroupCreateActivity.CREATE_BROADCAST, true); intent.putExtra(GroupCreateActivity.CREATE_BROADCAST, true);
startActivity(intent); startActivity(intent);

View file

@ -51,7 +51,8 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
private boolean chatIsMultiUser; private boolean chatIsMultiUser;
private boolean chatIsDeviceTalk; private boolean chatIsDeviceTalk;
private boolean chatIsMailingList; private boolean chatIsMailingList;
private boolean chatIsBroadcast; private boolean chatIsOutBroadcast;
private boolean chatIsInBroadcast;
private int contactId; private int contactId;
private boolean contactIsBot; private boolean contactIsBot;
private Toolbar toolbar; private Toolbar toolbar;
@ -76,8 +77,8 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
String title = getString(R.string.profile); String title = getString(R.string.profile);
if (chatIsMailingList) { if (chatIsMailingList) {
title = getString(R.string.mailing_list); title = getString(R.string.mailing_list);
} else if (chatIsBroadcast) { } else if (chatIsOutBroadcast || chatIsInBroadcast) {
title = getString(R.string.broadcast_list); title = getString(R.string.channel);
} else if (chatIsMultiUser) { } else if (chatIsMultiUser) {
title = getString(R.string.tab_group); title = getString(R.string.tab_group);
} else if (contactIsBot) { } else if (contactIsBot) {
@ -114,8 +115,7 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
menu.findItem(R.id.show_encr_info).setVisible(false); menu.findItem(R.id.show_encr_info).setVisible(false);
menu.findItem(R.id.share).setVisible(false); menu.findItem(R.id.share).setVisible(false);
} else if (chatIsMultiUser) { } else if (chatIsMultiUser) {
menu.findItem(R.id.edit_name).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); if (chatIsOutBroadcast) {
if (chatIsBroadcast) {
canReceive = false; canReceive = false;
} else { } else {
if (!dcChat.isEncrypted() if (!dcChat.isEncrypted()
@ -190,7 +190,8 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
chatIsMultiUser = false; chatIsMultiUser = false;
chatIsDeviceTalk = false; chatIsDeviceTalk = false;
chatIsMailingList= false; chatIsMailingList= false;
chatIsBroadcast = false; chatIsInBroadcast = false;
chatIsOutBroadcast = false;
if (contactId!=0) { if (contactId!=0) {
DcContact dcContact = dcContext.getContact(contactId); DcContact dcContact = dcContext.getContact(contactId);
@ -203,7 +204,8 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity
chatIsMultiUser = dcChat.isMultiUser(); chatIsMultiUser = dcChat.isMultiUser();
chatIsDeviceTalk = dcChat.isDeviceTalk(); chatIsDeviceTalk = dcChat.isDeviceTalk();
chatIsMailingList = dcChat.isMailingList(); chatIsMailingList = dcChat.isMailingList();
chatIsBroadcast = dcChat.isBroadcast(); chatIsInBroadcast = dcChat.isInBroadcast();
chatIsOutBroadcast = dcChat.isOutBroadcast();
if(!chatIsMultiUser) { if(!chatIsMultiUser) {
final int[] members = dcContext.getChatContacts(chatId); final int[] members = dcContext.getChatContacts(chatId);
contactId = members.length>=1? members[0] : 0; contactId = members.length>=1? members[0] : 0;

View file

@ -53,7 +53,7 @@ public class ProfileAdapter extends RecyclerView.Adapter
private final @NonNull ArrayList<ItemData> itemData = new ArrayList<>(); private final @NonNull ArrayList<ItemData> itemData = new ArrayList<>();
private DcChatlist itemDataSharedChats; private DcChatlist itemDataSharedChats;
private String itemDataStatusText; private String itemDataStatusText;
private boolean isBroadcast; private boolean isOutBroadcast;
private int memberCount; private int memberCount;
private final Set<Integer> selectedMembers; private final Set<Integer> selectedMembers;
@ -163,7 +163,7 @@ public class ProfileAdapter extends RecyclerView.Adapter
String addr = null; String addr = null;
if (contactId == DcContact.DC_CONTACT_ID_ADD_MEMBER) { if (contactId == DcContact.DC_CONTACT_ID_ADD_MEMBER) {
if (isBroadcast) { if (isOutBroadcast) {
name = context.getString(R.string.add_recipients); name = context.getString(R.string.add_recipients);
} else { } else {
name = context.getString(R.string.group_add_members); name = context.getString(R.string.group_add_members);
@ -275,7 +275,7 @@ public class ProfileAdapter extends RecyclerView.Adapter
itemData.clear(); itemData.clear();
itemDataSharedChats = sharedChats; itemDataSharedChats = sharedChats;
itemDataStatusText = ""; itemDataStatusText = "";
isBroadcast = dcChat != null && dcChat.isBroadcast(); isOutBroadcast = dcChat != null && dcChat.isOutBroadcast();
boolean isMailingList = dcChat != null && dcChat.isMailingList(); boolean isMailingList = dcChat != null && dcChat.isMailingList();
boolean isSelfTalk = dcChat != null && dcChat.isSelfTalk(); boolean isSelfTalk = dcChat != null && dcChat.isSelfTalk();
boolean isDeviceTalk = dcChat != null && dcChat.isDeviceTalk(); boolean isDeviceTalk = dcChat != null && dcChat.isDeviceTalk();
@ -313,7 +313,7 @@ public class ProfileAdapter extends RecyclerView.Adapter
if (dcChat != null) { if (dcChat != null) {
if (dcChat.canSend() && dcChat.isEncrypted()) { if (dcChat.canSend() && dcChat.isEncrypted()) {
itemData.add(new ItemData(ITEM_MEMBERS, DcContact.DC_CONTACT_ID_ADD_MEMBER, 0)); itemData.add(new ItemData(ITEM_MEMBERS, DcContact.DC_CONTACT_ID_ADD_MEMBER, 0));
if (!isBroadcast) { if (!isOutBroadcast) {
itemData.add(new ItemData(ITEM_MEMBERS, DcContact.DC_CONTACT_ID_QR_INVITE, 0)); itemData.add(new ItemData(ITEM_MEMBERS, DcContact.DC_CONTACT_ID_QR_INVITE, 0));
} }
} }

View file

@ -59,8 +59,10 @@ public class ProfileAvatarItem extends LinearLayout implements RecipientModified
if (dcChat.isMailingList()) { if (dcChat.isMailingList()) {
subtitle = dcChat.getMailinglistAddr(); subtitle = dcChat.getMailinglistAddr();
} else if (dcChat.isBroadcast()) { } else if (dcChat.isOutBroadcast()) {
subtitle = getContext().getResources().getQuantityString(R.plurals.n_recipients, memberCount, memberCount); subtitle = getContext().getResources().getQuantityString(R.plurals.n_recipients, memberCount, memberCount);
} else if (dcChat.isInBroadcast()) {
subtitle = getContext().getString(R.string.contact);
} else if (dcChat.getType() == DcChat.DC_CHAT_TYPE_GROUP) { } else if (dcChat.getType() == DcChat.DC_CHAT_TYPE_GROUP) {
subtitle = getContext().getResources().getQuantityString(R.plurals.n_members, memberCount, memberCount); subtitle = getContext().getResources().getQuantityString(R.plurals.n_members, memberCount, memberCount);
} }

View file

@ -294,7 +294,7 @@ public class ProfileFragment extends Fragment
mode.finish(); mode.finish();
}) })
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.setMessage(getString(dcChat.isBroadcast() ? R.string.ask_remove_from_broadcast : R.string.ask_remove_members, readableToDelList)) .setMessage(getString(dcChat.isOutBroadcast() ? R.string.ask_remove_from_channel : R.string.ask_remove_members, readableToDelList))
.show(); .show();
Util.redPositiveButton(dialog); Util.redPositiveButton(dialog);
return true; return true;

View file

@ -51,8 +51,8 @@ public class DcContactsLoader extends AsyncLoader<DcContactsLoader.Ret> {
} }
if (query == null && addCreateGroupLinks) { if (query == null && addCreateGroupLinks) {
additional_items = Util.appendInt(additional_items, DcContact.DC_CONTACT_ID_NEW_GROUP); additional_items = Util.appendInt(additional_items, DcContact.DC_CONTACT_ID_NEW_GROUP);
final boolean broadcastsEnabled = Prefs.isNewBroadcastListAvailable(getContext()); final boolean broadcastsEnabled = Prefs.isNewBroadcastAvailable(getContext());
if (broadcastsEnabled) additional_items = Util.appendInt(additional_items, DcContact.DC_CONTACT_ID_NEW_BROADCAST_LIST); if (broadcastsEnabled) additional_items = Util.appendInt(additional_items, DcContact.DC_CONTACT_ID_NEW_BROADCAST);
} }
int[] all_ids = new int[contact_ids.length + additional_items.length]; int[] all_ids = new int[contact_ids.length + additional_items.length];
System.arraycopy(additional_items, 0, all_ids, 0, additional_items.length); System.arraycopy(additional_items, 0, all_ids, 0, additional_items.length);

View file

@ -190,7 +190,7 @@ public class DcHelper {
dcContext.setStockTranslation(112, context.getString(R.string.error_x)); dcContext.setStockTranslation(112, context.getString(R.string.error_x));
dcContext.setStockTranslation(113, context.getString(R.string.not_supported_by_provider)); dcContext.setStockTranslation(113, context.getString(R.string.not_supported_by_provider));
dcContext.setStockTranslation(114, context.getString(R.string.messages)); dcContext.setStockTranslation(114, context.getString(R.string.messages));
dcContext.setStockTranslation(115, context.getString(R.string.broadcast_list)); dcContext.setStockTranslation(115, context.getString(R.string.channel));
dcContext.setStockTranslation(116, context.getString(R.string.part_of_total_used)); dcContext.setStockTranslation(116, context.getString(R.string.part_of_total_used));
dcContext.setStockTranslation(117, context.getString(R.string.secure_join_started)); dcContext.setStockTranslation(117, context.getString(R.string.secure_join_started));
dcContext.setStockTranslation(118, context.getString(R.string.secure_join_replies)); dcContext.setStockTranslation(118, context.getString(R.string.secure_join_replies));

View file

@ -268,8 +268,8 @@ public class ContactSelectionListAdapter extends RecyclerView.Adapter<ContactSel
itemMultiSelect = false; // the item creates a new contact in the list that will be selected instead itemMultiSelect = false; // the item creates a new contact in the list that will be selected instead
} else if (id == DcContact.DC_CONTACT_ID_NEW_GROUP) { } else if (id == DcContact.DC_CONTACT_ID_NEW_GROUP) {
name = context.getString(R.string.menu_new_group); name = context.getString(R.string.menu_new_group);
} else if (id == DcContact.DC_CONTACT_ID_NEW_BROADCAST_LIST) { } else if (id == DcContact.DC_CONTACT_ID_NEW_BROADCAST) {
name = context.getString(R.string.new_broadcast_list); name = context.getString(R.string.new_channel);
} else if (id == DcContact.DC_CONTACT_ID_QR_INVITE) { } else if (id == DcContact.DC_CONTACT_ID_QR_INVITE) {
name = context.getString(R.string.menu_new_contact); name = context.getString(R.string.menu_new_contact);
} else { } else {

View file

@ -65,9 +65,8 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientM
this.number = number; this.number = number;
if (specialId==DcContact.DC_CONTACT_ID_NEW_CLASSIC_CONTACT || specialId==DcContact.DC_CONTACT_ID_NEW_GROUP if (specialId==DcContact.DC_CONTACT_ID_NEW_CLASSIC_CONTACT || specialId==DcContact.DC_CONTACT_ID_NEW_GROUP
|| specialId==DcContact.DC_CONTACT_ID_NEW_BROADCAST_LIST || specialId==DcContact.DC_CONTACT_ID_NEW_BROADCAST
|| specialId==DcContact.DC_CONTACT_ID_ADD_MEMBER || specialId==DcContact.DC_CONTACT_ID_QR_INVITE) { || specialId==DcContact.DC_CONTACT_ID_ADD_MEMBER || specialId==DcContact.DC_CONTACT_ID_QR_INVITE) {
this.recipient = null;
this.nameView.setTypeface(null, Typeface.BOLD); this.nameView.setTypeface(null, Typeface.BOLD);
} }
else { else {

View file

@ -176,9 +176,8 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment
newBroadcastList.setOnPreferenceChangeListener((preference, newValue) -> { newBroadcastList.setOnPreferenceChangeListener((preference, newValue) -> {
if ((Boolean)newValue) { if ((Boolean)newValue) {
new AlertDialog.Builder(requireActivity()) new AlertDialog.Builder(requireActivity())
.setTitle("Thanks for trying out \"Broadcast Lists\"!") .setTitle("Thanks for trying out \"Channels\"!")
.setMessage("• You can now create new \"Broadcast Lists\" from the \"New Chat\" dialog\n\n" .setMessage("• You can now create new \"Channels\" from the \"New Chat\" dialog\n\n"
+ "• In case you are using more than one device, broadcast lists are currently not synced between them\n\n"
+ "• If you want to quit the experimental feature, you can disable it at \"Settings / Advanced\"") + "• If you want to quit the experimental feature, you can disable it at \"Settings / Advanced\"")
.setCancelable(false) .setCancelable(false)
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)

View file

@ -172,7 +172,7 @@ public class Prefs {
return getBooleanPreference(context, "pref_developer_mode_enabled", false); return getBooleanPreference(context, "pref_developer_mode_enabled", false);
} }
public static boolean isNewBroadcastListAvailable(Context context) { public static boolean isNewBroadcastAvailable(Context context) {
return getBooleanPreference(context, "pref_new_broadcast_list", false); return getBooleanPreference(context, "pref_new_broadcast_list", false);
} }

View file

@ -44,7 +44,7 @@
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:textSize="18sp" android:textSize="18sp"
android:text="@string/chat_new_broadcast_hint" /> android:text="@string/chat_new_channel_hint" />
<ListView android:id="@+id/selected_contacts_list" <ListView android:id="@+id/selected_contacts_list"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"

View file

@ -240,14 +240,27 @@
<!-- "Chat" is a verb here, "Message to" would also fit. the string might be used in the "New Chat" screen above the contact list --> <!-- "Chat" is a verb here, "Message to" would also fit. the string might be used in the "New Chat" screen above the contact list -->
<string name="chat_with">Chat with…</string> <string name="chat_with">Chat with…</string>
<string name="clone_chat">Clone Chat</string> <string name="clone_chat">Clone Chat</string>
<!-- consider keeping the term "broadcast" as in WhatsApp or Telegram --> <!-- deprecated -->
<string name="broadcast_list">Broadcast List</string> <string name="broadcast_list">Broadcast List</string>
<!-- deprecated -->
<string name="broadcast_lists">Broadcast Lists</string> <string name="broadcast_lists">Broadcast Lists</string>
<!-- deprecated -->
<string name="new_broadcast_list">New Broadcast List</string> <string name="new_broadcast_list">New Broadcast List</string>
<!-- consider keeping the term "channel" as in WhatsApp or Telegram -->
<string name="channel">Channel</string>
<!-- consider keeping the term "channel" as in WhatsApp or Telegram -->
<string name="channels">Channels</string>
<!-- consider keeping the term "channel" as in WhatsApp or Telegram -->
<string name="new_channel">New Channel</string>
<string name="add_recipients">Add Recipients</string> <string name="add_recipients">Add Recipients</string>
<!-- deprecated -->
<string name="edit_broadcast_list">Edit Broadcast List</string> <string name="edit_broadcast_list">Edit Broadcast List</string>
<!-- deprecated -->
<string name="broadcast_list_name">Broadcast List Name</string> <string name="broadcast_list_name">Broadcast List Name</string>
<!-- deprecated -->
<string name="please_enter_broadcast_list_name">Please enter a name for the broadcast list.</string> <string name="please_enter_broadcast_list_name">Please enter a name for the broadcast list.</string>
<!-- consider keeping the term "channel" as in WhatsApp or Telegram -->
<string name="channel_name">Channel Name</string>
<string name="menu_send">Send</string> <string name="menu_send">Send</string>
<string name="menu_toggle_keyboard">Toggle Emoji Keyboard</string> <string name="menu_toggle_keyboard">Toggle Emoji Keyboard</string>
<string name="menu_edit_group">Edit Group</string> <string name="menu_edit_group">Edit Group</string>
@ -257,6 +270,7 @@
<string name="menu_unarchive_chat">Unarchive Chat</string> <string name="menu_unarchive_chat">Unarchive Chat</string>
<string name="menu_add_attachment">Add Attachment</string> <string name="menu_add_attachment">Add Attachment</string>
<string name="menu_leave_group">Leave Group</string> <string name="menu_leave_group">Leave Group</string>
<string name="menu_leave_channel">Leave Channel</string>
<string name="menu_delete_chat">Delete Chat</string> <string name="menu_delete_chat">Delete Chat</string>
<!-- Command to delete all messages in a chat. The chat itself will not be deleted but will be empty afterwards, so make sure to be different from "Delete Chat" here. "Clear" is a verb here, "Empty Chat" would also be fine (eg. in German "Chat leeren") --> <!-- Command to delete all messages in a chat. The chat itself will not be deleted but will be empty afterwards, so make sure to be different from "Delete Chat" here. "Clear" is a verb here, "Empty Chat" would also be fine (eg. in German "Chat leeren") -->
<string name="clear_chat">Clear Chat</string> <string name="clear_chat">Clear Chat</string>
@ -377,7 +391,7 @@
<string name="videochat_invitation_body">You are invited to a video chat, click %1$s to join.</string> <string name="videochat_invitation_body">You are invited to a video chat, click %1$s to join.</string>
<!-- get confirmations --> <!-- get confirmations -->
<string name="ask_leave_group">Are you sure you want to leave this group?</string> <string name="ask_leave_group">Are you sure you want to leave?</string>
<plurals name="ask_delete_chat"> <plurals name="ask_delete_chat">
<item quantity="one">Delete %d chat on all your devices?</item> <item quantity="one">Delete %d chat on all your devices?</item>
<item quantity="other">Delete %d chats on all your devices?</item> <item quantity="other">Delete %d chats on all your devices?</item>
@ -403,8 +417,10 @@
<string name="ask_start_chat_with">Chat with %1$s?</string> <string name="ask_start_chat_with">Chat with %1$s?</string>
<!-- %1$s is replaced by a comma-separated list of names --> <!-- %1$s is replaced by a comma-separated list of names -->
<string name="ask_remove_members">Remove %1$s from group?</string> <string name="ask_remove_members">Remove %1$s from group?</string>
<!-- %1$s is replaced by a comma-separated list of names --> <!-- deprecated -->
<string name="ask_remove_from_broadcast">Remove %1$s from broadcast list?</string> <string name="ask_remove_from_broadcast">Remove %1$s from broadcast list?</string>
<!-- %1$s is replaced by a comma-separated list of names -->
<string name="ask_remove_from_channel">Remove %1$s from channel?</string>
<string name="open_url_confirmation">Do you want to open this link?</string> <string name="open_url_confirmation">Do you want to open this link?</string>
@ -434,7 +450,10 @@
</plurals> </plurals>
<!-- The placeholder will be replaced by the name of the recipient in a one-to-one chat. --> <!-- The placeholder will be replaced by the name of the recipient in a one-to-one chat. -->
<string name="chat_new_one_to_one_hint">Send a message to %1$s.</string> <string name="chat_new_one_to_one_hint">Send a message to %1$s.</string>
<!-- deprecated -->
<string name="chat_new_broadcast_hint">In a broadcast list, recipients will receive messages in a read-only chat with you.</string> <string name="chat_new_broadcast_hint">In a broadcast list, recipients will receive messages in a read-only chat with you.</string>
<!-- consider keeping the term "channel" as in WhatsApp or Telegram -->
<string name="chat_new_channel_hint">In a channel, recipients don\'t see who else is part of the channel.</string>
<string name="chat_new_group_hint">Others will only see this group after you sent a first message.</string> <string name="chat_new_group_hint">Others will only see this group after you sent a first message.</string>
<string name="chat_record_slide_to_cancel">Slide to cancel</string> <string name="chat_record_slide_to_cancel">Slide to cancel</string>
<string name="chat_record_explain">Tap and hold to record a voice message, release to send</string> <string name="chat_record_explain">Tap and hold to record a voice message, release to send</string>
@ -902,7 +921,7 @@
<!-- %1$s will be replaced by name and address of the contact removed from the group, %2$s will be replaced by name and address of the contact who did the action --> <!-- %1$s will be replaced by name and address of the contact removed from the group, %2$s will be replaced by name and address of the contact who did the action -->
<string name="remove_member_by_other">Member %1$s removed by %2$s.</string> <string name="remove_member_by_other">Member %1$s removed by %2$s.</string>
<!-- "left" in the meaning of "exited" --> <!-- "left" in the meaning of "exited" -->
<string name="group_left_by_you">You left the group.</string> <string name="group_left_by_you">You left.</string>
<!-- "left" in the meaning of "exited"; %1$s will be replaced by name and address of the contact leaving the group --> <!-- "left" in the meaning of "exited"; %1$s will be replaced by name and address of the contact leaving the group -->
<string name="group_left_by_other">Group left by %1$s.</string> <string name="group_left_by_other">Group left by %1$s.</string>
<string name="group_image_deleted_by_you">You deleted the group image.</string> <string name="group_image_deleted_by_you">You deleted the group image.</string>

View file

@ -27,7 +27,7 @@
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat <org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false" android:defaultValue="false"
android:key="pref_new_broadcast_list" android:key="pref_new_broadcast_list"
android:title="@string/broadcast_lists"/> android:title="@string/channels"/>
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat <org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false" android:defaultValue="false"