update fork #128

Manually merged
tristan merged 181 commits from mirror/monocles_chat_clean:master into master 2026-01-23 14:02:38 +01:00
12 changed files with 395 additions and 5 deletions
Showing only changes of commit f6ed6f0e14 - Show all commits

Add initial call log screen

Arne 2026-01-04 16:58:40 +01:00

View file

@ -468,6 +468,8 @@
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.ceb" />
</intent-filter>
</activity>
<activity
android:name=".ui.CallsActivity"
android:label="@string/calls" />
</application>
</manifest>

View file

@ -0,0 +1,47 @@
package eu.siacs.conversations.entities;
import eu.siacs.conversations.xmpp.Jid;
public class Call {
private final String contact;
private final Jid jid;
private final long startTime;
private final int status;
private final boolean isVideoCall;
private final boolean successful;
public Call(String contact, Jid jid, long startTime, int status, boolean isVideoCall, boolean successful) {
this.contact = contact;
this.jid = jid;
this.startTime = startTime;
this.status = status;
this.isVideoCall = isVideoCall;
this.successful = successful;
}
public String getContact() {
return contact;
}
public Jid getJid() {
return jid;
}
public long getStartTime() {
return startTime;
}
public int getStatus() {
return status;
}
public boolean isVideoCall() {
return isVideoCall;
}
public boolean isSuccessful() {
return successful;
}
}

View file

@ -0,0 +1,36 @@
package eu.siacs.conversations.ui;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.ActivityCallsBinding;
public class CallsActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCallsBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_calls);
setSupportActionBar(binding.toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, new CallsFragment())
.commit();
}
}
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
}

View file

@ -0,0 +1,114 @@
package eu.siacs.conversations.ui;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Call;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.adapter.CallsAdapter;
import eu.siacs.conversations.entities.RtpSessionStatus;
public class CallsFragment extends Fragment implements CallsAdapter.OnCallAgainClickListener {
private XmppConnectionService xmppConnectionService;
private RecyclerView recyclerView;
private CallsAdapter adapter;
private List<Call> calls = new ArrayList<>();
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
XmppConnectionService.XmppConnectionBinder binder = (XmppConnectionService.XmppConnectionBinder) service;
xmppConnectionService = binder.getService();
loadCalls();
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
xmppConnectionService = null;
}
};
@Override
public void onStart() {
super.onStart();
Intent intent = new Intent(getActivity(), XmppConnectionService.class);
getActivity().bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
public void onStop() {
super.onStop();
getActivity().unbindService(mConnection);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_calls, container, false);
recyclerView = view.findViewById(R.id.list);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
adapter = new CallsAdapter(calls, this);
recyclerView.setAdapter(adapter);
return view;
}
private void loadCalls() {
if (xmppConnectionService != null) {
calls.clear();
calls.addAll(getCalls());
adapter.notifyDataSetChanged();
}
}
private List<Call> getCalls() {
List<Call> calls = new ArrayList<>();
for (Conversation conversation : xmppConnectionService.getConversations()) {
for (Message message : xmppConnectionService.databaseBackend.getMessages(conversation, 100)) { // Limiting to last 100 messages per conversation for performance
if (message.getType() == Message.TYPE_RTP_SESSION) {
RtpSessionStatus rtpSessionStatus = RtpSessionStatus.of(message.getBody());
boolean isVideo = message.getBody().contains("video");
Call call = new Call(conversation.getName().toString(), conversation.getJid(), message.getTimeSent(), message.getStatus(), isVideo, rtpSessionStatus.successful);
calls.add(call);
}
}
}
return calls;
}
@Override
public void onCallAgainClick(Call call) {
Intent intent = new Intent(getActivity(), RtpSessionActivity.class);
if (call.isVideoCall()) {
intent.setAction(RtpSessionActivity.ACTION_MAKE_VIDEO_CALL);
} else {
intent.setAction(RtpSessionActivity.ACTION_MAKE_VOICE_CALL);
}
intent.putExtra("jid", call.getJid().toString());
startActivity(intent);
}
}

View file

