|
|
|
@ -1,9 +1,13 @@ |
|
|
|
|
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.Build; |
|
|
|
|
import android.os.Bundle; |
|
|
|
|
import android.support.annotation.NonNull; |
|
|
|
@ -12,15 +16,25 @@ 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.view.Menu; |
|
|
|
|
import android.view.MenuItem; |
|
|
|
|
import android.view.View; |
|
|
|
|
import android.widget.Button; |
|
|
|
|
import android.widget.EditText; |
|
|
|
|
import android.widget.ImageView; |
|
|
|
|
import android.widget.ProgressBar; |
|
|
|
|
import android.widget.TextView; |
|
|
|
|
|
|
|
|
|
import com.keylesspalace.tusky.entity.Account; |
|
|
|
|
import com.keylesspalace.tusky.entity.Profile; |
|
|
|
|
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 butterknife.BindView; |
|
|
|
|
import butterknife.ButterKnife; |
|
|
|
@ -30,18 +44,36 @@ import retrofit2.Response; |
|
|
|
|
|
|
|
|
|
public class EditProfileActivity extends BaseActivity { |
|
|
|
|
private static final String TAG = "EditProfileActivity"; |
|
|
|
|
private static final int MEDIA_PICK_RESULT = 1; |
|
|
|
|
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 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@BindView(R.id.edit_profile_display_name) EditText displayNameEditText; |
|
|
|
|
@BindView(R.id.edit_profile_note) EditText noteEditText; |
|
|
|
|
@BindView(R.id.edit_profile_avatar) Button avatarButton; |
|
|
|
|
@BindView(R.id.edit_profile_avatar_preview) ImageView avatarPreview; |
|
|
|
|
@BindView(R.id.edit_profile_avatar_progress) ProgressBar avatarProgress; |
|
|
|
|
@BindView(R.id.edit_profile_header) Button headerButton; |
|
|
|
|
@BindView(R.id.edit_profile_header_preview) ImageView headerPreview; |
|
|
|
|
@BindView(R.id.edit_profile_header_progress) ProgressBar headerProgress; |
|
|
|
|
@BindView(R.id.edit_profile_error) TextView errorText; |
|
|
|
|
|
|
|
|
|
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) { |
|
|
|
@ -62,22 +94,45 @@ public class EditProfileActivity extends BaseActivity { |
|
|
|
|
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(new View.OnClickListener() { |
|
|
|
|
@Override |
|
|
|
|
public void onClick(View v) { |
|
|
|
|
onMediaPick(); |
|
|
|
|
onMediaPick(PickType.AVATAR); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
headerButton.setOnClickListener(new View.OnClickListener() { |
|
|
|
|
@Override |
|
|
|
|
public void onClick(View v) { |
|
|
|
|
onMediaPick(); |
|
|
|
|
onMediaPick(PickType.HEADER); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
avatarPreview.setOnClickListener(new View.OnClickListener() { |
|
|
|
|
@Override |
|
|
|
|
public void onClick(View v) { |
|
|
|
|
avatarPreview.setImageBitmap(null); |
|
|
|
|
avatarPreview.setVisibility(View.GONE); |
|
|
|
|
avatarBase64 = null; |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
headerPreview.setOnClickListener(new View.OnClickListener() { |
|
|
|
|
@Override |
|
|
|
|
public void onClick(View v) { |
|
|
|
|
headerPreview.setImageBitmap(null); |
|
|
|
|
avatarPreview.setVisibility(View.GONE); |
|
|
|
|
headerBase64 = null; |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
@ -107,6 +162,9 @@ public class EditProfileActivity extends BaseActivity { |
|
|
|
|
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); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -114,7 +172,8 @@ public class EditProfileActivity extends BaseActivity { |
|
|
|
|
Log.e(TAG, "The account failed to load."); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void onMediaPick() { |
|
|
|
|
private void onMediaPick(PickType pickType) { |
|
|
|
|
beginMediaPicking(pickType); |
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && |
|
|
|
|
ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) |
|
|
|
|
!= PackageManager.PERMISSION_GRANTED) { |
|
|
|
@ -135,6 +194,7 @@ public class EditProfileActivity extends BaseActivity { |
|
|
|
|
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) { |
|
|
|
|
initiateMediaPicking(); |
|
|
|
|
} else { |
|
|
|
|
endMediaPicking(); |
|
|
|
|
errorText.setText(R.string.error_media_upload_permission); |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
@ -146,7 +206,17 @@ public class EditProfileActivity extends BaseActivity { |
|
|
|
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT); |
|
|
|
|
intent.addCategory(Intent.CATEGORY_OPENABLE); |
|
|
|
|
intent.setType("*/*"); |
|
|
|
|
startActivityForResult(intent, MEDIA_PICK_RESULT); |
|
|
|
|
switch (currentlyPicking) { |
|
|
|
|
case AVATAR: { |
|
|
|
|
startActivityForResult(intent, AVATAR_PICK_RESULT); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case HEADER: { |
|
|
|
|
startActivityForResult(intent, HEADER_PICK_RESULT); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@ -171,7 +241,7 @@ public class EditProfileActivity extends BaseActivity { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void save() { |
|
|
|
|
if (isAlreadySaving) { |
|
|
|
|
if (isAlreadySaving || currentlyPicking != PickType.NOTHING) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
String newDisplayName = displayNameEditText.getText().toString(); |
|
|
|
@ -196,11 +266,13 @@ public class EditProfileActivity extends BaseActivity { |
|
|
|
|
|
|
|
|
|
isAlreadySaving = true; |
|
|
|
|
|
|
|
|
|
Log.d(TAG, "avatar " + avatarBase64); |
|
|
|
|
|
|
|
|
|
Profile profile = new Profile(); |
|
|
|
|
profile.displayName = newDisplayName; |
|
|
|
|
profile.note = newNote; |
|
|
|
|
profile.avatar = null; |
|
|
|
|
profile.header = null; |
|
|
|
|
profile.avatar = avatarBase64; |
|
|
|
|
profile.header = headerBase64; |
|
|
|
|
mastodonAPI.accountUpdateCredentials(profile).enqueue(new Callback<Account>() { |
|
|
|
|
@Override |
|
|
|
|
public void onResponse(Call<Account> call, Response<Account> response) { |
|
|
|
@ -223,12 +295,176 @@ public class EditProfileActivity extends BaseActivity { |
|
|
|
|
errorText.setText(getString(R.string.error_media_upload_sending)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void beginMediaPicking(PickType pickType) { |
|
|
|
|
currentlyPicking = pickType; |
|
|
|
|
switch (currentlyPicking) { |
|
|
|
|
case AVATAR: { avatarProgress.setVisibility(View.VISIBLE); break; } |
|
|
|
|
case HEADER: { headerProgress.setVisibility(View.VISIBLE); 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); |
|
|
|
|
if (requestCode == MEDIA_PICK_RESULT && resultCode == RESULT_OK && data != null) { |
|
|
|
|
Uri uri = data.getData(); |
|
|
|
|
Log.d(TAG, "picked: " + uri.toString()); |
|
|
|
|
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); |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case HEADER_PICK_RESULT: { |
|
|
|
|
if (resultCode == RESULT_OK && data != null) { |
|
|
|
|
CropImage.activity(data.getData()) |
|
|
|
|
.setInitialCropWindowPaddingRatio(0) |
|
|
|
|
.setAspectRatio(HEADER_WIDTH, HEADER_HEIGHT) |
|
|
|
|
.start(this); |
|
|
|
|
} |
|
|
|
|
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) { |
|
|
|
|
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); |
|
|
|
|
switch (currentlyPicking) { |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
endMediaPicking(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public void onFailure() { |
|
|
|
|
onResizeFailure(); |
|
|
|
|
} |
|
|
|
|
}).execute(uri); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void onResizeFailure() { |
|
|
|
|
errorText.setText(getString(R.string.error_media_upload_sending)); |
|
|
|
|
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) { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
Bitmap sourceBitmap; |
|
|
|
|
try { |
|
|
|
|
sourceBitmap = BitmapFactory.decodeStream(inputStream, null, null); |
|
|
|
|
} catch (OutOfMemoryError 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(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|