Merge remote-tracking branch 'upstream/master' into try-to-merge-upstream
This commit is contained in:
@@ -15,6 +15,7 @@ import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
@@ -53,7 +54,8 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
||||
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, CharSequence fullText, Status status) {
|
||||
super(parentID, parentFragment);
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder(text);
|
||||
HtmlParser.parseCustomEmoji(ssb, emojis);
|
||||
if(AccountSessionManager.get(parentFragment.getAccountID()).getLocalPreferences().customEmojiInNames)
|
||||
HtmlParser.parseCustomEmoji(ssb, emojis);
|
||||
this.text=ssb;
|
||||
emojiHelper.setText(ssb);
|
||||
this.icon=icon;
|
||||
|
||||
@@ -180,10 +180,8 @@ public abstract class StatusDisplayItem{
|
||||
.ifPresent(hashtag -> items.add(new ReblogOrReplyLineStatusDisplayItem(
|
||||
parentID, fragment, hashtag.name, List.of(),
|
||||
R.drawable.ic_fluent_number_symbol_20sp_filled, null,
|
||||
i -> {
|
||||
args.putString("hashtag", hashtag.name);
|
||||
Nav.go(fragment.getActivity(), HashtagTimelineFragment.class, args);
|
||||
}, status
|
||||
i->UiUtils.openHashtagTimeline(fragment.getActivity(), accountID, hashtag),
|
||||
status
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
@@ -878,6 +878,8 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
|
||||
@Override
|
||||
public void onVideoSizeChanged(MediaPlayer mp, int width, int height){
|
||||
if(width<=0 || height<=0)
|
||||
return;
|
||||
FrameLayout.LayoutParams params=(FrameLayout.LayoutParams) wrap.getLayoutParams();
|
||||
params.width=width;
|
||||
params.height=height;
|
||||
|
||||
@@ -119,6 +119,9 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
|
||||
|
||||
int width=right-left;
|
||||
int height=bottom-top;
|
||||
if(width==0 || height==0)
|
||||
return;
|
||||
|
||||
float scale=Math.min(width/(float)child.getWidth(), height/(float)child.getHeight());
|
||||
minScale=scale;
|
||||
maxScale=Math.max(3f, height/(float)child.getHeight());
|
||||
@@ -306,8 +309,6 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
|
||||
}, 1f).setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_ALPHA));
|
||||
}
|
||||
}else{
|
||||
if(animatingTransition)
|
||||
Log.w(TAG, "updateViewTransform: ", new Throwable().fillInStackTrace());
|
||||
child.setScaleX(matrixValues[Matrix.MSCALE_X]);
|
||||
child.setScaleY(matrixValues[Matrix.MSCALE_Y]);
|
||||
child.setTranslationX(matrixValues[Matrix.MTRANS_X]);
|
||||
|
||||
@@ -100,9 +100,10 @@ public class HtmlParser{
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> idsByUrl=mentions.stream().collect(Collectors.toMap(m->m.url, m->m.id));
|
||||
Map<String, String> idsByUrl=mentions.stream().distinct().collect(Collectors.toMap(m->m.url, m->m.id));
|
||||
// Hashtags in remote posts have remote URLs, these have local URLs so they don't match.
|
||||
// Map<String, String> tagsByUrl=tags.stream().collect(Collectors.toMap(t->t.url, t->t.name));
|
||||
Map<String, Hashtag> tagsByTag=tags.stream().distinct().collect(Collectors.toMap(t->t.name.toLowerCase(), Function.identity()));
|
||||
|
||||
final SpannableStringBuilder ssb=new SpannableStringBuilder();
|
||||
Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){
|
||||
@@ -115,6 +116,7 @@ public class HtmlParser{
|
||||
}else if(node instanceof Element el){
|
||||
switch(el.nodeName()){
|
||||
case "a" -> {
|
||||
Object linkObject=null;
|
||||
String href=el.attr("href");
|
||||
LinkSpan.Type linkType;
|
||||
String text=el.text();
|
||||
@@ -122,6 +124,7 @@ public class HtmlParser{
|
||||
if(text.startsWith("#")){
|
||||
linkType=LinkSpan.Type.HASHTAG;
|
||||
href=text.substring(1);
|
||||
linkObject=tagsByTag.get(text.substring(1).toLowerCase());
|
||||
}else{
|
||||
linkType=LinkSpan.Type.URL;
|
||||
}
|
||||
@@ -136,7 +139,7 @@ public class HtmlParser{
|
||||
}else{
|
||||
linkType=LinkSpan.Type.URL;
|
||||
}
|
||||
openSpans.add(new SpanInfo(new LinkSpan(href, null, linkType, accountID), ssb.length(), el));
|
||||
openSpans.add(new SpanInfo(new LinkSpan(href, null, linkType, accountID, linkObject, text), ssb.length(), el));
|
||||
}
|
||||
case "br" -> ssb.append('\n');
|
||||
case "span" -> {
|
||||
@@ -271,7 +274,7 @@ public class HtmlParser{
|
||||
String url=matcher.group(3);
|
||||
if(TextUtils.isEmpty(matcher.group(4)))
|
||||
url="http://"+url;
|
||||
ssb.setSpan(new LinkSpan(url, null, LinkSpan.Type.URL, null), matcher.start(3), matcher.end(3), 0);
|
||||
ssb.setSpan(new LinkSpan(url, null, LinkSpan.Type.URL, null, null, null), matcher.start(3), matcher.end(3), 0);
|
||||
}while(matcher.find()); // Find more URLs
|
||||
return ssb;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.text.TextPaint;
|
||||
import android.text.style.CharacterStyle;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
public class LinkSpan extends CharacterStyle {
|
||||
@@ -14,17 +15,15 @@ public class LinkSpan extends CharacterStyle {
|
||||
private String link;
|
||||
private Type type;
|
||||
private String accountID;
|
||||
private Object linkObject;
|
||||
private String text;
|
||||
|
||||
public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID){
|
||||
this(link, listener, type, accountID, null);
|
||||
}
|
||||
|
||||
public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID, String text){
|
||||
public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID, Object linkObject, String text){
|
||||
this.listener=listener;
|
||||
this.link=link;
|
||||
this.type=type;
|
||||
this.accountID=accountID;
|
||||
this.linkObject=linkObject;
|
||||
this.text=text;
|
||||
}
|
||||
|
||||
@@ -42,7 +41,12 @@ public class LinkSpan extends CharacterStyle {
|
||||
switch(getType()){
|
||||
case URL -> UiUtils.openURL(context, accountID, link);
|
||||
case MENTION -> UiUtils.openProfileByID(context, accountID, link);
|
||||
case HASHTAG -> UiUtils.openHashtagTimeline(context, accountID, link, null);
|
||||
case HASHTAG -> {
|
||||
if(linkObject instanceof Hashtag ht)
|
||||
UiUtils.openHashtagTimeline(context, accountID, ht);
|
||||
else
|
||||
UiUtils.openHashtagTimeline(context, accountID, text);
|
||||
}
|
||||
case CUSTOM -> listener.onLinkClick(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +104,7 @@ import org.joinmastodon.android.model.AccountField;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.model.ScheduledStatus;
|
||||
import org.joinmastodon.android.model.SearchResults;
|
||||
@@ -445,12 +446,18 @@ public class UiUtils {
|
||||
Nav.go((Activity) context, ProfileFragment.class, args);
|
||||
}
|
||||
|
||||
public static void openHashtagTimeline(Context context, String accountID, String hashtag, @Nullable Boolean following) {
|
||||
Bundle args = new Bundle();
|
||||
public static void openHashtagTimeline(Context context, String accountID, Hashtag hashtag){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putString("hashtag", hashtag);
|
||||
if (following != null) args.putBoolean("following", following);
|
||||
Nav.go((Activity) context, HashtagTimelineFragment.class, args);
|
||||
args.putParcelable("hashtag", Parcels.wrap(hashtag));
|
||||
Nav.go((Activity)context, HashtagTimelineFragment.class, args);
|
||||
}
|
||||
|
||||
public static void openHashtagTimeline(Context context, String accountID, String hashtag){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putString("hashtagName", hashtag);
|
||||
Nav.go((Activity)context, HashtagTimelineFragment.class, args);
|
||||
}
|
||||
|
||||
public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, Runnable onConfirmed) {
|
||||
@@ -1157,7 +1164,7 @@ public class UiUtils {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(new GetSearchResults(query.getQuery(), type, true).setCallback(new Callback<>() {
|
||||
return Optional.of(new GetSearchResults(query.getQuery(), type, true, null, 0, 0).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(SearchResults results) {
|
||||
Optional<T> result = extractResult.apply(results);
|
||||
@@ -1254,7 +1261,7 @@ public class UiUtils {
|
||||
}
|
||||
public static MastodonAPIRequest<SearchResults> lookupAccountHandle(Context context, String accountID, Pair<String, Optional<String>> queryHandle, BiConsumer<Class<? extends Fragment>, Bundle> go) {
|
||||
String fullHandle = ("@" + queryHandle.first) + (queryHandle.second.map(domain -> "@" + domain).orElse(""));
|
||||
return new GetSearchResults(fullHandle, GetSearchResults.Type.ACCOUNTS, true)
|
||||
return new GetSearchResults(fullHandle, GetSearchResults.Type.ACCOUNTS, true, null, 0, 0)
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(SearchResults results) {
|
||||
@@ -1317,7 +1324,7 @@ public class UiUtils {
|
||||
})
|
||||
.execNoAuth(uri.getHost()));
|
||||
} else if (looksLikeFediverseUrl(url)) {
|
||||
return Optional.of(new GetSearchResults(url, null, true)
|
||||
return Optional.of(new GetSearchResults(url, null, true, null, 0, 0)
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(SearchResults results) {
|
||||
|
||||
@@ -15,15 +15,12 @@ import android.widget.TextView;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.model.SearchResults;
|
||||
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.FilterChipView;
|
||||
@@ -96,6 +93,24 @@ public class ComposeAutocompleteViewController{
|
||||
outRect.right=V.dp(8);
|
||||
}
|
||||
});
|
||||
// Set empty adapter to prevent NPEs
|
||||
list.setAdapter(new RecyclerView.Adapter<>(){
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
contentView.addView(list, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
emptyButton=new FilterChipView(activity);
|
||||
@@ -222,11 +237,13 @@ public class ComposeAutocompleteViewController{
|
||||
}
|
||||
|
||||
private void doSearchUsers(){
|
||||
currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.ACCOUNTS, false)
|
||||
currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.ACCOUNTS, false, null, 0, 0)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(SearchResults result){
|
||||
currentRequest=null;
|
||||
if(mode!=Mode.USERS)
|
||||
return;
|
||||
List<AccountViewModel> oldList=users;
|
||||
users=result.accounts.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList());
|
||||
if(isLoading){
|
||||
@@ -256,7 +273,7 @@ public class ComposeAutocompleteViewController{
|
||||
}
|
||||
|
||||
private void doSearchHashtags(){
|
||||
currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.HASHTAGS, false)
|
||||
currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.HASHTAGS, false, null, 0, 0)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(SearchResults result){
|
||||
|
||||
@@ -75,14 +75,14 @@ public class ComposePollViewController{
|
||||
|
||||
Instance instance=fragment.instance;
|
||||
if (!instance.isAkkoma()) {
|
||||
if(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0)
|
||||
if(instance!=null && instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0)
|
||||
maxPollOptions=instance.configuration.polls.maxOptions;
|
||||
if(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0)
|
||||
if(instance!=null && instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0)
|
||||
maxPollOptionLength=instance.configuration.polls.maxCharactersPerOption;
|
||||
} else {
|
||||
if (instance.pollLimits!=null && instance.pollLimits.maxOptions>0)
|
||||
if(instance!=null && instance.pollLimits!=null && instance.pollLimits.maxOptions>0)
|
||||
maxPollOptions=instance.pollLimits.maxOptions;
|
||||
if(instance.pollLimits!=null && instance.pollLimits.maxOptionChars>0)
|
||||
if(instance!=null && instance.pollLimits!=null && instance.pollLimits.maxOptionChars>0)
|
||||
maxPollOptionLength=instance.pollLimits.maxOptionChars;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ public class MediaGridLayout extends ViewGroup{
|
||||
|
||||
private static final int GAP=2; // dp
|
||||
private PhotoLayoutHelper.TiledLayoutResult tiledLayout;
|
||||
private int[] columnStarts=new int[10], columnEnds=new int[10], rowStarts=new int[10], rowEnds=new int[10];
|
||||
private int[] columnStarts, columnEnds, rowStarts, rowEnds;
|
||||
|
||||
public MediaGridLayout(Context context){
|
||||
this(context, null);
|
||||
@@ -41,6 +41,14 @@ public class MediaGridLayout extends ViewGroup{
|
||||
width=Math.round(width*(tiledLayout.width/(float)PhotoLayoutHelper.MAX_WIDTH));
|
||||
}
|
||||
|
||||
if(rowStarts==null || rowStarts.length<tiledLayout.rowSizes.length){
|
||||
rowStarts=new int[tiledLayout.rowSizes.length];
|
||||
rowEnds=new int[tiledLayout.rowSizes.length];
|
||||
}
|
||||
if(columnStarts==null || columnStarts.length<tiledLayout.columnSizes.length){
|
||||
columnStarts=new int[tiledLayout.columnSizes.length];
|
||||
columnEnds=new int[tiledLayout.columnSizes.length];
|
||||
}
|
||||
int offset=0;
|
||||
for(int i=0;i<tiledLayout.columnSizes.length;i++){
|
||||
columnStarts[i]=offset;
|
||||
@@ -73,7 +81,7 @@ public class MediaGridLayout extends ViewGroup{
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b){
|
||||
if(tiledLayout==null)
|
||||
if(tiledLayout==null || rowStarts==null)
|
||||
return;
|
||||
|
||||
int maxWidth=UiUtils.MAX_WIDTH;
|
||||
|
||||
Reference in New Issue
Block a user