Compare commits

...

7 commits

Author SHA1 Message Date
adbenitez
8ae6407461 add call duration to message bubble 2025-09-30 22:41:14 +02:00
adbenitez
ae8ce94c00 add spaces around == 2025-09-30 22:39:37 +02:00
adbenitez
74208fff69 update CallInfo 2025-09-30 22:37:47 +02:00
adbenitez
7ace2b9baf add spaces arround == 2025-09-30 20:46:01 +02:00
adbenitez
f28fffb9df update core 2025-09-30 20:07:47 +02:00
adbenitez
349e8d80c8 hide delivery status if not sending or error 2025-09-30 17:30:38 +02:00
adbenitez
4b9217e3e6 rename calls package 2025-09-30 16:29:47 +02:00
13 changed files with 86 additions and 36 deletions

@ -1 +1 @@
Subproject commit 1ba448fe199751e0854fccccde8c768229a5f44e
Subproject commit ba2e573c2358f7c29bf3ad3bb082dd74da167261

View file

@ -371,7 +371,7 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
</activity>
<activity android:name=".videochat.VideochatActivity"
<activity android:name=".calls.CallActivity"
android:label=""
android:theme="@style/TextSecure.LightTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"

View file

@ -2,6 +2,8 @@
package chat.delta.rpc.types;
public class CallInfo {
/* True if SDP offer has a video. */
public Boolean hasVideo;
/**
* SDP offer.
* <p>

View file

@ -117,7 +117,7 @@ import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
import org.thoughtcrime.securesms.util.guava.Optional;
import org.thoughtcrime.securesms.util.views.ProgressDialog;
import org.thoughtcrime.securesms.video.recode.VideoRecoder;
import org.thoughtcrime.securesms.videochat.VideochatUtil;
import org.thoughtcrime.securesms.calls.CallUtil;
import java.io.File;
import java.util.ArrayList;
@ -543,7 +543,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
WebxdcActivity.openMaps(this, chatId);
return true;
} else if (itemId == R.id.menu_start_call) {
VideochatUtil.startCall(this, chatId);
CallUtil.startCall(this, chatId);
return true;
} else if (itemId == R.id.menu_all_media) {
handleAllMedia();

View file

@ -65,7 +65,7 @@ import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.ConversationAdaptiveActionsToolbar;
import org.thoughtcrime.securesms.videochat.VideochatUtil;
import org.thoughtcrime.securesms.calls.CallUtil;
import java.util.Collections;
import java.util.LinkedList;

View file

@ -70,7 +70,7 @@ import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.Stub;
import org.thoughtcrime.securesms.videochat.VideochatUtil;
import org.thoughtcrime.securesms.calls.CallUtil;
import java.util.List;
import java.util.Set;
@ -412,7 +412,7 @@ public class ConversationItem extends BaseConversationItem
String text = messageRecord.getText();
if (messageRecord.getType()==DcMsg.DC_MSG_CALL || text.isEmpty()) {
if (messageRecord.getType() == DcMsg.DC_MSG_CALL || text.isEmpty()) {
bodyText.setVisibility(View.GONE);
}
else {
@ -578,7 +578,7 @@ public class ConversationItem extends BaseConversationItem
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
footer.setVisibility(VISIBLE);
}
else if (messageRecord.getType()==DcMsg.DC_MSG_CALL) {
else if (messageRecord.getType() == DcMsg.DC_MSG_CALL) {
callViewStub.get().setVisibility(View.VISIBLE);
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
@ -821,7 +821,7 @@ public class ConversationItem extends BaseConversationItem
return stickerStub.get().getFooter();
} else if (hasOnlyThumbnail(messageRecord) && TextUtils.isEmpty(messageRecord.getText())) {
return mediaThumbnailStub.get().getFooter();
} else if (messageRecord.getType()==DcMsg.DC_MSG_CALL) {
} else if (messageRecord.getType() == DcMsg.DC_MSG_CALL) {
return callViewStub.get().getFooter();
} else {
return footer;
@ -996,9 +996,9 @@ public class ConversationItem extends BaseConversationItem
int chatId = messageRecord.getChatId();
if (!messageRecord.isOutgoing() && callInfo.state instanceof CallState.Alerting) {
int callId = messageRecord.getId();
VideochatUtil.openCall(getContext(), accId, chatId, callId, callInfo.sdpOffer);
CallUtil.openCall(getContext(), accId, chatId, callId, callInfo.sdpOffer);
} else {
VideochatUtil.startCall(getContext(), accId, chatId);
CallUtil.startCall(getContext(), accId, chatId);
}
}
}

View file

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.videochat;
package org.thoughtcrime.securesms.calls;
import android.Manifest;
import android.annotation.SuppressLint;
@ -36,8 +36,8 @@ import java.util.Objects;
import chat.delta.rpc.Rpc;
import chat.delta.rpc.RpcException;
public class VideochatActivity extends WebViewActivity implements DcEventCenter.DcEventDelegate {
private static final String TAG = VideochatActivity.class.getSimpleName();
public class CallActivity extends WebViewActivity implements DcEventCenter.DcEventDelegate {
private static final String TAG = CallActivity.class.getSimpleName();
public static final String EXTRA_ACCOUNT_ID = "acc_id";
public static final String EXTRA_CHAT_ID = "chat_id";
@ -190,7 +190,7 @@ public class VideochatActivity extends WebViewActivity implements DcEventCenter.
@JavascriptInterface
public String getAvatar() {
final Context context = VideochatActivity.this;
final Context context = CallActivity.this;
final DcChat dcChat = dcContext.getChat(chatId);
if (!TextUtils.isEmpty(dcChat.getProfileImage())) {
return AvatarUtil.asDataUri(dcChat.getProfileImage());

View file

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.videochat;
package org.thoughtcrime.securesms.calls;
import android.Manifest;
import android.app.Activity;
@ -15,8 +15,8 @@ import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class VideochatUtil {
private static final String TAG = VideochatUtil.class.getSimpleName();
public class CallUtil {
private static final String TAG = CallUtil.class.getSimpleName();
public static void startCall(Activity activity, int chatId) {
Permissions.with(activity)
@ -31,11 +31,11 @@ public class VideochatUtil {
}
public static void startCall(Context context, int accId, int chatId) {
Intent intent = new Intent(context, VideochatActivity.class);
Intent intent = new Intent(context, CallActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(VideochatActivity.EXTRA_ACCOUNT_ID, accId);
intent.putExtra(VideochatActivity.EXTRA_CHAT_ID, chatId);
intent.putExtra(VideochatActivity.EXTRA_HASH, "#startCall");
intent.putExtra(CallActivity.EXTRA_ACCOUNT_ID, accId);
intent.putExtra(CallActivity.EXTRA_CHAT_ID, chatId);
intent.putExtra(CallActivity.EXTRA_HASH, "#startCall");
context.startActivity(intent);
}
@ -48,12 +48,12 @@ public class VideochatUtil {
Log.e(TAG, "Error", e);
}
Intent intent = new Intent(context, VideochatActivity.class);
Intent intent = new Intent(context, CallActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(VideochatActivity.EXTRA_ACCOUNT_ID, accId);
intent.putExtra(VideochatActivity.EXTRA_CHAT_ID, chatId);
intent.putExtra(VideochatActivity.EXTRA_CALL_ID, callId);
intent.putExtra(VideochatActivity.EXTRA_HASH, hash);
intent.putExtra(CallActivity.EXTRA_ACCOUNT_ID, accId);
intent.putExtra(CallActivity.EXTRA_CHAT_ID, chatId);
intent.putExtra(CallActivity.EXTRA_CALL_ID, callId);
intent.putExtra(CallActivity.EXTRA_HASH, hash);
context.startActivity(intent);
}

View file

@ -55,6 +55,12 @@ public class CallItemView extends FrameLayout {
public void setCallItem(boolean isOutgoing, CallInfo callInfo) {
this.callInfo = callInfo;
if (callInfo.state instanceof CallState.Completed) {
footer.setCallDuration(((CallState.Completed) callInfo.state).duration);
} else {
footer.setCallDuration(0); // reset
}
if (callInfo.state instanceof CallState.Missed) {
title.setText(R.string.missed_call);
} else if (callInfo.state instanceof CallState.Cancelled) {

View file

@ -26,6 +26,7 @@ public class ConversationItemFooter extends LinearLayout {
private ImageView locationIndicatorView;
private DeliveryStatusView deliveryStatusView;
private Integer textColor = null;
private int callDuration = 0;
public ConversationItemFooter(Context context) {
super(context);
@ -59,6 +60,11 @@ public class ConversationItemFooter extends LinearLayout {
}
}
/* Call duration in seconds. Only >0 if this is a call message */
public void setCallDuration(int duration) {
callDuration = duration;
}
public void setMessageRecord(@NonNull DcMsg messageRecord) {
presentDate(messageRecord);
boolean bookmark = messageRecord.getOriginalMsgId() != 0 || messageRecord.getSavedMsgId() != 0;
@ -86,18 +92,29 @@ public class ConversationItemFooter extends LinearLayout {
deliveryStatusView.setTint(color);
}
private void presentDate(@NonNull DcMsg messageRecord) {
private void presentDate(@NonNull DcMsg dcMsg) {
dateView.forceLayout();
dateView.setText(DateUtils.getExtendedRelativeTimeSpanString(getContext(), messageRecord.getTimestamp()));
Context context = getContext();
String date = dcMsg.getType() == DcMsg.DC_MSG_CALL?
DateUtils.getFormattedCallTime(context, dcMsg.getTimestamp())
: DateUtils.getExtendedRelativeTimeSpanString(context, dcMsg.getTimestamp());
if (callDuration > 0) {
String duration = DateUtils.getFormattedCallDuration(context, callDuration);
dateView.setText(context.getString(R.string.call_date_and_duration, date, duration));
} else {
dateView.setText(date);
}
}
private void presentDeliveryStatus(@NonNull DcMsg messageRecord) {
// isDownloading is temporary and should be checked first.
boolean isDownloading = messageRecord.getDownloadState() == DcMsg.DC_DOWNLOAD_IN_PROGRESS;
boolean isCall = messageRecord.getType() == DcMsg.DC_MSG_CALL;
if (isDownloading) deliveryStatusView.setDownloading();
else if (messageRecord.isPending()) deliveryStatusView.setPending();
else if (messageRecord.isFailed()) deliveryStatusView.setFailed();
else if (!messageRecord.isOutgoing()) deliveryStatusView.setNone();
else if (!messageRecord.isOutgoing() || isCall) deliveryStatusView.setNone();
else if (messageRecord.isRemoteRead()) deliveryStatusView.setRead();
else if (messageRecord.isDelivered()) deliveryStatusView.setSent();
else if (messageRecord.isPreparing()) deliveryStatusView.setPreparing();

View file

@ -49,7 +49,7 @@ import org.thoughtcrime.securesms.util.JsonUtils;
import org.thoughtcrime.securesms.util.Pair;
import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.videochat.VideochatActivity;
import org.thoughtcrime.securesms.calls.CallActivity;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
@ -178,12 +178,12 @@ public class NotificationCenter {
Log.e(TAG, "Error", e);
}
Intent intent = new Intent(context, VideochatActivity.class);
Intent intent = new Intent(context, CallActivity.class);
intent.setAction(autoAccept? Intent.ACTION_ANSWER : Intent.ACTION_VIEW);
intent.putExtra(VideochatActivity.EXTRA_ACCOUNT_ID, chatData.accountId);
intent.putExtra(VideochatActivity.EXTRA_CHAT_ID, chatData.chatId);
intent.putExtra(VideochatActivity.EXTRA_CALL_ID, callId);
intent.putExtra(VideochatActivity.EXTRA_HASH, hash);
intent.putExtra(CallActivity.EXTRA_ACCOUNT_ID, chatData.accountId);
intent.putExtra(CallActivity.EXTRA_CHAT_ID, chatData.chatId);
intent.putExtra(CallActivity.EXTRA_CALL_ID, callId);
intent.putExtra(CallActivity.EXTRA_HASH, hash);
intent.setPackage(context.getPackageName());
return TaskStackBuilder.create(context)
.addNextIntentWithParentStack(chatIntent)

View file

@ -119,6 +119,19 @@ public class DateUtils extends android.text.format.DateUtils {
TimeUnit.MILLISECONDS.toSeconds(millis-(TimeUnit.MILLISECONDS.toMinutes(millis)*60000)));
}
public static String getFormattedCallDuration(Context c, int seconds) {
if (seconds < 60) {
return c.getResources().getQuantityString(R.plurals.n_seconds_ext, seconds, seconds);
}
int mins = seconds / 60;
return c.getResources().getQuantityString(R.plurals.n_minutes_ext, mins, mins);
}
public static String getFormattedCallTime(final Context c, final long timestamp) {
return getFormattedDateTime(timestamp, DateFormat.is24HourFormat(c)? "HH:mm" : "hh:mm a");
}
public static String getFormattedTimespan(Context c, int timestamp) {
int mins = timestamp / (1000 * 60);
if (mins / 60 == 0) {

View file

@ -118,11 +118,22 @@
<!-- Refers to the time a contact was last seen. Shown below contact name in the profile. The placeholder will be replaced by a relative point in time as "3 minutes ago" (see https://momentjs.com for more examples and languages)-->
<string name="last_seen_relative">Last seen %1$s</string>
<string name="last_seen_unknown">Last seen: Unknown</string>
<!-- Shown in call duration. Avoid abbreviations, prefer full words ex. "N seconds". -->
<plurals name="n_seconds_ext">
<item quantity="one">%d second</item>
<item quantity="other">%d seconds</item>
</plurals>
<!-- Shown in call duration. Avoid abbreviations, prefer full words ex. "N minutes". -->
<plurals name="n_minutes_ext">
<item quantity="one">%d minute</item>
<item quantity="other">%d minutes</item>
</plurals>
<!-- Shown beside messages that are "N minutes old". Prefer short strings, or well-known abbreviations. -->
<plurals name="n_minutes">
<item quantity="one">%d min</item>
<item quantity="other">%d min</item>
</plurals>
<!-- Shown beside messages that are "N hours old". Prefer short strings, or well-known abbreviations. -->
<plurals name="n_hours">
<item quantity="one">%d hour</item>
@ -377,6 +388,7 @@
<string name="declined_call">Declined Call</string>
<string name="canceled_call">Canceled Call</string>
<string name="missed_call">Missed Call</string>
<string name="call_date_and_duration">%1$s, %2$s</string>
<!-- deprecated -->
<string name="videochat">Video Chat</string>