Profile editing

This commit is contained in:
Grishka
2022-02-10 17:59:47 +03:00
parent b8e3426a1e
commit 82a8d0cc29
24 changed files with 718 additions and 42 deletions

View File

@@ -45,10 +45,16 @@ public class ContentUriRequestBody extends RequestBody{
@Override
public void writeTo(BufferedSink sink) throws IOException{
try(Source source=Okio.source(MastodonApp.context.getContentResolver().openInputStream(uri))){
BufferedSink wrappedSink=Okio.buffer(new CountingSink(sink));
wrappedSink.writeAll(source);
wrappedSink.flush();
if(progressListener!=null){
try(Source source=Okio.source(MastodonApp.context.getContentResolver().openInputStream(uri))){
BufferedSink wrappedSink=Okio.buffer(new CountingSink(sink));
wrappedSink.writeAll(source);
wrappedSink.flush();
}
}else{
try(Source source=Okio.source(MastodonApp.context.getContentResolver().openInputStream(uri))){
sink.writeAll(source);
}
}
}

View File

@@ -1,5 +1,8 @@
package org.joinmastodon.android.api;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.net.Uri;
import android.util.Pair;
@@ -17,6 +20,7 @@ import java.util.List;
import java.util.Map;
import androidx.annotation.CallSuper;
import androidx.annotation.StringRes;
import me.grishka.appkit.api.APIRequest;
import me.grishka.appkit.api.Callback;
import okhttp3.Call;
@@ -36,6 +40,7 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
Token token;
boolean canceled;
Map<String, String> headers;
private ProgressDialog progressDialog;
public MastodonAPIRequest(HttpMethod method, String path, Class<T> respClass){
this.path=path;
@@ -82,6 +87,17 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
return this;
}
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable){
progressDialog=new ProgressDialog(activity);
progressDialog.setMessage(activity.getString(message));
progressDialog.setCancelable(cancelable);
if(cancelable){
progressDialog.setOnCancelListener(dialog->cancel());
}
progressDialog.show();
return this;
}
protected void setRequestBody(Object body){
requestBody=body;
}
@@ -149,10 +165,18 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
invokeSuccessCallback(resp);
}
@Override
protected void onRequestDone(){
if(progressDialog!=null){
progressDialog.dismiss();
}
}
public enum HttpMethod{
GET,
POST,
PUT,
DELETE
DELETE,
PATCH
}
}

View File

@@ -0,0 +1,55 @@
package org.joinmastodon.android.api.requests.accounts;
import android.net.Uri;
import org.joinmastodon.android.api.ContentUriRequestBody;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.AccountField;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.List;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
public class UpdateAccountCredentials extends MastodonAPIRequest<Account>{
private String displayName, bio;
private Uri avatar, cover;
private List<AccountField> fields;
public UpdateAccountCredentials(String displayName, String bio, Uri avatar, Uri cover, List<AccountField> fields){
super(HttpMethod.PATCH, "/accounts/update_credentials", Account.class);
this.displayName=displayName;
this.bio=bio;
this.avatar=avatar;
this.cover=cover;
this.fields=fields;
}
@Override
public RequestBody getRequestBody(){
MultipartBody.Builder bldr=new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("display_name", displayName)
.addFormDataPart("note", bio);
if(avatar!=null){
bldr.addFormDataPart("avatar", UiUtils.getFileName(avatar), new ContentUriRequestBody(avatar, null));
}
if(cover!=null){
bldr.addFormDataPart("header", UiUtils.getFileName(cover), new ContentUriRequestBody(cover, null));
}
if(fields.isEmpty()){
bldr.addFormDataPart("fields_attributes[0][name]", "").addFormDataPart("fields_attributes[0][value]", "");
}else{
int i=0;
for(AccountField field:fields){
bldr.addFormDataPart("fields_attributes["+i+"][name]", field.name).addFormDataPart("fields_attributes["+i+"][value]", field.value);
i++;
}
}
return bldr.build();
}
}

View File

