Merge branch 'master' into fork

This commit is contained in:
sk
2022-05-14 13:48:03 +02:00
18 changed files with 283 additions and 134 deletions

View File

@@ -233,6 +233,12 @@ public class CacheController{
}); });
} }
public void deleteStatus(String id){
runOnDbThread((db)->{
db.delete("home_timeline", "`id`=?", new String[]{id});
});
}
public void clearRecentSearches(){ public void clearRecentSearches(){
runOnDbThread((db)->db.delete("recent_searches", null, null)); runOnDbThread((db)->db.delete("recent_searches", null, null));
} }

View File

@@ -85,7 +85,7 @@ public class AccountSessionManager{
domains.add(session.domain.toLowerCase()); domains.add(session.domain.toLowerCase());
sessions.put(session.getID(), session); sessions.put(session.getID(), session);
} }
}catch(IOException|JsonParseException x){ }catch(Exception x){
Log.e(TAG, "Error loading accounts", x); Log.e(TAG, "Error loading accounts", x);
} }
lastActiveAccountID=prefs.getString("lastActiveAccount", null); lastActiveAccountID=prefs.getString("lastActiveAccount", null);
@@ -358,7 +358,7 @@ public class AccountSessionManager{
customEmojis.put(domain, groupCustomEmojis(emojis)); customEmojis.put(domain, groupCustomEmojis(emojis));
instances.put(domain, emojis.instance); instances.put(domain, emojis.instance);
instancesLastUpdated.put(domain, emojis.lastUpdated); instancesLastUpdated.put(domain, emojis.lastUpdated);
}catch(IOException|JsonParseException x){ }catch(Exception x){
Log.w(TAG, "Error reading instance info file for "+domain, x); Log.w(TAG, "Error reading instance info file for "+domain, x);
} }
} }

View File

@@ -461,9 +461,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class); HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if(header!=null) if(header!=null)
header.rebind(); header.rebind();
for(ImageStatusDisplayItem.Holder photo:(List<ImageStatusDisplayItem.Holder>)findAllHoldersOfType(itemID, ImageStatusDisplayItem.Holder.class)){ updateImagesSpoilerState(status, itemID);
photo.setRevealed(true);
}
} }
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder){ public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder){
@@ -472,12 +470,25 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
if(!TextUtils.isEmpty(status.spoilerText)){ if(!TextUtils.isEmpty(status.spoilerText)){
TextStatusDisplayItem.Holder text=findHolderOfType(holder.getItemID(), TextStatusDisplayItem.Holder.class); TextStatusDisplayItem.Holder text=findHolderOfType(holder.getItemID(), TextStatusDisplayItem.Holder.class);
if(text!=null){ if(text!=null){
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()+getMainAdapterOffset()); adapter.notifyItemChanged(text.getAbsoluteAdapterPosition());
} }
} }
holder.rebind(); holder.rebind();
for(ImageStatusDisplayItem.Holder<?> photo:(List<ImageStatusDisplayItem.Holder>)findAllHoldersOfType(holder.getItemID(), ImageStatusDisplayItem.Holder.class)){ updateImagesSpoilerState(status, holder.getItemID());
}
protected void updateImagesSpoilerState(Status status, String itemID){
ArrayList<Integer> updatedPositions=new ArrayList<>();
for(ImageStatusDisplayItem.Holder photo:(List<ImageStatusDisplayItem.Holder>)findAllHoldersOfType(itemID, ImageStatusDisplayItem.Holder.class)){
photo.setRevealed(status.spoilerRevealed); photo.setRevealed(status.spoilerRevealed);
updatedPositions.add(photo.getAbsoluteAdapterPosition()-getMainAdapterOffset());
}
int i=0;
for(StatusDisplayItem item:displayItems){
if(itemID.equals(item.parentID) && item instanceof ImageStatusDisplayItem && !updatedPositions.contains(i)){
adapter.notifyItemChanged(i);
}
i++;
} }
} }

View File

