mirror of
https://codeberg.org/tom79/Fedilab.git
synced 2025-07-14 07:30:29 +03:00
#1219 - highlight bottom hashtags
This commit is contained in:
parent
1df2cba3d0
commit
293a811392
6 changed files with 107 additions and 0 deletions
|
@ -16,6 +16,7 @@ package app.fedilab.android.mastodon.client.entities.api;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
@ -28,6 +29,7 @@ import java.lang.ref.WeakReference;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import app.fedilab.android.mastodon.helper.Helper;
|
||||||
import app.fedilab.android.mastodon.helper.SpannableHelper;
|
import app.fedilab.android.mastodon.helper.SpannableHelper;
|
||||||
import de.timfreiheit.mathjax.android.MathJaxView;
|
import de.timfreiheit.mathjax.android.MathJaxView;
|
||||||
|
|
||||||
|
@ -139,6 +141,8 @@ public class Status implements Serializable, Cloneable {
|
||||||
public boolean spoilerChecked = false;
|
public boolean spoilerChecked = false;
|
||||||
public Filter filteredByApp;
|
public Filter filteredByApp;
|
||||||
public transient Spannable contentSpan;
|
public transient Spannable contentSpan;
|
||||||
|
|
||||||
|
public transient String[] bottomTags;
|
||||||
public transient Spannable contentSpoilerSpan;
|
public transient Spannable contentSpoilerSpan;
|
||||||
public transient Spannable contentTranslateSpan;
|
public transient Spannable contentTranslateSpan;
|
||||||
public transient MathJaxView mathJaxView;
|
public transient MathJaxView mathJaxView;
|
||||||
|
@ -162,6 +166,13 @@ public class Status implements Serializable, Cloneable {
|
||||||
return contentSpan;
|
return contentSpan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized String[] getBottomTags() {
|
||||||
|
if(bottomTags == null) {
|
||||||
|
bottomTags = SpannableHelper.hasBottomTags(content);
|
||||||
|
}
|
||||||
|
return bottomTags;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized Spannable getSpanSpoiler(Context context, WeakReference<View> viewWeakReference, Callback callback) {
|
public synchronized Spannable getSpanSpoiler(Context context, WeakReference<View> viewWeakReference, Callback callback) {
|
||||||
if (contentSpoilerSpan == null) {
|
if (contentSpoilerSpan == null) {
|
||||||
contentSpoilerSpan = SpannableHelper.convert(context, spoiler_text, this, null, null, viewWeakReference, callback, true, false);
|
contentSpoilerSpan = SpannableHelper.convert(context, spoiler_text, this, null, null, viewWeakReference, callback, true, false);
|
||||||
|
|
|
@ -37,6 +37,7 @@ import android.text.TextPaint;
|
||||||
import android.text.style.ClickableSpan;
|
import android.text.style.ClickableSpan;
|
||||||
import android.text.style.QuoteSpan;
|
import android.text.style.QuoteSpan;
|
||||||
import android.text.style.URLSpan;
|
import android.text.style.URLSpan;
|
||||||
|
import android.util.Log;
|
||||||
import android.util.Patterns;
|
import android.util.Patterns;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
@ -107,6 +108,28 @@ public class SpannableHelper {
|
||||||
private static int linkColor;
|
private static int linkColor;
|
||||||
private static boolean underlineLinks;
|
private static boolean underlineLinks;
|
||||||
|
|
||||||
|
private static final String patternBottomTags = "\\n{2,}((#[\\w_À-ú-]+)\\s?)+$";
|
||||||
|
|
||||||
|
public static String[] hasBottomTags(String text) {
|
||||||
|
if (text == null) {
|
||||||
|
return new String[]{};
|
||||||
|
}
|
||||||
|
SpannableString initialContent;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
initialContent = new SpannableString(Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY));
|
||||||
|
} else {
|
||||||
|
initialContent = new SpannableString(Html.fromHtml(text));
|
||||||
|
}
|
||||||
|
final Pattern bottomTagsPattern = Pattern.compile(patternBottomTags, Pattern.CASE_INSENSITIVE);
|
||||||
|
Matcher matcherBottomTags = bottomTagsPattern.matcher(initialContent);
|
||||||
|
String[] tags = new String[]{};
|
||||||
|
while (matcherBottomTags.find()) {
|
||||||
|
String stringTags = Objects.requireNonNull(matcherBottomTags.group()).trim();
|
||||||
|
tags = stringTags.split("\\s");
|
||||||
|
}
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
public static Spannable convert(Context context, String text,
|
public static Spannable convert(Context context, String text,
|
||||||
Status status, Account account, Announcement announcement,
|
Status status, Account account, Announcement announcement,
|
||||||
WeakReference<View> viewWeakReference, Status.Callback callback, boolean convertHtml, boolean convertMarkdown) {
|
WeakReference<View> viewWeakReference, Status.Callback callback, boolean convertHtml, boolean convertMarkdown) {
|
||||||
|
@ -162,6 +185,7 @@ public class SpannableHelper {
|
||||||
} else {
|
} else {
|
||||||
initialContent = new SpannableString(text);
|
initialContent = new SpannableString(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean markdownSupport = sharedpreferences.getBoolean(context.getString(R.string.SET_MARKDOWN_SUPPORT), false);
|
boolean markdownSupport = sharedpreferences.getBoolean(context.getString(R.string.SET_MARKDOWN_SUPPORT), false);
|
||||||
//Get all links
|
//Get all links
|
||||||
SpannableStringBuilder content;
|
SpannableStringBuilder content;
|
||||||
|
@ -393,6 +417,20 @@ public class SpannableHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean underlineBottomHashTags = sharedpreferences.getBoolean(context.getString(R.string.SET_UNDERLINE_BOTTOM_HASHTAGS), true);
|
||||||
|
if(underlineBottomHashTags) {
|
||||||
|
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
|
||||||
|
final Pattern bottomTagsPattern = Pattern.compile(patternBottomTags, Pattern.CASE_INSENSITIVE);
|
||||||
|
Matcher matcherBottomTags = bottomTagsPattern.matcher(content);
|
||||||
|
int length = 0;
|
||||||
|
while (matcherBottomTags.find()) {
|
||||||
|
length = Objects.requireNonNull(matcherBottomTags.group()).length();
|
||||||
|
}
|
||||||
|
spannableStringBuilder.append(content,0, content.length()-length);
|
||||||
|
return trimSpannable(spannableStringBuilder);
|
||||||
|
|
||||||
}
|
}
|
||||||
return trimSpannable(new SpannableStringBuilder(content));
|
return trimSpannable(new SpannableStringBuilder(content));
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,6 +108,7 @@ import com.bumptech.glide.ListPreloader;
|
||||||
import com.bumptech.glide.RequestBuilder;
|
import com.bumptech.glide.RequestBuilder;
|
||||||
import com.bumptech.glide.request.RequestOptions;
|
import com.bumptech.glide.request.RequestOptions;
|
||||||
import com.github.stom79.mytransl.MyTransL;
|
import com.github.stom79.mytransl.MyTransL;
|
||||||
|
import com.google.android.material.chip.Chip;
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
import com.smarteist.autoimageslider.SliderAnimations;
|
import com.smarteist.autoimageslider.SliderAnimations;
|
||||||
import com.smarteist.autoimageslider.SliderView;
|
import com.smarteist.autoimageslider.SliderView;
|
||||||
|
@ -143,6 +144,7 @@ import app.fedilab.android.databinding.LayoutPollItemBinding;
|
||||||
import app.fedilab.android.mastodon.activities.ComposeActivity;
|
import app.fedilab.android.mastodon.activities.ComposeActivity;
|
||||||
import app.fedilab.android.mastodon.activities.ContextActivity;
|
import app.fedilab.android.mastodon.activities.ContextActivity;
|
||||||
import app.fedilab.android.mastodon.activities.CustomSharingActivity;
|
import app.fedilab.android.mastodon.activities.CustomSharingActivity;
|
||||||
|
import app.fedilab.android.mastodon.activities.HashTagActivity;
|
||||||
import app.fedilab.android.mastodon.activities.MediaActivity;
|
import app.fedilab.android.mastodon.activities.MediaActivity;
|
||||||
import app.fedilab.android.mastodon.activities.ProfileActivity;
|
import app.fedilab.android.mastodon.activities.ProfileActivity;
|
||||||
import app.fedilab.android.mastodon.activities.ReportActivity;
|
import app.fedilab.android.mastodon.activities.ReportActivity;
|
||||||
|
@ -508,6 +510,9 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
||||||
LinearLayoutCompat.MarginLayoutParams psc = (LinearLayoutCompat.MarginLayoutParams) holder.binding.statusContent.getLayoutParams();
|
LinearLayoutCompat.MarginLayoutParams psc = (LinearLayoutCompat.MarginLayoutParams) holder.binding.statusContent.getLayoutParams();
|
||||||
psc.setMarginStart((int) Helper.convertDpToPixel(6, context));
|
psc.setMarginStart((int) Helper.convertDpToPixel(6, context));
|
||||||
holder.binding.statusContent.setLayoutParams(psc);
|
holder.binding.statusContent.setLayoutParams(psc);
|
||||||
|
LinearLayoutCompat.MarginLayoutParams pst = (LinearLayoutCompat.MarginLayoutParams) holder.binding.statusHashtags.getLayoutParams();
|
||||||
|
pst.setMarginStart((int) Helper.convertDpToPixel(6, context));
|
||||||
|
holder.binding.statusHashtags.setLayoutParams(pst);
|
||||||
LinearLayoutCompat.MarginLayoutParams psq = (LinearLayoutCompat.MarginLayoutParams) holder.binding.quotedMessage.cardviewContainer.getLayoutParams();
|
LinearLayoutCompat.MarginLayoutParams psq = (LinearLayoutCompat.MarginLayoutParams) holder.binding.quotedMessage.cardviewContainer.getLayoutParams();
|
||||||
psq.setMarginStart((int) Helper.convertDpToPixel(6, context));
|
psq.setMarginStart((int) Helper.convertDpToPixel(6, context));
|
||||||
holder.binding.quotedMessage.cardviewContainer.setLayoutParams(psq);
|
holder.binding.quotedMessage.cardviewContainer.setLayoutParams(psq);
|
||||||
|
@ -1510,6 +1515,37 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
||||||
recyclerView.post(() -> adapter.notifyItemChanged(holder.getBindingAdapterPosition()));
|
recyclerView.post(() -> adapter.notifyItemChanged(holder.getBindingAdapterPosition()));
|
||||||
}),
|
}),
|
||||||
TextView.BufferType.SPANNABLE);
|
TextView.BufferType.SPANNABLE);
|
||||||
|
boolean underlineBottomHashTags = sharedpreferences.getBoolean(context.getString(R.string.SET_UNDERLINE_BOTTOM_HASHTAGS), true);
|
||||||
|
if(underlineBottomHashTags) {
|
||||||
|
if (statusToDeal.getBottomTags().length > 0) {
|
||||||
|
holder.binding.statusHashtags.setVisibility(View.VISIBLE);
|
||||||
|
holder.binding.statusHashtags.removeAllViews();
|
||||||
|
for (String tag : statusToDeal.getBottomTags()) {
|
||||||
|
Chip chip = new Chip(context);
|
||||||
|
chip.setClickable(true);
|
||||||
|
chip.setEnsureMinTouchTargetSize(false);
|
||||||
|
chip.setText(tag);
|
||||||
|
chip.setTextColor(ThemeHelper.getAttColor(context, R.attr.colorPrimary));
|
||||||
|
chip.setOnClickListener(v -> {
|
||||||
|
Intent intentTag = new Intent(context, HashTagActivity.class);
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString(Helper.ARG_SEARCH_KEYWORD, tag.replace("#", ""));
|
||||||
|
new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
|
||||||
|
intentTag.putExtras(bundle);
|
||||||
|
intentTag.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
context.startActivity(intentTag);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
holder.binding.statusHashtags.addView(chip);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
holder.binding.statusHashtags.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
holder.binding.statusHashtags.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
if (truncate_toots_size > 0) {
|
if (truncate_toots_size > 0) {
|
||||||
holder.binding.statusContent.setMaxLines(truncate_toots_size);
|
holder.binding.statusContent.setMaxLines(truncate_toots_size);
|
||||||
holder.binding.statusContent.setEllipsize(TextUtils.TruncateAt.END);
|
holder.binding.statusContent.setEllipsize(TextUtils.TruncateAt.END);
|
||||||
|
|
|
@ -281,6 +281,18 @@
|
||||||
tools:maxLines="10"
|
tools:maxLines="10"
|
||||||
tools:text="@tools:sample/lorem/random" />
|
tools:text="@tools:sample/lorem/random" />
|
||||||
|
|
||||||
|
<com.google.android.material.chip.ChipGroup
|
||||||
|
android:id="@+id/status_hashtags"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
android:layout_marginStart="48dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
app:chipSpacingHorizontal="6dp"
|
||||||
|
app:chipSpacingVertical="6dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/description"/>
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
android:id="@+id/status_content_maths"
|
android:id="@+id/status_content_maths"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -1265,6 +1265,7 @@
|
||||||
<string name="SET_TIMELINE_SCROLLBAR" translatable="false">SET_TIMELINE_SCROLLBAR</string>
|
<string name="SET_TIMELINE_SCROLLBAR" translatable="false">SET_TIMELINE_SCROLLBAR</string>
|
||||||
<string name="SET_MARKDOWN_SUPPORT" translatable="false">SET_MARKDOWN_SUPPORT</string>
|
<string name="SET_MARKDOWN_SUPPORT" translatable="false">SET_MARKDOWN_SUPPORT</string>
|
||||||
<string name="SET_TRUNCATE_LINKS" translatable="false">SET_TRUNCATE_LINKS</string>
|
<string name="SET_TRUNCATE_LINKS" translatable="false">SET_TRUNCATE_LINKS</string>
|
||||||
|
<string name="SET_UNDERLINE_BOTTOM_HASHTAGS" translatable="false">SET_UNDERLINE_BOTTOM_HASHTAGS</string>
|
||||||
<string name="SET_UNDERLINE_CLICKABLE" translatable="false">SET_UNDERLINE_CLICKABLE</string>
|
<string name="SET_UNDERLINE_CLICKABLE" translatable="false">SET_UNDERLINE_CLICKABLE</string>
|
||||||
<string name="SET_PRONOUNS_SUPPORT" translatable="false">SET_PRONOUNS_SUPPORT</string>
|
<string name="SET_PRONOUNS_SUPPORT" translatable="false">SET_PRONOUNS_SUPPORT</string>
|
||||||
<string name="SET_TRUNCATE_LINKS_MAX" translatable="false">SET_TRUNCATE_LINKS_MAX</string>
|
<string name="SET_TRUNCATE_LINKS_MAX" translatable="false">SET_TRUNCATE_LINKS_MAX</string>
|
||||||
|
@ -2081,6 +2082,7 @@
|
||||||
<string name="toot_error_no_media_description">There are missing media descriptions</string>
|
<string name="toot_error_no_media_description">There are missing media descriptions</string>
|
||||||
|
|
||||||
<string name="truncate_links">Truncate links</string>
|
<string name="truncate_links">Truncate links</string>
|
||||||
|
<string name="underline_bottom_hashtags">Highlight bottom hashtags</string>
|
||||||
<string name="underline_links">Underline clickable elements</string>
|
<string name="underline_links">Underline clickable elements</string>
|
||||||
<string name="truncate_links_max">Max chars in links</string>
|
<string name="truncate_links_max">Max chars in links</string>
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,14 @@
|
||||||
app:singleLineTitle="false"
|
app:singleLineTitle="false"
|
||||||
app:title="@string/truncate_links" />
|
app:title="@string/truncate_links" />
|
||||||
|
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="true"
|
||||||
|
app:iconSpaceReserved="false"
|
||||||
|
app:key="@string/SET_UNDERLINE_BOTTOM_HASHTAGS"
|
||||||
|
app:singleLineTitle="false"
|
||||||
|
app:title="@string/underline_bottom_hashtags" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
app:iconSpaceReserved="false"
|
app:iconSpaceReserved="false"
|
||||||
|
|
Loading…
Reference in a new issue