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:dynamicanimation:1.1.0-alpha03'
implementation 'me.grishka.litex:viewpager:1.0.0' implementation 'me.grishka.litex:viewpager:1.0.0'
implementation 'me.grishka.litex:viewpager2: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 'me.grishka.appkit:appkit:1.2.7'
implementation 'com.google.code.gson:gson:2.8.9' implementation 'com.google.code.gson:gson:2.8.9'
implementation 'org.jsoup:jsoup:1.14.3' 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 org.parceler.Parcels;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -57,6 +56,7 @@ public class AudioPlayerService extends Service{
private static HashSet<Callback> callbacks=new HashSet<>(); private static HashSet<Callback> callbacks=new HashSet<>();
private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener=this::onAudioFocusChanged; private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener=this::onAudioFocusChanged;
private boolean resumeAfterAudioFocusGain; private boolean resumeAfterAudioFocusGain;
private boolean isBuffering=true;
private BroadcastReceiver receiver=new BroadcastReceiver(){ private BroadcastReceiver receiver=new BroadcastReceiver(){
@Override @Override
@@ -176,6 +176,7 @@ public class AudioPlayerService extends Service{
player.setOnErrorListener(this::onPlayerError); player.setOnErrorListener(this::onPlayerError);
player.setOnCompletionListener(this::onPlayerCompletion); player.setOnCompletionListener(this::onPlayerCompletion);
player.setOnSeekCompleteListener(this::onPlayerSeekCompleted); player.setOnSeekCompleteListener(this::onPlayerSeekCompleted);
player.setOnInfoListener(this::onPlayerInfo);
try{ try{
player.setDataSource(this, Uri.parse(attachment.url)); player.setDataSource(this, Uri.parse(attachment.url));
player.prepareAsync(); player.prepareAsync();
@@ -187,7 +188,9 @@ public class AudioPlayerService extends Service{
} }
private void onPlayerPrepared(MediaPlayer mp){ private void onPlayerPrepared(MediaPlayer mp){
Log.i(TAG, "onPlayerPrepared");
playerReady=true; playerReady=true;
isBuffering=false;
player.start(); player.start();
updateSessionState(false); updateSessionState(false);
} }
@@ -205,6 +208,21 @@ public class AudioPlayerService extends Service{
stopSelf(); 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){ private void onAudioFocusChanged(int change){
switch(change){ switch(change){
case AudioManager.AUDIOFOCUS_LOSS -> { case AudioManager.AUDIOFOCUS_LOSS -> {
@@ -232,12 +250,16 @@ public class AudioPlayerService extends Service{
private void updateSessionState(boolean removeNotification){ private void updateSessionState(boolean removeNotification){
session.setPlaybackState(new PlaybackState.Builder() 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) .setActions(PlaybackState.ACTION_STOP | PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_SEEK_TO)
.build()); .build());
updateNotification(!player.isPlaying(), removeNotification); updateNotification(!player.isPlaying(), removeNotification);
for(Callback cb:callbacks) 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){ private void updateNotification(boolean dismissable, boolean removeNotification){
@@ -310,6 +332,12 @@ public class AudioPlayerService extends Service{
return attachment.id; return attachment.id;
} }
public PlayState getPlayState(){
if(isBuffering)
return PlayState.BUFFERING;
return player.isPlaying() ? PlayState.PLAYING : PlayState.PAUSED;
}
public static void registerCallback(Callback cb){ public static void registerCallback(Callback cb){
callbacks.add(cb); callbacks.add(cb);
} }
@@ -333,7 +361,13 @@ public class AudioPlayerService extends Service{
} }
public interface Callback{ public interface Callback{
void onPlayStateChanged(String attachmentID, boolean playing, int position); void onPlayStateChanged(String attachmentID, PlayState state, int position);
void onPlaybackStopped(String attachmentID); 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.MediaGridStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem; 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.StatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer; import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
@@ -48,6 +49,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -405,25 +407,26 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
.exec(accountID); .exec(accountID);
} }
public void onRevealSpoilerClick(TextStatusDisplayItem.Holder holder){ public void onRevealSpoilerClick(SpoilerStatusDisplayItem.Holder holder){
Status status=holder.getItem().status; Status status=holder.getItem().status;
revealSpoiler(status, holder.getItemID()); toggleSpoiler(status, holder.getItemID());
} }
public void onRevealSpoilerClick(MediaGridStatusDisplayItem.Holder holder){ protected void toggleSpoiler(Status status, String itemID){
Status status=holder.getItem().status; status.spoilerRevealed=!status.spoilerRevealed;
revealSpoiler(status, holder.getItemID()); 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){ int index=displayItems.indexOf(spoilerItem);
status.spoilerRevealed=true; if(status.spoilerRevealed){
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class); displayItems.addAll(index+1, spoilerItem.contentItems);
if(text!=null) adapter.notifyItemRangeInserted(index+1, spoilerItem.contentItems.size());
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()-getMainAdapterOffset()); }else{
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class); displayItems.subList(index+1, index+1+spoilerItem.contentItems.size()).clear();
if(header!=null) adapter.notifyItemRangeRemoved(index+1, spoilerItem.contentItems.size());
header.rebind(); }
updateImagesSpoilerState(status, itemID);
} }
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder){ 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); spoilerEdit=view.findViewById(R.id.content_warning);
LayerDrawable spoilerBg=(LayerDrawable) spoilerEdit.getBackground().mutate(); LayerDrawable spoilerBg=(LayerDrawable) spoilerEdit.getBackground().mutate();
spoilerBg.setDrawableByLayerId(R.id.left_drawable, new SpoilerStripesDrawable()); spoilerBg.setDrawableByLayerId(R.id.left_drawable, new SpoilerStripesDrawable(false));
spoilerBg.setDrawableByLayerId(R.id.right_drawable, new SpoilerStripesDrawable()); spoilerBg.setDrawableByLayerId(R.id.right_drawable, new SpoilerStripesDrawable(false));
spoilerEdit.setBackground(spoilerBg); spoilerEdit.setBackground(spoilerBg);
if((savedInstanceState!=null && savedInstanceState.getBoolean("hasSpoiler", false)) || hasSpoiler){ if((savedInstanceState!=null && savedInstanceState.getBoolean("hasSpoiler", false)) || hasSpoiler){
hasSpoiler=true; hasSpoiler=true;

View File

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

View File

@@ -127,6 +127,7 @@ public class Attachment extends BaseModel{
public PointF focus; public PointF focus;
public SizeMetadata original; public SizeMetadata original;
public SizeMetadata small; public SizeMetadata small;
public ColorsMetadata colors;
@Override @Override
public String toString(){ public String toString(){
@@ -138,6 +139,7 @@ public class Attachment extends BaseModel{
", focus="+focus+ ", focus="+focus+
", original="+original+ ", original="+original+
", small="+small+ ", 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()); 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){ public static ViewOutlineProvider roundedRect(int dp){
ViewOutlineProvider provider=roundedRects.get(dp); ViewOutlineProvider provider=roundedRects.get(dp);

View File

@@ -1,10 +1,20 @@
package org.joinmastodon.android.ui.displayitems; package org.joinmastodon.android.ui.displayitems;
import android.annotation.SuppressLint;
import android.content.Context; 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.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.SeekBar; import android.widget.SeekBar;
import android.widget.TextView; import android.widget.TextView;
@@ -13,16 +23,27 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Status; 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.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 class AudioStatusDisplayItem extends StatusDisplayItem{
public final Status status; public final Status status;
public final Attachment attachment; public final Attachment attachment;
private final ImageLoaderRequest imageRequest;
public AudioStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status, Attachment attachment){ public AudioStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status, Attachment attachment){
super(parentID, parentFragment); super(parentID, parentFragment);
this.status=status; this.status=status;
this.attachment=attachment; this.attachment=attachment;
imageRequest=new UrlImageLoaderRequest(TextUtils.isEmpty(attachment.previewUrl) ? status.account.avatarStatic : attachment.previewUrl, V.dp(100), V.dp(100));
} }
@Override @Override
@@ -30,25 +51,38 @@ public class AudioStatusDisplayItem extends StatusDisplayItem{
return Type.AUDIO; return Type.AUDIO;
} }
public static class Holder extends StatusDisplayItem.Holder<AudioStatusDisplayItem> implements AudioPlayerService.Callback{ @Override
private final ImageButton playPauseBtn; 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 TextView time;
private final SeekBar seekBar; private final ImageView image;
private final FrameLayout content;
private final AudioAttachmentBackgroundDrawable bgDrawable;
private int lastKnownPosition; private int lastKnownPosition;
private long lastKnownPositionTime; private long lastKnownPositionTime;
private boolean playing; private int lastPosSeconds=-1;
private int lastRemainingSeconds=-1; private AudioPlayerService.PlayState state;
private boolean seekbarBeingDragged;
private Runnable positionUpdater=this::updatePosition; private final Runnable positionUpdater=this::updatePosition;
public Holder(Context context, ViewGroup parent){ public Holder(Context context, ViewGroup parent){
super(context, R.layout.display_item_audio, parent); super(context, R.layout.display_item_audio, parent);
playPauseBtn=findViewById(R.id.play_pause_btn); playPauseBtn=findViewById(R.id.play_pause_btn);
time=findViewById(R.id.time); time=findViewById(R.id.time);
seekBar=findViewById(R.id.seekbar); image=findViewById(R.id.image);
seekBar.setThumb(new SeekBarThumbDrawable(context)); content=findViewById(R.id.content);
forwardBtn=findViewById(R.id.forward_btn);
rewindBtn=findViewById(R.id.rewind_btn);
playPauseBtn.setOnClickListener(this::onPlayPauseClick); playPauseBtn.setOnClickListener(this::onPlayPauseClick);
itemView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener(){ itemView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener(){
@Override @Override
@@ -61,76 +95,71 @@ public class AudioStatusDisplayItem extends StatusDisplayItem{
AudioPlayerService.unregisterCallback(Holder.this); AudioPlayerService.unregisterCallback(Holder.this);
} }
}); });
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener(){ forwardBtn.setOnClickListener(this::onSeekButtonClick);
@Override rewindBtn.setOnClickListener(this::onSeekButtonClick);
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));
}
}
@Override image.setOutlineProvider(OutlineProviders.OVAL);
public void onStartTrackingTouch(SeekBar seekBar){ image.setClipToOutline(true);
seekbarBeingDragged=true; content.setBackground(bgDrawable=new AudioAttachmentBackgroundDrawable());
}
@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);
}
});
} }
@Override @Override
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);
// 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(); AudioPlayerService service=AudioPlayerService.getInstance();
if(service!=null && service.getAttachmentID().equals(item.attachment.id)){ if(service!=null && service.getAttachmentID().equals(item.attachment.id)){
seekBar.setEnabled(true); forwardBtn.setVisibility(View.VISIBLE);
onPlayStateChanged(item.attachment.id, service.isPlaying(), service.getPosition()); rewindBtn.setVisibility(View.VISIBLE);
onPlayStateChanged(item.attachment.id, service.getPlayState(), service.getPosition());
actuallyUpdatePosition();
}else{ }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){ private void onPlayPauseClick(View v){
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)){
if(playing) if(state!=AudioPlayerService.PlayState.PAUSED)
service.pause(true); service.pause(true);
else else
service.play(); service.play();
}else{ }else{
AudioPlayerService.start(v.getContext(), item.status, item.attachment); AudioPlayerService.start(v.getContext(), item.status, item.attachment);
onPlayStateChanged(item.attachment.id, true, 0); onPlayStateChanged(item.attachment.id, AudioPlayerService.PlayState.BUFFERING, 0);
seekBar.setEnabled(true); forwardBtn.setVisibility(View.VISIBLE);
rewindBtn.setVisibility(View.VISIBLE);
} }
} }
@Override @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)){ if(attachmentID.equals(item.attachment.id)){
this.lastKnownPosition=position; this.lastKnownPosition=position;
lastKnownPositionTime=SystemClock.uptimeMillis(); lastKnownPositionTime=SystemClock.uptimeMillis();
this.playing=playing; this.state=state;
playPauseBtn.setImageResource(playing ? R.drawable.ic_fluent_pause_circle_24_filled : R.drawable.ic_fluent_play_circle_24_filled); setPlayButtonPlaying(state!=AudioPlayerService.PlayState.PAUSED, true);
if(!playing){ if(state==AudioPlayerService.PlayState.PLAYING){
lastRemainingSeconds=-1;
time.setText(formatDuration((int) item.attachment.getDuration()));
}else{
itemView.postOnAnimation(positionUpdater); itemView.postOnAnimation(positionUpdater);
}else if(state==AudioPlayerService.PlayState.BUFFERING){
actuallyUpdatePosition();
} }
} }
} }
@@ -138,14 +167,15 @@ public class AudioStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void onPlaybackStopped(String attachmentID){ public void onPlaybackStopped(String attachmentID){
if(attachmentID.equals(item.attachment.id)){ if(attachmentID.equals(item.attachment.id)){
playing=false; state=null;
playPauseBtn.setImageResource(R.drawable.ic_fluent_play_circle_24_filled); setPlayButtonPlaying(false, true);
seekBar.setProgress(0); forwardBtn.setVisibility(View.INVISIBLE);
seekBar.setEnabled(false); rewindBtn.setVisibility(View.INVISIBLE);
time.setText(formatDuration((int)item.attachment.getDuration())); time.setText(formatDuration((int)item.attachment.getDuration()));
} }
} }
@SuppressLint("DefaultLocale")
private String formatDuration(int seconds){ private String formatDuration(int seconds){
if(seconds>=3600) if(seconds>=3600)
return String.format("%d:%02d:%02d", seconds/3600, seconds%3600/60, seconds%60); 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(){ private void updatePosition(){
if(!playing || seekbarBeingDragged) if(state!=AudioPlayerService.PlayState.PLAYING)
return; return;
double pos=lastKnownPosition/1000.0+(SystemClock.uptimeMillis()-lastKnownPositionTime)/1000.0; actuallyUpdatePosition();
seekBar.setProgress((int)Math.round(pos/item.attachment.getDuration()*10000.0));
itemView.postOnAnimation(positionUpdater); itemView.postOnAnimation(positionUpdater);
int remainingSeconds=(int)(item.attachment.getDuration()-pos); }
if(remainingSeconds!=lastRemainingSeconds){
lastRemainingSeconds=remainingSeconds; @SuppressLint("SetTextI18n")
time.setText("-"+formatDuration(remainingSeconds)); 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.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
@@ -45,6 +47,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{ public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{
private final TextView reply, boost, favorite; private final TextView reply, boost, favorite;
private final ImageView share; private final ImageView share;
private final ColorStateList buttonColors;
private final View.AccessibilityDelegate buttonAccessibilityDelegate=new View.AccessibilityDelegate(){ private final View.AccessibilityDelegate buttonAccessibilityDelegate=new View.AccessibilityDelegate(){
@Override @Override
@@ -61,6 +64,27 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
boost=findViewById(R.id.boost); boost=findViewById(R.id.boost);
favorite=findViewById(R.id.favorite); favorite=findViewById(R.id.favorite);
share=findViewById(R.id.share); 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){ if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
UiUtils.fixCompoundDrawableTintOnAndroid6(reply); UiUtils.fixCompoundDrawableTintOnAndroid6(reply);
UiUtils.fixCompoundDrawableTintOnAndroid6(boost); UiUtils.fixCompoundDrawableTintOnAndroid6(boost);
@@ -94,7 +118,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
private void bindButton(TextView btn, long count){ private void bindButton(TextView btn, long count){
if(count>0 && !item.hideCounts){ if(count>0 && !item.hideCounts){
btn.setText(UiUtils.abbreviateNumber(count)); btn.setText(UiUtils.abbreviateNumber(count));
btn.setCompoundDrawablePadding(V.dp(8)); btn.setCompoundDrawablePadding(V.dp(6));
}else{ }else{
btn.setText(""); btn.setText("");
btn.setCompoundDrawablePadding(0); btn.setCompoundDrawablePadding(0);

View File

@@ -1,5 +1,6 @@
package org.joinmastodon.android.ui.displayitems; package org.joinmastodon.android.ui.displayitems;
import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.graphics.Outline; 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.Attachment;
import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.text.HtmlParser; 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.utils.UiUtils; 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{ public static class Holder extends StatusDisplayItem.Holder<HeaderStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView name, username, timestamp, extraText; private final TextView name, timeAndUsername, extraText;
private final ImageView avatar, more, visibility; private final ImageView avatar, more;
private final PopupMenu optionsMenu; private final PopupMenu optionsMenu;
private Relationship relationship; private Relationship relationship;
private APIRequest<?> currentRelationshipRequest; 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){ public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_header, parent); super(activity, R.layout.display_item_header, parent);
name=findViewById(R.id.name); name=findViewById(R.id.name);
username=findViewById(R.id.username); timeAndUsername=findViewById(R.id.time_and_username);
timestamp=findViewById(R.id.timestamp);
avatar=findViewById(R.id.avatar); avatar=findViewById(R.id.avatar);
more=findViewById(R.id.more); more=findViewById(R.id.more);
visibility=findViewById(R.id.visibility);
extraText=findViewById(R.id.extra_text); extraText=findViewById(R.id.extra_text);
avatar.setOnClickListener(this::onAvaClick); avatar.setOnClickListener(this::onAvaClick);
avatar.setOutlineProvider(roundCornersOutline); avatar.setOutlineProvider(OutlineProviders.roundedRect(10));
avatar.setClipToOutline(true); avatar.setClipToOutline(true);
more.setOnClickListener(this::onMoreClick); more.setOnClickListener(this::onMoreClick);
visibility.setOnClickListener(v->item.parentFragment.onVisibilityIconClick(this));
optionsMenu=new PopupMenu(activity, more); optionsMenu=new PopupMenu(activity, more);
optionsMenu.inflate(R.menu.post); optionsMenu.inflate(R.menu.post);
@@ -200,22 +192,17 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
}); });
} }
@SuppressLint("SetTextI18n")
@Override @Override
public void onBind(HeaderStatusDisplayItem item){ public void onBind(HeaderStatusDisplayItem item){
name.setText(item.parsedName); name.setText(item.parsedName);
username.setText('@'+item.user.acct); String time;
if(item.status==null || item.status.editedAt==null) if(item.status==null || item.status.editedAt==null)
timestamp.setText(UiUtils.formatRelativeTimestamp(itemView.getContext(), item.createdAt)); time=UiUtils.formatRelativeTimestamp(itemView.getContext(), item.createdAt);
else else
timestamp.setText(item.parentFragment.getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(itemView.getContext(), item.status.editedAt))); time=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){ timeAndUsername.setText(time+" · @"+item.user.acct);
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());
}
}
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0); itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0);
if(TextUtils.isEmpty(item.extraText)){ if(TextUtils.isEmpty(item.extraText)){
extraText.setVisibility(View.GONE); 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.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator; import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
public class MediaGridStatusDisplayItem extends StatusDisplayItem{ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
private static final String TAG="MediaGridDisplayItem"; private static final String TAG="MediaGridDisplayItem";
@@ -97,6 +98,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
wrapper=(FrameLayout)itemView; wrapper=(FrameLayout)itemView;
layout=new MediaGridLayout(activity); layout=new MediaGridLayout(activity);
wrapper.addView(layout); wrapper.addView(layout);
wrapper.setPadding(0, 0, 0, V.dp(8));
activity.getLayoutInflater().inflate(R.layout.overlay_image_alt_text, wrapper); activity.getLayoutInflater().inflate(R.layout.overlay_image_alt_text, wrapper);
altTextWrapper=findViewById(R.id.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); altTextClose=findViewById(R.id.alt_text_close);
altText=findViewById(R.id.alt_text); altText=findViewById(R.id.alt_text);
altTextClose.setOnClickListener(this::onAltTextCloseClick); altTextClose.setOnClickListener(this::onAltTextCloseClick);
wrapper.setClipToPadding(false);
} }
@Override @Override
@@ -158,11 +161,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
private void onViewClick(View v){ private void onViewClick(View v){
int index=(Integer)v.getTag(); int index=(Integer)v.getTag();
if(!item.status.spoilerRevealed){ ((PhotoViewerHost) item.parentFragment).openPhotoViewer(item.parentID, item.status, index, this);
item.parentFragment.onRevealSpoilerClick(this);
}else if(item.parentFragment instanceof PhotoViewerHost){
((PhotoViewerHost) item.parentFragment).openPhotoViewer(item.parentID, item.status, index, this);
}
} }
private void onAltTextClick(View v){ private void onAltTextClick(View v){

View File

@@ -37,9 +37,11 @@ public class PollFooterStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void onBind(PollFooterStatusDisplayItem item){ 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()){ if(item.poll.expiresAt!=null && !item.poll.isExpired()){
text+=" · "+UiUtils.formatTimeLeft(itemView.getContext(), item.poll.expiresAt); 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()){ }else if(item.poll.isExpired()){
text+=" · "+item.parentFragment.getString(R.string.poll_closed); 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.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Poll; import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.text.HtmlParser; import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper; import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
@@ -25,11 +26,13 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
private boolean showResults; private boolean showResults;
private float votesFraction; // 0..1 private float votesFraction; // 0..1
private boolean isMostVoted; private boolean isMostVoted;
private final int optionIndex;
public final Poll poll; 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); super(parentID, parentFragment);
this.option=option; this.optionIndex=optionIndex;
option=poll.options.get(optionIndex);
this.poll=poll; this.poll=poll;
text=HtmlParser.parseCustomEmoji(option.title, poll.emojis); text=HtmlParser.parseCustomEmoji(option.title, poll.emojis);
emojiHelper.setText(text); emojiHelper.setText(text);
@@ -61,23 +64,24 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder<PollOptionStatusDisplayItem> implements ImageLoaderViewHolder{ public static class Holder extends StatusDisplayItem.Holder<PollOptionStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView text, percent; private final TextView text, percent;
private final View icon, button; private final View check, button;
private final Drawable progressBg; private final Drawable progressBg;
public Holder(Activity activity, ViewGroup parent){ public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_poll_option, parent); super(activity, R.layout.display_item_poll_option, parent);
text=findViewById(R.id.text); text=findViewById(R.id.text);
percent=findViewById(R.id.percent); percent=findViewById(R.id.percent);
icon=findViewById(R.id.icon); check=findViewById(R.id.checkbox);
button=findViewById(R.id.button); button=findViewById(R.id.button);
progressBg=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted, activity.getTheme()).mutate(); progressBg=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted, activity.getTheme()).mutate();
itemView.setOnClickListener(this::onButtonClick); itemView.setOnClickListener(this::onButtonClick);
button.setOutlineProvider(OutlineProviders.roundedRect(20));
button.setClipToOutline(true);
} }
@Override @Override
public void onBind(PollOptionStatusDisplayItem item){ public void onBind(PollOptionStatusDisplayItem item){
text.setText(item.text); text.setText(item.text);
icon.setVisibility(item.showResults ? View.GONE : View.VISIBLE);
percent.setVisibility(item.showResults ? View.VISIBLE : View.GONE); percent.setVisibility(item.showResults ? View.VISIBLE : View.GONE);
itemView.setClickable(!item.showResults); itemView.setClickable(!item.showResults);
if(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 GAP -> new GapStatusDisplayItem.Holder(activity, parent);
case EXTENDED_FOOTER -> new ExtendedFooterStatusDisplayItem.Holder(activity, parent); case EXTENDED_FOOTER -> new ExtendedFooterStatusDisplayItem.Holder(activity, parent);
case MEDIA_GRID -> new MediaGridStatusDisplayItem.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<>(); ArrayList<StatusDisplayItem> items=new ArrayList<>();
Status statusForContent=status.getContentStatus(); Status statusForContent=status.getContentStatus();
if(status.reblog!=null){ 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)){ }else if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId)){
Account account=Objects.requireNonNull(knownAccounts.get(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; HeaderStatusDisplayItem header;
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null)); 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)) 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 else
header.needBottomPadding=true; header.needBottomPadding=true;
List<Attachment> imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList()); List<Attachment> imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList());
if(!imageAttachments.isEmpty()){ if(!imageAttachments.isEmpty()){
PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments); 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){ for(Attachment att:statusForContent.mediaAttachments){
if(att.type==Attachment.Type.AUDIO){ 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){ 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)){ 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){ if(addFooter){
items.add(new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID)); items.add(new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID));
@@ -109,12 +121,20 @@ public abstract class StatusDisplayItem{
item.inset=inset; item.inset=inset;
item.index=i++; item.index=i++;
} }
if(items!=contentItems){
for(StatusDisplayItem item:contentItems){
item.inset=inset;
item.index=i++;
}
}
return items; return items;
} }
public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, List<StatusDisplayItem> items){ public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, List<StatusDisplayItem> items){
int i=0;
for(Poll.Option opt:poll.options){ 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)); items.add(new PollFooterStatusDisplayItem(parentID, fragment, poll));
} }
@@ -133,7 +153,8 @@ public abstract class StatusDisplayItem{
HASHTAG, HASHTAG,
GAP, GAP,
EXTENDED_FOOTER, EXTENDED_FOOTER,
MEDIA_GRID MEDIA_GRID,
SPOILER
} }
public static abstract class Holder<T extends StatusDisplayItem> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{ 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.app.Activity;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
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;
@@ -21,8 +17,7 @@ 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(), spoilerEmojiHelper; private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
private CharSequence parsedSpoilerText;
public boolean textSelectable; public boolean textSelectable;
public final Status status; public final Status status;
@@ -31,11 +26,6 @@ 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
@@ -45,29 +35,20 @@ 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);
} }
public static class Holder extends StatusDisplayItem.Holder<TextStatusDisplayItem> implements ImageLoaderViewHolder{ public static class Holder extends StatusDisplayItem.Holder<TextStatusDisplayItem> implements ImageLoaderViewHolder{
private final LinkedTextView text; private final LinkedTextView text;
private final TextView spoilerTitle;
private final View spoilerOverlay;
public Holder(Activity activity, ViewGroup parent){ public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_text, parent); super(activity, R.layout.display_item_text, parent);
text=findViewById(R.id.text); 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 @Override
@@ -75,29 +56,13 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
text.setText(item.text); text.setText(item.text);
text.setTextIsSelectable(item.textSelectable); text.setTextIsSelectable(item.textSelectable);
text.setInvalidateOnEveryFrame(false); text.setInvalidateOnEveryFrame(false);
if(!TextUtils.isEmpty(item.status.spoilerText)){ itemView.setClickable(false);
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);
}
} }
@Override @Override
public void setImage(int index, Drawable image){ public void setImage(int index, Drawable image){
getEmojiHelper().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)
@@ -112,7 +77,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
} }
private CustomEmojiHelper getEmojiHelper(){ 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{ public class SpoilerStripesDrawable extends Drawable{
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG); 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.setColor(0xff000000);
paint.setStyle(Paint.Style.STROKE); paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3); paint.setStrokeWidth(3);
this.flipped=flipped;
} }
@Override @Override
@@ -34,7 +39,7 @@ public class SpoilerStripesDrawable extends Drawable{
float y1=6.80133f; float y1=6.80133f;
float y2=-1.22874f; float y2=-1.22874f;
while(y2<height){ 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; y1+=8.03007f;
y2+=8.03007f; y2+=8.03007f;
} }

