From 56459e7e7a4696c53a0c89d25714341bf988251a Mon Sep 17 00:00:00 2001 From: adbenitez Date: Mon, 1 Dec 2025 21:26:37 +0100 Subject: [PATCH 01/11] add TransportListActivity --- src/main/AndroidManifest.xml | 4 ++++ .../org/thoughtcrime/securesms/TransportListActivity.java | 0 2 files changed, 4 insertions(+) create mode 100644 src/main/java/org/thoughtcrime/securesms/TransportListActivity.java diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index b33753672..3eca0084a 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -241,6 +241,10 @@ android:exported="true"> + + Date: Tue, 2 Dec 2025 04:46:30 +0100 Subject: [PATCH 02/11] create RelayListActivity with basic listing of relays/transports --- src/main/AndroidManifest.xml | 2 +- .../securesms/TransportListActivity.java | 0 .../AdvancedPreferenceFragment.java | 25 ++-- .../securesms/relay/RelayListActivity.java | 112 ++++++++++++++++++ .../securesms/relay/RelayListAdapter.java | 84 +++++++++++++ src/main/res/layout/activity_relay_list.xml | 18 +++ src/main/res/layout/relay_list_item.xml | 40 +++++++ src/main/res/values/strings.xml | 1 + src/main/res/xml/preferences_advanced.xml | 4 +- 9 files changed, 267 insertions(+), 19 deletions(-) delete mode 100644 src/main/java/org/thoughtcrime/securesms/TransportListActivity.java create mode 100644 src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java create mode 100644 src/main/java/org/thoughtcrime/securesms/relay/RelayListAdapter.java create mode 100644 src/main/res/layout/activity_relay_list.xml create mode 100644 src/main/res/layout/relay_list_item.xml diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 3eca0084a..f044dd848 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -241,7 +241,7 @@ android:exported="true"> - diff --git a/src/main/java/org/thoughtcrime/securesms/TransportListActivity.java b/src/main/java/org/thoughtcrime/securesms/TransportListActivity.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java b/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java index 1432bc852..5387114b0 100644 --- a/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java @@ -7,9 +7,7 @@ import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_MVBOX_MOVE; import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_ONLY_FETCH_MVBOX; import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SHOW_EMAILS; import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_WEBXDC_REALTIME_ENABLED; -import static org.thoughtcrime.securesms.connect.DcHelper.getRpc; -import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -17,7 +15,6 @@ import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.View; -import android.webkit.WebView; import android.widget.EditText; import android.widget.Toast; @@ -29,12 +26,10 @@ import androidx.preference.ListPreference; import androidx.preference.Preference; import org.thoughtcrime.securesms.ApplicationPreferencesActivity; -import org.thoughtcrime.securesms.ConversationActivity; import org.thoughtcrime.securesms.LogViewActivity; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.EditTransportActivity; +import org.thoughtcrime.securesms.relay.RelayListActivity; import org.thoughtcrime.securesms.connect.DcEventCenter; -import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.proxy.ProxySettingsActivity; import org.thoughtcrime.securesms.util.Prefs; import org.thoughtcrime.securesms.util.ScreenLockUtil; @@ -48,8 +43,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.Objects; -import chat.delta.rpc.RpcException; - public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment implements DcEventCenter.DcEventDelegate @@ -207,12 +200,12 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment }); } - Preference passwordAndAccount = this.findPreference("password_account_settings_button"); - if (passwordAndAccount != null) { - passwordAndAccount.setOnPreferenceClickListener(((preference) -> { - boolean result = ScreenLockUtil.applyScreenLock(requireActivity(), getString(R.string.edit_transport), getString(R.string.enter_system_secret_to_continue), REQUEST_CODE_CONFIRM_CREDENTIALS_ACCOUNT); + Preference relayListBtn = this.findPreference("pref_relay_list_button"); + if (relayListBtn != null) { + relayListBtn.setOnPreferenceClickListener(((preference) -> { + boolean result = ScreenLockUtil.applyScreenLock(requireActivity(), getString(R.string.transports), getString(R.string.enter_system_secret_to_continue), REQUEST_CODE_CONFIRM_CREDENTIALS_ACCOUNT); if (!result) { - openRegistrationActivity(); + openRelayListActivity(); } return true; })); @@ -247,7 +240,7 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK && requestCode == REQUEST_CODE_CONFIRM_CREDENTIALS_ACCOUNT) { - openRegistrationActivity(); + openRelayListActivity(); } } @@ -324,8 +317,8 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment } } - private void openRegistrationActivity() { - Intent intent = new Intent(requireActivity(), EditTransportActivity.class); + private void openRelayListActivity() { + Intent intent = new Intent(requireActivity(), RelayListActivity.class); startActivity(intent); } diff --git a/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java b/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java new file mode 100644 index 000000000..1a2f430cc --- /dev/null +++ b/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java @@ -0,0 +1,112 @@ +package org.thoughtcrime.securesms.relay; + +import android.os.Bundle; +import android.util.Log; +import android.view.MenuItem; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.thoughtcrime.securesms.BaseActionBarActivity; +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.connect.DcHelper; +import org.thoughtcrime.securesms.util.Util; +import org.thoughtcrime.securesms.util.ViewUtil; + +import java.util.List; + +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; +import chat.delta.rpc.types.EnteredLoginParam; + +public class RelayListActivity extends BaseActionBarActivity + implements RelayListAdapter.OnRelayClickListener { + + private static final String TAG = RelayListActivity.class.getSimpleName(); + + private RelayListAdapter adapter; + private Rpc rpc; + private int accId; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_relay_list); + + rpc = DcHelper.getRpc(this); + accId = DcHelper.getContext(this).getAccountId(); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(R.string.transports); + actionBar.setDisplayHomeAsUpEnabled(true); + } + + RecyclerView recyclerView = findViewById(R.id.relay_list); + + // add padding to avoid content hidden behind system bars + ViewUtil.applyWindowInsets(recyclerView); + + LinearLayoutManager layoutManager = new LinearLayoutManager(this); + // Add the default divider (uses the theme’s `android.R.attr.listDivider`) + DividerItemDecoration divider = new DividerItemDecoration( + recyclerView.getContext(), + layoutManager.getOrientation()); + recyclerView.addItemDecoration(divider); + recyclerView.setLayoutManager(layoutManager); + + adapter = new RelayListAdapter(this); + recyclerView.setAdapter(adapter); + + loadRelays(); + } + + } + + private void loadRelays() { + Util.runOnAnyBackgroundThread(() -> { + String mainRelayAddr = ""; + try { + mainRelayAddr = rpc.getConfig(accId, DcHelper.CONFIG_CONFIGURED_ADDRESS); + } catch (RpcException e) { + Log.e(TAG, "RPC.getConfig() failed", e); + } + String finalMainRelayAddr = mainRelayAddr; + + try { + List relays = rpc.listTransports(accId); + + Util.runOnMain(() -> adapter.setRelays(relays, finalMainRelayAddr)); + } catch (RpcException e) { + Log.e(TAG, "RPC.listTransports() failed", e); + Util.runOnMain(() -> adapter.setRelays(null, finalMainRelayAddr)); + } + }); + } + + @Override + public void onRelayClick(EnteredLoginParam relay) { + if (relay.addr != null && !relay.addr.equals(adapter.getMainRelay())) { + Util.runOnAnyBackgroundThread(() -> { + try { + rpc.setConfig(accId, DcHelper.CONFIG_CONFIGURED_ADDRESS, relay.addr); + } catch (RpcException e) { + Log.e(TAG, "RPC.setConfig() failed", e); + } + + loadRelays(); + }); + } + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/src/main/java/org/thoughtcrime/securesms/relay/RelayListAdapter.java b/src/main/java/org/thoughtcrime/securesms/relay/RelayListAdapter.java new file mode 100644 index 000000000..191847e50 --- /dev/null +++ b/src/main/java/org/thoughtcrime/securesms/relay/RelayListAdapter.java @@ -0,0 +1,84 @@ +package org.thoughtcrime.securesms.relay; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import org.thoughtcrime.securesms.R; + +import java.util.ArrayList; +import java.util.List; + +import chat.delta.rpc.types.EnteredLoginParam; + +public class RelayListAdapter extends RecyclerView.Adapter { + + private List relays = new ArrayList<>(); + private final OnRelayClickListener listener; + private String mainRelayAddr; + + public interface OnRelayClickListener { + void onRelayClick(EnteredLoginParam relay); + } + + public RelayListAdapter(OnRelayClickListener listener) { + this.listener = listener; + } + + public String getMainRelay() { + return mainRelayAddr; + } + + public void setRelays(List relays, String mainRelayAddr) { + this.relays = relays != null ? relays : new ArrayList<>(); + this.mainRelayAddr = mainRelayAddr; + notifyDataSetChanged(); + } + + @NonNull + @Override + public RelayViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.relay_list_item, parent, false); + return new RelayViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull RelayViewHolder holder, int position) { + EnteredLoginParam relay = relays.get(position); + boolean isMain = relay.addr != null && relay.addr.equals(mainRelayAddr); + holder.bind(relay, isMain, listener); + } + + @Override + public int getItemCount() { + return relays.size(); + } + + public static class RelayViewHolder extends RecyclerView.ViewHolder { + private final TextView titleText; + private final ImageView mainIndicator; + + public RelayViewHolder(@NonNull View itemView) { + super(itemView); + titleText = itemView.findViewById(R.id.title); + mainIndicator = itemView.findViewById(R.id.main_indicator); + } + + public void bind(EnteredLoginParam relay, boolean isMain, OnRelayClickListener listener) { + titleText.setText(relay.addr); + mainIndicator.setVisibility(isMain ? View.VISIBLE : View.GONE); + + itemView.setOnClickListener(v -> { + if (listener != null) { + listener.onRelayClick(relay); + } + }); + } + } +} diff --git a/src/main/res/layout/activity_relay_list.xml b/src/main/res/layout/activity_relay_list.xml new file mode 100644 index 000000000..af7e550ca --- /dev/null +++ b/src/main/res/layout/activity_relay_list.xml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/src/main/res/layout/relay_list_item.xml b/src/main/res/layout/relay_list_item.xml new file mode 100644 index 000000000..329fb7d14 --- /dev/null +++ b/src/main/res/layout/relay_list_item.xml @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 6065dba54..368400a56 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -681,6 +681,7 @@ You are using a proxy. If you\'re having trouble connecting, try a different proxy. Transports + Main transport Add Transport Remove Transport Edit Transport diff --git a/src/main/res/xml/preferences_advanced.xml b/src/main/res/xml/preferences_advanced.xml index 9cf402105..0b8d71809 100644 --- a/src/main/res/xml/preferences_advanced.xml +++ b/src/main/res/xml/preferences_advanced.xml @@ -7,8 +7,8 @@ - + From 2f05b20b0faebb5cdac57293dd4905351aad6a12 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Tue, 2 Dec 2025 05:20:42 +0100 Subject: [PATCH 03/11] allow editing transports --- src/main/AndroidManifest.xml | 2 +- .../securesms/ConversationFragment.java | 5 ++- .../securesms/InstantOnboardingActivity.java | 8 ++--- .../EditRelayActivity.java} | 32 ++++++++++++++++--- .../securesms/relay/RelayListActivity.java | 8 +++++ .../securesms/relay/RelayListAdapter.java | 9 ++++++ src/main/res/layout/relay_list_item.xml | 12 +++++++ 7 files changed, 64 insertions(+), 12 deletions(-) rename src/main/java/org/thoughtcrime/securesms/{EditTransportActivity.java => relay/EditRelayActivity.java} (94%) diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index f044dd848..37c278ba4 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -234,7 +234,7 @@ android:theme="@style/TextSecure.LightNoActionBar" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> - { - startRegistrationActivity(); + startActivity(new Intent(this, EditRelayActivity.class)); signUpDialog.dismiss(); }); view.findViewById(R.id.scan_qr_button).setOnClickListener((v) -> { @@ -367,11 +368,6 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements signUpDialog.show(); } - private void startRegistrationActivity() { - Intent intent = new Intent(this, EditTransportActivity.class); - startActivity(intent); - } - private void updateProvider() { if (isDcLogin) { signUpBtn.setText(R.string.login_title); diff --git a/src/main/java/org/thoughtcrime/securesms/EditTransportActivity.java b/src/main/java/org/thoughtcrime/securesms/relay/EditRelayActivity.java similarity index 94% rename from src/main/java/org/thoughtcrime/securesms/EditTransportActivity.java rename to src/main/java/org/thoughtcrime/securesms/relay/EditRelayActivity.java index 9f7593887..62c5e905e 100644 --- a/src/main/java/org/thoughtcrime/securesms/EditTransportActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/relay/EditRelayActivity.java @@ -1,4 +1,4 @@ -package org.thoughtcrime.securesms; +package org.thoughtcrime.securesms.relay; import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_PROXY_ENABLED; import static org.thoughtcrime.securesms.connect.DcHelper.getContext; @@ -10,6 +10,7 @@ import android.os.Bundle; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; +import android.util.Log; import android.util.Patterns; import android.view.Menu; import android.view.MenuInflater; @@ -32,6 +33,11 @@ import com.b44t.messenger.DcEvent; import com.b44t.messenger.DcProvider; import com.google.android.material.textfield.TextInputEditText; +import org.thoughtcrime.securesms.BaseActionBarActivity; +import org.thoughtcrime.securesms.ConversationListActivity; +import org.thoughtcrime.securesms.LogViewActivity; +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.WelcomeActivity; import org.thoughtcrime.securesms.connect.DcEventCenter; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.permissions.Permissions; @@ -49,7 +55,7 @@ import chat.delta.rpc.types.EnteredCertificateChecks; import chat.delta.rpc.types.EnteredLoginParam; import chat.delta.rpc.types.Socket; -public class EditTransportActivity extends BaseActionBarActivity implements DcEventCenter.DcEventDelegate { +public class EditRelayActivity extends BaseActionBarActivity implements DcEventCenter.DcEventDelegate { private enum VerificationType { EMAIL, @@ -57,6 +63,9 @@ public class EditTransportActivity extends BaseActionBarActivity implements DcEv PORT, } + private static final String TAG = EditRelayActivity.class.getSimpleName(); + public final static String EXTRA_ADDR = "extra_addr"; + private TextInputEditText emailInput; private TextInputEditText passwordInput; @@ -118,11 +127,26 @@ public class EditTransportActivity extends BaseActionBarActivity implements DcEv startActivity(new Intent(this, ProxySettingsActivity.class)); }); + String addr = getIntent().getStringExtra(EXTRA_ADDR); EnteredLoginParam config = null; try { List relays = rpc.listTransports(accId); - if (!relays.isEmpty()) config = relays.get(0); - } catch (RpcException ignored) {} + for (EnteredLoginParam relay : relays) { + if (addr != null && addr.equals(relay.addr)) { + config = relay; + break; + } + } + if (config == null && !relays.isEmpty()) { + Log.e(TAG, "Error got unknown address: " + addr); + finish(); + return; + }; + } catch (RpcException e) { + Log.e(TAG, "Error calling Rpc.listTransports()", e); + finish(); + return; + } ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { diff --git a/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java b/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java index 1a2f430cc..a753d4ea5 100644 --- a/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.relay; +import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.MenuItem; @@ -101,6 +102,13 @@ public class RelayListActivity extends BaseActionBarActivity } } + @Override + public void onRelayEdit(EnteredLoginParam relay) { + Intent intent = new Intent(this, EditRelayActivity.class); + intent.putExtra(EditRelayActivity.EXTRA_ADDR, relay.addr); + startActivity(intent); + } + @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { if (item.getItemId() == android.R.id.home) { diff --git a/src/main/java/org/thoughtcrime/securesms/relay/RelayListAdapter.java b/src/main/java/org/thoughtcrime/securesms/relay/RelayListAdapter.java index 191847e50..f0bf1b8b5 100644 --- a/src/main/java/org/thoughtcrime/securesms/relay/RelayListAdapter.java +++ b/src/main/java/org/thoughtcrime/securesms/relay/RelayListAdapter.java @@ -24,6 +24,7 @@ public class RelayListAdapter extends RecyclerView.Adapter { + if (listener != null) { + listener.onRelayEdit(relay); + } + }); } } } diff --git a/src/main/res/layout/relay_list_item.xml b/src/main/res/layout/relay_list_item.xml index 329fb7d14..08b192914 100644 --- a/src/main/res/layout/relay_list_item.xml +++ b/src/main/res/layout/relay_list_item.xml @@ -37,4 +37,16 @@ android:textSize="16sp" tools:text="user@example.com" /> + From 9181a9c398c79d44d2d44244593f76d20099ed77 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Tue, 2 Dec 2025 05:21:02 +0100 Subject: [PATCH 04/11] add uncommited/missing import in RelayListActivity --- .../java/org/thoughtcrime/securesms/relay/RelayListActivity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java b/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java index a753d4ea5..dcb2de0a3 100644 --- a/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java @@ -7,6 +7,7 @@ import android.view.MenuItem; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; +import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; From 230fb795845ff02e16815cce924c63301ea4c075 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Tue, 2 Dec 2025 05:32:21 +0100 Subject: [PATCH 05/11] allow removing transports --- .../securesms/relay/RelayListActivity.java | 20 +++++++++++++++++-- .../securesms/relay/RelayListAdapter.java | 13 +++++++++++- src/main/res/layout/relay_list_item.xml | 14 +++++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java b/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java index dcb2de0a3..5ed04a2e4 100644 --- a/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java @@ -7,6 +7,7 @@ import android.view.MenuItem; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -65,8 +66,6 @@ public class RelayListActivity extends BaseActionBarActivity loadRelays(); } - } - private void loadRelays() { Util.runOnAnyBackgroundThread(() -> { String mainRelayAddr = ""; @@ -110,6 +109,23 @@ public class RelayListActivity extends BaseActionBarActivity startActivity(intent); } + @Override + public void onRelayDelete(EnteredLoginParam relay) { + new AlertDialog.Builder(this) + .setTitle(R.string.remove_transport) + .setMessage(getString(R.string.confirm_remove_transport, relay.addr)) + .setPositiveButton(R.string.ok, (dialog, which) -> { + try { + rpc.deleteTransport(accId, relay.addr); + loadRelays(); + } catch (RpcException e) { + Log.e(TAG, "RPC.deleteTransport() failed", e); + } + }) + .setNegativeButton(R.string.cancel, null) + .show(); + } + @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { if (item.getItemId() == android.R.id.home) { diff --git a/src/main/java/org/thoughtcrime/securesms/relay/RelayListAdapter.java b/src/main/java/org/thoughtcrime/securesms/relay/RelayListAdapter.java index f0bf1b8b5..365b9babf 100644 --- a/src/main/java/org/thoughtcrime/securesms/relay/RelayListAdapter.java +++ b/src/main/java/org/thoughtcrime/securesms/relay/RelayListAdapter.java @@ -7,6 +7,7 @@ import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import org.thoughtcrime.securesms.R; @@ -25,6 +26,7 @@ public class RelayListAdapter extends RecyclerView.Adapter relays, String mainRelayAddr) { + public void setRelays(@Nullable List relays, String mainRelayAddr) { this.relays = relays != null ? relays : new ArrayList<>(); this.mainRelayAddr = mainRelayAddr; notifyDataSetChanged(); @@ -65,17 +67,20 @@ public class RelayListAdapter extends RecyclerView.Adapter { if (listener != null) { @@ -88,6 +93,12 @@ public class RelayListAdapter extends RecyclerView.Adapter { + if (listener != null) { + listener.onRelayDelete(relay); + } + }); } } } diff --git a/src/main/res/layout/relay_list_item.xml b/src/main/res/layout/relay_list_item.xml index 08b192914..f028ebe3b 100644 --- a/src/main/res/layout/relay_list_item.xml +++ b/src/main/res/layout/relay_list_item.xml @@ -49,4 +49,18 @@ android:background="@drawable/touch_highlight_background" android:src="@drawable/ic_create_white_24dp" app:tint="?attr/conversation_list_item_date_color" /> + + + From d24f53895e1f2e573ad55db8106fa68d481166a4 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Thu, 4 Dec 2025 18:42:14 +0100 Subject: [PATCH 06/11] allow to add transport from QR scanner --- .../securesms/qr/QrCodeHandler.java | 56 +++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/qr/QrCodeHandler.java b/src/main/java/org/thoughtcrime/securesms/qr/QrCodeHandler.java index e2cf69561..e95d072eb 100644 --- a/src/main/java/org/thoughtcrime/securesms/qr/QrCodeHandler.java +++ b/src/main/java/org/thoughtcrime/securesms/qr/QrCodeHandler.java @@ -1,7 +1,9 @@ package org.thoughtcrime.securesms.qr; import android.app.Activity; +import android.content.DialogInterface; import android.content.Intent; +import android.util.Log; import android.widget.Toast; import androidx.annotation.StringRes; @@ -17,15 +19,25 @@ import org.thoughtcrime.securesms.connect.AccountManager; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.util.IntentUtils; import org.thoughtcrime.securesms.util.Util; +import org.thoughtcrime.securesms.util.views.ProgressDialog; + +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; public class QrCodeHandler { + private static final String TAG = QrCodeHandler.class.getSimpleName(); + private final Activity activity; private final DcContext dcContext; + private final Rpc rpc; + private final int accId; public QrCodeHandler(Activity activity) { this.activity = activity; dcContext = DcHelper.getContext(activity); + rpc = DcHelper.getRpc(activity); + accId = dcContext.getAccountId(); } public void onScanPerformed(IntentResult scanResult) { @@ -67,10 +79,7 @@ public class QrCodeHandler { case DcContext.DC_QR_ACCOUNT: case DcContext.DC_QR_LOGIN: final String scope = qrParsed.getText1(); - builder.setMessage(activity.getString(qrParsed.getState() == DcContext.DC_QR_ACCOUNT ? R.string.qraccount_ask_create_and_login_another : R.string.qrlogin_ask_login_another, scope)); - builder.setPositiveButton(R.string.ok, (dialog, which) -> { - AccountManager.getInstance().addAccountFromQr(activity, rawString); - }); + setAddTransportDialog(activity, builder, rawString, scope); builder.setNegativeButton(R.string.cancel, null); builder.setCancelable(false); break; @@ -246,4 +255,43 @@ public class QrCodeHandler { }); builder.setNegativeButton(android.R.string.cancel, null); } + + private void setAddTransportDialog(Activity activity, AlertDialog.Builder builder, String qrData, String transportName) { + builder.setTitle(R.string.confirm_add_transport); + builder.setMessage(transportName); + builder.setPositiveButton(R.string.ok, (d, w) -> { + ProgressDialog progressDialog = new ProgressDialog(activity); + progressDialog.setMessage(activity.getResources().getString(R.string.one_moment)); + progressDialog.setCanceledOnTouchOutside(false); + progressDialog.setCancelable(false); + String cancel = activity.getResources().getString(android.R.string.cancel); + progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, cancel, (d2, w2) -> { + dcContext.stopOngoingProcess(); + }); + progressDialog.show(); + + Util.runOnAnyBackgroundThread(() -> { + String error = null; + try { + rpc.addTransportFromQr(accId, qrData); + } catch (RpcException e) { + Log.w(TAG, e); + error = e.getMessage(); + } + final String finalError = error; + Util.runOnMain(() -> { + if (!progressDialog.isShowing()) return; // canceled dialog, nothing to do + if (finalError != null) { + Toast.makeText(activity, finalError, Toast.LENGTH_LONG).show(); + } + try { + progressDialog.dismiss(); + } catch (IllegalArgumentException e) { + // see https://stackoverflow.com/a/5102572/4557005 + Log.w(TAG, e); + } + }); + }); + }); + } } From 1ff99ddbe4a3de993d6122ce19954e2aed1b2726 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Thu, 4 Dec 2025 20:11:42 +0100 Subject: [PATCH 07/11] add "add transport" floating button --- .../thoughtcrime/securesms/qr/QrActivity.java | 26 ++++++++--- .../securesms/relay/RelayListActivity.java | 45 ++++++++++++++++++- src/main/res/layout/activity_relay_list.xml | 13 ++++++ 3 files changed, 78 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/qr/QrActivity.java b/src/main/java/org/thoughtcrime/securesms/qr/QrActivity.java index 540e6fe8f..4676930e8 100644 --- a/src/main/java/org/thoughtcrime/securesms/qr/QrActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/qr/QrActivity.java @@ -44,6 +44,8 @@ import java.io.InputStream; public class QrActivity extends BaseActionBarActivity implements View.OnClickListener { private final static String TAG = QrActivity.class.getSimpleName(); + public final static String EXTRA_SCAN_RELAY = "scan_relay"; + private final static int REQUEST_CODE_IMAGE = 46243; private final static int TAB_SHOW = 0; private final static int TAB_SCAN = 1; @@ -51,6 +53,7 @@ public class QrActivity extends BaseActionBarActivity implements View.OnClickLis private TabLayout tabLayout; private ViewPager viewPager; private QrShowFragment qrShowFragment; + private boolean scanRelay; @Override protected void onPreCreate() { @@ -61,8 +64,10 @@ public class QrActivity extends BaseActionBarActivity implements View.OnClickLis @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_qr); + + scanRelay = getIntent().getBooleanExtra(EXTRA_SCAN_RELAY, false); + qrShowFragment = new QrShowFragment(this); tabLayout = ViewUtil.findById(this, R.id.tab_layout); viewPager = ViewUtil.findById(this, R.id.pager); @@ -71,10 +76,11 @@ public class QrActivity extends BaseActionBarActivity implements View.OnClickLis setSupportActionBar(ViewUtil.findById(this, R.id.toolbar)); assert getSupportActionBar() != null; - getSupportActionBar().setTitle(R.string.menu_new_contact); + getSupportActionBar().setTitle(scanRelay? R.string.add_transport : R.string.menu_new_contact); getSupportActionBar().setDisplayHomeAsUpEnabled(true); - viewPager.setCurrentItem(TAB_SHOW); + viewPager.setCurrentItem(scanRelay? TAB_SCAN : TAB_SHOW); + if (scanRelay) tabLayout.setVisibility(View.GONE); viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override @@ -102,7 +108,13 @@ public class QrActivity extends BaseActionBarActivity implements View.OnClickLis .ifNecessary() .withPermanentDenialDialog(getString(R.string.perm_explain_access_to_camera_denied)) .onAllGranted(() -> ((QrScanFragment) adapter.getItem(TAB_SCAN)).handleQrScanWithPermissions(QrActivity.this)) - .onAnyDenied(() -> viewPager.setCurrentItem(TAB_SHOW)) + .onAnyDenied(() -> { + if (scanRelay) { + finish(); + } else { + viewPager.setCurrentItem(TAB_SHOW); + } + }) .execute(); } } @@ -149,7 +161,11 @@ public class QrActivity extends BaseActionBarActivity implements View.OnClickLis if (permissions.length > 0 && Manifest.permission.CAMERA.equals(permissions[0]) && grantResults[0] == PackageManager.PERMISSION_DENIED) { - viewPager.setCurrentItem(TAB_SHOW); + if (scanRelay) { + finish(); + } else { + viewPager.setCurrentItem(TAB_SHOW); + } // Workaround because sometimes something else requested the permissions before this class // (probably the CameraView) and then this class didn't notice when it was denied } diff --git a/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java b/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java index 5ed04a2e4..32bfb174d 100644 --- a/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java @@ -12,9 +12,18 @@ import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import com.b44t.messenger.DcContext; +import com.b44t.messenger.DcEvent; +import com.google.zxing.integration.android.IntentIntegrator; +import com.google.zxing.integration.android.IntentResult; + import org.thoughtcrime.securesms.BaseActionBarActivity; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton; +import org.thoughtcrime.securesms.connect.DcEventCenter; import org.thoughtcrime.securesms.connect.DcHelper; +import org.thoughtcrime.securesms.qr.QrActivity; +import org.thoughtcrime.securesms.qr.QrCodeHandler; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; @@ -25,7 +34,7 @@ import chat.delta.rpc.RpcException; import chat.delta.rpc.types.EnteredLoginParam; public class RelayListActivity extends BaseActionBarActivity - implements RelayListAdapter.OnRelayClickListener { + implements RelayListAdapter.OnRelayClickListener, DcEventCenter.DcEventDelegate { private static final String TAG = RelayListActivity.class.getSimpleName(); @@ -48,9 +57,16 @@ public class RelayListActivity extends BaseActionBarActivity } RecyclerView recyclerView = findViewById(R.id.relay_list); + PulsingFloatingActionButton fabAdd = findViewById(R.id.fab_add_relay); // add padding to avoid content hidden behind system bars ViewUtil.applyWindowInsets(recyclerView); + // Apply insets to prevent fab from being covered by system bars + ViewUtil.applyWindowInsetsAsMargin(fabAdd); + + fabAdd.setOnClickListener(v -> { + new IntentIntegrator(this).setCaptureActivity(QrActivity.class).addExtra(QrActivity.EXTRA_SCAN_RELAY, true).initiateScan(); + }); LinearLayoutManager layoutManager = new LinearLayoutManager(this); // Add the default divider (uses the theme’s `android.R.attr.listDivider`) @@ -64,6 +80,15 @@ public class RelayListActivity extends BaseActionBarActivity recyclerView.setAdapter(adapter); loadRelays(); + + DcEventCenter eventCenter = DcHelper.getEventCenter(this); + eventCenter.addObserver(DcContext.DC_EVENT_CONFIGURE_PROGRESS, this); + } + + @Override + public void onDestroy() { + super.onDestroy(); + DcHelper.getEventCenter(this).removeObservers(this); } private void loadRelays() { @@ -134,4 +159,22 @@ public class RelayListActivity extends BaseActionBarActivity } return super.onOptionsItemSelected(item); } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == IntentIntegrator.REQUEST_CODE) { + IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data); + QrCodeHandler qrCodeHandler = new QrCodeHandler(this); + qrCodeHandler.onScanPerformed(scanResult); + } + } + + @Override + public void handleEvent(@NonNull DcEvent event) { + int eventId = event.getId(); + if (eventId == DcContext.DC_EVENT_CONFIGURE_PROGRESS && event.getData1Int() == 1000) { + loadRelays(); + } + } } diff --git a/src/main/res/layout/activity_relay_list.xml b/src/main/res/layout/activity_relay_list.xml index af7e550ca..243fc3ccb 100644 --- a/src/main/res/layout/activity_relay_list.xml +++ b/src/main/res/layout/activity_relay_list.xml @@ -2,6 +2,7 @@ @@ -12,7 +13,19 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" + android:paddingBottom="88dp" android:scrollbars="vertical" tools:listitem="@layout/relay_list_item" /> + + From f24b2e254098d62b7e9eb5fd6276e3bb1e436d6a Mon Sep 17 00:00:00 2001 From: adbenitez Date: Thu, 4 Dec 2025 20:15:55 +0100 Subject: [PATCH 08/11] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 640944692..eeb57e262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ * Opened in-chat apps got a 'About Apps' menu item * Avoid gray avatar on profile creation * Add disk usage statistics to log +* Add new multi-transport experimental feature * Update to core 2.29.0 ## v2.25.0 From 429c3fb57c47b8d535eb6f939de215d0bf9e74af Mon Sep 17 00:00:00 2001 From: adbenitez Date: Fri, 5 Dec 2025 23:08:10 +0100 Subject: [PATCH 09/11] tweak changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a316501..68591e5a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ * Avoid gray avatar on profile creation * Avoid last item in chat list being covered by the floating button * Add disk usage statistics to log -* Add new multi-transport experimental feature +* New experimental feature: several addresses per profile * Update to core 2.33.0 ## v2.25.0 From c936d51bacd5bab6c2b45b13ce8e09991d3aad3c Mon Sep 17 00:00:00 2001 From: link2xt Date: Sat, 6 Dec 2025 21:02:00 +0000 Subject: [PATCH 10/11] flake.nix: update Android SDK from 35 to 36 Closes --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 5ab488602..de876b7f8 100644 --- a/flake.nix +++ b/flake.nix @@ -18,7 +18,7 @@ build-tools-35-0-0 cmdline-tools-latest platform-tools - platforms-android-35 + platforms-android-36 ndk-27-2-12479018 ]); rust-version = pkgs.lib.removeSuffix "\n" From 9f2523da21d16c38ba589a28751aabc68bf2453d Mon Sep 17 00:00:00 2001 From: adbenitez Date: Sun, 7 Dec 2025 11:50:47 +0100 Subject: [PATCH 11/11] use R.string.def, don't add extra string --- src/main/res/layout/relay_list_item.xml | 2 +- src/main/res/values/strings.xml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/res/layout/relay_list_item.xml b/src/main/res/layout/relay_list_item.xml index f028ebe3b..43e8a8a0b 100644 --- a/src/main/res/layout/relay_list_item.xml +++ b/src/main/res/layout/relay_list_item.xml @@ -21,7 +21,7 @@ android:layout_height="24dp" android:layout_marginEnd="16dp" android:src="@drawable/ic_check_white_24dp" - android:contentDescription="@string/main_transport" + android:contentDescription="@string/def" android:visibility="gone" app:tint="?attr/colorAccent" tools:visibility="visible" /> diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index df392451f..a7cb3ab76 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -681,7 +681,6 @@ You are using a proxy. If you\'re having trouble connecting, try a different proxy. Transports - Main transport Add Transport Remove Transport Edit Transport