diff --git a/CHANGELOG.md b/CHANGELOG.md index a3028bde0..f2da0e862 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,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 +* New experimental feature: several addresses per profile * Update to core 2.33.0 ## v2.25.0 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" diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index b33753672..37c278ba4 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -234,13 +234,17 @@ 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/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/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/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); + } + }); + }); + }); + } } 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 new file mode 100644 index 000000000..32bfb174d --- /dev/null +++ b/src/main/java/org/thoughtcrime/securesms/relay/RelayListActivity.java @@ -0,0 +1,180 @@ +package org.thoughtcrime.securesms.relay; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +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; + +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; + +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, DcEventCenter.DcEventDelegate { + + 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); + 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`) + DividerItemDecoration divider = new DividerItemDecoration( + recyclerView.getContext(), + layoutManager.getOrientation()); + recyclerView.addItemDecoration(divider); + recyclerView.setLayoutManager(layoutManager); + + adapter = new RelayListAdapter(this); + 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() { + 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 void onRelayEdit(EnteredLoginParam relay) { + Intent intent = new Intent(this, EditRelayActivity.class); + intent.putExtra(EditRelayActivity.EXTRA_ADDR, relay.addr); + 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) { + finish(); + return true; + } + 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/java/org/thoughtcrime/securesms/relay/RelayListAdapter.java b/src/main/java/org/thoughtcrime/securesms/relay/RelayListAdapter.java new file mode 100644 index 000000000..365b9babf --- /dev/null +++ b/src/main/java/org/thoughtcrime/securesms/relay/RelayListAdapter.java @@ -0,0 +1,104 @@ +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.annotation.Nullable; +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); + void onRelayEdit(EnteredLoginParam relay); + void onRelayDelete(EnteredLoginParam relay); + } + + public RelayListAdapter(OnRelayClickListener listener) { + this.listener = listener; + } + + public String getMainRelay() { + return mainRelayAddr; + } + + public void setRelays(@Nullable 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; + private final ImageView editButton; + private final ImageView deleteButton; + + public RelayViewHolder(@NonNull View itemView) { + super(itemView); + titleText = itemView.findViewById(R.id.title); + mainIndicator = itemView.findViewById(R.id.main_indicator); + editButton = itemView.findViewById(R.id.edit_button); + deleteButton = itemView.findViewById(R.id.delete_button); + } + + public void bind(EnteredLoginParam relay, boolean isMain, OnRelayClickListener listener) { + titleText.setText(relay.addr); + mainIndicator.setVisibility(isMain ? View.VISIBLE : View.GONE); + deleteButton.setVisibility(isMain ? View.GONE : View.VISIBLE); + + itemView.setOnClickListener(v -> { + if (listener != null) { + listener.onRelayClick(relay); + } + }); + + editButton.setOnClickListener(v -> { + if (listener != null) { + listener.onRelayEdit(relay); + } + }); + + deleteButton.setOnClickListener(v -> { + if (listener != null) { + listener.onRelayDelete(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..243fc3ccb --- /dev/null +++ b/src/main/res/layout/activity_relay_list.xml @@ -0,0 +1,31 @@ + + + + + + + + + + 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..43e8a8a0b --- /dev/null +++ b/src/main/res/layout/relay_list_item.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + diff --git a/src/main/res/xml/preferences_advanced.xml b/src/main/res/xml/preferences_advanced.xml index 9640bcb20..dc540cb4f 100644 --- a/src/main/res/xml/preferences_advanced.xml +++ b/src/main/res/xml/preferences_advanced.xml @@ -7,8 +7,8 @@ - +