Add support for maths.

This commit is contained in:
Thomas 2023-01-16 16:33:20 +01:00
parent 25ad71080e
commit d5c51e6dca
8 changed files with 112 additions and 37 deletions

View file

@ -327,7 +327,8 @@ public class Helper {
public static final Pattern groupPattern = Pattern.compile("(![\\w_]+)"); public static final Pattern groupPattern = Pattern.compile("(![\\w_]+)");
public static final Pattern mentionPattern = Pattern.compile("(@[\\w_.-]?[\\w]+)"); public static final Pattern mentionPattern = Pattern.compile("(@[\\w_.-]?[\\w]+)");
public static final Pattern mentionLongPattern = Pattern.compile("(@[\\w_.-]+@[a-zA-Z0-9][a-zA-Z0-9.-]{1,61}[a-zA-Z0-9](?:\\.[a-zA-Z]{2,})+)"); public static final Pattern mentionLongPattern = Pattern.compile("(@[\\w_.-]+@[a-zA-Z0-9][a-zA-Z0-9.-]{1,61}[a-zA-Z0-9](?:\\.[a-zA-Z]{2,})+)");
public static final Pattern mathsPattern = Pattern.compile("\\\\\\(|\\\\\\{|\\\\\\["); public static final Pattern mathsPattern = Pattern.compile("\\\\\\(|\\\\\\[");
public static final Pattern mathsComposePattern = Pattern.compile("\\\\\\(.*\\\\\\)|\\\\\\[.*\\\\\\]");
public static final Pattern twitterPattern = Pattern.compile("((@[\\w]+)@twitter\\.com)"); public static final Pattern twitterPattern = Pattern.compile("((@[\\w]+)@twitter\\.com)");
public static final Pattern youtubePattern = Pattern.compile("(www\\.|m\\.)?(youtube\\.com|youtu\\.be|youtube-nocookie\\.com)/(((?!([\"'<])).)*)"); public static final Pattern youtubePattern = Pattern.compile("(www\\.|m\\.)?(youtube\\.com|youtu\\.be|youtube-nocookie\\.com)/(((?!([\"'<])).)*)");
public static final Pattern nitterPattern = Pattern.compile("(mobile\\.|www\\.)?twitter.com([\\w-/]+)"); public static final Pattern nitterPattern = Pattern.compile("(mobile\\.|www\\.)?twitter.com([\\w-/]+)");

View file

@ -28,6 +28,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Color; import android.graphics.Color;
@ -531,7 +532,9 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
if (promptDraftListener != null) { if (promptDraftListener != null) {
promptDraftListener.promptDraft(); promptDraftListener.promptDraft();
} }
if (holder.binding.content.getSelectionStart() > 0) {
statusList.get(holder.getLayoutPosition()).cursorPosition = holder.binding.content.getSelectionStart(); statusList.get(holder.getLayoutPosition()).cursorPosition = holder.binding.content.getSelectionStart();
}
//Copy/past //Copy/past
int max_car = MastodonHelper.getInstanceMaxChars(context); int max_car = MastodonHelper.getInstanceMaxChars(context);
if (currentLength > max_car) { if (currentLength > max_car) {
@ -694,6 +697,24 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
updateCharacterCount(holder); updateCharacterCount(holder);
return; return;
} }
Matcher mathsPatterns = Helper.mathsComposePattern.matcher((s.toString()));
if (mathsPatterns.find()) {
if (holder.binding.laTexViewContainer.getVisibility() != View.VISIBLE) {
holder.binding.laTexViewContainer.setVisibility(View.VISIBLE);
switch (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) {
case Configuration.UI_MODE_NIGHT_YES:
holder.binding.laTexView.setTextColor("white");
break;
case Configuration.UI_MODE_NIGHT_NO:
holder.binding.laTexView.setTextColor("black");
break;
}
}
holder.binding.laTexView.setInputText(s.toString());
} else {
holder.binding.laTexViewContainer.setVisibility(View.GONE);
}
String patternh = "^(.|\\s)*(:fedilab_hugs:)"; String patternh = "^(.|\\s)*(:fedilab_hugs:)";
final Pattern hPattern = Pattern.compile(patternh); final Pattern hPattern = Pattern.compile(patternh);
@ -1272,7 +1293,34 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
ComposeViewHolder holder = (ComposeViewHolder) viewHolder; ComposeViewHolder holder = (ComposeViewHolder) viewHolder;
boolean extraFeatures = sharedpreferences.getBoolean(context.getString(R.string.SET_EXTAND_EXTRA_FEATURES) + MainActivity.currentUserID + MainActivity.currentInstance, false); boolean extraFeatures = sharedpreferences.getBoolean(context.getString(R.string.SET_EXTAND_EXTRA_FEATURES) + MainActivity.currentUserID + MainActivity.currentInstance, false);
boolean mathsComposer = sharedpreferences.getBoolean(context.getString(R.string.SET_MATHS_COMPOSER), true);
if (mathsComposer) {
holder.binding.buttonMathsComposer.setVisibility(View.VISIBLE);
holder.binding.buttonMathsComposer.setOnClickListener(v -> {
int cursorPosition = holder.binding.content.getSelectionStart();
AlertDialog.Builder builder = new AlertDialog.Builder(context, Helper.dialogStyle());
Resources res = context.getResources();
String[] formatArr = res.getStringArray(R.array.SET_MATHS_FORMAT);
builder.setItems(formatArr, (dialogInterface, i) -> {
if (statusDraft.text == null) {
statusDraft.text = "";
}
if (i == 0) {
statusDraft.text = new StringBuilder(statusDraft.text).insert(cursorPosition, "\\( \\)").toString();
} else {
statusDraft.text = new StringBuilder(statusDraft.text).insert(cursorPosition, "\\[ \\]").toString();
}
statusDraft.cursorPosition = cursorPosition + 3;
notifyItemChanged(position);
dialogInterface.dismiss();
});
builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
builder.create().show();
});
} else {
holder.binding.buttonMathsComposer.setVisibility(View.GONE);
}
holder.binding.buttonEmojiOne.setVisibility(View.VISIBLE); holder.binding.buttonEmojiOne.setVisibility(View.VISIBLE);
if (extraFeatures) { if (extraFeatures) {

View file

@ -674,9 +674,10 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
mathJaxConfig.setTextColor("white"); mathJaxConfig.setTextColor("white");
break; break;
case Configuration.UI_MODE_NIGHT_NO: case Configuration.UI_MODE_NIGHT_NO:
mathJaxConfig.setTextColor("dark"); mathJaxConfig.setTextColor("black");
break; break;
} }
mathJaxConfig.setAutomaticLinebreaks(true);
status.mathsShown = true; status.mathsShown = true;
MathJaxView mathview = new MathJaxView(context, mathJaxConfig); MathJaxView mathview = new MathJaxView(context, mathJaxConfig);
holder.binding.statusContentMaths.addView(mathview); holder.binding.statusContentMaths.addView(mathview);

View file

@ -32,6 +32,27 @@
android:clipToPadding="false" android:clipToPadding="false"
android:orientation="vertical"> android:orientation="vertical">
<androidx.core.widget.NestedScrollView
android:id="@+id/laTexView_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/button_emoji"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<de.timfreiheit.mathjax.android.MathJaxView
android:id="@+id/laTexView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:automaticLinebreaks="true"
app:input="TeX"
app:output="SVG" />
</androidx.core.widget.NestedScrollView>
<com.google.android.material.textfield.MaterialAutoCompleteTextView <com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/content_spoiler" android:id="@+id/content_spoiler"
android:layout_width="0dp" android:layout_width="0dp"
@ -44,7 +65,7 @@
android:visibility="gone" android:visibility="gone"
app:layout_constraintEnd_toStartOf="@id/button_emoji" app:layout_constraintEnd_toStartOf="@id/button_emoji"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toBottomOf="@+id/laTexView_container"
tools:visibility="visible" /> tools:visibility="visible" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
@ -55,7 +76,7 @@
android:contentDescription="@string/add_status" android:contentDescription="@string/add_status"
app:icon="@drawable/ic_compose_thread_add_status" app:icon="@drawable/ic_compose_thread_add_status"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toBottomOf="@+id/laTexView_container" />
<app.fedilab.android.helper.FedilabAutoCompleteTextView <app.fedilab.android.helper.FedilabAutoCompleteTextView
android:id="@+id/content" android:id="@+id/content"
@ -103,6 +124,17 @@
app:layout_constraintTop_toBottomOf="@id/button_emoji_one" app:layout_constraintTop_toBottomOf="@id/button_emoji_one"
tools:visibility="visible" /> tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_maths_composer"
style="@style/Fedilab.SmallIconButton"
android:layout_marginEnd="6dp"
android:contentDescription="@string/maths_format"
android:visibility="gone"
app:icon="@drawable/ic_baseline_functions_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_text_format"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_local_only" android:id="@+id/button_local_only"
style="@style/Fedilab.SmallIconButton" style="@style/Fedilab.SmallIconButton"
@ -111,7 +143,7 @@
android:visibility="gone" android:visibility="gone"
app:icon="@drawable/ic_baseline_remove_red_eye_24" app:icon="@drawable/ic_baseline_remove_red_eye_24"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_text_format" app:layout_constraintTop_toBottomOf="@id/button_maths_composer"
tools:visibility="visible" /> tools:visibility="visible" />
<com.google.android.material.checkbox.MaterialCheckBox <com.google.android.material.checkbox.MaterialCheckBox

View file

@ -763,6 +763,12 @@
<item>Mastalab</item> <item>Mastalab</item>
</string-array> </string-array>
<string-array name="SET_MATHS_FORMAT">
<item>Inline\nNotation that sits inline with other text\n</item>
<item>Display-mode\nNotation that sits on its own line\n</item>
</string-array>
<string-array name="SET_POST_FORMAT" translatable="false"> <string-array name="SET_POST_FORMAT" translatable="false">
<item>text/plain</item> <item>text/plain</item>
<item>text/html</item> <item>text/html</item>
@ -1483,6 +1489,7 @@
<string name="SET_UNLISTED_REPLIES" translatable="false">SET_UNLISTED_REPLIES</string> <string name="SET_UNLISTED_REPLIES" translatable="false">SET_UNLISTED_REPLIES</string>
<string name="SET_SELECTED_LANGUAGE" translatable="false">SET_SELECTED_LANGUAGE</string> <string name="SET_SELECTED_LANGUAGE" translatable="false">SET_SELECTED_LANGUAGE</string>
<string name="SET_WATERMARK_TEXT" translatable="false">SET_WATERMARK_TEXT</string> <string name="SET_WATERMARK_TEXT" translatable="false">SET_WATERMARK_TEXT</string>
<string name="SET_MATHS_COMPOSER" translatable="false">SET_MATHS_COMPOSER</string>
<string name="SET_PROXY_PASSWORD" translatable="false">SET_PROXY_PASSWORD</string> <string name="SET_PROXY_PASSWORD" translatable="false">SET_PROXY_PASSWORD</string>
<string name="SET_PROXY_LOGIN" translatable="false">SET_PROXY_LOGIN</string> <string name="SET_PROXY_LOGIN" translatable="false">SET_PROXY_LOGIN</string>
<string name="SET_ACCOUNTS_PER_CALL" translatable="false">SET_ACCOUNTS_PER_CALL</string> <string name="SET_ACCOUNTS_PER_CALL" translatable="false">SET_ACCOUNTS_PER_CALL</string>
@ -2214,4 +2221,6 @@
<string name="set_disable_release_notes">Disable release notes</string> <string name="set_disable_release_notes">Disable release notes</string>
<string name="set_disable_release_notes_indication">When a new version is published, you will not be alerted inside the app.</string> <string name="set_disable_release_notes_indication">When a new version is published, you will not be alerted inside the app.</string>
<string name="formula">Formula</string> <string name="formula">Formula</string>
<string name="set_maths_support">Write formula</string>
<string name="maths_format">Maths format</string>
</resources> </resources>

View file

@ -60,6 +60,12 @@
app:key="@string/SET_WATERMARK_TEXT" app:key="@string/SET_WATERMARK_TEXT"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<SwitchPreferenceCompat
app:defaultValue="true"
app:iconSpaceReserved="false"
app:key="@string/SET_MATHS_COMPOSER"
app:singleLineTitle="false"
app:title="@string/set_maths_support" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:defaultValue="false" app:defaultValue="false"

View file

@ -28,6 +28,7 @@ public class MathJaxConfig {
} }
} }
public MathJaxConfig(TypedArray attrs) { public MathJaxConfig(TypedArray attrs) {
this(); this();
int inputIndex = attrs.getInteger(R.styleable.MathJaxView_input, -1); int inputIndex = attrs.getInteger(R.styleable.MathJaxView_input, -1);

View file

@ -9,17 +9,13 @@ import android.os.Handler;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.Gravity; import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.webkit.WebSettings; import android.webkit.WebSettings;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import java.util.concurrent.atomic.AtomicReference;
/** /**
* Containerview for an WebView which renders LaTex using MathJax * Containerview for an WebView which renders LaTex using MathJax
@ -88,20 +84,26 @@ public class MathJaxView extends FrameLayout {
this.onMathJaxRenderListener = onMathJaxRenderListener; this.onMathJaxRenderListener = onMathJaxRenderListener;
} }
public void setTextColor(String color) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mWebView.evaluateJavascript("document.body.style.color=\"" + color + "\";", null);
}
}
@SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"}) @SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"})
private void init(Context context, AttributeSet attrSet, MathJaxConfig config) { private void init(Context context, AttributeSet attrSet, MathJaxConfig config) {
mWebView = new WebView(context); mWebView = new WebView(context);
int gravity = Gravity.START; int gravity = Gravity.START;
boolean verticalScrollbarsEnabled = false; boolean verticalScrollbarsEnabled = false;
boolean horizontalScrollbarsEnabled = true; boolean horizontalScrollbarsEnabled = false;
String textColor = config != null ? config.getTextColor() : null; String textColor = config != null ? config.getTextColor() : null;
if (attrSet != null) { if (attrSet != null) {
TypedArray attrs = context.obtainStyledAttributes(attrSet, R.styleable.MathJaxView); TypedArray attrs = context.obtainStyledAttributes(attrSet, R.styleable.MathJaxView);
gravity = attrs.getInteger(R.styleable.MathJaxView_android_gravity, Gravity.START); gravity = attrs.getInteger(R.styleable.MathJaxView_android_gravity, Gravity.START);
verticalScrollbarsEnabled = attrs.getBoolean(R.styleable.MathJaxView_verticalScrollbarsEnabled, false); verticalScrollbarsEnabled = attrs.getBoolean(R.styleable.MathJaxView_verticalScrollbarsEnabled, false);
horizontalScrollbarsEnabled = attrs.getBoolean(R.styleable.MathJaxView_horizontalScrollbarsEnabled, true); horizontalScrollbarsEnabled = attrs.getBoolean(R.styleable.MathJaxView_horizontalScrollbarsEnabled, false);
textColor = attrs.getString(R.styleable.MathJaxView_textColor); textColor = attrs.getString(R.styleable.MathJaxView_textColor);
config = new MathJaxConfig(attrs); config = new MathJaxConfig(attrs);
attrs.recycle(); attrs.recycle();
@ -153,31 +155,6 @@ public class MathJaxView extends FrameLayout {
mWebView.setHorizontalScrollBarEnabled(horizontalScrollbarsEnabled); mWebView.setHorizontalScrollBarEnabled(horizontalScrollbarsEnabled);
mWebView.setBackgroundColor(0); mWebView.setBackgroundColor(0);
mWebView.getSettings().setLoadWithOverviewMode(true); mWebView.getSettings().setLoadWithOverviewMode(true);
float touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
final boolean[] scrollFlag = {true};
AtomicReference<Float> downX = new AtomicReference<>((float) 0);
AtomicReference<Float> downY = new AtomicReference<>((float) 0);
mWebView.setOnTouchListener((View v, MotionEvent event) -> {
if (!scrollFlag[0] && event.getY() < getHeight() / 2) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX.set(event.getX());
downY.set(event.getY());
break;
case MotionEvent.ACTION_MOVE:
if (Math.abs(event.getY() - downY.get()) < touchSlop && Math.abs(event.getX() - downX.get()) > touchSlop) {
getParent().requestDisallowInterceptTouchEvent(true);
scrollFlag[0] = true;
}
break;
}
}
if (event.getAction() == MotionEvent.ACTION_UP)
scrollFlag[0] = false;
return false;
});
} }
/** /**