Detailed changes
  
  
    
    @@ -472,17 +472,17 @@ public class MucOptions {
         return subset;
     }
 
-    public static List<User> sub(List<User> users, int max) {
-        ArrayList<User> subset = new ArrayList<>();
-        HashSet<Jid> jids = new HashSet<>();
-        for (User user : users) {
-            jids.add(user.getAccount().getJid().asBareJid());
-            if (user.getRealJid() == null
-                    || (user.getRealJid().getLocal() != null && jids.add(user.getRealJid()))) {
+    public static List<User> sub(final List<User> users, final int max) {
+        final var subset = new ArrayList<User>();
+        final var addresses = new HashSet<Jid>();
+        for (final var user : users) {
+            addresses.add(user.getAccount().getJid().asBareJid());
+            final var address = user.getRealJid();
+            if (address == null || (address.getLocal() != null && addresses.add(address))) {
                 subset.add(user);
             }
             if (subset.size() >= max) {
-                break;
+                return subset;
             }
         }
         return subset;
  
  
  
    
    @@ -641,8 +641,12 @@ public class ConferenceDetailsActivity extends XmppActivity
                         }
                     }
                 });
-        this.mUserPreviewAdapter.submitList(
-                MucOptions.sub(users, GridManager.getCurrentColumnCount(binding.users)));
+        this.binding.users.post(
+                () -> {
+                    final var list =
+                            MucOptions.sub(users, GridManager.getCurrentColumnCount(binding.users));
+                    this.mUserPreviewAdapter.submitList(list);
+                });
         this.binding.invite.setVisibility(mucOptions.canInvite() ? View.VISIBLE : View.GONE);
         this.binding.showUsers.setVisibility(users.size() > 0 ? View.VISIBLE : View.GONE);
         this.binding.showUsers.setText(
  
  
  
    
    @@ -14,9 +14,11 @@ import android.preference.PreferenceManager;
 import android.provider.ContactsContract.CommonDataKinds;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Intents;
+import android.provider.Settings;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.style.RelativeSizeSpan;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -145,16 +147,14 @@ public class ContactDetailsActivity extends OmemoActivity
     private void checkContactPermissionAndShowAddDialog() {
         if (hasContactsPermission()) {
             showAddToPhoneBookDialog();
-        } else if (QuickConversationsService.isContactListIntegration(this)
-                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+        } else if (QuickConversationsService.isContactListIntegration(this)) {
             requestPermissions(
                     new String[] {Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
         }
     }
 
     private boolean hasContactsPermission() {
-        if (QuickConversationsService.isContactListIntegration(this)
-                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+        if (QuickConversationsService.isContactListIntegration(this)) {
             return checkSelfPermission(Manifest.permission.READ_CONTACTS)
                     == PackageManager.PERMISSION_GRANTED;
         } else {
@@ -175,7 +175,7 @@ public class ContactDetailsActivity extends OmemoActivity
             value = jid.toString();
         }
         final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
-        builder.setTitle(getString(R.string.action_add_phone_book));
+        builder.setTitle(getString(R.string.save_to_contact));
         builder.setMessage(getString(R.string.add_phone_book_text, value));
         builder.setNegativeButton(getString(R.string.cancel), null);
         builder.setPositiveButton(
@@ -295,14 +295,35 @@ public class ContactDetailsActivity extends OmemoActivity
             int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
         // TODO check for Camera / Scan permission
         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
-        if (grantResults.length > 0)
+        if (grantResults.length == 0) {
+            return;
+        }
+        if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) {
             if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-                if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) {
-                    showAddToPhoneBookDialog();
-                    xmppConnectionService.loadPhoneContacts();
-                    xmppConnectionService.startContactObserver();
-                }
+                showAddToPhoneBookDialog();
+                xmppConnectionService.loadPhoneContacts();
+                xmppConnectionService.startContactObserver();
+            } else {
+                showRedirectToAppSettings();
             }
+        }
+    }
+
+    private void showRedirectToAppSettings() {
+        final var dialogBuilder = new MaterialAlertDialogBuilder(this);
+        dialogBuilder.setTitle(R.string.save_to_contact);
+        dialogBuilder.setMessage(
+                getString(R.string.no_contacts_permission, getString(R.string.app_name)));
+        dialogBuilder.setPositiveButton(
+                R.string.continue_btn,
+                (d, w) -> {
+                    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+                    Uri uri = Uri.fromParts("package", getPackageName(), null);
+                    intent.setData(uri);
+                    startActivity(intent);
+                });
+        dialogBuilder.setNegativeButton(R.string.cancel, null);
+        dialogBuilder.create().show();
     }
 
     @Override
@@ -481,29 +502,41 @@ public class ContactDetailsActivity extends OmemoActivity
         }
 
         if (contact.isBlocked() && !this.showDynamicTags) {
-            binding.detailsLastseen.setVisibility(View.VISIBLE);
-            binding.detailsLastseen.setText(R.string.contact_blocked);
+            binding.detailsLastSeen.setVisibility(View.VISIBLE);
+            binding.detailsLastSeen.setText(R.string.contact_blocked);
         } else {
             if (showLastSeen
                     && contact.getLastseen() > 0
                     && contact.getPresences().allOrNonSupport(Namespace.IDLE)) {
-                binding.detailsLastseen.setVisibility(View.VISIBLE);
-                binding.detailsLastseen.setText(
+                binding.detailsLastSeen.setVisibility(View.VISIBLE);
+                binding.detailsLastSeen.setText(
                         UIHelper.lastseen(
                                 getApplicationContext(),
                                 contact.isActive(),
                                 contact.getLastseen()));
             } else {
-                binding.detailsLastseen.setVisibility(View.GONE);
+                binding.detailsLastSeen.setVisibility(View.GONE);
             }
         }
 
-        binding.detailsContactjid.setText(IrregularUnicodeDetector.style(this, contact.getJid()));
+        binding.detailsContactXmppAddress.setText(
+                IrregularUnicodeDetector.style(this, contact.getJid()));
         final String account = contact.getAccount().getJid().asBareJid().toString();
         binding.detailsAccount.setText(getString(R.string.using_account, account));
         AvatarWorkerTask.loadAvatar(
                 contact, binding.detailsContactBadge, R.dimen.avatar_on_details_screen_size);
         binding.detailsContactBadge.setOnClickListener(this::onBadgeClick);
+        if (QuickConversationsService.isContactListIntegration(this)) {
+            if (contact.getSystemAccount() == null) {
+                binding.addAddressBook.setText(R.string.save_to_contact);
+            } else {
+                binding.addAddressBook.setText(R.string.show_in_contacts);
+            }
+            binding.addAddressBook.setVisibility(View.VISIBLE);
+            binding.addAddressBook.setOnClickListener(this::onAddToAddressBookClick);
+        } else {
+            binding.addAddressBook.setVisibility(View.GONE);
+        }
 
         binding.detailsContactKeys.removeAllViews();
         boolean hasKeys = false;
  
  
  
    
    @@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M160,920L160,840L800,840L800,920L160,920ZM160,120L160,40L800,40L800,120L160,120ZM480,520Q530,520 565,485Q600,450 600,400Q600,350 565,315Q530,280 480,280Q430,280 395,315Q360,350 360,400Q360,450 395,485Q430,520 480,520ZM160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM230,720L730,720Q685,664 621,632Q557,600 480,600Q403,600 339,632Q275,664 230,720Z"/>
+</vector>
  
  
  
    
    @@ -46,76 +46,109 @@
 
                         <com.makeramen.roundedimageview.RoundedImageView
                             android:id="@+id/details_contact_badge"
-                            android:layout_width="@dimen/avatar_on_details_screen_size"
-                            android:layout_height="@dimen/avatar_on_details_screen_size"
+                            android:layout_width="@dimen/publish_avatar_size"
+                            android:layout_height="@dimen/publish_avatar_size"
                             android:layout_alignParentTop="true"
+                            android:layout_centerHorizontal="true"
                             android:scaleType="centerCrop"
                             app:riv_corner_radius="8dp" />
 
+                        <TextView
+                            android:id="@+id/details_contact_xmpp_address"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_below="@+id/details_contact_badge"
+                            android:layout_centerHorizontal="true"
+                            android:layout_marginTop="16sp"
+                            android:gravity="center_horizontal"
+                            android:minWidth="288dp"
+                            android:text="@string/account_settings_example_jabber_id"
+                            android:textAppearance="?textAppearanceTitleLarge" />
+
+
+
                         <LinearLayout
-                            android:id="@+id/details_jidbox"
+                            android:id="@+id/button_box"
                             android:layout_width="wrap_content"
                             android:layout_height="wrap_content"
-                            android:layout_marginLeft="16dp"
-                            android:layout_toRightOf="@+id/details_contact_badge"
-                            android:orientation="vertical">
+                            android:layout_below="@+id/details_contact_xmpp_address"
+                            android:layout_centerHorizontal="true"
+                            android:layout_marginTop="16dp"
+                            android:orientation="horizontal">
 
-                            <TextView
-                                android:id="@+id/details_contactjid"
+                            <Button
+                                android:id="@+id/add_contact_button"
                                 android:layout_width="wrap_content"
                                 android:layout_height="wrap_content"
-                                android:text="@string/account_settings_example_jabber_id"
-                                android:textAppearance="?textAppearanceTitleMedium" />
+                                android:layout_marginHorizontal="8dp"
+                                android:text="@string/add_contact" />
 
-                            <androidx.constraintlayout.widget.ConstraintLayout
-                                android:id="@+id/tags"
+                            <Button
+                                android:id="@+id/add_address_book"
+                                style="@style/Widget.Material3.Button.TonalButton"
                                 android:layout_width="wrap_content"
                                 android:layout_height="wrap_content"
-                                android:layout_marginTop="8sp">
-
-                                <androidx.constraintlayout.helper.widget.Flow
-                                    android:id="@+id/flow_widget"
-                                    android:layout_width="0dp"
-                                    android:layout_height="wrap_content"
-                                    app:flow_horizontalBias="0"
-                                    app:flow_horizontalGap="8sp"
-                                    app:flow_horizontalStyle="packed"
-                                    app:flow_verticalGap="4sp"
-                                    app:flow_wrapMode="chain"
-                                    app:layout_constraintEnd_toEndOf="parent"
-                                    app:layout_constraintStart_toStartOf="parent"
-                                    app:layout_constraintTop_toTopOf="parent" />
-                            </androidx.constraintlayout.widget.ConstraintLayout>
+                                android:layout_marginHorizontal="8dp"
+                                android:text="@string/save_to_contact"
+                                app:icon="@drawable/ic_contacts_24dp" />
+                        </LinearLayout>
 
-                            <TextView
-                                android:id="@+id/details_lastseen"
-                                android:layout_width="wrap_content"
+
+                        <androidx.constraintlayout.widget.ConstraintLayout
+                            android:id="@+id/tags"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_below="@+id/button_box"
+                            android:layout_centerHorizontal="true"
+                            android:layout_marginTop="16sp"
+                            android:visibility="gone">
+
+                            <androidx.constraintlayout.helper.widget.Flow
+                                android:id="@+id/flow_widget"
+                                android:layout_width="0dp"
                                 android:layout_height="wrap_content"
-                                android:layout_marginTop="4dp"
-                                android:textAppearance="?textAppearanceTitleSmall"
-                                tools:text="@string/just_now" />
+                                app:flow_horizontalBias="0"
+                                app:flow_horizontalGap="8sp"
+                                app:flow_horizontalStyle="packed"
+                                app:flow_verticalGap="4sp"
+                                app:flow_wrapMode="chain"
+                                app:layout_constraintEnd_toEndOf="parent"
+                                app:layout_constraintStart_toStartOf="parent"
+                                app:layout_constraintTop_toTopOf="parent" />
+                        </androidx.constraintlayout.widget.ConstraintLayout>
+
+
+                        <TextView
+                            android:id="@+id/details_last_seen"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_below="@+id/tags"
+                            android:layout_centerHorizontal="true"
+                            android:layout_marginTop="8sp"
+                            android:textAppearance="?textAppearanceTitleSmall"
+                            tools:text="@string/just_now" />
+
+                        <LinearLayout
+                            android:id="@+id/status_message_subscription_box"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_below="@+id/details_last_seen"
+                            android:layout_alignStart="@+id/details_contact_xmpp_address"
+                            android:layout_marginTop="8sp"
+                            android:orientation="vertical">
 
                             <TextView
                                 android:id="@+id/status_message"
                                 android:layout_width="wrap_content"
                                 android:layout_height="wrap_content"
-                                android:layout_marginTop="8dp"
+                                android:layout_marginBottom="8sp"
                                 android:textAppearance="?textAppearanceBodyMedium"
                                 tools:text="Hey there! Iām using Conversations" />
 
-                            <Button
-                                android:id="@+id/add_contact_button"
-                                style="@style/Widget.Material3.Button.ElevatedButton"
-                                android:layout_width="wrap_content"
-                                android:layout_height="wrap_content"
-                                android:layout_marginTop="8dp"
-                                android:text="@string/add_contact" />
-
                             <CheckBox
                                 android:id="@+id/details_send_presence"
                                 android:layout_width="wrap_content"
                                 android:layout_height="wrap_content"
-                                android:layout_marginTop="8dp"
                                 android:text="@string/send_presence_updates" />
 
                             <CheckBox
@@ -129,9 +162,10 @@
                             android:id="@+id/details_account"
                             android:layout_width="wrap_content"
                             android:layout_height="wrap_content"
-                            android:layout_below="@+id/details_jidbox"
+                            android:layout_below="@+id/status_message_subscription_box"
                             android:layout_alignParentEnd="true"
-                            android:layout_marginTop="32dp"
+                            android:layout_alignParentBottom="true"
+                            android:layout_marginTop="16sp"
                             android:text="@string/using_account"
                             android:textAppearance="?textAppearanceLabelMedium" />
                     </RelativeLayout>
  
  
  
    
    @@ -9,7 +9,6 @@
     <string name="channel_details">Channel details</string>
     <string name="action_add_account">Add account</string>
     <string name="action_edit_contact">Edit name</string>
-    <string name="action_add_phone_book">Add to address book</string>
     <string name="action_delete_contact">Delete from roster</string>
     <string name="action_block_contact">Block contact</string>
     <string name="action_unblock_contact">Unblock contact</string>
@@ -521,6 +520,7 @@
     <string name="shared_text_with_x">Text shared with %s</string>
     <string name="no_storage_permission">Grant %1$s access to external storage</string>
     <string name="no_camera_permission">Grant %1$s access to the camera</string>
+    <string name="no_contacts_permission">%1$s needs access to your contacts</string>
     <string name="quicksy_wants_your_consent">Quicksy asks for your consent to use your data</string>
     <string name="sync_with_contacts">Contact list integration</string>
     <string name="sync_with_contacts_long">%1$s processes your contact list locally, on your device, to show you the names and profile pictures for matching contacts on XMPP.\n\nNo contact list data ever leaves your device!</string>
@@ -1119,4 +1119,6 @@
     <string name="account_status_service_outage_scheduled">Planned Downtime</string>
     <string name="account_status_service_outage_known">Service Down (Known Issue)</string>
     <string name="sos_scheduled_return">The service is scheduled to return at %s</string>
+    <string name="save_to_contact">Save to Contacts</string>
+    <string name="show_in_contacts">Show in Contacts</string>
 </resources>