Compare commits

...

7 Commits

Author SHA1 Message Date
copilot-swe-agent[bot] 798e5c56aa Fix edge-to-edge: Use AppBarLayout with Toolbar to extend behind status bar
- Add AppBarLayout with fitsSystemWindows for proper status bar handling
- Toolbar now draws behind the status bar with proper padding
- Background color extends all the way to the top of the screen
- RecyclerView uses appbar_scrolling_view_behavior for proper layout
- FAB still handles navigation bar insets correctly

Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2025-11-25 02:44:22 +00:00
adb a78cd44857 Merge branch 'main' into copilot/add-transports-list-screen 2025-11-25 03:34:43 +01:00
copilot-swe-agent[bot] c3e85dc27a Implement proper edge-to-edge with insets and use checkmark for main transport
- Remove fitsSystemWindows hack, use proper window insets handling
- Apply system bar insets to RecyclerView top/bottom padding
- Apply bottom inset to FAB margin for proper positioning
- Replace "Main" text label with checkmark icon (ic_check_white_24dp)
- Checkmark appears to the left of transport email for main transport
- Properly handle edge-to-edge so toolbar color goes to status bar

Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2025-11-24 19:59:39 +00:00
copilot-swe-agent[bot] e36c34a6dc Implement edge-to-edge, add edit button, and main transport selection
- Add android:fitsSystemWindows="true" to activity layout for proper edge-to-edge support
- Add edit button (pencil icon) next to delete button for editing transports
- Add "Main" indicator for the main transport (CONFIG_CONFIGURED_ADDRESS)
- Clicking on a transport now makes it the main transport instead of opening edit
- Edit button opens EditTransportActivity with transport data
- FAB still adds new transport as intended

Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2025-11-24 19:37:40 +00:00
copilot-swe-agent[bot] 3ffdd2150c Improve layout using CoordinatorLayout for better FAB behavior
Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2025-11-24 18:51:22 +00:00
copilot-swe-agent[bot] 57243b50aa Add TransportListActivity to list, edit, and remove transports
Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2025-11-24 18:49:45 +00:00
copilot-swe-agent[bot] a42682e1fc Initial plan 2025-11-24 18:40:05 +00:00
8 changed files with 404 additions and 6 deletions
+4
View File
@@ -240,6 +240,10 @@
android:exported="true">
</activity>
<activity android:name=".TransportListActivity"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".proxy.ProxySettingsActivity"
android:label="@string/proxy_settings"
android:windowSoftInputMode="stateHidden"
@@ -119,9 +119,20 @@ public class EditTransportActivity extends BaseActionBarActivity implements DcEv
});
EnteredLoginParam config = null;
String transportAddr = getIntent().getStringExtra(TransportListActivity.EXTRA_TRANSPORT_ADDR);
try {
List<EnteredLoginParam> relays = rpc.listTransports(accId);
if (!relays.isEmpty()) config = relays.get(0);
if (transportAddr != null) {
// Find the transport with the specified address
for (EnteredLoginParam transport : relays) {
if (transportAddr.equals(transport.addr)) {
config = transport;
break;
}
}
} else if (!relays.isEmpty()) {
config = relays.get(0);
}
} catch (RpcException ignored) {}
ActionBar actionBar = getSupportActionBar();
@@ -0,0 +1,156 @@
package org.thoughtcrime.securesms;
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.util.Util;
import java.util.List;
import chat.delta.rpc.Rpc;
import chat.delta.rpc.RpcException;
import chat.delta.rpc.types.EnteredLoginParam;
public class TransportListActivity extends BaseActionBarActivity
implements TransportListAdapter.OnTransportClickListener {
public static final String EXTRA_TRANSPORT_ADDR = "transport_addr";
private RecyclerView recyclerView;
private TransportListAdapter adapter;
private FloatingActionButton fabAdd;
private Rpc rpc;
private int accId;
private String mainTransportAddr;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_transport_list);
rpc = DcHelper.getRpc(this);
accId = DcHelper.getContext(this).getAccountId();
// Setup toolbar
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setTitle(R.string.transports);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
recyclerView = findViewById(R.id.transport_list);
fabAdd = findViewById(R.id.fab_add_transport);
adapter = new TransportListAdapter(this);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
fabAdd.setOnClickListener(v -> openAddTransport());
// Handle window insets for FAB to stay above navigation bar
ViewCompat.setOnApplyWindowInsetsListener(fabAdd, (v, windowInsets) -> {
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
CoordinatorLayout.LayoutParams fabParams = (CoordinatorLayout.LayoutParams) fabAdd.getLayoutParams();
int fabMargin = (int) (16 * getResources().getDisplayMetrics().density);
fabParams.bottomMargin = insets.bottom + fabMargin;
fabParams.rightMargin = fabMargin;
fabAdd.setLayoutParams(fabParams);
return windowInsets;
});
loadTransports();
}
@Override
protected void onResume() {
super.onResume();
loadTransports();
}
private void loadTransports() {
Util.runOnBackground(() -> {
try {
List<EnteredLoginParam> transports = rpc.listTransports(accId);
mainTransportAddr = DcHelper.get(this, DcHelper.CONFIG_CONFIGURED_ADDRESS);
Util.runOnMain(() -> adapter.setTransports(transports, mainTransportAddr));
} catch (RpcException e) {
mainTransportAddr = DcHelper.get(this, DcHelper.CONFIG_CONFIGURED_ADDRESS);
Util.runOnMain(() -> adapter.setTransports(null, mainTransportAddr));
}
});
}
private void openAddTransport() {
Intent intent = new Intent(this, EditTransportActivity.class);
startActivity(intent);
}
@Override
public void onTransportClick(EnteredLoginParam transport) {
// Clicking on a transport makes it the main transport
if (transport.addr != null && !transport.addr.equals(mainTransportAddr)) {
Util.runOnBackground(() -> {
DcHelper.getContext(this).setConfig(DcHelper.CONFIG_CONFIGURED_ADDRESS, transport.addr);
Util.runOnMain(this::loadTransports);
});
}
}
@Override
public void onTransportEdit(EnteredLoginParam transport) {
Intent intent = new Intent(this, EditTransportActivity.class);
intent.putExtra(EXTRA_TRANSPORT_ADDR, transport.addr);
startActivity(intent);
}
@Override
public void onTransportDelete(EnteredLoginParam transport) {
new AlertDialog.Builder(this)
.setTitle(R.string.remove_transport)
.setMessage(getString(R.string.confirm_remove_transport, transport.addr))
.setPositiveButton(R.string.ok, (dialog, which) -> deleteTransport(transport))
.setNegativeButton(R.string.cancel, null)
.show();
}
private void deleteTransport(EnteredLoginParam transport) {
Util.runOnBackground(() -> {
try {
rpc.deleteTransport(accId, transport.addr);
Util.runOnMain(this::loadTransports);
} catch (RpcException e) {
Util.runOnMain(() -> {
new AlertDialog.Builder(this)
.setMessage(getString(R.string.error) + ": " + e.getMessage())
.setPositiveButton(R.string.ok, null)
.show();
});
}
});
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}
@@ -0,0 +1,106 @@
package org.thoughtcrime.securesms;
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 java.util.ArrayList;
import java.util.List;
import chat.delta.rpc.types.EnteredLoginParam;
public class TransportListAdapter extends RecyclerView.Adapter<TransportListAdapter.TransportViewHolder> {
private List<EnteredLoginParam> transports = new ArrayList<>();
private final OnTransportClickListener listener;
private String mainTransportAddr;
public interface OnTransportClickListener {
void onTransportClick(EnteredLoginParam transport);
void onTransportEdit(EnteredLoginParam transport);
void onTransportDelete(EnteredLoginParam transport);
}
public TransportListAdapter(OnTransportClickListener listener) {
this.listener = listener;
}
public void setTransports(List<EnteredLoginParam> transports, String mainTransportAddr) {
this.transports = transports != null ? transports : new ArrayList<>();
this.mainTransportAddr = mainTransportAddr;
notifyDataSetChanged();
}
@NonNull
@Override
public TransportViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.transport_list_item, parent, false);
return new TransportViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull TransportViewHolder holder, int position) {
EnteredLoginParam transport = transports.get(position);
boolean isMain = transport.addr != null && transport.addr.equals(mainTransportAddr);
holder.bind(transport, isMain, listener);
}
@Override
public int getItemCount() {
return transports.size();
}
static class TransportViewHolder extends RecyclerView.ViewHolder {
private final TextView emailText;
private final TextView serverText;
private final ImageView mainIndicator;
private final ImageView editButton;
private final ImageView deleteButton;
public TransportViewHolder(@NonNull View itemView) {
super(itemView);
emailText = itemView.findViewById(R.id.transport_email);
serverText = itemView.findViewById(R.id.transport_server);
mainIndicator = itemView.findViewById(R.id.main_transport_indicator);
editButton = itemView.findViewById(R.id.edit_button);
deleteButton = itemView.findViewById(R.id.delete_button);
}
public void bind(EnteredLoginParam transport, boolean isMain, OnTransportClickListener listener) {
emailText.setText(transport.addr);
String serverInfo = "";
if (transport.imapServer != null && !transport.imapServer.isEmpty()) {
serverInfo = transport.imapServer;
}
serverText.setText(serverInfo);
serverText.setVisibility(serverInfo.isEmpty() ? View.GONE : View.VISIBLE);
mainIndicator.setVisibility(isMain ? View.VISIBLE : View.GONE);
itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onTransportClick(transport);
}
});
editButton.setOnClickListener(v -> {
if (listener != null) {
listener.onTransportEdit(transport);
}
});
deleteButton.setOnClickListener(v -> {
if (listener != null) {
listener.onTransportDelete(transport);
}
});
}
}
}
@@ -31,7 +31,7 @@ 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.TransportListActivity;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.proxy.ProxySettingsActivity;
@@ -166,7 +166,7 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment
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);
if (!result) {
openRegistrationActivity();
openTransportListActivity();
}
return true;
}));
@@ -203,7 +203,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();
openTransportListActivity();
}
}
@@ -270,8 +270,8 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment
}
}
private void openRegistrationActivity() {
Intent intent = new Intent(requireActivity(), EditTransportActivity.class);
private void openTransportListActivity() {
Intent intent = new Intent(requireActivity(), TransportListActivity.class);
startActivity(intent);
}
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:background="?attr/colorPrimary">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:elevation="0dp">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:minHeight="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:contentInsetStart="14dp"
app:contentInsetLeft="14dp"
android:theme="?attr/actionBarStyle" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/transport_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/windowBackground"
android:clipToPadding="false"
android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/transport_list_item" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_add_transport"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
android:contentDescription="@string/add_transport"
android:src="@drawable/ic_add_white_24dp" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="?attr/selectableItemBackground"
android:minHeight="72dp"
android:gravity="center_vertical">
<ImageView
android:id="@+id/main_transport_indicator"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:src="@drawable/ic_check_white_24dp"
android:tint="?attr/colorPrimary"
android:contentDescription="@string/main"
android:visibility="gone"
tools:visibility="visible" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/transport_email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="16sp"
android:textColor="?android:attr/textColorPrimary"
tools:text="user@example.com" />
<TextView
android:id="@+id/transport_server"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="14sp"
android:textColor="?android:attr/textColorSecondary"
tools:text="imap.example.com" />
</LinearLayout>
<ImageView
android:id="@+id/edit_button"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="12dp"
android:src="@drawable/ic_create_white_24dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/edit_transport"
android:tint="?android:attr/textColorSecondary" />
<ImageView
android:id="@+id/delete_button"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="12dp"
android:src="@drawable/ic_delete_white_24dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/remove_transport"
android:tint="?android:attr/textColorSecondary" />
</LinearLayout>
+1
View File
@@ -693,6 +693,7 @@
<string name="add_transport">Add Transport</string>
<string name="remove_transport">Remove Transport</string>
<string name="edit_transport">Edit Transport</string>
<string name="main">Main</string>
<!-- shown if a QR code was scanned that can be used as a transport -->
<string name="confirm_add_transport">Add this transport?</string>
<string name="invalid_transport_qr">The scanned QR code does not contain a valid transport.</string>