diff --git a/mastodon/build.gradle b/mastodon/build.gradle index 618f29df0..a6841bdb4 100644 --- a/mastodon/build.gradle +++ b/mastodon/build.gradle @@ -80,6 +80,9 @@ android { shrinkResources true versionNameSuffix '-play' } + githubRelease { initWith release } + playRelease { initWith release } + fdroidRelease { initWith release } } compileOptions { sourceCompatibility JavaVersion.VERSION_17 @@ -114,7 +117,8 @@ 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.8' + implementation 'me.grishka.litex:palette:1.0.0' + implementation 'me.grishka.appkit:appkit:1.2.9' implementation 'com.google.code.gson:gson:2.9.0' implementation 'org.jsoup:jsoup:1.14.3' implementation 'com.squareup:otto:1.3.8' @@ -123,9 +127,10 @@ dependencies { 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:2.0.3' + implementation 'com.github.UnifiedPush:android-connector:2.1.1' - androidTestImplementation 'androidx.test:core:1.4.1-alpha05' - androidTestImplementation 'androidx.test.ext:junit:1.1.4-alpha05' - androidTestImplementation 'androidx.test:runner:1.5.0-alpha02' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha05' + androidTestImplementation 'androidx.test:core:1.5.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test:runner:1.5.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } diff --git a/mastodon/src/androidTest/java/org/joinmastodon/android/test/StoreScreenshotsGenerator.java b/mastodon/src/androidTest/java/org/joinmastodon/android/test/StoreScreenshotsGenerator.java index d895e4cca..301771b7e 100644 --- a/mastodon/src/androidTest/java/org/joinmastodon/android/test/StoreScreenshotsGenerator.java +++ b/mastodon/src/androidTest/java/org/joinmastodon/android/test/StoreScreenshotsGenerator.java @@ -65,6 +65,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.*; @LargeTest public class StoreScreenshotsGenerator{ private static final String PHOTO_FILE="IMG_1010.jpg"; + private static final long LOAD_WAIT_TIMEOUT=20_000; @Rule public ActivityScenarioRule activityScenarioRule=new ActivityScenarioRule<>(MainActivity.class); @@ -84,14 +85,14 @@ public class StoreScreenshotsGenerator{ AccountSession session=AccountSessionManager.getInstance().getAccount(AccountSessionManager.getInstance().getLastActiveAccountID()); MastodonApp.context.deleteDatabase(session.getID()+".db"); - onView(isRoot()).perform(waitId(R.id.more, 5000)); + onView(isRoot()).perform(waitId(R.id.more, LOAD_WAIT_TIMEOUT)); Thread.sleep(500); takeScreenshot("HomeTimeline"); GlobalUserPreferences.theme=GlobalUserPreferences.ThemePreference.DARK; activityScenarioRule.getScenario().recreate(); - onView(isRoot()).perform(waitId(R.id.more, 5000)); + onView(isRoot()).perform(waitId(R.id.more, LOAD_WAIT_TIMEOUT)); Thread.sleep(500); takeScreenshot("HomeTimeline_Dark"); @@ -100,8 +101,8 @@ public class StoreScreenshotsGenerator{ activityScenarioRule.getScenario().onActivity(activity->UiUtils.openProfileByID(activity, session.getID(), args.getString("profileAccountID"))); Thread.sleep(500); - onView(isRoot()).perform(waitId(R.id.avatar_border, 5000)); // wait for profile to load - onView(isRoot()).perform(waitId(R.id.more, 5000)); // wait for timeline to load + onView(isRoot()).perform(waitId(R.id.avatar_border, LOAD_WAIT_TIMEOUT)); // wait for profile to load + onView(isRoot()).perform(waitId(R.id.more, LOAD_WAIT_TIMEOUT)); // wait for timeline to load Thread.sleep(500); takeScreenshot("Profile"); 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 index 63c3fb866..4728a4861 100644 --- a/mastodon/src/androidTest/java/org/joinmastodon/android/ui/utils/UiUtilsTest.java +++ b/mastodon/src/androidTest/java/org/joinmastodon/android/ui/utils/UiUtilsTest.java @@ -2,15 +2,22 @@ package org.joinmastodon.android.ui.utils; import static org.junit.Assert.*; +import android.content.Context; +import android.content.res.Resources; import android.util.Pair; +import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.model.Account; +import org.joinmastodon.android.model.AccountField; import org.joinmastodon.android.model.Instance; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; import java.util.Optional; public class UiUtilsTest { @@ -103,4 +110,152 @@ public class UiUtilsTest { "somewhere.else" )); } + + private final String[] args = new String[] { "Megalodon", "♡" }; + + private String gen(String format, CharSequence... args) { + return UiUtils.generateFormattedString(format, args).toString(); + } + @Test + public void generateFormattedString() { + assertEquals( + "ordered substitution", + "Megalodon reacted with ♡", + gen("%s reacted with %s", args) + ); + + assertEquals( + "1 2 3 4 5", + gen("%s %s %s %s %s", "1", "2", "3", "4", "5") + ); + + assertEquals( + "indexed substitution", + "with ♡ was reacted by Megalodon", + gen("with %2$s was reacted by %1$s", args) + ); + + assertEquals( + "indexed substitution, in order", + "Megalodon reacted with ♡", + gen("%1$s reacted with %2$s", args) + ); + + assertEquals( + "indexed substitution, 0-based", + "Megalodon reacted with ♡", + gen("%0$s reacted with %1$s", args) + ); + + assertEquals( + "indexed substitution, 5 items", + "5 4 3 2 1", + gen("%5$s %4$s %3$s %2$s %1$s", "1", "2", "3", "4", "5") + ); + + assertEquals( + "one argument missing", + "Megalodon reacted with ♡", + gen("reacted with %s", args) + ); + + assertEquals( + "multiple arguments missing", + "Megalodon reacted with ♡", + gen("reacted with", args) + ); + + assertEquals( + "multiple arguments missing, numbers in expeced positions", + "1 2 x 3 4 5", + gen("%s x %s", "1", "2", "3", "4", "5") + ); + + assertEquals( + "one leading and trailing space", + "Megalodon reacted with ♡", + gen(" reacted with ", args) + ); + + assertEquals( + "multiple leading and trailing spaces", + "Megalodon reacted with ♡", + gen(" reacted with ", args) + ); + + assertEquals( + "invalid format produces expected invalid result", + "Megalodon reacted with % s ♡", + gen("reacted with % s", args) + ); + + assertEquals( + "plain string as format, all arguments get added", + "a x b c", + gen("x", new String[] { "a", "b", "c" }) + ); + + assertEquals("empty input produces empty output", "", gen("")); + + // not supported: +// assertEquals("a b a", gen("%1$s %2$s %2$s %1$s", new String[] { "a", "b", "c" })); +// assertEquals("x", gen("%s %1$s %2$s %1$s %s", new String[] { "a", "b", "c" })); + } + + private AccountField makeField(String name, String value) { + AccountField f = new AccountField(); + f.name = name; + f.value = value; + return f; + } + + private Account fakeAccount(AccountField... fields) { + Account a = new Account(); + a.fields = Arrays.asList(fields); + return a; + } + + @Test + public void extractPronouns() { + assertEquals("they", UiUtils.extractPronouns(MastodonApp.context, fakeAccount( + makeField("name and pronouns", "https://pronouns.site"), + makeField("pronouns", "they"), + makeField("pronouns something", "bla bla") + )).orElseThrow()); + + assertTrue(UiUtils.extractPronouns(MastodonApp.context, fakeAccount()).isEmpty()); + + assertEquals("it/its", UiUtils.extractPronouns(MastodonApp.context, fakeAccount( + makeField("pronouns pronouns pronouns", "hi hi hi"), + makeField("pronouns", "it/its"), + makeField("the pro's nouns", "professional") + )).orElseThrow()); + + assertEquals("she/he", UiUtils.extractPronouns(MastodonApp.context, fakeAccount( + makeField("my name is", "jeanette shork, apparently"), + makeField("my pronouns are", "she/he") + )).orElseThrow()); + + assertEquals("they/them", UiUtils.extractPronouns(MastodonApp.context, fakeAccount( + makeField("pronouns", "https://pronouns.cc/pronouns/they/them") + )).orElseThrow()); + + Context german = UiUtils.getLocalizedContext(MastodonApp.context, Locale.GERMAN); + + assertEquals("sie/ihr", UiUtils.extractPronouns(german, fakeAccount( + makeField("pronomen lauten", "sie/ihr"), + makeField("pronouns are", "she/her"), + makeField("die pronomen", "stehen oben") + )).orElseThrow()); + + assertEquals("er/ihm", UiUtils.extractPronouns(german, fakeAccount( + makeField("die pronomen", "stehen unten"), + makeField("pronomen sind", "er/ihm"), + makeField("pronouns are", "he/him") + )).orElseThrow()); + + assertEquals("* (asterisk)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount( + makeField("pronouns", "-- * (asterisk) --") + )).orElseThrow()); + } } \ 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 index 0a00fc665..84f156706 100644 --- a/mastodon/src/androidTest/java/org/joinmastodon/android/utils/StatusFilterPredicateTest.java +++ b/mastodon/src/androidTest/java/org/joinmastodon/android/utils/StatusFilterPredicateTest.java @@ -1,10 +1,10 @@ package org.joinmastodon.android.utils; -import static org.joinmastodon.android.model.Filter.FilterAction.*; -import static org.joinmastodon.android.model.Filter.FilterContext.*; +import static org.joinmastodon.android.model.FilterAction.*; +import static org.joinmastodon.android.model.FilterContext.*; import static org.junit.Assert.*; -import org.joinmastodon.android.model.Filter; +import org.joinmastodon.android.model.LegacyFilter; import org.joinmastodon.android.model.Status; import org.junit.Test; @@ -14,8 +14,8 @@ 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 LegacyFilter hideMeFilter = new LegacyFilter(), warnMeFilter = new LegacyFilter(); + private static final List allFilters = List.of(hideMeFilter, warnMeFilter); private static final Status hideInHomePublic = Status.ofFake(null, "hide me, please", Instant.now()), diff --git a/mastodon/src/github/java/org/joinmastodon/android/updater/GithubSelfUpdaterImpl.java b/mastodon/src/github/java/org/joinmastodon/android/updater/GithubSelfUpdaterImpl.java index 76eb2d853..73ca393d8 100644 --- a/mastodon/src/github/java/org/joinmastodon/android/updater/GithubSelfUpdaterImpl.java +++ b/mastodon/src/github/java/org/joinmastodon/android/updater/GithubSelfUpdaterImpl.java @@ -100,8 +100,8 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{ public void maybeCheckForUpdates(){ if(state!=UpdateState.NO_UPDATE && state!=UpdateState.UPDATE_AVAILABLE) return; - long timeSinceLastCheck=System.currentTimeMillis()-getPrefs().getLong("lastCheck", CHECK_PERIOD); - if(timeSinceLastCheck>=CHECK_PERIOD){ + long timeSinceLastCheck=System.currentTimeMillis()-getPrefs().getLong("lastCheck", 0); + if(timeSinceLastCheck>CHECK_PERIOD || forceUpdate){ setState(UpdateState.CHECKING); MastodonAPIController.runInBackground(this::actuallyCheckForUpdates); } @@ -148,7 +148,8 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{ curForkNumber=Integer.parseInt(matcher.group(4)); long newVersion=((long)newMajor << 32) | ((long)newMinor << 16) | newRevision; long curVersion=((long)curMajor << 32) | ((long)curMinor << 16) | curRevision; - if(newVersion>curVersion || newForkNumber>curForkNumber){ + if(newVersion>curVersion || newForkNumber>curForkNumber || forceUpdate){ + forceUpdate=false; String version=newMajor+"."+newMinor+"."+newRevision+"+fork."+newForkNumber; Log.d(TAG, "actuallyCheckForUpdates: new version: "+version); for(JsonElement el:obj.getAsJsonArray("assets")){ @@ -323,6 +324,15 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{ } } + @Override + public void reset(){ + getPrefs().edit().clear().apply(); + File apk=getUpdateApkFile(); + if(apk.exists()) + apk.delete(); + state=UpdateState.NO_UPDATE; + } + /*public static class InstallerStatusReceiver extends BroadcastReceiver{ @Override