Posts redesign wip

This commit is contained in:
Grishka 2023-03-14 19:17:37 +03:00
parent d6bcc9c156
commit 20799ef1a8
52 changed files with 926 additions and 365 deletions

View File

@ -69,6 +69,7 @@ dependencies {
implementation 'me.grishka.litex:dynamicanimation:1.1.0-alpha03'
implementation 'me.grishka.litex:viewpager:1.0.0'
implementation 'me.grishka.litex:viewpager2:1.0.0'
implementation 'me.grishka.litex:palette:1.0.0'
implementation 'me.grishka.appkit:appkit:1.2.7'
implementation 'com.google.code.gson:gson:2.8.9'
implementation 'org.jsoup:jsoup:1.14.3'

View File

@ -31,7 +31,6 @@ import org.joinmastodon.android.ui.text.HtmlParser;
import org.parceler.Parcels;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import androidx.annotation.Nullable;
@ -57,6 +56,7 @@ public class AudioPlayerService extends Service{
private static HashSet<Callback> callbacks=new HashSet<>();
private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener=this::onAudioFocusChanged;
private boolean resumeAfterAudioFocusGain;
private boolean isBuffering=true;
private BroadcastReceiver receiver=new BroadcastReceiver(){
@Override
@ -176,6 +176,7 @@ public class AudioPlayerService extends Service{
player.setOnErrorListener(this::onPlayerError);
player.setOnCompletionListener(this::onPlayerCompletion);
player.setOnSeekCompleteListener(this::onPlayerSeekCompleted);
player.setOnInfoListener(this::onPlayerInfo);
try{
player.setDataSource(this, Uri.parse(attachment.url));
player.prepareAsync();
@ -187,7 +188,9 @@ public class AudioPlayerService extends Service{
}
private void onPlayerPrepared(MediaPlayer mp){
Log.i(TAG, "onPlayerPrepared");
playerReady=true;
isBuffering=false;
player.start();
updateSessionState(false);
}
@ -205,6 +208,21 @@ public class AudioPlayerService extends Service{
stopSelf();
}
private boolean onPlayerInfo(MediaPlayer mp, int what, int extra){
switch(what){
case MediaPlayer.MEDIA_INFO_BUFFERING_START -> {
isBuffering=true;
updateSessionState(false);
}
case MediaPlayer.MEDIA_INFO_BUFFERING_END -> {
isBuffering=false;
updateSessionState(false);
}
default -> Log.i(TAG, "onPlayerInfo() called with: mp = ["+mp+"], what = ["+what+"], extra = ["+extra+"]");
}
return true;
}
private void onAudioFocusChanged(int change){
switch(change){
case AudioManager.AUDIOFOCUS_LOSS -> {
@ -232,12 +250,16 @@ public class AudioPlayerService extends Service{
private void updateSessionState(boolean removeNotification){
session.setPlaybackState(new PlaybackState.Builder()
.setState(player.isPlaying() ? PlaybackState.STATE_PLAYING : PlaybackState.STATE_PAUSED, player.getCurrentPosition(), 1f)
.setState(switch(getPlayState()){
case PLAYING -> PlaybackState.STATE_PLAYING;
case PAUSED -> PlaybackState.STATE_PAUSED;
case BUFFERING -> PlaybackState.STATE_BUFFERING;
}, player.getCurrentPosition(), 1f)
.setActions(PlaybackState.ACTION_STOP | PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_SEEK_TO)
.build());
updateNotification(!player.isPlaying(), removeNotification);
for(Callback cb:callbacks)
cb.onPlayStateChanged(attachment.id, player.isPlaying(), player.getCurrentPosition());
cb.onPlayStateChanged(attachment.id, getPlayState(), player.getCurrentPosition());
}
private void updateNotification(boolean dismissable, boolean removeNotification){
@ -310,6 +332,12 @@ public class AudioPlayerService extends Service{
return attachment.id;
}
public PlayState getPlayState(){
if(isBuffering)
return PlayState.BUFFERING;
return player.isPlaying() ? PlayState.PLAYING : PlayState.PAUSED;
}
public static void registerCallback(Callback cb){
callbacks.add(cb);
}
@ -333,7 +361,13 @@ public class AudioPlayerService extends Service{
}
public interface Callback{
void onPlayStateChanged(String attachmentID, boolean playing, int position);
void onPlayStateChanged(String attachmentID, PlayState state, int position);
void onPlaybackStopped(String attachmentID);
}
public enum PlayState{
PLAYING,
PAUSED,
BUFFERING
}
}

View File

@ -35,6 +35,7 @@ import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.SpoilerStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
@ -48,6 +49,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@ -405,25 +407,26 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
.exec(accountID);
}
public void onRevealSpoilerClick(TextStatusDisplayItem.Holder holder){
public void onRevealSpoilerClick(SpoilerStatusDisplayItem.Holder holder){
Status status=holder.getItem().status;
revealSpoiler(status, holder.getItemID());
toggleSpoiler(status, holder.getItemID());
}
public void onRevealSpoilerClick(MediaGridStatusDisplayItem.Holder holder){
Status status=holder.getItem().status;
revealSpoiler(status, holder.getItemID());
}
protected void toggleSpoiler(Status status, String itemID){
status.spoilerRevealed=!status.spoilerRevealed;
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
if(spoiler!=null)
spoiler.rebind();
SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(findItemOfType(itemID, SpoilerStatusDisplayItem.class));
protected void revealSpoiler(Status status, String itemID){
status.spoilerRevealed=true;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null)
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()-getMainAdapterOffset());
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if(header!=null)
header.rebind();
updateImagesSpoilerState(status, itemID);
int index=displayItems.indexOf(spoilerItem);
if(status.spoilerRevealed){
displayItems.addAll(index+1, spoilerItem.contentItems);
adapter.notifyItemRangeInserted(index+1, spoilerItem.contentItems.size());
}else{
displayItems.subList(index+1, index+1+spoilerItem.contentItems.size()).clear();
adapter.notifyItemRangeRemoved(index+1, spoilerItem.contentItems.size());
}
}
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder){

View File

@ -327,8 +327,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
spoilerEdit=view.findViewById(R.id.content_warning);
LayerDrawable spoilerBg=(LayerDrawable) spoilerEdit.getBackground().mutate();
spoilerBg.setDrawableByLayerId(R.id.left_drawable, new SpoilerStripesDrawable());
spoilerBg.setDrawableByLayerId(R.id.right_drawable, new SpoilerStripesDrawable());
spoilerBg.setDrawableByLayerId(R.id.left_drawable, new SpoilerStripesDrawable(false));
spoilerBg.setDrawableByLayerId(R.id.right_drawable, new SpoilerStripesDrawable(false));
spoilerEdit.setBackground(spoilerBg);
if((savedInstanceState!=null && savedInstanceState.getBoolean("hasSpoiler", false)) || hasSpoiler){
hasSpoiler=true;

View File

@ -336,6 +336,8 @@ public class OnboardingFollowSuggestionsFragment extends BaseRecyclerFragment<Pa
}
private void setActionProgressVisible(boolean visible){
if(visible)
actionProgress.setIndeterminateTintList(actionButton.getTextColors());
actionButton.setTextVisible(!visible);
actionProgress.setVisibility(visible ? View.VISIBLE : View.GONE);
actionButton.setClickable(!visible);

View File

@ -127,6 +127,7 @@ public class Attachment extends BaseModel{
public PointF focus;
public SizeMetadata original;
public SizeMetadata small;
public ColorsMetadata colors;
@Override
public String toString(){
@ -138,6 +139,7 @@ public class Attachment extends BaseModel{
", focus="+focus+
", original="+original+
", small="+small+
", colors="+colors+
'}';
}
}
@ -161,4 +163,20 @@ public class Attachment extends BaseModel{
'}';
}
}
@Parcel
public static class ColorsMetadata{
public String background;
public String foreground;
public String accent;
@Override
public String toString(){
return "ColorsMetadata{"+
"background='"+background+'\''+
", foreground='"+foreground+'\''+
", accent='"+accent+'\''+
'}';
}
}
}

View File

@ -21,6 +21,12 @@ public class OutlineProviders{
outline.setAlpha(view.getAlpha());
}
};
public static final ViewOutlineProvider OVAL=new ViewOutlineProvider(){
@Override
public void getOutline(View view, Outline outline){
outline.setOval(0, 0, view.getWidth(), view.getHeight());
}
};
public static ViewOutlineProvider roundedRect(int dp){
ViewOutlineProvider provider=roundedRects.get(dp);

View File

@ -1,10 +1,20 @@
package org.joinmastodon.android.ui.displayitems;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
@ -13,16 +23,27 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.drawables.AudioAttachmentBackgroundDrawable;
import org.joinmastodon.android.ui.drawables.SeekBarThumbDrawable;
import org.joinmastodon.android.ui.utils.UiUtils;
import androidx.palette.graphics.Palette;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V;
public class AudioStatusDisplayItem extends StatusDisplayItem{
public final Status status;
public final Attachment attachment;
private final ImageLoaderRequest imageRequest;
public AudioStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status, Attachment attachment){
super(parentID, parentFragment);
this.status=status;
this.attachment=attachment;
imageRequest=new UrlImageLoaderRequest(TextUtils.isEmpty(attachment.previewUrl) ? status.account.avatarStatic : attachment.previewUrl, V.dp(100), V.dp(100));
}
@Override
@ -30,25 +51,38 @@ public class AudioStatusDisplayItem extends StatusDisplayItem{
return Type.AUDIO;
}
public static class Holder extends StatusDisplayItem.Holder<AudioStatusDisplayItem> implements AudioPlayerService.Callback{
private final ImageButton playPauseBtn;
@Override
public int getImageCount(){
return 1;
}
@Override
public ImageLoaderRequest getImageRequest(int index){
return imageRequest;
}
public static class Holder extends StatusDisplayItem.Holder<AudioStatusDisplayItem> implements AudioPlayerService.Callback, ImageLoaderViewHolder{
private final ImageButton playPauseBtn, forwardBtn, rewindBtn;
private final TextView time;
private final SeekBar seekBar;
private final ImageView image;
private final FrameLayout content;
private final AudioAttachmentBackgroundDrawable bgDrawable;
private int lastKnownPosition;
private long lastKnownPositionTime;
private boolean playing;
private int lastRemainingSeconds=-1;
private boolean seekbarBeingDragged;
private int lastPosSeconds=-1;
private AudioPlayerService.PlayState state;
private Runnable positionUpdater=this::updatePosition;
private final Runnable positionUpdater=this::updatePosition;
public Holder(Context context, ViewGroup parent){
super(context, R.layout.display_item_audio, parent);
playPauseBtn=findViewById(R.id.play_pause_btn);
time=findViewById(R.id.time);
seekBar=findViewById(R.id.seekbar);
seekBar.setThumb(new SeekBarThumbDrawable(context));
image=findViewById(R.id.image);
content=findViewById(R.id.content);
forwardBtn=findViewById(R.id.forward_btn);
rewindBtn=findViewById(R.id.rewind_btn);
playPauseBtn.setOnClickListener(this::onPlayPauseClick);
itemView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener(){
@Override
@ -61,76 +95,71 @@ public class AudioStatusDisplayItem extends StatusDisplayItem{
AudioPlayerService.unregisterCallback(Holder.this);
}
});
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener(){
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser){
if(fromUser){
int seconds=(int)(seekBar.getProgress()/10000.0*item.attachment.getDuration());
time.setText(formatDuration(seconds));
}
}
forwardBtn.setOnClickListener(this::onSeekButtonClick);
rewindBtn.setOnClickListener(this::onSeekButtonClick);
@Override
public void onStartTrackingTouch(SeekBar seekBar){
seekbarBeingDragged=true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar){
AudioPlayerService service=AudioPlayerService.getInstance();
if(service!=null && service.getAttachmentID().equals(item.attachment.id)){
service.seekTo((int)(seekBar.getProgress()/10000.0*item.attachment.getDuration()*1000.0));
}
seekbarBeingDragged=false;
if(playing)
itemView.postOnAnimation(positionUpdater);
}
});
image.setOutlineProvider(OutlineProviders.OVAL);
image.setClipToOutline(true);
content.setBackground(bgDrawable=new AudioAttachmentBackgroundDrawable());
}
@Override
public void onBind(AudioStatusDisplayItem item){
int seconds=(int)item.attachment.getDuration();
String duration=formatDuration(seconds);
// 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);
AudioPlayerService service=AudioPlayerService.getInstance();
if(service!=null && service.getAttachmentID().equals(item.attachment.id)){
seekBar.setEnabled(true);
onPlayStateChanged(item.attachment.id, service.isPlaying(), service.getPosition());
forwardBtn.setVisibility(View.VISIBLE);
rewindBtn.setVisibility(View.VISIBLE);
onPlayStateChanged(item.attachment.id, service.getPlayState(), service.getPosition());
actuallyUpdatePosition();
}else{
seekBar.setEnabled(false);
state=null;
time.setText(duration);
forwardBtn.setVisibility(View.INVISIBLE);
rewindBtn.setVisibility(View.INVISIBLE);
setPlayButtonPlaying(false, false);
}
int mainColor;
if(item.attachment.meta!=null && item.attachment.meta.colors!=null){
try{
mainColor=Color.parseColor(item.attachment.meta.colors.background);
}catch(IllegalArgumentException x){
mainColor=0xff808080;
}
}else{
mainColor=0xff808080;
}
updateColors(mainColor);
}
private void onPlayPauseClick(View v){
AudioPlayerService service=AudioPlayerService.getInstance();
if(service!=null && service.getAttachmentID().equals(item.attachment.id)){
if(playing)
if(state!=AudioPlayerService.PlayState.PAUSED)
service.pause(true);
else
service.play();
}else{
AudioPlayerService.start(v.getContext(), item.status, item.attachment);
onPlayStateChanged(item.attachment.id, true, 0);
seekBar.setEnabled(true);
onPlayStateChanged(item.attachment.id, AudioPlayerService.PlayState.BUFFERING, 0);
forwardBtn.setVisibility(View.VISIBLE);
rewindBtn.setVisibility(View.VISIBLE);
}
}
@Override
public void onPlayStateChanged(String attachmentID, boolean playing, int position){
public void onPlayStateChanged(String attachmentID, AudioPlayerService.PlayState state, int position){
if(attachmentID.equals(item.attachment.id)){
this.lastKnownPosition=position;
lastKnownPositionTime=SystemClock.uptimeMillis();
this.playing=playing;
playPauseBtn.setImageResource(playing ? R.drawable.ic_fluent_pause_circle_24_filled : R.drawable.ic_fluent_play_circle_24_filled);
if(!playing){
lastRemainingSeconds=-1;
time.setText(formatDuration((int) item.attachment.getDuration()));
}else{
this.state=state;
setPlayButtonPlaying(state!=AudioPlayerService.PlayState.PAUSED, true);
if(state==AudioPlayerService.PlayState.PLAYING){
itemView.postOnAnimation(positionUpdater);
}else if(state==AudioPlayerService.PlayState.BUFFERING){
actuallyUpdatePosition();
}
}
}
@ -138,14 +167,15 @@ public class AudioStatusDisplayItem extends StatusDisplayItem{
@Override
public void onPlaybackStopped(String attachmentID){
if(attachmentID.equals(item.attachment.id)){
playing=false;
playPauseBtn.setImageResource(R.drawable.ic_fluent_play_circle_24_filled);
seekBar.setProgress(0);
seekBar.setEnabled(false);
state=null;
setPlayButtonPlaying(false, true);
forwardBtn.setVisibility(View.INVISIBLE);
rewindBtn.setVisibility(View.INVISIBLE);
time.setText(formatDuration((int)item.attachment.getDuration()));
}
}
@SuppressLint("DefaultLocale")
private String formatDuration(int seconds){
if(seconds>=3600)
return String.format("%d:%02d:%02d", seconds/3600, seconds%3600/60, seconds%60);
@ -154,16 +184,79 @@ public class AudioStatusDisplayItem extends StatusDisplayItem{
}
private void updatePosition(){
if(!playing || seekbarBeingDragged)
if(state!=AudioPlayerService.PlayState.PLAYING)
return;
double pos=lastKnownPosition/1000.0+(SystemClock.uptimeMillis()-lastKnownPositionTime)/1000.0;
seekBar.setProgress((int)Math.round(pos/item.attachment.getDuration()*10000.0));
actuallyUpdatePosition();
itemView.postOnAnimation(positionUpdater);
int remainingSeconds=(int)(item.attachment.getDuration()-pos);
if(remainingSeconds!=lastRemainingSeconds){
lastRemainingSeconds=remainingSeconds;
time.setText("-"+formatDuration(remainingSeconds));
}
@SuppressLint("SetTextI18n")
private void actuallyUpdatePosition(){
double pos=lastKnownPosition/1000.0;
if(state==AudioPlayerService.PlayState.PLAYING)
pos+=(SystemClock.uptimeMillis()-lastKnownPositionTime)/1000.0;
int posSeconds=(int)pos;
if(posSeconds!=lastPosSeconds){
lastPosSeconds=posSeconds;
time.setText(formatDuration(posSeconds)+"/"+formatDuration((int)item.attachment.getDuration()));
}
}
private void updateColors(int mainColor){
float[] hsv={0, 0, 0};
float[] hsv2={0, 0, 0};
Color.colorToHSV(mainColor, hsv);
boolean isGray=hsv[1]<0.2f;
boolean isDarkTheme=UiUtils.isDarkTheme();
hsv2[0]=hsv[0];
hsv2[1]=isGray ? hsv[1] : (isDarkTheme ? 0.6f : 0.4f);
hsv2[2]=isDarkTheme ? 0.3f : 0.75f;
int bgColor=Color.HSVToColor(hsv2);
hsv2[1]=isGray ? hsv[1] : (isDarkTheme ? 0.3f : 0.6f);
hsv2[2]=isDarkTheme ? 0.6f : 0.4f;
bgDrawable.setColors(bgColor, Color.HSVToColor(128, hsv2));
hsv2[1]=isGray ? hsv[1] : 0.1f;
hsv2[2]=1;
int controlsColor=Color.HSVToColor(hsv2);
time.setTextColor(controlsColor);
forwardBtn.setColorFilter(controlsColor);
rewindBtn.setColorFilter(controlsColor);
}
private void setPlayButtonPlaying(boolean playing, boolean animated){
playPauseBtn.setImageResource(playing ? R.drawable.ic_pause_48px : R.drawable.ic_play_arrow_48px);
playPauseBtn.setContentDescription(item.parentFragment.getString(playing ? R.string.pause : R.string.play));
if(playing)
bgDrawable.startAnimation();
else
bgDrawable.stopAnimation(animated);
}
private void onSeekButtonClick(View v){
int seekAmount=v.getId()==R.id.forward_btn ? 10_000 : -5_000;
AudioPlayerService service=AudioPlayerService.getInstance();
if(service!=null && service.getAttachmentID().equals(item.attachment.id)){
int newPos=Math.min(Math.max(0, service.getPosition()+seekAmount), (int)(item.attachment.getDuration()*1000));
service.seekTo(newPos);
}
}
@Override
public void setImage(int index, Drawable image){
this.image.setImageDrawable(image);
if((item.attachment.meta==null || item.attachment.meta.colors==null) && image instanceof BitmapDrawable bd){
Bitmap bitmap=bd.getBitmap();
if(Build.VERSION.SDK_INT>=26 && bitmap.getConfig()==Bitmap.Config.HARDWARE)
bitmap=bitmap.copy(Bitmap.Config.ARGB_8888, false);
int color=Palette.from(bitmap).maximumColorCount(1).generate().getDominantColor(0xff808080);
updateColors(color);
}
}
@Override
public void clearImage(int index){
setImage(index, null);
}
}
}

View File

@ -2,6 +2,8 @@ package org.joinmastodon.android.ui.displayitems;
import android.app.Activity;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
@ -45,6 +47,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{
private final TextView reply, boost, favorite;
private final ImageView share;
private final ColorStateList buttonColors;
private final View.AccessibilityDelegate buttonAccessibilityDelegate=new View.AccessibilityDelegate(){
@Override
@ -61,6 +64,27 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
boost=findViewById(R.id.boost);
favorite=findViewById(R.id.favorite);
share=findViewById(R.id.share);
float[] hsb={0, 0, 0};
Color.colorToHSV(UiUtils.getThemeColor(activity, R.attr.colorM3Primary), hsb);
hsb[1]+=0.1f;
hsb[2]+=0.16f;
buttonColors=new ColorStateList(new int[][]{
{android.R.attr.state_selected},
{android.R.attr.state_enabled},
{}
}, new int[]{
Color.HSVToColor(hsb),
UiUtils.getThemeColor(activity, R.attr.colorM3OnSurfaceVariant),
UiUtils.getThemeColor(activity, R.attr.colorM3OnSurfaceVariant) & 0x80FFFFFF
});
boost.setTextColor(buttonColors);
boost.setCompoundDrawableTintList(buttonColors);
favorite.setTextColor(buttonColors);
favorite.setCompoundDrawableTintList(buttonColors);
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
UiUtils.fixCompoundDrawableTintOnAndroid6(reply);
UiUtils.fixCompoundDrawableTintOnAndroid6(boost);
@ -94,7 +118,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
private void bindButton(TextView btn, long count){
if(count>0 && !item.hideCounts){
btn.setText(UiUtils.abbreviateNumber(count));
btn.setCompoundDrawablePadding(V.dp(8));
btn.setCompoundDrawablePadding(V.dp(6));
}else{
btn.setText("");
btn.setCompoundDrawablePadding(0);

View File

@ -1,5 +1,6 @@
package org.joinmastodon.android.ui.displayitems;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Outline;
@ -32,6 +33,7 @@ import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import org.joinmastodon.android.ui.utils.UiUtils;
@ -105,33 +107,23 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
}
public static class Holder extends StatusDisplayItem.Holder<HeaderStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView name, username, timestamp, extraText;
private final ImageView avatar, more, visibility;
private final TextView name, timeAndUsername, extraText;
private final ImageView avatar, more;
private final PopupMenu optionsMenu;
private Relationship relationship;
private APIRequest<?> currentRelationshipRequest;
private static final ViewOutlineProvider roundCornersOutline=new ViewOutlineProvider(){
@Override
public void getOutline(View view, Outline outline){
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), V.dp(12));
}
};
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_header, parent);
name=findViewById(R.id.name);
username=findViewById(R.id.username);
timestamp=findViewById(R.id.timestamp);
timeAndUsername=findViewById(R.id.time_and_username);
avatar=findViewById(R.id.avatar);
more=findViewById(R.id.more);
visibility=findViewById(R.id.visibility);
extraText=findViewById(R.id.extra_text);
avatar.setOnClickListener(this::onAvaClick);
avatar.setOutlineProvider(roundCornersOutline);
avatar.setOutlineProvider(OutlineProviders.roundedRect(10));
avatar.setClipToOutline(true);
more.setOnClickListener(this::onMoreClick);
visibility.setOnClickListener(v->item.parentFragment.onVisibilityIconClick(this));
optionsMenu=new PopupMenu(activity, more);
optionsMenu.inflate(R.menu.post);
@ -200,22 +192,17 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
});
}
@SuppressLint("SetTextI18n")
@Override
public void onBind(HeaderStatusDisplayItem item){
name.setText(item.parsedName);
username.setText('@'+item.user.acct);
String time;
if(item.status==null || item.status.editedAt==null)
timestamp.setText(UiUtils.formatRelativeTimestamp(itemView.getContext(), item.createdAt));
time=UiUtils.formatRelativeTimestamp(itemView.getContext(), item.createdAt);
else
timestamp.setText(item.parentFragment.getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(itemView.getContext(), item.status.editedAt)));
visibility.setVisibility(item.hasVisibilityToggle && !item.inset ? View.VISIBLE : View.GONE);
if(item.hasVisibilityToggle){
visibility.setImageResource(item.status.spoilerRevealed ? R.drawable.ic_visibility_off : R.drawable.ic_visibility);
visibility.setContentDescription(item.parentFragment.getString(item.status.spoilerRevealed ? R.string.hide_content : R.string.reveal_content));
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
visibility.setTooltipText(visibility.getContentDescription());
}
}
time=item.parentFragment.getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(itemView.getContext(), item.status.editedAt));
timeAndUsername.setText(time+" · @"+item.user.acct);
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0);
if(TextUtils.isEmpty(item.extraText)){
extraText.setVisibility(View.GONE);

View File

@ -31,6 +31,7 @@ import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
public class MediaGridStatusDisplayItem extends StatusDisplayItem{
private static final String TAG="MediaGridDisplayItem";
@ -97,6 +98,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
wrapper=(FrameLayout)itemView;
layout=new MediaGridLayout(activity);
wrapper.addView(layout);
wrapper.setPadding(0, 0, 0, V.dp(8));
activity.getLayoutInflater().inflate(R.layout.overlay_image_alt_text, wrapper);
altTextWrapper=findViewById(R.id.alt_text_wrapper);
@ -105,6 +107,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
altTextClose=findViewById(R.id.alt_text_close);
altText=findViewById(R.id.alt_text);
altTextClose.setOnClickListener(this::onAltTextCloseClick);
wrapper.setClipToPadding(false);
}
@Override
@ -158,11 +161,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
private void onViewClick(View v){
int index=(Integer)v.getTag();
if(!item.status.spoilerRevealed){
item.parentFragment.onRevealSpoilerClick(this);
}else if(item.parentFragment instanceof PhotoViewerHost){
((PhotoViewerHost) item.parentFragment).openPhotoViewer(item.parentID, item.status, index, this);
}
((PhotoViewerHost) item.parentFragment).openPhotoViewer(item.parentID, item.status, index, this);
}
private void onAltTextClick(View v){

View File

@ -37,9 +37,11 @@ public class PollFooterStatusDisplayItem extends StatusDisplayItem{
@Override
public void onBind(PollFooterStatusDisplayItem item){
String text=item.parentFragment.getResources().getQuantityString(R.plurals.x_voters, item.poll.votersCount, item.poll.votersCount);
String text=item.parentFragment.getResources().getQuantityString(R.plurals.x_votes, item.poll.votesCount, item.poll.votesCount);
if(item.poll.expiresAt!=null && !item.poll.isExpired()){
text+=" · "+UiUtils.formatTimeLeft(itemView.getContext(), item.poll.expiresAt);
if(item.poll.multiple)
text+=" · "+item.parentFragment.getString(R.string.poll_multiple_choice);
}else if(item.poll.isExpired()){
text+=" · "+item.parentFragment.getString(R.string.poll_closed);
}

View File

@ -10,6 +10,7 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
@ -25,11 +26,13 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
private boolean showResults;
private float votesFraction; // 0..1
private boolean isMostVoted;
private final int optionIndex;
public final Poll poll;
public PollOptionStatusDisplayItem(String parentID, Poll poll, Poll.Option option, BaseStatusListFragment parentFragment){
public PollOptionStatusDisplayItem(String parentID, Poll poll, int optionIndex, BaseStatusListFragment parentFragment){
super(parentID, parentFragment);
this.option=option;
this.optionIndex=optionIndex;
option=poll.options.get(optionIndex);
this.poll=poll;
text=HtmlParser.parseCustomEmoji(option.title, poll.emojis);
emojiHelper.setText(text);
@ -61,23 +64,24 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder<PollOptionStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView text, percent;
private final View icon, button;
private final View check, button;
private final Drawable progressBg;
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_poll_option, parent);
text=findViewById(R.id.text);
percent=findViewById(R.id.percent);
icon=findViewById(R.id.icon);
check=findViewById(R.id.checkbox);
button=findViewById(R.id.button);
progressBg=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted, activity.getTheme()).mutate();
itemView.setOnClickListener(this::onButtonClick);
button.setOutlineProvider(OutlineProviders.roundedRect(20));
button.setClipToOutline(true);
}
@Override
public void onBind(PollOptionStatusDisplayItem item){
text.setText(item.text);
icon.setVisibility(item.showResults ? View.GONE : View.VISIBLE);
percent.setVisibility(item.showResults ? View.VISIBLE : View.GONE);
itemView.setClickable(!item.showResults);
if(item.showResults){

View File

@ -0,0 +1,88 @@
package org.joinmastodon.android.ui.displayitems;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.drawables.SpoilerStripesDrawable;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import java.util.ArrayList;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
public class SpoilerStatusDisplayItem extends StatusDisplayItem{
public final Status status;
public final ArrayList<StatusDisplayItem> contentItems=new ArrayList<>();
private final CharSequence parsedTitle;
private final CustomEmojiHelper emojiHelper;
public SpoilerStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status){
super(parentID, parentFragment);
this.status=status;
parsedTitle=HtmlParser.parseCustomEmoji(status.spoilerText, status.emojis);
emojiHelper=new CustomEmojiHelper();
emojiHelper.setText(parsedTitle);
}
@Override
public int getImageCount(){
return emojiHelper.getImageCount();
}
@Override
public ImageLoaderRequest getImageRequest(int index){
return emojiHelper.getImageRequest(index);
}
@Override
public Type getType(){
return Type.SPOILER;
}
public static class Holder extends StatusDisplayItem.Holder<SpoilerStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView title, action;
private final View button;
public Holder(Context context, ViewGroup parent){
super(context, R.layout.display_item_spoiler, parent);
title=findViewById(R.id.spoiler_title);
action=findViewById(R.id.spoiler_action);
button=findViewById(R.id.spoiler_button);
button.setOutlineProvider(OutlineProviders.roundedRect(8));
button.setClipToOutline(true);
LayerDrawable spoilerBg=(LayerDrawable) button.getBackground().mutate();
spoilerBg.setDrawableByLayerId(R.id.left_drawable, new SpoilerStripesDrawable(true));
spoilerBg.setDrawableByLayerId(R.id.right_drawable, new SpoilerStripesDrawable(false));
button.setBackground(spoilerBg);
button.setOnClickListener(v->item.parentFragment.onRevealSpoilerClick(this));
}
@Override
public void onBind(SpoilerStatusDisplayItem item){
title.setText(item.parsedTitle);
action.setText(item.status.spoilerRevealed ? R.string.spoiler_hide : R.string.spoiler_show);
}
@Override
public void setImage(int index, Drawable image){
item.emojiHelper.setImageDrawable(index, image);
title.invalidate();
}
@Override
public void clearImage(int index){
setImage(index, null);
}
}
}

View File

@ -64,6 +64,7 @@ public abstract class StatusDisplayItem{
case GAP -> new GapStatusDisplayItem.Holder(activity, parent);
case EXTENDED_FOOTER -> new ExtendedFooterStatusDisplayItem.Holder(activity, parent);
case MEDIA_GRID -> new MediaGridStatusDisplayItem.Holder(activity, parent);
case SPOILER -> new SpoilerStatusDisplayItem.Holder(activity, parent);
};
}
@ -72,32 +73,43 @@ public abstract class StatusDisplayItem{
ArrayList<StatusDisplayItem> items=new ArrayList<>();
Status statusForContent=status.getContentStatus();
if(status.reblog!=null){
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.user_boosted, status.account.displayName), status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20_filled));
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.user_boosted, status.account.displayName), status.account.emojis, R.drawable.ic_repeat_20px));
}else if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId)){
Account account=Objects.requireNonNull(knownAccounts.get(status.inReplyToAccountId));
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.in_reply_to, account.displayName), account.emojis, R.drawable.ic_fluent_arrow_reply_20_filled));
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.in_reply_to, account.displayName), account.emojis, R.drawable.ic_reply_20px));
}
HeaderStatusDisplayItem header;
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null));
ArrayList<StatusDisplayItem> contentItems;
if(!TextUtils.isEmpty(statusForContent.spoilerText)){
SpoilerStatusDisplayItem spoilerItem=new SpoilerStatusDisplayItem(parentID, fragment, statusForContent);
items.add(spoilerItem);
contentItems=spoilerItem.contentItems;
}else{
contentItems=items;
}
if(!TextUtils.isEmpty(statusForContent.content))
items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent));
contentItems.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent));
else
header.needBottomPadding=true;
List<Attachment> imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList());
if(!imageAttachments.isEmpty()){
PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments);
items.add(new MediaGridStatusDisplayItem(parentID, fragment, layout, imageAttachments, statusForContent));
contentItems.add(new MediaGridStatusDisplayItem(parentID, fragment, layout, imageAttachments, statusForContent));
}
for(Attachment att:statusForContent.mediaAttachments){
if(att.type==Attachment.Type.AUDIO){
items.add(new AudioStatusDisplayItem(parentID, fragment, statusForContent, att));
contentItems.add(new AudioStatusDisplayItem(parentID, fragment, statusForContent, att));
}
}
if(statusForContent.poll!=null){
buildPollItems(parentID, fragment, statusForContent.poll, items);
buildPollItems(parentID, fragment, statusForContent.poll, contentItems);
}
if(statusForContent.card!=null && statusForContent.mediaAttachments.isEmpty() && TextUtils.isEmpty(statusForContent.spoilerText)){
items.add(new LinkCardStatusDisplayItem(parentID, fragment, statusForContent));
contentItems.add(new LinkCardStatusDisplayItem(parentID, fragment, statusForContent));
}
if(addFooter){
items.add(new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID));
@ -109,12 +121,20 @@ public abstract class StatusDisplayItem{
item.inset=inset;
item.index=i++;
}
if(items!=contentItems){
for(StatusDisplayItem item:contentItems){
item.inset=inset;
item.index=i++;
}
}
return items;
}
public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, List<StatusDisplayItem> items){
int i=0;
for(Poll.Option opt:poll.options){
items.add(new PollOptionStatusDisplayItem(parentID, poll, opt, fragment));
items.add(new PollOptionStatusDisplayItem(parentID, poll, i, fragment));
i++;
}
items.add(new PollFooterStatusDisplayItem(parentID, fragment, poll));
}
@ -133,7 +153,8 @@ public abstract class StatusDisplayItem{
HASHTAG,
GAP,
EXTENDED_FOOTER,
MEDIA_GRID
MEDIA_GRID,
SPOILER
}
public static abstract class Holder<T extends StatusDisplayItem> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{

View File

@ -3,15 +3,11 @@ package org.joinmastodon.android.ui.displayitems;
import android.app.Activity;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
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.views.LinkedTextView;
@ -21,8 +17,7 @@ import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
public class TextStatusDisplayItem extends StatusDisplayItem{
private CharSequence text;
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(), spoilerEmojiHelper;
private CharSequence parsedSpoilerText;
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
public boolean textSelectable;
public final Status status;
@ -31,11 +26,6 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
this.text=text;
this.status=status;
emojiHelper.setText(text);
if(!TextUtils.isEmpty(status.spoilerText)){
parsedSpoilerText=HtmlParser.parseCustomEmoji(status.spoilerText, status.emojis);
spoilerEmojiHelper=new CustomEmojiHelper();
spoilerEmojiHelper.setText(parsedSpoilerText);
}
}
@Override
@ -45,29 +35,20 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override
public int getImageCount(){
if(spoilerEmojiHelper!=null && !status.spoilerRevealed)
return spoilerEmojiHelper.getImageCount();
return emojiHelper.getImageCount();
}
@Override
public ImageLoaderRequest getImageRequest(int index){
if(spoilerEmojiHelper!=null && !status.spoilerRevealed)
return spoilerEmojiHelper.getImageRequest(index);
return emojiHelper.getImageRequest(index);
}
public static class Holder extends StatusDisplayItem.Holder<TextStatusDisplayItem> implements ImageLoaderViewHolder{
private final LinkedTextView text;
private final TextView spoilerTitle;
private final View spoilerOverlay;
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_text, parent);
text=findViewById(R.id.text);
spoilerTitle=findViewById(R.id.spoiler_title);
spoilerOverlay=findViewById(R.id.spoiler_overlay);
itemView.setOnClickListener(v->item.parentFragment.onRevealSpoilerClick(this));
}
@Override
@ -75,29 +56,13 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
text.setText(item.text);
text.setTextIsSelectable(item.textSelectable);
text.setInvalidateOnEveryFrame(false);
if(!TextUtils.isEmpty(item.status.spoilerText)){
spoilerTitle.setText(item.parsedSpoilerText);
if(item.status.spoilerRevealed){
spoilerOverlay.setVisibility(View.GONE);
text.setVisibility(View.VISIBLE);
itemView.setClickable(false);
}else{
spoilerOverlay.setVisibility(View.VISIBLE);
text.setVisibility(View.INVISIBLE);
itemView.setClickable(true);
}
}else{
spoilerOverlay.setVisibility(View.GONE);
text.setVisibility(View.VISIBLE);
itemView.setClickable(false);
}
itemView.setClickable(false);
}
@Override
public void setImage(int index, Drawable image){
getEmojiHelper().setImageDrawable(index, image);
text.invalidate();
spoilerTitle.invalidate();
if(image instanceof Animatable){
((Animatable) image).start();
if(image instanceof MovieDrawable)
@ -112,7 +77,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
}
private CustomEmojiHelper getEmojiHelper(){
return item.spoilerEmojiHelper!=null && !item.status.spoilerRevealed ? item.spoilerEmojiHelper : item.emojiHelper;
return item.emojiHelper;
}
}
}

