diff --git a/.github/workflows/validate-gradle-wrapper.yml b/.github/workflows/validate-gradle-wrapper.yml
new file mode 100644
index 000000000..b5f0b31ac
--- /dev/null
+++ b/.github/workflows/validate-gradle-wrapper.yml
@@ -0,0 +1,11 @@
+name: Validate Gradle Wrapper
+
+on: [pull_request, push]
+
+jobs:
+ validation:
+ name: Validation
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: gradle/wrapper-validation-action@v1
diff --git a/README.md b/README.md
index 902a0bea6..417609784 100644
--- a/README.md
+++ b/README.md
@@ -55,7 +55,7 @@ On the Fediverse, it’s quite common for people to pin posts they want others t
[apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk](https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk)
-
+
Note that you'll need to add Izzy's F-Droid repository to your F-Droid app first:
diff --git a/_layouts/default.html b/_layouts/default.html
index cd80d4a87..490f89903 100644
--- a/_layouts/default.html
+++ b/_layouts/default.html
@@ -6,7 +6,7 @@
Megalodon
-
+
diff --git a/build.gradle b/build.gradle
index d426d29b9..8357ab502 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.3.1'
+ classpath 'com.android.tools.build:gradle:8.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
diff --git a/crowdin.yml b/crowdin.yml
deleted file mode 100644
index 99e5b7d99..000000000
--- a/crowdin.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-files:
- - source: /mastodon/src/main/res/values/strings.xml
- translation: /mastodon/src/main/res/values-%android_code%/strings.xml
- - source: /fastlane/metadata/android/en-US/*.txt
- translation: /fastlane/metadata/android/%locale%/%original_file_name%
diff --git a/gradle.properties b/gradle.properties
index 52f5917cb..415e17f9f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -16,4 +16,7 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
-android.enableJetifier=true
\ No newline at end of file
+android.enableJetifier=false
+android.defaults.buildfeatures.buildconfig=true
+android.nonTransitiveRClass=true
+android.nonFinalResIds=false
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index e708b1c02..c1962a79e 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 112a498f8..2c3425d49 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,7 @@
-#Thu Jan 13 11:33:43 MSK 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
-zipStorePath=wrapper/dists
+distributionSha256Sum=e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
+networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 4f906e0c8..aeb74cbb4 100755
--- a/gradlew
+++ b/gradlew
@@ -1,7 +1,7 @@
-#!/usr/bin/env sh
+#!/bin/sh
#
-# Copyright 2015 the original author or authors.
+# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,67 +17,98 @@
#
##############################################################################
-##
-## Gradle start up script for UN*X
-##
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
##############################################################################
# Attempt to set APP_HOME
+
# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
+MAX_FD=maximum
warn () {
echo "$*"
-}
+} >&2
die () {
echo
echo "$*"
echo
exit 1
-}
+} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -87,9 +118,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACMD=$JAVA_HOME/jre/sh/java
else
- JAVACMD="$JAVA_HOME/bin/java"
+ JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -98,7 +129,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
- JAVACMD="java"
+ JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@@ -106,80 +137,109 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
- fi
- i=`expr $i + 1`
- done
- case $i in
- 0) set -- ;;
- 1) set -- "$args0" ;;
- 2) set -- "$args0" "$args1" ;;
- 3) set -- "$args0" "$args1" "$args2" ;;
- 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=`save "$@"`
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index ac1b06f93..6689b85be 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
-@if "%DEBUG%" == "" @echo off
+@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
+if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
+if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
diff --git a/mastodon/build.gradle b/mastodon/build.gradle
index 4e2700f2a..d43bc070a 100644
--- a/mastodon/build.gradle
+++ b/mastodon/build.gradle
@@ -2,6 +2,12 @@ plugins {
id 'com.android.application'
}
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(17)
+ }
+}
+
android {
compileSdk 33
defaultConfig {
@@ -9,11 +15,11 @@ android {
applicationId "org.joinmastodon.android.sk"
minSdk 23
targetSdk 33
- versionCode 73
- versionName "1.1.5+fork.73"
+ versionCode 91
+ versionName "1.2.3+fork.91"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "nl-rNL", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
- }
+ resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
+ }
buildTypes {
release {
@@ -49,14 +55,19 @@ android {
setRoot "src/github"
}
}
- lintOptions{
- checkReleaseBuilds false
+ namespace 'org.joinmastodon.android'
+ lint {
abortOnError false
+ checkReleaseBuilds false
+ }
+
+ buildFeatures {
+ buildConfig true
}
}
dependencies {
- api 'androidx.annotation:annotation:1.3.0'
+ api 'androidx.annotation:annotation:1.6.0'
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
implementation 'me.grishka.litex:recyclerview:1.2.1.1'
implementation 'me.grishka.litex:swiperefreshlayout:1.1.0.1'
@@ -64,12 +75,13 @@ dependencies {
implementation 'me.grishka.litex:dynamicanimation:1.1.0-alpha03'
implementation 'me.grishka.litex:viewpager:1.0.0'
implementation 'me.grishka.litex:viewpager2:1.0.0'
- implementation 'me.grishka.appkit:appkit:1.2.7'
- implementation 'com.google.code.gson:gson:2.8.9'
+ implementation 'me.grishka.appkit:appkit:1.2.8'
+ implementation 'com.google.code.gson:gson:2.9.0'
implementation 'org.jsoup:jsoup:1.14.3'
implementation 'com.squareup:otto:1.3.8'
implementation 'de.psdev:async-otto:1.0.3'
implementation 'org.parceler:parceler-api:1.1.12'
+ implementation 'com.github.bottom-software-foundation:bottom-java:2.1.0'
annotationProcessor 'org.parceler:parceler:1.1.12'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
diff --git a/mastodon/src/androidTest/java/org/joinmastodon/android/fragments/ThreadFragmentTest.java b/mastodon/src/androidTest/java/org/joinmastodon/android/fragments/ThreadFragmentTest.java
new file mode 100644
index 000000000..38a2a489c
--- /dev/null
+++ b/mastodon/src/androidTest/java/org/joinmastodon/android/fragments/ThreadFragmentTest.java
@@ -0,0 +1,113 @@
+package org.joinmastodon.android.fragments;
+
+import static org.junit.Assert.*;
+
+import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
+import org.joinmastodon.android.events.StatusUpdatedEvent;
+import org.joinmastodon.android.model.Status;
+import org.joinmastodon.android.model.StatusContext;
+import org.junit.Test;
+
+import java.time.Instant;
+import java.util.List;
+
+public class ThreadFragmentTest {
+
+ private Status fakeStatus(String id, String inReplyTo) {
+ Status status = Status.ofFake(id, null, null);
+ status.inReplyToId = inReplyTo;
+ return status;
+ }
+
+ private ThreadFragment.NeighborAncestryInfo fakeInfo(Status s, Status d, Status a) {
+ return new ThreadFragment.NeighborAncestryInfo(s, d, a);
+ }
+
+ @Test
+ public void mapNeighborhoodAncestry() {
+ StatusContext context = new StatusContext();
+ context.ancestors = List.of(
+ fakeStatus("oldest ancestor", null),
+ fakeStatus("younger ancestor", "oldest ancestor")
+ );
+ Status mainStatus = fakeStatus("main status", "younger ancestor");
+ context.descendants = List.of(
+ fakeStatus("first reply", "main status"),
+ fakeStatus("reply to first reply", "first reply"),
+ fakeStatus("third level reply", "reply to first reply"),
+ fakeStatus("another reply", "main status")
+ );
+
+ List neighbors =
+ ThreadFragment.mapNeighborhoodAncestry(mainStatus, context);
+
+ assertEquals(List.of(
+ fakeInfo(context.ancestors.get(0), context.ancestors.get(1), null),
+ fakeInfo(context.ancestors.get(1), mainStatus, context.ancestors.get(0)),
+ fakeInfo(mainStatus, context.descendants.get(0), context.ancestors.get(1)),
+ fakeInfo(context.descendants.get(0), context.descendants.get(1), mainStatus),
+ fakeInfo(context.descendants.get(1), context.descendants.get(2), context.descendants.get(0)),
+ fakeInfo(context.descendants.get(2), null, context.descendants.get(1)),
+ fakeInfo(context.descendants.get(3), null, null)
+ ), neighbors);
+ }
+
+ @Test
+ public void maybeApplyMainStatus() {
+ ThreadFragment fragment = new ThreadFragment();
+ fragment.contextInitiallyRendered = true;
+ fragment.mainStatus = Status.ofFake("123456", "original text", Instant.EPOCH);
+
+ Status update1 = Status.ofFake("123456", "updated text", Instant.EPOCH);
+ update1.editedAt = Instant.ofEpochSecond(1);
+ fragment.updatedStatus = update1;
+ StatusUpdatedEvent event1 = (StatusUpdatedEvent) fragment.maybeApplyMainStatus();
+ assertEquals("fired update event", update1, event1.status);
+ assertEquals("updated main status", update1, fragment.mainStatus);
+
+ Status update2 = Status.ofFake("123456", "updated text", Instant.EPOCH);
+ update2.favouritesCount = 123;
+ fragment.updatedStatus = update2;
+ StatusCountersUpdatedEvent event2 = (StatusCountersUpdatedEvent) fragment.maybeApplyMainStatus();
+ assertEquals("only fired counter update event", update2.id, event2.id);
+ assertEquals("updated counter is correct", 123, event2.favorites);
+ assertEquals("updated main status", update2, fragment.mainStatus);
+
+ Status update3 = Status.ofFake("123456", "whatever", Instant.EPOCH);
+ fragment.contextInitiallyRendered = false;
+ fragment.updatedStatus = update3;
+ assertNull("no update when context hasn't been rendered", fragment.maybeApplyMainStatus());
+ }
+
+ @Test
+ public void sortStatusContext() {
+ StatusContext context = new StatusContext();
+ context.ancestors = List.of(
+ fakeStatus("younger ancestor", "oldest ancestor"),
+ fakeStatus("oldest ancestor", null)
+ );
+ context.descendants = List.of(
+ fakeStatus("reply to first reply", "first reply"),
+ fakeStatus("third level reply", "reply to first reply"),
+ fakeStatus("first reply", "main status"),
+ fakeStatus("another reply", "main status")
+ );
+
+ ThreadFragment.sortStatusContext(
+ fakeStatus("main status", "younger ancestor"),
+ context
+ );
+ List expectedAncestors = List.of(
+ fakeStatus("oldest ancestor", null),
+ fakeStatus("younger ancestor", "oldest ancestor")
+ );
+ List expectedDescendants = List.of(
+ fakeStatus("first reply", "main status"),
+ fakeStatus("reply to first reply", "first reply"),
+ fakeStatus("third level reply", "reply to first reply"),
+ fakeStatus("another reply", "main status")
+ );
+
+ // TODO: ??? i have no idea how this code works. it certainly doesn't return what i'd expect
+ }
+}
\ No newline at end of file
diff --git a/mastodon/src/androidTest/java/org/joinmastodon/android/ui/utils/UiUtilsTest.java b/mastodon/src/androidTest/java/org/joinmastodon/android/ui/utils/UiUtilsTest.java
new file mode 100644
index 000000000..63c3fb866
--- /dev/null
+++ b/mastodon/src/androidTest/java/org/joinmastodon/android/ui/utils/UiUtilsTest.java
@@ -0,0 +1,106 @@
+package org.joinmastodon.android.ui.utils;
+
+import static org.junit.Assert.*;
+
+import android.util.Pair;
+
+import org.joinmastodon.android.api.session.AccountSessionManager;
+import org.joinmastodon.android.model.Account;
+import org.joinmastodon.android.model.Instance;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.Optional;
+
+public class UiUtilsTest {
+ @BeforeClass
+ public static void createDummySession() {
+ Instance dummyInstance = new Instance();
+ dummyInstance.uri = "test.tld";
+ Account dummyAccount = new Account();
+ dummyAccount.id = "123456";
+ AccountSessionManager.getInstance().addAccount(dummyInstance, null, dummyAccount, null, null);
+ }
+
+ @AfterClass
+ public static void cleanUp() {
+ AccountSessionManager.getInstance().removeAccount("test.tld_123456");
+ }
+
+ @Test
+ public void parseFediverseHandle() {
+ assertEquals(
+ Optional.of(Pair.create("megalodon", Optional.of("floss.social"))),
+ UiUtils.parseFediverseHandle("megalodon@floss.social")
+ );
+
+ assertEquals(
+ Optional.of(Pair.create("megalodon", Optional.of("floss.social"))),
+ UiUtils.parseFediverseHandle("@megalodon@floss.social")
+ );
+
+ assertEquals(
+ Optional.of(Pair.create("megalodon", Optional.empty())),
+ UiUtils.parseFediverseHandle("@megalodon")
+ );
+
+ assertEquals(
+ Optional.of(Pair.create("megalodon", Optional.of("floss.social"))),
+ UiUtils.parseFediverseHandle("mailto:megalodon@floss.social")
+ );
+
+ assertEquals(
+ Optional.empty(),
+ UiUtils.parseFediverseHandle("megalodon")
+ );
+
+ assertEquals(
+ Optional.empty(),
+ UiUtils.parseFediverseHandle("this is not a fedi handle")
+ );
+
+ assertEquals(
+ Optional.empty(),
+ UiUtils.parseFediverseHandle("not@a-domain")
+ );
+ }
+
+ @Test
+ public void acctMatches() {
+ assertTrue("local account, domain not specified", UiUtils.acctMatches(
+ "test.tld_123456",
+ "someone",
+ "someone",
+ null
+ ));
+
+ assertTrue("domain not specified", UiUtils.acctMatches(
+ "test.tld_123456",
+ "someone@somewhere.social",
+ "someone",
+ null
+ ));
+
+ assertTrue("local account, domain specified, different casing", UiUtils.acctMatches(
+ "test.tld_123456",
+ "SomeOne",
+ "someone",
+ "Test.TLD"
+ ));
+
+ assertFalse("username doesn't match", UiUtils.acctMatches(
+ "test.tld_123456",
+ "someone-else@somewhere.social",
+ "someone",
+ "somewhere.social"
+ ));
+
+ assertFalse("domain doesn't match", UiUtils.acctMatches(
+ "test.tld_123456",
+ "someone@somewhere.social",
+ "someone",
+ "somewhere.else"
+ ));
+ }
+}
\ No newline at end of file
diff --git a/mastodon/src/androidTest/java/org/joinmastodon/android/utils/StatusFilterPredicateTest.java b/mastodon/src/androidTest/java/org/joinmastodon/android/utils/StatusFilterPredicateTest.java
new file mode 100644
index 000000000..0a00fc665
--- /dev/null
+++ b/mastodon/src/androidTest/java/org/joinmastodon/android/utils/StatusFilterPredicateTest.java
@@ -0,0 +1,81 @@
+package org.joinmastodon.android.utils;
+
+import static org.joinmastodon.android.model.Filter.FilterAction.*;
+import static org.joinmastodon.android.model.Filter.FilterContext.*;
+import static org.junit.Assert.*;
+
+import org.joinmastodon.android.model.Filter;
+import org.joinmastodon.android.model.Status;
+import org.junit.Test;
+
+import java.time.Instant;
+import java.util.EnumSet;
+import java.util.List;
+
+public class StatusFilterPredicateTest {
+
+ private static final Filter hideMeFilter = new Filter(), warnMeFilter = new Filter();
+ private static final List allFilters = List.of(hideMeFilter, warnMeFilter);
+
+ private static final Status
+ hideInHomePublic = Status.ofFake(null, "hide me, please", Instant.now()),
+ warnInHomePublic = Status.ofFake(null, "display me with a warning", Instant.now());
+
+ static {
+ hideMeFilter.phrase = "hide me";
+ hideMeFilter.filterAction = HIDE;
+ hideMeFilter.context = EnumSet.of(PUBLIC, HOME);
+
+ warnMeFilter.phrase = "warning";
+ warnMeFilter.filterAction = WARN;
+ warnMeFilter.context = EnumSet.of(PUBLIC, HOME);
+ }
+
+ @Test
+ public void testHide() {
+ assertFalse("should not pass because matching filter applies to given context",
+ new StatusFilterPredicate(allFilters, HOME).test(hideInHomePublic));
+ }
+
+ @Test
+ public void testHideRegardlessOfContext() {
+ assertTrue("filters without context should always pass",
+ new StatusFilterPredicate(allFilters, null).test(hideInHomePublic));
+ }
+
+ @Test
+ public void testHideInDifferentContext() {
+ assertTrue("should pass because matching filter does not apply to given context",
+ new StatusFilterPredicate(allFilters, THREAD).test(hideInHomePublic));
+ }
+
+ @Test
+ public void testHideWithWarningText() {
+ assertTrue("should pass because matching filter is for warnings",
+ new StatusFilterPredicate(allFilters, HOME).test(warnInHomePublic));
+ }
+
+ @Test
+ public void testWarn() {
+ assertFalse("should not pass because filter applies to given context",
+ new StatusFilterPredicate(allFilters, HOME, WARN).test(warnInHomePublic));
+ }
+
+ @Test
+ public void testWarnRegardlessOfContext() {
+ assertTrue("filters without context should always pass",
+ new StatusFilterPredicate(allFilters, null, WARN).test(warnInHomePublic));
+ }
+
+ @Test
+ public void testWarnInDifferentContext() {
+ assertTrue("should pass because filter does not apply to given context",
+ new StatusFilterPredicate(allFilters, THREAD, WARN).test(warnInHomePublic));
+ }
+
+ @Test
+ public void testWarnWithHideText() {
+ assertTrue("should pass because matching filter is for hiding",
+ new StatusFilterPredicate(allFilters, HOME, WARN).test(hideInHomePublic));
+ }
+}
\ No newline at end of file
diff --git a/mastodon/src/github/AndroidManifest.xml b/mastodon/src/github/AndroidManifest.xml
index a75f12de6..5838d1c43 100644
--- a/mastodon/src/github/AndroidManifest.xml
+++ b/mastodon/src/github/AndroidManifest.xml
@@ -1,6 +1,5 @@
-
+
diff --git a/mastodon/src/main/AndroidManifest.xml b/mastodon/src/main/AndroidManifest.xml
index 7d9c6f2e9..d7c114c33 100644
--- a/mastodon/src/main/AndroidManifest.xml
+++ b/mastodon/src/main/AndroidManifest.xml
@@ -1,6 +1,5 @@
-
+
@@ -17,6 +16,9 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -44,7 +62,8 @@
-
+
diff --git a/mastodon/src/main/assets/blocks.tsv b/mastodon/src/main/assets/blocks.tsv
deleted file mode 100644
index ad6a59e93..000000000
--- a/mastodon/src/main/assets/blocks.tsv
+++ /dev/null
@@ -1,89 +0,0 @@
-# lists.d Mastodon Blocklist (c) 2022 Greyhat Academy LICENSED UNDER: CC-BY-NC-SA 4.0
-# https://raw.githubusercontent.com/greyhat-academy/lists.d/main/mastodon.domains.block.list.tsv
-# This list contains domains of toxic mastodon instances
-# Last-Modified: 1672044500
-
-# gab - a neonazi social network
-gab.ai
-gab.com
-gab.protohype.net
-
-# consequence-free speech
-social.unzensiert.to
-freeatlantis.com
-
-# reactionary bigotry and hatespeech against magrinalized groups
-poa.st
-freespeechextremist.com
-rdrama.cc
-outpoa.st
-anime.website
-gameliberty.club
-social.byoblu.com
-yggdrasil.social
-smuglo.li
-dogeposting.social
-unsafe.space
-freezepeach.xyz
-
-# + CSAM
-rojogato.com
-
-# antivaxxer shitposting & fearmongering
-shadowsocial.org
-
-# Kiwifarms
-kiwifarms.net
-kiwifarms.cc
-kiwifarms.is
-kiwifarms.pleroma.net
-
-
-# https://mastodon.art/@Curator/109649354849593592
-
-poa.st antisemitic racist homophobic
-nicecrew.digital antisemitic
-beefyboys.win antisemitic racist homophobic harassment
-cawfee.club antisemitic racist homophobic
-comfyboy.club antisemitic racist homophobic
-freespeechextremist.com racist homophobic
-cum.salon racist misogynist
-bae.st racist
-natehiggers.online racist
-rapemeat.solutions misogynist
-rapist.town misogynist
-rapefeminists.network misogynist
-kiwifarms.cc harassment
-noagendasocial.com noagenda
-posting.lolicon.rocks underage
-urchan.org harassment homophobic racist
-ryona.agency harassment
-yggdrasil.social antisemitic homophobic racist
-genderheretics.xyz transphobic
-baraag.net underage
-lolison.top underage
-shota.house underage
-shota.social underage
-aethy.com underage
-taullo.social underage
-childpawn.shop underage
-posting.lolicon.rocks underage
-loli.best underage
-gothloli.club underage
-smuglo.li underage
-youjo.love underage
-pedo.school underage
-lolison.network underage
-freak.university underage
-mirr0r.city underage
-xhais.love underage
-refusal.biz underage
-refusal.llc underage
-mirr0r.city underage
-nnia.space underage
-ignorelist.com malicious
-repl.co malicious
-
-# custom
-
-pawoo.net csam
diff --git a/mastodon/src/main/assets/blocks.txt b/mastodon/src/main/assets/blocks.txt
new file mode 100644
index 000000000..e6af4a505
--- /dev/null
+++ b/mastodon/src/main/assets/blocks.txt
@@ -0,0 +1,171 @@
+13bells.com
+4aem.com
+aethy.com
+anime.website
+annihilation.social
+anon-kenkai.com
+asbestos.cafe
+bae.st
+bajax.us
+banepo.st
+baraag.net
+beefyboys.win
+beepboop.ga
+berserker.town
+bikeshed.party
+boks.moe
+brainsoap.net
+breastmilk.club
+brighteon.social
+cawfee.club
+clew.lol
+clubcyberia.co
+collapsitarian.io
+comfyboy.club
+contrapointsfan.club
+cum.camp
+cum.salon
+cybercriminal.eu
+darknight-coffee.org
+dembased.xyz
+desupost.soy
+detroitriotcity.com
+eatthebugs.social
+eientei.org
+elementality.org
+eveningzoo.club
+firedragonstudios.com
+firefaithfellowship.com
+fluf.club
+foxfam.club
+freak.university
+freeatlantis.com
+freecumextremist.com
+freedomstrike.org
+freesoftwareextremist.com
+freespeech.group
+freespeechextremist.com
+freetalklive.com
+froth.zone
+fulltermprivacy.com
+gameliberty.club
+gearlandia.haus
+genderheretics.xyz
+geofront.rocks
+gleasonator.com
+glee.li
+glindr.org
+goyim.app
+goyslop.cafe
+haeder.net
+handholding.io
+hidamari.apartments
+hitchhiker.social
+hunk.city
+iddqd.social
+intkos.link
+justicewarrior.social
+kawa-kun.com
+kitsunemimi.club
+kiwifarms.cc
+kompost.cz
+kurosawa.moe
+leafposter.club
+leftychan.net
+lewdieheaven.com
+liberdon.com
+ligma.pro
+lizards.live
+lolicon.rocks
+lolison.top
+lovingexpressions.net
+lucasvl.nl
+mahodou.moe
+makemysarcophagus.com
+maladaptive.art
+masochi.st
+mastinator.com
+merovingian.club
+midwaytrades.com
+mirr0r.city
+moa.st
+mouse.services
+mugicha.club
+narrativerry.xyz
+natehiggers.online
+neckbeard.xyz
+needs.vodka
+neenster.org
+nicecrew.digital
+nnia.space
+noagendasocial.com
+noagendasocial.nl
+noagendatube.com
+nobodyhasthe.biz
+nukem.biz
+obo.sh
+onionfarms.org
+outpoa.st
+pawlicker.com
+pawoo.net
+pedo.school
+piazza.today
+pibvt.net
+pieville.net
+pisskey.io
+plagu.ee
+pmth.us
+poa.st
+poast.org
+poast.tv
+poster.place
+prospeech.space
+quodverum.com
+rakket.app
+rapemeat.solutions
+rdrama.cc
+rebelbase.site
+retardedniggers.forsale
+rojogato.com
+ryona.agency
+schwartzwelt.xyz
+seal.cafe
+shigusegubu.club
+shitpost.cloud
+shitposter.club
+shota.house
+silliness.observer
+skinheads.eu
+skinheads.io
+skinheads.social
+skinheads.uk
+skippers-bin.com
+skyshanty.xyz
+slash.cl
+sleepy.cafe
+smuglo.li
+sneed.social
+sonichu.com
+spinster.xyz
+springbo.cc
+starnix.network
+stereophonic.space
+strelizia.net
+syspxl.xyz
+tastingtraffic.net
+teci.world
+theapex.social
+thepostearthdestination.com
+tkammer.de
+trumpislovetrumpis.life
+truthsocial.co.in
+urchan.org
+varishangout.net
+whinge.house
+whinge.town
+wideboys.org
+wolfgirl.bar
+xn--p1abe3d.xn--80asehdb
+yggdrasil.social
+youjo.love
+zztails.gay
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ExitActivity.java b/mastodon/src/main/java/org/joinmastodon/android/ExitActivity.java
new file mode 100644
index 000000000..54a5ccbc4
--- /dev/null
+++ b/mastodon/src/main/java/org/joinmastodon/android/ExitActivity.java
@@ -0,0 +1,24 @@
+package org.joinmastodon.android;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class ExitActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ finishAndRemoveTask();
+ }
+
+ public static void exit(Context context) {
+ Intent intent = new Intent(context, ExitActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ context.startActivity(intent);
+ }
+
+}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java b/mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java
index 32d7c74cd..40418d5fe 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java
@@ -3,20 +3,25 @@ package org.joinmastodon.android;
import android.app.Fragment;
import android.content.ClipData;
import android.content.Intent;
-import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
+import android.util.Pair;
import android.widget.Toast;
+import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.ComposeFragment;
+import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.utils.UiUtils;
+import org.jsoup.internal.StringUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
+import java.util.function.BiConsumer;
import androidx.annotation.Nullable;
import me.grishka.appkit.FragmentStackActivity;
@@ -27,18 +32,52 @@ public class ExternalShareActivity extends FragmentStackActivity{
UiUtils.setUserPreferredTheme(this);
super.onCreate(savedInstanceState);
if(savedInstanceState==null){
+
+ Optional text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT));
+ Optional>> fediHandle = text.flatMap(UiUtils::parseFediverseHandle);
+ boolean isFediUrl = text.map(UiUtils::looksLikeFediverseUrl).orElse(false);
+ boolean isOpenable = isFediUrl || fediHandle.isPresent();
+
List sessions=AccountSessionManager.getInstance().getLoggedInAccounts();
- if(sessions.isEmpty()){
+ if (sessions.isEmpty()){
Toast.makeText(this, R.string.err_not_logged_in, Toast.LENGTH_SHORT).show();
finish();
- }else if(sessions.size()==1){
+ } else if (isOpenable || sessions.size() > 1) {
+ AccountSwitcherSheet sheet = new AccountSwitcherSheet(this, null, true, isOpenable);
+ sheet.setOnClick((accountId, open) -> {
+ if (open && text.isPresent()) {
+ BiConsumer, Bundle> callback = (clazz, args) -> {
+ if (clazz == null) {
+ Toast.makeText(this, R.string.sk_open_in_app_failed, Toast.LENGTH_SHORT).show();
+ // TODO: do something about the window getting leaked
+ sheet.dismiss();
+ finish();
+ return;
+ }
+ args.putString("fromExternalShare", clazz.getSimpleName());
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.putExtras(args);
+ finish();
+ startActivity(intent);
+ };
+
+ fediHandle
+ .>map(handle ->
+ UiUtils.lookupAccountHandle(this, accountId, handle, callback))
+ .or(() -> Optional.ofNullable(
+ UiUtils.lookupURL(this, accountId, text.get(), false, callback)))
+ .ifPresent(req ->
+ req.wrapProgress(this, R.string.loading, true, d -> {
+ UiUtils.transformDialogForLookup(this, accountId, isFediUrl ? text.get() : null, d);
+ d.setOnDismissListener((ev) -> finish());
+ }));
+ } else {
+ openComposeFragment(accountId);
+ }
+ });
+ sheet.show();
+ } else if (sessions.size() == 1) {
openComposeFragment(sessions.get(0).getID());
- }else{
- getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000));
- UiUtils.pickAccount(this, null, R.string.choose_account, 0,
- session -> openComposeFragment(session.getID()),
- b -> b.setOnCancelListener(d -> finish())
- );
}
}
}
@@ -51,9 +90,15 @@ public class ExternalShareActivity extends FragmentStackActivity{
String subject = "";
if (intent.hasExtra(Intent.EXTRA_SUBJECT)) {
subject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
- if (!subject.isBlank()) builder.append(subject).append("\n\n");
+ if (!StringUtil.isBlank(subject)) builder.append(subject).append("\n\n");
+ }
+ if (intent.hasExtra(Intent.EXTRA_TEXT)) {
+ String extra = intent.getStringExtra(Intent.EXTRA_TEXT);
+ if (!StringUtil.isBlank(extra)) {
+ if (extra.startsWith(subject)) extra = extra.substring(subject.length()).trim();
+ builder.append(extra).append("\n\n");
+ }
}
- if (intent.hasExtra(Intent.EXTRA_TEXT)) builder.append(intent.getStringExtra(Intent.EXTRA_TEXT)).append("\n");
String text=builder.toString();
List mediaUris;
if(Intent.ACTION_SEND.equals(intent.getAction())){
@@ -80,8 +125,7 @@ public class ExternalShareActivity extends FragmentStackActivity{
args.putString("account", accountID);
if(!TextUtils.isEmpty(text))
args.putString("prefilledText", text);
- if(!subject.isBlank())
- args.putInt("selectionEnd", subject.length());
+ args.putInt("selectionStart", StringUtil.isBlank(subject) ? 0 : subject.length());
if(mediaUris!=null && !mediaUris.isEmpty())
args.putParcelableArrayList("mediaAttachments", toArrayList(mediaUris));
Fragment fragment=new ComposeFragment();
diff --git a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java
index b857c9fbf..3366f213f 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java
@@ -8,6 +8,7 @@ import android.content.SharedPreferences;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
+import org.joinmastodon.android.model.ContentType;
import org.joinmastodon.android.model.TimelineDefinition;
import java.lang.reflect.Type;
@@ -39,16 +40,33 @@ public class GlobalUserPreferences{
public static boolean showAltIndicator;
public static boolean showNoAltIndicator;
public static boolean enablePreReleases;
+ public static boolean prefixRepliesWithRe;
+ public static boolean bottomEncoding;
+ public static boolean collapseLongPosts;
+ public static boolean spectatorMode;
+ public static boolean autoHideFab;
+ public static boolean replyLineAboveHeader;
+ public static boolean compactReblogReplyLine;
+ public static boolean confirmBeforeReblog;
+ public static boolean allowRemoteLoading;
public static String publishButtonText;
public static ThemePreference theme;
public static ColorPreference color;
private final static Type recentLanguagesType = new TypeToken