Compare commits
50 Commits
v1.1.5+for
...
v1.2.0+for
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92beac8dff | ||
|
|
ed1fdba9a5 | ||
|
|
5e194e3079 | ||
|
|
27c2791d6c | ||
|
|
d6bcc9c156 | ||
|
|
4c85fd4387 | ||
|
|
80d529d503 | ||
|
|
c5a19a2334 | ||
|
|
16857bebd9 | ||
|
|
1c340b7c66 | ||
|
|
7d9d8f0aae | ||
|
|
21fc35230c | ||
|
|
fc67c82040 | ||
|
|
4d04741fe0 | ||
|
|
5c7fe9dcb5 | ||
|
|
c3aa3af650 | ||
|
|
4a695b2a83 | ||
|
|
a8ba50e762 | ||
|
|
f79fc66578 | ||
|
|
4144639b75 | ||
|
|
7ea42c8403 | ||
|
|
af9b527f35 | ||
|
|
df58cdd86e | ||
|
|
507fcea646 | ||
|
|
5cd1e88da9 | ||
|
|
e2293899f0 | ||
|
|
1c743ee3a6 | ||
|
|
daf4c69df4 | ||
|
|
14d3add7b3 | ||
|
|
7e3193a708 | ||
|
|
545aa16cd3 | ||
|
|
4dcf32d13a | ||
|
|
e0aba23e80 | ||
|
|
b19ae9bb10 | ||
|
|
d20f8669e8 | ||
|
|
1567e5aba4 | ||
|
|
6b9b6710cf | ||
|
|
b07858a66d | ||
|
|
c05d0b600e | ||
|
|
0a8d73dc0b | ||
|
|
fd99f3caa1 | ||
|
|
794c4e5227 | ||
|
|
f5df8225d1 | ||
|
|
42c6446125 | ||
|
|
e3486ebf7c | ||
|
|
c0115f068c | ||
|
|
41682d1147 | ||
|
|
dd582c4bee | ||
|
|
3a0d314af0 | ||
|
|
a00ca599c1 |
@@ -9,10 +9,10 @@ android {
|
|||||||
applicationId "org.joinmastodon.android.sk"
|
applicationId "org.joinmastodon.android.sk"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 76
|
versionCode 77
|
||||||
versionName "1.1.5+fork.76"
|
versionName "1.2.0+fork.77"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "nl-rNL", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
|
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "da-rDK", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fa-rIR", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "ig-rNG", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "my-rMM", "nl-rNL", "no-rNO", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public class GlobalUserPreferences{
|
|||||||
public static boolean bottomEncoding;
|
public static boolean bottomEncoding;
|
||||||
public static boolean collapseLongPosts;
|
public static boolean collapseLongPosts;
|
||||||
public static boolean spectatorMode;
|
public static boolean spectatorMode;
|
||||||
|
public static boolean autoHideFab;
|
||||||
public static String publishButtonText;
|
public static String publishButtonText;
|
||||||
public static ThemePreference theme;
|
public static ThemePreference theme;
|
||||||
public static ColorPreference color;
|
public static ColorPreference color;
|
||||||
@@ -91,6 +92,7 @@ public class GlobalUserPreferences{
|
|||||||
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
|
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
|
||||||
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
|
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
|
||||||
spectatorMode=prefs.getBoolean("spectatorMode", false);
|
spectatorMode=prefs.getBoolean("spectatorMode", false);
|
||||||
|
autoHideFab=prefs.getBoolean("autoHideFab", true);
|
||||||
publishButtonText=prefs.getString("publishButtonText", "");
|
publishButtonText=prefs.getString("publishButtonText", "");
|
||||||
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
||||||
recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new HashMap<>());
|
recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new HashMap<>());
|
||||||
@@ -131,6 +133,7 @@ public class GlobalUserPreferences{
|
|||||||
.putBoolean("prefixRepliesWithRe", prefixRepliesWithRe)
|
.putBoolean("prefixRepliesWithRe", prefixRepliesWithRe)
|
||||||
.putBoolean("collapseLongPosts", collapseLongPosts)
|
.putBoolean("collapseLongPosts", collapseLongPosts)
|
||||||
.putBoolean("spectatorMode", spectatorMode)
|
.putBoolean("spectatorMode", spectatorMode)
|
||||||
|
.putBoolean("autoHideFab", autoHideFab)
|
||||||
.putString("publishButtonText", publishButtonText)
|
.putString("publishButtonText", publishButtonText)
|
||||||
.putBoolean("bottomEncoding", bottomEncoding)
|
.putBoolean("bottomEncoding", bottomEncoding)
|
||||||
.putInt("theme", theme.ordinal())
|
.putInt("theme", theme.ordinal())
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ package org.joinmastodon.android.fragments;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.animation.TranslateAnimation;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import android.text.TextUtils;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
|
import android.view.animation.TranslateAnimation;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
@@ -31,12 +32,10 @@ import org.joinmastodon.android.model.Poll;
|
|||||||
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.BetterItemAnimator;
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
|
||||||
import org.joinmastodon.android.ui.TileGridLayoutManager;
|
|
||||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
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.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
@@ -44,8 +43,10 @@ import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
|||||||
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
||||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
||||||
|
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout;
|
import org.joinmastodon.android.ui.views.MediaGridLayout;
|
||||||
|
import org.joinmastodon.android.utils.TypedObjectPool;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -56,7 +57,6 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
@@ -75,10 +75,12 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
protected DisplayItemsAdapter adapter;
|
protected DisplayItemsAdapter adapter;
|
||||||
protected String accountID;
|
protected String accountID;
|
||||||
protected PhotoViewer currentPhotoViewer;
|
protected PhotoViewer currentPhotoViewer;
|
||||||
|
protected ImageButton fab;
|
||||||
|
protected int scrollDiff = 0;
|
||||||
protected HashMap<String, Account> knownAccounts=new HashMap<>();
|
protected HashMap<String, Account> knownAccounts=new HashMap<>();
|
||||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||||
protected Rect tmpRect=new Rect();
|
protected Rect tmpRect=new Rect();
|
||||||
protected ImageButton fab;
|
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
|
||||||
|
|
||||||
public BaseStatusListFragment(){
|
public BaseStatusListFragment(){
|
||||||
super(20);
|
super(20);
|
||||||
@@ -188,21 +190,21 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void openPhotoViewer(String parentID, Status _status, int attachmentIndex){
|
public void openPhotoViewer(String parentID, Status _status, int attachmentIndex, MediaGridStatusDisplayItem.Holder gridHolder){
|
||||||
final Status status=_status.reblog!=null ? _status.reblog : _status;
|
final Status status=_status.getContentStatus();
|
||||||
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){
|
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){
|
||||||
private ImageStatusDisplayItem.Holder<?> transitioningHolder;
|
private MediaAttachmentViewController transitioningHolder;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPhotoViewVisibility(int index, boolean visible){
|
public void setPhotoViewVisibility(int index, boolean visible){
|
||||||
ImageStatusDisplayItem.Holder<?> holder=findPhotoViewHolder(index);
|
MediaAttachmentViewController holder=findPhotoViewHolder(index);
|
||||||
if(holder!=null)
|
if(holder!=null)
|
||||||
holder.photo.setAlpha(visible ? 1f : 0f);
|
holder.photo.setAlpha(visible ? 1f : 0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
|
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
|
||||||
ImageStatusDisplayItem.Holder<?> holder=findPhotoViewHolder(index);
|
MediaAttachmentViewController holder=findPhotoViewHolder(index);
|
||||||
if(holder!=null){
|
if(holder!=null){
|
||||||
transitioningHolder=holder;
|
transitioningHolder=holder;
|
||||||
View view=transitioningHolder.photo;
|
View view=transitioningHolder.photo;
|
||||||
@@ -210,7 +212,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
view.getLocationOnScreen(pos);
|
view.getLocationOnScreen(pos);
|
||||||
outRect.set(pos[0], pos[1], pos[0]+view.getWidth(), pos[1]+view.getHeight());
|
outRect.set(pos[0], pos[1], pos[0]+view.getWidth(), pos[1]+view.getHeight());
|
||||||
list.setClipChildren(false);
|
list.setClipChildren(false);
|
||||||
transitioningHolder.itemView.setElevation(1f);
|
gridHolder.setClipChildren(false);
|
||||||
|
transitioningHolder.view.setElevation(1f);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -237,15 +240,16 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
view.setTranslationY(0f);
|
view.setTranslationY(0f);
|
||||||
view.setScaleX(1f);
|
view.setScaleX(1f);
|
||||||
view.setScaleY(1f);
|
view.setScaleY(1f);
|
||||||
transitioningHolder.itemView.setElevation(0f);
|
transitioningHolder.view.setElevation(0f);
|
||||||
if(list!=null)
|
if(list!=null)
|
||||||
list.setClipChildren(true);
|
list.setClipChildren(true);
|
||||||
|
gridHolder.setClipChildren(true);
|
||||||
transitioningHolder=null;
|
transitioningHolder=null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Drawable getPhotoViewCurrentDrawable(int index){
|
public Drawable getPhotoViewCurrentDrawable(int index){
|
||||||
ImageStatusDisplayItem.Holder<?> holder=findPhotoViewHolder(index);
|
MediaAttachmentViewController holder=findPhotoViewHolder(index);
|
||||||
if(holder!=null)
|
if(holder!=null)
|
||||||
return holder.photo.getDrawable();
|
return holder.photo.getDrawable();
|
||||||
return null;
|
return null;
|
||||||
@@ -261,23 +265,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
requestPermissions(permissions, PhotoViewer.PERMISSION_REQUEST);
|
requestPermissions(permissions, PhotoViewer.PERMISSION_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImageStatusDisplayItem.Holder<?> findPhotoViewHolder(int index){
|
private MediaAttachmentViewController findPhotoViewHolder(int index){
|
||||||
if(list==null)
|
return gridHolder.getViewController(index);
|
||||||
return null;
|
|
||||||
int offset=0;
|
|
||||||
for(StatusDisplayItem item:displayItems){
|
|
||||||
if(item.parentID.equals(parentID)){
|
|
||||||
if(item instanceof ImageStatusDisplayItem){
|
|
||||||
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(getMainAdapterOffset()+offset+index);
|
|
||||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
|
|
||||||
return imgHolder;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
offset++;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -285,11 +274,42 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
fab=view.findViewById(R.id.fab);
|
||||||
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
||||||
@Override
|
@Override
|
||||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
||||||
if(currentPhotoViewer!=null)
|
if(currentPhotoViewer!=null)
|
||||||
currentPhotoViewer.offsetView(-dx, -dy);
|
currentPhotoViewer.offsetView(-dx, -dy);
|
||||||
|
|
||||||
|
if (fab!=null && GlobalUserPreferences.autoHideFab) {
|
||||||
|
if (dy > 0 && fab.getVisibility() == View.VISIBLE) {
|
||||||
|
TranslateAnimation animate = new TranslateAnimation(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
fab.getHeight() * 2);
|
||||||
|
animate.setDuration(300);
|
||||||
|
animate.setFillAfter(true);
|
||||||
|
fab.startAnimation(animate);
|
||||||
|
fab.setVisibility(View.INVISIBLE);
|
||||||
|
scrollDiff = 0;
|
||||||
|
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) {
|
||||||
|
if (list.getChildLayoutPosition(list.getChildAt(0)) == 0 || scrollDiff > 400) {
|
||||||
|
fab.setVisibility(View.VISIBLE);
|
||||||
|
TranslateAnimation animate = new TranslateAnimation(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
fab.getHeight() * 2,
|
||||||
|
0);
|
||||||
|
animate.setDuration(300);
|
||||||
|
animate.setFillAfter(true);
|
||||||
|
fab.startAnimation(animate);
|
||||||
|
scrollDiff = 0;
|
||||||
|
} else {
|
||||||
|
scrollDiff += Math.abs(dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
list.addItemDecoration(new StatusListItemDecoration());
|
list.addItemDecoration(new StatusListItemDecoration());
|
||||||
@@ -327,38 +347,12 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
updateToolbar();
|
updateToolbar();
|
||||||
|
|
||||||
if (withComposeButton()) {
|
if (withComposeButton()) {
|
||||||
fab = view.findViewById(R.id.fab);
|
|
||||||
fab.setVisibility(View.VISIBLE);
|
fab.setVisibility(View.VISIBLE);
|
||||||
fab.setOnClickListener(this::onFabClick);
|
fab.setOnClickListener(this::onFabClick);
|
||||||
fab.setOnLongClickListener(this::onFabLongClick);
|
fab.setOnLongClickListener(this::onFabLongClick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected RecyclerView.LayoutManager onCreateLayoutManager(){
|
|
||||||
GridLayoutManager lm=new TileGridLayoutManager(getActivity(), 1000);
|
|
||||||
lm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup(){
|
|
||||||
@Override
|
|
||||||
public int getSpanSize(int position){
|
|
||||||
position-=getMainAdapterOffset();
|
|
||||||
if(position>=0 && position<displayItems.size()){
|
|
||||||
StatusDisplayItem item=displayItems.get(position);
|
|
||||||
if(item instanceof ImageStatusDisplayItem imgItem){
|
|
||||||
PhotoLayoutHelper.TiledLayoutResult layout=imgItem.tiledLayout;
|
|
||||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgItem.thisTile;
|
|
||||||
int spans=0;
|
|
||||||
for(int i=0;i<tile.colSpan;i++){
|
|
||||||
spans+=layout.columnSizes[tile.startCol+i];
|
|
||||||
}
|
|
||||||
return spans;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1000;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return lm;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigurationChanged(Configuration newConfig){
|
public void onConfigurationChanged(Configuration newConfig){
|
||||||
super.onConfigurationChanged(newConfig);
|
super.onConfigurationChanged(newConfig);
|
||||||
@@ -477,7 +471,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
revealSpoiler(status, holder.getItemID());
|
revealSpoiler(status, holder.getItemID());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onRevealSpoilerClick(ImageStatusDisplayItem.Holder<?> holder){
|
public void onRevealSpoilerClick(MediaGridStatusDisplayItem.Holder holder){
|
||||||
Status status=holder.getItem().status;
|
Status status=holder.getItem().status;
|
||||||
revealSpoiler(status, holder.getItemID());
|
revealSpoiler(status, holder.getItemID());
|
||||||
}
|
}
|
||||||
@@ -525,13 +519,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
|
|
||||||
protected void updateImagesSpoilerState(Status status, String itemID){
|
protected void updateImagesSpoilerState(Status status, String itemID){
|
||||||
ArrayList<Integer> updatedPositions=new ArrayList<>();
|
ArrayList<Integer> updatedPositions=new ArrayList<>();
|
||||||
for(ImageStatusDisplayItem.Holder photo:(List<ImageStatusDisplayItem.Holder>)findAllHoldersOfType(itemID, ImageStatusDisplayItem.Holder.class)){
|
MediaGridStatusDisplayItem.Holder mediaGrid=findHolderOfType(itemID, MediaGridStatusDisplayItem.Holder.class);
|
||||||
photo.setRevealed(status.spoilerRevealed);
|
if(mediaGrid!=null){
|
||||||
updatedPositions.add(photo.getAbsoluteAdapterPosition()-getMainAdapterOffset());
|
mediaGrid.setRevealed(status.spoilerRevealed);
|
||||||
|
updatedPositions.add(mediaGrid.getAbsoluteAdapterPosition()-getMainAdapterOffset());
|
||||||
}
|
}
|
||||||
int i=0;
|
int i=0;
|
||||||
for(StatusDisplayItem item:displayItems){
|
for(StatusDisplayItem item:displayItems){
|
||||||
if(itemID.equals(item.parentID) && item instanceof ImageStatusDisplayItem && !updatedPositions.contains(i)){
|
if(itemID.equals(item.parentID) && item instanceof MediaGridStatusDisplayItem && !updatedPositions.contains(i)){
|
||||||
adapter.notifyItemChanged(i);
|
adapter.notifyItemChanged(i);
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
@@ -674,6 +669,15 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
return UiUtils.pickAccountForCompose(getActivity(), accountID);
|
return UiUtils.pickAccountForCompose(getActivity(), accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MediaAttachmentViewController makeNewMediaAttachmentView(MediaGridStatusDisplayItem.GridItemType type){
|
||||||
|
return new MediaAttachmentViewController(getActivity(), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> getAttachmentViewsPool(){
|
||||||
|
return attachmentViewsPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
||||||
|
|
||||||
public DisplayItemsAdapter(){
|
public DisplayItemsAdapter(){
|
||||||
@@ -711,16 +715,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||||
return displayItems.get(position).getImageRequest(image);
|
return displayItems.get(position).getImageRequest(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public void onViewDetachedFromWindow(@NonNull BindableViewHolder<StatusDisplayItem> holder){
|
|
||||||
// if(holder instanceof ImageLoaderViewHolder){
|
|
||||||
// int count=holder.getItem().getImageCount();
|
|
||||||
// for(int i=0;i<count;i++){
|
|
||||||
// ((ImageLoaderViewHolder) holder).clearImage(i);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class StatusListItemDecoration extends RecyclerView.ItemDecoration{
|
private class StatusListItemDecoration extends RecyclerView.ItemDecoration{
|
||||||
@@ -754,25 +748,21 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
for(int i=0;i<parent.getChildCount();i++){
|
for(int i=0;i<parent.getChildCount();i++){
|
||||||
View child=parent.getChildAt(i);
|
View child=parent.getChildAt(i);
|
||||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
|
if(holder instanceof MediaGridStatusDisplayItem.Holder imgHolder){
|
||||||
if(!imgHolder.getItem().status.spoilerRevealed && TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
|
if(!imgHolder.getItem().status.spoilerRevealed && TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
|
||||||
hiddenMediaPaint.setColor(0x80000000);
|
hiddenMediaPaint.setColor(0x80000000);
|
||||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgHolder.getItem().thisTile;
|
c.drawRect(child.getX(), child.getY(), child.getX()+child.getWidth(), child.getY()+child.getHeight(), hiddenMediaPaint);
|
||||||
float hGap=tile.startCol>0 ? V.dp(1) : 0;
|
|
||||||
float vGap=tile.startRow>0 ? V.dp(1) : 0;
|
|
||||||
c.drawRect(child.getX()-hGap, child.getY()-vGap, child.getX()+child.getWidth(), child.getY()+child.getHeight(), hiddenMediaPaint);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(int i=0;i<parent.getChildCount();i++){
|
for(int i=0;i<parent.getChildCount();i++){
|
||||||
View child=parent.getChildAt(i);
|
View child=parent.getChildAt(i);
|
||||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
|
if(holder instanceof MediaGridStatusDisplayItem.Holder imgHolder){
|
||||||
if(!imgHolder.getItem().status.spoilerRevealed){
|
if(!imgHolder.getItem().status.spoilerRevealed){
|
||||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgHolder.getItem().thisTile;
|
if(TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
|
||||||
if(tile.startCol==0 && tile.startRow==0 && TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
|
|
||||||
int listWidth=getListWidthForMediaLayout();
|
int listWidth=getListWidthForMediaLayout();
|
||||||
int width=Math.min(listWidth, V.dp(ImageAttachmentFrameLayout.MAX_WIDTH));
|
int width=Math.min(listWidth, V.dp(MediaGridLayout.MAX_WIDTH));
|
||||||
if(currentMediaHiddenLayoutsWidth!=width)
|
if(currentMediaHiddenLayoutsWidth!=width)
|
||||||
rebuildMediaHiddenLayouts(width-V.dp(32));
|
rebuildMediaHiddenLayouts(width-V.dp(32));
|
||||||
c.save();
|
c.save();
|
||||||
@@ -797,47 +787,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
|
||||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
|
||||||
if(holder instanceof ImageStatusDisplayItem.Holder){
|
|
||||||
int listWidth=getListWidthForMediaLayout();
|
|
||||||
int width=Math.min(listWidth, V.dp(ImageAttachmentFrameLayout.MAX_WIDTH));
|
|
||||||
PhotoLayoutHelper.TiledLayoutResult layout=((ImageStatusDisplayItem.Holder<?>) holder).getItem().tiledLayout;
|
|
||||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=((ImageStatusDisplayItem.Holder<?>) holder).getItem().thisTile;
|
|
||||||
if(tile.startCol+tile.colSpan<layout.columnSizes.length){
|
|
||||||
outRect.right=V.dp(1);
|
|
||||||
}
|
|
||||||
if(tile.startRow+tile.rowSpan<layout.rowSizes.length){
|
|
||||||
outRect.bottom=V.dp(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For a view that spans rows, compensate its additional height so the row it's in stays the right height
|
|
||||||
if(tile.rowSpan>1){
|
|
||||||
outRect.bottom=-(Math.round(tile.height/1000f*width)-Math.round(layout.rowSizes[tile.startRow]/1000f*width));
|
|
||||||
}
|
|
||||||
// ...and for its siblings, offset those on rows below first to the right where they belong
|
|
||||||
if(tile.startCol>0 && layout.tiles[0].rowSpan>1 && tile.startRow>layout.tiles[0].startRow){
|
|
||||||
int xOffset=Math.round(layout.tiles[0].width/1000f*listWidth);
|
|
||||||
outRect.left=xOffset;
|
|
||||||
outRect.right=-xOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the width of the media block is smaller than that of the RecyclerView, offset the views horizontally to center them
|
|
||||||
if(listWidth>width){
|
|
||||||
outRect.left+=(listWidth-V.dp(ImageAttachmentFrameLayout.MAX_WIDTH))/2;
|
|
||||||
if(tile.startCol>0){
|
|
||||||
int spanOffset=0;
|
|
||||||
for(int i=0;i<tile.startCol;i++){
|
|
||||||
spanOffset+=layout.columnSizes[i];
|
|
||||||
}
|
|
||||||
outRect.left-=Math.round(spanOffset/1000f*listWidth);
|
|
||||||
outRect.left+=Math.round(spanOffset/1000f*width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void rebuildMediaHiddenLayouts(int width){
|
private void rebuildMediaHiddenLayouts(int width){
|
||||||
currentMediaHiddenLayoutsWidth=width;
|
currentMediaHiddenLayoutsWidth=width;
|
||||||
String title=getString(R.string.sensitive_content);
|
String title=getString(R.string.sensitive_content);
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ import org.joinmastodon.android.ui.text.ComposeAutocompleteSpan;
|
|||||||
import org.joinmastodon.android.ui.text.ComposeHashtagOrMentionSpan;
|
import org.joinmastodon.android.ui.text.ComposeHashtagOrMentionSpan;
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||||
import org.joinmastodon.android.ui.utils.TransferSpeedTracker;
|
import org.joinmastodon.android.utils.TransferSpeedTracker;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.ComposeEditText;
|
import org.joinmastodon.android.ui.views.ComposeEditText;
|
||||||
import org.joinmastodon.android.ui.views.ComposeMediaLayout;
|
import org.joinmastodon.android.ui.views.ComposeMediaLayout;
|
||||||
@@ -971,9 +971,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onCustomEmojiClick(Emoji emoji){
|
private void onCustomEmojiClick(Emoji emoji){
|
||||||
int start=mainEditText.getSelectionStart();
|
if(getActivity().getCurrentFocus() instanceof EditText edit){
|
||||||
String prefix=start>0 && !Character.isWhitespace(mainEditText.getText().charAt(start-1)) ? " :" : ":";
|
int start=edit.getSelectionStart();
|
||||||
mainEditText.getText().replace(start, mainEditText.getSelectionEnd(), prefix+emoji.shortcode+':');
|
String prefix=start>0 && !Character.isWhitespace(edit.getText().charAt(start-1)) ? " :" : ":";
|
||||||
|
edit.getText().replace(start, edit.getSelectionEnd(), prefix+emoji.shortcode+':');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -358,7 +358,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
addListsToOverflowMenu();
|
addListsToOverflowMenu();
|
||||||
addHashtagsToOverflowMenu();
|
addHashtagsToOverflowMenu();
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !UiUtils.isEMUI()) {
|
||||||
m.setGroupDividerEnabled(true);
|
m.setGroupDividerEnabled(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,7 @@ import org.joinmastodon.android.model.Notification;
|
|||||||
import org.joinmastodon.android.model.PaginatedResponse;
|
import org.joinmastodon.android.model.PaginatedResponse;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
|
||||||
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.utils.DiscoverInfoBannerHelper;
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
@@ -40,7 +38,6 @@ import java.util.stream.Stream;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
|
|
||||||
public class NotificationsListFragment extends BaseStatusListFragment<Notification>{
|
public class NotificationsListFragment extends BaseStatusListFragment<Notification>{
|
||||||
private boolean onlyMentions;
|
private boolean onlyMentions;
|
||||||
@@ -97,14 +94,9 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
};
|
};
|
||||||
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, n.status, extraText, n, null) : null;
|
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, n.status, extraText, n, null) : null;
|
||||||
if(n.status!=null){
|
if(n.status!=null){
|
||||||
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n, false, Filter.FilterContext.NOTIFICATIONS, titleItem);
|
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n, false, Filter.FilterContext.NOTIFICATIONS);
|
||||||
if(titleItem!=null){
|
if(titleItem!=null)
|
||||||
for(StatusDisplayItem item:items){
|
items.add(0, titleItem);
|
||||||
if(item instanceof ImageStatusDisplayItem imgItem){
|
|
||||||
imgItem.horizontalInset=V.dp(32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return items;
|
return items;
|
||||||
}else if(titleItem!=null){
|
}else if(titleItem!=null){
|
||||||
AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this,
|
AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this,
|
||||||
@@ -125,6 +117,8 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
knownAccounts.put(s.account.id, s.account);
|
knownAccounts.put(s.account.id, s.account);
|
||||||
if(s.status!=null && !knownAccounts.containsKey(s.status.account.id))
|
if(s.status!=null && !knownAccounts.containsKey(s.status.account.id))
|
||||||
knownAccounts.put(s.status.account.id, s.status.account);
|
knownAccounts.put(s.status.account.id, s.status.account);
|
||||||
|
if(s.status!=null && s.status.reblog!=null && !knownAccounts.containsKey(s.status.reblog.account.id))
|
||||||
|
knownAccounts.put(s.status.reblog.account.id, s.status.reblog.account);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,24 +19,30 @@ import android.os.Bundle;
|
|||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.style.ImageSpan;
|
import android.text.style.ImageSpan;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.SubMenu;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewOutlineProvider;
|
import android.view.ViewOutlineProvider;
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.view.animation.TranslateAnimation;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
@@ -142,10 +148,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
private Uri editNewAvatar, editNewCover;
|
private Uri editNewAvatar, editNewCover;
|
||||||
private String profileAccountID;
|
private String profileAccountID;
|
||||||
private boolean refreshing;
|
private boolean refreshing;
|
||||||
private View fab;
|
private ImageButton fab;
|
||||||
private WindowInsets childInsets;
|
private WindowInsets childInsets;
|
||||||
private PhotoViewer currentPhotoViewer;
|
private PhotoViewer currentPhotoViewer;
|
||||||
private boolean editModeLoading;
|
private boolean editModeLoading;
|
||||||
|
protected int scrollDiff = 0;
|
||||||
|
|
||||||
private static final int MAX_FIELDS=4;
|
private static final int MAX_FIELDS=4;
|
||||||
|
|
||||||
@@ -298,6 +305,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
});
|
});
|
||||||
|
|
||||||
actionButton.setOnClickListener(this::onActionButtonClick);
|
actionButton.setOnClickListener(this::onActionButtonClick);
|
||||||
|
actionButton.setOnLongClickListener(this::onActionButtonLongClick);
|
||||||
notifyButton.setOnClickListener(this::onNotifyButtonClick);
|
notifyButton.setOnClickListener(this::onNotifyButtonClick);
|
||||||
avatar.setOnClickListener(this::onAvatarClick);
|
avatar.setOnClickListener(this::onAvatarClick);
|
||||||
cover.setOnClickListener(this::onCoverClick);
|
cover.setOnClickListener(this::onCoverClick);
|
||||||
@@ -489,8 +497,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
for (Account.Role role : account.roles) {
|
for (Account.Role role : account.roles) {
|
||||||
TextView roleText = new TextView(getActivity(), null, 0, R.style.role_label);
|
TextView roleText = new TextView(getActivity(), null, 0, R.style.role_label);
|
||||||
roleText.setText(role.name);
|
roleText.setText(role.name);
|
||||||
|
if (!TextUtils.isEmpty(role.color) && role.color.startsWith("#")) try {
|
||||||
GradientDrawable bg = (GradientDrawable) roleText.getBackground().mutate();
|
GradientDrawable bg = (GradientDrawable) roleText.getBackground().mutate();
|
||||||
bg.setStroke(V.dp(2), Color.parseColor(role.color));
|
bg.setStroke(V.dp(2), Color.parseColor(role.color));
|
||||||
|
} catch (Exception ignored) {}
|
||||||
rolesView.addView(roleText);
|
rolesView.addView(roleText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -599,6 +609,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
return;
|
return;
|
||||||
inflater.inflate(isOwnProfile ? R.menu.profile_own : R.menu.profile, menu);
|
inflater.inflate(isOwnProfile ? R.menu.profile_own : R.menu.profile, menu);
|
||||||
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.bookmarks, R.id.followed_hashtags);
|
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.bookmarks, R.id.followed_hashtags);
|
||||||
|
boolean hasMultipleAccounts = AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1;
|
||||||
|
MenuItem openWithAccounts = menu.findItem(R.id.open_with_account);
|
||||||
|
openWithAccounts.setVisible(hasMultipleAccounts);
|
||||||
|
SubMenu accountsMenu = openWithAccounts.getSubMenu();
|
||||||
|
if (hasMultipleAccounts) {
|
||||||
|
accountsMenu.clear();
|
||||||
|
UiUtils.populateAccountsMenu(accountID, accountsMenu, s-> UiUtils.openURL(
|
||||||
|
getActivity(), s.getID(), account.url, false
|
||||||
|
));
|
||||||
|
}
|
||||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getShortUsername()));
|
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getShortUsername()));
|
||||||
if(isOwnProfile)
|
if(isOwnProfile)
|
||||||
return;
|
return;
|
||||||
@@ -734,6 +754,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username));
|
notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ImageButton getFab() {
|
||||||
|
return fab;
|
||||||
|
}
|
||||||
|
|
||||||
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
||||||
int topBarsH=getToolbar().getHeight()+statusBarHeight;
|
int topBarsH=getToolbar().getHeight()+statusBarHeight;
|
||||||
if(scrollY>avatarBorder.getTop()-topBarsH){
|
if(scrollY>avatarBorder.getTop()-topBarsH){
|
||||||
@@ -764,6 +788,37 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
if(currentPhotoViewer!=null){
|
if(currentPhotoViewer!=null){
|
||||||
currentPhotoViewer.offsetView(0, oldScrollY-scrollY);
|
currentPhotoViewer.offsetView(0, oldScrollY-scrollY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (GlobalUserPreferences.autoHideFab) {
|
||||||
|
int dy = scrollY - oldScrollY;
|
||||||
|
if (dy > 0 && fab.getVisibility() == View.VISIBLE) {
|
||||||
|
TranslateAnimation animate = new TranslateAnimation(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
fab.getHeight() * 2);
|
||||||
|
animate.setDuration(300);
|
||||||
|
animate.setFillAfter(true);
|
||||||
|
fab.startAnimation(animate);
|
||||||
|
fab.setVisibility(View.INVISIBLE);
|
||||||
|
scrollDiff = 0;
|
||||||
|
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) {
|
||||||
|
if (v.getScrollY() == 0 || scrollDiff > 400) {
|
||||||
|
fab.setVisibility(View.VISIBLE);
|
||||||
|
TranslateAnimation animate = new TranslateAnimation(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
fab.getHeight() * 2,
|
||||||
|
0);
|
||||||
|
animate.setDuration(300);
|
||||||
|
animate.setFillAfter(true);
|
||||||
|
fab.startAnimation(animate);
|
||||||
|
scrollDiff = 0;
|
||||||
|
} else {
|
||||||
|
scrollDiff += Math.abs(dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Fragment getFragmentForPage(int page){
|
private Fragment getFragmentForPage(int page){
|
||||||
@@ -792,6 +847,31 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean onActionButtonLongClick(View v) {
|
||||||
|
if (isOwnProfile || AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false;
|
||||||
|
UiUtils.pickAccount(getActivity(), accountID, R.string.sk_follow_as, R.drawable.ic_fluent_person_add_28_regular, session -> {
|
||||||
|
UiUtils.lookupAccount(getActivity(), account, session.getID(), accountID, acc -> {
|
||||||
|
if (acc == null) return;
|
||||||
|
new SetAccountFollowed(acc.id, true, true).setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Relationship relationship) {
|
||||||
|
Toast.makeText(
|
||||||
|
getActivity(),
|
||||||
|
getString(R.string.sk_followed_as, session.self.getShortUsername()),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(getActivity());
|
||||||
|
}
|
||||||
|
}).exec(session.getID());
|
||||||
|
});
|
||||||
|
}, null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void setActionProgressVisible(boolean visible){
|
private void setActionProgressVisible(boolean visible){
|
||||||
actionButton.setTextVisible(!visible);
|
actionButton.setTextVisible(!visible);
|
||||||
actionProgress.setVisibility(visible ? View.VISIBLE : View.GONE);
|
actionProgress.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||||
|
|||||||
@@ -248,6 +248,11 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
needAppRestart=true;
|
needAppRestart=true;
|
||||||
}));
|
}));
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_hide_fab, R.drawable.ic_fluent_edit_24_regular, GlobalUserPreferences.autoHideFab, i->{
|
||||||
|
GlobalUserPreferences.autoHideFab=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
needAppRestart=true;
|
||||||
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_translate_only_opened, R.drawable.ic_fluent_translate_24_regular, GlobalUserPreferences.translateButtonOpenedOnly, i->{
|
items.add(new SwitchItem(R.string.sk_settings_translate_only_opened, R.drawable.ic_fluent_translate_24_regular, GlobalUserPreferences.translateButtonOpenedOnly, i->{
|
||||||
GlobalUserPreferences.translateButtonOpenedOnly=i.checked;
|
GlobalUserPreferences.translateButtonOpenedOnly=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
|||||||
protected void addAccountToKnown(Status s){
|
protected void addAccountToKnown(Status s){
|
||||||
if(!knownAccounts.containsKey(s.account.id))
|
if(!knownAccounts.containsKey(s.account.id))
|
||||||
knownAccounts.put(s.account.id, s.account);
|
knownAccounts.put(s.account.id, s.account);
|
||||||
|
if(s.reblog!=null && !knownAccounts.containsKey(s.reblog.account.id))
|
||||||
|
knownAccounts.put(s.reblog.account.id, s.reblog.account);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -22,10 +22,8 @@ import org.joinmastodon.android.events.FinishReportFragmentsEvent;
|
|||||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
|
||||||
import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
|
||||||
import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
@@ -132,22 +130,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
|||||||
if(holder.getAbsoluteAdapterPosition()==0)
|
if(holder.getAbsoluteAdapterPosition()==0)
|
||||||
return;
|
return;
|
||||||
outRect.left=V.dp(40);
|
outRect.left=V.dp(40);
|
||||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
|
if(holder instanceof AudioStatusDisplayItem.Holder){
|
||||||
PhotoLayoutHelper.TiledLayoutResult layout=imgHolder.getItem().tiledLayout;
|
|
||||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgHolder.getItem().thisTile;
|
|
||||||
String siblingID;
|
|
||||||
if(holder.getAbsoluteAdapterPosition()<parent.getAdapter().getItemCount()-1){
|
|
||||||
siblingID=displayItems.get(holder.getAbsoluteAdapterPosition()-getMainAdapterOffset()+1).parentID;
|
|
||||||
}else{
|
|
||||||
siblingID=null;
|
|
||||||
}
|
|
||||||
if(tile.startCol>0)
|
|
||||||
outRect.left=0;
|
|
||||||
outRect.left+=V.dp(16);
|
|
||||||
outRect.right=V.dp(16);
|
|
||||||
if(!imgHolder.getItemID().equals(siblingID) || tile.startRow+tile.rowSpan==layout.rowSizes.length)
|
|
||||||
outRect.bottom=V.dp(16);
|
|
||||||
}else if(holder instanceof AudioStatusDisplayItem.Holder){
|
|
||||||
outRect.bottom=V.dp(16);
|
outRect.bottom=V.dp(16);
|
||||||
}else if(holder instanceof LinkCardStatusDisplayItem.Holder){
|
}else if(holder instanceof LinkCardStatusDisplayItem.Holder){
|
||||||
outRect.bottom=V.dp(16);
|
outRect.bottom=V.dp(16);
|
||||||
@@ -166,10 +149,6 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
|||||||
parent.getDecoratedBoundsWithMargins(child, tmpRect);
|
parent.getDecoratedBoundsWithMargins(child, tmpRect);
|
||||||
String id=sdiHolder.getItemID();
|
String id=sdiHolder.getItemID();
|
||||||
int height=tmpRect.height();
|
int height=tmpRect.height();
|
||||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
|
|
||||||
if(imgHolder.getItem().thisTile.startCol+imgHolder.getItem().thisTile.colSpan<imgHolder.getItem().tiledLayout.columnSizes.length)
|
|
||||||
height=0;
|
|
||||||
}
|
|
||||||
if(!(holder instanceof HeaderStatusDisplayItem.Holder) && !(holder instanceof ReblogOrReplyLineStatusDisplayItem.Holder))
|
if(!(holder instanceof HeaderStatusDisplayItem.Holder) && !(holder instanceof ReblogOrReplyLineStatusDisplayItem.Holder))
|
||||||
postsWithKnownNonHeaderHeights.add(id);
|
postsWithKnownNonHeaderHeights.add(id);
|
||||||
knownDisplayItemHeights.put(holder.getAbsoluteAdapterPosition(), height);
|
knownDisplayItemHeights.put(holder.getAbsoluteAdapterPosition(), height);
|
||||||
@@ -236,17 +215,6 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
|||||||
return adapter;
|
return adapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
|
||||||
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null);
|
|
||||||
for(StatusDisplayItem item:items){
|
|
||||||
if(item instanceof ImageStatusDisplayItem isdi){
|
|
||||||
isdi.horizontalInset=V.dp(40+32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void drawDivider(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder, RecyclerView parent, Canvas c, Paint paint){
|
protected void drawDivider(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder, RecyclerView parent, Canvas c, Paint paint){
|
||||||
parent.getDecoratedBoundsWithMargins(child, tmpRect);
|
parent.getDecoratedBoundsWithMargins(child, tmpRect);
|
||||||
tmpRect.offset(0, Math.round(child.getTranslationY()));
|
tmpRect.offset(0, Math.round(child.getTranslationY()));
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import java.util.List;
|
|||||||
* Represents a user of Mastodon and their associated profile.
|
* Represents a user of Mastodon and their associated profile.
|
||||||
*/
|
*/
|
||||||
@Parcel
|
@Parcel
|
||||||
public class Account extends BaseModel{
|
public class Account extends BaseModel implements Searchable{
|
||||||
// Base attributes
|
// Base attributes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -135,6 +135,11 @@ public class Account extends BaseModel{
|
|||||||
|
|
||||||
public List<Role> roles;
|
public List<Role> roles;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getQuery() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
@Parcel
|
@Parcel
|
||||||
public static class Role {
|
public static class Role {
|
||||||
public String name;
|
public String name;
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ public class Instance extends BaseModel{
|
|||||||
ci.domain=uri;
|
ci.domain=uri;
|
||||||
ci.normalizedDomain=IDN.toUnicode(uri);
|
ci.normalizedDomain=IDN.toUnicode(uri);
|
||||||
ci.description=Html.fromHtml(shortDescription).toString().trim();
|
ci.description=Html.fromHtml(shortDescription).toString().trim();
|
||||||
if(languages!=null){
|
if(languages!=null && languages.size() > 0){
|
||||||
ci.language=languages.get(0);
|
ci.language=languages.get(0);
|
||||||
ci.languages=languages;
|
ci.languages=languages;
|
||||||
}else{
|
}else{
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public class Poll extends BaseModel{
|
|||||||
private boolean expired;
|
private boolean expired;
|
||||||
public boolean multiple;
|
public boolean multiple;
|
||||||
public int votersCount;
|
public int votersCount;
|
||||||
|
public int votesCount;
|
||||||
public boolean voted;
|
public boolean voted;
|
||||||
@RequiredField
|
@RequiredField
|
||||||
public List<Integer> ownVotes;
|
public List<Integer> ownVotes;
|
||||||
@@ -41,10 +42,12 @@ public class Poll extends BaseModel{
|
|||||||
", expired="+expired+
|
", expired="+expired+
|
||||||
", multiple="+multiple+
|
", multiple="+multiple+
|
||||||
", votersCount="+votersCount+
|
", votersCount="+votersCount+
|
||||||
|
", votesCount="+votesCount+
|
||||||
", voted="+voted+
|
", voted="+voted+
|
||||||
", ownVotes="+ownVotes+
|
", ownVotes="+ownVotes+
|
||||||
", options="+options+
|
", options="+options+
|
||||||
", emojis="+emojis+
|
", emojis="+emojis+
|
||||||
|
", selectedOptions="+selectedOptions+
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package org.joinmastodon.android.model;
|
||||||
|
|
||||||
|
public interface Searchable {
|
||||||
|
String getQuery();
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import java.time.Instant;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Parcel
|
@Parcel
|
||||||
public class Status extends BaseModel implements DisplayItemsParent{
|
public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
||||||
@RequiredField
|
@RequiredField
|
||||||
public String id;
|
public String id;
|
||||||
@RequiredField
|
@RequiredField
|
||||||
@@ -163,4 +163,9 @@ public class Status extends BaseModel implements DisplayItemsParent{
|
|||||||
s.filtered = List.of();
|
s.filtered = List.of();
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getQuery() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,14 @@ import java.util.List;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
public class PhotoLayoutHelper{
|
public class PhotoLayoutHelper{
|
||||||
|
public static final int MAX_WIDTH=1000;
|
||||||
|
public static final int MAX_HEIGHT=1910;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static TiledLayoutResult processThumbs(int _maxW, int _maxH, List<Attachment> thumbs){
|
public static TiledLayoutResult processThumbs(List<Attachment> thumbs){
|
||||||
|
int _maxW=MAX_WIDTH;
|
||||||
|
int _maxH=MAX_HEIGHT;
|
||||||
|
|
||||||
TiledLayoutResult result=new TiledLayoutResult();
|
TiledLayoutResult result=new TiledLayoutResult();
|
||||||
if(thumbs.size()==1){
|
if(thumbs.size()==1){
|
||||||
Attachment att=thumbs.get(0);
|
Attachment att=thumbs.get(0);
|
||||||
@@ -45,13 +51,8 @@ public class PhotoLayoutHelper{
|
|||||||
float avgRatio=!ratios.isEmpty() ? sum(ratios)/ratios.size() : 1.0f;
|
float avgRatio=!ratios.isEmpty() ? sum(ratios)/ratios.size() : 1.0f;
|
||||||
|
|
||||||
float maxW, maxH, marginW=0, marginH=0;
|
float maxW, maxH, marginW=0, marginH=0;
|
||||||
if(_maxW>0){
|
|
||||||
maxW=_maxW;
|
maxW=_maxW;
|
||||||
maxH=_maxH;
|
maxH=_maxH;
|
||||||
}else{
|
|
||||||
maxW=510;
|
|
||||||
maxH=510;
|
|
||||||
}
|
|
||||||
|
|
||||||
float maxRatio=maxW/maxH;
|
float maxRatio=maxW/maxH;
|
||||||
|
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
String accountID = session.getID();
|
String accountID = session.getID();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
UiUtils.lookupStatus(v.getContext(), item.status, accountID, item.accountID, status -> {
|
UiUtils.lookupStatus(v.getContext(), item.status, accountID, item.accountID, status -> {
|
||||||
|
if (status == null) return;
|
||||||
args.putParcelable("replyTo", Parcels.wrap(status));
|
args.putParcelable("replyTo", Parcels.wrap(status));
|
||||||
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
package org.joinmastodon.android.ui.displayitems;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.graphics.Outline;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.ViewOutlineProvider;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
|
||||||
import org.joinmastodon.android.model.Attachment;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
|
||||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
|
||||||
|
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
|
||||||
|
|
||||||
public class GifVStatusDisplayItem extends ImageStatusDisplayItem{
|
|
||||||
public GifVStatusDisplayItem(String parentID, Status status, Attachment attachment, BaseStatusListFragment parentFragment, int index, int totalPhotos, PhotoLayoutHelper.TiledLayoutResult tiledLayout, PhotoLayoutHelper.TiledLayoutResult.Tile thisTile){
|
|
||||||
super(parentID, parentFragment, attachment, status, index, totalPhotos, tiledLayout, thisTile);
|
|
||||||
request=new UrlImageLoaderRequest(attachment.previewUrl, 1000, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type getType(){
|
|
||||||
return Type.GIFV;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Holder extends ImageStatusDisplayItem.Holder<GifVStatusDisplayItem>{
|
|
||||||
|
|
||||||
public Holder(Activity activity, ViewGroup parent){
|
|
||||||
super(activity, R.layout.display_item_gifv, parent);
|
|
||||||
View play=findViewById(R.id.play_button);
|
|
||||||
play.setOutlineProvider(new ViewOutlineProvider(){
|
|
||||||
@Override
|
|
||||||
public void getOutline(View view, Outline outline){
|
|
||||||
outline.setOval(0, 0, view.getWidth(), view.getHeight());
|
|
||||||
outline.setAlpha(.99f); // fixes shadow rendering
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -455,7 +455,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
if (hasMultipleAccounts && accountsMenu != null) {
|
if (hasMultipleAccounts && accountsMenu != null) {
|
||||||
openWithAccounts.setVisible(true);
|
openWithAccounts.setVisible(true);
|
||||||
accountsMenu.clear();
|
accountsMenu.clear();
|
||||||
populateAccountsMenu(accountsMenu);
|
UiUtils.populateAccountsMenu(item.accountID, accountsMenu, s-> UiUtils.openURL(
|
||||||
|
item.parentFragment.getActivity(), s.getID(), item.status.url, false
|
||||||
|
));
|
||||||
} else if (openWithAccounts != null) {
|
} else if (openWithAccounts != null) {
|
||||||
openWithAccounts.setVisible(false);
|
openWithAccounts.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,244 +0,0 @@
|
|||||||
package org.joinmastodon.android.ui.displayitems;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.animation.AnimatorListenerAdapter;
|
|
||||||
import android.animation.AnimatorSet;
|
|
||||||
import android.animation.ObjectAnimator;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.ViewTreeObserver;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
|
||||||
import org.joinmastodon.android.model.Attachment;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
|
||||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
|
||||||
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
|
||||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
|
||||||
import org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout;
|
|
||||||
|
|
||||||
import androidx.annotation.LayoutRes;
|
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
|
||||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
|
||||||
|
|
||||||
public abstract class ImageStatusDisplayItem extends StatusDisplayItem{
|
|
||||||
public final int index;
|
|
||||||
public final int totalPhotos;
|
|
||||||
protected Attachment attachment;
|
|
||||||
protected ImageLoaderRequest request;
|
|
||||||
public final Status status;
|
|
||||||
public final PhotoLayoutHelper.TiledLayoutResult tiledLayout;
|
|
||||||
public final PhotoLayoutHelper.TiledLayoutResult.Tile thisTile;
|
|
||||||
public int horizontalInset;
|
|
||||||
|
|
||||||
public ImageStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Attachment photo, Status status, int index, int totalPhotos, PhotoLayoutHelper.TiledLayoutResult tiledLayout, PhotoLayoutHelper.TiledLayoutResult.Tile thisTile){
|
|
||||||
super(parentID, parentFragment);
|
|
||||||
this.attachment=photo;
|
|
||||||
this.status=status;
|
|
||||||
this.index=index;
|
|
||||||
this.totalPhotos=totalPhotos;
|
|
||||||
this.tiledLayout=tiledLayout;
|
|
||||||
this.thisTile=thisTile;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getImageCount(){
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ImageLoaderRequest getImageRequest(int index){
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static abstract class Holder<T extends ImageStatusDisplayItem> extends StatusDisplayItem.Holder<T> implements ImageLoaderViewHolder{
|
|
||||||
public final ImageView photo;
|
|
||||||
private ImageAttachmentFrameLayout layout;
|
|
||||||
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
|
|
||||||
private boolean didClear;
|
|
||||||
|
|
||||||
private AnimatorSet currentAnim;
|
|
||||||
private final FrameLayout altTextWrapper;
|
|
||||||
private final TextView altTextButton;
|
|
||||||
private final ImageView noAltTextButton;
|
|
||||||
private final View altTextScroller;
|
|
||||||
private final ImageButton altTextClose;
|
|
||||||
private final TextView altText, noAltText;
|
|
||||||
|
|
||||||
private View altOrNoAltButton;
|
|
||||||
private boolean altTextShown;
|
|
||||||
|
|
||||||
public Holder(Activity activity, @LayoutRes int layout, ViewGroup parent){
|
|
||||||
super(activity, layout, parent);
|
|
||||||
photo=findViewById(R.id.photo);
|
|
||||||
photo.setOnClickListener(this::onViewClick);
|
|
||||||
this.layout=(ImageAttachmentFrameLayout)itemView;
|
|
||||||
|
|
||||||
altTextWrapper=findViewById(R.id.alt_text_wrapper);
|
|
||||||
altTextButton=findViewById(R.id.alt_button);
|
|
||||||
noAltTextButton=findViewById(R.id.no_alt_button);
|
|
||||||
altTextScroller=findViewById(R.id.alt_text_scroller);
|
|
||||||
altTextClose=findViewById(R.id.alt_text_close);
|
|
||||||
altText=findViewById(R.id.alt_text);
|
|
||||||
noAltText=findViewById(R.id.no_alt_text);
|
|
||||||
|
|
||||||
altTextButton.setOnClickListener(this::onShowHideClick);
|
|
||||||
noAltTextButton.setOnClickListener(this::onShowHideClick);
|
|
||||||
altTextClose.setOnClickListener(this::onShowHideClick);
|
|
||||||
// altTextScroller.setNestedScrollingEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBind(ImageStatusDisplayItem item){
|
|
||||||
layout.setLayout(item.tiledLayout, item.thisTile, item.horizontalInset);
|
|
||||||
crossfadeDrawable.setSize(item.attachment.getWidth(), item.attachment.getHeight());
|
|
||||||
crossfadeDrawable.setBlurhashDrawable(item.attachment.blurhashPlaceholder);
|
|
||||||
crossfadeDrawable.setCrossfadeAlpha(item.status.spoilerRevealed ? 0f : 1f);
|
|
||||||
photo.setImageDrawable(null);
|
|
||||||
photo.setImageDrawable(crossfadeDrawable);
|
|
||||||
photo.setContentDescription(TextUtils.isEmpty(item.attachment.description) ? item.parentFragment.getString(R.string.media_no_description) : item.attachment.description);
|
|
||||||
didClear=false;
|
|
||||||
|
|
||||||
if (currentAnim != null) currentAnim.cancel();
|
|
||||||
|
|
||||||
boolean altTextMissing = TextUtils.isEmpty(item.attachment.description);
|
|
||||||
altOrNoAltButton = altTextMissing ? noAltTextButton : altTextButton;
|
|
||||||
altTextShown=false;
|
|
||||||
|
|
||||||
altTextScroller.setVisibility(View.GONE);
|
|
||||||
altTextClose.setVisibility(View.GONE);
|
|
||||||
altTextButton.setVisibility(View.VISIBLE);
|
|
||||||
noAltTextButton.setVisibility(View.VISIBLE);
|
|
||||||
altTextButton.setAlpha(1f);
|
|
||||||
noAltTextButton.setAlpha(1f);
|
|
||||||
altTextWrapper.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
if (altTextMissing){
|
|
||||||
if (GlobalUserPreferences.showNoAltIndicator) {
|
|
||||||
noAltTextButton.setVisibility(View.VISIBLE);
|
|
||||||
noAltText.setVisibility(View.VISIBLE);
|
|
||||||
altTextWrapper.setBackgroundResource(R.drawable.bg_image_no_alt_overlay);
|
|
||||||
altTextButton.setVisibility(View.GONE);
|
|
||||||
altText.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
altTextWrapper.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
if (GlobalUserPreferences.showAltIndicator) {
|
|
||||||
noAltTextButton.setVisibility(View.GONE);
|
|
||||||
noAltText.setVisibility(View.GONE);
|
|
||||||
altTextWrapper.setBackgroundResource(R.drawable.bg_image_alt_overlay);
|
|
||||||
altTextButton.setVisibility(View.VISIBLE);
|
|
||||||
altTextButton.setText(R.string.sk_alt_button);
|
|
||||||
altText.setVisibility(View.VISIBLE);
|
|
||||||
altText.setText(item.attachment.description);
|
|
||||||
altText.setPadding(0, 0, 0, 0);
|
|
||||||
} else {
|
|
||||||
altTextWrapper.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onShowHideClick(View v){
|
|
||||||
boolean show=v.getId()==R.id.alt_button || v.getId()==R.id.no_alt_button;
|
|
||||||
|
|
||||||
if(altTextShown==show)
|
|
||||||
return;
|
|
||||||
if(currentAnim!=null)
|
|
||||||
currentAnim.cancel();
|
|
||||||
|
|
||||||
altTextShown=show;
|
|
||||||
if(show){
|
|
||||||
altTextScroller.setVisibility(View.VISIBLE);
|
|
||||||
altTextClose.setVisibility(View.VISIBLE);
|
|
||||||
}else{
|
|
||||||
altOrNoAltButton.setVisibility(View.VISIBLE);
|
|
||||||
// Hide these views temporarily so FrameLayout measures correctly
|
|
||||||
altTextScroller.setVisibility(View.GONE);
|
|
||||||
altTextClose.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the current size...
|
|
||||||
int prevLeft=altTextWrapper.getLeft();
|
|
||||||
int prevRight=altTextWrapper.getRight();
|
|
||||||
int prevTop=altTextWrapper.getTop();
|
|
||||||
altTextWrapper.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
|
||||||
@Override
|
|
||||||
public boolean onPreDraw(){
|
|
||||||
altTextWrapper.getViewTreeObserver().removeOnPreDrawListener(this);
|
|
||||||
|
|
||||||
// ...and this is after the layout pass, right now the FrameLayout has its final size, but we animate that change
|
|
||||||
if(!show){
|
|
||||||
// Show these views again so they're visible for the duration of the animation.
|
|
||||||
// No one would notice they were missing during measure/layout.
|
|
||||||
altTextScroller.setVisibility(View.VISIBLE);
|
|
||||||
altTextClose.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
AnimatorSet set=new AnimatorSet();
|
|
||||||
set.playTogether(
|
|
||||||
ObjectAnimator.ofInt(altTextWrapper, "left", prevLeft, altTextWrapper.getLeft()),
|
|
||||||
ObjectAnimator.ofInt(altTextWrapper, "right", prevRight, altTextWrapper.getRight()),
|
|
||||||
ObjectAnimator.ofInt(altTextWrapper, "top", prevTop, altTextWrapper.getTop()),
|
|
||||||
ObjectAnimator.ofFloat(altOrNoAltButton, View.ALPHA, show ? 1f : 0f, show ? 0f : 1f),
|
|
||||||
ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, show ? 0f : 1f, show ? 1f : 0f),
|
|
||||||
ObjectAnimator.ofFloat(altTextClose, View.ALPHA, show ? 0f : 1f, show ? 1f : 0f)
|
|
||||||
);
|
|
||||||
set.setDuration(300);
|
|
||||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
|
||||||
set.addListener(new AnimatorListenerAdapter(){
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animation){
|
|
||||||
if(show){
|
|
||||||
altOrNoAltButton.setVisibility(View.GONE);
|
|
||||||
}else{
|
|
||||||
altTextScroller.setVisibility(View.GONE);
|
|
||||||
altTextClose.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
currentAnim=null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
set.start();
|
|
||||||
currentAnim=set;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setImage(int index, Drawable drawable){
|
|
||||||
crossfadeDrawable.setImageDrawable(drawable);
|
|
||||||
if(didClear && item.status.spoilerRevealed)
|
|
||||||
crossfadeDrawable.animateAlpha(0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearImage(int index){
|
|
||||||
crossfadeDrawable.setCrossfadeAlpha(1f);
|
|
||||||
crossfadeDrawable.setImageDrawable(null);
|
|
||||||
didClear=true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onViewClick(View v){
|
|
||||||
if(!item.status.spoilerRevealed){
|
|
||||||
item.parentFragment.onRevealSpoilerClick(this);
|
|
||||||
}else if(item.parentFragment instanceof PhotoViewerHost){
|
|
||||||
Status contentStatus=item.status.reblog!=null ? item.status.reblog : item.status;
|
|
||||||
((PhotoViewerHost) item.parentFragment).openPhotoViewer(item.parentID, item.status, contentStatus.mediaAttachments.indexOf(item.attachment));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRevealed(boolean revealed){
|
|
||||||
crossfadeDrawable.animateAlpha(revealed ? 0f : 1f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,311 @@
|
|||||||
|
package org.joinmastodon.android.ui.displayitems;
|
||||||
|
|
||||||
|
import static org.joinmastodon.android.ui.utils.MediaAttachmentViewController.altWrapPadding;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.animation.AnimatorSet;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
|
import org.joinmastodon.android.model.Attachment;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||||
|
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
||||||
|
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
|
||||||
|
import org.joinmastodon.android.ui.views.FrameLayoutThatOnlyMeasuresFirstChild;
|
||||||
|
import org.joinmastodon.android.ui.views.MediaGridLayout;
|
||||||
|
import org.joinmastodon.android.utils.TypedObjectPool;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||||
|
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||||
|
|
||||||
|
public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||||
|
private static final String TAG="MediaGridDisplayItem";
|
||||||
|
|
||||||
|
private final PhotoLayoutHelper.TiledLayoutResult tiledLayout;
|
||||||
|
private final TypedObjectPool<GridItemType, MediaAttachmentViewController> viewPool;
|
||||||
|
private final List<Attachment> attachments;
|
||||||
|
private final ArrayList<ImageLoaderRequest> requests=new ArrayList<>();
|
||||||
|
public final Status status;
|
||||||
|
|
||||||
|
public MediaGridStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, PhotoLayoutHelper.TiledLayoutResult tiledLayout, List<Attachment> attachments, Status status){
|
||||||
|
super(parentID, parentFragment);
|
||||||
|
this.tiledLayout=tiledLayout;
|
||||||
|
this.viewPool=parentFragment.getAttachmentViewsPool();
|
||||||
|
this.attachments=attachments;
|
||||||
|
this.status=status;
|
||||||
|
for(Attachment att:attachments){
|
||||||
|
requests.add(new UrlImageLoaderRequest(switch(att.type){
|
||||||
|
case IMAGE -> att.url;
|
||||||
|
case VIDEO, GIFV -> att.previewUrl;
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: "+att.type);
|
||||||
|
}, 1000, 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Type getType(){
|
||||||
|
return Type.MEDIA_GRID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getImageCount(){
|
||||||
|
return requests.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageLoaderRequest getImageRequest(int index){
|
||||||
|
return requests.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum GridItemType{
|
||||||
|
PHOTO,
|
||||||
|
VIDEO,
|
||||||
|
GIFV
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Holder extends StatusDisplayItem.Holder<MediaGridStatusDisplayItem> implements ImageLoaderViewHolder{
|
||||||
|
private final FrameLayout wrapper;
|
||||||
|
private final MediaGridLayout layout;
|
||||||
|
private final View.OnClickListener clickListener=this::onViewClick, altTextClickListener=this::onAltTextClick;
|
||||||
|
private final ArrayList<MediaAttachmentViewController> controllers=new ArrayList<>();
|
||||||
|
|
||||||
|
private final FrameLayout altTextWrapper;
|
||||||
|
private final TextView altTextButton;
|
||||||
|
private final ImageView noAltTextButton;
|
||||||
|
private final View altTextScroller;
|
||||||
|
private final ImageButton altTextClose;
|
||||||
|
private final TextView altText, noAltText;
|
||||||
|
|
||||||
|
private int altTextIndex=-1;
|
||||||
|
private Animator altTextAnimator;
|
||||||
|
|
||||||
|
public Holder(Activity activity, ViewGroup parent){
|
||||||
|
super(new FrameLayoutThatOnlyMeasuresFirstChild(activity));
|
||||||
|
wrapper=(FrameLayout)itemView;
|
||||||
|
layout=new MediaGridLayout(activity);
|
||||||
|
wrapper.addView(layout);
|
||||||
|
|
||||||
|
activity.getLayoutInflater().inflate(R.layout.overlay_image_alt_text, wrapper);
|
||||||
|
altTextWrapper=findViewById(R.id.alt_text_wrapper);
|
||||||
|
altTextButton=findViewById(R.id.alt_button);
|
||||||
|
noAltTextButton=findViewById(R.id.no_alt_button);
|
||||||
|
altTextScroller=findViewById(R.id.alt_text_scroller);
|
||||||
|
altTextClose=findViewById(R.id.alt_text_close);
|
||||||
|
altText=findViewById(R.id.alt_text);
|
||||||
|
noAltText=findViewById(R.id.no_alt_text);
|
||||||
|
altTextClose.setOnClickListener(this::onAltTextCloseClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(MediaGridStatusDisplayItem item){
|
||||||
|
if(altTextAnimator!=null)
|
||||||
|
altTextAnimator.cancel();
|
||||||
|
|
||||||
|
layout.setTiledLayout(item.tiledLayout);
|
||||||
|
for(MediaAttachmentViewController c:controllers){
|
||||||
|
item.viewPool.reuse(c.type, c);
|
||||||
|
}
|
||||||
|
layout.removeAllViews();
|
||||||
|
controllers.clear();
|
||||||
|
int i=0;
|
||||||
|
for(Attachment att:item.attachments){
|
||||||
|
MediaAttachmentViewController c=item.viewPool.obtain(switch(att.type){
|
||||||
|
case IMAGE -> GridItemType.PHOTO;
|
||||||
|
case VIDEO -> GridItemType.VIDEO;
|
||||||
|
case GIFV -> GridItemType.GIFV;
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: "+att.type);
|
||||||
|
});
|
||||||
|
if(c.view.getLayoutParams()==null)
|
||||||
|
c.view.setLayoutParams(new MediaGridLayout.LayoutParams(item.tiledLayout.tiles[i]));
|
||||||
|
else
|
||||||
|
((MediaGridLayout.LayoutParams) c.view.getLayoutParams()).tile=item.tiledLayout.tiles[i];
|
||||||
|
layout.addView(c.view);
|
||||||
|
c.view.setOnClickListener(clickListener);
|
||||||
|
c.view.setTag(i);
|
||||||
|
if(c.btnsWrap!=null){
|
||||||
|
c.btnsWrap.setOnClickListener(altTextClickListener);
|
||||||
|
c.btnsWrap.setTag(i);
|
||||||
|
c.btnsWrap.setAlpha(1f);
|
||||||
|
}
|
||||||
|
controllers.add(c);
|
||||||
|
c.bind(att, item.status);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
altTextButton.setVisibility(View.VISIBLE);
|
||||||
|
noAltTextButton.setVisibility(View.VISIBLE);
|
||||||
|
altTextWrapper.setVisibility(View.GONE);
|
||||||
|
altTextIndex=-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setImage(int index, Drawable drawable){
|
||||||
|
controllers.get(index).setImage(drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearImage(int index){
|
||||||
|
controllers.get(index).clearImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onViewClick(View v){
|
||||||
|
int index=(Integer)v.getTag();
|
||||||
|
if(!item.status.spoilerRevealed){
|
||||||
|
item.parentFragment.onRevealSpoilerClick(this);
|
||||||
|
}else if(item.parentFragment instanceof PhotoViewerHost){
|
||||||
|
((PhotoViewerHost) item.parentFragment).openPhotoViewer(item.parentID, item.status, index, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAltTextClick(View v){
|
||||||
|
if(altTextAnimator!=null)
|
||||||
|
altTextAnimator.cancel();
|
||||||
|
v.setVisibility(View.INVISIBLE);
|
||||||
|
int index=(Integer)v.getTag();
|
||||||
|
altTextIndex=index;
|
||||||
|
Attachment att=item.attachments.get(index);
|
||||||
|
boolean hasAltText = !TextUtils.isEmpty(att.description);
|
||||||
|
altTextButton.setVisibility(hasAltText && GlobalUserPreferences.showAltIndicator ? View.VISIBLE : View.GONE);
|
||||||
|
noAltTextButton.setVisibility(!hasAltText && GlobalUserPreferences.showNoAltIndicator ? View.VISIBLE : View.GONE);
|
||||||
|
altText.setVisibility(hasAltText && GlobalUserPreferences.showAltIndicator ? View.VISIBLE : View.GONE);
|
||||||
|
noAltText.setVisibility(!hasAltText && GlobalUserPreferences.showNoAltIndicator ? View.VISIBLE : View.GONE);
|
||||||
|
altText.setText(att.description);
|
||||||
|
altTextWrapper.setVisibility(View.VISIBLE);
|
||||||
|
altTextWrapper.setBackgroundResource(hasAltText ? R.drawable.bg_image_alt_overlay : R.drawable.bg_image_no_alt_overlay);
|
||||||
|
altTextWrapper.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||||
|
@Override
|
||||||
|
public boolean onPreDraw(){
|
||||||
|
altTextWrapper.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||||
|
|
||||||
|
int[] loc={0, 0};
|
||||||
|
v.getLocationInWindow(loc);
|
||||||
|
int btnL=loc[0], btnT=loc[1];
|
||||||
|
wrapper.getLocationInWindow(loc);
|
||||||
|
btnL-=loc[0];
|
||||||
|
btnT-=loc[1];
|
||||||
|
|
||||||
|
ArrayList<Animator> anims=new ArrayList<>();
|
||||||
|
anims.add(ObjectAnimator.ofFloat(altTextButton, View.ALPHA, 1, 0));
|
||||||
|
anims.add(ObjectAnimator.ofFloat(noAltTextButton, View.ALPHA, 1, 0));
|
||||||
|
anims.add(ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, 0, 1));
|
||||||
|
anims.add(ObjectAnimator.ofFloat(altTextClose, View.ALPHA, 0, 1));
|
||||||
|
anims.add(ObjectAnimator.ofInt(altTextWrapper, "left", btnL+altWrapPadding[0], altTextWrapper.getLeft()));
|
||||||
|
anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT+altWrapPadding[1], altTextWrapper.getTop()));
|
||||||
|
anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+v.getWidth()-altWrapPadding[2], altTextWrapper.getRight()));
|
||||||
|
anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+v.getHeight()-altWrapPadding[3], altTextWrapper.getBottom()));
|
||||||
|
for(Animator a:anims)
|
||||||
|
a.setDuration(300);
|
||||||
|
|
||||||
|
for(MediaAttachmentViewController c:controllers){
|
||||||
|
if(c.btnsWrap!=null && c.btnsWrap!=v){
|
||||||
|
anims.add(ObjectAnimator.ofFloat(c.btnsWrap, View.ALPHA, 1, 0).setDuration(150));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatorSet set=new AnimatorSet();
|
||||||
|
set.playTogether(anims);
|
||||||
|
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||||
|
set.addListener(new AnimatorListenerAdapter(){
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation){
|
||||||
|
altTextAnimator=null;
|
||||||
|
for(MediaAttachmentViewController c:controllers){
|
||||||
|
if(c.btnsWrap!=null){
|
||||||
|
c.btnsWrap.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
altTextAnimator=set;
|
||||||
|
set.start();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAltTextCloseClick(View v){
|
||||||
|
if(altTextAnimator!=null)
|
||||||
|
altTextAnimator.cancel();
|
||||||
|
|
||||||
|
View btn=controllers.get(altTextIndex).btnsWrap;
|
||||||
|
for(MediaAttachmentViewController c:controllers){
|
||||||
|
if(c.btnsWrap!=null && c.btnsWrap!=btn) {
|
||||||
|
c.btnsWrap.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] loc={0, 0};
|
||||||
|
btn.getLocationInWindow(loc);
|
||||||
|
int btnL=loc[0], btnT=loc[1];
|
||||||
|
wrapper.getLocationInWindow(loc);
|
||||||
|
btnL-=loc[0];
|
||||||
|
btnT-=loc[1];
|
||||||
|
|
||||||
|
ArrayList<Animator> anims=new ArrayList<>();
|
||||||
|
anims.add(ObjectAnimator.ofFloat(altTextButton, View.ALPHA, 1));
|
||||||
|
anims.add(ObjectAnimator.ofFloat(noAltTextButton, View.ALPHA, 1));
|
||||||
|
anims.add(ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, 0));
|
||||||
|
anims.add(ObjectAnimator.ofFloat(altTextClose, View.ALPHA, 0));
|
||||||
|
anims.add(ObjectAnimator.ofInt(altTextWrapper, "left", btnL+altWrapPadding[0]));
|
||||||
|
anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT+altWrapPadding[1]));
|
||||||
|
anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+btn.getWidth()-altWrapPadding[2]));
|
||||||
|
anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+btn.getHeight()-altWrapPadding[3]));
|
||||||
|
for(Animator a:anims)
|
||||||
|
a.setDuration(300);
|
||||||
|
|
||||||
|
for(MediaAttachmentViewController c:controllers){
|
||||||
|
// if(c.btnsWrap!=null && c.btnsWrap!=btn){
|
||||||
|
anims.add(ObjectAnimator.ofFloat(c.btnsWrap, View.ALPHA, 1).setDuration(150));
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatorSet set=new AnimatorSet();
|
||||||
|
set.playTogether(anims);
|
||||||
|
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||||
|
set.addListener(new AnimatorListenerAdapter(){
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation){
|
||||||
|
altTextAnimator=null;
|
||||||
|
altTextWrapper.setVisibility(View.GONE);
|
||||||
|
btn.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
altTextAnimator=set;
|
||||||
|
set.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRevealed(boolean revealed){
|
||||||
|
for(MediaAttachmentViewController c:controllers){
|
||||||
|
c.setRevealed(revealed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MediaAttachmentViewController getViewController(int index){
|
||||||
|
return controllers.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClipChildren(boolean clip){
|
||||||
|
layout.setClipChildren(clip);
|
||||||
|
wrapper.setClipChildren(clip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package org.joinmastodon.android.ui.displayitems;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.animation.AnimatorListenerAdapter;
|
|
||||||
import android.animation.AnimatorSet;
|
|
||||||
import android.animation.ObjectAnimator;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.ViewTreeObserver;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.ScrollView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
|
||||||
import org.joinmastodon.android.model.Attachment;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
|
||||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
|
||||||
|
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
|
||||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
|
|
||||||
public class PhotoStatusDisplayItem extends ImageStatusDisplayItem{
|
|
||||||
public PhotoStatusDisplayItem(String parentID, Status status, Attachment photo, BaseStatusListFragment parentFragment, int index, int totalPhotos, PhotoLayoutHelper.TiledLayoutResult tiledLayout, PhotoLayoutHelper.TiledLayoutResult.Tile thisTile){
|
|
||||||
super(parentID, parentFragment, photo, status, index, totalPhotos, tiledLayout, thisTile);
|
|
||||||
request=new UrlImageLoaderRequest(photo.url, 1000, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type getType(){
|
|
||||||
return Type.PHOTO;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Holder extends ImageStatusDisplayItem.Holder<PhotoStatusDisplayItem> {
|
|
||||||
public Holder(Activity activity, ViewGroup parent) {
|
|
||||||
super(activity, R.layout.display_item_photo, parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -35,8 +35,9 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
|
|||||||
text=HtmlParser.parseCustomEmoji(option.title, poll.emojis);
|
text=HtmlParser.parseCustomEmoji(option.title, poll.emojis);
|
||||||
emojiHelper.setText(text);
|
emojiHelper.setText(text);
|
||||||
showResults=poll.isExpired() || poll.voted;
|
showResults=poll.isExpired() || poll.voted;
|
||||||
if(showResults && option.votesCount!=null && poll.votersCount>0){
|
int total=poll.votersCount>0 ? poll.votersCount : poll.votesCount;
|
||||||
votesFraction=(float)option.votesCount/(float)poll.votersCount;
|
if(showResults && option.votesCount!=null && total>0){
|
||||||
|
votesFraction=(float)option.votesCount/(float)total;
|
||||||
int mostVotedCount=0;
|
int mostVotedCount=0;
|
||||||
for(Poll.Option opt:poll.options)
|
for(Poll.Option opt:poll.options)
|
||||||
mostVotedCount=Math.max(mostVotedCount, opt.votesCount);
|
mostVotedCount=Math.max(mostVotedCount, opt.votesCount);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
||||||
private CharSequence text;
|
private CharSequence text;
|
||||||
@@ -37,6 +38,8 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
|||||||
private int iconEnd;
|
private int iconEnd;
|
||||||
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
||||||
private View.OnClickListener handleClick;
|
private View.OnClickListener handleClick;
|
||||||
|
private boolean isLastLine = true;
|
||||||
|
private int lineNo = 0;
|
||||||
|
|
||||||
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick){
|
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick){
|
||||||
super(parentID, parentFragment);
|
super(parentID, parentFragment);
|
||||||
@@ -51,6 +54,14 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
|||||||
updateVisibility(visibility);
|
updateVisibility(visibility);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setIsLastLine(boolean isLastLine) {
|
||||||
|
this.isLastLine = isLastLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLineNo(int lineNo) {
|
||||||
|
this.lineNo = lineNo;
|
||||||
|
}
|
||||||
|
|
||||||
public void updateVisibility(StatusPrivacy visibility) {
|
public void updateVisibility(StatusPrivacy visibility) {
|
||||||
this.visibility = visibility;
|
this.visibility = visibility;
|
||||||
this.iconEnd = visibility != null ? switch (visibility) {
|
this.iconEnd = visibility != null ? switch (visibility) {
|
||||||
@@ -78,18 +89,21 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
|||||||
|
|
||||||
public static class Holder extends StatusDisplayItem.Holder<ReblogOrReplyLineStatusDisplayItem> implements ImageLoaderViewHolder{
|
public static class Holder extends StatusDisplayItem.Holder<ReblogOrReplyLineStatusDisplayItem> implements ImageLoaderViewHolder{
|
||||||
private final TextView text;
|
private final TextView text;
|
||||||
|
private final View frame;
|
||||||
|
|
||||||
public Holder(Activity activity, ViewGroup parent){
|
public Holder(Activity activity, ViewGroup parent){
|
||||||
super(activity, R.layout.display_item_reblog_or_reply_line, parent);
|
super(activity, R.layout.display_item_reblog_or_reply_line, parent);
|
||||||
text=findViewById(R.id.text);
|
text=findViewById(R.id.text);
|
||||||
|
frame=findViewById(R.id.frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(ReblogOrReplyLineStatusDisplayItem item){
|
public void onBind(ReblogOrReplyLineStatusDisplayItem item){
|
||||||
text.setText(item.text);
|
text.setText(item.text);
|
||||||
text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, item.iconEnd, 0);
|
text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, item.iconEnd, 0);
|
||||||
if(item.handleClick!=null) text.setOnClickListener(item.handleClick);
|
text.setOnClickListener(item.handleClick);
|
||||||
text.setEnabled(!item.inset);
|
text.setEnabled(!item.inset && item.handleClick != null);
|
||||||
text.setClickable(!item.inset);
|
text.setClickable(!item.inset && item.handleClick != null);
|
||||||
Context ctx = itemView.getContext();
|
Context ctx = itemView.getContext();
|
||||||
int visibilityText = item.visibility != null ? switch (item.visibility) {
|
int visibilityText = item.visibility != null ? switch (item.visibility) {
|
||||||
case PUBLIC -> R.string.visibility_public;
|
case PUBLIC -> R.string.visibility_public;
|
||||||
@@ -100,6 +114,10 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
|||||||
if (visibilityText != 0) text.setContentDescription(item.text + " (" + ctx.getString(visibilityText) + ")");
|
if (visibilityText != 0) text.setContentDescription(item.text + " (" + ctx.getString(visibilityText) + ")");
|
||||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N)
|
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N)
|
||||||
UiUtils.fixCompoundDrawableTintOnAndroid6(text);
|
UiUtils.fixCompoundDrawableTintOnAndroid6(text);
|
||||||
|
ViewGroup.MarginLayoutParams params = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
|
params.bottomMargin = V.dp(item.isLastLine ? -12 : -18);
|
||||||
|
params.leftMargin = V.dp(13) * item.lineNo;
|
||||||
|
frame.setLayoutParams(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
|
|||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
||||||
import org.joinmastodon.android.fragments.HomeTabFragment;
|
import org.joinmastodon.android.fragments.HomeTabFragment;
|
||||||
import org.joinmastodon.android.fragments.HomeTimelineFragment;
|
|
||||||
import org.joinmastodon.android.fragments.ListTimelineFragment;
|
import org.joinmastodon.android.fragments.ListTimelineFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||||
@@ -20,7 +19,6 @@ import org.joinmastodon.android.model.Account;
|
|||||||
import org.joinmastodon.android.model.Attachment;
|
import org.joinmastodon.android.model.Attachment;
|
||||||
import org.joinmastodon.android.model.DisplayItemsParent;
|
import org.joinmastodon.android.model.DisplayItemsParent;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.model.Poll;
|
import org.joinmastodon.android.model.Poll;
|
||||||
import org.joinmastodon.android.model.ScheduledStatus;
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
@@ -31,11 +29,8 @@ import org.joinmastodon.android.utils.StatusFilterPredicate;
|
|||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
@@ -69,10 +64,7 @@ public abstract class StatusDisplayItem{
|
|||||||
case HEADER -> new HeaderStatusDisplayItem.Holder(activity, parent);
|
case HEADER -> new HeaderStatusDisplayItem.Holder(activity, parent);
|
||||||
case REBLOG_OR_REPLY_LINE -> new ReblogOrReplyLineStatusDisplayItem.Holder(activity, parent);
|
case REBLOG_OR_REPLY_LINE -> new ReblogOrReplyLineStatusDisplayItem.Holder(activity, parent);
|
||||||
case TEXT -> new TextStatusDisplayItem.Holder(activity, parent);
|
case TEXT -> new TextStatusDisplayItem.Holder(activity, parent);
|
||||||
case PHOTO -> new PhotoStatusDisplayItem.Holder(activity, parent);
|
|
||||||
case GIFV -> new GifVStatusDisplayItem.Holder(activity, parent);
|
|
||||||
case AUDIO -> new AudioStatusDisplayItem.Holder(activity, parent);
|
case AUDIO -> new AudioStatusDisplayItem.Holder(activity, parent);
|
||||||
case VIDEO -> new VideoStatusDisplayItem.Holder(activity, parent);
|
|
||||||
case POLL_OPTION -> new PollOptionStatusDisplayItem.Holder(activity, parent);
|
case POLL_OPTION -> new PollOptionStatusDisplayItem.Holder(activity, parent);
|
||||||
case POLL_FOOTER -> new PollFooterStatusDisplayItem.Holder(activity, parent);
|
case POLL_FOOTER -> new PollFooterStatusDisplayItem.Holder(activity, parent);
|
||||||
case CARD -> new LinkCardStatusDisplayItem.Holder(activity, parent);
|
case CARD -> new LinkCardStatusDisplayItem.Holder(activity, parent);
|
||||||
@@ -82,6 +74,7 @@ public abstract class StatusDisplayItem{
|
|||||||
case HASHTAG -> new HashtagStatusDisplayItem.Holder(activity, parent);
|
case HASHTAG -> new HashtagStatusDisplayItem.Holder(activity, parent);
|
||||||
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 WARNING -> new WarningFilteredStatusDisplayItem.Holder(activity, parent);
|
case WARNING -> new WarningFilteredStatusDisplayItem.Holder(activity, parent);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -99,10 +92,6 @@ public abstract class StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate, Filter.FilterContext filterContext){
|
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate, Filter.FilterContext filterContext){
|
||||||
return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, disableTranslate, filterContext, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate, Filter.FilterContext filterContext, StatusDisplayItem titleItem){
|
|
||||||
String parentID=parentObject.getID();
|
String parentID=parentObject.getID();
|
||||||
ArrayList<StatusDisplayItem> items=new ArrayList<>();
|
ArrayList<StatusDisplayItem> items=new ArrayList<>();
|
||||||
|
|
||||||
@@ -125,18 +114,10 @@ public abstract class StatusDisplayItem{
|
|||||||
args.putParcelable("profileAccount", Parcels.wrap(status.account));
|
args.putParcelable("profileAccount", Parcels.wrap(status.account));
|
||||||
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
|
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
|
||||||
}));
|
}));
|
||||||
}else if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId)){
|
} else if (!(status.tags.isEmpty() ||
|
||||||
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, null, i->{
|
|
||||||
args.putParcelable("profileAccount", Parcels.wrap(account));
|
|
||||||
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
|
|
||||||
}));
|
|
||||||
} else if (
|
|
||||||
!(status.tags.isEmpty() ||
|
|
||||||
fragment instanceof HashtagTimelineFragment ||
|
fragment instanceof HashtagTimelineFragment ||
|
||||||
fragment instanceof ListTimelineFragment
|
fragment instanceof ListTimelineFragment
|
||||||
) && fragment.getParentFragment() instanceof HomeTabFragment home
|
) && fragment.getParentFragment() instanceof HomeTabFragment home) {
|
||||||
) {
|
|
||||||
home.getHashtags().stream()
|
home.getHashtags().stream()
|
||||||
.filter(followed -> status.tags.stream()
|
.filter(followed -> status.tags.stream()
|
||||||
.anyMatch(hashtag -> followed.name.equalsIgnoreCase(hashtag.name)))
|
.anyMatch(hashtag -> followed.name.equalsIgnoreCase(hashtag.name)))
|
||||||
@@ -151,6 +132,32 @@ public abstract class StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(statusForContent.inReplyToAccountId!=null){
|
||||||
|
Account account = knownAccounts.get(statusForContent.inReplyToAccountId);
|
||||||
|
View.OnClickListener handleClick = account == null ? null : i -> {
|
||||||
|
args.putParcelable("profileAccount", Parcels.wrap(account));
|
||||||
|
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
|
||||||
|
};
|
||||||
|
String text = account != null ? fragment.getString(R.string.in_reply_to, account.displayName) : fragment.getString(R.string.sk_in_reply);
|
||||||
|
items.add(new ReblogOrReplyLineStatusDisplayItem(
|
||||||
|
parentID, fragment, text, account == null ? List.of() : account.emojis,
|
||||||
|
R.drawable.ic_fluent_arrow_reply_20_filled, null, handleClick
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
int l = 0;
|
||||||
|
ReblogOrReplyLineStatusDisplayItem lastLine = null;
|
||||||
|
for (StatusDisplayItem item : items) {
|
||||||
|
if (item instanceof ReblogOrReplyLineStatusDisplayItem line) {
|
||||||
|
line.setLineNo(l);
|
||||||
|
line.setIsLastLine(false);
|
||||||
|
lastLine = line;
|
||||||
|
l++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lastLine != null) lastLine.setIsLastLine(true);
|
||||||
|
|
||||||
HeaderStatusDisplayItem header;
|
HeaderStatusDisplayItem header;
|
||||||
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, notification, scheduledStatus));
|
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, notification, scheduledStatus));
|
||||||
if(!TextUtils.isEmpty(statusForContent.content))
|
if(!TextUtils.isEmpty(statusForContent.content))
|
||||||
@@ -159,20 +166,8 @@ public abstract class StatusDisplayItem{
|
|||||||
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()){
|
||||||
int photoIndex=0;
|
PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments);
|
||||||
PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(1000, 1910, imageAttachments);
|
items.add(new MediaGridStatusDisplayItem(parentID, fragment, layout, imageAttachments, statusForContent));
|
||||||
for(Attachment attachment:imageAttachments){
|
|
||||||
if(attachment.type==Attachment.Type.IMAGE){
|
|
||||||
items.add(new PhotoStatusDisplayItem(parentID, statusForContent, attachment, fragment, photoIndex, imageAttachments.size(), layout, layout.tiles[photoIndex]));
|
|
||||||
}else if(attachment.type==Attachment.Type.GIFV){
|
|
||||||
items.add(new GifVStatusDisplayItem(parentID, statusForContent, attachment, fragment, photoIndex, imageAttachments.size(), layout, layout.tiles[photoIndex]));
|
|
||||||
}else if(attachment.type==Attachment.Type.VIDEO){
|
|
||||||
items.add(new VideoStatusDisplayItem(parentID, statusForContent, attachment, fragment, photoIndex, imageAttachments.size(), layout, layout.tiles[photoIndex]));
|
|
||||||
}else{
|
|
||||||
throw new IllegalStateException("This isn't supposed to happen, type is "+attachment.type);
|
|
||||||
}
|
|
||||||
photoIndex++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for(Attachment att:statusForContent.mediaAttachments){
|
for(Attachment att:statusForContent.mediaAttachments){
|
||||||
if(att.type==Attachment.Type.AUDIO){
|
if(att.type==Attachment.Type.AUDIO){
|
||||||
@@ -196,8 +191,6 @@ public abstract class StatusDisplayItem{
|
|||||||
item.index=i++;
|
item.index=i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (titleItem != null) items.add(0, titleItem);
|
|
||||||
|
|
||||||
if (!statusForContent.filterRevealed) {
|
if (!statusForContent.filterRevealed) {
|
||||||
return new ArrayList<>(List.of(
|
return new ArrayList<>(List.of(
|
||||||
new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, items)
|
new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, items)
|
||||||
@@ -218,9 +211,6 @@ public abstract class StatusDisplayItem{
|
|||||||
HEADER,
|
HEADER,
|
||||||
REBLOG_OR_REPLY_LINE,
|
REBLOG_OR_REPLY_LINE,
|
||||||
TEXT,
|
TEXT,
|
||||||
PHOTO,
|
|
||||||
VIDEO,
|
|
||||||
GIFV,
|
|
||||||
AUDIO,
|
AUDIO,
|
||||||
POLL_OPTION,
|
POLL_OPTION,
|
||||||
POLL_FOOTER,
|
POLL_FOOTER,
|
||||||
@@ -230,8 +220,9 @@ public abstract class StatusDisplayItem{
|
|||||||
ACCOUNT,
|
ACCOUNT,
|
||||||
HASHTAG,
|
HASHTAG,
|
||||||
GAP,
|
GAP,
|
||||||
WARNING,
|
EXTENDED_FOOTER,
|
||||||
EXTENDED_FOOTER
|
MEDIA_GRID,
|
||||||
|
WARNING
|
||||||
}
|
}
|
||||||
|
|
||||||
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{
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
package org.joinmastodon.android.ui.displayitems;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.graphics.Outline;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.ViewOutlineProvider;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
|
||||||
import org.joinmastodon.android.model.Attachment;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
|
||||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
|
||||||
|
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
|
||||||
|
|
||||||
public class VideoStatusDisplayItem extends ImageStatusDisplayItem{
|
|
||||||
public VideoStatusDisplayItem(String parentID, Status status, Attachment attachment, BaseStatusListFragment parentFragment, int index, int totalPhotos, PhotoLayoutHelper.TiledLayoutResult tiledLayout, PhotoLayoutHelper.TiledLayoutResult.Tile thisTile){
|
|
||||||
super(parentID, parentFragment, attachment, status, index, totalPhotos, tiledLayout, thisTile);
|
|
||||||
request=new UrlImageLoaderRequest(attachment.previewUrl, 1000, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type getType(){
|
|
||||||
return Type.VIDEO;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Holder extends ImageStatusDisplayItem.Holder<VideoStatusDisplayItem>{
|
|
||||||
|
|
||||||
public Holder(Activity activity, ViewGroup parent){
|
|
||||||
super(activity, R.layout.display_item_video, parent);
|
|
||||||
View play=findViewById(R.id.play_button);
|
|
||||||
play.setOutlineProvider(new ViewOutlineProvider(){
|
|
||||||
@Override
|
|
||||||
public void getOutline(View view, Outline outline){
|
|
||||||
outline.setOval(0, 0, view.getWidth(), view.getHeight());
|
|
||||||
outline.setAlpha(.99f); // fixes shadow rendering
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
package org.joinmastodon.android.ui.photoviewer;
|
package org.joinmastodon.android.ui.photoviewer;
|
||||||
|
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
||||||
|
|
||||||
public interface PhotoViewerHost{
|
public interface PhotoViewerHost{
|
||||||
void openPhotoViewer(String parentID, Status status, int attachmentIndex);
|
void openPhotoViewer(String parentID, Status status, int attachmentIndex, MediaGridStatusDisplayItem.Holder gridHolder);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,24 +8,25 @@ import android.graphics.Rect;
|
|||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
|
import android.view.GestureDetector;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.SoundEffectConstants;
|
import android.view.SoundEffectConstants;
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewConfiguration;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class ClickableLinksDelegate {
|
public class ClickableLinksDelegate {
|
||||||
|
|
||||||
private Paint hlPaint;
|
private final Paint hlPaint;
|
||||||
private Path hlPath;
|
private Path hlPath;
|
||||||
private LinkSpan selectedSpan;
|
private LinkSpan selectedSpan;
|
||||||
private TextView view;
|
private final TextView view;
|
||||||
|
|
||||||
private final Runnable longClickRunnable = () -> {
|
private final GestureDetector gestureDetector;
|
||||||
if (selectedSpan != null) selectedSpan.onLongClick(view);
|
|
||||||
};
|
|
||||||
|
|
||||||
public ClickableLinksDelegate(TextView view) {
|
public ClickableLinksDelegate(TextView view) {
|
||||||
this.view=view;
|
this.view=view;
|
||||||
@@ -33,11 +34,45 @@ public class ClickableLinksDelegate {
|
|||||||
hlPaint.setAntiAlias(true);
|
hlPaint.setAntiAlias(true);
|
||||||
hlPaint.setPathEffect(new CornerPathEffect(V.dp(3)));
|
hlPaint.setPathEffect(new CornerPathEffect(V.dp(3)));
|
||||||
// view.setHighlightColor(view.getResources().getColor(android.R.color.holo_blue_light));
|
// view.setHighlightColor(view.getResources().getColor(android.R.color.holo_blue_light));
|
||||||
|
gestureDetector = new GestureDetector(view.getContext(), new LinkGestureListener(), view.getHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean onTouch(MotionEvent event) {
|
public boolean onTouch(MotionEvent event) {
|
||||||
long eventDuration = event.getEventTime() - event.getDownTime();
|
if(event.getAction()==MotionEvent.ACTION_CANCEL){
|
||||||
if(event.getAction()==MotionEvent.ACTION_DOWN){
|
// the gestureDetector does not provide a callback for CANCEL, therefore:
|
||||||
|
// remove background color of view before passing event to gestureDetector
|
||||||
|
resetAndInvalidate();
|
||||||
|
}
|
||||||
|
return gestureDetector.onTouchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove highlighting from span and let the system redraw the view
|
||||||
|
*/
|
||||||
|
private void resetAndInvalidate() {
|
||||||
|
hlPath=null;
|
||||||
|
selectedSpan=null;
|
||||||
|
view.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDraw(Canvas canvas){
|
||||||
|
if(hlPath!=null){
|
||||||
|
canvas.save();
|
||||||
|
canvas.translate(0, view.getPaddingTop());
|
||||||
|
canvas.drawPath(hlPath, hlPaint);
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GestureListener for spans that represent URLs.
|
||||||
|
* onDown: on start of touch event, set highlighting
|
||||||
|
* onSingleTapUp: when there was a (short) tap, call onClick and reset highlighting
|
||||||
|
* onLongPress: copy URL to clipboard, let user know, reset highlighting
|
||||||
|
*/
|
||||||
|
private class LinkGestureListener extends GestureDetector.SimpleOnGestureListener {
|
||||||
|
@Override
|
||||||
|
public boolean onDown(@NonNull MotionEvent event) {
|
||||||
int line=-1;
|
int line=-1;
|
||||||
Rect rect=new Rect();
|
Rect rect=new Rect();
|
||||||
Layout l=view.getLayout();
|
Layout l=view.getLayout();
|
||||||
@@ -52,8 +87,7 @@ public class ClickableLinksDelegate {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
CharSequence text=view.getText();
|
CharSequence text=view.getText();
|
||||||
if(text instanceof Spanned){
|
if(text instanceof Spanned s){
|
||||||
Spanned s=(Spanned)text;
|
|
||||||
LinkSpan[] spans=s.getSpans(0, s.length()-1, LinkSpan.class);
|
LinkSpan[] spans=s.getSpans(0, s.length()-1, LinkSpan.class);
|
||||||
if(spans.length>0){
|
if(spans.length>0){
|
||||||
for(LinkSpan span:spans){
|
for(LinkSpan span:spans){
|
||||||
@@ -70,7 +104,6 @@ public class ClickableLinksDelegate {
|
|||||||
}
|
}
|
||||||
hlPath=new Path();
|
hlPath=new Path();
|
||||||
selectedSpan=span;
|
selectedSpan=span;
|
||||||
view.postDelayed(longClickRunnable, ViewConfiguration.getLongPressTimeout());
|
|
||||||
hlPaint.setColor((span.getColor() & 0x00FFFFFF) | 0x33000000);
|
hlPaint.setColor((span.getColor() & 0x00FFFFFF) | 0x33000000);
|
||||||
//l.getSelectionPath(start, end, hlPath);
|
//l.getSelectionPath(start, end, hlPath);
|
||||||
for(int j=lstart;j<=lend;j++){
|
for(int j=lstart;j<=lend;j++){
|
||||||
@@ -96,35 +129,26 @@ public class ClickableLinksDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return super.onDown(event);
|
||||||
}
|
}
|
||||||
if(event.getAction()==MotionEvent.ACTION_UP && selectedSpan!=null){
|
|
||||||
if (eventDuration <= ViewConfiguration.getLongPressTimeout()) {
|
@Override
|
||||||
|
public boolean onSingleTapUp(@NonNull MotionEvent event) {
|
||||||
|
if(selectedSpan!=null){
|
||||||
view.playSoundEffect(SoundEffectConstants.CLICK);
|
view.playSoundEffect(SoundEffectConstants.CLICK);
|
||||||
selectedSpan.onClick(view.getContext());
|
selectedSpan.onClick(view.getContext());
|
||||||
}
|
resetAndInvalidate();
|
||||||
view.removeCallbacks(longClickRunnable);
|
return true;
|
||||||
hlPath=null;
|
|
||||||
selectedSpan=null;
|
|
||||||
view.invalidate();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(event.getAction()==MotionEvent.ACTION_CANCEL){
|
|
||||||
hlPath=null;
|
|
||||||
selectedSpan=null;
|
|
||||||
view.removeCallbacks(longClickRunnable);
|
|
||||||
view.invalidate();
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onDraw(Canvas canvas){
|
@Override
|
||||||
if(hlPath!=null){
|
public void onLongPress(@NonNull MotionEvent event) {
|
||||||
canvas.save();
|
if (selectedSpan == null) return;
|
||||||
canvas.translate(0, view.getPaddingTop());
|
UiUtils.copyText(view, selectedSpan.getType() == LinkSpan.Type.URL ? selectedSpan.getLink() : selectedSpan.getText());
|
||||||
canvas.drawPath(hlPath, hlPaint);
|
//reset view
|
||||||
canvas.restore();
|
resetAndInvalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,14 +46,14 @@ public class LinkSpan extends CharacterStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onLongClick(View view) {
|
|
||||||
UiUtils.copyText(view, getType() == Type.URL ? link : text);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLink(){
|
public String getLink(){
|
||||||
return link;
|
return link;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
public Type getType(){
|
public Type getType(){
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ import android.view.View;
|
|||||||
|
|
||||||
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.fragments.NotificationsListFragment;
|
|
||||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
|
||||||
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
|
||||||
import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -87,21 +85,11 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
|
|||||||
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
||||||
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
||||||
int pad;
|
int pad;
|
||||||
if(holder instanceof ImageStatusDisplayItem.Holder || holder instanceof LinkCardStatusDisplayItem.Holder)
|
if(holder instanceof MediaGridStatusDisplayItem.Holder || holder instanceof LinkCardStatusDisplayItem.Holder)
|
||||||
pad=V.dp(16);
|
pad=V.dp(16);
|
||||||
else
|
else
|
||||||
pad=V.dp(12);
|
pad=V.dp(12);
|
||||||
boolean insetLeft=true, insetRight=true;
|
boolean insetLeft=true, insetRight=true;
|
||||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> img){
|
|
||||||
PhotoLayoutHelper.TiledLayoutResult layout=img.getItem().tiledLayout;
|
|
||||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=img.getItem().thisTile;
|
|
||||||
// only inset those items that are on the edges of the layout
|
|
||||||
insetLeft=tile.startCol==0;
|
|
||||||
insetRight=tile.startCol+tile.colSpan==layout.columnSizes.length;
|
|
||||||
// inset all items in the bottom row
|
|
||||||
if(tile.startRow+tile.rowSpan==layout.rowSizes.length)
|
|
||||||
bottomSiblingInset=false;
|
|
||||||
}
|
|
||||||
if(insetLeft)
|
if(insetLeft)
|
||||||
outRect.left=pad;
|
outRect.left=pad;
|
||||||
if(insetRight)
|
if(insetRight)
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package org.joinmastodon.android.ui.utils;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.model.Attachment;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
||||||
|
|
||||||
|
public class MediaAttachmentViewController{
|
||||||
|
public final View view;
|
||||||
|
public final MediaGridStatusDisplayItem.GridItemType type;
|
||||||
|
public final ImageView photo;
|
||||||
|
public final View altButton, noAltButton, btnsWrap;
|
||||||
|
public static int[] altWrapPadding = null;
|
||||||
|
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
|
||||||
|
private final Context context;
|
||||||
|
private boolean didClear;
|
||||||
|
private Status status;
|
||||||
|
|
||||||
|
public MediaAttachmentViewController(Context context, MediaGridStatusDisplayItem.GridItemType type){
|
||||||
|
view=context.getSystemService(LayoutInflater.class).inflate(switch(type){
|
||||||
|
case PHOTO -> R.layout.display_item_photo;
|
||||||
|
case VIDEO -> R.layout.display_item_video;
|
||||||
|
case GIFV -> R.layout.display_item_gifv;
|
||||||
|
}, null);
|
||||||
|
photo=view.findViewById(R.id.photo);
|
||||||
|
altButton=view.findViewById(R.id.alt_button);
|
||||||
|
noAltButton=view.findViewById(R.id.no_alt_button);
|
||||||
|
btnsWrap=view.findViewById(R.id.alt_badges);
|
||||||
|
this.type=type;
|
||||||
|
this.context=context;
|
||||||
|
if (altWrapPadding == null) {
|
||||||
|
altWrapPadding = new int[] { btnsWrap.getPaddingLeft(), btnsWrap.getPaddingTop(), btnsWrap.getPaddingRight(), btnsWrap.getPaddingBottom() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(Attachment attachment, Status status){
|
||||||
|
this.status=status;
|
||||||
|
crossfadeDrawable.setSize(attachment.getWidth(), attachment.getHeight());
|
||||||
|
crossfadeDrawable.setBlurhashDrawable(attachment.blurhashPlaceholder);
|
||||||
|
crossfadeDrawable.setCrossfadeAlpha(status.spoilerRevealed ? 0f : 1f);
|
||||||
|
photo.setImageDrawable(null);
|
||||||
|
photo.setImageDrawable(crossfadeDrawable);
|
||||||
|
boolean hasAltText = !TextUtils.isEmpty(attachment.description);
|
||||||
|
photo.setContentDescription(!hasAltText ? context.getString(R.string.media_no_description) : attachment.description);
|
||||||
|
if(btnsWrap!=null){
|
||||||
|
btnsWrap.setVisibility(View.VISIBLE);
|
||||||
|
altButton.setVisibility(hasAltText && GlobalUserPreferences.showAltIndicator ? View.VISIBLE : View.GONE);
|
||||||
|
noAltButton.setVisibility(!hasAltText && GlobalUserPreferences.showNoAltIndicator ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
didClear=false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImage(Drawable drawable){
|
||||||
|
crossfadeDrawable.setImageDrawable(drawable);
|
||||||
|
if(didClear && status.spoilerRevealed)
|
||||||
|
crossfadeDrawable.animateAlpha(0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearImage(){
|
||||||
|
crossfadeDrawable.setCrossfadeAlpha(1f);
|
||||||
|
crossfadeDrawable.setImageDrawable(null);
|
||||||
|
didClear=true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRevealed(boolean revealed){
|
||||||
|
crossfadeDrawable.animateAlpha(revealed ? 0f : 1f);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -87,6 +87,7 @@ import org.joinmastodon.android.model.Notification;
|
|||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
import org.joinmastodon.android.model.ScheduledStatus;
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
import org.joinmastodon.android.model.SearchResults;
|
import org.joinmastodon.android.model.SearchResults;
|
||||||
|
import org.joinmastodon.android.model.Searchable;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
@@ -107,8 +108,10 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.function.BiPredicate;
|
import java.util.function.BiPredicate;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -691,6 +694,9 @@ public class UiUtils {
|
|||||||
TypedArray ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
|
TypedArray ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
|
||||||
button.setBackground(ta.getDrawable(0));
|
button.setBackground(ta.getDrawable(0));
|
||||||
ta.recycle();
|
ta.recycle();
|
||||||
|
ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor});
|
||||||
|
button.setTextColor(ta.getColorStateList(0));
|
||||||
|
ta.recycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void performAccountAction(Activity activity, Account account, String accountID, Relationship relationship, Button button, Consumer<Boolean> progressCallback, Consumer<Relationship> resultCallback) {
|
public static void performAccountAction(Activity activity, Account account, String accountID, Relationship relationship, Button button, Consumer<Boolean> progressCallback, Consumer<Relationship> resultCallback) {
|
||||||
@@ -982,6 +988,8 @@ public class UiUtils {
|
|||||||
public static void pickInteractAs(Context context, String accountID, Status sourceStatus, Predicate<Status> checkInteracted, InteractionPerformer interactionPerformer, @StringRes int interactAsRes, @StringRes int interactedAsAccountRes, @StringRes int alreadyInteractedRes, @DrawableRes int iconRes) {
|
public static void pickInteractAs(Context context, String accountID, Status sourceStatus, Predicate<Status> checkInteracted, InteractionPerformer interactionPerformer, @StringRes int interactAsRes, @StringRes int interactedAsAccountRes, @StringRes int alreadyInteractedRes, @DrawableRes int iconRes) {
|
||||||
pickAccount(context, accountID, interactAsRes, iconRes, session -> {
|
pickAccount(context, accountID, interactAsRes, iconRes, session -> {
|
||||||
lookupStatus(context, sourceStatus, session.getID(), accountID, status -> {
|
lookupStatus(context, sourceStatus, session.getID(), accountID, status -> {
|
||||||
|
if (status == null) return;
|
||||||
|
|
||||||
if (checkInteracted.test(status)) {
|
if (checkInteracted.test(status)) {
|
||||||
Toast.makeText(context, alreadyInteractedRes, Toast.LENGTH_SHORT).show();
|
Toast.makeText(context, alreadyInteractedRes, Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
@@ -997,18 +1005,33 @@ public class UiUtils {
|
|||||||
}, null);
|
}, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void lookupStatus(Context context, Status queryStatus, String targetAccountID, @Nullable String sourceAccountID, Consumer<Status> statusConsumer) {
|
public static void lookupStatus(Context context, Status queryStatus, String targetAccountID, @Nullable String sourceAccountID, Consumer<Status> resultConsumer) {
|
||||||
|
lookup(context, queryStatus, targetAccountID, sourceAccountID, GetSearchResults.Type.STATUSES, resultConsumer, results ->
|
||||||
|
!results.statuses.isEmpty() ? Optional.of(results.statuses.get(0)) : Optional.empty()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void lookupAccount(Context context, Account queryAccount, String targetAccountID, @Nullable String sourceAccountID, Consumer<Account> resultConsumer) {
|
||||||
|
lookup(context, queryAccount, targetAccountID, sourceAccountID, GetSearchResults.Type.ACCOUNTS, resultConsumer, results ->
|
||||||
|
!results.accounts.isEmpty() ? Optional.of(results.accounts.get(0)) : Optional.empty()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends Searchable> void lookup(Context context, T query, String targetAccountID, @Nullable String sourceAccountID, @Nullable GetSearchResults.Type type, Consumer<T> resultConsumer, Function<SearchResults, Optional<T>> extractResult) {
|
||||||
if (sourceAccountID != null && targetAccountID.startsWith(sourceAccountID.substring(0, sourceAccountID.indexOf('_')))) {
|
if (sourceAccountID != null && targetAccountID.startsWith(sourceAccountID.substring(0, sourceAccountID.indexOf('_')))) {
|
||||||
statusConsumer.accept(queryStatus);
|
resultConsumer.accept(query);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
new GetSearchResults(queryStatus.url, GetSearchResults.Type.STATUSES, true).setCallback(new Callback<>() {
|
new GetSearchResults(query.getQuery(), type, true).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(SearchResults results) {
|
public void onSuccess(SearchResults results) {
|
||||||
if (!results.statuses.isEmpty()) statusConsumer.accept(results.statuses.get(0));
|
Optional<T> result = extractResult.apply(results);
|
||||||
else
|
if (result.isPresent()) resultConsumer.accept(result.get());
|
||||||
|
else {
|
||||||
Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show();
|
Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show();
|
||||||
|
resultConsumer.accept(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1123,6 +1146,10 @@ public class UiUtils {
|
|||||||
return !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.code"));
|
return !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.code"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isEMUI() {
|
||||||
|
return !TextUtils.isEmpty(getSystemProperty("ro.build.version.emui"));
|
||||||
|
}
|
||||||
|
|
||||||
public static int alphaBlendColors(int color1, int color2, float alpha) {
|
public static int alphaBlendColors(int color1, int color2, float alpha) {
|
||||||
float alpha0 = 1f - alpha;
|
float alpha0 = 1f - alpha;
|
||||||
int r = Math.round(((color1 >> 16) & 0xFF) * alpha0 + ((color2 >> 16) & 0xFF) * alpha);
|
int r = Math.round(((color1 >> 16) & 0xFF) * alpha0 + ((color2 >> 16) & 0xFF) * alpha);
|
||||||
@@ -1245,6 +1272,17 @@ public class UiUtils {
|
|||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void populateAccountsMenu(String excludeAccountID, Menu menu, Consumer<AccountSession> onClick) {
|
||||||
|
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts();
|
||||||
|
sessions.stream().filter(s -> !s.getID().equals(excludeAccountID)).forEach(s -> {
|
||||||
|
String username = "@"+s.self.username+"@"+s.domain;
|
||||||
|
menu.add(username).setOnMenuItemClickListener((c) -> {
|
||||||
|
onClick.accept(s);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps a View.OnClickListener to filter multiple clicks in succession.
|
* Wraps a View.OnClickListener to filter multiple clicks in succession.
|
||||||
* Useful for buttons that perform some action that changes their state asynchronously.
|
* Useful for buttons that perform some action that changes their state asynchronously.
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package org.joinmastodon.android.ui.views;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
public class FrameLayoutThatOnlyMeasuresFirstChild extends FrameLayout{
|
||||||
|
public FrameLayoutThatOnlyMeasuresFirstChild(Context context){
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameLayoutThatOnlyMeasuresFirstChild(Context context, AttributeSet attrs){
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameLayoutThatOnlyMeasuresFirstChild(Context context, AttributeSet attrs, int defStyle){
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||||
|
if(getChildCount()==0)
|
||||||
|
return;
|
||||||
|
View child0=getChildAt(0);
|
||||||
|
measureChild(child0, widthMeasureSpec, heightMeasureSpec);
|
||||||
|
super.onMeasure(child0.getMeasuredWidth() | MeasureSpec.EXACTLY, child0.getMeasuredHeight() | MeasureSpec.EXACTLY);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package org.joinmastodon.android.ui.views;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
|
|
||||||
public class ImageAttachmentFrameLayout extends FrameLayout{
|
|
||||||
public static final int MAX_WIDTH=400; // dp
|
|
||||||
|
|
||||||
private PhotoLayoutHelper.TiledLayoutResult tileLayout;
|
|
||||||
private PhotoLayoutHelper.TiledLayoutResult.Tile tile;
|
|
||||||
private int horizontalInset;
|
|
||||||
|
|
||||||
public ImageAttachmentFrameLayout(@NonNull Context context){
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImageAttachmentFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs){
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImageAttachmentFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr){
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
|
||||||
if(isInEditMode()){
|
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int w=Math.min(((View)getParent()).getMeasuredWidth(), V.dp(MAX_WIDTH))-horizontalInset;
|
|
||||||
int actualHeight=Math.round(tile.height/1000f*w)+V.dp(1)*(tile.rowSpan-1);
|
|
||||||
int actualWidth=Math.round(tile.width/1000f*w);
|
|
||||||
if(tile.startCol+tile.colSpan<tileLayout.columnSizes.length)
|
|
||||||
actualWidth-=V.dp(1);
|
|
||||||
heightMeasureSpec=actualHeight | MeasureSpec.EXACTLY;
|
|
||||||
widthMeasureSpec=actualWidth | MeasureSpec.EXACTLY;
|
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLayout(PhotoLayoutHelper.TiledLayoutResult layout, PhotoLayoutHelper.TiledLayoutResult.Tile tile, int horizontalInset){
|
|
||||||
tileLayout=layout;
|
|
||||||
this.tile=tile;
|
|
||||||
this.horizontalInset=horizontalInset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package org.joinmastodon.android.ui.views;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||||
|
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
|
public class MediaGridLayout extends ViewGroup{
|
||||||
|
private static final String TAG="MediaGridLayout";
|
||||||
|
|
||||||
|
public static final int MAX_WIDTH=400; // dp
|
||||||
|
private static final int GAP=1; // dp
|
||||||
|
private PhotoLayoutHelper.TiledLayoutResult tiledLayout;
|
||||||
|
private int[] columnStarts=new int[10], columnEnds=new int[10], rowStarts=new int[10], rowEnds=new int[10];
|
||||||
|
|
||||||
|
public MediaGridLayout(Context context){
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MediaGridLayout(Context context, AttributeSet attrs){
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MediaGridLayout(Context context, AttributeSet attrs, int defStyle){
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||||
|
if(tiledLayout==null){
|
||||||
|
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int width=Math.min(V.dp(MAX_WIDTH), MeasureSpec.getSize(widthMeasureSpec));
|
||||||
|
int height=Math.round(width*(tiledLayout.height/(float)PhotoLayoutHelper.MAX_WIDTH));
|
||||||
|
|
||||||
|
int offset=0;
|
||||||
|
for(int i=0;i<tiledLayout.columnSizes.length;i++){
|
||||||
|
columnStarts[i]=offset;
|
||||||
|
offset+=Math.round(tiledLayout.columnSizes[i]/(float)tiledLayout.width*width);
|
||||||
|
columnEnds[i]=offset;
|
||||||
|
offset+=V.dp(GAP);
|
||||||
|
}
|
||||||
|
columnEnds[tiledLayout.columnSizes.length-1]=width;
|
||||||
|
offset=0;
|
||||||
|
for(int i=0;i<tiledLayout.rowSizes.length;i++){
|
||||||
|
rowStarts[i]=offset;
|
||||||
|
offset+=Math.round(tiledLayout.rowSizes[i]/(float)tiledLayout.height*height);
|
||||||
|
rowEnds[i]=offset;
|
||||||
|
offset+=V.dp(GAP);
|
||||||
|
}
|
||||||
|
rowEnds[tiledLayout.rowSizes.length-1]=height;
|
||||||
|
|
||||||
|
for(int i=0;i<getChildCount();i++){
|
||||||
|
View child=getChildAt(i);
|
||||||
|
LayoutParams lp=(LayoutParams) child.getLayoutParams();
|
||||||
|
int colSpan=Math.max(1, lp.tile.colSpan)-1;
|
||||||
|
int rowSpan=Math.max(1, lp.tile.rowSpan)-1;
|
||||||
|
int w=columnEnds[lp.tile.startCol+colSpan]-columnStarts[lp.tile.startCol];
|
||||||
|
int h=rowEnds[lp.tile.startRow+rowSpan]-rowStarts[lp.tile.startRow];
|
||||||
|
child.measure(w | MeasureSpec.EXACTLY, h | MeasureSpec.EXACTLY);
|
||||||
|
}
|
||||||
|
|
||||||
|
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onLayout(boolean changed, int l, int t, int r, int b){
|
||||||
|
if(tiledLayout==null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int maxWidth=V.dp(MAX_WIDTH);
|
||||||
|
int xOffset=0;
|
||||||
|
if(r-l>maxWidth){
|
||||||
|
xOffset=(r-l)/2-maxWidth/2;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i=0;i<getChildCount();i++){
|
||||||
|
View child=getChildAt(i);
|
||||||
|
LayoutParams lp=(LayoutParams) child.getLayoutParams();
|
||||||
|
int colSpan=Math.max(1, lp.tile.colSpan)-1;
|
||||||
|
int rowSpan=Math.max(1, lp.tile.rowSpan)-1;
|
||||||
|
child.layout(columnStarts[lp.tile.startCol]+xOffset, rowStarts[lp.tile.startRow], columnEnds[lp.tile.startCol+colSpan]+xOffset, rowEnds[lp.tile.startRow+rowSpan]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTiledLayout(PhotoLayoutHelper.TiledLayoutResult tiledLayout){
|
||||||
|
this.tiledLayout=tiledLayout;
|
||||||
|
requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LayoutParams extends ViewGroup.LayoutParams{
|
||||||
|
public PhotoLayoutHelper.TiledLayoutResult.Tile tile;
|
||||||
|
|
||||||
|
public LayoutParams(PhotoLayoutHelper.TiledLayoutResult.Tile tile){
|
||||||
|
super(WRAP_CONTENT, WRAP_CONTENT);
|
||||||
|
this.tile=tile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.joinmastodon.android.ui.utils;
|
package org.joinmastodon.android.utils;
|
||||||
|
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.joinmastodon.android.utils;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class TypedObjectPool<K, V>{
|
||||||
|
private final Function<K, V> producer;
|
||||||
|
private final HashMap<K, LinkedList<V>> pool=new HashMap<>();
|
||||||
|
|
||||||
|
public TypedObjectPool(Function<K, V> producer){
|
||||||
|
this.producer=producer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public V obtain(K type){
|
||||||
|
LinkedList<V> tp=pool.get(type);
|
||||||
|
if(tp==null)
|
||||||
|
pool.put(type, tp=new LinkedList<>());
|
||||||
|
|
||||||
|
V value=tp.poll();
|
||||||
|
if(value==null)
|
||||||
|
value=producer.apply(type);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reuse(K type, V obj){
|
||||||
|
Objects.requireNonNull(obj);
|
||||||
|
Objects.requireNonNull(type);
|
||||||
|
|
||||||
|
LinkedList<V> tp=pool.get(type);
|
||||||
|
if(tp==null)
|
||||||
|
pool.put(type, tp=new LinkedList<>());
|
||||||
|
tp.add(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,5 +4,5 @@
|
|||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
<corners android:radius="4sp" />
|
<corners android:radius="4sp" />
|
||||||
<solid android:color="?colorBackgroundLight" />
|
<solid android:color="?colorBackgroundLight" />
|
||||||
<stroke android:width="2dp" android:color="#00ff00" />
|
<stroke android:width="2dp" android:color="?android:colorAccent" />
|
||||||
</shape>
|
</shape>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="28dp" android:height="28dp" android:viewportWidth="28" android:viewportHeight="28">
|
||||||
|
<path android:pathData="M15.114 25.719c-0.396-0.408-0.746-0.861-1.04-1.35C13.418 24.453 12.725 24.5 12 24.5c-5.111 0-8.5-2.111-8.5-4.785V19l0.007-0.145C3.58 18.095 4.22 17.5 5 17.5h8.624c0.234-0.535 0.529-1.038 0.875-1.5H5c-1.657 0-3 1.343-3 3v0.715C2 23.433 6.21 26 12 26c1.101 0 2.145-0.098 3.114-0.281zM18 8c0-3.314-2.686-6-6-6S6 4.686 6 8s2.686 6 6 6 6-2.686 6-6zM7.5 8c0-2.485 2.015-4.5 4.5-4.5s4.5 2.015 4.5 4.5-2.015 4.5-4.5 4.5S7.5 10.485 7.5 8zm13 19c3.59 0 6.5-2.91 6.5-6.5S24.09 14 20.5 14 14 16.91 14 20.5s2.91 6.5 6.5 6.5zm0-11c0.276 0 0.5 0.224 0.5 0.5V20h3.5c0.276 0 0.5 0.224 0.5 0.5S24.776 21 24.5 21H21v3.5c0 0.276-0.224 0.5-0.5 0.5S20 24.776 20 24.5V21h-3.5c-0.276 0-0.5-0.224-0.5-0.5s0.224-0.5 0.5-0.5H20v-3.5c0-0.276 0.224-0.5 0.5-0.5z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -1,79 +1,34 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- This is hidden from screenreaders because that same alt text is set as content description on the ImageView -->
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
<FrameLayout
|
android:id="@+id/alt_badges"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/alt_text_wrapper"
|
|
||||||
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_margin="12dp"
|
android:padding="12dp"
|
||||||
android:importantForAccessibility="noHideDescendants"
|
android:importantForAccessibility="noHideDescendants">
|
||||||
android:background="@drawable/bg_image_alt_overlay">
|
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/no_alt_button"
|
android:id="@+id/no_alt_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
android:padding="4dp"
|
android:padding="4dp"
|
||||||
android:src="@drawable/ic_fluent_important_20_filled"
|
android:src="@drawable/ic_fluent_important_20_filled"
|
||||||
|
android:background="@drawable/bg_image_no_alt_overlay"
|
||||||
android:tint="?colorGray25" />
|
android:tint="?colorGray25" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/alt_button"
|
android:id="@+id/alt_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
android:textAppearance="@style/m3_label_large"
|
android:textAppearance="@style/m3_label_large"
|
||||||
android:textColor="?colorGray25"
|
android:textColor="?colorGray25"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:includeFontPadding="false"
|
android:includeFontPadding="false"
|
||||||
android:paddingHorizontal="5dp"
|
android:paddingHorizontal="5dp"
|
||||||
android:paddingVertical="1dp"
|
android:paddingVertical="1dp"
|
||||||
|
android:background="@drawable/bg_image_alt_overlay"
|
||||||
android:text="@string/sk_alt_button"/>
|
android:text="@string/sk_alt_button"/>
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/alt_text_close"
|
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
android:layout_gravity="end|top"
|
|
||||||
android:src="@drawable/ic_baseline_close_24"
|
|
||||||
android:tint="#FFF"
|
|
||||||
android:background="?android:actionBarItemBackground"/>
|
|
||||||
|
|
||||||
<org.joinmastodon.android.ui.views.NestableScrollView
|
|
||||||
android:id="@+id/alt_text_scroller"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_marginEnd="40dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/alt_text"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:textAppearance="@style/m3_body_medium"
|
|
||||||
android:textColor="?colorGray25"
|
|
||||||
tools:text="Alt text goes here"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/no_alt_text"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginVertical="8dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:layout_marginStart="14dp"
|
|
||||||
android:textAppearance="@style/m3_label_large"
|
|
||||||
android:textColor="?colorGray25"
|
|
||||||
android:text="@string/sk_no_alt_text"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</org.joinmastodon.android.ui.views.NestableScrollView>
|
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout 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">
|
||||||
|
|
||||||
@@ -27,4 +27,4 @@
|
|||||||
|
|
||||||
<include layout="@layout/alt_badge" />
|
<include layout="@layout/alt_badge" />
|
||||||
|
|
||||||
</org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout>
|
</FrameLayout>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:background="?android:actionBarItemBackground"
|
android:background="?android:actionBarItemBackground"
|
||||||
android:contentDescription="@string/sk_delete_notification"
|
android:contentDescription="@string/sk_delete_notification"
|
||||||
|
android:tooltipText="@string/sk_delete_notification"
|
||||||
android:scaleType="center"
|
android:scaleType="center"
|
||||||
android:src="@drawable/ic_fluent_dismiss_20_filled"
|
android:src="@drawable/ic_fluent_dismiss_20_filled"
|
||||||
android:tint="?android:textColorSecondary" />
|
android:tint="?android:textColorSecondary" />
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout 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">
|
||||||
|
|
||||||
@@ -12,4 +13,4 @@
|
|||||||
|
|
||||||
<include layout="@layout/alt_badge" />
|
<include layout="@layout/alt_badge" />
|
||||||
|
|
||||||
</org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout>
|
</FrameLayout>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<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:id="@+id/frame"
|
||||||
android:layout_marginBottom="-12dp">
|
android:layout_marginBottom="-12dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout 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">
|
||||||
|
|
||||||
@@ -20,4 +20,4 @@
|
|||||||
|
|
||||||
<include layout="@layout/alt_badge" />
|
<include layout="@layout/alt_badge" />
|
||||||
|
|
||||||
</org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout>
|
</FrameLayout>
|
||||||
|
|||||||
@@ -101,9 +101,9 @@
|
|||||||
android:id="@+id/self_extra_text"
|
android:id="@+id/self_extra_text"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="2sp"
|
|
||||||
android:layout_marginStart="8sp"
|
android:layout_marginStart="8sp"
|
||||||
android:layout_toEndOf="@id/self_name"
|
android:layout_toEndOf="@id/self_name"
|
||||||
|
android:paddingTop="4sp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:fontFamily="sans-serif"
|
android:fontFamily="sans-serif"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
android:inputType="textFilter|textNoSuggestions"
|
android:inputType="textUri|textNoSuggestions"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:imeOptions="actionGo"
|
android:imeOptions="actionGo"
|
||||||
android:drawableStart="@drawable/ic_fluent_globe_20_regular"
|
android:drawableStart="@drawable/ic_fluent_globe_20_regular"
|
||||||
|
|||||||
77
mastodon/src/main/res/layout/overlay_image_alt_text.xml
Normal file
77
mastodon/src/main/res/layout/overlay_image_alt_text.xml
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<?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:id="@+id/alt_text_wrapper"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|bottom"
|
||||||
|
android:layout_margin="12dp"
|
||||||
|
android:importantForAccessibility="noHideDescendants"
|
||||||
|
android:background="@drawable/bg_image_alt_overlay">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/no_alt_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:src="@drawable/ic_fluent_important_20_filled"
|
||||||
|
android:tint="?colorGray25" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/alt_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="@style/m3_label_large"
|
||||||
|
android:textColor="?colorGray25"
|
||||||
|
android:gravity="center"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:paddingHorizontal="5dp"
|
||||||
|
android:paddingVertical="1dp"
|
||||||
|
android:text="@string/sk_alt_button"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/alt_text_close"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_gravity="end|top"
|
||||||
|
android:src="@drawable/ic_baseline_close_24"
|
||||||
|
android:tint="#FFF"
|
||||||
|
android:background="?android:actionBarItemBackground"/>
|
||||||
|
|
||||||
|
<org.joinmastodon.android.ui.views.NestableScrollView
|
||||||
|
android:id="@+id/alt_text_scroller"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginEnd="40dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/alt_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:textAppearance="@style/m3_body_medium"
|
||||||
|
android:textColor="#FFF"
|
||||||
|
tools:text="Alt text goes here"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/no_alt_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginVertical="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginStart="14dp"
|
||||||
|
android:textAppearance="@style/m3_label_large"
|
||||||
|
android:textColor="?colorGray25"
|
||||||
|
android:text="@string/sk_no_alt_text"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</org.joinmastodon.android.ui.views.NestableScrollView>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:id="@+id/open_with_account" android:title="@string/sk_open_with_account" android:icon="@drawable/ic_fluent_person_swap_24_regular">
|
||||||
|
<menu android:id="@+id/accounts" />
|
||||||
|
</item>
|
||||||
<item android:id="@+id/share" android:title="@string/share_user" android:icon="@drawable/ic_fluent_share_24_regular"/>
|
<item android:id="@+id/share" android:title="@string/share_user" android:icon="@drawable/ic_fluent_share_24_regular"/>
|
||||||
<item android:id="@+id/mute" android:title="@string/mute_user" android:icon="@drawable/ic_fluent_speaker_off_24_regular"/>
|
<item android:id="@+id/mute" android:title="@string/mute_user" android:icon="@drawable/ic_fluent_speaker_off_24_regular"/>
|
||||||
<item android:id="@+id/block" android:title="@string/block_user" android:icon="@drawable/ic_fluent_person_prohibited_24_regular"/>
|
<item android:id="@+id/block" android:title="@string/block_user" android:icon="@drawable/ic_fluent_person_prohibited_24_regular"/>
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:id="@+id/followed_hashtags" android:title="@string/sk_hashtags_you_follow" android:icon="@drawable/ic_fluent_number_symbol_24_regular" android:showAsAction="always"/>
|
<item android:id="@+id/followed_hashtags" android:title="@string/sk_hashtags_you_follow" android:icon="@drawable/ic_fluent_number_symbol_24_regular" android:showAsAction="always"/>
|
||||||
<item android:id="@+id/bookmarks" android:title="@string/bookmarks" android:icon="@drawable/ic_fluent_bookmark_multiple_24_regular" android:showAsAction="always"/>
|
<item android:id="@+id/bookmarks" android:title="@string/bookmarks" android:icon="@drawable/ic_fluent_bookmark_multiple_24_regular" android:showAsAction="always"/>
|
||||||
|
<item android:id="@+id/open_with_account" android:title="@string/sk_open_with_account" android:icon="@drawable/ic_fluent_person_swap_24_regular">
|
||||||
|
<menu android:id="@+id/accounts" />
|
||||||
|
</item>
|
||||||
<item android:id="@+id/manage_user_lists" android:title="@string/sk_your_lists" android:icon="@drawable/ic_fluent_people_24_regular"/>
|
<item android:id="@+id/manage_user_lists" android:title="@string/sk_your_lists" android:icon="@drawable/ic_fluent_people_24_regular"/>
|
||||||
<item android:id="@+id/favorites" android:title="@string/your_favorites" android:icon="@drawable/ic_fluent_star_24_regular"/>
|
<item android:id="@+id/favorites" android:title="@string/your_favorites" android:icon="@drawable/ic_fluent_star_24_regular"/>
|
||||||
<item android:id="@+id/scheduled" android:title="@string/sk_unsent_posts" android:icon="@drawable/ic_fluent_folder_open_24_regular"/>
|
<item android:id="@+id/scheduled" android:title="@string/sk_unsent_posts" android:icon="@drawable/ic_fluent_folder_open_24_regular"/>
|
||||||
|
|||||||
@@ -258,4 +258,7 @@
|
|||||||
<string name="sk_unfinished_attachments">Anhänge richtig stellen\?</string>
|
<string name="sk_unfinished_attachments">Anhänge richtig stellen\?</string>
|
||||||
<string name="sk_settings_hide_interaction">Interaktions-Buttons verstecken</string>
|
<string name="sk_settings_hide_interaction">Interaktions-Buttons verstecken</string>
|
||||||
<string name="sk_unfinished_attachments_message">Einige Anhänge sind nicht fertig hochgeladen.</string>
|
<string name="sk_unfinished_attachments_message">Einige Anhänge sind nicht fertig hochgeladen.</string>
|
||||||
|
<string name="sk_followed_as">Mit %s gefolgt</string>
|
||||||
|
<string name="sk_settings_hide_fab">Verfassen-Button automatisch ausblenden</string>
|
||||||
|
<string name="sk_follow_as">Mit anderem Account folgen</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -258,4 +258,5 @@
|
|||||||
<string name="sk_unfinished_attachments_message">Algunos adjuntos no han terminado de subirse.</string>
|
<string name="sk_unfinished_attachments_message">Algunos adjuntos no han terminado de subirse.</string>
|
||||||
<string name="sk_settings_prefix_reply_cw_with_re">Añadir \"re:\" a respuestas a Advertencias de Contenido</string>
|
<string name="sk_settings_prefix_reply_cw_with_re">Añadir \"re:\" a respuestas a Advertencias de Contenido</string>
|
||||||
<string name="sk_spectator_mode">Modo espectador</string>
|
<string name="sk_spectator_mode">Modo espectador</string>
|
||||||
|
<string name="sk_settings_hide_interaction">Ocultar los botones interactivos</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -252,10 +252,11 @@
|
|||||||
<string name="sk_notify_poll_results">Resultados da enquisa</string>
|
<string name="sk_notify_poll_results">Resultados da enquisa</string>
|
||||||
<string name="sk_collapse">Contraer</string>
|
<string name="sk_collapse">Contraer</string>
|
||||||
<string name="sk_unfinished_attachments_message">Algúns arquivos adxuntos aínda non remataron de cargarse.</string>
|
<string name="sk_unfinished_attachments_message">Algúns arquivos adxuntos aínda non remataron de cargarse.</string>
|
||||||
<string name="sk_settings_prefix_reply_cw_with_re">Prefixo resposta CW con \"re:\"</string>
|
<string name="sk_settings_prefix_reply_cw_with_re">Prefixar a resposta de AC con \"re:\"</string>
|
||||||
<string name="sk_filtered">Filtrados: %s</string>
|
<string name="sk_filtered">Filtrados: %s</string>
|
||||||
<string name="sk_expand">Expandir</string>
|
<string name="sk_expand">Expandir</string>
|
||||||
<string name="sk_settings_collapse_long_posts">Contraer publicacións moi longas</string>
|
<string name="sk_settings_collapse_long_posts">Contraer publicacións moi longas</string>
|
||||||
<string name="sk_unfinished_attachments">Arranxar arquivos adxuntos\?</string>
|
<string name="sk_unfinished_attachments">Arranxar arquivos adxuntos\?</string>
|
||||||
<string name="sk_spectator_mode">Modo espectador</string>
|
<string name="sk_spectator_mode">Modo espectador</string>
|
||||||
|
<string name="sk_settings_hide_interaction">Ocultar botóns de interacción</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -258,4 +258,5 @@
|
|||||||
<string name="sk_local_only">로컬 인스턴스 전용</string>
|
<string name="sk_local_only">로컬 인스턴스 전용</string>
|
||||||
<string name="sk_settings_collapse_long_posts">아주 긴 게시물 접기</string>
|
<string name="sk_settings_collapse_long_posts">아주 긴 게시물 접기</string>
|
||||||
<string name="sk_spectator_mode">관객 모드</string>
|
<string name="sk_spectator_mode">관객 모드</string>
|
||||||
|
<string name="sk_settings_hide_interaction">상호작용 버튼 가리기</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -257,4 +257,5 @@
|
|||||||
<string name="sk_unfinished_attachments_message">Niektóre załączniki jeszcze nie zostały przesłane.</string>
|
<string name="sk_unfinished_attachments_message">Niektóre załączniki jeszcze nie zostały przesłane.</string>
|
||||||
<string name="sk_settings_prefix_reply_cw_with_re">Zaczynaj CW odpowiedzi od “re:”</string>
|
<string name="sk_settings_prefix_reply_cw_with_re">Zaczynaj CW odpowiedzi od “re:”</string>
|
||||||
<string name="sk_settings_collapse_long_posts">Zwijaj bardzo długie posty</string>
|
<string name="sk_settings_collapse_long_posts">Zwijaj bardzo długie posty</string>
|
||||||
|
<string name="sk_settings_hide_interaction">Ukryj przyciski interakcji</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -258,4 +258,5 @@
|
|||||||
<string name="sk_settings_collapse_long_posts">Згортати надто довгі дописи</string>
|
<string name="sk_settings_collapse_long_posts">Згортати надто довгі дописи</string>
|
||||||
<string name="sk_unfinished_attachments_message">Деякі вкладення не повністю завантажилися.</string>
|
<string name="sk_unfinished_attachments_message">Деякі вкладення не повністю завантажилися.</string>
|
||||||
<string name="sk_spectator_mode">Режим глядача</string>
|
<string name="sk_spectator_mode">Режим глядача</string>
|
||||||
|
<string name="sk_settings_hide_interaction">Сховати кнопки взаємодії</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<string name="sk_federated_timeline_info_banner">这是在你的联邦宇宙中最新发布的嘟文。</string>
|
<string name="sk_federated_timeline_info_banner">这是在你的联邦宇宙中最新发布的嘟文。</string>
|
||||||
<string name="sk_app_name">Megalodon</string>
|
<string name="sk_app_name">Megalodon</string>
|
||||||
<string name="sk_settings_show_replies">显示回复</string>
|
<string name="sk_settings_show_replies">显示回复</string>
|
||||||
<string name="sk_settings_show_boosts">显示转发</string>
|
<string name="sk_settings_show_boosts">显示转嘟</string>
|
||||||
<string name="sk_settings_load_new_posts">自动加载新嘟文</string>
|
<string name="sk_settings_load_new_posts">自动加载新嘟文</string>
|
||||||
<string name="sk_settings_show_interaction_counts">显示互动次数</string>
|
<string name="sk_settings_show_interaction_counts">显示互动次数</string>
|
||||||
<string name="sk_settings_app_version">Megalodon v%1$s (%2$d)</string>
|
<string name="sk_settings_app_version">Megalodon v%1$s (%2$d)</string>
|
||||||
@@ -82,8 +82,8 @@
|
|||||||
<string name="sk_clear_all_notifications_confirm_action">删除所有</string>
|
<string name="sk_clear_all_notifications_confirm_action">删除所有</string>
|
||||||
<string name="sk_clear_all_notifications_confirm">您确定要清除所有通知吗?</string>
|
<string name="sk_clear_all_notifications_confirm">您确定要清除所有通知吗?</string>
|
||||||
<string name="sk_loading_fediverse_resource_title">在联邦宇宙上查找它</string>
|
<string name="sk_loading_fediverse_resource_title">在联邦宇宙上查找它</string>
|
||||||
<string name="sk_undo_reblog">撤销转发</string>
|
<string name="sk_undo_reblog">撤销转嘟</string>
|
||||||
<string name="sk_reblog_with_visibility">转发可见性</string>
|
<string name="sk_reblog_with_visibility">转嘟可见性</string>
|
||||||
<string name="sk_quote_post">关于这个嘟文</string>
|
<string name="sk_quote_post">关于这个嘟文</string>
|
||||||
<string name="sk_copy_link_to_post">复制链接到嘟文</string>
|
<string name="sk_copy_link_to_post">复制链接到嘟文</string>
|
||||||
<string name="sk_hashtags_you_follow">您关注的标签</string>
|
<string name="sk_hashtags_you_follow">您关注的标签</string>
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
<string name="sk_favorite_as">用其他账号收藏</string>
|
<string name="sk_favorite_as">用其他账号收藏</string>
|
||||||
<string name="sk_favorited_as">收藏为 %s</string>
|
<string name="sk_favorited_as">收藏为 %s</string>
|
||||||
<string name="sk_already_favorited">已收藏</string>
|
<string name="sk_already_favorited">已收藏</string>
|
||||||
<string name="sk_reblog_as">用其他账号转发</string>
|
<string name="sk_reblog_as">用其他账号转嘟</string>
|
||||||
<string name="sk_reblogged_as">重新登录为 %s</string>
|
<string name="sk_reblogged_as">重新登录为 %s</string>
|
||||||
<string name="sk_already_reblogged">已重新登录</string>
|
<string name="sk_already_reblogged">已重新登录</string>
|
||||||
<string name="sk_reply_as">用其他帐号回复</string>
|
<string name="sk_reply_as">用其他帐号回复</string>
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
<string name="sk_icon_headphones">耳机</string>
|
<string name="sk_icon_headphones">耳机</string>
|
||||||
<string name="sk_icon_human">人类</string>
|
<string name="sk_icon_human">人类</string>
|
||||||
<string name="sk_icon_globe">地球</string>
|
<string name="sk_icon_globe">地球</string>
|
||||||
<string name="sk_notify_update">编辑转发的嘟文</string>
|
<string name="sk_notify_update">编辑了你转嘟的嘟文</string>
|
||||||
<string name="sk_icon_pin">钉子</string>
|
<string name="sk_icon_pin">钉子</string>
|
||||||
<string name="sk_remove_follower_confirm">通过屏蔽并立即解除屏蔽以移除%s的关注者身份?</string>
|
<string name="sk_remove_follower_confirm">通过屏蔽并立即解除屏蔽以移除%s的关注者身份?</string>
|
||||||
<string name="sk_icon_clapper_board">拍板</string>
|
<string name="sk_icon_clapper_board">拍板</string>
|
||||||
@@ -259,4 +259,5 @@
|
|||||||
<string name="sk_settings_collapse_long_posts">折叠很长的嘟文</string>
|
<string name="sk_settings_collapse_long_posts">折叠很长的嘟文</string>
|
||||||
<string name="sk_settings_prefix_reply_cw_with_re">在回复带有内容警告的嘟文前加上 \"re:\"</string>
|
<string name="sk_settings_prefix_reply_cw_with_re">在回复带有内容警告的嘟文前加上 \"re:\"</string>
|
||||||
<string name="sk_spectator_mode">旁观模式</string>
|
<string name="sk_spectator_mode">旁观模式</string>
|
||||||
|
<string name="sk_settings_hide_interaction">隐藏互动按钮</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -259,4 +259,8 @@
|
|||||||
<string name="sk_unfinished_attachments">Fix attachments?</string>
|
<string name="sk_unfinished_attachments">Fix attachments?</string>
|
||||||
<string name="sk_unfinished_attachments_message">Some attachments haven’t finished uploading.</string>
|
<string name="sk_unfinished_attachments_message">Some attachments haven’t finished uploading.</string>
|
||||||
<string name="sk_settings_hide_interaction">Hide interaction buttons</string>
|
<string name="sk_settings_hide_interaction">Hide interaction buttons</string>
|
||||||
|
<string name="sk_follow_as">Follow from other account</string>
|
||||||
|
<string name="sk_followed_as">Followed from %s</string>
|
||||||
|
<string name="sk_settings_hide_fab">Auto-hide Compose button</string>
|
||||||
|
<string name="sk_in_reply">In reply</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -6,11 +6,13 @@
|
|||||||
<locale android:name="bs-BA"/>
|
<locale android:name="bs-BA"/>
|
||||||
<locale android:name="ca-ES"/>
|
<locale android:name="ca-ES"/>
|
||||||
<locale android:name="cs-CZ"/>
|
<locale android:name="cs-CZ"/>
|
||||||
|
<locale android:name="da-DK"/>
|
||||||
<locale android:name="de-DE"/>
|
<locale android:name="de-DE"/>
|
||||||
<locale android:name="el-GR"/>
|
<locale android:name="el-GR"/>
|
||||||
<locale android:name="en"/>
|
<locale android:name="en"/>
|
||||||
<locale android:name="es-ES"/>
|
<locale android:name="es-ES"/>
|
||||||
<locale android:name="eu-ES"/>
|
<locale android:name="eu-ES"/>
|
||||||
|
<locale android:name="fa-IR"/>
|
||||||
<locale android:name="fi-FI"/>
|
<locale android:name="fi-FI"/>
|
||||||
<locale android:name="fil-PH"/>
|
<locale android:name="fil-PH"/>
|
||||||
<locale android:name="fr-FR"/>
|
<locale android:name="fr-FR"/>
|
||||||
@@ -21,6 +23,7 @@
|
|||||||
<locale android:name="hr-HR"/>
|
<locale android:name="hr-HR"/>
|
||||||
<locale android:name="hu-HU"/>
|
<locale android:name="hu-HU"/>
|
||||||
<locale android:name="hy-AM"/>
|
<locale android:name="hy-AM"/>
|
||||||
|
<locale android:name="ig-NG"/>
|
||||||
<locale android:name="in-ID"/>
|
<locale android:name="in-ID"/>
|
||||||
<locale android:name="is-IS"/>
|
<locale android:name="is-IS"/>
|
||||||
<locale android:name="it-IT"/>
|
<locale android:name="it-IT"/>
|
||||||
@@ -28,7 +31,9 @@
|
|||||||
<locale android:name="ja-JP"/>
|
<locale android:name="ja-JP"/>
|
||||||
<locale android:name="kab"/>
|
<locale android:name="kab"/>
|
||||||
<locale android:name="ko-KR"/>
|
<locale android:name="ko-KR"/>
|
||||||
|
<locale android:name="my-MM"/>
|
||||||
<locale android:name="nl-NL"/>
|
<locale android:name="nl-NL"/>
|
||||||
|
<locale android:name="no-NO"/>
|
||||||
<locale android:name="oc-FR"/>
|
<locale android:name="oc-FR"/>
|
||||||
<locale android:name="pl-PL"/>
|
<locale android:name="pl-PL"/>
|
||||||
<locale android:name="pt-BR"/>
|
<locale android:name="pt-BR"/>
|
||||||
|
|||||||
4
metadata/de-DE/changelogs/77.txt
Normal file
4
metadata/de-DE/changelogs/77.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
- Folgen-Button lange drücken, um Profil von anderem Account zu folgen
|
||||||
|
- Option, um Profile mit anderem Account zu öffnen
|
||||||
|
- Verfassen-Button wird beim Scrollen automatisch ausgeblendet
|
||||||
|
- Crash beim Öffnen von Admin-Accounts behoben
|
||||||
4
metadata/en-US/changelogs/77.txt
Normal file
4
metadata/en-US/changelogs/77.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
- Long-press follow button to follow profiles from other account
|
||||||
|
- Option to open profiles in other account
|
||||||
|
- Auto-hide compose button when scrolling down the timeline
|
||||||
|
- Fix crash when opening server admin's profiles
|
||||||
6
metadata/es/changelogs/76.txt
Normal file
6
metadata/es/changelogs/76.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
- Filtros mejorados, incluida la compatibilidad con la opción "Ocultar con advertencia"
|
||||||
|
- Página de perfil rediseñada con los metadatos directamente debajo de la biografía.
|
||||||
|
- Función de contraer/expandir para mensajes muy largos.
|
||||||
|
- Opción de anteponer automáticamente el prefijo "re:" a los mensajes de respuesta.
|
||||||
|
- Opción de ocultar los botones de interacción en la línea de tiempo
|
||||||
|
- Varias correcciones de errores, ajustes y mejoras
|
||||||
6
metadata/gl-ES/changelogs/76.txt
Normal file
6
metadata/gl-ES/changelogs/76.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
- Filtros mellorados, incluíndo a compatibilidade co filtro "Ocultar con aviso"
|
||||||
|
- Páxina de perfil redeseñada coa metadata diretamente debaixo da biografía
|
||||||
|
- Función de contraer/expander publicacións moi longas
|
||||||
|
- Opción de prefixar con "re:" automaticamente as respostas a ACs (avisos de contido)
|
||||||
|
- Opción para ocultar automáticamente os botóns de interación na cronolixía
|
||||||
|
- Varias correccións de erros, axustes e melloras
|
||||||
6
metadata/ko/changelogs/76.txt
Normal file
6
metadata/ko/changelogs/76.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
- “경고와 함께 숨기기” 호환성 등이 개선된 필터
|
||||||
|
- 바이오 바로 밑에 메타데이터가 보이는 리디자인된 프로필 페이지
|
||||||
|
- 아주 긴 게시물을 위한 접기/펴기 기능
|
||||||
|
- 열람주의 게시물에 답글을 달 때 자동으로 접두사 “re:”를 추가하는 옵션
|
||||||
|
- 타임라인에서 상호작용 버튼을 가릴 수 있는 옵션
|
||||||
|
- 다양한 버그 수정 및 기능 개선
|
||||||
6
metadata/pl/changelogs/76.txt
Normal file
6
metadata/pl/changelogs/76.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
- Ulepszone filtry, w tym kompatybilność z "Ukryj z ostrzeżeniem"
|
||||||
|
- Przeprojektowana strona profilu z metadanymi bezpośrednio pod bio
|
||||||
|
- Funkcja zwijania/rozwijania dla bardzo długich postów
|
||||||
|
- Opcja automatycznego prefiksowania odpowiedzi CW z "re:"
|
||||||
|
- Opcja ukrywania przycisków interakcji na osi czasu
|
||||||
|
- Różne poprawki błędów, usprawnienia i ulepszenia
|
||||||
6
metadata/uk/changelogs/76.txt
Normal file
6
metadata/uk/changelogs/76.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
- Удосконалено фільтри, включно з сумісними зі «Сховати з попередженням»
|
||||||
|
- Перероблена сторінка профілю з метаданими безпосередньо під біографією
|
||||||
|
- Функція згортання/розгортання дуже довгих повідомлень
|
||||||
|
- Опція автоматичного додавання префікса «re:» до відповідей на CW-повідомлення
|
||||||
|
- Можливість сховати кнопки взаємодії на часовій шкалі
|
||||||
|
- Різноманітні виправлення, зміни та поліпшення
|
||||||
Reference in New Issue
Block a user