diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/OverviewActivity.java b/app/src/main/java/org/openobservatory/ooniprobe/activity/OverviewActivity.java
index 9d1b79018..296232913 100644
--- a/app/src/main/java/org/openobservatory/ooniprobe/activity/OverviewActivity.java
+++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/OverviewActivity.java
@@ -12,6 +12,7 @@
import org.openobservatory.ooniprobe.R;
import org.openobservatory.ooniprobe.common.PreferenceManager;
+import org.openobservatory.ooniprobe.common.ReadMorePlugin;
import org.openobservatory.ooniprobe.databinding.ActivityOverviewBinding;
import org.openobservatory.ooniprobe.model.database.Result;
import org.openobservatory.ooniprobe.test.suite.AbstractSuite;
@@ -50,11 +51,11 @@ public static Intent newIntent(Context context, AbstractSuite testSuite) {
setTitle(testSuite.getTitle());
binding.icon.setImageResource(testSuite.getIcon());
binding.customUrl.setVisibility(testSuite.getName().equals(WebsitesSuite.NAME) ? View.VISIBLE : View.GONE);
- if(testSuite.isTestEmpty(preferenceManager)){
- binding.run.setAlpha(0.5F);
- binding.run.setEnabled(false);
- }
- Markwon markwon = Markwon.builder(this).build();
+ Markwon markwon = Markwon.builder(this)
+ .usePlugin(new ReadMorePlugin(
+ getString(R.string.OONIRun_ReadMore),
+ getString(R.string.OONIRun_ReadLess))
+ ).build();
if (testSuite.getName().equals(ExperimentalSuite.NAME)) {
String experimentalLinks =
"\n\n* [STUN Reachability](https://github.com/ooni/spec/blob/master/nettests/ts-025-stun-reachability.md)" +
@@ -63,11 +64,13 @@ public static Intent newIntent(Context context, AbstractSuite testSuite) {
"\n\n* [Tor Snowflake](https://ooni.org/nettest/tor-snowflake/) "+ String.format(" ( %s )",getString(R.string.Settings_TestOptions_LongRunningTest))+
"\n\n* [Vanilla Tor](https://github.com/ooni/spec/blob/master/nettests/ts-016-vanilla-tor.md) " + String.format(" ( %s )",getString(R.string.Settings_TestOptions_LongRunningTest));
markwon.setMarkdown(binding.desc, getString(testSuite.getDesc1(), experimentalLinks));
- if (TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL)
- binding.desc.setTextDirection(View.TEXT_DIRECTION_RTL);
+ if (TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL) {
+ binding.desc.setTextDirection(View.TEXT_DIRECTION_RTL);
+ }
}
- else
- markwon.setMarkdown(binding.desc, getString(testSuite.getDesc1()));
+ else {
+ markwon.setMarkdown(binding.desc, getString(testSuite.getDesc1()));
+ }
Result lastResult = Result.getLastResult(testSuite.getName());
if (lastResult == null)
binding.lastTime.setText(R.string.Dashboard_Overview_LastRun_Never);
@@ -78,7 +81,6 @@ public static Intent newIntent(Context context, AbstractSuite testSuite) {
}
private void setUpOnCLickListeners() {
- binding.run.setOnClickListener(view -> onRunClick());
binding.customUrl.setOnClickListener(view -> customUrlClick());
}
@@ -95,12 +97,6 @@ public boolean onSupportNavigateUp() {
return true;
}
- void onRunClick() {
- if(!testSuite.isTestEmpty(preferenceManager)){
- RunningActivity.runAsForegroundService(this, testSuite.asArray(), this::bindTestService, preferenceManager);
- }
- }
-
void customUrlClick() {
startActivity(new Intent(this, CustomWebsiteActivity.class));
}
diff --git a/app/src/main/java/org/openobservatory/ooniprobe/common/ReadMorePlugin.kt b/app/src/main/java/org/openobservatory/ooniprobe/common/ReadMorePlugin.kt
new file mode 100644
index 000000000..e71dfefb2
--- /dev/null
+++ b/app/src/main/java/org/openobservatory/ooniprobe/common/ReadMorePlugin.kt
@@ -0,0 +1,126 @@
+package org.openobservatory.ooniprobe.common
+
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+import android.text.style.ClickableSpan
+import android.text.style.ReplacementSpan
+import android.view.View
+import android.widget.TextView
+import io.noties.markwon.AbstractMarkwonPlugin
+
+
+/**
+ * Read more plugin based on text length.
+ * @see ReadMorePluginSample
+ */
+class ReadMorePlugin(private val labelMore: String, private val labelLess: String) : AbstractMarkwonPlugin() {
+ private val maxLength = 150
+
+ override fun afterSetText(textView: TextView) {
+ val text = textView.text
+ if (text.length < maxLength) {
+ // everything is OK, no need to ellipsize)
+ return
+ }
+ val breakAt = breakTextAt(text, 0, maxLength)
+ val cs = createCollapsedString(text, 0, breakAt)
+ textView.text = cs
+ }
+
+ private fun createCollapsedString(text: CharSequence, start: Int, end: Int): CharSequence {
+ val builder = SpannableStringBuilder(text, start, end)
+ val spans = builder.getSpans(
+ 0, builder.length,
+ ReplacementSpan::class.java
+ )
+ if (spans != null) {
+ for (span in spans) {
+ builder.removeSpan(span)
+ }
+ }
+
+
+ trim(builder)
+ val fullText = createFullText(text, builder)
+ builder.append(" ...")
+ val length = builder.length
+ builder.append("\n\n")
+ builder.append(labelMore)
+ builder.setSpan(object : ClickableSpan() {
+ override fun onClick(widget: View) {
+ (widget as TextView).text = fullText
+ }
+ }, length, builder.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+ return builder
+ }
+
+ private fun createFullText(text: CharSequence, collapsedText: CharSequence): CharSequence {
+ val fullText: CharSequence
+ val builder = SpannableStringBuilder(text)
+ builder.append(' ')
+ val length = builder.length
+ builder.append("\n\n")
+ builder.append(labelLess)
+ builder.setSpan(object : ClickableSpan() {
+ override fun onClick(widget: View) {
+ (widget as TextView).text = collapsedText
+ }
+ }, length, builder.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+ fullText = builder
+
+ return fullText
+ }
+
+ companion object {
+ private fun trim(builder: SpannableStringBuilder) {
+
+ // NB! tables use `\u00a0` (non breaking space) which is not reported as white-space
+ var c: Char
+ run {
+ var i = 0
+ val length = builder.length
+ while (i < length) {
+ c = builder[i]
+ if (!Character.isWhitespace(c) && c != '\u00a0') {
+ if (i > 0) {
+ builder.replace(0, i, "")
+ }
+ break
+ }
+ i++
+ }
+ }
+ for (i in builder.length - 1 downTo 0) {
+ c = builder[i]
+ if (!Character.isWhitespace(c) && c != '\u00a0') {
+ if (i < builder.length - 1) {
+ builder.replace(i, builder.length, "")
+ }
+ break
+ }
+ }
+ }
+
+ // depending on your locale these can be different
+ // There is a BreakIterator in Android, but it is not reliable, still theoretically
+ // it should work better than hand-written and hardcoded rules
+ private fun breakTextAt(text: CharSequence, start: Int, max: Int): Int {
+ var last = start
+
+ // no need to check for _start_ (anyway will be ignored)
+ for (i in start + max - 1 downTo start + 1) {
+ val c = text[i]
+ if (Character.isWhitespace(c) || c == '.' || c == ',' || c == '!' || c == '?') {
+ // include this special character
+ last = i - 1
+ break
+ }
+ }
+ return if (last <= start) {
+ // when used in subSequence last index is exclusive,
+ // so given max=150 would result in 0-149 subSequence
+ start + max
+ } else last
+ }
+ }
+}
diff --git a/app/src/main/res/layout/activity_overview.xml b/app/src/main/res/layout/activity_overview.xml
index e4c1b1665..ea5263718 100644
--- a/app/src/main/res/layout/activity_overview.xml
+++ b/app/src/main/res/layout/activity_overview.xml
@@ -1,127 +1,125 @@
-
+ tools:context=".activity.OverviewActivity">
+ android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
-
-
-
-
-
-
-
-
-
-
-
-
+ app:contentScrim="?attr/colorPrimary"
+ app:titleEnabled="false"
+ app:layout_scrollFlags="scroll|snap|exitUntilCollapsed">
-
-
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ app:cornerRadius="24dp"
+ app:rippleColor="@color/ripple_material_dark"
+ app:strokeColor="@android:color/white"/>
-
-
+
-
+
-
-
-
-
-
-
-
-
+
+ android:layout_height="wrap_content"
+ android:padding="32dp" />
+
-
-
\ No newline at end of file
+
+
diff --git a/app/src/main/res/layout/activity_result_detail.xml b/app/src/main/res/layout/activity_result_detail.xml
index c09cf62ad..772987d7d 100644
--- a/app/src/main/res/layout/activity_result_detail.xml
+++ b/app/src/main/res/layout/activity_result_detail.xml
@@ -14,7 +14,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:contentScrim="?attr/colorPrimary"
- app:titleEnabled="false">
+ app:titleEnabled="false"
+ app:layout_scrollFlags="scroll|snap|exitUntilCollapsed">
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index db87e2f35..d350c9f65 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -468,6 +468,8 @@
The OONI Run link is either malformed or your app is out of date.
You will test a random sample of websites.
Please wait for the test to finish running before tapping on an OONI Run link.
+ Read more >
+ Read less >
Drugs & Alcohol
Religion
Pornography
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 3a0290794..333ba2e02 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,6 @@
+#Wed Oct 25 19:25:06 WAT 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists