Domain badges & info sheet & my fanciest animation yet

This commit is contained in:
Grishka 2024-02-13 07:31:42 +03:00
parent efb8cd565b
commit 8dffbff97c
12 changed files with 670 additions and 71 deletions

View File

@ -62,10 +62,12 @@ import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.SimpleViewHolder;
import org.joinmastodon.android.ui.SingleImagePhotoViewerListener;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.sheets.DecentralizationExplainerSheet;
import org.joinmastodon.android.ui.tabs.TabLayout;
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.text.ImageSpanThatDoesNotBreakShitForNoGoodReason;
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.CoverImageView;
@ -107,7 +109,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private ImageView avatar;
private CoverImageView cover;
private View avatarBorder;
private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel;
private TextView name, username, usernameDomain, bio, followersCount, followersLabel, followingCount, followingLabel;
private ProgressBarButton actionButton;
private ViewPager2 pager;
private NestedRecyclerScrollView scrollView;
@ -185,6 +187,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
avatarBorder=content.findViewById(R.id.avatar_border);
name=content.findViewById(R.id.name);
username=content.findViewById(R.id.username);
usernameDomain=content.findViewById(R.id.username_domain);
bio=content.findViewById(R.id.bio);
followersCount=content.findViewById(R.id.followers_count);
followersLabel=content.findViewById(R.id.followers_label);
@ -320,6 +323,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
nameEdit.addTextChangedListener(new SimpleTextWatcher(e->editDirty=true));
bioEdit.addTextChangedListener(new SimpleTextWatcher(e->editDirty=true));
usernameDomain.setOnClickListener(v->new DecentralizationExplainerSheet(getActivity(), accountID, account).show());
return sizeWrapper;
}
@ -499,22 +504,21 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account);
if(account.locked){
ssb=new SpannableStringBuilder("@");
ssb.append(account.acct);
if(isSelf){
ssb.append('@');
ssb.append(AccountSessionManager.getInstance().getAccount(accountID).domain);
}
ssb=new SpannableStringBuilder(account.username);
ssb.append(" ");
Drawable lock=username.getResources().getDrawable(R.drawable.ic_lock_fill1_20px, getActivity().getTheme()).mutate();
lock.setBounds(0, 0, lock.getIntrinsicWidth(), lock.getIntrinsicHeight());
lock.setTint(username.getCurrentTextColor());
ssb.append(getString(R.string.manually_approves_followers), new ImageSpan(lock, ImageSpan.ALIGN_BOTTOM), 0);
ssb.append(getString(R.string.manually_approves_followers), new ImageSpanThatDoesNotBreakShitForNoGoodReason(lock, ImageSpan.ALIGN_BOTTOM), 0);
username.setText(ssb);
}else{
// noinspection SetTextI18n
username.setText('@'+account.acct+(isSelf ? ('@'+AccountSessionManager.getInstance().getAccount(accountID).domain) : ""));
username.setText(account.username);
}
String domain=account.getDomain();
if(TextUtils.isEmpty(domain))
domain=AccountSessionManager.get(accountID).domain;
usernameDomain.setText(domain);
CharSequence parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID, account);
if(TextUtils.isEmpty(parsedBio)){
bio.setVisibility(View.GONE);

View File

@ -90,7 +90,7 @@ public class Snackbar{
if(current!=null)
current.dismiss();
current=this;
WindowManager.LayoutParams lp=new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION_PANEL, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, PixelFormat.TRANSLUCENT);
WindowManager.LayoutParams lp=new WindowManager.LayoutParams(WindowManager.LayoutParams.LAST_APPLICATION_WINDOW, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, PixelFormat.TRANSLUCENT);
lp.width=ViewGroup.LayoutParams.MATCH_PARENT;
lp.height=ViewGroup.LayoutParams.WRAP_CONTENT;
lp.gravity=Gravity.BOTTOM;

View File

@ -0,0 +1,101 @@
package org.joinmastodon.android.ui.sheets;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.Snackbar;
import org.joinmastodon.android.ui.text.LinkSpan;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.RippleAnimationTextView;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.NodeVisitor;
import androidx.annotation.NonNull;
import me.grishka.appkit.views.BottomSheet;
public class DecentralizationExplainerSheet extends BottomSheet{
private final String handleStr;
public DecentralizationExplainerSheet(@NonNull Context context, String accountID, Account account){
super(context);
View content=context.getSystemService(LayoutInflater.class).inflate(R.layout.sheet_decentralization_info, null);
setContentView(content);
setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(context, R.attr.colorM3Surface),
UiUtils.getThemeColor(context, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());
TextView handleTitle=findViewById(R.id.handle_title);
RippleAnimationTextView handle=findViewById(R.id.handle);
TextView usernameExplanation=findViewById(R.id.username_text);
TextView serverExplanation=findViewById(R.id.server_text);
TextView handleExplanation=findViewById(R.id.handle_explanation);
findViewById(R.id.btn_cancel).setOnClickListener(v->dismiss());
String domain=account.getDomain();
if(TextUtils.isEmpty(domain))
domain=AccountSessionManager.get(accountID).domain;
handleStr="@"+account.username+"@"+domain;
boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account);
handleTitle.setText(isSelf ? R.string.handle_title_own : R.string.handle_title);
handle.setText(handleStr);
usernameExplanation.setText(isSelf ? R.string.handle_username_explanation_own : R.string.handle_username_explanation);
serverExplanation.setText(isSelf ? R.string.handle_server_explanation_own : R.string.handle_server_explanation);
String explanation=context.getString(isSelf ? R.string.handle_explanation_own : R.string.handle_explanation);
SpannableStringBuilder ssb=new SpannableStringBuilder();
Jsoup.parseBodyFragment(explanation).body().traverse(new NodeVisitor(){
private int spanStart;
@Override
public void head(Node node, int depth){
if(node instanceof TextNode tn){
ssb.append(tn.text());
}else if(node instanceof Element){
spanStart=ssb.length();
}
}
@Override
public void tail(Node node, int depth){
if(node instanceof Element){
ssb.setSpan(new LinkSpan("", DecentralizationExplainerSheet.this::showActivityPubAlert, LinkSpan.Type.CUSTOM, null, null, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
});
handleExplanation.setText(ssb);
findViewById(R.id.handle_wrap).setOnClickListener(v->{
context.getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, handleStr));
if(UiUtils.needShowClipboardToast()){
new Snackbar.Builder(context)
.setText(R.string.handle_copied)
.show();
}
});
String _domain=domain;
findViewById(R.id.username_row).setOnClickListener(v->handle.animate(1, account.username.length()+1));
findViewById(R.id.server_row).setOnClickListener(v->handle.animate(handleStr.length()-_domain.length(), handleStr.length()));
}
private void showActivityPubAlert(LinkSpan s){
new M3AlertDialogBuilder(getContext())
.setTitle(R.string.what_is_activitypub_title)
.setMessage(R.string.what_is_activitypub)
.setPositiveButton(R.string.ok, null)
.show();
}
}

View File

@ -0,0 +1,67 @@
package org.joinmastodon.android.ui.text;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.text.style.ImageSpan;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class ImageSpanThatDoesNotBreakShitForNoGoodReason extends ImageSpan{
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Bitmap b){
super(b);
}
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Bitmap b, int verticalAlignment){
super(b, verticalAlignment);
}
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Context context, @NonNull Bitmap bitmap){
super(context, bitmap);
}
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Context context, @NonNull Bitmap bitmap, int verticalAlignment){
super(context, bitmap, verticalAlignment);
}
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Drawable drawable){
super(drawable);
}
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Drawable drawable, int verticalAlignment){
super(drawable, verticalAlignment);
}
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Drawable drawable, @NonNull String source){
super(drawable, source);
}
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Drawable drawable, @NonNull String source, int verticalAlignment){
super(drawable, source, verticalAlignment);
}
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Context context, @NonNull Uri uri){
super(context, uri);
}
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Context context, @NonNull Uri uri, int verticalAlignment){
super(context, uri, verticalAlignment);
}
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Context context, int resourceId){
super(context, resourceId);
}
public ImageSpanThatDoesNotBreakShitForNoGoodReason(@NonNull Context context, int resourceId, int verticalAlignment){
super(context, resourceId, verticalAlignment);
}
@Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm){
// Purposefully not touching the font metrics
return getDrawable().getBounds().right;
}
}