@@ -610,12 +610,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
public void onSuccess(Status result){ public void onSuccess(Status result){
wm.removeView(sendingOverlay); wm.removeView(sendingOverlay);
sendingOverlay=null; sendingOverlay=null;
Nav.finish(ComposeFragment.this);
E.post(new StatusCreatedEvent(result)); E.post(new StatusCreatedEvent(result));
if(replyTo!=null){ if(replyTo!=null){
replyTo.repliesCount++; replyTo.repliesCount++;
E.post(new StatusCountersUpdatedEvent(replyTo)); E.post(new StatusCountersUpdatedEvent(replyTo));
} }
Nav.finish(ComposeFragment.this);
} }
@Override @Override

View File

@@ -515,10 +515,17 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
return; return;
} }
if(relationship==null) if(relationship==null && !isOwnProfile)
return; return;
inflater.inflate(R.menu.profile, menu); inflater.inflate(R.menu.profile, menu);
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getDisplayUsername())); menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getDisplayUsername()));
if(isOwnProfile){
for(int i=0;i<menu.size();i++){
MenuItem item=menu.getItem(i);
item.setVisible(item.getItemId()==R.id.share);
}
return;
}
menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername())); menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername())); menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername()));
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getDisplayUsername())); menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getDisplayUsername()));

View File

@@ -13,7 +13,6 @@ import org.joinmastodon.android.events.StatusUnpinnedEvent;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.parceler.Parcels; import org.parceler.Parcels;
@@ -116,10 +115,15 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
return; return;
data.remove(status); data.remove(status);
preloadedData.remove(status); preloadedData.remove(status);
HeaderStatusDisplayItem item=findItemOfType(ev.id, HeaderStatusDisplayItem.class); int index=-1;
if(item==null) for(int i=0;i<displayItems.size();i++){
if(ev.id.equals(displayItems.get(i).parentID)){
index=i;
break;
}
}
if(index==-1)
return; return;
int index=displayItems.indexOf(item);
int lastIndex; int lastIndex;
for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){ for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){
if(!displayItems.get(lastIndex).parentID.equals(ev.id)) if(!displayItems.get(lastIndex).parentID.equals(ev.id))

View File

@@ -157,6 +157,18 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
} }
}); });
tabLayoutMediator.attach(); tabLayoutMediator.attach();
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
@Override
public void onTabSelected(TabLayout.Tab tab){}
@Override
public void onTabUnselected(TabLayout.Tab tab){}
@Override
public void onTabReselected(TabLayout.Tab tab){
scrollToTop();
}
});
searchEdit=view.findViewById(R.id.search_edit); searchEdit=view.findViewById(R.id.search_edit);
searchEdit.setOnFocusChangeListener(this::onSearchEditFocusChanged); searchEdit.setOnFocusChangeListener(this::onSearchEditFocusChanged);

View File

@@ -205,7 +205,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
@Override @Override
public void onTabReselected(TabLayout.Tab tab){ public void onTabReselected(TabLayout.Tab tab){
scrollToTop();
} }
}); });
} }

View File

