PublishProfilePictureActivity.java

  1package eu.siacs.conversations.ui;
  2
  3import android.content.Intent;
  4import android.graphics.Bitmap;
  5import android.net.Uri;
  6import android.os.Bundle;
  7import android.util.Log;
  8import android.view.Menu;
  9import android.view.MenuItem;
 10import android.view.View;
 11import android.view.View.OnLongClickListener;
 12import android.widget.Toast;
 13import androidx.activity.result.ActivityResultLauncher;
 14import androidx.annotation.NonNull;
 15import androidx.annotation.StringRes;
 16import androidx.databinding.DataBindingUtil;
 17import com.canhub.cropper.CropImageContract;
 18import com.canhub.cropper.CropImageContractOptions;
 19import com.canhub.cropper.CropImageOptions;
 20import com.google.android.material.dialog.MaterialAlertDialogBuilder;
 21import eu.siacs.conversations.Config;
 22import eu.siacs.conversations.R;
 23import eu.siacs.conversations.databinding.ActivityPublishProfilePictureBinding;
 24import eu.siacs.conversations.entities.Account;
 25import eu.siacs.conversations.services.XmppConnectionService;
 26import eu.siacs.conversations.ui.interfaces.OnAvatarPublication;
 27import eu.siacs.conversations.utils.PhoneHelper;
 28
 29public class PublishProfilePictureActivity extends XmppActivity
 30        implements XmppConnectionService.OnAccountUpdate, OnAvatarPublication {
 31
 32    public static final int REQUEST_CHOOSE_PICTURE = 0x1337;
 33
 34    private ActivityPublishProfilePictureBinding binding;
 35    private Uri avatarUri;
 36    private Uri defaultUri;
 37    private Account account;
 38    private boolean support = false;
 39    private boolean publishing = false;
 40    private final OnLongClickListener backToDefaultListener =
 41            new OnLongClickListener() {
 42
 43                @Override
 44                public boolean onLongClick(View v) {
 45                    avatarUri = defaultUri;
 46                    loadImageIntoPreview(defaultUri);
 47                    return true;
 48                }
 49            };
 50    private boolean mInitialAccountSetup;
 51
 52    final ActivityResultLauncher<CropImageContractOptions> cropImage =
 53            registerForActivityResult(
 54                    new CropImageContract(),
 55                    cropResult -> {
 56                        if (cropResult.isSuccessful()) {
 57                            onAvatarPicked(cropResult.getUriContent());
 58                        }
 59                    });
 60
 61    @Override
 62    public void onAvatarPublicationSucceeded() {
 63        runOnUiThread(
 64                () -> {
 65                    if (mInitialAccountSetup) {
 66                        Intent intent =
 67                                new Intent(
 68                                        getApplicationContext(), StartConversationActivity.class);
 69                        StartConversationActivity.addInviteUri(intent, getIntent());
 70                        intent.putExtra("init", true);
 71                        intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString());
 72                        startActivity(intent);
 73                    }
 74                    Toast.makeText(
 75                                    PublishProfilePictureActivity.this,
 76                                    R.string.avatar_has_been_published,
 77                                    Toast.LENGTH_SHORT)
 78                            .show();
 79                    finish();
 80                });
 81    }
 82
 83    @Override
 84    public void onAvatarPublicationFailed(final int res) {
 85        runOnUiThread(
 86                () -> {
 87                    this.binding.hintOrWarning.setText(res);
 88                    this.binding.hintOrWarning.setVisibility(View.VISIBLE);
 89                    this.publishing = false;
 90                    togglePublishButton(true, R.string.publish);
 91                });
 92    }
 93
 94    @Override
 95    public void onCreate(final Bundle savedInstanceState) {
 96
 97        super.onCreate(savedInstanceState);
 98
 99        this.binding =
100                DataBindingUtil.setContentView(this, R.layout.activity_publish_profile_picture);
101
102        setSupportActionBar(binding.toolbar);
103
104        Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
105
106        this.binding.publishButton.setOnClickListener(
107                v -> {
108                    final boolean open = !this.binding.contactOnly.isChecked();
109                    final var uri = this.avatarUri;
110                    if (uri == null) {
111                        return;
112                    }
113                    publishing = true;
114                    togglePublishButton(false, R.string.publishing);
115                    xmppConnectionService.publishAvatar(account, uri, open, this);
116                });
117        this.binding.cancelButton.setOnClickListener(
118                v -> {
119                    if (mInitialAccountSetup) {
120                        final Intent intent =
121                                new Intent(
122                                        getApplicationContext(), StartConversationActivity.class);
123                        if (xmppConnectionService != null
124                                && xmppConnectionService.getAccounts().size() == 1) {
125                            intent.putExtra("init", true);
126                        }
127                        StartConversationActivity.addInviteUri(intent, getIntent());
128                        if (account != null) {
129                            intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString());
130                        }
131                        startActivity(intent);
132                    }
133                    finish();
134                });
135        this.binding.accountImage.setOnClickListener(v -> pickAvatar(null));
136        this.defaultUri = PhoneHelper.getProfilePictureUri(getApplicationContext());
137        if (savedInstanceState != null) {
138            this.avatarUri = savedInstanceState.getParcelable("uri");
139        }
140    }
141
142    @Override
143    public boolean onCreateOptionsMenu(@NonNull final Menu menu) {
144        getMenuInflater().inflate(R.menu.activity_publish_profile_picture, menu);
145        return true;
146    }
147
148    @Override
149    public boolean onOptionsItemSelected(final MenuItem item) {
150        if (item.getItemId() == R.id.action_delete_avatar) {
151            if (account != null) {
152                deleteAvatar(account);
153            }
154            return true;
155        } else {
156            return super.onOptionsItemSelected(item);
157        }
158    }
159
160    private void deleteAvatar(final Account account) {
161        new MaterialAlertDialogBuilder(this)
162                .setTitle(R.string.delete_avatar)
163                .setMessage(R.string.delete_avatar_message)
164                .setNegativeButton(R.string.cancel, null)
165                .setPositiveButton(
166                        R.string.confirm,
167                        (d, v) -> {
168                            if (xmppConnectionService != null) {
169                                xmppConnectionService.deleteAvatar(account);
170                            }
171                        })
172                .create()
173                .show();
174    }
175
176    @Override
177    public void onSaveInstanceState(@NonNull Bundle outState) {
178        if (this.avatarUri != null) {
179            outState.putParcelable("uri", this.avatarUri);
180        }
181        super.onSaveInstanceState(outState);
182    }
183
184    public void pickAvatar(final Uri image) {
185        this.cropImage.launch(new CropImageContractOptions(image, getCropImageOptions()));
186    }
187
188    public static CropImageOptions getCropImageOptions() {
189        final var cropImageOptions = new CropImageOptions();
190        cropImageOptions.aspectRatioX = 1;
191        cropImageOptions.aspectRatioY = 1;
192        cropImageOptions.fixAspectRatio = true;
193        cropImageOptions.outputCompressFormat = Bitmap.CompressFormat.PNG;
194        cropImageOptions.imageSourceIncludeCamera = false;
195        cropImageOptions.minCropResultHeight = Config.AVATAR_THUMBNAIL_SIZE;
196        cropImageOptions.minCropResultWidth = Config.AVATAR_THUMBNAIL_SIZE;
197        return cropImageOptions;
198    }
199
200    private void onAvatarPicked(final Uri uri) {
201        Log.d(Config.LOGTAG, "onAvatarPicked(" + uri + ")");
202        this.avatarUri = uri;
203        if (xmppConnectionServiceBound) {
204            loadImageIntoPreview(uri);
205        } else {
206            Log.d(Config.LOGTAG, "not ready during avatarPick");
207        }
208    }
209
210    @Override
211    protected void onBackendConnected() {
212        this.account = extractAccount(getIntent());
213        if (this.account != null) {
214            reloadAvatar();
215        }
216    }
217
218    private void reloadAvatar() {
219        this.support =
220                this.account.getXmppConnection() != null
221                        && this.account.getXmppConnection().getFeatures().pep();
222        if (this.avatarUri == null) {
223            if (this.account.getAvatar() != null || this.defaultUri == null) {
224                loadImageIntoPreview(null);
225            } else {
226                this.avatarUri = this.defaultUri;
227                loadImageIntoPreview(this.defaultUri);
228            }
229        } else {
230            loadImageIntoPreview(avatarUri);
231        }
232    }
233
234    @Override
235    public void onStart() {
236        super.onStart();
237        final Intent intent = getIntent();
238        if (intent == null) {
239            return;
240        }
241        this.mInitialAccountSetup = intent.getBooleanExtra("setup", false);
242
243        final var data = intent.getData();
244        final var account = intent.getStringExtra(EXTRA_ACCOUNT);
245        if (Intent.ACTION_ATTACH_DATA.equals(intent.getAction())
246                && data != null
247                && account != null) {
248            pickAvatar(data);
249            final var replacement = new Intent(Intent.ACTION_MAIN);
250            replacement.putExtra(EXTRA_ACCOUNT, account);
251            setIntent(replacement);
252            return;
253        }
254
255        if (this.mInitialAccountSetup) {
256            this.binding.cancelButton.setText(R.string.skip);
257        }
258        configureActionBar(getSupportActionBar(), !this.mInitialAccountSetup);
259    }
260
261    protected void loadImageIntoPreview(final Uri uri) {
262        Log.d(Config.LOGTAG, "loadImageIntoPreview(" + uri + ")");
263        final Bitmap bitmap;
264        if (uri == null) {
265            bitmap =
266                    avatarService()
267                            .get(
268                                    account,
269                                    (int) getResources().getDimension(R.dimen.publish_avatar_size));
270        } else {
271            bitmap =
272                    xmppConnectionService
273                            .getFileBackend()
274                            .cropCenterSquare(
275                                    uri,
276                                    (int) getResources().getDimension(R.dimen.publish_avatar_size));
277        }
278
279        if (bitmap == null) {
280            togglePublishButton(false, R.string.publish);
281            this.binding.hintOrWarning.setVisibility(View.VISIBLE);
282            this.binding.hintOrWarning.setText(R.string.error_publish_avatar_converting);
283            return;
284        }
285        this.binding.accountImage.setImageBitmap(bitmap);
286        if (support) {
287            togglePublishButton(uri != null, R.string.publish);
288            this.binding.hintOrWarning.setVisibility(View.INVISIBLE);
289        } else {
290            togglePublishButton(false, R.string.publish);
291            this.binding.hintOrWarning.setVisibility(View.VISIBLE);
292            if (account.getStatus() == Account.State.ONLINE) {
293                this.binding.hintOrWarning.setText(R.string.error_publish_avatar_no_server_support);
294            } else {
295                this.binding.hintOrWarning.setText(R.string.error_publish_avatar_offline);
296            }
297        }
298        if (this.defaultUri == null || this.defaultUri.equals(uri)) {
299            this.binding.secondaryHint.setVisibility(View.INVISIBLE);
300            this.binding.accountImage.setOnLongClickListener(null);
301        } else if (this.defaultUri != null) {
302            this.binding.secondaryHint.setVisibility(View.VISIBLE);
303            this.binding.accountImage.setOnLongClickListener(this.backToDefaultListener);
304        }
305    }
306
307    protected void togglePublishButton(boolean enabled, @StringRes int res) {
308        final boolean status = enabled && !publishing;
309        this.binding.publishButton.setText(publishing ? R.string.publishing : res);
310        this.binding.publishButton.setEnabled(status);
311    }
312
313    public void refreshUiReal() {
314        if (this.account != null) {
315            reloadAvatar();
316        }
317    }
318
319    @Override
320    public void onAccountUpdate() {
321        refreshUi();
322    }
323}