@ -365,9 +365,11 @@ public class ConversationsOverviewFragment extends XmppFragment {
final MenuItem manageAccounts = menu.findItem(R.id.action_accounts);
final MenuItem settings = menu.findItem(R.id.action_settings);
final MenuItem stories = menu.findItem(R.id.action_stories);
final MenuItem calls = menu.findItem(R.id.action_calls);
if (manageAccounts != null) manageAccounts.setVisible(false);
if (settings != null) settings.setVisible(false);
if (stories != null) stories.setVisible(false);
if (calls != null) calls.setVisible(false);
}
if (activity == null || activity.xmppConnectionService == null || activity.xmppConnectionService.getAccounts().size() != 1) {
noteToSelf.setVisible(false);
@ -484,10 +486,12 @@ public class ConversationsOverviewFragment extends XmppFragment {
MenuItem manageAccount = menu.findItem(R.id.action_account);
MenuItem manageAccounts = menu.findItem(R.id.action_accounts);
MenuItem stories = menu.findItem(R.id.action_stories);
MenuItem calls = menu.findItem(R.id.action_calls);
if (navBarVisible) {
manageAccount.setVisible(false);
manageAccounts.setVisible(false);
stories.setVisible(false);
calls.setVisible(false);
} else {
AccountUtils.showHideMenuItems(menu);
}
@ -541,6 +545,9 @@ public class ConversationsOverviewFragment extends XmppFragment {
case R.id.action_stories:
startActivity(new Intent(getActivity(), StoriesActivity.class));
return true;
case R.id.action_calls:
startActivity(new Intent(getActivity(), CallsActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}

View file

@ -0,0 +1,78 @@
package eu.siacs.conversations.ui.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import java.util.List;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Call;
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
import eu.siacs.conversations.ui.widget.AvatarView;
import eu.siacs.conversations.utils.UIHelper;
public class CallsAdapter extends RecyclerView.Adapter<CallsAdapter.CallViewHolder> {
private final List<Call> calls;
private final OnCallAgainClickListener listener;
public interface OnCallAgainClickListener {
void onCallAgainClick(Call call);
}
public CallsAdapter(List<Call> calls, OnCallAgainClickListener listener) {
this.calls = calls;
this.listener = listener;
}
@NonNull
@Override
public CallViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_call, parent, false);
return new CallViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull CallViewHolder holder, int position) {
Call call = calls.get(position);
holder.bind(call, listener);
}
@Override
public int getItemCount() {
return calls.size();
}
static class CallViewHolder extends RecyclerView.ViewHolder {
private final AvatarView avatar;
private final TextView contactName;
private final TextView callInfo;
private final ImageButton callAgainButton;
public CallViewHolder(@NonNull View itemView) {
super(itemView);
avatar = itemView.findViewById(R.id.avatar);
contactName = itemView.findViewById(R.id.contact_name);
callInfo = itemView.findViewById(R.id.call_info);
callAgainButton = itemView.findViewById(R.id.call_again);
}
public void bind(final Call call, final OnCallAgainClickListener listener) {
Glide.with(itemView.getContext()).load(call).into(avatar);
//AvatarWorkerTask.loadAvatar(call, avatar, R.dimen.avatar_story_size); //TODO add correct avatar loading
contactName.setText(call.getContact());
callInfo.setText(UIHelper.getCallInfo(itemView.getContext(), call));
callAgainButton.setOnClickListener(v -> listener.onCallAgainClick(call));
}
}
}

View file

@ -1,3 +1,4 @@
package eu.siacs.conversations.utils;
import android.content.Context;
@ -38,6 +39,7 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Call;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Conversational;
@ -60,7 +62,7 @@ import java.util.Locale;
public class UIHelper {
private static final List<String> LOCATION_QUESTIONS =
private static final List<String> LOCATION_QUESTIONS =
Arrays.asList(
"where are you", // en
"where are you now", // en
@ -81,12 +83,12 @@ public class UIHelper {
"donde estas" // es
);
private static final List<Character> PUNCTIONATION =
private static final List<Character> PUNCTIONATION =
Arrays.asList('.', ',', '?', '!', ';', ':');
private static final int SHORT_DATE_FLAGS =
private static final int SHORT_DATE_FLAGS =
DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR | DateUtils.FORMAT_ABBREV_ALL;
private static final int FULL_DATE_FLAGS =
private static final int FULL_DATE_FLAGS =
DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE;
public static String readableTimeDifference(Context context, long time, boolean allowRelative) {
@ -635,6 +637,15 @@ public class UIHelper {
ContextCompat.getColor(textView.getContext(), color))));
}
public static String getCallInfo(Context context, Call call) {
final boolean received = call.getStatus() == Message.STATUS_RECEIVED;
if (!call.isSuccessful() && received) {
return context.getString(R.string.missed_call);
} else {
return context.getString(received ? R.string.incoming_call : R.string.outgoing_call);
}
}
public static String filesizeToString(long size) {
if (size > (1.5 * 1024 * 1024)) {
return Math.round(size * 1f / (1024 * 1024)) + " MiB";

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:fitsSystemWindows="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/calls" />
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</layout>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<eu.siacs.conversations.ui.widget.AvatarView
android:id="@+id/avatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:scaleType="centerCrop" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/avatar"
android:layout_toStartOf="@+id/call_again"
android:layout_centerVertical="true"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/contact_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold" />
<TextView
android:id="@+id/call_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
<ImageButton
android:id="@+id/call_again"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:background="?selectableItemBackgroundBorderless"
android:src="@drawable/ic_call_24dp" />
</RelativeLayout>

View file

@ -34,6 +34,11 @@
android:orderInCategory="92"
android:title="@string/stories"
app:showAsAction="never" />
<item
android:id="@+id/action_calls"
android:orderInCategory="93"
android:title="@string/calls"
app:showAsAction="never" />
<item
android:id="@+id/action_privacy_policy"
android:orderInCategory="98"

View file

@ -1593,4 +1593,5 @@
<string name="no_active_account_to_show_stories">No active account to show or publish stories</string>
<string name="of">of</string>
<string name="error_creating_story">Could not create story</string>
<string name="calls">Calls</string>
</resources>