Profile editing
This commit is contained in:
parent
b8e3426a1e
commit
82a8d0cc29
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<ripple android:color="@color/highlight_over_dark">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@color/gray_600"/>
|
||||
<corners android:radius="10dp"/>
|
||||
<padding android:left="16dp" android:right="16dp" android:top="8dp" android:bottom="8dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
</item>
|
||||
</selector>
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_focused="true">
|
||||
<layer-list>
|
||||
<item android:gravity="bottom">
|
||||
<shape>
|
||||
<size android:height="1dp"/>
|
||||
<solid android:color="@color/primary_700"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
</item>
|
||||
</selector>
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:tint="@color/gray_800">
|
||||
<solid android:color="#CC000000"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:drawable="@drawable/ic_fluent_image_edit_24_regular"
|
||||
android:width="44dp"
|
||||
android:height="44dp"
|
||||
android:gravity="center"/>
|
||||
</layer-list>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@color/gray_200"/>
|
||||
<stroke android:color="@color/gray_400" android:width="1dp"/>
|
||||
<corners android:radius="4dp"/>
|
||||
<padding android:left="8dp" android:top="8dp" android:right="8dp" android:bottom="8dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
|
@ -0,0 +1,3 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2zm0 1.5c-4.694 0-8.5 3.806-8.5 8.5s3.806 8.5 8.5 8.5 8.5-3.806 8.5-8.5-3.806-8.5-8.5-8.5zM12 7c0.414 0 0.75 0.336 0.75 0.75v3.5h3.5c0.414 0 0.75 0.336 0.75 0.75s-0.336 0.75-0.75 0.75h-3.5v3.5c0 0.414-0.336 0.75-0.75 0.75s-0.75-0.336-0.75-0.75v-3.5h-3.5C7.336 12.75 7 12.414 7 12s0.336-0.75 0.75-0.75h3.5v-3.5C11.25 7.336 11.586 7 12 7z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
|
@ -0,0 +1,3 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M20.998 6.25c0-1.795-1.454-3.25-3.25-3.25H6.25C4.455 3 3 4.455 3 6.25v11.499c0 1.795 1.455 3.25 3.25 3.25h4.914l0.356-1.424 0.02-0.076H6.25c-0.204 0-0.4-0.035-0.582-0.1l5.807-5.685 0.083-0.07c0.291-0.213 0.7-0.19 0.966 0.07l2.079 2.036 1.06-1.06-2.09-2.048-0.128-0.116c-0.878-0.738-2.187-0.7-3.02 0.116l-5.822 5.7C4.536 18.157 4.5 17.957 4.5 17.75V6.25c0-0.966 0.783-1.75 1.75-1.75h11.499c0.966 0 1.75 0.784 1.75 1.75v4.983c0.478-0.19 0.993-0.264 1.5-0.22V6.25zm-3.495 2.502c0-1.244-1.008-2.252-2.252-2.252-1.244 0-2.252 1.008-2.252 2.252 0 1.243 1.008 2.252 2.252 2.252 1.244 0 2.252-1.009 2.252-2.252zm-3.004 0C14.499 8.336 14.836 8 15.251 8s0.752 0.336 0.752 0.752c0 0.415-0.337 0.752-0.752 0.752s-0.752-0.337-0.752-0.752zm4.6 3.917l-5.902 5.901c-0.345 0.345-0.589 0.776-0.707 1.248l-0.457 1.83c-0.2 0.797 0.522 1.518 1.318 1.319l1.83-0.458c0.472-0.118 0.904-0.362 1.248-0.706L22.33 15.9c0.892-0.893 0.892-2.34 0-3.232-0.893-0.893-2.34-0.893-3.233 0z" android:fillColor="@color/gray_25"/>
|
||||
</vector>
|
|
@ -0,0 +1,3 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M15.5 17c0.828 0 1.5 0.672 1.5 1.5S16.328 20 15.5 20 14 19.328 14 18.5s0.672-1.5 1.5-1.5zm-7 0c0.828 0 1.5 0.672 1.5 1.5S9.328 20 8.5 20 7 19.328 7 18.5 7.672 17 8.5 17zm7-7c0.828 0 1.5 0.672 1.5 1.5S16.328 13 15.5 13 14 12.328 14 11.5s0.672-1.5 1.5-1.5zm-7 0c0.828 0 1.5 0.672 1.5 1.5S9.328 13 8.5 13 7 12.328 7 11.5 7.672 10 8.5 10zm7-7C16.328 3 17 3.672 17 4.5S16.328 6 15.5 6 14 5.328 14 4.5 14.672 3 15.5 3zm-7 0C9.328 3 10 3.672 10 4.5S9.328 6 8.5 6 7 5.328 7 4.5 7.672 3 8.5 3z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
|
@ -45,6 +45,7 @@
|
|||
android:layout_alignParentStart="true"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="-38dp"
|
||||
android:scaleType="centerCrop"
|
||||
tools:src="#0f0" />
|
||||
|
||||
<LinearLayout
|
||||
|
@ -101,25 +102,27 @@
|
|||
android:id="@+id/posts_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="56dp"
|
||||
android:layout_toStartOf="@id/followers_btn"
|
||||
android:layout_below="@id/cover"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:padding="4dp"
|
||||
android:layout_toStartOf="@id/followers_btn"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal">
|
||||
android:padding="4dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/posts_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_large"
|
||||
tools:text="123"/>
|
||||
tools:text="123" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/posts_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
tools:text="following"/>
|
||||
tools:text="following" />
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
|
@ -137,11 +140,11 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/avatar"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_toStartOf="@id/profile_action_btn"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_toStartOf="@id/profile_action_btn"
|
||||
android:textAppearance="@style/m3_headline_small"
|
||||
tools:text="Eugen"/>
|
||||
tools:text="Eugen" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username"
|
||||
|
@ -165,6 +168,36 @@
|
|||
android:textAppearance="@style/m3_body_large"
|
||||
tools:text="Founder, CEO and lead developer @Mastodon, Germany." />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/name_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/avatar"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_toStartOf="@id/profile_action_btn"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:background="@drawable/edit_text_border"
|
||||
android:inputType="textPersonName|textCapWords"
|
||||
android:visibility="gone"
|
||||
tools:text="Eugen" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/bio_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/username"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:background="@null"
|
||||
android:padding="0px"
|
||||
android:inputType="textMultiLine|textCapSentences"
|
||||
android:visibility="gone"
|
||||
tools:text="Founder, CEO and lead developer @Mastodon, Germany." />
|
||||
|
||||
</RelativeLayout>
|
||||
<org.joinmastodon.android.ui.tabs.TabLayout
|
||||
android:id="@+id/tabbar"
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/gray_50"
|
||||
android:elevation="3dp"
|
||||
android:elevation="2dp"
|
||||
android:outlineProvider="background"
|
||||
android:padding="16dp">
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:elevation="2dp"
|
||||
android:outlineProvider="background">
|
||||
|
||||
<View
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:layout_marginStart="16dp"
|
||||
android:backgroundTint="@color/gray_900"
|
||||
android:background="@drawable/ic_fluent_add_circle_24_regular"/>
|
||||
|
||||
</FrameLayout>
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/gray_50"
|
||||
android:elevation="2dp"
|
||||
android:outlineProvider="background"
|
||||
android:padding="16dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toStartOf="@id/dragger_thingy"
|
||||
android:textAppearance="@style/m3_label_medium"
|
||||
android:background="@drawable/bg_profile_field_edit_text"
|
||||
android:hint="@string/field_label"
|
||||
android:minHeight="16dp"
|
||||
android:inputType="textCapSentences"
|
||||
tools:text="Field title"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/value"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_toStartOf="@id/dragger_thingy"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:background="@drawable/bg_profile_field_edit_text"
|
||||
android:hint="@string/field_content"
|
||||
android:inputType="textCapSentences"
|
||||
tools:text="Field value"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/dragger_thingy"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginEnd="-16dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignBottom="@id/value"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_fluent_re_order_dots_vertical_24_regular"/>
|
||||
|
||||
</RelativeLayout>
|
|
@ -15,8 +15,13 @@
|
|||
<color name="gray_50">#F9FAFB</color>
|
||||
<color name="gray_100">#F2F4F7</color>
|
||||
<color name="gray_200">#E4E7EC</color>
|
||||
<color name="gray_800">#282C37</color>
|
||||
<color name="gray_400">#98A2B3</color>
|
||||
<color name="gray_500">#667085</color>
|
||||
<color name="gray_600">#475467</color>
|
||||
<color name="gray_800">#282C37</color>
|
||||
<color name="gray_900">#101828</color>
|
||||
|
||||
<color name="primary_700">#2B90D9</color>
|
||||
|
||||
<color name="gray_800_alpha50">#80282C37</color>
|
||||
<color name="light_ui_action_button">#606984</color>
|
||||
|
|
|
@ -67,4 +67,9 @@
|
|||
<item quantity="other">%,d posts</item>
|
||||
</plurals>
|
||||
<string name="profile_joined">Joined</string>
|
||||
<string name="done">Done</string>
|
||||
<string name="loading">Loading…</string>
|
||||
<string name="field_label">Label</string>
|
||||
<string name="field_content">Content</string>
|
||||
<string name="saving">Saving…</string>
|
||||
</resources>
|
|
@ -44,6 +44,10 @@
|
|||
<item name="android:minWidth">0px</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Mastodon.Button.Secondary">
|
||||
<item name="android:background">@drawable/bg_button_secondary</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Mastodon.Dialog.Alert" parent="android:Theme.Material.Light.Dialog.Alert">
|
||||
<item name="android:windowTitleStyle">@style/alert_title</item>
|
||||
<item name="android:dialogPreferredPadding">24dp</item>
|
||||
|
|
Loading…
Reference in New Issue