Compare commits

..

4 Commits

Author SHA1 Message Date
copilot-swe-agent[bot] deb4e356bd Fix race condition by tracking removed contacts explicitly
Instead of comparing current members at result time (which can change),
ContactMultiSelectionActivity now tracks preselected contacts and
explicitly returns which ones were removed. This eliminates the race
condition where member list changes could cause accidental removals.

Changes:
- ContactMultiSelectionActivity: Store preselected contacts, calculate and return removed contacts
- ProfileFragment: Use explicitly removed contacts instead of calculating from current member list

Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2026-02-02 23:03:32 +00:00
copilot-swe-agent[bot] 88d6992a9e Optimize member removal with HashSet for O(1) lookup
- Use HashSet instead of List for checking member selection
- Improves performance from O(n²) to O(n) for large member lists

Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2026-02-02 17:20:55 +00:00
copilot-swe-agent[bot] 5e6b09031a Fix member removal in Add Members screen
- Modified ProfileFragment.onActivityResult() to handle both adding and removing members
- When unchecking members in the Add Members screen, they are now properly removed from the group
- Preserves existing functionality for adding new members

Co-authored-by: adbenitez <24558636+adbenitez@users.noreply.github.com>
2026-02-02 17:19:59 +00:00
copilot-swe-agent[bot] 115ebf52b6 Initial plan 2026-02-02 17:17:22 +00:00
5 changed files with 34 additions and 82 deletions
-1
View File
@@ -360,7 +360,6 @@
<activity android:name=".WebxdcActivity"
android:label=""
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="adjustPan"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode">
</activity>
@@ -23,7 +23,9 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Activity container for selecting a list of contacts.
@@ -34,6 +36,9 @@ import java.util.List;
public class ContactMultiSelectionActivity extends ContactSelectionActivity {
public static final String CONTACTS_EXTRA = "contacts_extra";
public static final String REMOVED_CONTACTS_EXTRA = "removed_contacts_extra";
private ArrayList<Integer> preselectedContacts;
@Override
protected void onCreate(Bundle icicle, boolean ready) {
@@ -44,6 +49,9 @@ public class ContactMultiSelectionActivity extends ContactSelectionActivity {
// it's a bit confusing having one "X" button on the left and one on the right -
// and the "clear search" button is not that important.
getToolbar().setUseClearButton(false);
// Store preselected contacts to track which ones were removed
preselectedContacts = getIntent().getIntegerArrayListExtra(ContactSelectionListFragment.PRESELECTED_CONTACTS);
}
@Override
@@ -72,6 +80,19 @@ public class ContactMultiSelectionActivity extends ContactSelectionActivity {
Intent resultIntent = getIntent();
List<Integer> selectedContacts = contactsFragment.getSelectedContacts();
resultIntent.putIntegerArrayListExtra(CONTACTS_EXTRA, new ArrayList<>(selectedContacts));
// Calculate which contacts were removed (preselected but not in final selection)
if (preselectedContacts != null) {
Set<Integer> selectedSet = new HashSet<>(selectedContacts);
ArrayList<Integer> removedContacts = new ArrayList<>();
for (Integer preselectedId : preselectedContacts) {
if (!selectedSet.contains(preselectedId)) {
removedContacts.add(preselectedId);
}
}
resultIntent.putIntegerArrayListExtra(REMOVED_CONTACTS_EXTRA, removedContacts);
}
setResult(RESULT_OK, resultIntent);
}
}
@@ -310,13 +310,24 @@ public class ProfileFragment extends Fragment
super.onActivityResult(requestCode, resultCode, data);
if (requestCode==REQUEST_CODE_PICK_CONTACT && resultCode==Activity.RESULT_OK && data!=null) {
List<Integer> selected = data.getIntegerArrayListExtra(ContactMultiSelectionActivity.CONTACTS_EXTRA);
List<Integer> removed = data.getIntegerArrayListExtra(ContactMultiSelectionActivity.REMOVED_CONTACTS_EXTRA);
if(selected == null) return;
Util.runOnAnyBackgroundThread(() -> {
// Add new members
for (Integer contactId : selected) {
if (contactId!=null) {
if (contactId != null) {
dcContext.addContactToChat(chatId, contactId);
}
}
// Remove members that were explicitly unchecked
if (removed != null) {
for (Integer contactId : removed) {
if (contactId != null) {
dcContext.removeContactFromChat(chatId, contactId);
}
}
}
});
}
}
@@ -77,8 +77,7 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity
findViewById(R.id.status_bar_background).setBackgroundResource(R.drawable.search_toolbar_shadow);
} else {
// add padding to avoid content hidden behind system bars
// Use applyWindowInsetsExcludingIme to respect navigation bars but not keyboard
ViewUtil.applyWindowInsetsExcludingIme(findViewById(R.id.content_container));
ViewUtil.applyWindowInsets(findViewById(R.id.content_container));
}
webView.setWebViewClient(new WebViewClient() {
@@ -316,23 +316,6 @@ public class ViewUtil {
return Insets.max(systemBars, displayCutout);
}
/**
* Get combined insets from status bar, navigation bar and display cutout areas,
* excluding the IME (soft keyboard).
*
* @param windowInsets The window insets to extract from
* @return Combined insets excluding IME
*/
private static Insets getCombinedInsetsExcludingIme(@NonNull WindowInsetsCompat windowInsets) {
Insets systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
Insets displayCutout = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout());
// Combine systemBars (which excludes IME) with displayCutout using max to handle notches/cutouts
Insets combined = Insets.max(systemBars, displayCutout);
return combined;
}
/**
* Apply window insets to a view by adding margin to avoid drawing it behind system bars.
* Convenience method that applies insets to all sides.
@@ -468,67 +451,6 @@ public class ViewUtil {
}
}
/**
* Apply window insets to a view by adding padding to avoid drawing elements behind system bars,
* excluding IME (soft keyboard) insets. This is useful for views that should respect navigation
* bars but handle keyboard resizing through the window's soft input mode.
*
* This method stores the original padding values in view tags to ensure that
* padding doesn't accumulate on multiple inset applications.
*
* Note: This feature is only enabled on API 30+ (Android 11+) where WindowInsets APIs
* work correctly. On older API levels, the method returns early and the view will use
* default system bar handling (content may be drawn behind system bars).
*
* @param view The view to apply insets to
*/
public static void applyWindowInsetsExcludingIme(@NonNull View view) {
// Only enable on API 30+ where WindowInsets APIs work correctly
if (!isEdgeToEdgeSupported()) return;
// Store the original padding as a tag only if not already stored
// This prevents losing the true original padding on subsequent calls
if (view.getTag(R.id.tag_window_insets_padding_left) == null) {
view.setTag(R.id.tag_window_insets_padding_left, view.getPaddingLeft());
view.setTag(R.id.tag_window_insets_padding_top, view.getPaddingTop());
view.setTag(R.id.tag_window_insets_padding_right, view.getPaddingRight());
view.setTag(R.id.tag_window_insets_padding_bottom, view.getPaddingBottom());
}
ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> {
Insets insets = getCombinedInsetsExcludingIme(windowInsets);
// Retrieve the original padding values from tags with null checks
Integer leftTag = (Integer) v.getTag(R.id.tag_window_insets_padding_left);
Integer topTag = (Integer) v.getTag(R.id.tag_window_insets_padding_top);
Integer rightTag = (Integer) v.getTag(R.id.tag_window_insets_padding_right);
Integer bottomTag = (Integer) v.getTag(R.id.tag_window_insets_padding_bottom);
int basePaddingLeft = leftTag != null ? leftTag : 0;
int basePaddingTop = topTag != null ? topTag : 0;
int basePaddingRight = rightTag != null ? rightTag : 0;
int basePaddingBottom = bottomTag != null ? bottomTag : 0;
v.setPadding(
basePaddingLeft + insets.left,
basePaddingTop + insets.top,
basePaddingRight + insets.right,
basePaddingBottom + insets.bottom
);
// Consume IME insets to prevent them from affecting child views (like WebView)
// This stops the double-padding issue where both window resize and inset padding occur
WindowInsetsCompat withoutIme = new WindowInsetsCompat.Builder(windowInsets)
.setInsets(WindowInsetsCompat.Type.ime(), Insets.NONE)
.build();
return withoutIme;
});
// Request the initial insets to be dispatched if the view is attached
if (view.isAttachedToWindow()) {
ViewCompat.requestApplyInsets(view);
}
}
/**
* Apply the top status bar inset as the height of a view.
*/