View File

@ -929,11 +929,15 @@ public class UiUtils{
public static void maybeShowTextCopiedToast(Context context){
//show toast, android from S_V2 on has built-in popup, as documented in
//https://developer.android.com/develop/ui/views/touch-and-input/copy-paste#duplicate-notifications
if(Build.VERSION.SDK_INT<=Build.VERSION_CODES.S_V2){
if(needShowClipboardToast()){
Toast.makeText(context, R.string.text_copied, Toast.LENGTH_SHORT).show();
}
}
public static boolean needShowClipboardToast(){
return Build.VERSION.SDK_INT<=Build.VERSION_CODES.S_V2;
}
public static void setAllPaddings(View view, int paddingDp){
int pad=V.dp(paddingDp);
view.setPadding(pad, pad, pad, pad);

View File

@ -0,0 +1,170 @@
package org.joinmastodon.android.ui.views;
import android.animation.ArgbEvaluator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.Layout;
import android.util.AttributeSet;
import android.widget.TextView;
import androidx.dynamicanimation.animation.FloatValueHolder;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import me.grishka.appkit.utils.CustomViewHelper;
public class RippleAnimationTextView extends TextView implements CustomViewHelper{
private final Paint animationPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
private CharacterAnimationState[] charStates;
private final ArgbEvaluator colorEvaluator=new ArgbEvaluator();
private int runningAnimCount=0;
private Runnable[] delayedAnimations1, delayedAnimations2;
public RippleAnimationTextView(Context context){
this(context, null);
}
public RippleAnimationTextView(Context context, AttributeSet attrs){
this(context, attrs, 0);
}
public RippleAnimationTextView(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
}
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter){
super.onTextChanged(text, start, lengthBefore, lengthAfter);
if(charStates!=null){
for(CharacterAnimationState state:charStates){
state.colorAnimation.cancel();
state.shadowAnimation.cancel();
state.scaleAnimation.cancel();
}
for(Runnable r:delayedAnimations1){
if(r!=null)
removeCallbacks(r);
}
for(Runnable r:delayedAnimations2){
if(r!=null)
removeCallbacks(r);
}
}
charStates=new CharacterAnimationState[lengthAfter];
delayedAnimations1=new Runnable[lengthAfter];
delayedAnimations2=new Runnable[lengthAfter];
}
@Override
protected void onDraw(Canvas canvas){
if(runningAnimCount==0 && !areThereDelayedAnimations()){
super.onDraw(canvas);
return;
}
Layout layout=getLayout();
animationPaint.set(getPaint());
CharSequence text=layout.getText();
for(int i=0;i<layout.getLineCount();i++){
int baseline=layout.getLineBaseline(i);
for(int offset=layout.getLineStart(i); offset<layout.getLineEnd(i); offset++){
float x=layout.getPrimaryHorizontal(offset);
CharacterAnimationState state=charStates[offset];
if(state==null || state.scaleAnimation==null){
animationPaint.setColor(getCurrentTextColor());
animationPaint.clearShadowLayer();
canvas.drawText(text, offset, offset+1, x, baseline, animationPaint);
}else{
animationPaint.setColor((int)colorEvaluator.evaluate(Math.max(0, Math.min(1, state.color.getValue())), getCurrentTextColor(), getLinkTextColors().getDefaultColor()));
float scale=state.scale.getValue();
int shadowAlpha=Math.round(255*Math.max(0, Math.min(1, state.shadowAlpha.getValue())));
animationPaint.setShadowLayer(dp(4), 0, dp(3), (getPaint().linkColor & 0xFFFFFF) | (shadowAlpha << 24));
canvas.save();
canvas.scale(scale, scale, x, baseline);
canvas.drawText(text, offset, offset+1, x, baseline, animationPaint);
canvas.restore();
}
}
}
invalidate();
}
public void animate(int startIndex, int endIndex){
for(int i=startIndex;i<endIndex;i++){
CharacterAnimationState _state=charStates[i];
if(_state==null){
_state=charStates[i]=new CharacterAnimationState();
}
CharacterAnimationState state=_state;
int finalI=i;
postOnAnimationDelayed(()->{
if(!state.colorAnimation.isRunning())
runningAnimCount++;
state.colorAnimation.animateToFinalPosition(1f);
if(!state.shadowAnimation.isRunning())
runningAnimCount++;
state.shadowAnimation.animateToFinalPosition(0.3f);
if(!state.scaleAnimation.isRunning())
runningAnimCount++;
state.scaleAnimation.animateToFinalPosition(1.2f);
invalidate();
if(delayedAnimations1[finalI]!=null)
removeCallbacks(delayedAnimations1[finalI]);
if(delayedAnimations2[finalI]!=null)
removeCallbacks(delayedAnimations2[finalI]);
Runnable delay1=()->{
if(!state.colorAnimation.isRunning())
runningAnimCount++;
state.colorAnimation.animateToFinalPosition(0f);
if(!state.shadowAnimation.isRunning())
runningAnimCount++;
state.shadowAnimation.animateToFinalPosition(0f);
invalidate();
delayedAnimations1[finalI]=null;
};
Runnable delay2=()->{
if(!state.scaleAnimation.isRunning())
runningAnimCount++;
state.scaleAnimation.animateToFinalPosition(1f);
delayedAnimations2[finalI]=null;
};
delayedAnimations1[finalI]=delay1;
delayedAnimations2[finalI]=delay2;
postOnAnimationDelayed(delay1, 2000);
postOnAnimationDelayed(delay2, 100);
}, 20L*(i-startIndex));
}
}
private boolean areThereDelayedAnimations(){
for(Runnable r:delayedAnimations1){
if(r!=null)
return true;
}
for(Runnable r:delayedAnimations2){
if(r!=null)
return true;
}
return false;
}
private class CharacterAnimationState extends FloatValueHolder{
private final SpringAnimation scaleAnimation, colorAnimation, shadowAnimation;
private final FloatValueHolder scale=new FloatValueHolder(1), color=new FloatValueHolder(), shadowAlpha=new FloatValueHolder();
public CharacterAnimationState(){
scaleAnimation=new SpringAnimation(scale);
colorAnimation=new SpringAnimation(color);
shadowAnimation=new SpringAnimation(shadowAlpha);
setupSpring(scaleAnimation);
setupSpring(colorAnimation);
setupSpring(shadowAnimation);
}
private void setupSpring(SpringAnimation anim){
anim.setMinimumVisibleChange(0.01f);
anim.setSpring(new SpringForce().setStiffness(500f).setDampingRatio(0.175f));
anim.addEndListener((animation, canceled, value, velocity)->runningAnimCount--);
}
}
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape
android:tint="@color/m3_primary_alpha11"
android:tintMode="src_over">
<solid android:color="?colorM3Surface" />
<corners android:radius="8dp"/>
</shape>
</item>
<item>
<shape>
<stroke android:width="2dp" android:color="?colorM3OutlineVariant" android:dashWidth="5dp" android:dashGap="5dp"/>
<corners android:radius="8dp"/>
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M4,22Q3.175,22 2.588,21.413Q2,20.825 2,20V9Q2,8.175 2.588,7.587Q3.175,7 4,7H9V4Q9,3.175 9.588,2.587Q10.175,2 11,2H13Q13.825,2 14.413,2.587Q15,3.175 15,4V7H20Q20.825,7 21.413,7.587Q22,8.175 22,9V20Q22,20.825 21.413,21.413Q20.825,22 20,22ZM4,20H20Q20,20 20,20Q20,20 20,20V9Q20,9 20,9Q20,9 20,9H15Q15,9.825 14.413,10.412Q13.825,11 13,11H11Q10.175,11 9.588,10.412Q9,9.825 9,9H4Q4,9 4,9Q4,9 4,9V20Q4,20 4,20Q4,20 4,20ZM6,18H12V17.55Q12,17.125 11.762,16.762Q11.525,16.4 11.1,16.2Q10.6,15.975 10.088,15.863Q9.575,15.75 9,15.75Q8.425,15.75 7.913,15.863Q7.4,15.975 6.9,16.2Q6.475,16.4 6.238,16.762Q6,17.125 6,17.55ZM14,16.5H18V15H14ZM9,15Q9.625,15 10.062,14.562Q10.5,14.125 10.5,13.5Q10.5,12.875 10.062,12.438Q9.625,12 9,12Q8.375,12 7.938,12.438Q7.5,12.875 7.5,13.5Q7.5,14.125 7.938,14.562Q8.375,15 9,15ZM14,13.5H18V12H14ZM11,9H13V4H11ZM12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Q12,14.5 12,14.5Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22ZM11,19.95V18Q10.175,18 9.588,17.413Q9,16.825 9,16V15L4.2,10.2Q4.125,10.65 4.062,11.1Q4,11.55 4,12Q4,15.025 5.988,17.3Q7.975,19.575 11,19.95ZM17.9,17.4Q18.925,16.275 19.462,14.887Q20,13.5 20,12Q20,9.55 18.638,7.525Q17.275,5.5 15,4.6V5Q15,5.825 14.413,6.412Q13.825,7 13,7H11V9Q11,9.425 10.713,9.712Q10.425,10 10,10H8V12H14Q14.425,12 14.713,12.287Q15,12.575 15,13V16H16Q16.65,16 17.175,16.387Q17.7,16.775 17.9,17.4Z"/>
</vector>

View File

@ -61,7 +61,7 @@
android:layout_below="@id/cover"
android:layout_alignParentStart="true"
android:layout_marginStart="12dp"
android:layout_marginTop="-44dp"
android:layout_marginTop="-36dp"
android:background="@drawable/profile_ava_bg"
android:outlineProvider="@null">
@ -76,64 +76,63 @@
</FrameLayout>
<FrameLayout
android:id="@+id/profile_action_btn_wrap"
android:layout_width="wrap_content"
<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/cover"
android:layout_alignParentEnd="true"
android:layout_marginTop="16dp"
android:layout_marginHorizontal="16dp"
android:paddingStart="120dp">
android:layout_toEndOf="@id/avatar_border"
android:layout_marginTop="14dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="16dp"
android:fontFamily="sans-serif"
android:textAlignment="viewStart"
android:textAppearance="@style/m3_title_large"
android:textColor="?colorM3OnSurface"
android:maxLines="2"
android:ellipsize="end"
tools:text="Eugen" />
<org.joinmastodon.android.ui.views.ProgressBarButton
android:id="@+id/profile_action_btn"
android:layout_width="wrap_content"
android:layout_height="40dp"
style="@style/Widget.Mastodon.M3.Button.Filled"
android:minWidth="156dp"
android:paddingHorizontal="16dp"
tools:text="@string/save_changes" />
<org.joinmastodon.android.ui.views.WrappingLinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/name"
android:layout_toEndOf="@id/avatar_border"
android:layout_marginStart="12dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="2dp"
android:horizontalGap="4dp"
android:verticalGap="0dp">
<ProgressBar
android:id="@+id/action_progress"
style="?android:progressBarStyleSmall"
<TextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:elevation="10dp"
android:indeterminate="true"
android:outlineProvider="none"
android:visibility="gone" />
</FrameLayout>
android:layout_height="20dp"
android:textAppearance="@style/m3_body_medium"
android:textColor="?colorM3OnSurfaceVariant"
android:singleLine="true"
android:ellipsize="end"
android:gravity="center_vertical"
tools:text="Gargron" />
<TextView
android:id="@+id/username_domain"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:textAppearance="@style/m3_label_small"
android:gravity="center_vertical"
android:paddingHorizontal="4dp"
android:textColor="?colorM3OnSurfaceVariant"
android:singleLine="true"
android:ellipsize="end"
android:background="@drawable/rect_4dp"
android:backgroundTint="?colorM3SurfaceVariant"
tools:text="mastodon.social"/>
</org.joinmastodon.android.ui.views.WrappingLinearLayout>
</RelativeLayout>
<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="12dp"
android:layout_marginRight="16dp"
android:fontFamily="sans-serif"
android:textAlignment="viewStart"
android:textAppearance="@style/m3_title_large"
android:textColor="?colorM3OnSurface"
tools:text="Eugen" />
<TextView
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="2dp"
android:layout_marginRight="16dp"
android:textAppearance="@style/m3_title_small"
android:textColor="?colorM3OnSurfaceVariant"
tools:text="\@Gargron" />
<org.joinmastodon.android.ui.views.LinkedTextView
android:id="@+id/bio"
android:layout_width="match_parent"
@ -192,18 +191,11 @@
tools:text="followers" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="28dp"
android:gravity="center"
android:text="·"
android:textAppearance="@style/m3_label_large"
android:textColor="?colorM3OnSurfaceVariant" />
<LinearLayout
android:id="@+id/following_btn"
android:layout_width="wrap_content"
android:layout_height="28dp"
android:layout_marginStart="-4dp"
android:background="@drawable/bg_button_borderless_rounded"
android:gravity="center_horizontal"
android:orientation="horizontal"
@ -289,6 +281,41 @@
android:padding="16dp"
tools:text="Founder, CEO and lead developer @Mastodon, Germany." />
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="horizontal"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="8dp">
<FrameLayout
android:id="@+id/profile_action_btn_wrap"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<org.joinmastodon.android.ui.views.ProgressBarButton
android:id="@+id/profile_action_btn"
android:layout_width="match_parent"
android:layout_height="40dp"
style="@style/Widget.Mastodon.M3.Button.Filled"
android:minWidth="156dp"
android:paddingHorizontal="16dp"
tools:text="@string/save_changes" />
<ProgressBar
android:id="@+id/action_progress"
style="?android:progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:elevation="10dp"
android:indeterminate="true"
android:outlineProvider="none"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>
<org.joinmastodon.android.ui.tabs.TabLayout
android:id="@+id/tabbar"

View File

@ -0,0 +1,178 @@
<?xml version="1.0" encoding="utf-8"?>
<org.joinmastodon.android.ui.views.CustomScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@drawable/bg_bottom_sheet"
android:outlineProvider="background"
android:elevation="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:id="@+id/sheet_handle"
android:layout_width="match_parent"
android:layout_height="36dp"
android:background="@drawable/bg_bottom_sheet_handle"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:id="@+id/icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="16dp"
android:background="@drawable/white_circle"
android:backgroundTint="?colorM3SecondaryContainer"
android:scaleType="center"
android:tint="?colorM3OnSecondaryContainer"
android:src="@drawable/ic_badge_24px"
android:importantForAccessibility="no"/>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_large"
android:fontFamily="sans-serif"
android:textColor="?colorM3OnSurface"
android:text="@string/handle_help_title"/>
</LinearLayout>
<LinearLayout
android:id="@+id/handle_wrap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:layout_marginHorizontal="16dp"
android:orientation="vertical"
android:background="@drawable/bg_handle_help"
android:clipChildren="false"
android:clipToPadding="false"
android:padding="12dp">
<TextView
android:id="@+id/handle_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_label_small"
android:textColor="?colorM3OnSurfaceVariant"
android:alpha="0.6"
tools:text="@string/handle_title"/>
<org.joinmastodon.android.ui.views.RippleAnimationTextView
android:id="@+id/handle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textAppearance="@style/m3_body_large"
android:textColor="?colorM3OnSurface"
tools:text="\@Gargron@mastodon.social"/>
</LinearLayout>
<RelativeLayout
android:id="@+id/username_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:layout_marginBottom="16dp">
<ImageView
android:id="@+id/icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="16dp"
android:importantForAccessibility="no"
android:scaleType="center"
android:tint="?colorM3Primary"
android:src="@drawable/ic_alternate_email_24px"/>
<TextView
android:id="@+id/username_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/icon"
android:textAppearance="@style/m3_title_medium"
android:textColor="?colorM3OnSurface"
android:text="@string/username"/>
<TextView
android:id="@+id/username_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/icon"
android:layout_below="@id/username_title"
android:textAppearance="@style/m3_body_small"
android:textColor="?colorM3OnSurfaceVariant"
tools:text="@string/handle_username_explanation"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/server_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:layout_marginBottom="16dp">
<ImageView
android:id="@+id/icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="16dp"
android:importantForAccessibility="no"
android:scaleType="center"
android:tint="?colorM3Primary"
android:src="@drawable/ic_public_24px"/>
<TextView
android:id="@+id/server_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/icon"
android:textAppearance="@style/m3_title_medium"
android:textColor="?colorM3OnSurface"
android:text="@string/server"/>
<TextView
android:id="@+id/server_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/icon"
android:layout_below="@id/server_title"
android:textAppearance="@style/m3_body_small"
android:textColor="?colorM3OnSurfaceVariant"
tools:text="@string/handle_server_explanation"/>
</RelativeLayout>
<org.joinmastodon.android.ui.views.LinkedTextView
android:id="@+id/handle_explanation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="16dp"
android:textAppearance="@style/m3_body_medium"
android:textColor="?colorM3OnSurfaceVariant"
tools:text="@string/handle_explanation"/>
<Button
android:id="@+id/btn_cancel"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginBottom="8dp"
android:layout_marginHorizontal="16dp"
style="@style/Widget.Mastodon.M3.Button.Filled"
android:text="@string/got_it"/>
</LinearLayout>
</org.joinmastodon.android.ui.views.CustomScrollView>

View File

@ -689,4 +689,17 @@
<string name="server_cant_mention_or_follow_you">Nobody from this server can follow you.</string>
<string name="server_can_interact_with_older">People from this server can interact with your old posts.</string>
<string name="unblocked_domain_x">Unblocked domain %s</string>
<string name="handle_help_title">Whats in a handle?</string>
<string name="handle_title">Their handle</string>
<string name="handle_username_explanation">Their unique identifier on their server. Its possible to find users with the same username on different servers.</string>
<string name="handle_title_own">Your handle</string>
<string name="handle_username_explanation_own">Your unique identifier on this server. Its possible to find users with the same username on different servers.</string>
<string name="server">Server</string>
<string name="handle_server_explanation">Their digital home, where all of their posts live.</string>
<string name="handle_explanation">Since handles say who someone is and where they are, you can interact with people across the social web of &lt;a>ActivityPub-powered platforms&lt;/a>.</string>
<string name="handle_server_explanation_own">Your digital home, where all of your posts live. Dont like this one? Transfer servers at any time and bring your followers, too.</string>
<string name="handle_explanation_own">Because your handle says who you are and where you are, people can interact with you across the social web of &lt;a>ActivityPub-powered platforms&lt;/a>.</string>
<string name="what_is_activitypub_title">Whats ActivityPub?</string>
<string name="what_is_activitypub">ActivityPub is like the language Mastodon speaks with other social networks.\n\nIt lets you connect and interact with people not just on Mastodon, but across different social apps too.</string>
<string name="handle_copied">Handle copied to clipboard.</string>
</resources>