diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 178898ae4..cb7579214 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -4577,23 +4577,32 @@ public class XmppConnectionService extends Service { }).start(); } - public void publishAvatar(final Account account, final Uri image, final OnAvatarPublication callback) { - new Thread(() -> { - final Bitmap.CompressFormat format = Config.AVATAR_FORMAT; - final int size = Config.AVATAR_SIZE; - final Avatar avatar = getFileBackend().getPepAvatar(image, size, format); - if (avatar != null) { - if (!getFileBackend().save(avatar)) { - Log.d(Config.LOGTAG, "unable to save vcard"); - callback.onAvatarPublicationFailed(R.string.error_saving_avatar); - return; - } - publishAvatar(account, avatar, callback); - } else { - callback.onAvatarPublicationFailed(R.string.error_publish_avatar_converting); - } - }).start(); + public void publishAvatarAsync( + final Account account, + final Uri image, + final boolean open, + final OnAvatarPublication callback) { + new Thread(() -> publishAvatar(account, image, open, callback)).start(); + } + private void publishAvatar( + final Account account, + final Uri image, + final boolean open, + final OnAvatarPublication callback) { + final Bitmap.CompressFormat format = Config.AVATAR_FORMAT; + final int size = Config.AVATAR_SIZE; + final Avatar avatar = getFileBackend().getPepAvatar(image, size, format); + if (avatar != null) { + if (!getFileBackend().save(avatar)) { + Log.d(Config.LOGTAG, "unable to save vcard"); + callback.onAvatarPublicationFailed(R.string.error_saving_avatar); + return; + } + publishAvatar(account, avatar, open, callback); + } else { + callback.onAvatarPublicationFailed(R.string.error_publish_avatar_converting); + } } private void publishMucAvatar(Conversation conversation, Avatar avatar, OnAvatarPublication callback) { @@ -4631,10 +4640,14 @@ public class XmppConnectionService extends Service { }); } - public void publishAvatar(Account account, final Avatar avatar, final OnAvatarPublication callback) { + public void publishAvatar( + final Account account, + final Avatar avatar, + final boolean open, + final OnAvatarPublication callback) { final Bundle options; if (account.getXmppConnection().getFeatures().pepPublishOptions()) { - options = PublishOptions.openAccess(); + options = open ? PublishOptions.openAccess() : PublishOptions.presenceAccess(); } else { options = null; } @@ -4706,7 +4719,7 @@ public class XmppConnectionService extends Service { }); } - public void republishAvatarIfNeeded(Account account) { + public void republishAvatarIfNeeded(final Account account) { if (account.getAxolotlService().isPepBroken()) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": skipping republication of avatar because pep is broken"); return; @@ -4735,12 +4748,19 @@ public class XmppConnectionService extends Service { @Override public void accept(final Iq packet) { if (packet.getType() == Iq.Type.RESULT || errorIsItemNotFound(packet)) { - Avatar serverAvatar = parseAvatar(packet); + final Avatar serverAvatar = parseAvatar(packet); if (serverAvatar == null && account.getAvatar() != null) { - Avatar avatar = fileBackend.getStoredPepAvatar(account.getAvatar()); + final Avatar avatar = + fileBackend.getStoredPepAvatar(account.getAvatar()); if (avatar != null) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": avatar on server was null. republishing"); - publishAvatar(account, fileBackend.getStoredPepAvatar(account.getAvatar()), null); + // publishing as 'open' - old server (that requires + // republication) likely doesn't support access models anyway + publishAvatar( + account, + fileBackend.getStoredPepAvatar(account.getAvatar()), + true, + null); } else { Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": error rereading avatar"); } diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java index 11568dc03..525c42da0 100644 --- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java @@ -14,19 +14,12 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnLongClickListener; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.databinding.DataBindingUtil; - import com.canhub.cropper.CropImage; - -import java.util.concurrent.atomic.AtomicBoolean; - +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityPublishProfilePictureBinding; @@ -35,89 +28,97 @@ import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.interfaces.OnAvatarPublication; import eu.siacs.conversations.utils.PhoneHelper; +import java.util.concurrent.atomic.AtomicBoolean; -public class PublishProfilePictureActivity extends XmppActivity implements XmppConnectionService.OnAccountUpdate, OnAvatarPublication { - +public class PublishProfilePictureActivity extends XmppActivity + implements XmppConnectionService.OnAccountUpdate, OnAvatarPublication { public static final int REQUEST_CHOOSE_PICTURE = 0x1337; - private ImageView avatar; - private TextView hintOrWarning; - private TextView secondaryHint; - private Button cancelButton; - private Button publishButton; + private ActivityPublishProfilePictureBinding binding; private Uri avatarUri; private Uri defaultUri; private Account account; private boolean support = false; private boolean publishing = false; private final AtomicBoolean handledExternalUri = new AtomicBoolean(false); - private final OnLongClickListener backToDefaultListener = new OnLongClickListener() { + private final OnLongClickListener backToDefaultListener = + new OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - avatarUri = defaultUri; - loadImageIntoPreview(defaultUri); - return true; - } - }; + @Override + public boolean onLongClick(View v) { + avatarUri = defaultUri; + loadImageIntoPreview(defaultUri); + return true; + } + }; private boolean mInitialAccountSetup; @Override public void onAvatarPublicationSucceeded() { - runOnUiThread(() -> { - if (mInitialAccountSetup) { - Intent intent = new Intent(getApplicationContext(), StartConversationActivity.class); - StartConversationActivity.addInviteUri(intent, getIntent()); - intent.putExtra("init", true); - intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); - startActivity(intent); - } - Toast.makeText(PublishProfilePictureActivity.this, - R.string.avatar_has_been_published, - Toast.LENGTH_SHORT).show(); - finish(); - }); + runOnUiThread( + () -> { + if (mInitialAccountSetup) { + Intent intent = + new Intent( + getApplicationContext(), StartConversationActivity.class); + StartConversationActivity.addInviteUri(intent, getIntent()); + intent.putExtra("init", true); + intent.putExtra( + EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); + startActivity(intent); + } + Toast.makeText( + PublishProfilePictureActivity.this, + R.string.avatar_has_been_published, + Toast.LENGTH_SHORT) + .show(); + finish(); + }); } @Override - public void onAvatarPublicationFailed(int res) { - runOnUiThread(() -> { - hintOrWarning.setText(res); - hintOrWarning.setVisibility(View.VISIBLE); - publishing = false; - togglePublishButton(true, R.string.publish); - }); + public void onAvatarPublicationFailed(final int res) { + runOnUiThread( + () -> { + this.binding.hintOrWarning.setText(res); + this.binding.hintOrWarning.setVisibility(View.VISIBLE); + this.publishing = false; + togglePublishButton(true, R.string.publish); + }); } @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - ActivityPublishProfilePictureBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_publish_profile_picture); + this.binding = + DataBindingUtil.setContentView(this, R.layout.activity_publish_profile_picture); setSupportActionBar(binding.toolbar); Activities.setStatusAndNavigationBarColors(this, binding.getRoot()); - this.avatar = findViewById(R.id.account_image); - this.cancelButton = findViewById(R.id.cancel_button); - this.publishButton = findViewById(R.id.publish_button); - this.hintOrWarning = findViewById(R.id.hint_or_warning); - this.secondaryHint = findViewById(R.id.secondary_hint); - this.publishButton.setOnClickListener(v -> { - if (avatarUri != null) { - publishing = true; - togglePublishButton(false, R.string.publishing); - xmppConnectionService.publishAvatar(account, avatarUri, this); - } - }); - this.cancelButton.setOnClickListener( + this.binding.publishButton.setOnClickListener( + v -> { + final boolean open = !this.binding.contactOnly.isChecked(); + final var uri = this.avatarUri; + if (uri == null) { + return; + } + publishing = true; + togglePublishButton(false, R.string.publishing); + xmppConnectionService.publishAvatarAsync(account, uri, open, this); + }); + this.binding.cancelButton.setOnClickListener( v -> { if (mInitialAccountSetup) { final Intent intent = new Intent( getApplicationContext(), StartConversationActivity.class); - intent.putExtra("init", true); + if (xmppConnectionService != null + && xmppConnectionService.getAccounts().size() == 1) { + intent.putExtra("init", true); + } StartConversationActivity.addInviteUri(intent, getIntent()); if (account != null) { intent.putExtra( @@ -127,11 +128,12 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC } finish(); }); - this.avatar.setOnClickListener(v -> chooseAvatar(this)); + this.binding.accountImage.setOnClickListener(v -> chooseAvatar(this)); this.defaultUri = PhoneHelper.getProfilePictureUri(getApplicationContext()); if (savedInstanceState != null) { this.avatarUri = savedInstanceState.getParcelable("uri"); - this.handledExternalUri.set(savedInstanceState.getBoolean("handle_external_uri",false)); + this.handledExternalUri.set( + savedInstanceState.getBoolean("handle_external_uri", false)); } } @@ -144,8 +146,8 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC @Override public boolean onOptionsItemSelected(final MenuItem item) { if (item.getItemId() == R.id.action_delete_avatar) { - if (xmppConnectionService != null && account != null) { - xmppConnectionService.deleteAvatar(account); + if (account != null) { + deleteAvatar(account); } return true; } else { @@ -153,6 +155,22 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC } } + private void deleteAvatar(final Account account) { + new MaterialAlertDialogBuilder(this) + .setTitle(R.string.delete_avatar) + .setMessage(R.string.delete_avatar_message) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton( + R.string.confirm, + (d, v) -> { + if (xmppConnectionService != null) { + xmppConnectionService.deleteAvatar(account); + } + }) + .create() + .show(); + } + @Override public void onSaveInstanceState(@NonNull Bundle outState) { if (this.avatarUri != null) { @@ -191,9 +209,9 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/*"); activity.startActivityForResult( - Intent.createChooser(intent, activity.getString(R.string.attach_choose_picture)), - REQUEST_CHOOSE_PICTURE - ); + Intent.createChooser( + intent, activity.getString(R.string.attach_choose_picture)), + REQUEST_CHOOSE_PICTURE); } else { CropImage.activity() .setOutputCompressFormat(Bitmap.CompressFormat.PNG) @@ -212,7 +230,9 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC } private void reloadAvatar() { - this.support = this.account.getXmppConnection() != null && this.account.getXmppConnection().getFeatures().pep(); + this.support = + this.account.getXmppConnection() != null + && this.account.getXmppConnection().getFeatures().pep(); if (this.avatarUri == null) { if (this.account.getAvatar() != null || this.defaultUri == null) { loadImageIntoPreview(null); @@ -233,21 +253,22 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC final Uri uri = intent != null ? intent.getData() : null; - if (uri != null && handledExternalUri.compareAndSet(false,true)) { + if (uri != null && handledExternalUri.compareAndSet(false, true)) { cropUri(uri); return; } if (this.mInitialAccountSetup) { - this.cancelButton.setText(R.string.skip); + this.binding.cancelButton.setText(R.string.skip); } - configureActionBar(getSupportActionBar(), !this.mInitialAccountSetup && !handledExternalUri.get()); + configureActionBar( + getSupportActionBar(), !this.mInitialAccountSetup && !handledExternalUri.get()); } public void cropUri(final Uri uri) { if (Build.VERSION.SDK_INT >= 28) { loadImageIntoPreview(uri); - if (this.avatar.getDrawable() instanceof AnimatedImageDrawable || this.avatar.getDrawable() instanceof FileBackend.SVGDrawable) { + if (this.binding.accountImage.getDrawable() instanceof AnimatedImageDrawable || this.binding.accountImage.getDrawable() instanceof FileBackend.SVGDrawable) { this.avatarUri = uri; return; } @@ -259,7 +280,7 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC .start(this); } - protected void loadImageIntoPreview(Uri uri) { + protected void loadImageIntoPreview(final Uri uri) { Drawable bm = null; if (uri == null) { @@ -267,46 +288,46 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC } else { try { bm = xmppConnectionService.getFileBackend().cropCenterSquareDrawable(uri, (int) getResources().getDimension(R.dimen.publish_avatar_size)); - } catch (Exception e) { + } catch (final Exception e) { Log.d(Config.LOGTAG, "unable to load bitmap into image view", e); } } if (bm == null) { togglePublishButton(false, R.string.publish); - this.hintOrWarning.setVisibility(View.VISIBLE); - this.hintOrWarning.setText(R.string.error_publish_avatar_converting); + this.binding.hintOrWarning.setVisibility(View.VISIBLE); + this.binding.hintOrWarning.setText(R.string.error_publish_avatar_converting); return; } - this.avatar.setImageDrawable(bm); + this.binding.accountImage.setImageDrawable(bm); if (Build.VERSION.SDK_INT >= 28 && bm instanceof AnimatedImageDrawable) { ((AnimatedImageDrawable) bm).start(); } if (support) { togglePublishButton(uri != null, R.string.publish); - this.hintOrWarning.setVisibility(View.INVISIBLE); + this.binding.hintOrWarning.setVisibility(View.INVISIBLE); } else { togglePublishButton(false, R.string.publish); - this.hintOrWarning.setVisibility(View.VISIBLE); + this.binding.hintOrWarning.setVisibility(View.VISIBLE); if (account.getStatus() == Account.State.ONLINE) { - this.hintOrWarning.setText(R.string.error_publish_avatar_no_server_support); + this.binding.hintOrWarning.setText(R.string.error_publish_avatar_no_server_support); } else { - this.hintOrWarning.setText(R.string.error_publish_avatar_offline); + this.binding.hintOrWarning.setText(R.string.error_publish_avatar_offline); } } if (this.defaultUri == null || this.defaultUri.equals(uri)) { - this.secondaryHint.setVisibility(View.INVISIBLE); - this.avatar.setOnLongClickListener(null); + this.binding.secondaryHint.setVisibility(View.INVISIBLE); + this.binding.accountImage.setOnLongClickListener(null); } else if (this.defaultUri != null) { - this.secondaryHint.setVisibility(View.VISIBLE); - this.avatar.setOnLongClickListener(this.backToDefaultListener); + this.binding.secondaryHint.setVisibility(View.VISIBLE); + this.binding.accountImage.setOnLongClickListener(this.backToDefaultListener); } } protected void togglePublishButton(boolean enabled, @StringRes int res) { final boolean status = enabled && !publishing; - this.publishButton.setText(publishing ? R.string.publishing : res); - this.publishButton.setEnabled(status); + this.binding.publishButton.setText(publishing ? R.string.publishing : res); + this.binding.publishButton.setEnabled(status); } public void refreshUiReal() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java b/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java index ee3770ead..c9b764752 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java +++ b/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java @@ -1,16 +1,13 @@ package eu.siacs.conversations.xmpp.pep; import android.os.Bundle; - import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import im.conversations.android.xmpp.model.stanza.Iq; public class PublishOptions { - private PublishOptions() { - - } + private PublishOptions() {} public static Bundle openAccess() { final Bundle options = new Bundle(); @@ -18,6 +15,12 @@ public class PublishOptions { return options; } + public static Bundle presenceAccess() { + final Bundle options = new Bundle(); + options.putString("pubsub#access_model", "presence"); + return options; + } + public static Bundle persistentWhitelistAccess() { final Bundle options = new Bundle(); options.putString("pubsub#persist_items", "true"); @@ -32,14 +35,15 @@ public class PublishOptions { options.putString("pubsub#send_last_published_item", "never"); options.putString("pubsub#max_items", "max"); options.putString("pubsub#notify_delete", "true"); - options.putString("pubsub#notify_retract", "true"); //one could also set notify=true on the retract + options.putString( + "pubsub#notify_retract", "true"); // one could also set notify=true on the retract return options; } public static boolean preconditionNotMet(Iq response) { - final Element error = response.getType() == Iq.Type.ERROR ? response.findChild("error") : null; + final Element error = + response.getType() == Iq.Type.ERROR ? response.findChild("error") : null; return error != null && error.hasChild("precondition-not-met", Namespace.PUBSUB_ERROR); } - } diff --git a/src/main/res/layout/activity_publish_profile_picture.xml b/src/main/res/layout/activity_publish_profile_picture.xml index a6c7c1fc0..f1974dc76 100644 --- a/src/main/res/layout/activity_publish_profile_picture.xml +++ b/src/main/res/layout/activity_publish_profile_picture.xml @@ -1,5 +1,6 @@ - + - + android:layout_above="@+id/button_bar" + android:layout_below="@id/app_bar_layout"> @@ -62,17 +64,26 @@ android:text="@string/or_long_press_for_default" android:textAppearance="?textAppearanceBodyMedium" /> + + + android:textColor="?colorError" + tools:text="@string/error_saving_avatar" /> - + + + Prefer IPv6 Use colored user names Use colored user names in group chats + Would you like to delete your avatar? Some clients might continue to display a cached copy of your avatar. + Show to contacts only \ No newline at end of file