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"> + +