@@ -9,6 +9,7 @@ import org.joinmastodon.android.api.ContentUriRequestBody;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.ProgressListener;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.ui.utils.UiUtils;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
@@ -29,16 +30,9 @@ public class UploadAttachment extends MastodonAPIRequest<Attachment>{
@Override
public RequestBody getRequestBody(){
String fileName;
try(Cursor cursor=MastodonApp.context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)){
cursor.moveToFirst();
fileName=cursor.getString(0);
}
if(fileName==null)
fileName=uri.getLastPathSegment();
return new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", fileName, new ContentUriRequestBody(uri, progressListener))
.addFormDataPart("file", UiUtils.getFileName(uri), new ContentUriRequestBody(uri, progressListener))
.build();
}
}

View File

@@ -311,6 +311,13 @@ public class AccountSessionManager{
return r==null ? Collections.emptyList() : r;
}
public void updateAccountInfo(String id, Account account){
AccountSession session=getAccount(id);
session.self=account;
session.infoLastUpdated=System.currentTimeMillis();
writeAccountsFile();
}
private static class SessionsStorageWrapper{
public List<AccountSession> accounts;
}

View File

@@ -24,12 +24,13 @@ import androidx.annotation.Nullable;
import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.fragments.AppKitFragment;
import me.grishka.appkit.fragments.LoaderFragment;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.FragmentRootLinearLayout;
public class HomeFragment extends AppKitFragment{
public class HomeFragment extends AppKitFragment implements OnBackPressedListener{
private FragmentRootLinearLayout content;
private HomeTimelineFragment homeTimelineFragment;
private NotificationsFragment notificationsFragment;
@@ -155,4 +156,11 @@ public class HomeFragment extends AppKitFragment{
currentTab=tab;
((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this);
}
@Override
public boolean onBackPressed(){
if(currentTab==R.id.tab_profile)
return profileFragment.onBackPressed();
return false;
}
}

View File

@@ -1,6 +1,7 @@
package org.joinmastodon.android.fragments;
import android.app.Fragment;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Paint;
@@ -11,10 +12,12 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.widget.EditText;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.AccountField;
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
import org.joinmastodon.android.ui.views.LinkedTextView;
import java.util.Collections;
@@ -22,21 +25,32 @@ import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public class ProfileAboutFragment extends Fragment{
private static final int MAX_FIELDS=4;
public UsableRecyclerView list;
private List<AccountField> fields=Collections.emptyList();
private AboutAdapter adapter;
private Paint dividerPaint=new Paint();
private boolean isInEditMode;
private ItemTouchHelper dragHelper=new ItemTouchHelper(new ReorderCallback());
private RecyclerView.ViewHolder draggedViewHolder;
public void setFields(List<AccountField> fields){
this.fields=fields;
if(isInEditMode){
isInEditMode=false;
dragHelper.attachToRecyclerView(null);
}
if(adapter!=null)
adapter.notifyDataSetChanged();
}
@@ -60,7 +74,8 @@ public class ProfileAboutFragment extends Fragment{
for(int i=0;i<parent.getChildCount();i++){
View item=parent.getChildAt(i);
int pos=parent.getChildAdapterPosition(item);
if(pos<fields.size()-1){
int draggedPos=draggedViewHolder==null ? -1 : draggedViewHolder.getAbsoluteAdapterPosition();
if(pos<adapter.getItemCount()-1 && pos!=draggedPos && pos!=draggedPos-1){
c.drawLine(item.getLeft(), item.getBottom(), item.getRight(), item.getBottom(), dividerPaint);
}
}
@@ -69,47 +84,75 @@ public class ProfileAboutFragment extends Fragment{
return list;
}
private class AboutAdapter extends UsableRecyclerView.Adapter<AboutViewHolder>{
public void enterEditMode(List<AccountField> editableFields){
isInEditMode=true;
fields=editableFields;
adapter.notifyDataSetChanged();
dragHelper.attachToRecyclerView(list);
}
public List<AccountField> getFields(){
return fields;
}
private class AboutAdapter extends UsableRecyclerView.Adapter<BaseViewHolder>{
public AboutAdapter(){
super(null);
}
@NonNull
@Override
public AboutViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new AboutViewHolder();
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return switch(viewType){
case 0 -> new AboutViewHolder();
case 1 -> new EditableAboutViewHolder();
case 2 -> new AddRowViewHolder();
default -> throw new IllegalStateException("Unexpected value: "+viewType);
};
}
@Override
public void onBindViewHolder(AboutViewHolder holder, int position){
holder.bind(fields.get(position));
public void onBindViewHolder(BaseViewHolder holder, int position){
if(position<fields.size()){
holder.bind(fields.get(position));
}else{
holder.bind(null);
}
super.onBindViewHolder(holder, position);
}
@Override
public int getItemCount(){
if(isInEditMode){
int size=fields.size();
if(size<MAX_FIELDS)
size++;
return size;
}
return fields.size();
}
@Override
public int getItemViewType(int position){
if(isInEditMode){
return position==fields.size() ? 2 : 1;
}
return 0;
}
}
private class AboutViewHolder extends BindableViewHolder<AccountField>{
private TextView title;
private LinkedTextView value;
private abstract class BaseViewHolder extends BindableViewHolder<AccountField>{
private ShapeDrawable background=new ShapeDrawable();
public AboutViewHolder(){
super(getActivity(), R.layout.item_profile_about, list);
title=findViewById(R.id.title);
value=findViewById(R.id.value);
public BaseViewHolder(int layout){
super(getActivity(), layout, list);
background.getPaint().setColor(getResources().getColor(R.color.gray_50));
itemView.setBackground(background);
}
@Override
public void onBind(AccountField item){
title.setText(item.name);
value.setText(item.parsedValue);
boolean first=getAbsoluteAdapterPosition()==0, last=getAbsoluteAdapterPosition()==fields.size()-1;
boolean first=getAbsoluteAdapterPosition()==0, last=getAbsoluteAdapterPosition()==adapter.getItemCount()-1;
float radius=V.dp(10);
float[] rad=new float[8];
if(first)
@@ -120,4 +163,117 @@ public class ProfileAboutFragment extends Fragment{
itemView.invalidateOutline();
}
}
private class AboutViewHolder extends BaseViewHolder{
private TextView title;
private LinkedTextView value;
public AboutViewHolder(){
super(R.layout.item_profile_about);
title=findViewById(R.id.title);
value=findViewById(R.id.value);
}
@Override
public void onBind(AccountField item){
super.onBind(item);
title.setText(item.name);
value.setText(item.parsedValue);
}
}
private class EditableAboutViewHolder extends BaseViewHolder{
private EditText title;
private EditText value;
public EditableAboutViewHolder(){
super(R.layout.item_profile_about_editable);
title=findViewById(R.id.title);
value=findViewById(R.id.value);
findViewById(R.id.dragger_thingy).setOnLongClickListener(v->{
dragHelper.startDrag(this);
return true;
});
title.addTextChangedListener(new SimpleTextWatcher(e->item.name=e.toString()));
value.addTextChangedListener(new SimpleTextWatcher(e->item.value=e.toString()));
}
@Override
public void onBind(AccountField item){
super.onBind(item);
title.setText(item.name);
value.setText(item.value);
}
}
private class AddRowViewHolder extends BaseViewHolder implements UsableRecyclerView.Clickable{
public AddRowViewHolder(){
super(R.layout.item_profile_about_add_row);
}
@Override
public void onClick(){
fields.add(new AccountField());
if(fields.size()==MAX_FIELDS){ // replace this row with new row
adapter.notifyItemChanged(fields.size()-1);
}else{
adapter.notifyItemInserted(fields.size()-1);
rebind();
}
}
}
private class ReorderCallback extends ItemTouchHelper.SimpleCallback{
public ReorderCallback(){
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target){
if(target instanceof AddRowViewHolder)
return false;
int fromPosition=viewHolder.getAbsoluteAdapterPosition();
int toPosition=target.getAbsoluteAdapterPosition();
if (fromPosition<toPosition) {
for (int i=fromPosition;i<toPosition;i++) {
Collections.swap(fields, i, i+1);
}
} else {
for (int i=fromPosition;i>toPosition;i--) {
Collections.swap(fields, i, i-1);
}
}
adapter.notifyItemMoved(fromPosition, toPosition);
((BindableViewHolder)viewHolder).rebind();
((BindableViewHolder)target).rebind();
return true;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction){
}
@Override
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState){
super.onSelectedChanged(viewHolder, actionState);
if(actionState==ItemTouchHelper.ACTION_STATE_DRAG){
viewHolder.itemView.setTag(R.id.item_touch_helper_previous_elevation, viewHolder.itemView.getElevation()); // prevents the default behavior of changing elevation in onDraw()
viewHolder.itemView.animate().translationZ(V.dp(1)).setDuration(200).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
draggedViewHolder=viewHolder;
}
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder){
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.animate().translationZ(0).setDuration(100).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
draggedViewHolder=null;
}
@Override
public boolean isLongPressDragEnabled(){
return false;
}
}
}

View File

@@ -1,12 +1,18 @@
package org.joinmastodon.android.fragments;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.app.Fragment;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Outline;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -17,14 +23,18 @@ import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toolbar;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.AccountField;
@@ -38,7 +48,6 @@ import org.joinmastodon.android.ui.views.CoverImageView;
import org.joinmastodon.android.ui.views.NestedRecyclerScrollView;
import org.parceler.Parcels;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
@@ -55,11 +64,15 @@ import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.fragments.LoaderFragment;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
public class ProfileFragment extends LoaderFragment{
public class ProfileFragment extends LoaderFragment implements OnBackPressedListener{
private static final int AVATAR_RESULT=722;
private static final int COVER_RESULT=343;
private ImageView avatar;
private CoverImageView cover;
@@ -74,6 +87,8 @@ public class ProfileFragment extends LoaderFragment{
private SwipeRefreshLayout refreshLayout;
private CoverOverlayGradientDrawable coverGradient=new CoverOverlayGradientDrawable();
private float titleTransY;
private View postsBtn, followersBtn, followingBtn;
private EditText nameEdit, bioEdit;
private Account account;
private String accountID;
@@ -82,6 +97,9 @@ public class ProfileFragment extends LoaderFragment{
private boolean isOwnProfile;
private ArrayList<AccountField> fields=new ArrayList<>();
private boolean isInEditMode;
private Uri editNewAvatar, editNewCover;
public ProfileFragment(){
super(R.layout.loader_fragment_overlay_toolbar);
}
@@ -107,15 +125,20 @@ public class ProfileFragment extends LoaderFragment{
bio=content.findViewById(R.id.bio);
followersCount=content.findViewById(R.id.followers_count);
followersLabel=content.findViewById(R.id.followers_label);
followersBtn=content.findViewById(R.id.followers_btn);
followingCount=content.findViewById(R.id.following_count);
followingLabel=content.findViewById(R.id.following_label);
followingBtn=content.findViewById(R.id.following_btn);
postsCount=content.findViewById(R.id.posts_count);
postsLabel=content.findViewById(R.id.posts_label);
postsBtn=content.findViewById(R.id.posts_btn);
actionButton=content.findViewById(R.id.profile_action_btn);
pager=content.findViewById(R.id.pager);
scrollView=content.findViewById(R.id.scroller);
tabbar=content.findViewById(R.id.tabbar);
refreshLayout=content.findViewById(R.id.refresh_layout);
nameEdit=content.findViewById(R.id.name_edit);
bioEdit=content.findViewById(R.id.bio_edit);
avatar.setOutlineProvider(new ViewOutlineProvider(){
@Override
@@ -174,6 +197,10 @@ public class ProfileFragment extends LoaderFragment{
}
});
actionButton.setOnClickListener(this::onActionButtonClick);
avatar.setOnClickListener(this::onAvatarClick);
cover.setOnClickListener(this::onCoverClick);
return sizeWrapper;
}
@@ -287,6 +314,19 @@ public class ProfileFragment extends LoaderFragment{
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
if(isOwnProfile && isInEditMode){
Button cancelButton=new Button(getActivity(), null, 0, R.style.Widget_Mastodon_Button_Secondary);
cancelButton.setText(R.string.cancel);
cancelButton.setOnClickListener(v->exitEditMode());
FrameLayout wrap=new FrameLayout(getActivity());
wrap.addView(cancelButton, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.TOP|Gravity.LEFT));
wrap.setPadding(V.dp(16), V.dp(4), V.dp(16), V.dp(8));
wrap.setClipToPadding(false);
MenuItem item=menu.add(R.string.cancel);
item.setActionView(wrap);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
return;
}
if(relationship==null)
return;
inflater.inflate(R.menu.profile, menu);
@@ -383,6 +423,179 @@ public class ProfileFragment extends LoaderFragment{
return getFragmentForPage(pager.getCurrentItem()).getView().findViewById(R.id.list);
}
private void onActionButtonClick(View v){
if(isOwnProfile){
if(!isInEditMode)
loadAccountInfoAndEnterEditMode();
else
saveAndExitEditMode();
}
}
private void loadAccountInfoAndEnterEditMode(){
new GetOwnAccount()
.setCallback(new Callback<>(){
@Override
public void onSuccess(Account result){
enterEditMode(result);
}
@Override
public void onError(ErrorResponse error){
error.showToast(getActivity());
}
})
.wrapProgress(getActivity(), R.string.loading, true)
.exec(accountID);
}
private void enterEditMode(Account account){
if(isInEditMode)
throw new IllegalStateException();
isInEditMode=true;
invalidateOptionsMenu();
pager.setUserInputEnabled(false);
actionButton.setText(R.string.done);
pager.setCurrentItem(3);
ArrayList<Animator> animators=new ArrayList<>();
for(int i=0;i<3;i++){
animators.add(ObjectAnimator.ofFloat(tabbar.getTabAt(i).view, View.ALPHA, .3f));
tabbar.getTabAt(i).view.setEnabled(false);
}
Drawable overlay=getResources().getDrawable(R.drawable.edit_avatar_overlay).mutate();
avatar.setForeground(overlay);
animators.add(ObjectAnimator.ofInt(overlay, "alpha", 0, 255));
nameEdit.setVisibility(View.VISIBLE);
nameEdit.setText(account.displayName);
RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) username.getLayoutParams();
lp.addRule(RelativeLayout.BELOW, R.id.name_edit);
username.getParent().requestLayout();
animators.add(ObjectAnimator.ofFloat(nameEdit, View.ALPHA, 0f, 1f));
bioEdit.setVisibility(View.VISIBLE);
bioEdit.setText(account.source.note);
animators.add(ObjectAnimator.ofFloat(bioEdit, View.ALPHA, 0f, 1f));
animators.add(ObjectAnimator.ofFloat(bio, View.ALPHA, 0f));
animators.add(ObjectAnimator.ofFloat(postsBtn, View.ALPHA, .3f));
animators.add(ObjectAnimator.ofFloat(followersBtn, View.ALPHA, .3f));
animators.add(ObjectAnimator.ofFloat(followingBtn, View.ALPHA, .3f));
AnimatorSet set=new AnimatorSet();
set.playTogether(animators);
set.setDuration(300);
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.start();
aboutFragment.enterEditMode(account.source.fields);
}
private void exitEditMode(){
if(!isInEditMode)
throw new IllegalStateException();
isInEditMode=false;
invalidateOptionsMenu();
ArrayList<Animator> animators=new ArrayList<>();
actionButton.setText(R.string.edit_profile);
for(int i=0;i<3;i++){
animators.add(ObjectAnimator.ofFloat(tabbar.getTabAt(i).view, View.ALPHA, 1f));
}
animators.add(ObjectAnimator.ofInt(avatar.getForeground(), "alpha", 0));
animators.add(ObjectAnimator.ofFloat(nameEdit, View.ALPHA, 0f));
animators.add(ObjectAnimator.ofFloat(bioEdit, View.ALPHA, 0f));
animators.add(ObjectAnimator.ofFloat(bio, View.ALPHA, 1f));
animators.add(ObjectAnimator.ofFloat(postsBtn, View.ALPHA, 1f));
animators.add(ObjectAnimator.ofFloat(followersBtn, View.ALPHA, 1f));
animators.add(ObjectAnimator.ofFloat(followingBtn, View.ALPHA, 1f));
AnimatorSet set=new AnimatorSet();
set.playTogether(animators);
set.setDuration(200);
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
for(int i=0;i<3;i++){
tabbar.getTabAt(i).view.setEnabled(true);
}
pager.setUserInputEnabled(true);
nameEdit.setVisibility(View.GONE);
bioEdit.setVisibility(View.GONE);
RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) username.getLayoutParams();
lp.addRule(RelativeLayout.BELOW, R.id.name);
username.getParent().requestLayout();
avatar.setForeground(null);
}
});
set.start();
bindHeaderView();
}
private void saveAndExitEditMode(){
if(!isInEditMode)
throw new IllegalStateException();
new UpdateAccountCredentials(nameEdit.getText().toString(), bioEdit.getText().toString(), editNewAvatar, editNewCover, aboutFragment.getFields())
.setCallback(new Callback<>(){
@Override
public void onSuccess(Account result){
account=result;
AccountSessionManager.getInstance().updateAccountInfo(accountID, account);
exitEditMode();
}
@Override
public void onError(ErrorResponse error){
error.showToast(getActivity());
}
})
.wrapProgress(getActivity(), R.string.saving, false)
.exec(accountID);
}
@Override
public boolean onBackPressed(){
if(isInEditMode){
exitEditMode();
return true;
}
return false;
}
private void onAvatarClick(View v){
if(isInEditMode){
startImagePicker(AVATAR_RESULT);
}
}
private void onCoverClick(View v){
if(isInEditMode){
startImagePicker(COVER_RESULT);
}
}
private void startImagePicker(int requestCode){
Intent intent=new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, requestCode);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data){
if(resultCode==Activity.RESULT_OK){
if(requestCode==AVATAR_RESULT){
editNewAvatar=data.getData();
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(editNewAvatar, V.dp(100), V.dp(100)));
}else if(requestCode==COVER_RESULT){
editNewCover=data.getData();
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(editNewCover, V.dp(1000), V.dp(1000)));
}
}
}
private class ProfilePagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{
@NonNull
@Override

View File

@@ -0,0 +1,31 @@
package org.joinmastodon.android.ui.utils;
import android.text.Editable;
import android.text.TextWatcher;
import java.util.function.Consumer;
import androidx.annotation.NonNull;
public class SimpleTextWatcher implements TextWatcher{
private final Consumer<Editable> delegate;
public SimpleTextWatcher(@NonNull Consumer<Editable> delegate){
this.delegate=delegate;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after){
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count){
}
@Override
public void afterTextChanged(Editable s){
delegate.accept(s);
}
}

View File

@@ -3,13 +3,16 @@ package org.joinmastodon.android.ui.utils;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.provider.OpenableColumns;
import android.util.Log;
import android.widget.TextView;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import java.time.Instant;
@@ -80,4 +83,14 @@ public class UiUtils{
public static int lerp(int startValue, int endValue, float fraction) {
return startValue + Math.round(fraction * (endValue - startValue));
}
public static String getFileName(Uri uri){
try(Cursor cursor=MastodonApp.context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)){
cursor.moveToFirst();
String name=cursor.getString(0);
if(name!=null)
return name;
}
return uri.getLastPathSegment();
}
}