@@ -162,6 +162,7 @@ public class ComposeAutocompleteViewController{
.map(WrappedEmoji::new) .map(WrappedEmoji::new)
.collect(Collectors.toList()); .collect(Collectors.toList());
UiUtils.updateList(oldList, emojis, list, emojisAdapter, (e1, e2)->e1.emoji.shortcode.equals(e2.emoji.shortcode)); UiUtils.updateList(oldList, emojis, list, emojisAdapter, (e1, e2)->e1.emoji.shortcode.equals(e2.emoji.shortcode));
imgLoader.updateImages();
} }
} }
@@ -186,6 +187,7 @@ public class ComposeAutocompleteViewController{
List<WrappedAccount> oldList=users; List<WrappedAccount> oldList=users;
users=result.accounts.stream().map(WrappedAccount::new).collect(Collectors.toList()); users=result.accounts.stream().map(WrappedAccount::new).collect(Collectors.toList());
UiUtils.updateList(oldList, users, list, usersAdapter, (a1, a2)->a1.account.id.equals(a2.account.id)); UiUtils.updateList(oldList, users, list, usersAdapter, (a1, a2)->a1.account.id.equals(a2.account.id));
imgLoader.updateImages();
if(listIsHidden){ if(listIsHidden){
listIsHidden=false; listIsHidden=false;
V.setVisibilityAnimated(list, View.VISIBLE); V.setVisibilityAnimated(list, View.VISIBLE);
@@ -210,6 +212,7 @@ public class ComposeAutocompleteViewController{
List<Hashtag> oldList=hashtags; List<Hashtag> oldList=hashtags;
hashtags=result.hashtags; hashtags=result.hashtags;
UiUtils.updateList(oldList, hashtags, list, hashtagsAdapter, (t1, t2)->t1.name.equals(t2.name)); UiUtils.updateList(oldList, hashtags, list, hashtagsAdapter, (t1, t2)->t1.name.equals(t2.name));
imgLoader.updateImages();
if(listIsHidden){ if(listIsHidden){
listIsHidden=false; listIsHidden=false;
V.setVisibilityAnimated(list, View.VISIBLE); V.setVisibilityAnimated(list, View.VISIBLE);

View File

@@ -92,7 +92,9 @@ public class AudioStatusDisplayItem extends StatusDisplayItem{
public void onBind(AudioStatusDisplayItem item){ public void onBind(AudioStatusDisplayItem item){
int seconds=(int)item.attachment.getDuration(); int seconds=(int)item.attachment.getDuration();
String duration=formatDuration(seconds); String duration=formatDuration(seconds);
time.getLayoutParams().width=(int)Math.ceil(time.getPaint().measureText("-"+duration)); // Some fonts (not Roboto) have different-width digits. 0 is supposedly the widest.
time.getLayoutParams().width=(int)Math.ceil(Math.max(time.getPaint().measureText("-"+duration),
time.getPaint().measureText("-"+duration.replaceAll("\\d", "0"))));
time.setText(duration); time.setText(duration);
AudioPlayerService service=AudioPlayerService.getInstance(); AudioPlayerService service=AudioPlayerService.getInstance();
if(service!=null && service.getAttachmentID().equals(item.attachment.id)){ if(service!=null && service.getAttachmentID().equals(item.attachment.id)){

View File

@@ -11,6 +11,7 @@ import android.widget.TextView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper; import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import org.joinmastodon.android.ui.views.LinkedTextView; import org.joinmastodon.android.ui.views.LinkedTextView;
@@ -20,7 +21,8 @@ import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
public class TextStatusDisplayItem extends StatusDisplayItem{ public class TextStatusDisplayItem extends StatusDisplayItem{
private CharSequence text; private CharSequence text;
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(); private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(), spoilerEmojiHelper;
private CharSequence parsedSpoilerText;
public boolean textSelectable; public boolean textSelectable;
public final Status status; public final Status status;
@@ -29,6 +31,11 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
this.text=text; this.text=text;
this.status=status; this.status=status;
emojiHelper.setText(text); emojiHelper.setText(text);
if(!TextUtils.isEmpty(status.spoilerText)){
parsedSpoilerText=HtmlParser.parseCustomEmoji(status.spoilerText, status.emojis);
spoilerEmojiHelper=new CustomEmojiHelper();
spoilerEmojiHelper.setText(parsedSpoilerText);
}
} }
@Override @Override
@@ -38,11 +45,15 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public int getImageCount(){ public int getImageCount(){
if(spoilerEmojiHelper!=null && !status.spoilerRevealed)
return spoilerEmojiHelper.getImageCount();
return emojiHelper.getImageCount(); return emojiHelper.getImageCount();
} }
@Override @Override
public ImageLoaderRequest getImageRequest(int index){ public ImageLoaderRequest getImageRequest(int index){
if(spoilerEmojiHelper!=null && !status.spoilerRevealed)
return spoilerEmojiHelper.getImageRequest(index);
return emojiHelper.getImageRequest(index); return emojiHelper.getImageRequest(index);
} }
@@ -65,7 +76,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
text.setTextIsSelectable(item.textSelectable); text.setTextIsSelectable(item.textSelectable);
text.setInvalidateOnEveryFrame(false); text.setInvalidateOnEveryFrame(false);
if(!TextUtils.isEmpty(item.status.spoilerText)){ if(!TextUtils.isEmpty(item.status.spoilerText)){
spoilerTitle.setText(item.status.spoilerText); spoilerTitle.setText(item.parsedSpoilerText);
if(item.status.spoilerRevealed){ if(item.status.spoilerRevealed){
spoilerOverlay.setVisibility(View.GONE); spoilerOverlay.setVisibility(View.GONE);
text.setVisibility(View.VISIBLE); text.setVisibility(View.VISIBLE);
@@ -84,8 +95,9 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void setImage(int index, Drawable image){ public void setImage(int index, Drawable image){
item.emojiHelper.setImageDrawable(index, image); getEmojiHelper().setImageDrawable(index, image);
text.invalidate(); text.invalidate();
spoilerTitle.invalidate();
if(image instanceof Animatable){ if(image instanceof Animatable){
((Animatable) image).start(); ((Animatable) image).start();
if(image instanceof MovieDrawable) if(image instanceof MovieDrawable)
@@ -95,8 +107,12 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void clearImage(int index){ public void clearImage(int index){
item.emojiHelper.setImageDrawable(index, null); getEmojiHelper().setImageDrawable(index, null);
text.invalidate(); text.invalidate();
} }
private CustomEmojiHelper getEmojiHelper(){
return item.spoilerEmojiHelper!=null && !item.status.spoilerRevealed ? item.spoilerEmojiHelper : item.emojiHelper;
}
} }
} }

View File

@@ -0,0 +1,8 @@
package org.joinmastodon.android.ui.text;
/**
* A span to mark character ranges that should be deleted when copied to the clipboard.
* Works with {@link org.joinmastodon.android.ui.views.LinkedTextView}.
*/
public class DeleteWhenCopiedSpan{
}

View File

@@ -67,10 +67,9 @@ public class HtmlParser{
@Override @Override
public void head(@NonNull Node node, int depth){ public void head(@NonNull Node node, int depth){
if(node instanceof TextNode){ if(node instanceof TextNode textNode){
ssb.append(((TextNode) node).text()); ssb.append(textNode.text());
}else if(node instanceof Element){ }else if(node instanceof Element el){
Element el=(Element)node;
switch(el.nodeName()){ switch(el.nodeName()){
case "a" -> { case "a" -> {
String href=el.attr("href"); String href=el.attr("href");
@@ -108,10 +107,9 @@ public class HtmlParser{
@Override @Override
public void tail(@NonNull Node node, int depth){ public void tail(@NonNull Node node, int depth){
if(node instanceof Element){ if(node instanceof Element el){
Element el=(Element)node;
if("span".equals(el.nodeName()) && el.hasClass("ellipsis")){ if("span".equals(el.nodeName()) && el.hasClass("ellipsis")){
ssb.append('…'); ssb.append("", new DeleteWhenCopiedSpan(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}else if("p".equals(el.nodeName())){ }else if("p".equals(el.nodeName())){
if(node.nextSibling()!=null) if(node.nextSibling()!=null)
ssb.append("\n\n"); ssb.append("\n\n");

View File

@@ -109,7 +109,9 @@ public class UiUtils{
long t=instant.toEpochMilli(); long t=instant.toEpochMilli();
long now=System.currentTimeMillis(); long now=System.currentTimeMillis();
long diff=now-t; long diff=now-t;
if(diff<60_000L){ if(diff<1000L){
return context.getString(R.string.time_now);
}else if(diff<60_000L){
return context.getString(R.string.time_seconds, diff/1000L); return context.getString(R.string.time_seconds, diff/1000L);
}else if(diff<3600_000L){ }else if(diff<3600_000L){
return context.getString(R.string.time_minutes, diff/60_000L); return context.getString(R.string.time_minutes, diff/60_000L);
@@ -338,6 +340,7 @@ public class UiUtils{
@Override @Override
public void onSuccess(Status result){ public void onSuccess(Status result){
resultCallback.accept(result); resultCallback.accept(result);
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().deleteStatus(status.id);
E.post(new StatusDeletedEvent(status.id, accountID)); E.post(new StatusDeletedEvent(status.id, accountID));
} }

View File

@@ -1,38 +1,68 @@
package org.joinmastodon.android.ui.views; package org.joinmastodon.android.ui.views;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.widget.TextView; import android.widget.TextView;
import org.joinmastodon.android.ui.text.ClickableLinksDelegate; import org.joinmastodon.android.ui.text.ClickableLinksDelegate;
import org.joinmastodon.android.ui.text.DeleteWhenCopiedSpan;
public class LinkedTextView extends TextView { public class LinkedTextView extends TextView{
private ClickableLinksDelegate delegate=new ClickableLinksDelegate(this); private ClickableLinksDelegate delegate=new ClickableLinksDelegate(this);
private boolean needInvalidate; private boolean needInvalidate;
private ActionMode currentActionMode;
public LinkedTextView(Context context) {
super(context); public LinkedTextView(Context context){
// TODO Auto-generated constructor stub this(context, null);
} }
public LinkedTextView(Context context, AttributeSet attrs) { public LinkedTextView(Context context, AttributeSet attrs){
super(context, attrs); this(context, attrs, 0);
// TODO Auto-generated constructor stub
} }
public LinkedTextView(Context context, AttributeSet attrs, int defStyle) { public LinkedTextView(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle); super(context, attrs, defStyle);
// TODO Auto-generated constructor stub setCustomSelectionActionModeCallback(new ActionMode.Callback(){
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu){
currentActionMode=mode;
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu){
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item){
onTextContextMenuItem(item.getItemId());
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode){
currentActionMode=null;
}
});
} }
public boolean onTouchEvent(MotionEvent ev){ public boolean onTouchEvent(MotionEvent ev){
if(delegate.onTouch(ev)) return true; if(delegate.onTouch(ev)) return true;
return super.onTouchEvent(ev); return super.onTouchEvent(ev);
} }
public void onDraw(Canvas c){ public void onDraw(Canvas c){
super.onDraw(c); super.onDraw(c);
delegate.onDraw(c); delegate.onDraw(c);
@@ -47,4 +77,43 @@ public class LinkedTextView extends TextView {
invalidate(); invalidate();
} }
@Override
public boolean onTextContextMenuItem(int id){
if(id==android.R.id.copy){
final int selStart=getSelectionStart();
final int selEnd=getSelectionEnd();
int min=Math.max(0, Math.min(selStart, selEnd));
int max=Math.max(0, Math.max(selStart, selEnd));
final ClipData copyData=ClipData.newPlainText(null, deleteTextWithinDeleteSpans(getText().subSequence(min, max)));
ClipboardManager clipboard=getContext().getSystemService(ClipboardManager.class);
try {
clipboard.setPrimaryClip(copyData);
} catch (Throwable t) {
Log.w("LinkedTextView", t);
}
if(currentActionMode!=null){
currentActionMode.finish();
}
return true;
}
return super.onTextContextMenuItem(id);
}
private CharSequence deleteTextWithinDeleteSpans(CharSequence text){
if(text instanceof Spanned spanned){
DeleteWhenCopiedSpan[] delSpans=spanned.getSpans(0, text.length(), DeleteWhenCopiedSpan.class);
if(delSpans.length>0){
SpannableStringBuilder ssb=new SpannableStringBuilder(spanned);
for(DeleteWhenCopiedSpan span:delSpans){
int start=ssb.getSpanStart(span);
int end=ssb.getSpanStart(span);
if(start==-1)
continue;
ssb.delete(start, end+1);
}
return ssb;
}
}
return text;
}
} }

View File

@@ -46,6 +46,7 @@
android:textAppearance="@style/m3_label_medium" android:textAppearance="@style/m3_label_medium"
android:textColor="?colorButtonText" android:textColor="?colorButtonText"
android:gravity="end" android:gravity="end"
android:singleLine="true"
tools:text="1:23"/> tools:text="1:23"/>
</LinearLayout> </LinearLayout>

View File

@@ -78,102 +78,110 @@
tools:text="Founder, CEO and lead developer @Mastodon, Germany." /> tools:text="Founder, CEO and lead developer @Mastodon, Germany." />
<LinearLayout <LinearLayout
android:id="@+id/posts_btn" android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_below="@id/bio"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:gravity="center_horizontal"
android:orientation="vertical">
<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" />
<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" />
</LinearLayout>
<LinearLayout
android:id="@+id/followers_btn"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_toEndOf="@id/posts_btn"
android:layout_alignTop="@id/posts_btn"
android:layout_marginStart="12dp"
android:orientation="vertical"
android:gravity="center_horizontal">
<TextView
android:id="@+id/followers_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_large"
tools:text="123"/>
<TextView
android:id="@+id/followers_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_small"
tools:text="following"/>
</LinearLayout>
<LinearLayout
android:id="@+id/following_btn"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_alignTop="@id/posts_btn"
android:layout_toEndOf="@id/followers_btn"
android:layout_marginStart="12dp"
android:orientation="vertical"
android:gravity="center_horizontal">
<TextView
android:id="@+id/following_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_large"
tools:text="123"/>
<TextView
android:id="@+id/following_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_small"
tools:text="following"/>
</LinearLayout>
<FrameLayout
android:id="@+id/action_btn_wrap"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" android:layout_below="@id/bio"
android:layout_alignTop="@id/posts_btn" android:orientation="horizontal">
android:layout_marginTop="-8dp"
android:padding="8dp" <LinearLayout
android:layout_marginEnd="8dp" android:id="@+id/posts_btn"
android:clipToPadding="false"> android:layout_width="wrap_content"
<org.joinmastodon.android.ui.views.ProgressBarButton android:layout_height="48dp"
android:id="@+id/action_btn" android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:gravity="center_horizontal"
android:orientation="vertical">
<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" />
<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" />
</LinearLayout>
<LinearLayout
android:id="@+id/followers_btn"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="16dp"
android:layout_marginStart="12dp"
android:orientation="vertical"
android:gravity="center_horizontal">
<TextView
android:id="@+id/followers_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_large"
tools:text="123"/>
<TextView
android:id="@+id/followers_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_small"
tools:text="following"/>
</LinearLayout>
<LinearLayout
android:id="@+id/following_btn"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="16dp"
android:layout_marginStart="12dp"
android:orientation="vertical"
android:gravity="center_horizontal">
<TextView
android:id="@+id/following_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_large"
tools:text="123"/>
<TextView
android:id="@+id/following_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_small"
tools:text="following"/>
</LinearLayout>
<Space
android:layout_width="0px"
android:layout_height="1px"
android:layout_weight="1"/>
<FrameLayout
android:id="@+id/action_btn_wrap"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:text="Edit Profile"/> android:layout_marginTop="8dp"
<ProgressBar android:padding="8dp"
android:id="@+id/action_progress" android:layout_marginEnd="8dp"
android:layout_width="wrap_content" android:clipToPadding="false">
android:layout_height="wrap_content" <org.joinmastodon.android.ui.views.ProgressBarButton
android:layout_gravity="center" android:id="@+id/action_btn"
android:indeterminate="true" android:layout_width="wrap_content"
style="?android:progressBarStyleSmall" android:layout_height="wrap_content"
android:elevation="10dp" android:singleLine="true"
android:outlineProvider="none" tools:text="@string/follow_back"/>
android:indeterminateTint="?colorButtonText" <ProgressBar
android:visibility="gone"/> android:id="@+id/action_progress"
</FrameLayout> android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
style="?android:progressBarStyleSmall"
android:elevation="10dp"
android:outlineProvider="none"
android:indeterminateTint="?colorButtonText"
android:visibility="gone"/>
</FrameLayout>
</LinearLayout>
</RelativeLayout> </RelativeLayout>

View File

@@ -352,4 +352,5 @@
<item quantity="other">%,d reblogs</item> <item quantity="other">%,d reblogs</item>
</plurals> </plurals>
<string name="timestamp_via_app">%1$s via %2$s</string> <string name="timestamp_via_app">%1$s via %2$s</string>
<string name="time_now">now</string>
</resources> </resources>