Rewrite EditProfileActivity in Kotlin (#525)
* rewrite EditProfileActivity in Kotlin * fix bug in MainActivity where profiles would duplicate * fix code style
This commit is contained in:
parent
0b59b8d0ac
commit
17a122b293
6 changed files with 548 additions and 579 deletions
|
@ -1,518 +0,0 @@
|
||||||
/* Copyright 2017 Andrew Dawson
|
|
||||||
*
|
|
||||||
* This file is a part of Tusky.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
|
||||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
|
||||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
|
||||||
* Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
|
||||||
* see <http://www.gnu.org/licenses>. */
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.design.widget.Snackbar;
|
|
||||||
import android.support.v4.app.ActivityCompat;
|
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.support.v7.app.ActionBar;
|
|
||||||
import android.support.v7.widget.Toolbar;
|
|
||||||
import android.util.Base64;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
|
|
||||||
import com.keylesspalace.tusky.entity.Account;
|
|
||||||
import com.keylesspalace.tusky.entity.Profile;
|
|
||||||
import com.keylesspalace.tusky.util.IOUtils;
|
|
||||||
import com.pkmmte.view.CircularImageView;
|
|
||||||
import com.squareup.picasso.Picasso;
|
|
||||||
import com.theartofdev.edmodo.cropper.CropImage;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import retrofit2.Call;
|
|
||||||
import retrofit2.Callback;
|
|
||||||
import retrofit2.Response;
|
|
||||||
|
|
||||||
public class EditProfileActivity extends BaseActivity {
|
|
||||||
private static final String TAG = "EditProfileActivity";
|
|
||||||
private static final int AVATAR_PICK_RESULT = 1;
|
|
||||||
private static final int HEADER_PICK_RESULT = 2;
|
|
||||||
private static final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1;
|
|
||||||
private static final int AVATAR_WIDTH = 120;
|
|
||||||
private static final int AVATAR_HEIGHT = 120;
|
|
||||||
private static final int HEADER_WIDTH = 700;
|
|
||||||
private static final int HEADER_HEIGHT = 335;
|
|
||||||
|
|
||||||
private enum PickType {
|
|
||||||
NOTHING,
|
|
||||||
AVATAR,
|
|
||||||
HEADER
|
|
||||||
}
|
|
||||||
|
|
||||||
private ImageView headerPreview;
|
|
||||||
private ProgressBar headerProgress;
|
|
||||||
private ImageButton avatarButton;
|
|
||||||
private ImageView avatarPreview;
|
|
||||||
private ProgressBar avatarProgress;
|
|
||||||
private EditText displayNameEditText;
|
|
||||||
private EditText noteEditText;
|
|
||||||
private ProgressBar saveProgress;
|
|
||||||
private String priorDisplayName;
|
|
||||||
private String priorNote;
|
|
||||||
private boolean isAlreadySaving;
|
|
||||||
private PickType currentlyPicking;
|
|
||||||
private String avatarBase64;
|
|
||||||
private String headerBase64;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_edit_profile);
|
|
||||||
|
|
||||||
ImageButton headerButton = findViewById(R.id.edit_profile_header);
|
|
||||||
headerPreview = findViewById(R.id.edit_profile_header_preview);
|
|
||||||
headerProgress = findViewById(R.id.edit_profile_header_progress);
|
|
||||||
avatarButton = findViewById(R.id.edit_profile_avatar);
|
|
||||||
avatarPreview = findViewById(R.id.edit_profile_avatar_preview);
|
|
||||||
avatarProgress = findViewById(R.id.edit_profile_avatar_progress);
|
|
||||||
displayNameEditText = findViewById(R.id.edit_profile_display_name);
|
|
||||||
noteEditText = findViewById(R.id.edit_profile_note);
|
|
||||||
saveProgress = findViewById(R.id.edit_profile_save_progress);
|
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
|
||||||
setSupportActionBar(toolbar);
|
|
||||||
ActionBar actionBar = getSupportActionBar();
|
|
||||||
if (actionBar != null) {
|
|
||||||
actionBar.setTitle(R.string.title_edit_profile);
|
|
||||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
|
||||||
actionBar.setDisplayShowHomeEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
priorDisplayName = savedInstanceState.getString("priorDisplayName");
|
|
||||||
priorNote = savedInstanceState.getString("priorNote");
|
|
||||||
isAlreadySaving = savedInstanceState.getBoolean("isAlreadySaving");
|
|
||||||
currentlyPicking = (PickType) savedInstanceState.getSerializable("currentlyPicking");
|
|
||||||
avatarBase64 = savedInstanceState.getString("avatarBase64");
|
|
||||||
headerBase64 = savedInstanceState.getString("headerBase64");
|
|
||||||
} else {
|
|
||||||
priorDisplayName = null;
|
|
||||||
priorNote = null;
|
|
||||||
isAlreadySaving = false;
|
|
||||||
currentlyPicking = PickType.NOTHING;
|
|
||||||
avatarBase64 = null;
|
|
||||||
headerBase64 = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
avatarButton.setOnClickListener(v -> onMediaPick(PickType.AVATAR));
|
|
||||||
headerButton.setOnClickListener(v -> onMediaPick(PickType.HEADER));
|
|
||||||
|
|
||||||
avatarPreview.setOnClickListener(v -> {
|
|
||||||
avatarPreview.setImageBitmap(null);
|
|
||||||
avatarPreview.setVisibility(View.INVISIBLE);
|
|
||||||
avatarBase64 = null;
|
|
||||||
});
|
|
||||||
headerPreview.setOnClickListener(v -> {
|
|
||||||
headerPreview.setImageBitmap(null);
|
|
||||||
headerPreview.setVisibility(View.INVISIBLE);
|
|
||||||
headerBase64 = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
mastodonApi.accountVerifyCredentials().enqueue(new Callback<Account>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<Account> call, @NonNull Response<Account> response) {
|
|
||||||
if (!response.isSuccessful()) {
|
|
||||||
onAccountVerifyCredentialsFailed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Account me = response.body();
|
|
||||||
priorDisplayName = me.getDisplayName();
|
|
||||||
priorNote = me.note.toString();
|
|
||||||
CircularImageView avatar =
|
|
||||||
findViewById(R.id.edit_profile_avatar_preview);
|
|
||||||
ImageView header = findViewById(R.id.edit_profile_header_preview);
|
|
||||||
|
|
||||||
displayNameEditText.setText(priorDisplayName);
|
|
||||||
noteEditText.setText(priorNote);
|
|
||||||
Picasso.with(avatar.getContext())
|
|
||||||
.load(me.avatar)
|
|
||||||
.placeholder(R.drawable.avatar_default)
|
|
||||||
.into(avatar);
|
|
||||||
Picasso.with(header.getContext())
|
|
||||||
.load(me.header)
|
|
||||||
.placeholder(R.drawable.account_header_default)
|
|
||||||
.into(header);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<Account> call, @NonNull Throwable t) {
|
|
||||||
onAccountVerifyCredentialsFailed();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
|
||||||
outState.putString("priorDisplayName", priorDisplayName);
|
|
||||||
outState.putString("priorNote", priorNote);
|
|
||||||
outState.putBoolean("isAlreadySaving", isAlreadySaving);
|
|
||||||
outState.putSerializable("currentlyPicking", currentlyPicking);
|
|
||||||
outState.putString("avatarBase64", avatarBase64);
|
|
||||||
outState.putString("headerBase64", headerBase64);
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onAccountVerifyCredentialsFailed() {
|
|
||||||
Log.e(TAG, "The account failed to load.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onMediaPick(PickType pickType) {
|
|
||||||
if (currentlyPicking != PickType.NOTHING) {
|
|
||||||
// Ignore inputs if another pick operation is still occurring.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentlyPicking = pickType;
|
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
|
||||||
!= PackageManager.PERMISSION_GRANTED) {
|
|
||||||
ActivityCompat.requestPermissions(this,
|
|
||||||
new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
|
|
||||||
PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);
|
|
||||||
} else {
|
|
||||||
initiateMediaPicking();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
|
|
||||||
@NonNull int[] grantResults) {
|
|
||||||
switch (requestCode) {
|
|
||||||
case PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE: {
|
|
||||||
if (grantResults.length > 0
|
|
||||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
initiateMediaPicking();
|
|
||||||
} else {
|
|
||||||
endMediaPicking();
|
|
||||||
Snackbar.make(avatarButton, R.string.error_media_upload_permission,
|
|
||||||
Snackbar.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initiateMediaPicking() {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
|
||||||
intent.setType("image/*");
|
|
||||||
switch (currentlyPicking) {
|
|
||||||
case AVATAR: { startActivityForResult(intent, AVATAR_PICK_RESULT); break; }
|
|
||||||
case HEADER: { startActivityForResult(intent, HEADER_PICK_RESULT); break; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
getMenuInflater().inflate(R.menu.edit_profile_toolbar, menu);
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
switch (item.getItemId()) {
|
|
||||||
case android.R.id.home: {
|
|
||||||
onBackPressed();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case R.id.action_save: {
|
|
||||||
save();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void save() {
|
|
||||||
if (isAlreadySaving || currentlyPicking != PickType.NOTHING) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String newDisplayName = displayNameEditText.getText().toString();
|
|
||||||
if (newDisplayName.isEmpty()) {
|
|
||||||
displayNameEditText.setError(getString(R.string.error_empty));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (priorDisplayName != null && priorDisplayName.equals(newDisplayName)) {
|
|
||||||
// If it's not any different, don't patch it.
|
|
||||||
newDisplayName = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String newNote = noteEditText.getText().toString();
|
|
||||||
if (newNote.isEmpty()) {
|
|
||||||
noteEditText.setError(getString(R.string.error_empty));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (priorNote != null && priorNote.equals(newNote)) {
|
|
||||||
// If it's not any different, don't patch it.
|
|
||||||
newNote = null;
|
|
||||||
}
|
|
||||||
if (newDisplayName == null && newNote == null && avatarBase64 == null
|
|
||||||
&& headerBase64 == null) {
|
|
||||||
// If nothing is changed, then there's nothing to save.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
saveProgress.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
isAlreadySaving = true;
|
|
||||||
|
|
||||||
Profile profile = new Profile();
|
|
||||||
profile.displayName = newDisplayName;
|
|
||||||
profile.note = newNote;
|
|
||||||
profile.avatar = avatarBase64;
|
|
||||||
profile.header = headerBase64;
|
|
||||||
mastodonApi.accountUpdateCredentials(profile).enqueue(new Callback<Account>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<Account> call, @NonNull Response<Account> response) {
|
|
||||||
if (!response.isSuccessful()) {
|
|
||||||
onSaveFailure();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
getPrivatePreferences().edit()
|
|
||||||
.putBoolean("refreshProfileHeader", true)
|
|
||||||
.apply();
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<Account> call, @NonNull Throwable t) {
|
|
||||||
onSaveFailure();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onSaveFailure() {
|
|
||||||
isAlreadySaving = false;
|
|
||||||
Snackbar.make(avatarButton, R.string.error_media_upload_sending, Snackbar.LENGTH_LONG)
|
|
||||||
.show();
|
|
||||||
saveProgress.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void beginMediaPicking() {
|
|
||||||
switch (currentlyPicking) {
|
|
||||||
case AVATAR: {
|
|
||||||
avatarProgress.setVisibility(View.VISIBLE);
|
|
||||||
avatarPreview.setVisibility(View.INVISIBLE);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case HEADER: {
|
|
||||||
headerProgress.setVisibility(View.VISIBLE);
|
|
||||||
headerPreview.setVisibility(View.INVISIBLE);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void endMediaPicking() {
|
|
||||||
switch (currentlyPicking) {
|
|
||||||
case AVATAR: {
|
|
||||||
avatarProgress.setVisibility(View.GONE);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case HEADER: {
|
|
||||||
headerProgress.setVisibility(View.GONE);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentlyPicking = PickType.NOTHING;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
switch (requestCode) {
|
|
||||||
case AVATAR_PICK_RESULT: {
|
|
||||||
if (resultCode == RESULT_OK && data != null) {
|
|
||||||
CropImage.activity(data.getData())
|
|
||||||
.setInitialCropWindowPaddingRatio(0)
|
|
||||||
.setAspectRatio(AVATAR_WIDTH, AVATAR_HEIGHT)
|
|
||||||
.start(this);
|
|
||||||
} else {
|
|
||||||
endMediaPicking();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case HEADER_PICK_RESULT: {
|
|
||||||
if (resultCode == RESULT_OK && data != null) {
|
|
||||||
CropImage.activity(data.getData())
|
|
||||||
.setInitialCropWindowPaddingRatio(0)
|
|
||||||
.setAspectRatio(HEADER_WIDTH, HEADER_HEIGHT)
|
|
||||||
.start(this);
|
|
||||||
} else {
|
|
||||||
endMediaPicking();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE: {
|
|
||||||
CropImage.ActivityResult result = CropImage.getActivityResult(data);
|
|
||||||
if (resultCode == RESULT_OK) {
|
|
||||||
beginResize(result.getUri());
|
|
||||||
} else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) {
|
|
||||||
onResizeFailure();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void beginResize(Uri uri) {
|
|
||||||
beginMediaPicking();
|
|
||||||
int width, height;
|
|
||||||
switch (currentlyPicking) {
|
|
||||||
default: {
|
|
||||||
throw new AssertionError("PickType not set.");
|
|
||||||
}
|
|
||||||
case AVATAR: {
|
|
||||||
width = AVATAR_WIDTH;
|
|
||||||
height = AVATAR_HEIGHT;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case HEADER: {
|
|
||||||
width = HEADER_WIDTH;
|
|
||||||
height = HEADER_HEIGHT;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
new ResizeImageTask(getContentResolver(), width, height, new ResizeImageTask.Listener() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<Bitmap> contentList) {
|
|
||||||
Bitmap bitmap = contentList.get(0);
|
|
||||||
PickType pickType = currentlyPicking;
|
|
||||||
endMediaPicking();
|
|
||||||
switch (pickType) {
|
|
||||||
case AVATAR: {
|
|
||||||
avatarPreview.setImageBitmap(bitmap);
|
|
||||||
avatarPreview.setVisibility(View.VISIBLE);
|
|
||||||
avatarBase64 = bitmapToBase64(bitmap);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case HEADER: {
|
|
||||||
headerPreview.setImageBitmap(bitmap);
|
|
||||||
headerPreview.setVisibility(View.VISIBLE);
|
|
||||||
headerBase64 = bitmapToBase64(bitmap);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure() {
|
|
||||||
onResizeFailure();
|
|
||||||
}
|
|
||||||
}).execute(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onResizeFailure() {
|
|
||||||
Snackbar.make(avatarButton, R.string.error_media_upload_sending, Snackbar.LENGTH_LONG)
|
|
||||||
.show();
|
|
||||||
endMediaPicking();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String bitmapToBase64(Bitmap bitmap) {
|
|
||||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
||||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
|
|
||||||
byte[] byteArray = stream.toByteArray();
|
|
||||||
IOUtils.closeQuietly(stream);
|
|
||||||
return "data:image/png;base64," + Base64.encodeToString(byteArray, Base64.DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ResizeImageTask extends AsyncTask<Uri, Void, Boolean> {
|
|
||||||
private ContentResolver contentResolver;
|
|
||||||
private int resizeWidth;
|
|
||||||
private int resizeHeight;
|
|
||||||
private Listener listener;
|
|
||||||
private List<Bitmap> resultList;
|
|
||||||
|
|
||||||
ResizeImageTask(ContentResolver contentResolver, int width, int height, Listener listener) {
|
|
||||||
this.contentResolver = contentResolver;
|
|
||||||
this.resizeWidth = width;
|
|
||||||
this.resizeHeight = height;
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Boolean doInBackground(Uri... uris) {
|
|
||||||
resultList = new ArrayList<>();
|
|
||||||
for (Uri uri : uris) {
|
|
||||||
InputStream inputStream;
|
|
||||||
try {
|
|
||||||
inputStream = contentResolver.openInputStream(uri);
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
Log.d(TAG, Log.getStackTraceString(e));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Bitmap sourceBitmap;
|
|
||||||
try {
|
|
||||||
sourceBitmap = BitmapFactory.decodeStream(inputStream, null, null);
|
|
||||||
} catch (OutOfMemoryError error) {
|
|
||||||
Log.d(TAG, Log.getStackTraceString(error));
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(inputStream);
|
|
||||||
}
|
|
||||||
if (sourceBitmap == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Bitmap bitmap = Bitmap.createScaledBitmap(sourceBitmap, resizeWidth, resizeHeight,
|
|
||||||
false);
|
|
||||||
sourceBitmap.recycle();
|
|
||||||
if (bitmap == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
resultList.add(bitmap);
|
|
||||||
if (isCancelled()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Boolean successful) {
|
|
||||||
if (successful) {
|
|
||||||
listener.onSuccess(resultList);
|
|
||||||
} else {
|
|
||||||
listener.onFailure();
|
|
||||||
}
|
|
||||||
super.onPostExecute(successful);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Listener {
|
|
||||||
void onSuccess(List<Bitmap> contentList);
|
|
||||||
void onFailure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
495
app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt
Normal file
495
app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt
Normal file
|
@ -0,0 +1,495 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is a part of Tusky.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||||
|
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||||
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
* Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||||
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
|
package com.keylesspalace.tusky
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.AsyncTask
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.design.widget.Snackbar
|
||||||
|
import android.support.v4.app.ActivityCompat
|
||||||
|
import android.support.v4.content.ContextCompat
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import com.keylesspalace.tusky.entity.Account
|
||||||
|
import com.keylesspalace.tusky.util.IOUtils
|
||||||
|
import com.squareup.picasso.Picasso
|
||||||
|
import com.theartofdev.edmodo.cropper.CropImage
|
||||||
|
import kotlinx.android.synthetic.main.activity_edit_profile.*
|
||||||
|
import kotlinx.android.synthetic.main.toolbar_basic.*
|
||||||
|
import okhttp3.MediaType
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.Callback
|
||||||
|
import retrofit2.Response
|
||||||
|
import java.io.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
private const val TAG = "EditProfileActivity"
|
||||||
|
|
||||||
|
private const val HEADER_FILE_NAME = "header.png"
|
||||||
|
private const val AVATAR_FILE_NAME = "avatar.png"
|
||||||
|
|
||||||
|
private const val KEY_OLD_DISPLAY_NAME = "OLD_DISPLAY_NAME"
|
||||||
|
private const val KEY_OLD_NOTE = "OLD_NOTE"
|
||||||
|
private const val KEY_IS_SAVING = "IS_SAVING"
|
||||||
|
private const val KEY_CURRENTLY_PICKING = "CURRENTLY_PICKING"
|
||||||
|
private const val KEY_AVATAR_CHANGED = "AVATAR_CHANGED"
|
||||||
|
private const val KEY_HEADER_CHANGED = "HEADER_CHANGED"
|
||||||
|
|
||||||
|
private const val AVATAR_PICK_RESULT = 1
|
||||||
|
private const val HEADER_PICK_RESULT = 2
|
||||||
|
private const val PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1
|
||||||
|
private const val AVATAR_SIZE = 120
|
||||||
|
private const val HEADER_WIDTH = 700
|
||||||
|
private const val HEADER_HEIGHT = 335
|
||||||
|
|
||||||
|
class EditProfileActivity : BaseActivity() {
|
||||||
|
|
||||||
|
private var oldDisplayName: String? = null
|
||||||
|
private var oldNote: String? = null
|
||||||
|
private var isSaving: Boolean = false
|
||||||
|
private var currentlyPicking: PickType = PickType.NOTHING
|
||||||
|
private var avatarChanged: Boolean = false
|
||||||
|
private var headerChanged: Boolean = false
|
||||||
|
|
||||||
|
private enum class PickType {
|
||||||
|
NOTHING,
|
||||||
|
AVATAR,
|
||||||
|
HEADER
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_edit_profile)
|
||||||
|
|
||||||
|
setSupportActionBar(toolbar)
|
||||||
|
supportActionBar?.run {
|
||||||
|
setTitle(R.string.title_edit_profile)
|
||||||
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
savedInstanceState?.let {
|
||||||
|
oldDisplayName = it.getString(KEY_OLD_DISPLAY_NAME)
|
||||||
|
oldNote = it.getString(KEY_OLD_NOTE)
|
||||||
|
isSaving = it.getBoolean(KEY_IS_SAVING)
|
||||||
|
currentlyPicking = it.getSerializable(KEY_CURRENTLY_PICKING) as PickType
|
||||||
|
avatarChanged = it.getBoolean(KEY_AVATAR_CHANGED)
|
||||||
|
headerChanged = it.getBoolean(KEY_HEADER_CHANGED)
|
||||||
|
|
||||||
|
if(avatarChanged) {
|
||||||
|
val avatar = BitmapFactory.decodeFile(getCacheFileForName(AVATAR_FILE_NAME).absolutePath)
|
||||||
|
avatarPreview.setImageBitmap(avatar)
|
||||||
|
}
|
||||||
|
if(headerChanged) {
|
||||||
|
val header = BitmapFactory.decodeFile(getCacheFileForName(HEADER_FILE_NAME).absolutePath)
|
||||||
|
headerPreview.setImageBitmap(header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
avatarButton.setOnClickListener { onMediaPick(PickType.AVATAR) }
|
||||||
|
headerButton.setOnClickListener { onMediaPick(PickType.HEADER) }
|
||||||
|
|
||||||
|
avatarPreview.setOnClickListener {
|
||||||
|
avatarPreview.setImageBitmap(null)
|
||||||
|
avatarPreview.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
headerPreview.setOnClickListener {
|
||||||
|
headerPreview.setImageBitmap(null)
|
||||||
|
headerPreview.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
mastodonApi.accountVerifyCredentials().enqueue(object : Callback<Account> {
|
||||||
|
override fun onResponse(call: Call<Account>, response: Response<Account>) {
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
onAccountVerifyCredentialsFailed()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val me = response.body()
|
||||||
|
oldDisplayName = me!!.displayName
|
||||||
|
oldNote = me.note.toString()
|
||||||
|
|
||||||
|
|
||||||
|
displayNameEditText.setText(oldDisplayName)
|
||||||
|
noteEditText.setText(oldNote)
|
||||||
|
if(!avatarChanged) {
|
||||||
|
Picasso.with(avatarPreview.context)
|
||||||
|
.load(me.avatar)
|
||||||
|
.placeholder(R.drawable.avatar_default)
|
||||||
|
.into(avatarPreview)
|
||||||
|
}
|
||||||
|
if(!headerChanged) {
|
||||||
|
Picasso.with(headerPreview.context)
|
||||||
|
.load(me.header)
|
||||||
|
.placeholder(R.drawable.account_header_default)
|
||||||
|
.into(headerPreview)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<Account>, t: Throwable) {
|
||||||
|
onAccountVerifyCredentialsFailed()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
outState.run {
|
||||||
|
putString(KEY_OLD_DISPLAY_NAME, oldDisplayName)
|
||||||
|
putString(KEY_OLD_NOTE, oldNote)
|
||||||
|
putBoolean(KEY_IS_SAVING, isSaving)
|
||||||
|
putSerializable(KEY_CURRENTLY_PICKING, currentlyPicking)
|
||||||
|
putBoolean(KEY_AVATAR_CHANGED, avatarChanged)
|
||||||
|
putBoolean(KEY_HEADER_CHANGED, headerChanged)
|
||||||
|
}
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onAccountVerifyCredentialsFailed() {
|
||||||
|
Log.e(TAG, "The account failed to load.")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onMediaPick(pickType: PickType) {
|
||||||
|
if (currentlyPicking != PickType.NOTHING) {
|
||||||
|
// Ignore inputs if another pick operation is still occurring.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
currentlyPicking = pickType
|
||||||
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE)
|
||||||
|
} else {
|
||||||
|
initiateMediaPicking()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
|
||||||
|
grantResults: IntArray) {
|
||||||
|
when (requestCode) {
|
||||||
|
PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE -> {
|
||||||
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
initiateMediaPicking()
|
||||||
|
} else {
|
||||||
|
endMediaPicking()
|
||||||
|
Snackbar.make(avatarButton, R.string.error_media_upload_permission, Snackbar.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initiateMediaPicking() {
|
||||||
|
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
intent.type = "image/*"
|
||||||
|
when (currentlyPicking) {
|
||||||
|
EditProfileActivity.PickType.AVATAR -> {
|
||||||
|
startActivityForResult(intent, AVATAR_PICK_RESULT)
|
||||||
|
}
|
||||||
|
EditProfileActivity.PickType.HEADER -> {
|
||||||
|
startActivityForResult(intent, HEADER_PICK_RESULT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
menuInflater.inflate(R.menu.edit_profile_toolbar, menu)
|
||||||
|
return super.onCreateOptionsMenu(menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
when (item.itemId) {
|
||||||
|
android.R.id.home -> {
|
||||||
|
onBackPressed()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
R.id.action_save -> {
|
||||||
|
save()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun save() {
|
||||||
|
if (isSaving || currentlyPicking != PickType.NOTHING) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isSaving = true
|
||||||
|
saveProgressBar.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
val newDisplayName = displayNameEditText.text.toString()
|
||||||
|
val displayName = if (oldDisplayName == newDisplayName) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
RequestBody.create(MultipartBody.FORM, newDisplayName)
|
||||||
|
}
|
||||||
|
|
||||||
|
val newNote = noteEditText.text.toString()
|
||||||
|
val note = if (oldNote == newNote) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
RequestBody.create(MultipartBody.FORM, newNote)
|
||||||
|
}
|
||||||
|
|
||||||
|
val avatar = if(avatarChanged) {
|
||||||
|
val avatarBody = RequestBody.create(MediaType.parse("image/png"), getCacheFileForName(AVATAR_FILE_NAME))
|
||||||
|
MultipartBody.Part.createFormData("avatar", getFileName(), avatarBody)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val header = if(headerChanged) {
|
||||||
|
val headerBody = RequestBody.create(MediaType.parse("image/png"), getCacheFileForName(HEADER_FILE_NAME))
|
||||||
|
MultipartBody.Part.createFormData("header", getFileName(), headerBody)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
if(displayName == null && note == null && avatar == null && header == null) {
|
||||||
|
/** if nothing has changed, there is no need to make a network request */
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mastodonApi.accountUpdateCredentials(displayName, note, avatar, header).enqueue(object : Callback<Account> {
|
||||||
|
override fun onResponse(call: Call<Account>, response: Response<Account>) {
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
onSaveFailure()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
privatePreferences.edit()
|
||||||
|
.putBoolean("refreshProfileHeader", true)
|
||||||
|
.apply()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<Account>, t: Throwable) {
|
||||||
|
onSaveFailure()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSaveFailure() {
|
||||||
|
isSaving = false
|
||||||
|
Snackbar.make(avatarButton, R.string.error_media_upload_sending, Snackbar.LENGTH_LONG).show()
|
||||||
|
saveProgressBar.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun beginMediaPicking() {
|
||||||
|
when (currentlyPicking) {
|
||||||
|
EditProfileActivity.PickType.AVATAR -> {
|
||||||
|
avatarProgressBar.visibility = View.VISIBLE
|
||||||
|
avatarPreview.visibility = View.INVISIBLE
|
||||||
|
avatarButton.setImageDrawable(null)
|
||||||
|
|
||||||
|
}
|
||||||
|
EditProfileActivity.PickType.HEADER -> {
|
||||||
|
headerProgressBar.visibility = View.VISIBLE
|
||||||
|
headerPreview.visibility = View.INVISIBLE
|
||||||
|
headerButton.setImageDrawable(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun endMediaPicking() {
|
||||||
|
avatarProgressBar.visibility = View.GONE
|
||||||
|
headerProgressBar.visibility = View.GONE
|
||||||
|
|
||||||
|
currentlyPicking = PickType.NOTHING
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
when (requestCode) {
|
||||||
|
AVATAR_PICK_RESULT -> {
|
||||||
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||||
|
CropImage.activity(data.data)
|
||||||
|
.setInitialCropWindowPaddingRatio(0f)
|
||||||
|
.setAspectRatio(AVATAR_SIZE, AVATAR_SIZE)
|
||||||
|
.start(this)
|
||||||
|
} else {
|
||||||
|
endMediaPicking()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HEADER_PICK_RESULT -> {
|
||||||
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||||
|
CropImage.activity(data.data)
|
||||||
|
.setInitialCropWindowPaddingRatio(0f)
|
||||||
|
.setAspectRatio(HEADER_WIDTH, HEADER_HEIGHT)
|
||||||
|
.start(this)
|
||||||
|
} else {
|
||||||
|
endMediaPicking()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE -> {
|
||||||
|
val result = CropImage.getActivityResult(data)
|
||||||
|
when (resultCode) {
|
||||||
|
Activity.RESULT_OK -> beginResize(result.uri)
|
||||||
|
CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE -> onResizeFailure()
|
||||||
|
else -> endMediaPicking()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun beginResize(uri: Uri) {
|
||||||
|
beginMediaPicking()
|
||||||
|
val width: Int
|
||||||
|
val height: Int
|
||||||
|
val cacheFile: File
|
||||||
|
when (currentlyPicking) {
|
||||||
|
EditProfileActivity.PickType.AVATAR -> {
|
||||||
|
width = AVATAR_SIZE
|
||||||
|
height = AVATAR_SIZE
|
||||||
|
cacheFile = getCacheFileForName(AVATAR_FILE_NAME)
|
||||||
|
}
|
||||||
|
EditProfileActivity.PickType.HEADER -> {
|
||||||
|
width = HEADER_WIDTH
|
||||||
|
height = HEADER_HEIGHT
|
||||||
|
cacheFile = getCacheFileForName(HEADER_FILE_NAME)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw AssertionError("PickType not set.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ResizeImageTask(contentResolver, width, height, cacheFile, object : ResizeImageTask.Listener {
|
||||||
|
override fun onSuccess(resizedImage: Bitmap?) {
|
||||||
|
val pickType = currentlyPicking
|
||||||
|
endMediaPicking()
|
||||||
|
when (pickType) {
|
||||||
|
EditProfileActivity.PickType.AVATAR -> {
|
||||||
|
avatarPreview.setImageBitmap(resizedImage)
|
||||||
|
avatarPreview.visibility = View.VISIBLE
|
||||||
|
avatarButton.setImageResource(R.drawable.ic_add_a_photo_32dp)
|
||||||
|
avatarChanged = true
|
||||||
|
}
|
||||||
|
EditProfileActivity.PickType.HEADER -> {
|
||||||
|
headerPreview.setImageBitmap(resizedImage)
|
||||||
|
headerPreview.visibility = View.VISIBLE
|
||||||
|
headerButton.setImageResource(R.drawable.ic_add_a_photo_32dp)
|
||||||
|
headerChanged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure() {
|
||||||
|
onResizeFailure()
|
||||||
|
}
|
||||||
|
}).execute(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onResizeFailure() {
|
||||||
|
Snackbar.make(avatarButton, R.string.error_media_upload_sending, Snackbar.LENGTH_LONG).show()
|
||||||
|
endMediaPicking()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCacheFileForName(filename: String): File {
|
||||||
|
return File(cacheDir, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFileName(): String {
|
||||||
|
return java.lang.Long.toHexString(Random().nextLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ResizeImageTask (private val contentResolver: ContentResolver,
|
||||||
|
private val resizeWidth: Int,
|
||||||
|
private val resizeHeight: Int,
|
||||||
|
private val cacheFile: File,
|
||||||
|
private val listener: Listener) : AsyncTask<Uri, Void, Boolean>() {
|
||||||
|
private var resultBitmap: Bitmap? = null
|
||||||
|
|
||||||
|
override fun doInBackground(vararg uris: Uri): Boolean? {
|
||||||
|
val uri = uris[0]
|
||||||
|
val inputStream: InputStream?
|
||||||
|
try {
|
||||||
|
inputStream = contentResolver.openInputStream(uri)
|
||||||
|
} catch (e: FileNotFoundException) {
|
||||||
|
Log.d(TAG, Log.getStackTraceString(e))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val sourceBitmap: Bitmap?
|
||||||
|
try {
|
||||||
|
sourceBitmap = BitmapFactory.decodeStream(inputStream, null, null)
|
||||||
|
} catch (error: OutOfMemoryError) {
|
||||||
|
Log.d(TAG, Log.getStackTraceString(error))
|
||||||
|
return false
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(inputStream)
|
||||||
|
}
|
||||||
|
if (sourceBitmap == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val bitmap = Bitmap.createScaledBitmap(sourceBitmap, resizeWidth, resizeHeight, true)
|
||||||
|
sourceBitmap.recycle()
|
||||||
|
if (bitmap == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
resultBitmap = bitmap
|
||||||
|
|
||||||
|
if (!saveBitmapToFile(bitmap, cacheFile)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCancelled) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPostExecute(successful: Boolean) {
|
||||||
|
if (successful) {
|
||||||
|
listener.onSuccess(resultBitmap)
|
||||||
|
} else {
|
||||||
|
listener.onFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveBitmapToFile(bitmap: Bitmap, file: File): Boolean {
|
||||||
|
|
||||||
|
val outputStream: OutputStream
|
||||||
|
|
||||||
|
try {
|
||||||
|
outputStream = FileOutputStream(file)
|
||||||
|
} catch (e: FileNotFoundException) {
|
||||||
|
Log.w(TAG, Log.getStackTraceString(e))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
|
||||||
|
IOUtils.closeQuietly(outputStream)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
internal interface Listener {
|
||||||
|
fun onSuccess(resizedImage: Bitmap?)
|
||||||
|
fun onFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -494,6 +494,14 @@ public class MainActivity extends BaseActivity implements ActionButtonActivity {
|
||||||
|
|
||||||
List<AccountEntity> allAccounts = am.getAllAccountsOrderedByActive();
|
List<AccountEntity> allAccounts = am.getAllAccountsOrderedByActive();
|
||||||
|
|
||||||
|
//remove profiles before adding them again to avoid duplicates
|
||||||
|
List<IProfile> profiles = new ArrayList<>(headerResult.getProfiles());
|
||||||
|
for(IProfile profile: profiles) {
|
||||||
|
if(profile.getIdentifier() != DRAWER_ITEM_ADD_ACCOUNT) {
|
||||||
|
headerResult.removeProfile(profile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for(AccountEntity acc: allAccounts) {
|
for(AccountEntity acc: allAccounts) {
|
||||||
headerResult.addProfiles(
|
headerResult.addProfiles(
|
||||||
new ProfileDrawerItem()
|
new ProfileDrawerItem()
|
||||||
|
@ -506,7 +514,7 @@ public class MainActivity extends BaseActivity implements ActionButtonActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show follow requests in the menu, if this is a locked account.
|
// Show follow requests in the menu, if this is a locked account.
|
||||||
if (me.locked) {
|
if (me.locked && drawer.getDrawerItem(DRAWER_ITEM_FOLLOW_REQUESTS) == null) {
|
||||||
PrimaryDrawerItem followRequestsItem = new PrimaryDrawerItem()
|
PrimaryDrawerItem followRequestsItem = new PrimaryDrawerItem()
|
||||||
.withIdentifier(DRAWER_ITEM_FOLLOW_REQUESTS)
|
.withIdentifier(DRAWER_ITEM_FOLLOW_REQUESTS)
|
||||||
.withName(R.string.action_view_follow_requests)
|
.withName(R.string.action_view_follow_requests)
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
package com.keylesspalace.tusky.entity;
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
|
||||||
|
|
||||||
public class Profile {
|
|
||||||
@SerializedName("display_name")
|
|
||||||
public String displayName;
|
|
||||||
|
|
||||||
@SerializedName("note")
|
|
||||||
public String note;
|
|
||||||
|
|
||||||
/** Encoded in Base-64 */
|
|
||||||
@SerializedName("avatar")
|
|
||||||
public String avatar;
|
|
||||||
|
|
||||||
/** Encoded in Base-64 */
|
|
||||||
@SerializedName("header")
|
|
||||||
public String header;
|
|
||||||
}
|
|
|
@ -24,7 +24,6 @@ import com.keylesspalace.tusky.entity.Attachment;
|
||||||
import com.keylesspalace.tusky.entity.Card;
|
import com.keylesspalace.tusky.entity.Card;
|
||||||
import com.keylesspalace.tusky.entity.MastoList;
|
import com.keylesspalace.tusky.entity.MastoList;
|
||||||
import com.keylesspalace.tusky.entity.Notification;
|
import com.keylesspalace.tusky.entity.Notification;
|
||||||
import com.keylesspalace.tusky.entity.Profile;
|
|
||||||
import com.keylesspalace.tusky.entity.Relationship;
|
import com.keylesspalace.tusky.entity.Relationship;
|
||||||
import com.keylesspalace.tusky.entity.SearchResults;
|
import com.keylesspalace.tusky.entity.SearchResults;
|
||||||
import com.keylesspalace.tusky.entity.Status;
|
import com.keylesspalace.tusky.entity.Status;
|
||||||
|
@ -33,9 +32,9 @@ import com.keylesspalace.tusky.entity.StatusContext;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import okhttp3.MultipartBody;
|
import okhttp3.MultipartBody;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.http.Body;
|
|
||||||
import retrofit2.http.DELETE;
|
import retrofit2.http.DELETE;
|
||||||
import retrofit2.http.Field;
|
import retrofit2.http.Field;
|
||||||
import retrofit2.http.FormUrlEncoded;
|
import retrofit2.http.FormUrlEncoded;
|
||||||
|
@ -136,8 +135,15 @@ public interface MastodonApi {
|
||||||
|
|
||||||
@GET("api/v1/accounts/verify_credentials")
|
@GET("api/v1/accounts/verify_credentials")
|
||||||
Call<Account> accountVerifyCredentials();
|
Call<Account> accountVerifyCredentials();
|
||||||
|
|
||||||
|
@Multipart
|
||||||
@PATCH("api/v1/accounts/update_credentials")
|
@PATCH("api/v1/accounts/update_credentials")
|
||||||
Call<Account> accountUpdateCredentials(@Body Profile profile);
|
Call<Account> accountUpdateCredentials(
|
||||||
|
@Nullable @Part(value="display_name") RequestBody displayName,
|
||||||
|
@Nullable @Part(value="note") RequestBody note,
|
||||||
|
@Nullable @Part MultipartBody.Part avatar,
|
||||||
|
@Nullable @Part MultipartBody.Part header);
|
||||||
|
|
||||||
@GET("api/v1/accounts/search")
|
@GET("api/v1/accounts/search")
|
||||||
Call<List<Account>> searchAccounts(
|
Call<List<Account>> searchAccounts(
|
||||||
@Query("q") String q,
|
@Query("q") String q,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<android.support.design.widget.CoordinatorLayout
|
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -24,60 +23,58 @@
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
android:id="@+id/headerPreview"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="200dp"
|
android:layout_height="200dp"
|
||||||
android:id="@+id/edit_profile_header_preview"
|
|
||||||
android:contentDescription="@null" />
|
android:contentDescription="@null" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/headerButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="200dp"
|
||||||
|
android:background="#66000000"
|
||||||
|
android:contentDescription="@string/label_header"
|
||||||
|
app:srcCompat="@drawable/ic_add_a_photo_32dp" />
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
|
android:id="@+id/headerProgressBar"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/edit_profile_header_progress"
|
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
android:indeterminate="true"
|
android:indeterminate="true"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="200dp"
|
|
||||||
android:id="@+id/edit_profile_header"
|
|
||||||
app:srcCompat="@drawable/ic_add_a_photo_32dp"
|
|
||||||
android:background="#66000000"
|
|
||||||
android:contentDescription="@string/label_header" />
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:paddingLeft="16dp"
|
|
||||||
android:paddingEnd="0dp"
|
|
||||||
android:paddingRight="0dp"
|
|
||||||
android:layout_marginTop="-40dp">
|
android:layout_marginTop="-40dp">
|
||||||
|
|
||||||
<com.pkmmte.view.CircularImageView
|
<com.pkmmte.view.CircularImageView
|
||||||
|
android:id="@+id/avatarPreview"
|
||||||
android:layout_width="80dp"
|
android:layout_width="80dp"
|
||||||
android:layout_height="80dp"
|
android:layout_height="80dp"
|
||||||
android:id="@+id/edit_profile_avatar_preview"
|
|
||||||
android:contentDescription="@null" />
|
android:contentDescription="@null" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/avatarButton"
|
||||||
|
android:layout_width="80dp"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:background="@drawable/round_button"
|
||||||
|
android:contentDescription="@string/label_avatar"
|
||||||
|
android:elevation="4dp"
|
||||||
|
app:srcCompat="@drawable/ic_add_a_photo_32dp" />
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
|
android:id="@+id/avatarProgressBar"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/edit_profile_avatar_progress"
|
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
android:indeterminate="true"
|
android:indeterminate="true"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:layout_width="80dp"
|
|
||||||
android:layout_height="80dp"
|
|
||||||
android:id="@+id/edit_profile_avatar"
|
|
||||||
app:srcCompat="@drawable/ic_add_a_photo_32dp"
|
|
||||||
android:elevation="4dp"
|
|
||||||
android:background="@drawable/round_button"
|
|
||||||
android:contentDescription="@string/label_avatar" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
<android.support.design.widget.TextInputLayout
|
<android.support.design.widget.TextInputLayout
|
||||||
|
@ -87,13 +84,13 @@
|
||||||
android:layout_marginTop="30dp">
|
android:layout_marginTop="30dp">
|
||||||
|
|
||||||
<android.support.design.widget.TextInputEditText
|
<android.support.design.widget.TextInputEditText
|
||||||
|
android:id="@+id/displayNameEditText"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/edit_profile_display_name"
|
android:layout_marginEnd="16dp"
|
||||||
android:hint="@string/hint_display_name"
|
|
||||||
android:maxLength="30"
|
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="16dp" />
|
android:hint="@string/hint_display_name"
|
||||||
|
android:maxLength="30" />
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|
||||||
|
@ -104,14 +101,14 @@
|
||||||
android:layout_marginTop="30dp">
|
android:layout_marginTop="30dp">
|
||||||
|
|
||||||
<android.support.design.widget.TextInputEditText
|
<android.support.design.widget.TextInputEditText
|
||||||
|
android:id="@+id/noteEditText"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/edit_profile_note"
|
android:layout_marginBottom="16dp"
|
||||||
android:hint="@string/hint_note"
|
|
||||||
android:maxLength="160"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginBottom="16dp" />
|
android:layout_marginStart="16dp"
|
||||||
|
android:hint="@string/hint_note"
|
||||||
|
android:maxLength="160" />
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|
||||||
|
@ -122,11 +119,11 @@
|
||||||
<include layout="@layout/toolbar_shadow_shim" />
|
<include layout="@layout/toolbar_shadow_shim" />
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/edit_profile_save_progress"
|
android:id="@+id/saveProgressBar"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
android:indeterminate="true"
|
android:indeterminate="true"
|
||||||
android:visibility="gone"
|
android:visibility="gone" />
|
||||||
android:layout_gravity="center" />
|
|
||||||
|
|
||||||
</android.support.design.widget.CoordinatorLayout>
|
</android.support.design.widget.CoordinatorLayout>
|
Loading…
Reference in a new issue