Profile editor functionally complete.
This commit is contained in:
parent
18e40855ad
commit
5941a2f5b3
|
@ -47,6 +47,7 @@ dependencies {
|
|||
compile 'com.github.chrisbanes:PhotoView:1.3.1'
|
||||
compile 'com.mikepenz:google-material-typeface:3.0.1.0.original@aar'
|
||||
compile 'com.github.arimorty:floatingsearchview:2.0.3'
|
||||
compile 'com.theartofdev.edmodo:android-image-cropper:2.4.0'
|
||||
compile 'com.jakewharton:butterknife:8.4.0'
|
||||
compile 'com.google.firebase:firebase-messaging:10.0.1'
|
||||
compile 'com.google.firebase:firebase-crash:10.0.1'
|
||||
|
|
|
@ -66,16 +66,19 @@
|
|||
<activity
|
||||
android:name=".ReportActivity"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||
<activity
|
||||
android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
|
||||
android:theme="@style/Base.Theme.AppCompat" />
|
||||
|
||||
<service android:name=".MyFirebaseInstanceIdService" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
|
||||
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service android:name=".MyFirebaseMessagingService" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
|
@ -88,7 +91,7 @@
|
|||
android:label="Compose Toot"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE"/>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import android.support.annotation.Nullable;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
class IOUtils {
|
||||
static void closeQuietly(@Nullable InputStream stream) {
|
||||
|
@ -30,4 +31,14 @@ class IOUtils {
|
|||
// intentionally unhandled
|
||||
}
|
||||
}
|
||||
|
||||
static void closeQuietly(@Nullable OutputStream stream) {
|
||||
try {
|
||||
if (stream != null) {
|
||||
stream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// intentionally unhandled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,27 @@
|
|||
android:id="@id/edit_profile_avatar"
|
||||
android:text="@string/action_photo_pick" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:id="@+id/edit_profile_avatar_preview"
|
||||
android:contentDescription="@null"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/edit_profile_avatar_progress"
|
||||
android:layout_centerInParent="true"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -62,6 +83,27 @@
|
|||
android:id="@id/edit_profile_header"
|
||||
android:text="@string/action_photo_pick" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="167.2dp"
|
||||
android:layout_height="80dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="167.2dp"
|
||||
android:layout_height="80dp"
|
||||
android:id="@+id/edit_profile_header_preview"
|
||||
android:contentDescription="@null"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/edit_profile_header_progress"
|
||||
android:layout_centerInParent="true"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
Loading…
Reference in New Issue