View File

@ -0,0 +1,103 @@
package org.joinmastodon.android.ui.drawables;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
public class AudioAttachmentBackgroundDrawable extends Drawable{
private int bgColor, wavesColor;
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
private long[] animationStartTimes={0, 0};
private boolean animationRunning;
private Runnable[] restartRunnables={()->restartAnimation(0), ()->restartAnimation(1)};
@Override
public void draw(@NonNull Canvas canvas){
Rect bounds=getBounds();
paint.setColor(bgColor);
canvas.drawRect(bounds, paint);
float initialRadius=V.dp(48);
float finalRadius=bounds.width()/2f;
long time=SystemClock.uptimeMillis();
boolean animationsStillRunning=false;
for(int i=0;i<animationStartTimes.length;i++){
long t=time-animationStartTimes[i];
if(t<0)
continue;
float fraction=t/3000f;
if(fraction>1)
continue;
fraction=CubicBezierInterpolator.EASE_OUT.getInterpolation(fraction);
paint.setColor(wavesColor);
paint.setAlpha(Math.round(paint.getAlpha()*(1f-fraction)));
canvas.drawCircle(bounds.centerX(), bounds.centerY(), initialRadius+(finalRadius-initialRadius)*fraction, paint);
animationsStillRunning=true;
}
if(animationsStillRunning){
invalidateSelf();
}
}
@Override
public void setAlpha(int alpha){
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter){
}
@Override
public int getOpacity(){
return PixelFormat.OPAQUE;
}
public void setColors(int bg, int waves){
bgColor=bg;
wavesColor=waves;
}
public void startAnimation(){
if(animationRunning)
return;
long time=SystemClock.uptimeMillis();
animationStartTimes[0]=time;
scheduleSelf(restartRunnables[0], time+3000);
scheduleSelf(restartRunnables[1], time+1500);
animationRunning=true;
invalidateSelf();
}
public void stopAnimation(boolean gracefully){
if(!animationRunning)
return;
animationRunning=false;
for(Runnable r:restartRunnables)
unscheduleSelf(r);
if(!gracefully){
animationStartTimes[0]=animationStartTimes[1]=0;
}
}
private void restartAnimation(int index){
long time=SystemClock.uptimeMillis();
animationStartTimes[index]=time;
if(animationRunning)
scheduleSelf(restartRunnables[index], time+3000);
}
}