View File

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

View File

@@ -24,6 +24,7 @@ public class FrameLayoutThatOnlyMeasuresFirstChild extends FrameLayout{
return; return;
View child0=getChildAt(0); View child0=getChildAt(0);
measureChild(child0, widthMeasureSpec, heightMeasureSpec); 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"> <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
<item> <item>
<shape> <shape>
<solid android:color="?android:colorBackground"/> <stroke android:width="1dp" android:color="?colorM3Outline"/>
<corners android:radius="10dp"/> <corners android:radius="20dp"/>
</shape>
</item>
<item android:id="@android:id/mask">
<shape>
<solid android:color="#000"/>
<corners android:radius="20dp"/>
</shape> </shape>
</item> </item>
</ripple> </ripple>

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item> <item>
<shape> <scale android:scaleGravity="start|fill_vertical" android:scaleWidth="100%">
<solid android:color="?android:colorBackground"/> <shape>
<corners android:radius="10dp"/> <solid android:color="?colorM3SecondaryContainer"/>
</shape> <corners android:radius="20dp"/>
</shape>
</scale>
</item> </item>
<item> <item>
<clip android:clipOrientation="horizontal" android:gravity="start"> <shape>
<shape> <stroke android:width="1dp" android:color="?colorM3Outline"/>
<solid android:color="@color/poll_option_progress"/> <corners android:radius="20dp"/>
<corners android:radius="10dp"/> </shape>
</shape>
</clip>
</item> </item>
</layer-list> </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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" 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:paddingBottom="8dp"
android:clipToPadding="false"> android:clipToPadding="false">
<LinearLayout <FrameLayout
android:id="@+id/content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="40dp" android:layout_height="188dp">
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"/>
<TextView <TextView
android:id="@+id/time" android:id="@+id/time"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="20dp"
android:textAppearance="@style/m3_label_medium" android:layout_margin="8dp"
android:layout_gravity="bottom|start"
android:textAppearance="@style/m3_label_large"
android:textColor="?colorButtonText" android:textColor="?colorButtonText"
android:gravity="end" android:gravity="center_vertical"
android:singleLine="true" android:singleLine="true"
android:fontFeatureSettings="'tnum'"
tools:text="1:23"/> 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> </FrameLayout>

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,53 +5,60 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingLeft="16dp" android:paddingLeft="16dp"
android:paddingRight="16dp" android:paddingRight="16dp"
android:paddingTop="5dp" android:paddingBottom="8dp"
android:paddingBottom="5dp"
android:clipToPadding="false"> android:clipToPadding="false">
<LinearLayout <LinearLayout
android:id="@+id/button" android:id="@+id/button"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="40dp"
android:outlineProvider="background"
android:elevation="2dp"
android:background="@drawable/bg_poll_option_clickable" android:background="@drawable/bg_poll_option_clickable"
android:duplicateParentState="true" android:duplicateParentState="true"
android:layoutDirection="locale"> android:layoutDirection="locale">
<ImageView <LinearLayout
android:id="@+id/icon" android:layout_width="0dp"
android:layout_width="24dp" android:layout_height="match_parent"
android:layout_height="24dp" android:layout_weight="1"
android:layout_marginStart="12dp" android:orientation="horizontal">
android:layout_gravity="center_vertical" <TextView
android:duplicateParentState="true" android:id="@+id/text"
android:tint="?colorDarkIcon" android:layout_width="wrap_content"
android:src="@drawable/ic_poll_option_button"/> 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 <TextView
android:id="@+id/percent" android:id="@+id/percent"
android:layout_width="46dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginStart="16dp" android:textAppearance="@style/m3_label_large"
android:textAppearance="@style/m3_title_medium" android:textColor="?colorM3OnSecondaryContainer"
android:visibility="gone" android:visibility="gone"
android:gravity="end"
tools:visibility="visible" tools:visibility="visible"
tools:text="00.0%"/> 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> </LinearLayout>
</FrameLayout> </FrameLayout>

View File

@@ -2,7 +2,6 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="-6dp"
android:paddingLeft="16dp" android:paddingLeft="16dp"
android:paddingRight="16dp" android:paddingRight="16dp"
android:paddingTop="16dp"> android:paddingTop="16dp">
@@ -11,9 +10,10 @@
android:id="@+id/text" android:id="@+id/text"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_small" android:textAppearance="@style/m3_label_large"
android:drawableStart="@drawable/ic_fluent_arrow_repeat_all_20_filled" android:drawableStart="@drawable/ic_repeat_20px"
android:drawableTint="?android:textColorSecondary" android:drawableTint="?colorM3OnSurfaceVariant"
android:textColor="?colorM3OnSurfaceVariant"
android:drawablePadding="6dp" android:drawablePadding="6dp"
android:singleLine="true" android:singleLine="true"
android:ellipsize="end"/> 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:layout_height="wrap_content"
android:paddingLeft="16dp" android:paddingLeft="16dp"
android:paddingRight="16dp" android:paddingRight="16dp"
android:paddingTop="10dp" android:paddingTop="16dp"
android:paddingBottom="12dp"> android:paddingBottom="8dp">
<org.joinmastodon.android.ui.views.LinkedTextView <org.joinmastodon.android.ui.views.LinkedTextView
android:id="@+id/text" android:id="@+id/text"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="16sp" android:textSize="16sp"
android:textColor="?colorM3OnSurface"
android:textAppearance="@style/m3_body_large"/> 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> </FrameLayout>

View File

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

View File

@@ -103,9 +103,9 @@
<item quantity="one">%d day left</item> <item quantity="one">%d day left</item>
<item quantity="other">%d days left</item> <item quantity="other">%d days left</item>
</plurals> </plurals>
<plurals name="x_voters"> <plurals name="x_votes">
<item quantity="one">%,d voter</item> <item quantity="one">%,d vote</item>
<item quantity="other">%,d voters</item> <item quantity="other">%,d votes</item>
</plurals> </plurals>
<string name="poll_closed">Closed</string> <string name="poll_closed">Closed</string>
<string name="confirm_mute_title">Mute Account</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. --> <!-- %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_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="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> </resources>

View File

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