diff --git a/src/cheogram/java/com/cheogram/android/Util.java b/src/cheogram/java/com/cheogram/android/Util.java
index 1211141f18ae19770e1ef72dcf70b8882178e589..d416fba89be2170ead8feee571888ca7542369ad 100644
--- a/src/cheogram/java/com/cheogram/android/Util.java
+++ b/src/cheogram/java/com/cheogram/android/Util.java
@@ -7,7 +7,11 @@ import android.widget.ListView;
import android.widget.ListAdapter;
public class Util {
- public static void justifyListViewHeightBasedOnChildren (ListView listView) {
+ public static void justifyListViewHeightBasedOnChildren(ListView listView) {
+ justifyListViewHeightBasedOnChildren(listView, 0, false);
+ }
+
+ public static void justifyListViewHeightBasedOnChildren(ListView listView, int offset, boolean andWidth) {
ListAdapter adapter = listView.getAdapter();
if (adapter == null) {
@@ -15,16 +19,26 @@ public class Util {
}
ViewGroup vg = listView;
int totalHeight = 0;
- final int width = listView.getWidth() > 0 ? listView.getWidth() : listView.getContext().getResources().getDisplayMetrics().widthPixels;
+ int maxWidth = 0;
+ final var displayWidth = listView.getContext().getResources().getDisplayMetrics().widthPixels;
+ final int width = !andWidth && listView.getWidth() > 0 ? listView.getWidth() : (displayWidth - offset);
final int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST);
for (int i = 0; i < adapter.getCount(); i++) {
View listItem = adapter.getView(i, null, vg);
listItem.measure(widthSpec, 0);
totalHeight += listItem.getMeasuredHeight();
+ maxWidth = Math.max(maxWidth, listItem.getMeasuredWidth());
}
ViewGroup.LayoutParams par = listView.getLayoutParams();
- par.height = totalHeight + (listView.getDividerHeight() * (adapter.getCount() - 1));
+ par.height = totalHeight + (listView.getDividerHeight() * Math.max(0, adapter.getCount() - 1));
+ if (andWidth) {
+ if (maxWidth <= (displayWidth - offset) && maxWidth > offset*2) {
+ par.width = maxWidth;
+ } else {
+ par.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ }
+ }
listView.setLayoutParams(par);
listView.requestLayout();
}
diff --git a/src/cheogram/res/drawable/background_link_description.xml b/src/cheogram/res/drawable/background_link_description.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6b8d9a0cfae1a1915e68c6fc4614bc4c4ec6abc8
--- /dev/null
+++ b/src/cheogram/res/drawable/background_link_description.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/cheogram/res/layout/link_description.xml b/src/cheogram/res/layout/link_description.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f302c2b2f1e1dabf75577ef66e50d2c76fff0e77
--- /dev/null
+++ b/src/cheogram/res/layout/link_description.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java
index d7da48c895f181d52837d35ca909fde7c33b8a72..9ef0a55e1f74b314d3cbeaeeac735421eadd89d3 100644
--- a/src/main/java/eu/siacs/conversations/entities/Message.java
+++ b/src/main/java/eu/siacs/conversations/entities/Message.java
@@ -1323,6 +1323,19 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
return null;
}
+ public List getLinkDescriptions() {
+ final ArrayList result = new ArrayList<>();
+ if (this.payloads == null) return result;
+
+ for (Element el : this.payloads) {
+ if (el.getName().equals("Description") && el.getNamespace().equals("http://www.w3.org/1999/02/22-rdf-syntax-ns#")) {
+ result.add(el);
+ }
+ }
+
+ return result;
+ }
+
public String getMimeType() {
String extension;
if (relativeFilePath != null) {
diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
index b46bd28bc22a5f8b9223292b7c82a712752f2490..c28b2ba735dd98d335a62e55d8d164377eb8f68a 100644
--- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
@@ -777,6 +777,9 @@ public class MessageParser extends AbstractParser implements Consumer {
viewHolder.inReplyToQuote = view.findViewById(R.id.in_reply_to_quote);
viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received);
viewHolder.audioPlayer = view.findViewById(R.id.audio_player);
+ viewHolder.link_descriptions = view.findViewById(R.id.link_descriptions);
viewHolder.thread_identicon = view.findViewById(R.id.thread_identicon);
break;
case RECEIVED:
@@ -1139,6 +1145,7 @@ public class MessageAdapter extends ArrayAdapter {
viewHolder.encryption = view.findViewById(R.id.message_encryption);
viewHolder.audioPlayer = view.findViewById(R.id.audio_player);
viewHolder.commands_list = view.findViewById(R.id.commands_list);
+ viewHolder.link_descriptions = view.findViewById(R.id.link_descriptions);
viewHolder.thread_identicon = view.findViewById(R.id.thread_identicon);
break;
case STATUS:
@@ -1152,6 +1159,16 @@ public class MessageAdapter extends ArrayAdapter {
default:
throw new AssertionError("Unknown view type");
}
+ if (viewHolder.link_descriptions != null) {
+ viewHolder.link_descriptions.setOnItemClickListener((adapter, v, pos, id) -> {
+ final var desc = (Element) adapter.getItemAtPosition(pos);
+ var url = desc.findChildContent("url", "https://ogp.me/ns#");
+ // should we prefer about? Maybe, it's the real original link, but it's not what we show the user
+ if (url == null || url.length() < 1) url = desc.getAttribute("{http://www.w3.org/1999/02/22-rdf-syntax-ns#}about");
+ if (url == null || url.length() < 1) return;
+ new FixedURLSpan(url).onClick(v);
+ });
+ }
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
@@ -1523,6 +1540,19 @@ public class MessageAdapter extends ArrayAdapter {
viewHolder.inReplyToQuote.setOnClickListener((v) -> mConversationFragment.jumpTo(message.getInReplyTo()));
setTextColor(viewHolder.inReplyTo, bubbleColor);
}
+
+ final var descriptions = message.getLinkDescriptions();
+ viewHolder.link_descriptions.setAdapter(new ArrayAdapter<>(activity, 0, descriptions) {
+ @Override
+ public View getView(int position, View view, @NonNull ViewGroup parent) {
+ final LinkDescriptionBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.link_description, parent, false);
+ binding.title.setText(getItem(position).findChildContent("title", "https://ogp.me/ns#"));
+ binding.description.setText(getItem(position).findChildContent("description", "https://ogp.me/ns#"));
+ binding.url.setText(getItem(position).findChildContent("url", "https://ogp.me/ns#"));
+ return binding.getRoot();
+ }
+ });
+ Util.justifyListViewHeightBasedOnChildren(viewHolder.link_descriptions, (int)(metrics.density * 100), true);
}
displayStatus(viewHolder, message, type, bubbleColor);
@@ -1750,6 +1780,7 @@ public class MessageAdapter extends ArrayAdapter {
protected TextView status_message;
protected TextView encryption;
protected ListView commands_list;
+ protected ListView link_descriptions;
protected GithubIdenticonView thread_identicon;
}
diff --git a/src/main/res/layout/item_message_content.xml b/src/main/res/layout/item_message_content.xml
index aa79f772635504cc43ff90dca85e7a9d03102064..523b5d137d0cfa4ad21c770ad94b276a5d4811f9 100644
--- a/src/main/res/layout/item_message_content.xml
+++ b/src/main/res/layout/item_message_content.xml
@@ -89,6 +89,14 @@
android:divider="@android:color/transparent"
android:dividerHeight="0dp">
+
+