View File

@ -14,11 +14,16 @@ import androidx.annotation.Nullable;
public class SpoilerStripesDrawable extends Drawable{
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
private boolean flipped;
public SpoilerStripesDrawable(){
private static final float X1=-0.860365f;
private static final float X2=10.6078f;
public SpoilerStripesDrawable(boolean flipped){
paint.setColor(0xff000000);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
this.flipped=flipped;
}
@Override
@ -34,7 +39,7 @@ public class SpoilerStripesDrawable extends Drawable{
float y1=6.80133f;
float y2=-1.22874f;
while(y2<height){
canvas.drawLine(-0.860365f, y1, 10.6078f, y2, paint);
canvas.drawLine(flipped ? X2 : X1, y1, flipped ? X1 : X2, y2, paint);
y1+=8.03007f;
y2+=8.03007f;
}

View File

@ -28,6 +28,7 @@ public class LinkSpan extends CharacterStyle {
@Override
public void updateDrawState(TextPaint tp) {
tp.setColor(color=tp.linkColor);
tp.setUnderlineText(true);
}
public void onClick(Context context){

View File

@ -24,6 +24,7 @@ public class FrameLayoutThatOnlyMeasuresFirstChild extends FrameLayout{
return;
View child0=getChildAt(0);
measureChild(child0, widthMeasureSpec, heightMeasureSpec);
super.onMeasure(child0.getMeasuredWidth() | MeasureSpec.EXACTLY, child0.getMeasuredHeight() | MeasureSpec.EXACTLY);
int vpad=getPaddingTop()+getPaddingBottom();
super.onMeasure(child0.getMeasuredWidth() | MeasureSpec.EXACTLY, (child0.getMeasuredHeight()+vpad) | MeasureSpec.EXACTLY);
}
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/highlight_over_dark">
<item>
<shape android:shape="oval">
<solid android:color="#80000000"/>
</shape>
</item>
<item android:id="@android:id/mask">
<shape android:shape="oval">
<solid android:color="#ff000000"/>
</shape>
</item>
</ripple>

View File

@ -2,8 +2,14 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
<item>
<shape>
<solid android:color="?android:colorBackground"/>
<corners android:radius="10dp"/>
<stroke android:width="1dp" android:color="?colorM3Outline"/>
<corners android:radius="20dp"/>
</shape>
</item>
<item android:id="@android:id/mask">
<shape>
<solid android:color="#000"/>
<corners android:radius="20dp"/>
</shape>
</item>
</ripple>

View File

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="?android:colorBackground"/>
<corners android:radius="10dp"/>
</shape>
<scale android:scaleGravity="start|fill_vertical" android:scaleWidth="100%">
<shape>
<solid android:color="?colorM3SecondaryContainer"/>
<corners android:radius="20dp"/>
</shape>
</scale>
</item>
<item>
<clip android:clipOrientation="horizontal" android:gravity="start">
<shape>
<solid android:color="@color/poll_option_progress"/>
<corners android:radius="10dp"/>
</shape>
</clip>
<shape>
<stroke android:width="1dp" android:color="?colorM3Outline"/>
<corners android:radius="20dp"/>
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
<item android:id="@android:id/mask">
<shape android:shape="oval">
<solid android:color="#000"/>
</shape>
</item>
</ripple>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<color android:color="?colorM3SecondaryContainer"/>
</item>
<item android:gravity="left" android:width="5dp">
<shape>
<gradient android:type="linear" android:angle="270" android:startColor="#FEC84B" android:endColor="#F79009"/>
</shape>
</item>
<item android:id="@+id/left_drawable" android:width="5dp" android:gravity="left">
<color android:color="#0f0"/>
</item>
<item android:gravity="right" android:width="5dp">
<shape>
<gradient android:type="linear" android:angle="270" android:startColor="#FEC84B" android:endColor="#F79009"/>
</shape>
</item>
<item android:id="@+id/right_drawable" android:width="5dp" android:gravity="right">
<color android:color="#0f0"/>
</item>
<item>
<ripple android:color="?android:colorControlHighlight">
<item android:id="@android:id/mask">
<shape>
<solid android:color="#000"/>
</shape>
</item>
</ripple>
</item>
</layer-list>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@android:color/white"
android:pathData="M8.229,14.062 L4.708,10.521 5.75,9.479 8.229,11.938 14.25,5.938 15.292,7Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="@android:color/white"
android:pathData="M18,32.5V21.9H15.3V19.45H20.5V32.5ZM25.35,32.5Q24.4,32.5 23.775,31.875Q23.15,31.25 23.15,30.3V21.65Q23.15,20.7 23.775,20.075Q24.4,19.45 25.35,19.45H29.5Q30.45,19.45 31.075,20.075Q31.7,20.7 31.7,21.65V30.3Q31.7,31.25 31.075,31.875Q30.45,32.5 29.5,32.5ZM25.65,30H29.2Q29.2,30 29.2,30Q29.2,30 29.2,30V21.9Q29.2,21.9 29.2,21.9Q29.2,21.9 29.2,21.9H25.65Q25.65,21.9 25.65,21.9Q25.65,21.9 25.65,21.9V30Q25.65,30 25.65,30Q25.65,30 25.65,30ZM24,44Q20.25,44 16.975,42.6Q13.7,41.2 11.25,38.75Q8.8,36.3 7.4,33.025Q6,29.75 6,26Q6,22.25 7.4,18.975Q8.8,15.7 11.25,13.25Q13.7,10.8 16.975,9.4Q20.25,8 24,8H25.05L21.15,4.1L23.2,2.05L30.55,9.4L23.2,16.75L21.15,14.7L24.85,11H24Q17.75,11 13.375,15.375Q9,19.75 9,26Q9,32.25 13.375,36.625Q17.75,41 24,41Q30.25,41 34.625,36.625Q39,32.25 39,26H42Q42,29.75 40.6,33.025Q39.2,36.3 36.75,38.75Q34.3,41.2 31.025,42.6Q27.75,44 24,44Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@android:color/white"
android:pathData="M10,16Q9.375,16 8.938,15.562Q8.5,15.125 8.5,14.5Q8.5,13.875 8.938,13.438Q9.375,13 10,13Q10.625,13 11.062,13.438Q11.5,13.875 11.5,14.5Q11.5,15.125 11.062,15.562Q10.625,16 10,16ZM10,11.5Q9.375,11.5 8.938,11.062Q8.5,10.625 8.5,10Q8.5,9.375 8.938,8.938Q9.375,8.5 10,8.5Q10.625,8.5 11.062,8.938Q11.5,9.375 11.5,10Q11.5,10.625 11.062,11.062Q10.625,11.5 10,11.5ZM10,7Q9.375,7 8.938,6.562Q8.5,6.125 8.5,5.5Q8.5,4.875 8.938,4.438Q9.375,4 10,4Q10.625,4 11.062,4.438Q11.5,4.875 11.5,5.5Q11.5,6.125 11.062,6.562Q10.625,7 10,7Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="@android:color/white"
android:pathData="M28.25,38V10H36V38ZM12,38V10H19.75V38Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="@android:color/white"
android:pathData="M16,37.85V9.85L38,23.85ZM19,23.85ZM19,32.4 L32.45,23.85 19,15.3Z"/>
</vector>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:drawable="@drawable/ic_check_20px"/>
</selector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@android:color/white"
android:pathData="M5.875,18.333 L2.5,14.958 5.792,11.667 7.042,12.917 5.875,14.083H14.083V10.833H15.833V15.833H5.875L7.125,17.083ZM4.167,9.167V4.167H14.125L12.875,2.917L14.125,1.667L17.5,5.042L14.188,8.354L12.938,7.104L14.125,5.917H5.917V9.167Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_repeat_wght700grad200fill1_20px" android:state_selected="true"/>
<item android:drawable="@drawable/ic_repeat_20px"/>
</selector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@android:color/white"
android:pathData="M5.812,18.792 L2.042,15.021 5.729,11.354 7.354,12.979 6.417,13.875H13.896V10.604H16.188V16.167H6.417L7.438,17.167ZM3.833,9.375V3.812H13.583L12.562,2.812L14.188,1.188L17.958,4.958L14.25,8.667L12.625,7.042L13.583,6.104H6.125V9.375Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="@android:color/white"
android:pathData="M24,44Q20.25,44 16.975,42.6Q13.7,41.2 11.25,38.75Q8.8,36.3 7.4,33.025Q6,29.75 6,26H9Q9,32.25 13.375,36.625Q17.75,41 24,41Q30.25,41 34.625,36.625Q39,32.25 39,26Q39,19.75 34.75,15.375Q30.5,11 24.25,11H23.15L26.8,14.65L24.7,16.75L17.35,9.4L24.7,2.05L26.75,4.1L22.85,8H24Q27.75,8 31.025,9.4Q34.3,10.8 36.75,13.25Q39.2,15.7 40.6,18.975Q42,22.25 42,26Q42,29.75 40.6,33.025Q39.2,36.3 36.75,38.75Q34.3,41.2 31.025,42.6Q27.75,44 24,44ZM19,32.5V30H25.35V27.2H19V19.45H27.8V21.9H21.5V24.75H26.1Q26.8,24.75 27.3,25.25Q27.8,25.75 27.8,26.45V30.8Q27.8,31.5 27.3,32Q26.8,32.5 26.1,32.5Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@android:color/white"
android:pathData="M15.5,15.625V12.625Q15.5,11.583 14.771,10.854Q14.042,10.125 13,10.125H5.875L8.438,12.688L7.375,13.75L3,9.375L7.375,5L8.438,6.062L5.875,8.625H13Q14.667,8.625 15.833,9.792Q17,10.958 17,12.625V15.625Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@android:color/white"
android:pathData="M14.958,18.333Q13.896,18.333 13.167,17.594Q12.438,16.854 12.438,15.792Q12.438,15.646 12.458,15.5Q12.479,15.354 12.521,15.229L6.75,11.896Q6.396,12.208 5.958,12.385Q5.521,12.562 5.042,12.562Q3.979,12.562 3.24,11.823Q2.5,11.083 2.5,10.021Q2.5,8.958 3.24,8.219Q3.979,7.479 5.042,7.479Q5.521,7.479 5.958,7.646Q6.396,7.812 6.75,8.125L12.521,4.792Q12.479,4.667 12.448,4.521Q12.417,4.375 12.417,4.229Q12.417,3.167 13.156,2.427Q13.896,1.688 14.958,1.688Q16.021,1.688 16.76,2.427Q17.5,3.167 17.5,4.229Q17.5,5.292 16.76,6.031Q16.021,6.771 14.958,6.771Q14.479,6.771 14.052,6.594Q13.625,6.417 13.271,6.104L7.5,9.438Q7.542,9.562 7.562,9.719Q7.583,9.875 7.583,10.021Q7.583,10.146 7.562,10.302Q7.542,10.458 7.5,10.583L13.271,13.917Q13.625,13.604 14.052,13.427Q14.479,13.25 14.958,13.25Q16.021,13.25 16.76,13.99Q17.5,14.729 17.5,15.792Q17.5,16.854 16.76,17.594Q16.021,18.333 14.958,18.333ZM14.958,5.021Q15.292,5.021 15.521,4.792Q15.75,4.562 15.75,4.229Q15.75,3.896 15.521,3.667Q15.292,3.438 14.958,3.438Q14.625,3.438 14.396,3.667Q14.167,3.896 14.167,4.229Q14.167,4.562 14.396,4.792Q14.625,5.021 14.958,5.021ZM5.042,10.812Q5.375,10.812 5.604,10.583Q5.833,10.354 5.833,10.021Q5.833,9.688 5.604,9.458Q5.375,9.229 5.042,9.229Q4.708,9.229 4.479,9.458Q4.25,9.688 4.25,10.021Q4.25,10.354 4.479,10.583Q4.708,10.812 5.042,10.812ZM14.958,16.583Q15.292,16.583 15.521,16.354Q15.75,16.125 15.75,15.792Q15.75,15.458 15.521,15.229Q15.292,15 14.958,15Q14.625,15 14.396,15.229Q14.167,15.458 14.167,15.792Q14.167,16.125 14.396,16.354Q14.625,16.583 14.958,16.583ZM14.958,4.229Q14.958,4.229 14.958,4.229Q14.958,4.229 14.958,4.229Q14.958,4.229 14.958,4.229Q14.958,4.229 14.958,4.229Q14.958,4.229 14.958,4.229Q14.958,4.229 14.958,4.229Q14.958,4.229 14.958,4.229Q14.958,4.229 14.958,4.229ZM5.042,10.021Q5.042,10.021 5.042,10.021Q5.042,10.021 5.042,10.021Q5.042,10.021 5.042,10.021Q5.042,10.021 5.042,10.021Q5.042,10.021 5.042,10.021Q5.042,10.021 5.042,10.021Q5.042,10.021 5.042,10.021Q5.042,10.021 5.042,10.021ZM14.958,15.792Q14.958,15.792 14.958,15.792Q14.958,15.792 14.958,15.792Q14.958,15.792 14.958,15.792Q14.958,15.792 14.958,15.792Q14.958,15.792 14.958,15.792Q14.958,15.792 14.958,15.792Q14.958,15.792 14.958,15.792Q14.958,15.792 14.958,15.792Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@android:color/white"
android:pathData="M7.333,14.896 L10,13.312 12.688,14.896 11.979,11.896 14.292,9.917 11.229,9.646 10,6.792 8.771,9.646 5.708,9.917 8.042,11.896ZM5.062,18 L6.375,12.458 2,8.729 7.75,8.229 10,3 12.25,8.25 18,8.729 13.625,12.458 14.938,18 10,15.062ZM10,11.062Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_star_wght700grad200fill1_20px" android:state_selected="true"/>
<item android:drawable="@drawable/ic_star_20px"/>
</selector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@android:color/white"
android:pathData="M3.958,19.708 L6.25,12.292 0.125,7.792H7.583L10,0.062L12.417,7.792H19.875L13.75,12.292L16.062,19.708L10.021,15.104Z"/>
</vector>

View File

@ -3,52 +3,60 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-8dp"
android:layout_marginBottom="-8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:clipToPadding="false">
<LinearLayout
<FrameLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:background="?buttonBackground"
android:outlineProvider="background"
android:elevation="2dp">
<ImageButton
android:id="@+id/play_pause_btn"
android:layout_width="24dp"
android:layout_height="24dp"
android:background="?android:selectableItemBackgroundBorderless"
android:tint="?colorButtonText"
android:src="@drawable/ic_fluent_play_circle_24_filled"/>
<SeekBar
android:id="@+id/seekbar"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:progressDrawable="@drawable/seekbar_progress"
android:max="10000"
android:splitTrack="false"/>
android:layout_height="188dp">
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_label_medium"
android:layout_height="20dp"
android:layout_margin="8dp"
android:layout_gravity="bottom|start"
android:textAppearance="@style/m3_label_large"
android:textColor="?colorButtonText"
android:gravity="end"
android:gravity="center_vertical"
android:singleLine="true"
android:fontFeatureSettings="'tnum'"
tools:text="1:23"/>
<ImageView
android:id="@+id/image"
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_gravity="center"
android:importantForAccessibility="no"
tools:src="#0f0"/>
</LinearLayout>
<ImageButton
android:id="@+id/play_pause_btn"
android:layout_width="96dp"
android:layout_height="96dp"
android:background="@drawable/bg_audio_play_button"
android:layout_gravity="center"
android:src="@drawable/ic_play_arrow_48px"/>
<ImageButton
android:id="@+id/forward_btn"
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_gravity="center_vertical|end"
android:layout_margin="16dp"
android:src="@drawable/ic_forward_10_48px"
android:background="@drawable/bg_round_ripple"/>
<ImageButton
android:id="@+id/rewind_btn"
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_gravity="center_vertical|start"
android:layout_margin="16dp"
android:src="@drawable/ic_replay_5_48px"
android:background="@drawable/bg_round_ripple"/>
</FrameLayout>
</FrameLayout>

View File

@ -3,25 +3,30 @@
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="48dp"
android:paddingLeft="20dp"
android:paddingRight="20dp">
android:layout_height="42dp"
android:paddingBottom="8dp"
android:paddingLeft="8dp"
android:paddingRight="8dp">
<FrameLayout
android:id="@+id/reply_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:minWidth="56dp">
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:background="?android:actionBarItemBackground"
android:minWidth="34dp">
<TextView
android:id="@+id/reply"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_gravity="center"
android:drawableStart="@drawable/ic_fluent_chat_multiple_24_regular"
android:drawablePadding="8dp"
android:drawableTint="?android:textColorSecondary"
android:drawableStart="@drawable/ic_reply_20px"
android:drawablePadding="6dp"
android:drawableTint="?colorM3OnSurfaceVariant"
android:textColor="?colorM3OnSurfaceVariant"
android:gravity="center_vertical"
android:textAppearance="@style/m3_label_large"
android:textAppearance="@style/m3_label_medium"
tools:text="123"/>
</FrameLayout>
@ -34,18 +39,21 @@
android:id="@+id/boost_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:minWidth="56dp">
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:background="?android:actionBarItemBackground"
android:minWidth="34dp">
<TextView
android:id="@+id/boost"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_gravity="center"
android:drawableStart="@drawable/ic_boost"
android:drawablePadding="8dp"
android:drawableTint="@color/boost_icon"
android:textColor="@color/boost_icon"
android:drawableStart="@drawable/ic_repeat_selector"
android:drawablePadding="6dp"
android:drawableTint="?colorM3OnSurfaceVariant"
android:textColor="?colorM3OnSurfaceVariant"
android:gravity="center_vertical"
android:textAppearance="@style/m3_label_large"
android:textAppearance="@style/m3_label_medium"
tools:text="123"/>
</FrameLayout>
@ -58,18 +66,21 @@
android:id="@+id/favorite_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:minWidth="56dp">
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:background="?android:actionBarItemBackground"
android:minWidth="34dp">
<TextView
android:id="@+id/favorite"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_gravity="center"
android:drawableStart="@drawable/ic_fluent_star_24_selector"
android:drawablePadding="8dp"
android:drawableTint="@color/favorite_icon"
android:textColor="@color/favorite_icon"
android:drawableStart="@drawable/ic_star_selector"
android:drawablePadding="6dp"
android:drawableTint="?colorM3OnSurfaceVariant"
android:textColor="?colorM3OnSurfaceVariant"
android:gravity="center_vertical"
android:textAppearance="@style/m3_label_large"
android:textAppearance="@style/m3_label_medium"
tools:text="123"/>
</FrameLayout>
@ -82,14 +93,17 @@
android:id="@+id/share_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:minWidth="56dp">
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:background="?android:actionBarItemBackground"
android:minWidth="34dp">
<ImageView
android:id="@+id/share"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_gravity="center"
android:src="@drawable/ic_fluent_share_24_regular"
android:tint="?android:textColorSecondary"
android:src="@drawable/ic_share_20px"
android:tint="?colorM3OnSurfaceVariant"
android:gravity="center_vertical"/>
</FrameLayout>

View File

@ -13,30 +13,22 @@
android:layout_height="24dp"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="-2dp"
android:layout_marginEnd="-2dp"
android:background="?android:selectableItemBackgroundBorderless"
android:scaleType="center"
android:tint="?android:textColorSecondary"
android:tint="?colorM3OnSurfaceVariant"
android:contentDescription="@string/more_options"
android:src="@drawable/ic_post_more" />
<ImageView
android:id="@+id/visibility"
android:layout_width="24dp"
android:layout_height="20dp"
android:layout_alignParentEnd="true"
android:layout_below="@id/more"
android:background="?android:selectableItemBackgroundBorderless"
android:scaleType="center"
android:tint="?android:textColorSecondary"
android:src="@drawable/ic_visibility" />
android:src="@drawable/ic_more_vert_20px" />
<ImageView
android:id="@+id/avatar"
android:layout_width="46dp"
android:layout_height="46dp"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginEnd="12dp" />
android:layout_marginTop="2dp"
android:layout_marginEnd="8dp" />
<org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout
android:id="@+id/name_wrap"
@ -53,7 +45,8 @@
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="@style/m3_title_medium"
android:textAlignment="viewStart"
android:textColor="?colorM3OnSurface"
android:gravity="start|center_vertical"
tools:text="Eugen" />
<TextView
@ -66,46 +59,22 @@
android:textAppearance="@style/m3_title_medium"
android:fontFamily="sans-serif"
android:textAlignment="viewStart"
android:textColor="?colorM3OnSurface"
tools:text="boosted your cat picture" />
</org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout>
<org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout
<TextView
android:id="@+id/time_and_username"
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_below="@id/name_wrap"
android:layout_toEndOf="@id/avatar"
android:layout_toStartOf="@id/visibility"
android:layoutDirection="locale"
android:orientation="horizontal">
<TextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="@style/m3_title_small"
tools:text="\@Gargron" />
<TextView
android:id="@+id/separator"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:importantForAccessibility="no"
android:text="·"
android:textAppearance="@style/m3_title_small" />
<TextView
android:id="@+id/timestamp"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:textAppearance="@style/m3_title_small"
android:singleLine="true"
tools:text="3h" />
</org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout>
android:singleLine="true"
android:ellipsize="end"
android:textAppearance="@style/m3_title_small"
android:gravity="center_vertical"
android:textColor="?colorM3OnSurfaceVariant"
tools:text="9h ago · \@Gargron@mastodon.social"/>
</RelativeLayout>

View File

@ -17,8 +17,8 @@
android:layout_width="40dp"
android:layout_height="22dp"
android:layout_gravity="start|bottom"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:importantForAccessibility="no"

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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"
android:orientation="vertical">
@ -8,20 +9,24 @@
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="20dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingBottom="8dp"
android:textAppearance="@style/m3_label_large"
android:textColor="?android:textColorPrimary" />
android:layout_marginBottom="8dp"
android:textAppearance="@style/m3_body_medium"
android:gravity="center_vertical"
android:textColor="?colorM3OnSurfaceVariant"
tools:text="fdsafdsafsdafds"/>
<Button
android:id="@+id/vote_btn"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="8dp"
android:enabled="false"
style="@style/Widget.Mastodon.M3.Button.Filled"
android:text="@string/action_vote"/>
</LinearLayout>

View File

@ -5,53 +5,60 @@
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:paddingBottom="8dp"
android:clipToPadding="false">
<LinearLayout
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="48dp"
android:outlineProvider="background"
android:elevation="2dp"
android:layout_height="40dp"
android:background="@drawable/bg_poll_option_clickable"
android:duplicateParentState="true"
android:layoutDirection="locale">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="12dp"
android:layout_gravity="center_vertical"
android:duplicateParentState="true"
android:tint="?colorDarkIcon"
android:src="@drawable/ic_poll_option_button"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:textAppearance="@style/m3_label_large"
android:textColor="?colorM3Primary"
android:singleLine="true"
android:ellipsize="end"
android:paddingEnd="26dp"
tools:text="scream into void jsfdklfjdalskfjdsalkfjdsalkfjdsalkfdjsalkfdsajlk"/>
<ImageView
android:id="@+id/checkbox"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="-26dp"
android:tint="?colorM3OnSecondaryContainer"
android:scaleType="center"
android:src="@drawable/ic_poll_check" />
</LinearLayout>
<TextView
android:id="@+id/percent"
android:layout_width="46dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:textAppearance="@style/m3_title_medium"
android:textAppearance="@style/m3_label_large"
android:textColor="?colorM3OnSecondaryContainer"
android:visibility="gone"
android:gravity="end"
tools:visibility="visible"
tools:text="00.0%"/>
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:textAppearance="@style/m3_title_medium"
android:singleLine="true"
android:ellipsize="end"
tools:text="scream into void"/>
</LinearLayout>
</FrameLayout>

View File

@ -2,7 +2,6 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="-6dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp">
@ -11,9 +10,10 @@
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_small"
android:drawableStart="@drawable/ic_fluent_arrow_repeat_all_20_filled"
android:drawableTint="?android:textColorSecondary"
android:textAppearance="@style/m3_label_large"
android:drawableStart="@drawable/ic_repeat_20px"
android:drawableTint="?colorM3OnSurfaceVariant"
android:textColor="?colorM3OnSurfaceVariant"
android:drawablePadding="6dp"
android:singleLine="true"
android:ellipsize="end"/>

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:paddingRight="16dp">
<LinearLayout
android:id="@+id/spoiler_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/bg_spoiler"
android:paddingLeft="12dp"
android:paddingTop="8dp"
android:paddingRight="12dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/spoiler_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:textAppearance="@style/m3_body_large"
android:textColor="?colorM3OnSecondaryContainer"
tools:text="Spoilery stuff"/>
<TextView
android:id="@+id/spoiler_action"
android:layout_width="match_parent"
android:layout_height="20dp"
android:textAppearance="@style/m3_label_large"
android:singleLine="true"
android:gravity="center_vertical"
android:textColor="?colorM3Primary"
tools:text="Re-hide"/>
</LinearLayout>
</FrameLayout>

View File

@ -5,38 +5,15 @@
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="10dp"
android:paddingBottom="12dp">
android:paddingTop="16dp"
android:paddingBottom="8dp">
<org.joinmastodon.android.ui.views.LinkedTextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="?colorM3OnSurface"
android:textAppearance="@style/m3_body_large"/>
<LinearLayout
android:id="@+id/spoiler_overlay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/spoiler_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:textAppearance="@style/m3_title_large"
tools:text="CW title"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center_horizontal"
android:text="@string/tap_to_reveal"/>
</LinearLayout>
</FrameLayout>

View File

@ -5,8 +5,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|bottom"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:importantForAccessibility="noHideDescendants"

View File

@ -103,9 +103,9 @@
<item quantity="one">%d day left</item>
<item quantity="other">%d days left</item>
</plurals>
<plurals name="x_voters">
<item quantity="one">%,d voter</item>
<item quantity="other">%,d voters</item>
<plurals name="x_votes">
<item quantity="one">%,d vote</item>
<item quantity="other">%,d votes</item>
</plurals>
<string name="poll_closed">Closed</string>
<string name="confirm_mute_title">Mute Account</string>
@ -438,4 +438,7 @@
<!-- %1$s is server domain, %2$s is email domain. You can reorder these placeholders to fit your language better. -->
<string name="signup_email_domain_blocked">%1$s doesn\'t allow signups from %2$s. Try a different one or &lt;a>pick a different server&lt;/a>.</string>
<string name="signup_username_taken">This username is taken.</string>
<string name="spoiler_show">Show anyway</string>
<string name="spoiler_hide">Re-hide</string>
<string name="poll_multiple_choice">Choose one or more</string>
</resources>

View File

@ -4,7 +4,6 @@
<!-- needed to disable scrim on API 29+ -->
<item name="android:enforceNavigationBarContrast" tools:ignore="NewApi">false</item>
<item name="android:enforceStatusBarContrast" tools:ignore="NewApi">false</item>
<item name="appkitBackDrawable">@drawable/ic_fluent_arrow_left_24_regular</item>
<item name="android:splitMotionEvents">false</item>
<item name="android:windowBackground">?colorWindowBackground</item>
<item name="android:editTextStyle">@style/Widget.Mastodon.EditText</item>
@ -23,7 +22,6 @@
<item name="colorBackgroundLight">@color/gray_50</item>
<item name="colorBackgroundLightest">@color/gray_25</item>
<item name="colorDarkIcon">@color/gray_900</item>
<item name="colorWindowBackground">@color/white</item>
<item name="android:statusBarColor">@color/gray_50</item>
<item name="android:navigationBarColor">@color/navigation_bar_bg</item>
<item name="android:actionBarTheme">@style/Theme.Mastodon.Toolbar</item>
@ -70,13 +68,14 @@
<item name="colorM3OnError">#FFF</item>
<item name="colorM3ErrorContainer">#F9DEDC</item>
<item name="colorM3OnErrorContainer">#410E0B</item>
<item name="colorWindowBackground">?colorM3Background</item>
</style>
<style name="Theme.Mastodon.Dark" parent="Theme.AppKit">
<!-- needed to disable scrim on API 29+ -->
<item name="android:enforceNavigationBarContrast" tools:ignore="NewApi">false</item>
<item name="android:enforceStatusBarContrast" tools:ignore="NewApi">false</item>
<item name="appkitBackDrawable">@drawable/ic_fluent_arrow_left_24_regular</item>
<item name="android:splitMotionEvents">false</item>
<item name="android:windowBackground">?colorWindowBackground</item>
<item name="android:editTextStyle">@style/Widget.Mastodon.EditText</item>