From 41682d1147078a3986d3f247167cb8a913693cb8 Mon Sep 17 00:00:00 2001 From: Torge Rosendahl Date: Wed, 15 Feb 2023 17:30:31 -0500 Subject: [PATCH 01/34] added press-and-hold listener to ClickableLinks --- .../ui/text/ClickableLinksDelegate.java | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java index 482a7eacf..6b7573a5f 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java @@ -6,11 +6,14 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; +import android.os.Handler; import android.text.Layout; import android.text.Spanned; import android.view.MotionEvent; import android.view.SoundEffectConstants; +import android.view.ViewConfiguration; import android.widget.TextView; +import android.widget.Toast; import me.grishka.appkit.utils.V; @@ -20,6 +23,7 @@ public class ClickableLinksDelegate { private Path hlPath; private LinkSpan selectedSpan; private TextView view; + private final Handler longClickHandler = new Handler(); public ClickableLinksDelegate(TextView view) { this.view=view; @@ -63,6 +67,7 @@ public class ClickableLinksDelegate { } hlPath=new Path(); selectedSpan=span; + longClickHandler.postDelayed(copyTextToClipboard, ViewConfiguration.getLongPressTimeout()); hlPaint.setColor((span.getColor() & 0x00FFFFFF) | 0x33000000); //l.getSelectionPath(start, end, hlPath); for(int j=lstart;j<=lend;j++){ @@ -90,21 +95,31 @@ public class ClickableLinksDelegate { } } if(event.getAction()==MotionEvent.ACTION_UP && selectedSpan!=null){ + longClickHandler.removeCallbacks(copyTextToClipboard); view.playSoundEffect(SoundEffectConstants.CLICK); selectedSpan.onClick(view.getContext()); - hlPath=null; - selectedSpan=null; - view.invalidate(); + resetAndInvalidate(); return false; } if(event.getAction()==MotionEvent.ACTION_CANCEL){ - hlPath=null; - selectedSpan=null; - view.invalidate(); + resetAndInvalidate(); return false; } return false; } + + Runnable copyTextToClipboard = () -> { + //TODO actually copy to clipboard + //TODO think about removing toast, system > A12 (?) has a built-in popup + Toast.makeText(view.getContext(), "copied to clipboard", Toast.LENGTH_SHORT).show(); + resetAndInvalidate(); + }; + + private void resetAndInvalidate() { + hlPath=null; + selectedSpan=null; + view.invalidate(); + } public void onDraw(Canvas canvas){ if(hlPath!=null){ From c0115f068c68fa9f0795a5dec0da2a74d2d62a08 Mon Sep 17 00:00:00 2001 From: Torge Rosendahl Date: Wed, 15 Feb 2023 17:40:43 -0500 Subject: [PATCH 02/34] implemented copy service --- .../android/ui/text/ClickableLinksDelegate.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java index 6b7573a5f..1684458e1 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java @@ -1,11 +1,15 @@ package org.joinmastodon.android.ui.text; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; import android.graphics.Canvas; import android.graphics.CornerPathEffect; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; +import android.os.Build; import android.os.Handler; import android.text.Layout; import android.text.Spanned; @@ -109,9 +113,15 @@ public class ClickableLinksDelegate { } Runnable copyTextToClipboard = () -> { - //TODO actually copy to clipboard - //TODO think about removing toast, system > A12 (?) has a built-in popup - Toast.makeText(view.getContext(), "copied to clipboard", Toast.LENGTH_SHORT).show(); + //copy link text to clipboard + ClipboardManager clipboard = (ClipboardManager) view.getContext().getSystemService(Context.CLIPBOARD_SERVICE); + clipboard.setPrimaryClip(ClipData.newPlainText("", selectedSpan.getLink())); + //show toast, android from S_V2 on has built-in popup, as documented in + //https://developer.android.com/develop/ui/views/touch-and-input/copy-paste#duplicate-notifications + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { + Toast.makeText(view.getContext(), "Copied", Toast.LENGTH_SHORT).show(); + } + //reset view resetAndInvalidate(); }; From e3486ebf7c673646ac7456c787ee17599a441eb5 Mon Sep 17 00:00:00 2001 From: Torge Rosendahl Date: Wed, 15 Feb 2023 18:00:45 -0500 Subject: [PATCH 03/34] added clickable link type switch for copy, to not copy hashtags and user IDs --- .../joinmastodon/android/ui/text/ClickableLinksDelegate.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java index 1684458e1..4ae75ca3d 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java @@ -113,6 +113,8 @@ public class ClickableLinksDelegate { } Runnable copyTextToClipboard = () -> { + //if target is not a link, don't copy + if (selectedSpan.getType() != LinkSpan.Type.URL) return; //copy link text to clipboard ClipboardManager clipboard = (ClipboardManager) view.getContext().getSystemService(Context.CLIPBOARD_SERVICE); clipboard.setPrimaryClip(ClipData.newPlainText("", selectedSpan.getLink())); From 42c6446125595c4b00970e37ac8208fa18caac06 Mon Sep 17 00:00:00 2001 From: Torge Rosendahl Date: Wed, 15 Feb 2023 18:03:40 -0500 Subject: [PATCH 04/34] refactoring: moved runnable and made it private, added copy toast localization. --- .../ui/text/ClickableLinksDelegate.java | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java index 4ae75ca3d..036d6f9af 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java @@ -19,6 +19,8 @@ import android.view.ViewConfiguration; import android.widget.TextView; import android.widget.Toast; +import org.joinmastodon.android.R; + import me.grishka.appkit.utils.V; public class ClickableLinksDelegate { @@ -28,6 +30,21 @@ public class ClickableLinksDelegate { private LinkSpan selectedSpan; private TextView view; private final Handler longClickHandler = new Handler(); + + private final Runnable copyTextToClipboard = () -> { + //if target is not a link, don't copy + if (selectedSpan.getType() != LinkSpan.Type.URL) return; + //copy link text to clipboard + ClipboardManager clipboard = (ClipboardManager) view.getContext().getSystemService(Context.CLIPBOARD_SERVICE); + clipboard.setPrimaryClip(ClipData.newPlainText("", selectedSpan.getLink())); + //show toast, android from S_V2 on has built-in popup, as documented in + //https://developer.android.com/develop/ui/views/touch-and-input/copy-paste#duplicate-notifications + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { + Toast.makeText(view.getContext(), R.string.text_copied, Toast.LENGTH_SHORT).show(); + } + //reset view + resetAndInvalidate(); + }; public ClickableLinksDelegate(TextView view) { this.view=view; @@ -112,21 +129,6 @@ public class ClickableLinksDelegate { return false; } - Runnable copyTextToClipboard = () -> { - //if target is not a link, don't copy - if (selectedSpan.getType() != LinkSpan.Type.URL) return; - //copy link text to clipboard - ClipboardManager clipboard = (ClipboardManager) view.getContext().getSystemService(Context.CLIPBOARD_SERVICE); - clipboard.setPrimaryClip(ClipData.newPlainText("", selectedSpan.getLink())); - //show toast, android from S_V2 on has built-in popup, as documented in - //https://developer.android.com/develop/ui/views/touch-and-input/copy-paste#duplicate-notifications - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { - Toast.makeText(view.getContext(), "Copied", Toast.LENGTH_SHORT).show(); - } - //reset view - resetAndInvalidate(); - }; - private void resetAndInvalidate() { hlPath=null; selectedSpan=null; From f5df8225d1fb1039f312787d6ad5ddc5da24e0fc Mon Sep 17 00:00:00 2001 From: Torge Rosendahl Date: Wed, 15 Feb 2023 18:20:02 -0500 Subject: [PATCH 05/34] whitespace corrections --- .../joinmastodon/android/ui/text/ClickableLinksDelegate.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java index 036d6f9af..1906b2f39 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java @@ -45,7 +45,7 @@ public class ClickableLinksDelegate { //reset view resetAndInvalidate(); }; - + public ClickableLinksDelegate(TextView view) { this.view=view; hlPaint=new Paint(); @@ -134,7 +134,7 @@ public class ClickableLinksDelegate { selectedSpan=null; view.invalidate(); } - + public void onDraw(Canvas canvas){ if(hlPath!=null){ canvas.save(); From 794c4e52274b897036449ff39dd5aa8328510c07 Mon Sep 17 00:00:00 2001 From: Torge Rosendahl Date: Wed, 15 Feb 2023 19:54:08 -0500 Subject: [PATCH 06/34] removed longClickHandler and moved to view itself --- .../android/ui/text/ClickableLinksDelegate.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java index 1906b2f39..6035d2b51 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java @@ -10,7 +10,6 @@ import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; -import android.os.Handler; import android.text.Layout; import android.text.Spanned; import android.view.MotionEvent; @@ -29,7 +28,6 @@ public class ClickableLinksDelegate { private Path hlPath; private LinkSpan selectedSpan; private TextView view; - private final Handler longClickHandler = new Handler(); private final Runnable copyTextToClipboard = () -> { //if target is not a link, don't copy @@ -88,7 +86,7 @@ public class ClickableLinksDelegate { } hlPath=new Path(); selectedSpan=span; - longClickHandler.postDelayed(copyTextToClipboard, ViewConfiguration.getLongPressTimeout()); + view.postDelayed(copyTextToClipboard, ViewConfiguration.getLongPressTimeout()); hlPaint.setColor((span.getColor() & 0x00FFFFFF) | 0x33000000); //l.getSelectionPath(start, end, hlPath); for(int j=lstart;j<=lend;j++){ @@ -116,7 +114,7 @@ public class ClickableLinksDelegate { } } if(event.getAction()==MotionEvent.ACTION_UP && selectedSpan!=null){ - longClickHandler.removeCallbacks(copyTextToClipboard); + view.removeCallbacks(copyTextToClipboard); view.playSoundEffect(SoundEffectConstants.CLICK); selectedSpan.onClick(view.getContext()); resetAndInvalidate(); From fd99f3caa14bb1d471d67e17a15d9957b711526d Mon Sep 17 00:00:00 2001 From: Torge Rosendahl Date: Wed, 15 Feb 2023 20:14:16 -0500 Subject: [PATCH 07/34] changed url longclick implementation to GestureListener --- .../ui/text/ClickableLinksDelegate.java | 94 +++++++++++-------- 1 file changed, 55 insertions(+), 39 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java index 6035d2b51..300938f8a 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java @@ -12,12 +12,15 @@ import android.graphics.RectF; import android.os.Build; import android.text.Layout; import android.text.Spanned; +import android.view.GestureDetector; import android.view.MotionEvent; import android.view.SoundEffectConstants; import android.view.ViewConfiguration; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; + import org.joinmastodon.android.R; import me.grishka.appkit.utils.V; @@ -29,20 +32,7 @@ public class ClickableLinksDelegate { private LinkSpan selectedSpan; private TextView view; - private final Runnable copyTextToClipboard = () -> { - //if target is not a link, don't copy - if (selectedSpan.getType() != LinkSpan.Type.URL) return; - //copy link text to clipboard - ClipboardManager clipboard = (ClipboardManager) view.getContext().getSystemService(Context.CLIPBOARD_SERVICE); - clipboard.setPrimaryClip(ClipData.newPlainText("", selectedSpan.getLink())); - //show toast, android from S_V2 on has built-in popup, as documented in - //https://developer.android.com/develop/ui/views/touch-and-input/copy-paste#duplicate-notifications - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { - Toast.makeText(view.getContext(), R.string.text_copied, Toast.LENGTH_SHORT).show(); - } - //reset view - resetAndInvalidate(); - }; + GestureDetector gestureDetector; public ClickableLinksDelegate(TextView view) { this.view=view; @@ -50,10 +40,34 @@ public class ClickableLinksDelegate { hlPaint.setAntiAlias(true); hlPaint.setPathEffect(new CornerPathEffect(V.dp(3))); // 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) { - if(event.getAction()==MotionEvent.ACTION_DOWN){ + if(event.getAction()==MotionEvent.ACTION_CANCEL){ + resetAndInvalidate(); + } + return gestureDetector.onTouchEvent(event); + } + + 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(); + } + } + + private class LinkGestureListener extends GestureDetector.SimpleOnGestureListener { + @Override + public boolean onDown(@NonNull MotionEvent event) { int line=-1; Rect rect=new Rect(); Layout l=view.getLayout(); @@ -112,34 +126,36 @@ public class ClickableLinksDelegate { } } } + return super.onDown(event); } - if(event.getAction()==MotionEvent.ACTION_UP && selectedSpan!=null){ - view.removeCallbacks(copyTextToClipboard); - view.playSoundEffect(SoundEffectConstants.CLICK); - selectedSpan.onClick(view.getContext()); - resetAndInvalidate(); + + @Override + public boolean onSingleTapUp(@NonNull MotionEvent event) { + if(selectedSpan!=null){ + view.removeCallbacks(copyTextToClipboard); + view.playSoundEffect(SoundEffectConstants.CLICK); + selectedSpan.onClick(view.getContext()); + resetAndInvalidate(); + return true; + } return false; } - if(event.getAction()==MotionEvent.ACTION_CANCEL){ + + @Override + public void onLongPress(@NonNull MotionEvent event) { + //if target is not a link, don't copy + if (selectedSpan == null) return; + if (selectedSpan.getType() != LinkSpan.Type.URL) return; + //copy link text to clipboard + ClipboardManager clipboard = (ClipboardManager) view.getContext().getSystemService(Context.CLIPBOARD_SERVICE); + clipboard.setPrimaryClip(ClipData.newPlainText("", selectedSpan.getLink())); + //show toast, android from S_V2 on has built-in popup, as documented in + //https://developer.android.com/develop/ui/views/touch-and-input/copy-paste#duplicate-notifications + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { + Toast.makeText(view.getContext(), R.string.text_copied, Toast.LENGTH_SHORT).show(); + } + //reset view resetAndInvalidate(); - return false; - } - return false; - } - - 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(); } } - } From 0a8d73dc0bd8e5fd517f4e1654771c9928b4a466 Mon Sep 17 00:00:00 2001 From: Torge Rosendahl Date: Wed, 15 Feb 2023 20:19:10 -0500 Subject: [PATCH 08/34] cleanup, resolved some warnings --- .../android/ui/text/ClickableLinksDelegate.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java index 300938f8a..be9921907 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java @@ -15,7 +15,6 @@ import android.text.Spanned; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.SoundEffectConstants; -import android.view.ViewConfiguration; import android.widget.TextView; import android.widget.Toast; @@ -27,12 +26,12 @@ import me.grishka.appkit.utils.V; public class ClickableLinksDelegate { - private Paint hlPaint; + final private Paint hlPaint; private Path hlPath; private LinkSpan selectedSpan; - private TextView view; + final private TextView view; - GestureDetector gestureDetector; + final GestureDetector gestureDetector; public ClickableLinksDelegate(TextView view) { this.view=view; @@ -82,8 +81,7 @@ public class ClickableLinksDelegate { return false; } CharSequence text=view.getText(); - if(text instanceof Spanned){ - Spanned s=(Spanned)text; + if(text instanceof Spanned s){ LinkSpan[] spans=s.getSpans(0, s.length()-1, LinkSpan.class); if(spans.length>0){ for(LinkSpan span:spans){ @@ -100,7 +98,6 @@ public class ClickableLinksDelegate { } hlPath=new Path(); selectedSpan=span; - view.postDelayed(copyTextToClipboard, ViewConfiguration.getLongPressTimeout()); hlPaint.setColor((span.getColor() & 0x00FFFFFF) | 0x33000000); //l.getSelectionPath(start, end, hlPath); for(int j=lstart;j<=lend;j++){ @@ -132,7 +129,6 @@ public class ClickableLinksDelegate { @Override public boolean onSingleTapUp(@NonNull MotionEvent event) { if(selectedSpan!=null){ - view.removeCallbacks(copyTextToClipboard); view.playSoundEffect(SoundEffectConstants.CLICK); selectedSpan.onClick(view.getContext()); resetAndInvalidate(); From 4144639b7564a9d3be9306f165c8be63e493b9ae Mon Sep 17 00:00:00 2001 From: Torge Rosendahl Date: Sun, 26 Feb 2023 13:59:39 -0500 Subject: [PATCH 09/34] docu --- .../android/ui/text/ClickableLinksDelegate.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java index be9921907..9dac109c0 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java @@ -44,11 +44,16 @@ public class ClickableLinksDelegate { public boolean onTouch(MotionEvent event) { if(event.getAction()==MotionEvent.ACTION_CANCEL){ + // 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; @@ -64,6 +69,12 @@ public class ClickableLinksDelegate { } } + /** + * 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) { From f79fc66578d350d5323ed0be08d5e67062d87e90 Mon Sep 17 00:00:00 2001 From: Grishka Date: Sun, 5 Mar 2023 22:33:18 +0300 Subject: [PATCH 10/34] Fix --- .../android/ui/text/ClickableLinksDelegate.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java index 9dac109c0..e7b219940 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java @@ -26,12 +26,12 @@ import me.grishka.appkit.utils.V; public class ClickableLinksDelegate { - final private Paint hlPaint; + private final Paint hlPaint; private Path hlPath; private LinkSpan selectedSpan; - final private TextView view; + private final TextView view; - final GestureDetector gestureDetector; + private final GestureDetector gestureDetector; public ClickableLinksDelegate(TextView view) { this.view=view; From 4a695b2a83a52b0450637b9952ae80ba57468ac7 Mon Sep 17 00:00:00 2001 From: Grishka Date: Mon, 6 Mar 2023 02:25:13 +0300 Subject: [PATCH 11/34] Use a single display item for the image attachment grid --- .../fragments/BaseStatusListFragment.java | 156 +++------- .../android/fragments/ComposeFragment.java | 4 +- .../fragments/NotificationsListFragment.java | 8 - .../report/ReportAddPostsChoiceFragment.java | 33 +- .../android/ui/PhotoLayoutHelper.java | 17 +- .../displayitems/GifVStatusDisplayItem.java | 42 --- .../displayitems/ImageStatusDisplayItem.java | 105 ------- .../MediaGridStatusDisplayItem.java | 292 ++++++++++++++++++ .../displayitems/PhotoStatusDisplayItem.java | 144 --------- .../ui/displayitems/StatusDisplayItem.java | 41 ++- .../displayitems/VideoStatusDisplayItem.java | 42 --- .../ui/photoviewer/PhotoViewerHost.java | 3 +- .../ui/utils/InsetStatusItemDecoration.java | 15 +- .../utils/MediaAttachmentViewController.java | 67 ++++ ...FrameLayoutThatOnlyMeasuresFirstChild.java | 29 ++ .../ui/views/ImageAttachmentFrameLayout.java | 54 ---- .../android/ui/views/MediaGridLayout.java | 108 +++++++ .../{ui => }/utils/TransferSpeedTracker.java | 2 +- .../android/utils/TypedObjectPool.java | 36 +++ .../src/main/res/layout/display_item_gifv.xml | 4 +- .../main/res/layout/display_item_photo.xml | 66 +--- .../main/res/layout/display_item_video.xml | 4 +- .../res/layout/overlay_image_alt_text.xml | 58 ++++ 23 files changed, 679 insertions(+), 651 deletions(-) delete mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/GifVStatusDisplayItem.java delete mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ImageStatusDisplayItem.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/MediaGridStatusDisplayItem.java delete mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PhotoStatusDisplayItem.java delete mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/VideoStatusDisplayItem.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/utils/MediaAttachmentViewController.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/views/FrameLayoutThatOnlyMeasuresFirstChild.java delete mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/views/ImageAttachmentFrameLayout.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/views/MediaGridLayout.java rename mastodon/src/main/java/org/joinmastodon/android/{ui => }/utils/TransferSpeedTracker.java (96%) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/utils/TypedObjectPool.java create mode 100644 mastodon/src/main/res/layout/overlay_image_alt_text.xml diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java index 32faa1c90..8587ebc5a 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java @@ -29,20 +29,20 @@ import org.joinmastodon.android.model.Poll; import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Status; 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.GapStatusDisplayItem; 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.PollOptionStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem; import org.joinmastodon.android.ui.photoviewer.PhotoViewer; 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.views.ImageAttachmentFrameLayout; +import org.joinmastodon.android.ui.views.MediaGridLayout; +import org.joinmastodon.android.utils.TypedObjectPool; import java.util.ArrayList; import java.util.Collections; @@ -53,7 +53,6 @@ import java.util.stream.Collectors; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.ErrorResponse; @@ -73,6 +72,7 @@ public abstract class BaseStatusListFragment exten protected HashMap knownAccounts=new HashMap<>(); protected HashMap relationships=new HashMap<>(); protected Rect tmpRect=new Rect(); + protected TypedObjectPool attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView); public BaseStatusListFragment(){ super(20); @@ -171,21 +171,21 @@ public abstract class BaseStatusListFragment exten } @Override - public void openPhotoViewer(String parentID, Status _status, int attachmentIndex){ - final Status status=_status.reblog!=null ? _status.reblog : _status; + public void openPhotoViewer(String parentID, Status _status, int attachmentIndex, MediaGridStatusDisplayItem.Holder gridHolder){ + final Status status=_status.getContentStatus(); currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){ - private ImageStatusDisplayItem.Holder transitioningHolder; + private MediaAttachmentViewController transitioningHolder; @Override public void setPhotoViewVisibility(int index, boolean visible){ - ImageStatusDisplayItem.Holder holder=findPhotoViewHolder(index); + MediaAttachmentViewController holder=findPhotoViewHolder(index); if(holder!=null) holder.photo.setAlpha(visible ? 1f : 0f); } @Override public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){ - ImageStatusDisplayItem.Holder holder=findPhotoViewHolder(index); + MediaAttachmentViewController holder=findPhotoViewHolder(index); if(holder!=null){ transitioningHolder=holder; View view=transitioningHolder.photo; @@ -193,7 +193,8 @@ public abstract class BaseStatusListFragment exten view.getLocationOnScreen(pos); outRect.set(pos[0], pos[1], pos[0]+view.getWidth(), pos[1]+view.getHeight()); list.setClipChildren(false); - transitioningHolder.itemView.setElevation(1f); + gridHolder.setClipChildren(false); + transitioningHolder.view.setElevation(1f); return true; } return false; @@ -220,15 +221,16 @@ public abstract class BaseStatusListFragment exten view.setTranslationY(0f); view.setScaleX(1f); view.setScaleY(1f); - transitioningHolder.itemView.setElevation(0f); + transitioningHolder.view.setElevation(0f); if(list!=null) list.setClipChildren(true); + gridHolder.setClipChildren(true); transitioningHolder=null; } @Override public Drawable getPhotoViewCurrentDrawable(int index){ - ImageStatusDisplayItem.Holder holder=findPhotoViewHolder(index); + MediaAttachmentViewController holder=findPhotoViewHolder(index); if(holder!=null) return holder.photo.getDrawable(); return null; @@ -244,23 +246,8 @@ public abstract class BaseStatusListFragment exten requestPermissions(permissions, PhotoViewer.PERMISSION_REQUEST); } - private ImageStatusDisplayItem.Holder findPhotoViewHolder(int index){ - if(list==null) - 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; + private MediaAttachmentViewController findPhotoViewHolder(int index){ + return gridHolder.getViewController(index); } }); } @@ -310,31 +297,6 @@ public abstract class BaseStatusListFragment exten updateToolbar(); } - @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 exten revealSpoiler(status, holder.getItemID()); } - public void onRevealSpoilerClick(ImageStatusDisplayItem.Holder holder){ + public void onRevealSpoilerClick(MediaGridStatusDisplayItem.Holder holder){ Status status=holder.getItem().status; revealSpoiler(status, holder.getItemID()); } @@ -479,13 +441,14 @@ public abstract class BaseStatusListFragment exten protected void updateImagesSpoilerState(Status status, String itemID){ ArrayList updatedPositions=new ArrayList<>(); - for(ImageStatusDisplayItem.Holder photo:(List)findAllHoldersOfType(itemID, ImageStatusDisplayItem.Holder.class)){ - photo.setRevealed(status.spoilerRevealed); - updatedPositions.add(photo.getAbsoluteAdapterPosition()-getMainAdapterOffset()); + MediaGridStatusDisplayItem.Holder mediaGrid=findHolderOfType(itemID, MediaGridStatusDisplayItem.Holder.class); + if(mediaGrid!=null){ + mediaGrid.setRevealed(status.spoilerRevealed); + updatedPositions.add(mediaGrid.getAbsoluteAdapterPosition()-getMainAdapterOffset()); } int i=0; 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); } i++; @@ -609,6 +572,14 @@ public abstract class BaseStatusListFragment exten currentPhotoViewer.onPause(); } + private MediaAttachmentViewController makeNewMediaAttachmentView(MediaGridStatusDisplayItem.GridItemType type){ + return new MediaAttachmentViewController(getActivity(), type); + } + + public TypedObjectPool getAttachmentViewsPool(){ + return attachmentViewsPool; + } + protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter> implements ImageLoaderRecyclerAdapter{ public DisplayItemsAdapter(){ @@ -646,16 +617,6 @@ public abstract class BaseStatusListFragment exten public ImageLoaderRequest getImageRequest(int position, int image){ return displayItems.get(position).getImageRequest(image); } - -// @Override -// public void onViewDetachedFromWindow(@NonNull BindableViewHolder holder){ -// if(holder instanceof ImageLoaderViewHolder){ -// int count=holder.getItem().getImageCount(); -// for(int i=0;i exten for(int i=0;i imgHolder){ + if(holder instanceof MediaGridStatusDisplayItem.Holder imgHolder){ if(!imgHolder.getItem().status.spoilerRevealed && TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){ hiddenMediaPaint.setColor(0x80000000); - PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgHolder.getItem().thisTile; - 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); + c.drawRect(child.getX(), child.getY(), child.getX()+child.getWidth(), child.getY()+child.getHeight(), hiddenMediaPaint); } } } for(int i=0;i imgHolder){ + if(holder instanceof MediaGridStatusDisplayItem.Holder imgHolder){ if(!imgHolder.getItem().status.spoilerRevealed){ - PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgHolder.getItem().thisTile; - if(tile.startCol==0 && tile.startRow==0 && TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){ + if(TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){ 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) rebuildMediaHiddenLayouts(width-V.dp(32)); c.save(); @@ -732,47 +689,6 @@ public abstract class BaseStatusListFragment 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.colSpan1){ - 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 items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null); - if(titleItem!=null){ - for(StatusDisplayItem item:items){ - if(item instanceof ImageStatusDisplayItem imgItem){ - imgItem.horizontalInset=V.dp(32); - } - } - } if(titleItem!=null) items.add(0, titleItem); return items; diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java index c7b84c2ec..73c80ed70 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java @@ -25,7 +25,6 @@ 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.HeaderStatusDisplayItem; -import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; @@ -131,22 +130,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{ if(holder.getAbsoluteAdapterPosition()==0) return; outRect.left=V.dp(40); - if(holder instanceof ImageStatusDisplayItem.Holder imgHolder){ - PhotoLayoutHelper.TiledLayoutResult layout=imgHolder.getItem().tiledLayout; - PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgHolder.getItem().thisTile; - String siblingID; - if(holder.getAbsoluteAdapterPosition()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){ + if(holder instanceof AudioStatusDisplayItem.Holder){ outRect.bottom=V.dp(16); }else if(holder instanceof LinkCardStatusDisplayItem.Holder){ outRect.bottom=V.dp(16); @@ -165,10 +149,6 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{ parent.getDecoratedBoundsWithMargins(child, tmpRect); String id=sdiHolder.getItemID(); int height=tmpRect.height(); - if(holder instanceof ImageStatusDisplayItem.Holder imgHolder){ - if(imgHolder.getItem().thisTile.startCol+imgHolder.getItem().thisTile.colSpan buildDisplayItems(Status s){ - List items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false); - 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){ parent.getDecoratedBoundsWithMargins(child, tmpRect); tmpRect.offset(0, Math.round(child.getTranslationY())); diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/PhotoLayoutHelper.java b/mastodon/src/main/java/org/joinmastodon/android/ui/PhotoLayoutHelper.java index 9f1a653a5..fe3accc4f 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/PhotoLayoutHelper.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/PhotoLayoutHelper.java @@ -11,8 +11,14 @@ import java.util.List; import androidx.annotation.NonNull; public class PhotoLayoutHelper{ + public static final int MAX_WIDTH=1000; + public static final int MAX_HEIGHT=1910; + @NonNull - public static TiledLayoutResult processThumbs(int _maxW, int _maxH, List thumbs){ + public static TiledLayoutResult processThumbs(List thumbs){ + int _maxW=MAX_WIDTH; + int _maxH=MAX_HEIGHT; + TiledLayoutResult result=new TiledLayoutResult(); if(thumbs.size()==1){ Attachment att=thumbs.get(0); @@ -45,13 +51,8 @@ public class PhotoLayoutHelper{ float avgRatio=!ratios.isEmpty() ? sum(ratios)/ratios.size() : 1.0f; float maxW, maxH, marginW=0, marginH=0; - if(_maxW>0){ - maxW=_maxW; - maxH=_maxH; - }else{ - maxW=510; - maxH=510; - } + maxW=_maxW; + maxH=_maxH; float maxRatio=maxW/maxH; diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/GifVStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/GifVStatusDisplayItem.java deleted file mode 100644 index 8461121b8..000000000 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/GifVStatusDisplayItem.java +++ /dev/null @@ -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{ - - 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 - } - }); - } - } -} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ImageStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ImageStatusDisplayItem.java deleted file mode 100644 index 782f94e17..000000000 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ImageStatusDisplayItem.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.joinmastodon.android.ui.displayitems; - -import android.app.Activity; -import android.graphics.drawable.Drawable; -import android.text.TextUtils; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; - -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; - -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 extends StatusDisplayItem.Holder implements ImageLoaderViewHolder{ - public final ImageView photo; - private ImageAttachmentFrameLayout layout; - private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable(); - private boolean didClear; - - 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; - } - - @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; - } - - @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); - } - } -} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/MediaGridStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/MediaGridStatusDisplayItem.java new file mode 100644 index 000000000..5f0daeb3e --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/MediaGridStatusDisplayItem.java @@ -0,0 +1,292 @@ +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.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.widget.TextView; + +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 viewPool; + private final List attachments; + private final ArrayList requests=new ArrayList<>(); + public final Status status; + + public MediaGridStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, PhotoLayoutHelper.TiledLayoutResult tiledLayout, List 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 implements ImageLoaderViewHolder{ + private final FrameLayout wrapper; + private final MediaGridLayout layout; + private final View.OnClickListener clickListener=this::onViewClick, altTextClickListener=this::onAltTextClick; + private final ArrayList controllers=new ArrayList<>(); + + private final FrameLayout altTextWrapper; + private final TextView altTextButton; + private final View altTextScroller; + private final ImageButton altTextClose; + private final TextView altText; + + 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); + altTextScroller=findViewById(R.id.alt_text_scroller); + altTextClose=findViewById(R.id.alt_text_close); + altText=findViewById(R.id.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.altButton!=null){ + c.altButton.setOnClickListener(altTextClickListener); + c.altButton.setTag(i); + c.altButton.setAlpha(1f); + } + controllers.add(c); + c.bind(att, item.status); + i++; + } + 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); + altText.setText(att.description); + altTextWrapper.setVisibility(View.VISIBLE); + 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 anims=new ArrayList<>(); + anims.add(ObjectAnimator.ofFloat(altTextButton, 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, altTextWrapper.getLeft())); + anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT, altTextWrapper.getTop())); + anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+v.getWidth(), altTextWrapper.getRight())); + anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+v.getHeight(), altTextWrapper.getBottom())); + for(Animator a:anims) + a.setDuration(300); + + for(MediaAttachmentViewController c:controllers){ + if(c.altButton!=null && c.altButton!=v){ + anims.add(ObjectAnimator.ofFloat(c.altButton, 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.altButton!=null){ + c.altButton.setVisibility(View.INVISIBLE); + } + } + } + }); + altTextAnimator=set; + set.start(); + + return true; + } + }); + } + + private void onAltTextCloseClick(View v){ + if(altTextAnimator!=null) + altTextAnimator.cancel(); + + View btn=controllers.get(altTextIndex).altButton; + for(MediaAttachmentViewController c:controllers){ + if(c.altButton!=null && c.altButton!=btn) + c.altButton.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 anims=new ArrayList<>(); + anims.add(ObjectAnimator.ofFloat(altTextButton, 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)); + anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT)); + anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+btn.getWidth())); + anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+btn.getHeight())); + for(Animator a:anims) + a.setDuration(300); + + for(MediaAttachmentViewController c:controllers){ + if(c.altButton!=null && c.altButton!=btn){ + anims.add(ObjectAnimator.ofFloat(c.altButton, 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); + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PhotoStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PhotoStatusDisplayItem.java deleted file mode 100644 index 104b02fb6..000000000 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PhotoStatusDisplayItem.java +++ /dev/null @@ -1,144 +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.ScrollView; -import android.widget.TextView; - -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; - -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{ - private final FrameLayout altTextWrapper; - private final TextView altTextButton; - private final View altTextScroller; - private final ImageButton altTextClose; - private final TextView altText; - - private boolean altTextShown; - private AnimatorSet currentAnim; - - public Holder(Activity activity, ViewGroup parent){ - super(activity, R.layout.display_item_photo, parent); - altTextWrapper=findViewById(R.id.alt_text_wrapper); - altTextButton=findViewById(R.id.alt_button); - altTextScroller=findViewById(R.id.alt_text_scroller); - altTextClose=findViewById(R.id.alt_text_close); - altText=findViewById(R.id.alt_text); - - altTextButton.setOnClickListener(this::onShowHideClick); - altTextClose.setOnClickListener(this::onShowHideClick); -// altTextScroller.setNestedScrollingEnabled(true); - } - - @Override - public void onBind(ImageStatusDisplayItem item){ - super.onBind(item); - altTextShown=false; - if(currentAnim!=null) - currentAnim.cancel(); - altTextScroller.setVisibility(View.GONE); - altTextClose.setVisibility(View.GONE); - altTextButton.setVisibility(View.VISIBLE); - altTextButton.setAlpha(1f); - if(TextUtils.isEmpty(item.attachment.description)){ - altTextWrapper.setVisibility(View.GONE); - }else{ - altTextWrapper.setVisibility(View.VISIBLE); - altText.setText(item.attachment.description); - } - } - - private void onShowHideClick(View v){ - boolean show=v.getId()==R.id.alt_button; - - if(altTextShown==show) - return; - if(currentAnim!=null) - currentAnim.cancel(); - - altTextShown=show; - if(show){ - altTextScroller.setVisibility(View.VISIBLE); - altTextClose.setVisibility(View.VISIBLE); - }else{ - altTextButton.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(altTextButton, 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){ - altTextButton.setVisibility(View.GONE); - }else{ - altTextScroller.setVisibility(View.GONE); - altTextClose.setVisibility(View.GONE); - } - currentAnim=null; - } - }); - set.start(); - currentAnim=set; - - return true; - } - }); - } - } -} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index d1e5e1fd8..adedc0e8e 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -53,10 +53,7 @@ public abstract class StatusDisplayItem{ case HEADER -> new HeaderStatusDisplayItem.Holder(activity, parent); case REBLOG_OR_REPLY_LINE -> new ReblogOrReplyLineStatusDisplayItem.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 VIDEO -> new VideoStatusDisplayItem.Holder(activity, parent); case POLL_OPTION -> new PollOptionStatusDisplayItem.Holder(activity, parent); case POLL_FOOTER -> new PollFooterStatusDisplayItem.Holder(activity, parent); case CARD -> new LinkCardStatusDisplayItem.Holder(activity, parent); @@ -66,6 +63,7 @@ public abstract class StatusDisplayItem{ case HASHTAG -> new HashtagStatusDisplayItem.Holder(activity, parent); case GAP -> new GapStatusDisplayItem.Holder(activity, parent); case EXTENDED_FOOTER -> new ExtendedFooterStatusDisplayItem.Holder(activity, parent); + case MEDIA_GRID -> new MediaGridStatusDisplayItem.Holder(activity, parent); }; } @@ -87,20 +85,23 @@ public abstract class StatusDisplayItem{ header.needBottomPadding=true; List imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList()); if(!imageAttachments.isEmpty()){ - int photoIndex=0; - PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(1000, 1910, imageAttachments); - 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++; - } + PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments); + + items.add(new MediaGridStatusDisplayItem(parentID, fragment, layout, imageAttachments, statusForContent)); + +// int photoIndex=0; +// 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){ if(att.type==Attachment.Type.AUDIO){ @@ -137,9 +138,6 @@ public abstract class StatusDisplayItem{ HEADER, REBLOG_OR_REPLY_LINE, TEXT, - PHOTO, - VIDEO, - GIFV, AUDIO, POLL_OPTION, POLL_FOOTER, @@ -149,7 +147,8 @@ public abstract class StatusDisplayItem{ ACCOUNT, HASHTAG, GAP, - EXTENDED_FOOTER + EXTENDED_FOOTER, + MEDIA_GRID } public static abstract class Holder extends BindableViewHolder implements UsableRecyclerView.DisableableClickable{ diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/VideoStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/VideoStatusDisplayItem.java deleted file mode 100644 index 41c54aeef..000000000 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/VideoStatusDisplayItem.java +++ /dev/null @@ -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{ - - 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 - } - }); - } - } -} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewerHost.java b/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewerHost.java index 72dcb47ce..fc168087b 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewerHost.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewerHost.java @@ -1,7 +1,8 @@ package org.joinmastodon.android.ui.photoviewer; import org.joinmastodon.android.model.Status; +import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem; public interface PhotoViewerHost{ - void openPhotoViewer(String parentID, Status status, int attachmentIndex); + void openPhotoViewer(String parentID, Status status, int attachmentIndex, MediaGridStatusDisplayItem.Holder gridHolder); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/InsetStatusItemDecoration.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/InsetStatusItemDecoration.java index a811fb464..12581b09c 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/InsetStatusItemDecoration.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/InsetStatusItemDecoration.java @@ -8,10 +8,9 @@ import android.view.View; import org.joinmastodon.android.R; 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.MediaGridStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import java.util.List; @@ -87,21 +86,11 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{ boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset; boolean bottomSiblingInset=pos 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) outRect.left=pad; if(insetRight) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/MediaAttachmentViewController.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/MediaAttachmentViewController.java new file mode 100644 index 000000000..9f269a2c8 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/MediaAttachmentViewController.java @@ -0,0 +1,67 @@ +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.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; + 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); + this.type=type; + this.context=context; + } + + 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); + photo.setContentDescription(TextUtils.isEmpty(attachment.description) ? context.getString(R.string.media_no_description) : attachment.description); + if(altButton!=null){ + altButton.setVisibility(TextUtils.isEmpty(attachment.description) ? View.GONE : View.VISIBLE); + } + 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); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/FrameLayoutThatOnlyMeasuresFirstChild.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/FrameLayoutThatOnlyMeasuresFirstChild.java new file mode 100644 index 000000000..f195ac633 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/FrameLayoutThatOnlyMeasuresFirstChild.java @@ -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); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/ImageAttachmentFrameLayout.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/ImageAttachmentFrameLayout.java deleted file mode 100644 index 0bfcbdb16..000000000 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/views/ImageAttachmentFrameLayout.java +++ /dev/null @@ -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.colSpanmaxWidth){ + xOffset=(r-l)/2-maxWidth/2; + } + + for(int i=0;i{ + private final Function producer; + private final HashMap> pool=new HashMap<>(); + + public TypedObjectPool(Function producer){ + this.producer=producer; + } + + public V obtain(K type){ + LinkedList 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 tp=pool.get(type); + if(tp==null) + pool.put(type, tp=new LinkedList<>()); + tp.add(obj); + } +} diff --git a/mastodon/src/main/res/layout/display_item_gifv.xml b/mastodon/src/main/res/layout/display_item_gifv.xml index 4dccf3eff..8f373c682 100644 --- a/mastodon/src/main/res/layout/display_item_gifv.xml +++ b/mastodon/src/main/res/layout/display_item_gifv.xml @@ -1,5 +1,5 @@ - @@ -25,4 +25,4 @@ android:layout_margin="8dp" android:background="@drawable/ic_gif"/> - \ No newline at end of file + \ No newline at end of file diff --git a/mastodon/src/main/res/layout/display_item_photo.xml b/mastodon/src/main/res/layout/display_item_photo.xml index 00b0bd176..2ba0d2860 100644 --- a/mastodon/src/main/res/layout/display_item_photo.xml +++ b/mastodon/src/main/res/layout/display_item_photo.xml @@ -1,5 +1,5 @@ - @@ -12,61 +12,21 @@ android:scaleType="centerCrop"/> - - - + android:importantForAccessibility="no" + android:textAppearance="@style/m3_label_large" + android:textColor="#FFF" + android:gravity="center" + android:includeFontPadding="false" + android:background="@drawable/bg_image_alt_overlay" + android:text="ALT"/> - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/mastodon/src/main/res/layout/display_item_video.xml b/mastodon/src/main/res/layout/display_item_video.xml index 83e8112d9..5ecaa5782 100644 --- a/mastodon/src/main/res/layout/display_item_video.xml +++ b/mastodon/src/main/res/layout/display_item_video.xml @@ -1,5 +1,5 @@ - @@ -18,4 +18,4 @@ android:elevation="3dp" android:background="@drawable/play_button"/> - \ No newline at end of file + \ No newline at end of file diff --git a/mastodon/src/main/res/layout/overlay_image_alt_text.xml b/mastodon/src/main/res/layout/overlay_image_alt_text.xml new file mode 100644 index 000000000..53f4e5ba6 --- /dev/null +++ b/mastodon/src/main/res/layout/overlay_image_alt_text.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + From c3aa3af6507eed33b965c9da85a0da3b7047c2e1 Mon Sep 17 00:00:00 2001 From: Grishka Date: Wed, 8 Mar 2023 22:46:24 +0300 Subject: [PATCH 12/34] Fix #540 --- .../android/fragments/ComposeFragment.java | 8 +++++--- .../ui/displayitems/StatusDisplayItem.java | 15 --------------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java index 0145642e5..371e5b02f 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java @@ -640,9 +640,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr } private void onCustomEmojiClick(Emoji emoji){ - int start=mainEditText.getSelectionStart(); - String prefix=start>0 && !Character.isWhitespace(mainEditText.getText().charAt(start-1)) ? " :" : ":"; - mainEditText.getText().replace(start, mainEditText.getSelectionEnd(), prefix+emoji.shortcode+':'); + if(getActivity().getCurrentFocus() instanceof EditText edit){ + int start=edit.getSelectionStart(); + String prefix=start>0 && !Character.isWhitespace(edit.getText().charAt(start-1)) ? " :" : ":"; + edit.getText().replace(start, edit.getSelectionEnd(), prefix+emoji.shortcode+':'); + } } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index adedc0e8e..783c2f3f0 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -86,22 +86,7 @@ public abstract class StatusDisplayItem{ List imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList()); if(!imageAttachments.isEmpty()){ PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments); - items.add(new MediaGridStatusDisplayItem(parentID, fragment, layout, imageAttachments, statusForContent)); - -// int photoIndex=0; -// 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){ if(att.type==Attachment.Type.AUDIO){ From 5c7fe9dcb505566a461e8df5f6bd9a28c036115b Mon Sep 17 00:00:00 2001 From: FineFindus <63370021+FineFindus@users.noreply.github.com> Date: Sat, 11 Mar 2023 13:00:36 +0100 Subject: [PATCH 13/34] fix: disable group divider on EMUI (#453) Fixes an issues, where the forth menu item does not show up, when the divider is enabled on EMUI devices --- .../org/joinmastodon/android/fragments/HomeTabFragment.java | 2 +- .../main/java/org/joinmastodon/android/ui/utils/UiUtils.java | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java index 37ce0e002..adf202bce 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java @@ -358,7 +358,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab addListsToOverflowMenu(); addHashtagsToOverflowMenu(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !UiUtils.isEMUI()) { m.setGroupDividerEnabled(true); } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java index c720958a5..ee413c9be 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java @@ -1143,6 +1143,10 @@ public class UiUtils { 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) { float alpha0 = 1f - alpha; int r = Math.round(((color1 >> 16) & 0xFF) * alpha0 + ((color2 >> 16) & 0xFF) * alpha); From 4d04741fe000af8a9d833c84ecb0d3bf7d09c9a0 Mon Sep 17 00:00:00 2001 From: FineFindus <63370021+FineFindus@users.noreply.github.com> Date: Sat, 11 Mar 2023 13:01:11 +0100 Subject: [PATCH 14/34] feat(welcome): use URI InputType (#454) --- mastodon/src/main/res/layout/header_welcome_custom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mastodon/src/main/res/layout/header_welcome_custom.xml b/mastodon/src/main/res/layout/header_welcome_custom.xml index ed6bda666..ea6c2a9e7 100644 --- a/mastodon/src/main/res/layout/header_welcome_custom.xml +++ b/mastodon/src/main/res/layout/header_welcome_custom.xml @@ -36,7 +36,7 @@ android:layout_marginTop="16dp" android:layout_marginBottom="8dp" android:layout_marginHorizontal="16dp" - android:inputType="textFilter|textNoSuggestions" + android:inputType="textUri|textNoSuggestions" android:singleLine="true" android:imeOptions="actionGo" android:drawableStart="@drawable/ic_fluent_globe_20_regular" From fc67c8204012540c9f835f7e051025d5a7aaf71d Mon Sep 17 00:00:00 2001 From: Grishka Date: Mon, 13 Mar 2023 20:46:29 +0300 Subject: [PATCH 15/34] Fix #544 --- .../src/main/java/org/joinmastodon/android/model/Poll.java | 3 +++ .../android/ui/displayitems/PollOptionStatusDisplayItem.java | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Poll.java b/mastodon/src/main/java/org/joinmastodon/android/model/Poll.java index 037a1115b..4430bed62 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Poll.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Poll.java @@ -16,6 +16,7 @@ public class Poll extends BaseModel{ private boolean expired; public boolean multiple; public int votersCount; + public int votesCount; public boolean voted; @RequiredField public List ownVotes; @@ -41,10 +42,12 @@ public class Poll extends BaseModel{ ", expired="+expired+ ", multiple="+multiple+ ", votersCount="+votersCount+ + ", votesCount="+votesCount+ ", voted="+voted+ ", ownVotes="+ownVotes+ ", options="+options+ ", emojis="+emojis+ + ", selectedOptions="+selectedOptions+ '}'; } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PollOptionStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PollOptionStatusDisplayItem.java index 3c50869c6..6333f32fe 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PollOptionStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PollOptionStatusDisplayItem.java @@ -34,8 +34,9 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{ text=HtmlParser.parseCustomEmoji(option.title, poll.emojis); emojiHelper.setText(text); showResults=poll.isExpired() || poll.voted; - if(showResults && option.votesCount!=null && poll.votersCount>0){ - votesFraction=(float)option.votesCount/(float)poll.votersCount; + int total=poll.votersCount>0 ? poll.votersCount : poll.votesCount; + if(showResults && option.votesCount!=null && total>0){ + votesFraction=(float)option.votesCount/(float)total; int mostVotedCount=0; for(Poll.Option opt:poll.options) mostVotedCount=Math.max(mostVotedCount, opt.votesCount); From 1c340b7c666ec254160b63bbef2ffa8bc30abe75 Mon Sep 17 00:00:00 2001 From: sk Date: Mon, 13 Mar 2023 18:56:06 +0100 Subject: [PATCH 16/34] fix extra text padding in compose --- mastodon/src/main/res/layout/fragment_compose.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mastodon/src/main/res/layout/fragment_compose.xml b/mastodon/src/main/res/layout/fragment_compose.xml index ce06d7675..80426d5a5 100644 --- a/mastodon/src/main/res/layout/fragment_compose.xml +++ b/mastodon/src/main/res/layout/fragment_compose.xml @@ -101,9 +101,9 @@ android:id="@+id/self_extra_text" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="2sp" android:layout_marginStart="8sp" android:layout_toEndOf="@id/self_name" + android:paddingTop="4sp" android:ellipsize="end" android:fontFamily="sans-serif" android:singleLine="true" From 16857bebd98ecc4a1b4d66805a5e5c5f4485416d Mon Sep 17 00:00:00 2001 From: sk Date: Mon, 13 Mar 2023 19:05:22 +0100 Subject: [PATCH 17/34] add tooltip closes sk22#436 --- mastodon/src/main/res/layout/display_item_header.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/mastodon/src/main/res/layout/display_item_header.xml b/mastodon/src/main/res/layout/display_item_header.xml index be6903169..8217f2750 100644 --- a/mastodon/src/main/res/layout/display_item_header.xml +++ b/mastodon/src/main/res/layout/display_item_header.xml @@ -27,6 +27,7 @@ android:visibility="gone" android:background="?android:actionBarItemBackground" android:contentDescription="@string/sk_delete_notification" + android:tooltipText="@string/sk_delete_notification" android:scaleType="center" android:src="@drawable/ic_fluent_dismiss_20_filled" android:tint="?android:textColorSecondary" /> From c5a19a2334a1cbf1e01383294616f1a6c865b422 Mon Sep 17 00:00:00 2001 From: sk Date: Mon, 13 Mar 2023 19:29:40 +0100 Subject: [PATCH 18/34] fix duplicate notification status header --- .../android/fragments/NotificationsListFragment.java | 2 +- .../android/ui/displayitems/StatusDisplayItem.java | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java index a8e421746..5857a446d 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java @@ -94,7 +94,7 @@ public class NotificationsListFragment extends BaseStatusListFragment items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n, false, Filter.FilterContext.NOTIFICATIONS, titleItem); + ArrayList items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n, false, Filter.FilterContext.NOTIFICATIONS); if(titleItem!=null) items.add(0, titleItem); return items; diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index 7b835fba8..a7ce2594b 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -93,10 +93,6 @@ public abstract class StatusDisplayItem{ } public static ArrayList buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map 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 buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate, Filter.FilterContext filterContext, StatusDisplayItem titleItem){ String parentID=parentObject.getID(); ArrayList items=new ArrayList<>(); @@ -178,8 +174,6 @@ public abstract class StatusDisplayItem{ item.index=i++; } - if (titleItem != null) items.add(0, titleItem); - if (!statusForContent.filterRevealed) { return new ArrayList<>(List.of( new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, items) From 80d529d503961c296dff9a9d753f5fd0b21ffd41 Mon Sep 17 00:00:00 2001 From: sk Date: Mon, 13 Mar 2023 20:00:56 +0100 Subject: [PATCH 19/34] display reply header for unknown original poster re: mastodon#342 --- .../ui/displayitems/StatusDisplayItem.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index a7ce2594b..793df98e5 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -115,12 +115,16 @@ public abstract class StatusDisplayItem{ args.putParcelable("profileAccount", Parcels.wrap(status.account)); Nav.go(fragment.getActivity(), ProfileFragment.class, args); })); - }else if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId)){ - Account account=Objects.requireNonNull(knownAccounts.get(status.inReplyToAccountId)); - items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.in_reply_to, account.displayName), account.emojis, R.drawable.ic_fluent_arrow_reply_20_filled, null, i->{ - args.putParcelable("profileAccount", Parcels.wrap(account)); - Nav.go(fragment.getActivity(), ProfileFragment.class, args); - })); + }else if(status.inReplyToAccountId!=null){ + if (knownAccounts.containsKey(status.inReplyToAccountId)) { + Account account = Objects.requireNonNull(knownAccounts.get(status.inReplyToAccountId)); + items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.in_reply_to, account.displayName), account.emojis, R.drawable.ic_fluent_arrow_reply_20_filled, null, i -> { + args.putParcelable("profileAccount", Parcels.wrap(account)); + Nav.go(fragment.getActivity(), ProfileFragment.class, args); + })); + } else { + items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.in_reply_to), List.of(), R.drawable.ic_fluent_arrow_reply_20_filled, null, null)); + } } else if ( !(status.tags.isEmpty() || fragment instanceof HashtagTimelineFragment || From 4c85fd43873b6a216d95408cabf67518aa6a5257 Mon Sep 17 00:00:00 2001 From: sk Date: Mon, 13 Mar 2023 20:06:05 +0100 Subject: [PATCH 20/34] use custom string for anonymous reply --- .../android/ui/displayitems/StatusDisplayItem.java | 4 ++-- mastodon/src/main/res/values/strings_sk.xml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index 793df98e5..c6a15e7ea 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -115,7 +115,7 @@ public abstract class StatusDisplayItem{ args.putParcelable("profileAccount", Parcels.wrap(status.account)); Nav.go(fragment.getActivity(), ProfileFragment.class, args); })); - }else if(status.inReplyToAccountId!=null){ + } if(status.inReplyToAccountId!=null){ if (knownAccounts.containsKey(status.inReplyToAccountId)) { Account account = Objects.requireNonNull(knownAccounts.get(status.inReplyToAccountId)); items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.in_reply_to, account.displayName), account.emojis, R.drawable.ic_fluent_arrow_reply_20_filled, null, i -> { @@ -123,7 +123,7 @@ public abstract class StatusDisplayItem{ Nav.go(fragment.getActivity(), ProfileFragment.class, args); })); } else { - items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.in_reply_to), List.of(), R.drawable.ic_fluent_arrow_reply_20_filled, null, null)); + items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.sk_in_reply), List.of(), R.drawable.ic_fluent_arrow_reply_20_filled, null, null)); } } else if ( !(status.tags.isEmpty() || diff --git a/mastodon/src/main/res/values/strings_sk.xml b/mastodon/src/main/res/values/strings_sk.xml index a94de6489..a58cf7dd7 100644 --- a/mastodon/src/main/res/values/strings_sk.xml +++ b/mastodon/src/main/res/values/strings_sk.xml @@ -262,4 +262,5 @@ Follow from other account Followed from %s Auto-hide Compose button + In reply \ No newline at end of file From d6bcc9c156746d057db1806b52ff014765919a55 Mon Sep 17 00:00:00 2001 From: Grishka Date: Tue, 14 Mar 2023 07:31:01 +0300 Subject: [PATCH 21/34] Fix button color --- .../main/java/org/joinmastodon/android/ui/utils/UiUtils.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java index 47a736aa5..0af92ccff 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java @@ -470,6 +470,9 @@ public class UiUtils{ TypedArray ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background}); button.setBackground(ta.getDrawable(0)); 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 progressCallback, Consumer resultCallback){ From 27c2791d6c19208346a7fa1df8db56fd7ba8e77d Mon Sep 17 00:00:00 2001 From: sk Date: Tue, 14 Mar 2023 14:58:21 +0100 Subject: [PATCH 22/34] add reblogged account to known accounts --- .../android/fragments/NotificationsListFragment.java | 2 ++ .../org/joinmastodon/android/fragments/StatusListFragment.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java index 5857a446d..a95f061af 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java @@ -117,6 +117,8 @@ public class NotificationsListFragment extends BaseStatusListFragment{ protected void addAccountToKnown(Status s){ if(!knownAccounts.containsKey(s.account.id)) 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 From 5e194e30795be42a4b2a1696a10bfaeba2bc39f1 Mon Sep 17 00:00:00 2001 From: sk Date: Tue, 14 Mar 2023 14:59:11 +0100 Subject: [PATCH 23/34] display both reply and reblog re: sk22#448 --- .../ReblogOrReplyLineStatusDisplayItem.java | 23 ++++++++-- .../ui/displayitems/StatusDisplayItem.java | 46 +++++++++++-------- .../display_item_reblog_or_reply_line.xml | 1 + 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java index 366d5ba9f..b958fa7ed 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java @@ -27,6 +27,7 @@ import androidx.annotation.Nullable; import me.grishka.appkit.imageloader.ImageLoaderViewHolder; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; +import me.grishka.appkit.utils.V; public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ private CharSequence text; @@ -37,8 +38,13 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ private int iconEnd; private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(); private View.OnClickListener handleClick; + private boolean isLastLine; public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick){ + this(parentID, parentFragment, text, emojis, icon, visibility, handleClick, true); + } + + public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, boolean isLastLine){ super(parentID, parentFragment); SpannableStringBuilder ssb=new SpannableStringBuilder(text); HtmlParser.parseCustomEmoji(ssb, emojis); @@ -49,6 +55,11 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true); updateVisibility(visibility); + setIsLastLine(isLastLine); + } + + public void setIsLastLine(boolean isLastLine) { + this.isLastLine = isLastLine; } public void updateVisibility(StatusPrivacy visibility) { @@ -78,18 +89,21 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ public static class Holder extends StatusDisplayItem.Holder implements ImageLoaderViewHolder{ private final TextView text; + private final View frame; + public Holder(Activity activity, ViewGroup parent){ super(activity, R.layout.display_item_reblog_or_reply_line, parent); text=findViewById(R.id.text); + frame=findViewById(R.id.frame); } @Override public void onBind(ReblogOrReplyLineStatusDisplayItem item){ text.setText(item.text); text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, item.iconEnd, 0); - if(item.handleClick!=null) text.setOnClickListener(item.handleClick); - text.setEnabled(!item.inset); - text.setClickable(!item.inset); + text.setOnClickListener(item.handleClick); + text.setEnabled(!item.inset && item.handleClick != null); + text.setClickable(!item.inset && item.handleClick != null); Context ctx = itemView.getContext(); int visibilityText = item.visibility != null ? switch (item.visibility) { case PUBLIC -> R.string.visibility_public; @@ -100,6 +114,9 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ if (visibilityText != 0) text.setContentDescription(item.text + " (" + ctx.getString(visibilityText) + ")"); if(Build.VERSION.SDK_INT{ args.putParcelable("profileAccount", Parcels.wrap(status.account)); Nav.go(fragment.getActivity(), ProfileFragment.class, args); - })); - } if(status.inReplyToAccountId!=null){ - if (knownAccounts.containsKey(status.inReplyToAccountId)) { - Account account = Objects.requireNonNull(knownAccounts.get(status.inReplyToAccountId)); - items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.in_reply_to, account.displayName), account.emojis, R.drawable.ic_fluent_arrow_reply_20_filled, null, i -> { - args.putParcelable("profileAccount", Parcels.wrap(account)); - Nav.go(fragment.getActivity(), ProfileFragment.class, args); - })); - } else { - items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.sk_in_reply), List.of(), R.drawable.ic_fluent_arrow_reply_20_filled, null, null)); - } - } else if ( - !(status.tags.isEmpty() || - fragment instanceof HashtagTimelineFragment || - fragment instanceof ListTimelineFragment - ) && fragment.getParentFragment() instanceof HomeTabFragment home - ) { + }, false)); + } + + 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, false + )); + } + + if (status.reblog == null && !(status.tags.isEmpty() || + fragment instanceof HashtagTimelineFragment || + fragment instanceof ListTimelineFragment + ) && fragment.getParentFragment() instanceof HomeTabFragment home) { home.getHashtags().stream() .filter(followed -> status.tags.stream() .anyMatch(hashtag -> followed.name.equalsIgnoreCase(hashtag.name))) @@ -142,9 +144,15 @@ public abstract class StatusDisplayItem{ i -> { args.putString("hashtag", hashtag.name); Nav.go(fragment.getActivity(), HashtagTimelineFragment.class, args); - } + }, + false ))); } + + if (items.size() > 0) { + ((ReblogOrReplyLineStatusDisplayItem) items.get(items.size() - 1)).setIsLastLine(true); + } + HeaderStatusDisplayItem header; items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, notification, scheduledStatus)); if(!TextUtils.isEmpty(statusForContent.content)) diff --git a/mastodon/src/main/res/layout/display_item_reblog_or_reply_line.xml b/mastodon/src/main/res/layout/display_item_reblog_or_reply_line.xml index b97a3c8a0..90bf482c3 100644 --- a/mastodon/src/main/res/layout/display_item_reblog_or_reply_line.xml +++ b/mastodon/src/main/res/layout/display_item_reblog_or_reply_line.xml @@ -2,6 +2,7 @@ Date: Tue, 14 Mar 2023 15:34:05 +0100 Subject: [PATCH 24/34] fuck it, indented header line closes #448 --- .../ReblogOrReplyLineStatusDisplayItem.java | 13 ++--- .../ui/displayitems/StatusDisplayItem.java | 47 ++++++++++--------- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java index b958fa7ed..a6924dc52 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java @@ -38,13 +38,10 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ private int iconEnd; private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(); private View.OnClickListener handleClick; - private boolean isLastLine; + private boolean isLastLine = true; + private int lineNo = 0; public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick){ - this(parentID, parentFragment, text, emojis, icon, visibility, handleClick, true); - } - - public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, boolean isLastLine){ super(parentID, parentFragment); SpannableStringBuilder ssb=new SpannableStringBuilder(text); HtmlParser.parseCustomEmoji(ssb, emojis); @@ -55,13 +52,16 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true); updateVisibility(visibility); - setIsLastLine(isLastLine); } public void setIsLastLine(boolean isLastLine) { this.isLastLine = isLastLine; } + public void setLineNo(int lineNo) { + this.lineNo = lineNo; + } + public void updateVisibility(StatusPrivacy visibility) { this.visibility = visibility; this.iconEnd = visibility != null ? switch (visibility) { @@ -116,6 +116,7 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ 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); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index 3ad8a7c39..f40c93c8b 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -113,23 +113,8 @@ public abstract class StatusDisplayItem{ items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.user_boosted, status.account.displayName), status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20_filled, isOwnPost ? status.visibility : null, i->{ args.putParcelable("profileAccount", Parcels.wrap(status.account)); Nav.go(fragment.getActivity(), ProfileFragment.class, args); - }, false)); - } - - 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, false - )); - } - - if (status.reblog == null && !(status.tags.isEmpty() || + })); + } else if (!(status.tags.isEmpty() || fragment instanceof HashtagTimelineFragment || fragment instanceof ListTimelineFragment ) && fragment.getParentFragment() instanceof HomeTabFragment home) { @@ -144,15 +129,35 @@ public abstract class StatusDisplayItem{ i -> { args.putString("hashtag", hashtag.name); Nav.go(fragment.getActivity(), HashtagTimelineFragment.class, args); - }, - false + } ))); } - if (items.size() > 0) { - ((ReblogOrReplyLineStatusDisplayItem) items.get(items.size() - 1)).setIsLastLine(true); + 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; items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, notification, scheduledStatus)); if(!TextUtils.isEmpty(statusForContent.content)) From 56b24420d15cce8b786070703c5593651305f859 Mon Sep 17 00:00:00 2001 From: sk Date: Tue, 14 Mar 2023 15:37:06 +0100 Subject: [PATCH 25/34] add local-only string --- .../ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java | 1 + 1 file changed, 1 insertion(+) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java index a6924dc52..4d3022a46 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java @@ -109,6 +109,7 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ case PUBLIC -> R.string.visibility_public; case UNLISTED -> R.string.sk_visibility_unlisted; case PRIVATE -> R.string.visibility_followers_only; + case LOCAL -> R.string.sk_local_only; default -> 0; } : 0; if (visibilityText != 0) text.setContentDescription(item.text + " (" + ctx.getString(visibilityText) + ")"); From bad72985cbf7cb969109c30778ed40a5cb8f0642 Mon Sep 17 00:00:00 2001 From: sk Date: Wed, 15 Mar 2023 12:49:09 +0100 Subject: [PATCH 26/34] bump version --- mastodon/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mastodon/build.gradle b/mastodon/build.gradle index 60e8ba425..a7006cc63 100644 --- a/mastodon/build.gradle +++ b/mastodon/build.gradle @@ -9,8 +9,8 @@ android { applicationId "org.joinmastodon.android.sk" minSdk 23 targetSdk 33 - versionCode 77 - versionName "1.2.0+fork.77" + versionCode 78 + versionName "1.2.0+fork.78" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 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" } From 584700225c22034a53594d3363dcabc3beba4272 Mon Sep 17 00:00:00 2001 From: sk Date: Wed, 15 Mar 2023 17:36:07 +0100 Subject: [PATCH 27/34] reply line below avatar --- .../android/GlobalUserPreferences.java | 3 +++ .../android/fragments/ComposeFragment.java | 4 ++- .../android/fragments/SettingsFragment.java | 4 +++ .../ReblogOrReplyLineStatusDisplayItem.java | 24 ++++++----------- .../ui/displayitems/StatusDisplayItem.java | 26 ++++++++++--------- .../src/main/res/layout/fragment_compose.xml | 19 +++++++++++++- mastodon/src/main/res/values/strings_sk.xml | 1 + 7 files changed, 51 insertions(+), 30 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java index ce645c5c5..d66b63f04 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java +++ b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java @@ -44,6 +44,7 @@ public class GlobalUserPreferences{ public static boolean collapseLongPosts; public static boolean spectatorMode; public static boolean autoHideFab; + public static boolean replyLineBelowHeader; public static String publishButtonText; public static ThemePreference theme; public static ColorPreference color; @@ -93,6 +94,7 @@ public class GlobalUserPreferences{ collapseLongPosts=prefs.getBoolean("collapseLongPosts", true); spectatorMode=prefs.getBoolean("spectatorMode", false); autoHideFab=prefs.getBoolean("autoHideFab", true); + replyLineBelowHeader=prefs.getBoolean("replyLineBelowHeader", true); publishButtonText=prefs.getString("publishButtonText", ""); theme=ThemePreference.values()[prefs.getInt("theme", 0)]; recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new HashMap<>()); @@ -136,6 +138,7 @@ public class GlobalUserPreferences{ .putBoolean("autoHideFab", autoHideFab) .putString("publishButtonText", publishButtonText) .putBoolean("bottomEncoding", bottomEncoding) + .putBoolean("replyLineBelowHeader", replyLineBelowHeader) .putInt("theme", theme.ordinal()) .putString("color", color.name()) .putString("recentLanguages", gson.toJson(recentLanguages)) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java index db7ee5596..a27f9c73c 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java @@ -333,7 +333,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr scheduleTimeBtn=view.findViewById(R.id.scheduled_time_btn); sensitiveIcon=view.findViewById(R.id.sensitive_icon); sensitiveItem=view.findViewById(R.id.sensitive_item); - replyText=view.findViewById(R.id.reply_text); + replyText=view.findViewById(GlobalUserPreferences.replyLineBelowHeader ? R.id.reply_text_below : R.id.reply_text); + view.findViewById(GlobalUserPreferences.replyLineBelowHeader ? R.id.reply_text : R.id.reply_text_below) + .setVisibility(View.GONE); if (isPhotoPickerAvailable()) { PopupMenu attachPopup = new PopupMenu(getContext(), mediaBtn); diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java index 34e7a522f..d7f299143 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java @@ -253,6 +253,10 @@ public class SettingsFragment extends MastodonToolbarFragment{ GlobalUserPreferences.save(); needAppRestart=true; })); + items.add(new SwitchItem(R.string.sk_reply_line_below_avatar, R.drawable.ic_fluent_arrow_reply_24_regular, GlobalUserPreferences.replyLineBelowHeader, i->{ + GlobalUserPreferences.replyLineBelowHeader=i.checked; + GlobalUserPreferences.save(); + })); 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.save(); diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java index 4d3022a46..71973c466 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java @@ -38,10 +38,9 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ private int iconEnd; private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(); private View.OnClickListener handleClick; - private boolean isLastLine = true; - private int lineNo = 0; + boolean belowHeader, needBottomPadding; - public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick){ + public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick) { super(parentID, parentFragment); SpannableStringBuilder ssb=new SpannableStringBuilder(text); HtmlParser.parseCustomEmoji(ssb, emojis); @@ -54,14 +53,6 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ updateVisibility(visibility); } - public void setIsLastLine(boolean isLastLine) { - this.isLastLine = isLastLine; - } - - public void setLineNo(int lineNo) { - this.lineNo = lineNo; - } - public void updateVisibility(StatusPrivacy visibility) { this.visibility = visibility; this.iconEnd = visibility != null ? switch (visibility) { @@ -89,12 +80,10 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ public static class Holder extends StatusDisplayItem.Holder implements ImageLoaderViewHolder{ private final TextView text; - private final View frame; public Holder(Activity activity, ViewGroup parent){ super(activity, R.layout.display_item_reblog_or_reply_line, parent); text=findViewById(R.id.text); - frame=findViewById(R.id.frame); } @Override @@ -116,9 +105,12 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ if(Build.VERSION.SDK_INT { @@ -140,28 +142,28 @@ public abstract class StatusDisplayItem{ 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( + replyLine = 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 (replyLine != null && !GlobalUserPreferences.replyLineBelowHeader) { + items.add(replyLine); } - if (lastLine != null) lastLine.setIsLastLine(true); HeaderStatusDisplayItem header; items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, notification, scheduledStatus)); + + if (replyLine != null && GlobalUserPreferences.replyLineBelowHeader) { + replyLine.belowHeader = true; + items.add(replyLine); + } + if(!TextUtils.isEmpty(statusForContent.content)) items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent, disableTranslate)); + else if (GlobalUserPreferences.replyLineBelowHeader && replyLine != null) + replyLine.needBottomPadding=true; else header.needBottomPadding=true; List imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList()); diff --git a/mastodon/src/main/res/layout/fragment_compose.xml b/mastodon/src/main/res/layout/fragment_compose.xml index 80426d5a5..56f797dc6 100644 --- a/mastodon/src/main/res/layout/fragment_compose.xml +++ b/mastodon/src/main/res/layout/fragment_compose.xml @@ -64,10 +64,10 @@ android:paddingBottom="6dp" android:textAppearance="@style/m3_title_small" android:drawableStart="@drawable/ic_fluent_arrow_reply_20_filled" - tools:drawableEnd="@drawable/ic_fluent_earth_20_regular" android:drawableTint="?android:textColorSecondary" android:drawablePadding="6dp" android:singleLine="true" + android:text="@string/sk_in_reply" android:ellipsize="end"/> + + Followed from %s Auto-hide Compose button In reply + “In reply to” line below avatar \ No newline at end of file From 16f907c91f0d48170f65fd0f291627e26f88f547 Mon Sep 17 00:00:00 2001 From: sk Date: Wed, 15 Mar 2023 19:23:20 +0100 Subject: [PATCH 28/34] show thread reply line --- .../android/ui/displayitems/StatusDisplayItem.java | 13 ++++++++++--- mastodon/src/main/res/values/strings_sk.xml | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index 92f1fd05f..62caa539a 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -135,13 +135,20 @@ public abstract class StatusDisplayItem{ } ReblogOrReplyLineStatusDisplayItem replyLine = null; - if(statusForContent.inReplyToAccountId!=null){ + boolean threadReply = statusForContent.inReplyToAccountId != null && + statusForContent.inReplyToAccountId.equals(status.account.id); + + if(statusForContent.inReplyToAccountId!=null && !(threadReply && fragment instanceof ThreadFragment)){ Account account = knownAccounts.get(statusForContent.inReplyToAccountId); - View.OnClickListener handleClick = account == null ? null : i -> { + View.OnClickListener handleClick = account == null || threadReply ? 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); + + String text = threadReply ? + fragment.getString(R.string.sk_show_thread) : account != null ? + fragment.getString(R.string.in_reply_to, account.displayName) : fragment.getString(R.string.sk_in_reply); + replyLine = new ReblogOrReplyLineStatusDisplayItem( parentID, fragment, text, account == null ? List.of() : account.emojis, R.drawable.ic_fluent_arrow_reply_20_filled, null, handleClick diff --git a/mastodon/src/main/res/values/strings_sk.xml b/mastodon/src/main/res/values/strings_sk.xml index 020abb079..4406ecca2 100644 --- a/mastodon/src/main/res/values/strings_sk.xml +++ b/mastodon/src/main/res/values/strings_sk.xml @@ -264,4 +264,5 @@ Auto-hide Compose button In reply “In reply to” line below avatar + Show thread \ No newline at end of file From e2193e8e3c700addbe6951e3fb821726d56d93a2 Mon Sep 17 00:00:00 2001 From: sk Date: Wed, 15 Mar 2023 21:24:25 +0100 Subject: [PATCH 29/34] reply line below, compact above --- .../android/GlobalUserPreferences.java | 9 ++- .../android/fragments/ComposeFragment.java | 4 +- .../android/fragments/SettingsFragment.java | 15 ++++- .../ReblogOrReplyLineStatusDisplayItem.java | 20 ++++-- .../ui/displayitems/StatusDisplayItem.java | 65 ++++++++++++------- .../ic_fluent_re_order_24_regular.xml | 3 + .../display_item_reblog_or_reply_line.xml | 41 ++++++++++-- mastodon/src/main/res/values/strings_sk.xml | 3 +- 8 files changed, 116 insertions(+), 44 deletions(-) create mode 100644 mastodon/src/main/res/drawable/ic_fluent_re_order_24_regular.xml diff --git a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java index d66b63f04..fa2d08f3e 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java +++ b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java @@ -44,7 +44,8 @@ public class GlobalUserPreferences{ public static boolean collapseLongPosts; public static boolean spectatorMode; public static boolean autoHideFab; - public static boolean replyLineBelowHeader; + public static boolean replyLineAboveHeader; + public static boolean compactReblogReplyLine; public static String publishButtonText; public static ThemePreference theme; public static ColorPreference color; @@ -94,7 +95,8 @@ public class GlobalUserPreferences{ collapseLongPosts=prefs.getBoolean("collapseLongPosts", true); spectatorMode=prefs.getBoolean("spectatorMode", false); autoHideFab=prefs.getBoolean("autoHideFab", true); - replyLineBelowHeader=prefs.getBoolean("replyLineBelowHeader", true); + replyLineAboveHeader =prefs.getBoolean("replyLineAboveHeader", false); + compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true); publishButtonText=prefs.getString("publishButtonText", ""); theme=ThemePreference.values()[prefs.getInt("theme", 0)]; recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new HashMap<>()); @@ -136,9 +138,10 @@ public class GlobalUserPreferences{ .putBoolean("collapseLongPosts", collapseLongPosts) .putBoolean("spectatorMode", spectatorMode) .putBoolean("autoHideFab", autoHideFab) + .putBoolean("compactReblogReplyLine", compactReblogReplyLine) .putString("publishButtonText", publishButtonText) .putBoolean("bottomEncoding", bottomEncoding) - .putBoolean("replyLineBelowHeader", replyLineBelowHeader) + .putBoolean("replyLineAboveHeader", replyLineAboveHeader) .putInt("theme", theme.ordinal()) .putString("color", color.name()) .putString("recentLanguages", gson.toJson(recentLanguages)) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java index a27f9c73c..7afaf9935 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java @@ -333,8 +333,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr scheduleTimeBtn=view.findViewById(R.id.scheduled_time_btn); sensitiveIcon=view.findViewById(R.id.sensitive_icon); sensitiveItem=view.findViewById(R.id.sensitive_item); - replyText=view.findViewById(GlobalUserPreferences.replyLineBelowHeader ? R.id.reply_text_below : R.id.reply_text); - view.findViewById(GlobalUserPreferences.replyLineBelowHeader ? R.id.reply_text : R.id.reply_text_below) + replyText=view.findViewById(GlobalUserPreferences.replyLineAboveHeader ? R.id.reply_text : R.id.reply_text_below); + view.findViewById(GlobalUserPreferences.replyLineAboveHeader ? R.id.reply_text_below : R.id.reply_text) .setVisibility(View.GONE); if (isPhotoPickerAvailable()) { diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java index d7f299143..6704813bc 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java @@ -80,7 +80,7 @@ public class SettingsFragment extends MastodonToolbarFragment{ private ArrayList items=new ArrayList<>(); private ThemeItem themeItem; private NotificationPolicyItem notificationPolicyItem; - private SwitchItem showNewPostsButtonItem, glitchModeItem; + private SwitchItem showNewPostsButtonItem, glitchModeItem, compactReblogReplyLineItem; private String accountID; private boolean needUpdateNotificationSettings; private boolean needAppRestart; @@ -253,10 +253,19 @@ public class SettingsFragment extends MastodonToolbarFragment{ GlobalUserPreferences.save(); needAppRestart=true; })); - items.add(new SwitchItem(R.string.sk_reply_line_below_avatar, R.drawable.ic_fluent_arrow_reply_24_regular, GlobalUserPreferences.replyLineBelowHeader, i->{ - GlobalUserPreferences.replyLineBelowHeader=i.checked; + items.add(new SwitchItem(R.string.sk_reply_line_above_avatar, R.drawable.ic_fluent_arrow_reply_24_regular, GlobalUserPreferences.replyLineAboveHeader, i->{ + GlobalUserPreferences.replyLineAboveHeader=i.checked; + GlobalUserPreferences.compactReblogReplyLine=i.checked; + compactReblogReplyLineItem.enabled=i.checked; + compactReblogReplyLineItem.checked= GlobalUserPreferences.replyLineAboveHeader; + if (list.findViewHolderForAdapterPosition(items.indexOf(compactReblogReplyLineItem)) instanceof SwitchViewHolder svh) svh.rebind(); GlobalUserPreferences.save(); })); + items.add(compactReblogReplyLineItem=new SwitchItem(R.string.sk_compact_reblog_reply_line, R.drawable.ic_fluent_re_order_24_regular, GlobalUserPreferences.compactReblogReplyLine, i->{ + GlobalUserPreferences.compactReblogReplyLine=i.checked; + GlobalUserPreferences.save();; + })); + compactReblogReplyLineItem.enabled=GlobalUserPreferences.replyLineAboveHeader; 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.save(); diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java index 71973c466..edb1b60d3 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java @@ -39,6 +39,7 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(); private View.OnClickListener handleClick; boolean belowHeader, needBottomPadding; + ReblogOrReplyLineStatusDisplayItem secondary; public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick) { super(parentID, parentFragment); @@ -79,15 +80,17 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ } public static class Holder extends StatusDisplayItem.Holder implements ImageLoaderViewHolder{ - private final TextView text; + private final TextView text, secondaryText; + private final View secondaryWrap; public Holder(Activity activity, ViewGroup parent){ super(activity, R.layout.display_item_reblog_or_reply_line, parent); text=findViewById(R.id.text); + secondaryText=findViewById(R.id.secondary_text); + secondaryWrap=findViewById(R.id.secondary_wrap); } - @Override - public void onBind(ReblogOrReplyLineStatusDisplayItem item){ + private void bindLine(ReblogOrReplyLineStatusDisplayItem item, TextView text) { text.setText(item.text); text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, item.iconEnd, 0); text.setOnClickListener(item.handleClick); @@ -104,11 +107,18 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ if (visibilityText != 0) text.setContentDescription(item.text + " (" + ctx.getString(visibilityText) + ")"); if(Build.VERSION.SDK_INT { + args.putParcelable("profileAccount", Parcels.wrap(account)); + Nav.go(fragment.getActivity(), ProfileFragment.class, args); + }; + + String text = threadReply ? fragment.getString(R.string.sk_show_thread) + : account == null ? fragment.getString(R.string.sk_in_reply) + : GlobalUserPreferences.compactReblogReplyLine && status.reblog != null ? account.displayName + : fragment.getString(R.string.in_reply_to, account.displayName); + + replyLine = new ReblogOrReplyLineStatusDisplayItem( + parentID, fragment, text, account == null ? List.of() : account.emojis, + R.drawable.ic_fluent_arrow_reply_20_filled, null, handleClick + ); + } + if(status.reblog!=null){ boolean isOwnPost = AccountSessionManager.getInstance().isSelf(fragment.getAccountID(), status.account); - items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.user_boosted, status.account.displayName), status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20_filled, isOwnPost ? status.visibility : null, i->{ + String text = GlobalUserPreferences.compactReblogReplyLine && replyLine != null + ? status.account.displayName + : fragment.getString(R.string.user_boosted, status.account.displayName); + + items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, text, status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20_filled, isOwnPost ? status.visibility : null, i->{ args.putParcelable("profileAccount", Parcels.wrap(status.account)); Nav.go(fragment.getActivity(), ProfileFragment.class, args); })); @@ -134,42 +161,30 @@ public abstract class StatusDisplayItem{ ))); } - ReblogOrReplyLineStatusDisplayItem replyLine = null; - boolean threadReply = statusForContent.inReplyToAccountId != null && - statusForContent.inReplyToAccountId.equals(status.account.id); + if (replyLine != null && GlobalUserPreferences.replyLineAboveHeader) { + Optional primaryLine = items.stream() + .filter(i -> i instanceof ReblogOrReplyLineStatusDisplayItem) + .map(ReblogOrReplyLineStatusDisplayItem.class::cast) + .findFirst(); - if(statusForContent.inReplyToAccountId!=null && !(threadReply && fragment instanceof ThreadFragment)){ - Account account = knownAccounts.get(statusForContent.inReplyToAccountId); - View.OnClickListener handleClick = account == null || threadReply ? null : i -> { - args.putParcelable("profileAccount", Parcels.wrap(account)); - Nav.go(fragment.getActivity(), ProfileFragment.class, args); - }; - - String text = threadReply ? - fragment.getString(R.string.sk_show_thread) : account != null ? - fragment.getString(R.string.in_reply_to, account.displayName) : fragment.getString(R.string.sk_in_reply); - - replyLine = new ReblogOrReplyLineStatusDisplayItem( - parentID, fragment, text, account == null ? List.of() : account.emojis, - R.drawable.ic_fluent_arrow_reply_20_filled, null, handleClick - ); - } - - if (replyLine != null && !GlobalUserPreferences.replyLineBelowHeader) { - items.add(replyLine); + if (primaryLine.isPresent() && GlobalUserPreferences.compactReblogReplyLine) { + primaryLine.get().secondary = replyLine; + } else { + items.add(replyLine); + } } HeaderStatusDisplayItem header; items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, notification, scheduledStatus)); - if (replyLine != null && GlobalUserPreferences.replyLineBelowHeader) { + if (replyLine != null && !GlobalUserPreferences.replyLineAboveHeader) { replyLine.belowHeader = true; items.add(replyLine); } if(!TextUtils.isEmpty(statusForContent.content)) items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent, disableTranslate)); - else if (GlobalUserPreferences.replyLineBelowHeader && replyLine != null) + else if (!GlobalUserPreferences.replyLineAboveHeader && replyLine != null) replyLine.needBottomPadding=true; else header.needBottomPadding=true; diff --git a/mastodon/src/main/res/drawable/ic_fluent_re_order_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_re_order_24_regular.xml new file mode 100644 index 000000000..15ef7bc46 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_re_order_24_regular.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/layout/display_item_reblog_or_reply_line.xml b/mastodon/src/main/res/layout/display_item_reblog_or_reply_line.xml index 90bf482c3..f3de28551 100644 --- a/mastodon/src/main/res/layout/display_item_reblog_or_reply_line.xml +++ b/mastodon/src/main/res/layout/display_item_reblog_or_reply_line.xml @@ -1,16 +1,16 @@ - - \ No newline at end of file + + + + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/values/strings_sk.xml b/mastodon/src/main/res/values/strings_sk.xml index 4406ecca2..1a6a70b3e 100644 --- a/mastodon/src/main/res/values/strings_sk.xml +++ b/mastodon/src/main/res/values/strings_sk.xml @@ -263,6 +263,7 @@ Followed from %s Auto-hide Compose button In reply - “In reply to” line below avatar + “In reply to” line above avatar Show thread + Compact reblog/reply line \ No newline at end of file From 938ae97cacb5e50f35c6cc346a14cd7b745a92f1 Mon Sep 17 00:00:00 2001 From: sk Date: Wed, 15 Mar 2023 21:26:41 +0100 Subject: [PATCH 30/34] remove click handler for reply line --- .../android/ui/displayitems/StatusDisplayItem.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index 4179ee64d..19b30ceb0 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -116,19 +116,13 @@ public abstract class StatusDisplayItem{ if(statusForContent.inReplyToAccountId!=null && !(threadReply && fragment instanceof ThreadFragment)){ Account account = knownAccounts.get(statusForContent.inReplyToAccountId); - View.OnClickListener handleClick = account == null || threadReply ? null : i -> { - args.putParcelable("profileAccount", Parcels.wrap(account)); - Nav.go(fragment.getActivity(), ProfileFragment.class, args); - }; - String text = threadReply ? fragment.getString(R.string.sk_show_thread) : account == null ? fragment.getString(R.string.sk_in_reply) : GlobalUserPreferences.compactReblogReplyLine && status.reblog != null ? account.displayName : fragment.getString(R.string.in_reply_to, account.displayName); - replyLine = new ReblogOrReplyLineStatusDisplayItem( parentID, fragment, text, account == null ? List.of() : account.emojis, - R.drawable.ic_fluent_arrow_reply_20_filled, null, handleClick + R.drawable.ic_fluent_arrow_reply_20_filled, null, null ); } From 499ac8f727be45cb35c3045266bdd3797ff1b049 Mon Sep 17 00:00:00 2001 From: sk Date: Wed, 15 Mar 2023 22:30:57 +0100 Subject: [PATCH 31/34] improve compact reply/reblog header --- .../android/GlobalUserPreferences.java | 2 +- .../android/fragments/SettingsFragment.java | 4 +- .../ReblogOrReplyLineStatusDisplayItem.java | 29 +++++++--- .../ui/displayitems/StatusDisplayItem.java | 2 +- .../display_item_reblog_or_reply_line.xml | 58 +++++++++---------- 5 files changed, 54 insertions(+), 41 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java index fa2d08f3e..6115c1120 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java +++ b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java @@ -95,7 +95,7 @@ public class GlobalUserPreferences{ collapseLongPosts=prefs.getBoolean("collapseLongPosts", true); spectatorMode=prefs.getBoolean("spectatorMode", false); autoHideFab=prefs.getBoolean("autoHideFab", true); - replyLineAboveHeader =prefs.getBoolean("replyLineAboveHeader", false); + replyLineAboveHeader=prefs.getBoolean("replyLineAboveHeader", true); compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true); publishButtonText=prefs.getString("publishButtonText", ""); theme=ThemePreference.values()[prefs.getInt("theme", 0)]; diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java index 6704813bc..487751cdc 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java @@ -260,10 +260,12 @@ public class SettingsFragment extends MastodonToolbarFragment{ compactReblogReplyLineItem.checked= GlobalUserPreferences.replyLineAboveHeader; if (list.findViewHolderForAdapterPosition(items.indexOf(compactReblogReplyLineItem)) instanceof SwitchViewHolder svh) svh.rebind(); GlobalUserPreferences.save(); + needAppRestart=true; })); items.add(compactReblogReplyLineItem=new SwitchItem(R.string.sk_compact_reblog_reply_line, R.drawable.ic_fluent_re_order_24_regular, GlobalUserPreferences.compactReblogReplyLine, i->{ GlobalUserPreferences.compactReblogReplyLine=i.checked; - GlobalUserPreferences.save();; + GlobalUserPreferences.save(); + needAppRestart=true; })); compactReblogReplyLineItem.enabled=GlobalUserPreferences.replyLineAboveHeader; items.add(new SwitchItem(R.string.sk_settings_translate_only_opened, R.drawable.ic_fluent_translate_24_regular, GlobalUserPreferences.translateButtonOpenedOnly, i->{ diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java index edb1b60d3..2e1b8a77f 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java @@ -10,8 +10,10 @@ import android.text.SpannableStringBuilder; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; +import android.widget.LinearLayout; 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.Emoji; @@ -39,7 +41,7 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(); private View.OnClickListener handleClick; boolean belowHeader, needBottomPadding; - ReblogOrReplyLineStatusDisplayItem secondary; + ReblogOrReplyLineStatusDisplayItem extra; public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick) { super(parentID, parentFragment); @@ -80,14 +82,26 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ } public static class Holder extends StatusDisplayItem.Holder implements ImageLoaderViewHolder{ - private final TextView text, secondaryText; - private final View secondaryWrap; + private final TextView text, extraText; + private final View separator; public Holder(Activity activity, ViewGroup parent){ super(activity, R.layout.display_item_reblog_or_reply_line, parent); text=findViewById(R.id.text); - secondaryText=findViewById(R.id.secondary_text); - secondaryWrap=findViewById(R.id.secondary_wrap); + extraText=findViewById(R.id.extra_text); + separator=findViewById(R.id.separator); + if (GlobalUserPreferences.replyLineAboveHeader && GlobalUserPreferences.compactReblogReplyLine) { + itemView.getViewTreeObserver().addOnPreDrawListener(() -> { + int orientation = ((LinearLayout) itemView).getOrientation(); + extraText.setPaddingRelative(extraText.getPaddingStart(), V.dp(16), extraText.getPaddingEnd(), extraText.getPaddingBottom()); + separator.setVisibility(View.GONE); + if (getItem() != null && getItem().extra != null) { + if (orientation == LinearLayout.VERTICAL) extraText.setPaddingRelative(extraText.getPaddingStart(), 0, extraText.getPaddingEnd(), extraText.getPaddingBottom()); + else separator.setVisibility(View.VISIBLE); + } + return true; + }); + } } private void bindLine(ReblogOrReplyLineStatusDisplayItem item, TextView text) { @@ -114,8 +128,9 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ @Override public void onBind(ReblogOrReplyLineStatusDisplayItem item){ bindLine(item, text); - if (item.secondary != null) bindLine(item.secondary, secondaryText); - secondaryWrap.setVisibility(item.secondary == null ? View.GONE : View.VISIBLE); + if (item.extra != null) bindLine(item.extra, extraText); + extraText.setVisibility(item.extra == null ? View.GONE : View.VISIBLE); + separator.setVisibility(item.extra == null ? View.GONE : View.VISIBLE); ViewGroup.MarginLayoutParams params = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); params.bottomMargin = item.belowHeader ? V.dp(-6) : V.dp(-12); params.topMargin = item.belowHeader ? V.dp(-6) : 0; diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index 19b30ceb0..3e3bb3bf0 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -162,7 +162,7 @@ public abstract class StatusDisplayItem{ .findFirst(); if (primaryLine.isPresent() && GlobalUserPreferences.compactReblogReplyLine) { - primaryLine.get().secondary = replyLine; + primaryLine.get().extra = replyLine; } else { items.add(replyLine); } diff --git a/mastodon/src/main/res/layout/display_item_reblog_or_reply_line.xml b/mastodon/src/main/res/layout/display_item_reblog_or_reply_line.xml index f3de28551..2138522c0 100644 --- a/mastodon/src/main/res/layout/display_item_reblog_or_reply_line.xml +++ b/mastodon/src/main/res/layout/display_item_reblog_or_reply_line.xml @@ -1,16 +1,17 @@ - - + android:layout_marginHorizontal="6dp" + android:paddingTop="16dp" + android:paddingHorizontal="1dp" + android:textAppearance="@style/m3_title_small" + android:gravity="center_horizontal" + android:importantForAccessibility="no" + android:includeFontPadding="false" + android:text="@string/sk_separator" /> - - - - - + \ No newline at end of file From f71bb6b78c6f6ac4d5c40ce3295e0bfc5dd8977a Mon Sep 17 00:00:00 2001 From: sk Date: Wed, 15 Mar 2023 22:36:30 +0100 Subject: [PATCH 32/34] use pre draw listener instead of post hopefully fixes sk22#422 --- .../displayitems/TextStatusDisplayItem.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java index 6e94d4195..83a8b7ef1 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java @@ -6,6 +6,7 @@ import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.widget.LinearLayout; import android.widget.Button; import android.widget.ScrollView; @@ -227,13 +228,20 @@ public class TextStatusDisplayItem extends StatusDisplayItem{ readMore.setVisibility(View.GONE); } - if (GlobalUserPreferences.collapseLongPosts) text.post(() -> { - boolean tooBig = text.getMeasuredHeight() > textMaxHeight; - boolean inTimeline = !item.textSelectable; - boolean hasSpoiler = !TextUtils.isEmpty(item.status.spoilerText); - boolean expandable = inTimeline && tooBig && !hasSpoiler; - item.parentFragment.onEnableExpandable(this, expandable); - }); + if (GlobalUserPreferences.collapseLongPosts) { + text.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + text.getViewTreeObserver().removeOnPreDrawListener(this); + boolean tooBig = text.getMeasuredHeight() > textMaxHeight; + boolean inTimeline = !item.textSelectable; + boolean hasSpoiler = !TextUtils.isEmpty(item.status.spoilerText); + boolean expandable = inTimeline && tooBig && !hasSpoiler; + item.parentFragment.onEnableExpandable(Holder.this, expandable); + return true; + } + }); + } readMore.setVisibility(item.status.textExpandable && !item.status.textExpanded ? View.VISIBLE : View.GONE); textScrollView.setLayoutParams(item.status.textExpandable && !item.status.textExpanded ? collapseParams : wrapParams); From 94b862a3ff526df33367ed93630d5159db2d9f84 Mon Sep 17 00:00:00 2001 From: sk Date: Wed, 15 Mar 2023 23:27:43 +0100 Subject: [PATCH 33/34] improve reblog/reply line render --- .../displayitems/ReblogOrReplyLineStatusDisplayItem.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java index 2e1b8a77f..6f9766c3d 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java @@ -92,13 +92,10 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ separator=findViewById(R.id.separator); if (GlobalUserPreferences.replyLineAboveHeader && GlobalUserPreferences.compactReblogReplyLine) { itemView.getViewTreeObserver().addOnPreDrawListener(() -> { + if (item == null) return true; int orientation = ((LinearLayout) itemView).getOrientation(); - extraText.setPaddingRelative(extraText.getPaddingStart(), V.dp(16), extraText.getPaddingEnd(), extraText.getPaddingBottom()); - separator.setVisibility(View.GONE); - if (getItem() != null && getItem().extra != null) { - if (orientation == LinearLayout.VERTICAL) extraText.setPaddingRelative(extraText.getPaddingStart(), 0, extraText.getPaddingEnd(), extraText.getPaddingBottom()); - else separator.setVisibility(View.VISIBLE); - } + extraText.setPaddingRelative(extraText.getPaddingStart(), item.extra != null && orientation == LinearLayout.VERTICAL ? 0 : V.dp(16), extraText.getPaddingEnd(), extraText.getPaddingBottom()); + separator.setVisibility(item.extra != null && orientation == LinearLayout.HORIZONTAL ? View.VISIBLE : View.GONE); return true; }); } From 9cc8b2668c96bc0ea1c636ae8de904058a33452c Mon Sep 17 00:00:00 2001 From: sk Date: Wed, 15 Mar 2023 23:31:51 +0100 Subject: [PATCH 34/34] fix show thread not working for reposts --- .../joinmastodon/android/ui/displayitems/StatusDisplayItem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index 3e3bb3bf0..e57cb0218 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -112,7 +112,7 @@ public abstract class StatusDisplayItem{ ReblogOrReplyLineStatusDisplayItem replyLine = null; boolean threadReply = statusForContent.inReplyToAccountId != null && - statusForContent.inReplyToAccountId.equals(status.account.id); + statusForContent.inReplyToAccountId.equals(statusForContent.account.id); if(statusForContent.inReplyToAccountId!=null && !(threadReply && fragment instanceof ThreadFragment)){ Account account = knownAccounts.get(statusForContent.inReplyToAccountId);