show call log messages in conversation stream
|
@ -57,6 +57,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
|||
public static final int TYPE_STATUS = 3;
|
||||
public static final int TYPE_PRIVATE = 4;
|
||||
public static final int TYPE_PRIVATE_FILE = 5;
|
||||
public static final int TYPE_RTP_SESSION = 6;
|
||||
|
||||
public static final String CONVERSATION = "conversationUuid";
|
||||
public static final String COUNTERPART = "counterpart";
|
||||
|
@ -151,6 +152,31 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
|||
null);
|
||||
}
|
||||
|
||||
public Message(Conversation conversation, int status, int type, final String remoteMsgId) {
|
||||
this(conversation, java.util.UUID.randomUUID().toString(),
|
||||
conversation.getUuid(),
|
||||
conversation.getJid() == null ? null : conversation.getJid().asBareJid(),
|
||||
null,
|
||||
null,
|
||||
System.currentTimeMillis(),
|
||||
Message.ENCRYPTION_NONE,
|
||||
status,
|
||||
type,
|
||||
false,
|
||||
remoteMsgId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
null);
|
||||
}
|
||||
|
||||
protected Message(final Conversational conversation, final String uuid, final String conversationUUid, final Jid counterpart,
|
||||
final Jid trueCounterpart, final String body, final long timeSent,
|
||||
final int encryption, final int status, final int type, final boolean carbon,
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package eu.siacs.conversations.entities;
|
||||
|
||||
import android.support.annotation.DrawableRes;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
|
||||
public class RtpSessionStatus {
|
||||
|
||||
public final boolean successful;
|
||||
public final long duration;
|
||||
|
||||
|
||||
public RtpSessionStatus(boolean successful, long duration) {
|
||||
this.successful = successful;
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return successful + ":" + duration;
|
||||
}
|
||||
|
||||
public static RtpSessionStatus of(final String body) {
|
||||
final String[] parts = Strings.nullToEmpty(body).split(":", 2);
|
||||
long duration = 0;
|
||||
if (parts.length == 2) {
|
||||
try {
|
||||
duration = Long.parseLong(parts[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
//do nothing
|
||||
}
|
||||
}
|
||||
boolean made;
|
||||
try {
|
||||
made = Boolean.parseBoolean(parts[0]);
|
||||
} catch (Exception e) {
|
||||
made = false;
|
||||
}
|
||||
return new RtpSessionStatus(made, duration);
|
||||
}
|
||||
|
||||
public static @DrawableRes int getDrawable(final boolean received, final boolean successful, final boolean darkTheme) {
|
||||
if (received) {
|
||||
if (successful) {
|
||||
return darkTheme ? R.drawable.ic_call_received_white_18dp : R.drawable.ic_call_received_black_18dp;
|
||||
} else {
|
||||
return darkTheme ? R.drawable.ic_call_missed_white_18dp : R.drawable.ic_call_missed_black_18dp;
|
||||
}
|
||||
} else {
|
||||
if (successful) {
|
||||
return darkTheme ? R.drawable.ic_call_made_white_18dp : R.drawable.ic_call_made_black_18dp;
|
||||
} else {
|
||||
return darkTheme ? R.drawable.ic_call_missed_outgoing_white_18dp : R.drawable.ic_call_missed_outgoing_black_18dp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -49,6 +49,7 @@ import eu.siacs.conversations.entities.Conversational;
|
|||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.Message.FileParams;
|
||||
import eu.siacs.conversations.entities.RtpSessionStatus;
|
||||
import eu.siacs.conversations.entities.Transferable;
|
||||
import eu.siacs.conversations.http.P1S3UrlStreamHandler;
|
||||
import eu.siacs.conversations.persistance.FileBackend;
|
||||
|
@ -72,6 +73,7 @@ import eu.siacs.conversations.utils.Emoticons;
|
|||
import eu.siacs.conversations.utils.GeoHelper;
|
||||
import eu.siacs.conversations.utils.MessageUtils;
|
||||
import eu.siacs.conversations.utils.StylingHelper;
|
||||
import eu.siacs.conversations.utils.TimeframeUtils;
|
||||
import eu.siacs.conversations.utils.UIHelper;
|
||||
import eu.siacs.conversations.xmpp.mam.MamReference;
|
||||
import rocks.xmpp.addr.Jid;
|
||||
|
@ -83,6 +85,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
|||
private static final int RECEIVED = 1;
|
||||
private static final int STATUS = 2;
|
||||
private static final int DATE_SEPARATOR = 3;
|
||||
private static final int RTP_SESSION = 4;
|
||||
private final XmppActivity activity;
|
||||
private final ListSelectionManager listSelectionManager = new ListSelectionManager();
|
||||
private final AudioPlayer audioPlayer;
|
||||
|
@ -92,6 +95,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
|||
private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
|
||||
private boolean mUseGreenBackground = false;
|
||||
private OnQuoteListener onQuoteListener;
|
||||
|
||||
public MessageAdapter(XmppActivity activity, List<Message> messages) {
|
||||
super(activity, 0, messages);
|
||||
this.audioPlayer = new AudioPlayer(this);
|
||||
|
@ -101,7 +105,6 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
|||
}
|
||||
|
||||
|
||||
|
||||
private static void resetClickListener(View... views) {
|
||||
for (View view : views) {
|
||||
view.setOnClickListener(null);
|
||||
|
@ -135,7 +138,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
|||
|
||||
@Override
|
||||
public int getViewTypeCount() {
|
||||
return 4;
|
||||
return 5;
|
||||
}
|
||||
|
||||
private int getItemViewType(Message message) {
|
||||
|
@ -145,12 +148,14 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
|||
} else {
|
||||
return STATUS;
|
||||
}
|
||||
} else if (message.getType() == Message.TYPE_RTP_SESSION) {
|
||||
return RTP_SESSION;
|
||||
} else if (message.getStatus() <= Message.STATUS_RECEIVED) {
|
||||
return RECEIVED;
|
||||
}
|
||||
|
||||
} else {
|
||||
return SENT;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
|
@ -283,7 +288,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
|||
final String formattedTime = UIHelper.readableTimeDifferenceFull(getContext(), message.getMergedTimeSent());
|
||||
final String bodyLanguage = message.getBodyLanguage();
|
||||
final String bodyLanguageInfo = bodyLanguage == null ? "" : String.format(" \u00B7 %s", bodyLanguage.toUpperCase(Locale.US));
|
||||
if (message.getStatus() <= Message.STATUS_RECEIVED) { ;
|
||||
if (message.getStatus() <= Message.STATUS_RECEIVED) {
|
||||
if ((filesize != null) && (info != null)) {
|
||||
viewHolder.time.setText(formattedTime + " \u00B7 " + filesize + " \u00B7 " + info + bodyLanguageInfo);
|
||||
} else if ((filesize == null) && (info != null)) {
|
||||
|
@ -291,7 +296,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
|||
} else if ((filesize != null) && (info == null)) {
|
||||
viewHolder.time.setText(formattedTime + " \u00B7 " + filesize + bodyLanguageInfo);
|
||||
} else {
|
||||
viewHolder.time.setText(formattedTime+bodyLanguageInfo);
|
||||
viewHolder.time.setText(formattedTime + bodyLanguageInfo);
|
||||
}
|
||||
} else {
|
||||
if ((filesize != null) && (info != null)) {
|
||||
|
@ -305,7 +310,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
|||
} else if ((filesize != null) && (info == null)) {
|
||||
viewHolder.time.setText(filesize + " \u00B7 " + formattedTime + bodyLanguageInfo);
|
||||
} else {
|
||||
viewHolder.time.setText(formattedTime+bodyLanguageInfo);
|
||||
viewHolder.time.setText(formattedTime + bodyLanguageInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -491,7 +496,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
|||
if (highlightedTerm != null) {
|
||||
StylingHelper.highlight(activity, body, highlightedTerm, StylingHelper.isDarkText(viewHolder.messageBody));
|
||||
}
|
||||
MyLinkify.addLinks(body,true);
|
||||
MyLinkify.addLinks(body, true);
|
||||
viewHolder.messageBody.setAutoLinkMask(0);
|
||||
viewHolder.messageBody.setText(EmojiWrapper.transform(body));
|
||||
viewHolder.messageBody.setTextIsSelectable(true);
|
||||
|
@ -622,6 +627,13 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
|||
view = activity.getLayoutInflater().inflate(R.layout.message_date_bubble, parent, false);
|
||||
viewHolder.status_message = view.findViewById(R.id.message_body);
|
||||
viewHolder.message_box = view.findViewById(R.id.message_box);
|
||||
viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received);
|
||||
break;
|
||||
case RTP_SESSION:
|
||||
view = activity.getLayoutInflater().inflate(R.layout.message_rtp_session, parent, false);
|
||||
viewHolder.status_message = view.findViewById(R.id.message_body);
|
||||
viewHolder.message_box = view.findViewById(R.id.message_box);
|
||||
viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received);
|
||||
break;
|
||||
case SENT:
|
||||
view = activity.getLayoutInflater().inflate(R.layout.message_sent, parent, false);
|
||||
|
@ -684,6 +696,28 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
|||
}
|
||||
viewHolder.message_box.setBackgroundResource(activity.isDarkTheme() ? R.drawable.date_bubble_grey : R.drawable.date_bubble_white);
|
||||
return view;
|
||||
} else if (type == RTP_SESSION) {
|
||||
final boolean isDarkTheme = activity.isDarkTheme();
|
||||
final boolean received = message.getStatus() <= Message.STATUS_RECEIVED;
|
||||
final RtpSessionStatus rtpSessionStatus = RtpSessionStatus.of(message.getBody());
|
||||
final long duration = rtpSessionStatus.duration;
|
||||
if (received) {
|
||||
if (duration > 0) {
|
||||
viewHolder.status_message.setText(activity.getString(R.string.incoming_call_duration, TimeframeUtils.resolve(activity,duration)));
|
||||
} else {
|
||||
viewHolder.status_message.setText(R.string.incoming_call);
|
||||
}
|
||||
} else {
|
||||
if (duration > 0) {
|
||||
viewHolder.status_message.setText(activity.getString(R.string.outgoing_call_duration, TimeframeUtils.resolve(activity,duration)));
|
||||
} else {
|
||||
viewHolder.status_message.setText(R.string.outgoing_call);
|
||||
}
|
||||
}
|
||||
viewHolder.indicatorReceived.setImageResource(RtpSessionStatus.getDrawable(received,rtpSessionStatus.successful,isDarkTheme));
|
||||
viewHolder.indicatorReceived.setAlpha(isDarkTheme ? 0.7f : 0.57f);
|
||||
viewHolder.message_box.setBackgroundResource(isDarkTheme ? R.drawable.date_bubble_grey : R.drawable.date_bubble_white);
|
||||
return view;
|
||||
} else if (type == STATUS) {
|
||||
if ("LOAD_MORE".equals(message.getBody())) {
|
||||
viewHolder.status_message.setVisibility(View.GONE);
|
||||
|
|
|
@ -299,6 +299,8 @@ public class UIHelper {
|
|||
return new Pair<>(context.getString(R.string.omemo_decryption_failed), true);
|
||||
} else if (message.isFileOrImage()) {
|
||||
return new Pair<>(getFileDescriptionString(context, message), true);
|
||||
} else if (message.getType() == Message.TYPE_RTP_SESSION) {
|
||||
return new Pair<>(context.getString(message.getStatus() == Message.STATUS_RECEIVED ? R.string.incoming_call : R.string.outgoing_call), true);
|
||||
} else {
|
||||
final String body = MessageUtils.filterLtrRtl(message.getBody());
|
||||
if (body.startsWith(Message.ME_COMMAND)) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package eu.siacs.conversations.xmpp.jingle;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
|
@ -18,6 +19,10 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Conversational;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.RtpSessionStatus;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xml.Namespace;
|
||||
import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
|
||||
|
@ -94,13 +99,27 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
|||
|
||||
private final WebRTCWrapper webRTCWrapper = new WebRTCWrapper(this);
|
||||
private final ArrayDeque<IceCandidate> pendingIceCandidates = new ArrayDeque<>();
|
||||
private final Message message;
|
||||
private State state = State.NULL;
|
||||
private RtpContentMap initiatorRtpContentMap;
|
||||
private RtpContentMap responderRtpContentMap;
|
||||
private long rtpConnectionStarted = 0; //time of 'connected'
|
||||
|
||||
|
||||
JingleRtpConnection(JingleConnectionManager jingleConnectionManager, Id id, Jid initiator) {
|
||||
super(jingleConnectionManager, id, initiator);
|
||||
final Conversation conversation = jingleConnectionManager.getXmppConnectionService().findOrCreateConversation(
|
||||
id.account,
|
||||
id.with.asBareJid(),
|
||||
false,
|
||||
false
|
||||
);
|
||||
this.message = new Message(
|
||||
conversation,
|
||||
isInitiator() ? Message.STATUS_SEND : Message.STATUS_RECEIVED,
|
||||
Message.TYPE_RTP_SESSION,
|
||||
id.sessionId
|
||||
);
|
||||
}
|
||||
|
||||
private static State reasonToState(Reason reason) {
|
||||
|
@ -153,7 +172,9 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
|||
return;
|
||||
}
|
||||
webRTCWrapper.close();
|
||||
transitionOrThrow(reasonToState(wrapper.reason));
|
||||
final State target = reasonToState(wrapper.reason);
|
||||
transitionOrThrow(target);
|
||||
writeLogMessage(target);
|
||||
if (previous == State.PROPOSED || previous == State.SESSION_INITIALIZED) {
|
||||
xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
|
||||
}
|
||||
|
@ -455,7 +476,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
|||
if (transition(State.RETRACTED)) {
|
||||
xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
|
||||
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": session with " + id.with + " has been retracted");
|
||||
//TODO create missed call notification/message
|
||||
writeLogMessageMissed();
|
||||
jingleConnectionManager.finishConnection(this);
|
||||
} else {
|
||||
Log.d(Config.LOGTAG, "ignoring retract because already in " + this.state);
|
||||
|
@ -509,6 +530,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
|||
private void sendSessionTerminate(final Reason reason, final String text) {
|
||||
final State target = reasonToState(reason);
|
||||
transitionOrThrow(target);
|
||||
writeLogMessage(target);
|
||||
final JinglePacket jinglePacket = new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId);
|
||||
jinglePacket.setReason(reason, text);
|
||||
send(jinglePacket);
|
||||
|
@ -773,9 +795,12 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
|||
public void onConnectionChange(final PeerConnection.PeerConnectionState newState) {
|
||||
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnectionState changed to " + newState);
|
||||
updateEndUserState();
|
||||
if (newState == PeerConnection.PeerConnectionState.CONNECTED && this.rtpConnectionStarted == 0) {
|
||||
this.rtpConnectionStarted = SystemClock.elapsedRealtime();
|
||||
}
|
||||
if (newState == PeerConnection.PeerConnectionState.FAILED) {
|
||||
if (TERMINATED.contains(this.state)) {
|
||||
Log.d(Config.LOGTAG,id.account.getJid().asBareJid()+": not sending session-terminate after connectivity error because session is already in state "+this.state);
|
||||
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": not sending session-terminate after connectivity error because session is already in state " + this.state);
|
||||
return;
|
||||
}
|
||||
sendSessionTerminate(Reason.CONNECTIVITY_ERROR);
|
||||
|
@ -850,6 +875,37 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
|||
}
|
||||
}
|
||||
|
||||
private void writeLogMessage(final State state) {
|
||||
final long started = this.rtpConnectionStarted;
|
||||
long duration = started <= 0 ? 0 : SystemClock.elapsedRealtime() - started;
|
||||
if (state == State.TERMINATED_SUCCESS || (state == State.TERMINATED_CONNECTIVITY_ERROR && duration > 0)) {
|
||||
writeLogMessageSuccess(duration);
|
||||
} else {
|
||||
writeLogMessageMissed();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeLogMessageSuccess(final long duration) {
|
||||
this.message.setBody(new RtpSessionStatus(true, duration).toString());
|
||||
this.writeMessage();
|
||||
}
|
||||
|
||||
private void writeLogMessageMissed() {
|
||||
this.message.setBody(new RtpSessionStatus(false,0).toString());
|
||||
this.writeMessage();
|
||||
}
|
||||
|
||||
private void writeMessage() {
|
||||
final Conversational conversational = message.getConversation();
|
||||
if (conversational instanceof Conversation) {
|
||||
((Conversation) conversational).add(this.message);
|
||||
xmppConnectionService.databaseBackend.createMessage(message);
|
||||
xmppConnectionService.updateConversationUi();
|
||||
} else {
|
||||
throw new IllegalStateException("Somehow the conversation in a message was a stub");
|
||||
}
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
return this.state;
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 159 B |
After Width: | Height: | Size: 174 B |
After Width: | Height: | Size: 179 B |
After Width: | Height: | Size: 180 B |
After Width: | Height: | Size: 180 B |
After Width: | Height: | Size: 191 B |
After Width: | Height: | Size: 159 B |
After Width: | Height: | Size: 169 B |
After Width: | Height: | Size: 132 B |
After Width: | Height: | Size: 135 B |
After Width: | Height: | Size: 141 B |
After Width: | Height: | Size: 134 B |
After Width: | Height: | Size: 136 B |
After Width: | Height: | Size: 147 B |
After Width: | Height: | Size: 133 B |
After Width: | Height: | Size: 140 B |
After Width: | Height: | Size: 174 B |
After Width: | Height: | Size: 189 B |
After Width: | Height: | Size: 201 B |
After Width: | Height: | Size: 188 B |
After Width: | Height: | Size: 193 B |
After Width: | Height: | Size: 215 B |
After Width: | Height: | Size: 175 B |
After Width: | Height: | Size: 189 B |
After Width: | Height: | Size: 202 B |
After Width: | Height: | Size: 225 B |
After Width: | Height: | Size: 247 B |
After Width: | Height: | Size: 235 B |
After Width: | Height: | Size: 235 B |
After Width: | Height: | Size: 263 B |
After Width: | Height: | Size: 202 B |
After Width: | Height: | Size: 228 B |
After Width: | Height: | Size: 212 B |
After Width: | Height: | Size: 247 B |
After Width: | Height: | Size: 267 B |
After Width: | Height: | Size: 257 B |
After Width: | Height: | Size: 248 B |
After Width: | Height: | Size: 291 B |
After Width: | Height: | Size: 214 B |
After Width: | Height: | Size: 257 B |
|
@ -1,24 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="5dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingTop="5dp">
|
||||
android:paddingBottom="5dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/message_box"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/date_bubble_white"
|
||||
android:id="@+id/message_box"
|
||||
android:layout_centerHorizontal="true">
|
||||
android:layout_centerHorizontal="true"
|
||||
android:background="@drawable/date_bubble_white">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/message_body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Body1.Secondary"
|
||||
android:id="@+id/message_body" />
|
||||
tools:text="Yesterday" />
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingBottom="5dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/message_box"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:background="@drawable/date_bubble_white"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/indicator_received"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="4sp"
|
||||
android:layout_marginLeft="0sp"
|
||||
tools:alpha="0.57"
|
||||
tools:src="@drawable/ic_call_received_black_18dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/message_body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="@string/incoming_call"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Body1.Secondary" />
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
|
@ -903,6 +903,10 @@
|
|||
<string name="hang_up">Hang up</string>
|
||||
<string name="ongoing_call">Ongoing call</string>
|
||||
<string name="disable_tor_to_make_call">Disable Tor to make calls</string>
|
||||
<string name="incoming_call">Incoming call</string>
|
||||
<string name="incoming_call_duration">Incoming call · %s</string>
|
||||
<string name="outgoing_call">Outgoing call</string>
|
||||
<string name="outgoing_call_duration">Outgoing call · %s</string>
|
||||
<plurals name="view_users">
|
||||
<item quantity="one">View %1$d Participant</item>
|
||||
<item quantity="other">View %1$d Participants</item>
|
||||
|
|