Compare commits

..

4 Commits

Author SHA1 Message Date
LucasGGamerM
b29189ad2a refactor: move mastodon/session/session to mastodon/session package 2023-05-14 14:43:57 -03:00
LucasGGamerM
f725e46bb2 refactor: move mastodon api calls to own package
This should help organization for implementing the misskey side of the api
2023-05-14 11:24:18 -03:00
LucasGGamerM
3c14ec8195 feat: hacking together a prototype for the api abstraction layer
So far it works for the home timelines, next I gotta figure out how to see what type of server a logged in account is from
2023-05-14 11:24:18 -03:00
LucasGGamerM
eb78823cb1 feat: initial commit for the ApiAdapter class
This needs a *lot* of work to make moshidon *key compatible
2023-05-14 11:24:18 -03:00
437 changed files with 3515 additions and 13393 deletions

1
.github/FUNDING.yml vendored
View File

@@ -1,7 +1,6 @@
# These are supported funding model platforms
github: LucasGGamerM
custom: ["https://liberapay.com/LucasGGamerM/donate", liberapay.com]
patreon: # mastodon
open_collective: # Replace with a single Open Collective username e.g., user1
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel

View File

@@ -10,28 +10,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Appkit Repo
uses: actions/checkout@v3
with:
repository: grishka/appkit
- name: set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'corretto'
cache: gradle
- name: Comment out signing config in appkits gradle file
run: |
sed -i 's/sign publishing\.publications\.release/\/\/ sign publishing.publications.release/' appkit/maven-push.gradle
- name: Grant execute permission for gradlew for Appkit
run: chmod +x gradlew
- name: Compile appkit
run: ./gradlew publishToMavenLocal
- uses: actions/checkout@v3
- name: set up JDK 17
uses: actions/setup-java@v3

4
FAQ.md
View File

@@ -3,7 +3,3 @@
Q: What are the main differences between Moshidon and Megalodon?
A: There are many, but the most outstanding differences are: the ability to have other server's local timeline inside the app. It can be acessed in the "Add community" option in the top right corner of the Edit timelines screen. Other outstanding features that Moshidon has are some quality of life improvements, such as notification actions and allowing for unlisted replies by default. Most other features are pretty minor, such as profile notes directly available in the person's profile. Other features are quite minor usability and visibility improvements. All of which can be found in the settings page.
Q: Will there ever be a versjon of Moshidon for iOS?
A: No. As android and iOS apps do not share code, it is incredibly hard to port.

View File

@@ -19,10 +19,6 @@
<a href="https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.moshinda"><img height="50" alt="Get it on IzzyOnDroid" src="img/izzy-badge.png"></a>
## Help out the project by donating at: https://github.com/sponsors/LucasGGamerM!
### We also support LiberaPay at: https://liberapay.com/LucasGGamerM/donate!
### You can also donate some Monero through this wallet address as well:
4886mdarcyB6Yf8Qc6vDJBK1fz6ibHFLZUmHb4GZZz9yLGNhcG3XC64e5UZ8dVQYTLZb82W6P9WhteowW4STJEec97Gf22j
---
@@ -195,8 +191,6 @@ This project is released under the [GPL-3 License](./LICENSE).
[Official matrix chatroom:](https://matrix.to/#/#moshidon:floss.social) https://matrix.to/#/#moshidon:floss.social
[Moshidon roadmap](https://github.com/users/LucasGGamerM/projects/1)
<a rel="me" href="https://floss.social/@moshidon">@moshidon<wbr>@floss.social</a>
---

View File

@@ -3,7 +3,6 @@ buildscript {
repositories {
google()
mavenCentral()
mavenLocal()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.0.0'

View File

@@ -1,9 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<style>
path { fill: black; }
@media (prefers-color-scheme: dark) {
path { fill: white; }
}
</style>
<path d="M15.4925 3.50673C14.652 3.58251 13.9933 4.28929 13.9933 5.15V10C13.9933 10.4142 13.6577 10.75 13.2437 10.75C11.8002 10.75 10.7863 11.3378 10.0365 12.238C9.26389 13.1656 8.7607 14.444 8.44554 15.7954C8.13254 17.1376 8.01871 18.4912 7.98453 19.5172C7.97182 19.8987 7.9702 20.2324 7.97313 20.5H14.9928V19.75C14.9928 18.5074 13.986 17.5 12.744 17.5H11.4947C11.0807 17.5 10.7451 17.1642 10.7451 16.75C10.7451 16.3358 11.0807 16 11.4947 16H12.744C14.8139 16 16.4919 17.6789 16.4919 19.75V20.5H17.2415C17.6555 20.5 17.9911 20.1642 17.9911 19.75V9.75C17.9911 9.33579 18.3267 9 18.7407 9H19.2472C20.2264 9 20.8249 7.92404 20.309 7.09132L19.6893 6.09132C19.4615 5.72367 19.0599 5.5 18.6275 5.5H16.2421C15.8281 5.5 15.4925 5.16421 15.4925 4.75V3.50673ZM6.47388 20.5C6.47098 20.2156 6.47293 19.8655 6.4862 19.4672C6.52229 18.3838 6.64271 16.9249 6.98559 15.4546C7.32631 13.9935 7.90065 12.4594 8.88484 11.2777C9.75681 10.2307 10.9399 9.47669 12.4942 9.29318V5.15C12.4942 3.4103 13.9037 2 15.6424 2C16.3876 2 16.9916 2.60442 16.9916 3.35V4H18.6275C19.5787 4 20.4622 4.49207 20.9634 5.30092L21.5831 6.30092C22.6749 8.06291 21.4985 10.32 19.4903 10.4898V19.75C19.4903 20.9926 18.4835 22 17.2415 22H7.24708L7.24537 22H5.79625C3.69964 22 2 20.2994 2 18.2016C2 17.2395 2.36489 16.3133 3.02098 15.6099L4.15612 14.393C4.92005 13.5741 5.17521 12.4027 4.82117 11.3399C4.67114 10.8896 4.41837 10.4804 4.08288 10.1447L2.96914 9.03042C2.67641 8.73753 2.67639 8.26266 2.96912 7.96976C3.26184 7.67686 3.73645 7.67685 4.02919 7.96974L5.14293 9.08405C5.643 9.58438 6.01977 10.1943 6.2434 10.8656C6.77114 12.4497 6.3908 14.1958 5.25209 15.4165L4.11695 16.6334C3.71996 17.059 3.49916 17.6195 3.49916 18.2016C3.49916 19.471 4.52761 20.5 5.79625 20.5H6.47388Z" fill="#212121"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -16,8 +16,8 @@ android {
applicationId "org.joinmastodon.android.moshinda"
minSdk 23
targetSdk 33
versionCode 101
versionName "1.2.3+fork.101.moshinda"
versionCode 100
versionName "1.3.0+fork.100.moshinda"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
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']
}
@@ -62,6 +62,7 @@ android {
initWith release
}
nightly{
initWith release
if(System.getenv("CURRENT_DATE") != null){
versionNameSuffix '-nightly+@' + System.getenv("CURRENT_DATE")
} else {
@@ -70,7 +71,6 @@ android {
versionNameSuffix '-nightly+@' + properties.getProperty('CURRENT_DATE')
}
applicationIdSuffix '.nightly'
signingConfig signingConfigs.nightly
manifestPlaceholders = [oAuthScheme:"moshidon-android-nightly-auth"]
}
@@ -114,7 +114,7 @@ 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.appkit:appkit:1.2.7'
implementation 'com.google.code.gson:gson:2.9.0'
implementation 'org.jsoup:jsoup:1.14.3'
implementation 'com.squareup:otto:1.3.8'

View File

@@ -30,9 +30,6 @@
*;
}
# i don't know how proguard works
-keep class org.joinmastodon.android.** { *; }
# Keep all enums for debugging purposes
-keepnames public enum * {
*;
@@ -56,39 +53,3 @@
-keep interface org.parceler.Parcel
-keep @org.parceler.Parcel class * { *; }
-keep class **$$Parcelable { *; }
##---------------Begin: proguard configuration for Gson ----------
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# For using GSON @Expose annotation
-keepattributes *Annotation*
# Gson specific classes
-dontwarn sun.misc.**
#-keep class com.google.gson.stream.** { *; }
# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { <fields>; }
# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
# Prevent R8 from leaving Data object members always null
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
##---------------End: proguard configuration for Gson ----------
-dontobfuscate

View File

@@ -1,113 +0,0 @@
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<ThreadFragment.NeighborAncestryInfo> 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<Status> expectedAncestors = List.of(
fakeStatus("oldest ancestor", null),
fakeStatus("younger ancestor", "oldest ancestor")
);
List<Status> 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
}
}

View File

@@ -1,10 +1,8 @@
package org.joinmastodon.android.test;
import android.app.Instrumentation;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
@@ -14,10 +12,10 @@ import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.instance.GetInstance;
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.api.mastodon.requests.instance.GetInstance;
import org.joinmastodon.android.api.mastodon.requests.statuses.GetStatusByID;
import org.joinmastodon.android.api.mastodon.session.AccountSession;
import org.joinmastodon.android.api.mastodon.session.AccountSessionManager;
import org.joinmastodon.android.fragments.ComposeFragment;
import org.joinmastodon.android.fragments.ThreadFragment;
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
@@ -32,12 +30,9 @@ import org.parceler.Parcels;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeoutException;
import androidx.test.core.app.ActivityScenario;
import androidx.test.espresso.PerformException;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
@@ -47,18 +42,15 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.screenshot.ScreenCapture;
import androidx.test.runner.screenshot.Screenshot;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import okio.BufferedSink;
import okio.Okio;
import okio.Sink;
import okio.Source;
import static androidx.test.espresso.Espresso.*;
import static androidx.test.espresso.action.ViewActions.*;
import static androidx.test.espresso.assertion.ViewAssertions.*;
import static androidx.test.espresso.matcher.ViewMatchers.*;
@RunWith(AndroidJUnit4.class)

View File

@@ -1,106 +0,0 @@
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"
));
}
}

View File

@@ -1,81 +0,0 @@
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<Filter> 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));
}
}

View File

@@ -5,7 +5,6 @@
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
@@ -41,22 +40,6 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".PanicResponderActivity"
android:exported="true"
android:launchMode="singleInstance"
android:noHistory="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="info.guardianproject.panic.action.TRIGGER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".ExitActivity"
android:exported="false"
android:theme="@android:style/Theme.NoDisplay" />
<activity android:name=".OAuthActivity" android:exported="true" android:configChanges="orientation|screenSize" android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
@@ -93,16 +76,6 @@
</intent-filter>
</receiver>
<provider
android:name="org.joinmastodon.android.utils.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

View File

@@ -0,0 +1,89 @@
# 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 marginalized 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
1 # lists.d Mastodon Blocklist (c) 2022 Greyhat Academy LICENSED UNDER: CC-BY-NC-SA 4.0
2 # https://raw.githubusercontent.com/greyhat-academy/lists.d/main/mastodon.domains.block.list.tsv
3 # This list contains domains of toxic mastodon instances
4 # Last-Modified: 1672044500
5 # gab - a neonazi social network
6 gab.ai
7 gab.com
8 gab.protohype.net
9 # consequence-free speech
10 social.unzensiert.to
11 freeatlantis.com
12 # reactionary bigotry and hatespeech against marginalized groups
13 poa.st
14 freespeechextremist.com
15 rdrama.cc
16 outpoa.st
17 anime.website
18 gameliberty.club
19 social.byoblu.com
20 yggdrasil.social
21 smuglo.li
22 dogeposting.social
23 unsafe.space
24 freezepeach.xyz
25 # + CSAM
26 rojogato.com
27 # antivaxxer shitposting & fearmongering
28 shadowsocial.org
29 # Kiwifarms
30 kiwifarms.net
31 kiwifarms.cc
32 kiwifarms.is
33 kiwifarms.pleroma.net
34 # https://mastodon.art/@Curator/109649354849593592
35 poa.st antisemitic racist homophobic
36 nicecrew.digital antisemitic
37 beefyboys.win antisemitic racist homophobic harassment
38 cawfee.club antisemitic racist homophobic
39 comfyboy.club antisemitic racist homophobic
40 freespeechextremist.com racist homophobic
41 cum.salon racist misogynist
42 bae.st racist
43 natehiggers.online racist
44 rapemeat.solutions misogynist
45 rapist.town misogynist
46 rapefeminists.network misogynist
47 kiwifarms.cc harassment
48 noagendasocial.com noagenda
49 posting.lolicon.rocks underage
50 urchan.org harassment homophobic racist
51 ryona.agency harassment
52 yggdrasil.social antisemitic homophobic racist
53 genderheretics.xyz transphobic
54 baraag.net underage
55 lolison.top underage
56 shota.house underage
57 shota.social underage
58 aethy.com underage
59 taullo.social underage
60 childpawn.shop underage
61 posting.lolicon.rocks underage
62 loli.best underage
63 gothloli.club underage
64 smuglo.li underage
65 youjo.love underage
66 pedo.school underage
67 lolison.network underage
68 freak.university underage
69 mirr0r.city underage
70 xhais.love underage
71 refusal.biz underage
72 refusal.llc underage
73 mirr0r.city underage
74 nnia.space underage
75 ignorelist.com malicious
76 repl.co malicious
77 # custom
78 pawoo.net csam

View File

@@ -1,171 +0,0 @@
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

View File

@@ -1,62 +1,5 @@
package org.joinmastodon.android;
import android.app.Activity;
import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabsIntent;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.PushSubscriptionManager;
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
import org.joinmastodon.android.api.requests.accounts.GetPreferences;
import org.joinmastodon.android.api.requests.accounts.GetWordFilters;
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
import org.joinmastodon.android.api.requests.instance.GetInstance;
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
import org.joinmastodon.android.api.session.AccountActivationInfo;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.EmojiUpdatedEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Application;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.Token;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
public class DomainManager {
private static final String TAG="DomainManager";

View File

@@ -1,24 +0,0 @@
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);
}
}

View File

@@ -1,17 +1,16 @@
package org.joinmastodon.android;
import android.app.Fragment;
import android.app.assist.AssistContent;
import android.content.ClipData;
import android.content.Intent;
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.api.mastodon.session.AccountSession;
import org.joinmastodon.android.api.mastodon.session.AccountSessionManager;
import org.joinmastodon.android.fragments.ComposeFragment;
import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.utils.UiUtils;
@@ -20,8 +19,6 @@ 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;
@@ -33,51 +30,22 @@ public class ExternalShareActivity extends FragmentStackActivity{
super.onCreate(savedInstanceState);
if(savedInstanceState==null){
Optional<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT));
Optional<Pair<String, Optional<String>>> fediHandle = text.flatMap(UiUtils::parseFediverseHandle);
boolean isFediUrl = text.map(UiUtils::looksLikeMastodonUrl).orElse(false);
boolean isOpenable = isFediUrl || fediHandle.isPresent();
String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
boolean isMastodonURL = UiUtils.looksLikeMastodonUrl(text);
List<AccountSession> 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 (isOpenable || sessions.size() > 1) {
AccountSwitcherSheet sheet = new AccountSwitcherSheet(this, null, true, isOpenable);
sheet.setOnClick((accountId, open) -> {
if (open && text.isPresent()) {
BiConsumer<Class<? extends Fragment>, 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
.<MastodonAPIRequest<?>>map(handle ->
UiUtils.lookupAccountHandle(this, accountId, handle, callback))
.or(() ->
UiUtils.lookupURL(this, accountId, text.get(), callback))
.ifPresent(req ->
req.wrapProgress(this, R.string.loading, true, d -> {
UiUtils.transformDialogForLookup(this, accountId, isFediUrl ? text.get() : null, d);
d.setOnDismissListener((x) -> finish());
}));
} else {
openComposeFragment(accountId);
}
});
sheet.show();
} else if (sessions.size() == 1) {
}else if(sessions.size()==1 && !isMastodonURL){
openComposeFragment(sessions.get(0).getID());
}else{
new AccountSwitcherSheet(this, false, false, isMastodonURL, accountSession -> {
if(accountSession!=null)
openComposeFragment(accountSession.getID());
else
UiUtils.openURL(this, AccountSessionManager.getInstance().getLastActiveAccountID(), text);
}).show();
}
}
}
@@ -140,4 +108,11 @@ public class ExternalShareActivity extends FragmentStackActivity{
return null;
return new ArrayList<>(l);
}
@Override
public void onProvideAssistContent(AssistContent outContent) {
super.onProvideAssistContent(outContent);
outContent.setWebUri(Uri.parse(DomainManager.getInstance().getCurrentDomain()));
}
}

View File

@@ -9,7 +9,6 @@ import android.os.Build;
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;
@@ -31,48 +30,41 @@ public class GlobalUserPreferences{
public static boolean alwaysExpandContentWarnings;
public static boolean disableMarquee;
public static boolean disableSwipe;
public static boolean showDividers;
public static boolean disableDividers;
public static boolean voteButtonForSingleChoice;
public static boolean enableDeleteNotifications;
public static boolean translateButtonOpenedOnly;
public static boolean uniformNotificationIcon;
public static boolean enableDeleteNotifications;
public static boolean relocatePublishButton;
public static boolean reduceMotion;
public static boolean keepOnlyLatestNotification;
public static boolean enableFabAutoHide;
public static boolean disableAltTextReminder;
public static boolean showAltIndicator;
public static boolean showNoAltIndicator;
public static boolean enablePreReleases;
public static PrefixRepliesMode prefixReplies;
public static boolean prefixRepliesWithRe;
public static boolean bottomEncoding;
public static boolean collapseLongPosts;
public static boolean spectatorMode;
public static boolean autoHideFab;
public static boolean defaultToUnlistedReplies;
public static boolean doubleTapToSwipe;
public static boolean disableDoubleTapToSwipe;
public static boolean compactReblogReplyLine;
public static boolean confirmBeforeReblog;
public static boolean replyLineAboveHeader;
public static boolean swapBookmarkWithBoostAction;
public static boolean loadRemoteAccountFollowers;
public static boolean mentionRebloggerAutomatically;
public static boolean allowRemoteLoading;
public static boolean forwardReportDefault;
public static AutoRevealMode autoRevealEqualSpoilers;
public static String publishButtonText;
public static ThemePreference theme;
public static ColorPreference color;
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
private final static Type pinnedTimelinesType = new TypeToken<Map<String, List<TimelineDefinition>>>() {}.getType();
public static Map<String, List<String>> recentLanguages;
public static Map<String, List<TimelineDefinition>> pinnedTimelines;
public static Set<String> accountsWithLocalOnlySupport;
public static Set<String> accountsInGlitchMode;
public static Set<String> accountsWithContentTypesEnabled;
public static Map<String, ContentType> accountsDefaultContentTypes;
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
private final static Type pinnedTimelinesType = new TypeToken<Map<String, List<TimelineDefinition>>>() {}.getType();
private final static Type accountsDefaultContentTypesType = new TypeToken<Map<String, ContentType>>() {}.getType();
private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType();
public static Map<String, Integer> recentEmojis;
@@ -82,6 +74,7 @@ public class GlobalUserPreferences{
*/
public static String replyVisibility;
public static SharedPreferences getPrefs(){
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
}
@@ -92,16 +85,6 @@ public class GlobalUserPreferences{
catch (JsonSyntaxException ignored) { return orElse; }
}
public static void removeAccount(String accountId) {
recentLanguages.remove(accountId);
pinnedTimelines.remove(accountId);
accountsInGlitchMode.remove(accountId);
accountsWithLocalOnlySupport.remove(accountId);
accountsWithContentTypesEnabled.remove(accountId);
accountsDefaultContentTypes.remove(accountId);
save();
}
public static void load(){
SharedPreferences prefs=getPrefs();
playGifs=prefs.getBoolean("playGifs", true);
@@ -116,26 +99,25 @@ public class GlobalUserPreferences{
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
disableMarquee=prefs.getBoolean("disableMarquee", false);
disableSwipe=prefs.getBoolean("disableSwipe", false);
showDividers =prefs.getBoolean("showDividers", false);
disableDividers=prefs.getBoolean("disableDividers", true);
relocatePublishButton=prefs.getBoolean("relocatePublishButton", true);
voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true);
enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", false);
translateButtonOpenedOnly=prefs.getBoolean("translateButtonOpenedOnly", false);
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
reduceMotion=prefs.getBoolean("reduceMotion", false);
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
enableFabAutoHide=prefs.getBoolean("enableFabAutoHide", true);
disableAltTextReminder=prefs.getBoolean("disableAltTextReminder", false);
showAltIndicator=prefs.getBoolean("showAltIndicator", true);
showNoAltIndicator=prefs.getBoolean("showNoAltIndicator", true);
enablePreReleases=prefs.getBoolean("enablePreReleases", false);
prefixReplies=PrefixRepliesMode.valueOf(prefs.getString("prefixReplies", PrefixRepliesMode.NEVER.name()));
prefixRepliesWithRe=prefs.getBoolean("prefixRepliesWithRe", false);
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
spectatorMode=prefs.getBoolean("spectatorMode", false);
autoHideFab=prefs.getBoolean("autoHideFab", true);
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
defaultToUnlistedReplies=prefs.getBoolean("defaultToUnlistedReplies", false);
doubleTapToSwipe =prefs.getBoolean("doubleTapToSwipe", true);
disableDoubleTapToSwipe=prefs.getBoolean("disableDoubleTapToSwipe", false);
replyLineAboveHeader=prefs.getBoolean("replyLineAboveHeader", true);
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
confirmBeforeReblog=prefs.getBoolean("confirmBeforeReblog", false);
@@ -151,20 +133,6 @@ public class GlobalUserPreferences{
accountsWithLocalOnlySupport=prefs.getStringSet("accountsWithLocalOnlySupport", new HashSet<>());
accountsInGlitchMode=prefs.getStringSet("accountsInGlitchMode", new HashSet<>());
replyVisibility=prefs.getString("replyVisibility", null);
accountsWithContentTypesEnabled=prefs.getStringSet("accountsWithContentTypesEnabled", new HashSet<>());
accountsDefaultContentTypes=fromJson(prefs.getString("accountsDefaultContentTypes", null), accountsDefaultContentTypesType, new HashMap<>());
allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true);
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
forwardReportDefault=prefs.getBoolean("forwardReportDefault", true);
if (prefs.contains("prefixRepliesWithRe")) {
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
? PrefixRepliesMode.TO_OTHERS : PrefixRepliesMode.NEVER;
prefs.edit()
.putString("prefixReplies", prefixReplies.name())
.remove("prefixRepliesWithRe")
.apply();
}
try {
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
@@ -191,27 +159,25 @@ public class GlobalUserPreferences{
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
.putBoolean("disableMarquee", disableMarquee)
.putBoolean("disableSwipe", disableSwipe)
.putBoolean("enableDeleteNotifications", enableDeleteNotifications)
.putBoolean("translateButtonOpenedOnly", translateButtonOpenedOnly)
.putBoolean("showDividers", showDividers)
.putBoolean("disableDividers", disableDividers)
.putBoolean("relocatePublishButton", relocatePublishButton)
.putBoolean("uniformNotificationIcon", uniformNotificationIcon)
.putBoolean("enableDeleteNotifications", enableDeleteNotifications)
.putBoolean("reduceMotion", reduceMotion)
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
.putBoolean("enableFabAutoHide", enableFabAutoHide)
.putBoolean("disableAltTextReminder", disableAltTextReminder)
.putBoolean("showAltIndicator", showAltIndicator)
.putBoolean("showNoAltIndicator", showNoAltIndicator)
.putBoolean("enablePreReleases", enablePreReleases)
.putString("prefixReplies", prefixReplies.name())
.putBoolean("prefixRepliesWithRe", prefixRepliesWithRe)
.putBoolean("collapseLongPosts", collapseLongPosts)
.putBoolean("spectatorMode", spectatorMode)
.putBoolean("autoHideFab", autoHideFab)
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
.putString("publishButtonText", publishButtonText)
.putBoolean("bottomEncoding", bottomEncoding)
.putBoolean("defaultToUnlistedReplies", defaultToUnlistedReplies)
.putBoolean("doubleTapToSwipe", doubleTapToSwipe)
.putBoolean("disableDoubleTapToSwipe", disableDoubleTapToSwipe)
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
.putBoolean("replyLineAboveHeader", replyLineAboveHeader)
.putBoolean("confirmBeforeReblog", confirmBeforeReblog)
@@ -226,11 +192,6 @@ public class GlobalUserPreferences{
.putStringSet("accountsWithLocalOnlySupport", accountsWithLocalOnlySupport)
.putStringSet("accountsInGlitchMode", accountsInGlitchMode)
.putString("replyVisibility", replyVisibility)
.putStringSet("accountsWithContentTypesEnabled", accountsWithContentTypesEnabled)
.putString("accountsDefaultContentTypes", gson.toJson(accountsDefaultContentTypes))
.putBoolean("allowRemoteLoading", allowRemoteLoading)
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
.putBoolean("forwardReportDefault", forwardReportDefault)
.apply();
}
@@ -251,16 +212,5 @@ public class GlobalUserPreferences{
LIGHT,
DARK
}
public enum AutoRevealMode {
NEVER,
THREADS,
DISCUSSIONS
}
public enum PrefixRepliesMode {
NEVER,
ALWAYS,
TO_OTHERS
}
}

View File

@@ -1,28 +1,18 @@
package org.joinmastodon.android;
import static org.joinmastodon.android.fragments.ComposeFragment.CAMERA_PERMISSION_CODE;
import static org.joinmastodon.android.fragments.ComposeFragment.CAMERA_PIC_REQUEST_CODE;
import android.Manifest;
import android.app.Activity;
import android.app.Fragment;
import android.app.assist.AssistContent;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.Toast;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.TakePictureRequestEvent;
import org.joinmastodon.android.api.mastodon.session.AccountSession;
import org.joinmastodon.android.api.mastodon.session.AccountSessionManager;
import org.joinmastodon.android.fragments.ComposeFragment;
import org.joinmastodon.android.fragments.HomeFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
@@ -32,13 +22,13 @@ import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater;
import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.parceler.Parcels;
import androidx.annotation.Nullable;
import me.grishka.appkit.FragmentStackActivity;
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
public class MainActivity extends FragmentStackActivity{
@Override
protected void onCreate(@Nullable Bundle savedInstanceState){
UiUtils.setUserPreferredTheme(this);
@@ -48,18 +38,10 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
showFragmentClearingBackStack(new CustomWelcomeFragment());
}else{
AccountSessionManager.getInstance().maybeUpdateLocalInfo();
AccountSession session;
Bundle args=new Bundle();
Intent intent=getIntent();
if(intent.hasExtra("fromExternalShare")) {
AccountSessionManager.getInstance()
.setLastActiveAccountID(intent.getStringExtra("account"));
AccountSessionManager.getInstance().maybeUpdateLocalInfo(
AccountSessionManager.getInstance().getLastActiveAccount());
showFragmentForExternalShare(intent.getExtras());
return;
}
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
boolean hasNotification = intent.hasExtra("notification");
if(fromNotification){
@@ -73,7 +55,6 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
}else{
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
AccountSessionManager.getInstance().maybeUpdateLocalInfo(session);
args.putString("account", session.getID());
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
fragment.setArguments(args);
@@ -97,12 +78,12 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
@Override
protected void onNewIntent(Intent intent){
super.onNewIntent(intent);
AccountSessionManager.getInstance().maybeUpdateLocalInfo();
if (intent.hasExtra("fromExternalShare")) showFragmentForExternalShare(intent.getExtras());
else if (intent.getBooleanExtra("fromNotification", false)) {
if(intent.getBooleanExtra("fromNotification", false)){
String accountID=intent.getStringExtra("accountID");
AccountSession accountSession;
try{
AccountSessionManager.getInstance().getAccount(accountID);
accountSession=AccountSessionManager.getInstance().getAccount(accountID);
DomainManager.getInstance().setCurrentDomain(accountSession.domain);
}catch(IllegalStateException x){
return;
}
@@ -126,26 +107,23 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
}
private void showFragmentForNotification(Notification notification, String accountID){
Fragment fragment;
Bundle args=new Bundle();
args.putString("account", accountID);
args.putBoolean("_can_go_back", true);
try{
notification.postprocess();
}catch(ObjectValidationException x){
Log.w("MainActivity", x);
return;
}
Bundle args = new Bundle();
args.putBoolean("noTransition", true);
UiUtils.showFragmentForNotification(this, notification, accountID, args);
if(notification.status!=null){
fragment=new ThreadFragment();
args.putParcelable("status", Parcels.wrap(notification.status));
}else{
fragment=new ProfileFragment();
args.putParcelable("profileAccount", Parcels.wrap(notification.account));
}
private void showFragmentForExternalShare(Bundle args) {
String className = args.getString("fromExternalShare");
Fragment fragment = switch (className) {
case "ThreadFragment" -> new ThreadFragment();
case "ProfileFragment" -> new ProfileFragment();
default -> null;
};
if (fragment == null) return;
args.putBoolean("_can_go_back", true);
fragment.setArguments(args);
showFragment(fragment);
}
@@ -179,58 +157,25 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
(fragmentContainers.get(fragmentContainers.size() - 1)).getId()
);
Bundle currentArgs = currentFragment.getArguments();
if (fragmentContainers.size() != 1
|| currentArgs == null
|| !currentArgs.getBoolean("_can_go_back", false)) {
super.onBackPressed();
return;
}
if (currentArgs.getBoolean("_finish_on_back", false)) {
finish();
} else if (currentArgs.containsKey("account")) {
if (this.fragmentContainers.size() == 1
&& currentArgs != null
&& currentArgs.getBoolean("_can_go_back", false)
&& currentArgs.containsKey("account")) {
Bundle args = new Bundle();
args.putString("account", currentArgs.getString("account"));
if (getIntent().getBooleanExtra("fromNotification", false)) {
args.putString("tab", "notifications");
}
Fragment fragment=new HomeFragment();
fragment.setArguments(args);
showFragmentClearingBackStack(fragment);
}
}
// @Override
// public void onActivityResult(int requestCode, int resultCode, Intent data){
// if(requestCode==CAMERA_PIC_REQUEST_CODE && resultCode== Activity.RESULT_OK){
// E.post(new TakePictureRequestEvent());
// }
// }
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == CAMERA_PERMISSION_CODE && (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
E.post(new TakePictureRequestEvent());
} else {
Toast.makeText(this, R.string.permission_required, Toast.LENGTH_SHORT);
super.onBackPressed();
}
}
public Fragment getCurrentFragment() {
for (int i = fragmentContainers.size() - 1; i >= 0; i--) {
FrameLayout fl = fragmentContainers.get(i);
if (fl.getVisibility() == View.VISIBLE) {
return getFragmentManager().findFragmentById(fl.getId());
}
}
return null;
}
@Override
public void onProvideAssistContent(AssistContent assistContent) {
super.onProvideAssistContent(assistContent);
Fragment fragment = getCurrentFragment();
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
public void onProvideAssistContent(AssistContent outContent) {
super.onProvideAssistContent(outContent);
outContent.setWebUri(Uri.parse(DomainManager.getInstance().getCurrentDomain()));
}
}

View File

@@ -6,12 +6,11 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
import org.joinmastodon.android.api.requests.oauth.GetOauthToken;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.api.mastodon.requests.accounts.GetOwnAccount;
import org.joinmastodon.android.api.mastodon.requests.oauth.GetOauthToken;
import org.joinmastodon.android.api.mastodon.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Application;
import org.joinmastodon.android.model.Instance;
@@ -61,9 +60,6 @@ public class OAuthActivity extends Activity{
@Override
public void onSuccess(Token token){
new GetOwnAccount()
// in case the instance (looking at pixelfed) wants to redirect to a
// website, we need to pass a context so we can launch a browser
.setContext(OAuthActivity.this)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Account account){

View File

@@ -1,49 +0,0 @@
package org.joinmastodon.android;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
public class PanicResponderActivity extends Activity {
public static final String PANIC_TRIGGER_ACTION = "info.guardianproject.panic.action.TRIGGER";
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Intent intent = getIntent();
if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) {
AccountSessionManager.getInstance().getLoggedInAccounts().forEach(accountSession -> logOut(accountSession.getID()));
ExitActivity.exit(this);
}
finishAndRemoveTask();
}
private void logOut(String accountID){
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
new RevokeOauthToken(session.app.clientId, session.app.clientSecret, session.token.accessToken)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Object result){
onLoggedOut(accountID);
}
@Override
public void onError(ErrorResponse error){
onLoggedOut(accountID);
}
})
.exec(accountID);
}
private void onLoggedOut(String accountID){
AccountSessionManager.getInstance().removeAccount(accountID);
}
}

View File

@@ -1,7 +1,5 @@
package org.joinmastodon.android;
import static org.joinmastodon.android.GlobalUserPreferences.PrefixRepliesMode.ALWAYS;
import static org.joinmastodon.android.GlobalUserPreferences.PrefixRepliesMode.TO_OTHERS;
import static org.joinmastodon.android.GlobalUserPreferences.getPrefs;
import android.app.Notification;
@@ -14,34 +12,29 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.opengl.Visibility;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.api.requests.notifications.GetNotificationByID;
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.api.mastodon.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.api.mastodon.requests.notifications.GetNotificationByID;
import org.joinmastodon.android.api.mastodon.requests.statuses.CreateStatus;
import org.joinmastodon.android.api.mastodon.requests.statuses.SetStatusBookmarked;
import org.joinmastodon.android.api.mastodon.requests.statuses.SetStatusFavorited;
import org.joinmastodon.android.api.mastodon.requests.statuses.SetStatusReblogged;
import org.joinmastodon.android.api.mastodon.session.AccountSession;
import org.joinmastodon.android.api.mastodon.session.AccountSessionManager;
import org.joinmastodon.android.events.NotificationReceivedEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Mention;
import org.joinmastodon.android.model.NotificationAction;
import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.PushNotification;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
@@ -61,7 +54,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
private static final String ACTION_KEY_TEXT_REPLY = "ACTION_KEY_TEXT_REPLY";
private static final int SUMMARY_ID = 791;
private static int notificationId = 0;
private static int notificationId;
@Override
public void onReceive(Context context, Intent intent){
@@ -302,37 +295,17 @@ public class PushNotificationReceiver extends BroadcastReceiver{
}
CharSequence input = remoteInput.getCharSequence(ACTION_KEY_TEXT_REPLY);
// copied from ComposeFragment - TODO: generalize?
ArrayList<String> mentions=new ArrayList<>();
Status status = notification.status;
String ownID=AccountSessionManager.getInstance().getAccount(accountID).self.id;
if(!status.account.id.equals(ownID))
mentions.add('@'+status.account.acct);
for(Mention mention:status.mentions){
if(mention.id.equals(ownID))
continue;
String m='@'+mention.acct;
if(!mentions.contains(m))
mentions.add(m);
}
String initialText=mentions.isEmpty() ? "" : TextUtils.join(" ", mentions)+" ";
CreateStatus.Request req=new CreateStatus.Request();
req.status = initialText + input.toString();
req.language = preferences.postingDefaultLanguage;
req.visibility = preferences.postingDefaultVisibility;
req.status = input.toString() + "\n\n" + "@" + notification.status.account.acct;
req.language = notification.status.language;
req.visibility = (notification.status.visibility == StatusPrivacy.PUBLIC && GlobalUserPreferences.defaultToUnlistedReplies ? StatusPrivacy.UNLISTED : notification.status.visibility);
req.inReplyToId = notification.status.id;
if (!notification.status.spoilerText.isEmpty() &&
(GlobalUserPreferences.prefixReplies == ALWAYS
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(notification.status.account.id)))
&& !notification.status.spoilerText.startsWith("re: ")) {
if(!notification.status.spoilerText.isEmpty() && GlobalUserPreferences.prefixRepliesWithRe && !notification.status.spoilerText.startsWith("re: ")){
req.spoilerText = "re: " + notification.status.spoilerText;
}
new CreateStatus(req, UUID.randomUUID().toString()).setCallback(new Callback<>() {
@Override
public void onSuccess(Status status) {
new CreateStatus(req, UUID.randomUUID().toString()).exec(accountID);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification.Builder builder = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O ?
new Notification.Builder(context, accountID+"_"+notification.type) :
@@ -340,26 +313,9 @@ public class PushNotificationReceiver extends BroadcastReceiver{
.setPriority(Notification.PRIORITY_DEFAULT)
.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE);
notification.status = status;
Intent contentIntent=new Intent(context, MainActivity.class);
contentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
contentIntent.putExtra("fromNotification", true);
contentIntent.putExtra("accountID", accountID);
contentIntent.putExtra("notification", Parcels.wrap(notification));
Notification repliedNotification = builder.setSmallIcon(R.drawable.ic_ntf_logo)
.setContentTitle(context.getString(R.string.sk_notification_action_replied, notification.status.account.displayName))
.setContentText(status.getStrippedText())
.setCategory(Notification.CATEGORY_SOCIAL)
.setContentIntent(PendingIntent.getActivity(context, notificationId, contentIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
.setContentText(context.getString(R.string.mo_notification_action_replied, notification.status.account.getDisplayUsername()))
.build();
notificationManager.notify(accountID, notificationId, repliedNotification);
}
@Override
public void onError(ErrorResponse errorResponse) {
}
}).exec(accountID);
}
}

View File

@@ -11,10 +11,10 @@ import android.util.Log;
import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.api.adapter.ApiAdapter;
import org.joinmastodon.android.api.mastodon.requests.notifications.GetNotifications;
import org.joinmastodon.android.api.mastodon.session.AccountSession;
import org.joinmastodon.android.api.mastodon.session.AccountSessionManager;
import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Instance;
@@ -36,7 +36,7 @@ import me.grishka.appkit.utils.WorkerThread;
public class CacheController{
private static final String TAG="CacheController";
private static final int DB_VERSION=4;
private static final int DB_VERSION=3;
private static final WorkerThread databaseThread=new WorkerThread("databaseThread");
private static final Handler uiHandler=new Handler(Looper.getMainLooper());
@@ -61,7 +61,7 @@ public class CacheController{
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
if(!forceReload){
SQLiteDatabase db=getOrOpenDatabase();
try(Cursor cursor=db.query("home_timeline", new String[]{"json", "flags"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`time` DESC", count+"")){
try(Cursor cursor=db.query("home_timeline", new String[]{"json", "flags"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`id` DESC", count+"")){
if(cursor.getCount()==count){
ArrayList<Status> result=new ArrayList<>();
cursor.moveToFirst();
@@ -73,8 +73,10 @@ public class CacheController{
int flags=cursor.getInt(1);
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0);
newMaxID=status.id;
if (!new StatusFilterPredicate(filters, Filter.FilterContext.HOME).test(status))
for(Filter filter:filters){
if(filter.matches(status))
continue outer;
}
result.add(status);
}while(cursor.moveToNext());
String _newMaxID=newMaxID;
@@ -85,11 +87,12 @@ public class CacheController{
Log.w(TAG, "getHomeTimeline: corrupted status object in database", x);
}
}
new GetHomeTimeline(maxID, null, count, null)
ApiAdapter apiAdapter = new ApiAdapter(ApiAdapter.ServerType.MASTODON);
apiAdapter.getHomeTimeline(maxID, null, count, null)
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Status> result){
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(new StatusFilterPredicate(filters, Filter.FilterContext.HOME)).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(new StatusFilterPredicate(filters)).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
putHomeTimeline(result, maxID==null);
}
@@ -112,7 +115,7 @@ public class CacheController{
runOnDbThread((db)->{
if(clear)
db.delete("home_timeline", null, null);
ContentValues values=new ContentValues(4);
ContentValues values=new ContentValues(3);
for(Status s:posts){
values.put("id", s.id);
values.put("json", MastodonAPIController.gson.toJson(s));
@@ -120,7 +123,6 @@ public class CacheController{
if(s.hasGapAfter)
flags|=POST_FLAG_GAP_AFTER;
values.put("flags", flags);
values.put("time", s.createdAt.getEpochSecond());
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
}
});
@@ -135,7 +137,7 @@ public class CacheController{
if(!forceReload){
SQLiteDatabase db=getOrOpenDatabase();
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
try(Cursor cursor=db.query(table, new String[]{"json"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`time` DESC", count+"")){
try(Cursor cursor=db.query(table, new String[]{"json"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`id` DESC", count+"")){
if(cursor.getCount()==count){
ArrayList<Notification> result=new ArrayList<>();
cursor.moveToFirst();
@@ -146,9 +148,11 @@ public class CacheController{
ntf.postprocess();
newMaxID=ntf.id;
if(ntf.status!=null){
if (!new StatusFilterPredicate(filters, Filter.FilterContext.NOTIFICATIONS).test(ntf.status))
for(Filter filter:filters){
if(filter.matches(ntf.status))
continue outer;
}
}
result.add(ntf);
}while(cursor.moveToNext());
String _newMaxID=newMaxID;
@@ -160,13 +164,17 @@ public class CacheController{
}
}
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(accountSession.domain);
new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), instance.isAkkoma())
new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), instance.pleroma != null)
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Notification> result){
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(ntf->{
if(ntf.status!=null){
return new StatusFilterPredicate(filters, Filter.FilterContext.NOTIFICATIONS).test(ntf.status);
for(Filter filter:filters){
if(filter.matches(ntf.status)){
return false;
}
}
}
return true;
}).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
@@ -193,7 +201,7 @@ public class CacheController{
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
if(clear)
db.delete(table, null, null);
ContentValues values=new ContentValues(4);
ContentValues values=new ContentValues(3);
for(Notification n:notifications){
if(n.type==null){
continue;
@@ -201,7 +209,6 @@ public class CacheController{
values.put("id", n.id);
values.put("json", MastodonAPIController.gson.toJson(n));
values.put("type", n.type.ordinal());
values.put("time", n.createdAt.getEpochSecond());
db.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_REPLACE);
}
});
@@ -298,24 +305,21 @@ public class CacheController{
CREATE TABLE `home_timeline` (
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
`json` TEXT NOT NULL,
`flags` INTEGER NOT NULL DEFAULT 0,
`time` INTEGER NOT NULL
`flags` INTEGER NOT NULL DEFAULT 0
)""");
db.execSQL("""
CREATE TABLE `notifications_all` (
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
`json` TEXT NOT NULL,
`flags` INTEGER NOT NULL DEFAULT 0,
`type` INTEGER NOT NULL,
`time` INTEGER NOT NULL
`type` INTEGER NOT NULL
)""");
db.execSQL("""
CREATE TABLE `notifications_mentions` (
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
`json` TEXT NOT NULL,
`flags` INTEGER NOT NULL DEFAULT 0,
`type` INTEGER NOT NULL,
`time` INTEGER NOT NULL
`type` INTEGER NOT NULL
)""");
createRecentSearchesTable(db);
createPostsNotificationsTable(db);
@@ -323,16 +327,12 @@ public class CacheController{
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
if(oldVersion<2){
if(oldVersion==1){
createRecentSearchesTable(db);
}
if(oldVersion<3){
// MEGALODON-SPECIFIC
if(oldVersion==2){
createPostsNotificationsTable(db);
}
if(oldVersion<4){
addTimeColumns(db);
}
}
private void createRecentSearchesTable(SQLiteDatabase db){
@@ -350,21 +350,9 @@ public class CacheController{
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
`json` TEXT NOT NULL,
`flags` INTEGER NOT NULL DEFAULT 0,
`type` INTEGER NOT NULL,
`time` INTEGER NOT NULL
`type` INTEGER NOT NULL
)""");
}
private void addTimeColumns(SQLiteDatabase db){
db.execSQL("DELETE FROM `home_timeline`");
db.execSQL("DELETE FROM `notifications_all`");
db.execSQL("DELETE FROM `notifications_mentions`");
db.execSQL("DELETE FROM `notifications_posts`");
db.execSQL("ALTER TABLE `home_timeline` ADD `time` INTEGER NOT NULL DEFAULT 0");
db.execSQL("ALTER TABLE `notifications_all` ADD `time` INTEGER NOT NULL DEFAULT 0");
db.execSQL("ALTER TABLE `notifications_mentions` ADD `time` INTEGER NOT NULL DEFAULT 0");
db.execSQL("ALTER TABLE `notifications_posts` ADD `time` INTEGER NOT NULL DEFAULT 0");
}
}
@FunctionalInterface

View File

@@ -15,9 +15,8 @@ import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.api.gson.IsoInstantTypeAdapter;
import org.joinmastodon.android.api.gson.IsoLocalDateTypeAdapter;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.mastodon.session.AccountSession;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.io.BufferedReader;
import java.io.IOException;
@@ -29,7 +28,6 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -53,9 +51,7 @@ public class MastodonAPIController{
.registerTypeAdapter(Status.class, new Status.StatusDeserializer())
.create();
private static WorkerThread thread=new WorkerThread("MastodonAPIController");
private static OkHttpClient httpClient=new OkHttpClient.Builder()
.readTimeout(5, TimeUnit.MINUTES)
.build();
private static OkHttpClient httpClient=new OkHttpClient.Builder().build();
private AccountSession session;
private static List<String> badDomains = new ArrayList<>();
@@ -64,7 +60,7 @@ public class MastodonAPIController{
thread.start();
try {
final BufferedReader reader = new BufferedReader(new InputStreamReader(
MastodonApp.context.getAssets().open("blocks.txt")
MastodonApp.context.getAssets().open("blocks.tsv")
));
String line;
while ((line = reader.readLine()) != null) {
@@ -95,7 +91,7 @@ public class MastodonAPIController{
Request.Builder builder=new Request.Builder()
.url(req.getURL().toString())
.method(req.getMethod(), req.getRequestBody())
.header("User-Agent", "MoshidonAndroid/"+BuildConfig.VERSION_NAME);
.header("User-Agent", "MastodonAndroid/"+BuildConfig.VERSION_NAME);
String token=null;
if(session!=null)
@@ -162,11 +158,6 @@ public class MastodonAPIController{
respObj=gson.fromJson(reader, req.respClass);
}
}catch(JsonIOException|JsonSyntaxException x){
if (req.context != null && response.body().contentType().subtype().equals("html")) {
UiUtils.launchWebBrowser(req.context, response.request().url().toString());
req.cancel();
return;
}
if(BuildConfig.DEBUG)
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error parsing or reading body", x);
req.onError(x.getLocalizedMessage(), response.code(), x);

View File

@@ -2,7 +2,6 @@ package org.joinmastodon.android.api;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import android.util.Pair;
@@ -10,8 +9,8 @@ import android.util.Pair;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.api.mastodon.session.AccountSession;
import org.joinmastodon.android.api.mastodon.session.AccountSessionManager;
import org.joinmastodon.android.model.BaseModel;
import org.joinmastodon.android.model.Token;
@@ -21,11 +20,9 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import me.grishka.appkit.api.APIRequest;
import me.grishka.appkit.api.Callback;
@@ -47,11 +44,10 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
TypeToken<T> respTypeToken;
Call okhttpCall;
Token token;
boolean canceled, isRemote;
boolean canceled;
Map<String, String> headers;
private ProgressDialog progressDialog;
protected boolean removeUnsupportedItems;
@Nullable Context context;
public MastodonAPIRequest(HttpMethod method, String path, Class<T> respClass){
this.path=path;
@@ -105,21 +101,6 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
return this;
}
public MastodonAPIRequest<T> execRemote(String domain) {
return execRemote(domain, null);
}
public MastodonAPIRequest<T> execRemote(String domain, @Nullable AccountSession remoteSession) {
this.isRemote = true;
return Optional.ofNullable(remoteSession)
.or(() -> AccountSessionManager.getInstance().getLoggedInAccounts().stream()
.filter(acc -> acc.domain.equals(domain))
.findAny())
.map(AccountSession::getID)
.map(this::exec)
.orElse(this.execNoAuth(domain));
}
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable){
return wrapProgress(activity, message, cancelable, null);
}
@@ -183,20 +164,9 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
return this;
}
public MastodonAPIRequest<T> setContext(Context context) {
this.context = context;
return this;
}
@Nullable
public Context getContext() {
return context;
}
@CallSuper
public void validateAndPostprocessResponse(T respObj, Response httpResponse) throws IOException{
if(respObj instanceof BaseModel){
((BaseModel) respObj).isRemote = isRemote;
((BaseModel) respObj).postprocess();
}else if(respObj instanceof List){
if(removeUnsupportedItems){
@@ -205,7 +175,6 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
Object item=itr.next();
if(item instanceof BaseModel){
try{
((BaseModel) item).isRemote = isRemote;
((BaseModel) item).postprocess();
}catch(ObjectValidationException x){
Log.w(TAG, "Removing invalid object from list", x);
@@ -213,24 +182,19 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
}
}
}
// no idea why we're post-processing twice, but well, as long
// as upstream does it like this, i don't wanna break anything
for(Object item:((List<?>) respObj)){
if(item instanceof BaseModel){
((BaseModel) item).isRemote = isRemote;
((BaseModel) item).postprocess();
}
}
}else{
for(Object item:((List<?>) respObj)){
if(item instanceof BaseModel) {
((BaseModel) item).isRemote = isRemote;
if(item instanceof BaseModel)
((BaseModel) item).postprocess();
}
}
}
}
}
void onError(ErrorResponse err){
if(!canceled)

View File

@@ -12,10 +12,10 @@ import android.util.Log;
import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.api.requests.notifications.RegisterForPushNotifications;
import org.joinmastodon.android.api.requests.notifications.UpdatePushSettings;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.api.mastodon.requests.notifications.RegisterForPushNotifications;
import org.joinmastodon.android.api.mastodon.requests.notifications.UpdatePushSettings;
import org.joinmastodon.android.api.mastodon.session.AccountSession;
import org.joinmastodon.android.api.mastodon.session.AccountSessionManager;
import org.joinmastodon.android.model.PushNotification;
import org.joinmastodon.android.model.PushSubscription;
@@ -45,7 +45,6 @@ import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyAgreement;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

View File

@@ -4,9 +4,9 @@ import android.os.Looper;
import org.joinmastodon.android.E;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
import org.joinmastodon.android.api.mastodon.requests.statuses.SetStatusBookmarked;
import org.joinmastodon.android.api.mastodon.requests.statuses.SetStatusFavorited;
import org.joinmastodon.android.api.mastodon.requests.statuses.SetStatusReblogged;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
@@ -46,7 +46,7 @@ public class StatusInteractionController{
@Override
public void onSuccess(Status result){
runningFavoriteRequests.remove(status.id);
result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1));
result.favouritesCount = Math.max(0, status.favouritesCount) + (favorited ? 1 : -1);
cb.accept(result);
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
}
@@ -80,7 +80,7 @@ public class StatusInteractionController{
public void onSuccess(Status reblog){
Status result = reblog.getContentStatus();
runningReblogRequests.remove(status.id);
result.reblogsCount = Math.max(0, status.reblogsCount + (reblogged ? 1 : -1));
result.reblogsCount = Math.max(0, status.reblogsCount) + (reblogged ? 1 : -1);
cb.accept(result);
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
}

View File

@@ -0,0 +1,47 @@
package org.joinmastodon.android.api.adapter;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.mastodon.requests.statuses.GetStatusByID;
import org.joinmastodon.android.api.mastodon.requests.timelines.GetHomeTimeline;
import org.joinmastodon.android.model.Status;
import java.util.List;
public class ApiAdapter {
public final ServerType serverType;
public ApiAdapter(ServerType serverType){
this.serverType = serverType;
}
public MastodonAPIRequest<?> getPostById(String id){
switch (serverType){
case MASTODON -> {
return new GetStatusByID(id);
}
case MISSKEY -> {
return null;
}
}
return null;
}
public MastodonAPIRequest<List<Status>> getHomeTimeline(String maxID, String minID, int limit, String sinceID){
switch (serverType){
case MASTODON -> {
return new GetHomeTimeline(maxID, minID, limit, sinceID);
}
case MISSKEY -> {
return null;
}
}
return null;
}
public enum ServerType {
MASTODON,
MISSKEY,
}
}

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests;
package org.joinmastodon.android.api.mastodon.requests;
import android.net.Uri;
import android.text.TextUtils;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Relationship;

View File

@@ -1,13 +1,9 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Account;
public class GetAccountByHandle extends MastodonAPIRequest<Account>{
/**
* note that this method usually only returns a result if the instance already knows about an
* account - so it makes sense for looking up local users, search might be preferred otherwise
*/
public GetAccountByHandle(String acct){
super(HttpMethod.GET, "/accounts/lookup", Account.class);
addQueryParameter("acct", acct);

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Account;

View File

@@ -1,8 +1,8 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.api.mastodon.requests.HeaderPaginationRequest;
import org.joinmastodon.android.model.Account;
public class GetAccountFollowers extends HeaderPaginationRequest<Account>{

View File

@@ -1,8 +1,8 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.api.mastodon.requests.HeaderPaginationRequest;
import org.joinmastodon.android.model.Account;
public class GetAccountFollowing extends HeaderPaginationRequest<Account>{

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import com.google.gson.reflect.TypeToken;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import com.google.gson.reflect.TypeToken;

View File

@@ -1,8 +1,8 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.api.mastodon.requests.HeaderPaginationRequest;
import org.joinmastodon.android.model.Account;
public class GetFollowRequests extends HeaderPaginationRequest<Account>{

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import com.google.gson.reflect.TypeToken;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Account;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Preferences;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import com.google.gson.reflect.TypeToken;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Token;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Relationship;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Relationship;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Relationship;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Relationship;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Relationship;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.mastodon.requests.accounts;
import android.net.Uri;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.announcements;
package org.joinmastodon.android.api.mastodon.requests.announcements;
import org.joinmastodon.android.api.MastodonAPIRequest;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.announcements;
package org.joinmastodon.android.api.mastodon.requests.announcements;
import com.google.gson.reflect.TypeToken;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.catalog;
package org.joinmastodon.android.api.mastodon.requests.catalog;
import android.net.Uri;
import android.text.TextUtils;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.catalog;
package org.joinmastodon.android.api.mastodon.requests.catalog;
import android.net.Uri;
import android.text.TextUtils;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.instance;
package org.joinmastodon.android.api.mastodon.requests.instance;
import com.google.gson.reflect.TypeToken;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.instance;
package org.joinmastodon.android.api.mastodon.requests.instance;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Instance;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.lists;
package org.joinmastodon.android.api.mastodon.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest;
import java.util.List;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.lists;
package org.joinmastodon.android.api.mastodon.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest;
import java.util.List;

View File

@@ -1,21 +1,19 @@
package org.joinmastodon.android.api.requests.lists;
package org.joinmastodon.android.api.mastodon.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline;
public class CreateList extends MastodonAPIRequest<ListTimeline> {
public CreateList(String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) {
public CreateList(String title, ListTimeline.RepliesPolicy repliesPolicy) {
super(HttpMethod.POST, "/lists", ListTimeline.class);
Request req = new Request();
req.title = title;
req.exclusive = exclusive;
req.repliesPolicy = repliesPolicy;
setRequestBody(req);
}
public static class Request {
public String title;
public boolean exclusive;
public ListTimeline.RepliesPolicy repliesPolicy;
}
}

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.lists;
package org.joinmastodon.android.api.mastodon.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.lists;
package org.joinmastodon.android.api.mastodon.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest;
import java.util.List;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.lists;
package org.joinmastodon.android.api.mastodon.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.lists;
package org.joinmastodon.android.api.mastodon.requests.lists;
import com.google.gson.reflect.TypeToken;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.lists;
package org.joinmastodon.android.api.mastodon.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest;
import java.util.List;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.lists;
package org.joinmastodon.android.api.mastodon.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest;
import java.util.List;

View File

@@ -1,14 +1,13 @@
package org.joinmastodon.android.api.requests.lists;
package org.joinmastodon.android.api.mastodon.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline;
public class UpdateList extends MastodonAPIRequest<ListTimeline> {
public UpdateList(String id, String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) {
public UpdateList(String id, String title, ListTimeline.RepliesPolicy repliesPolicy) {
super(HttpMethod.PUT, "/lists/" + id, ListTimeline.class);
CreateList.Request req = new CreateList.Request();
req.title = title;
req.exclusive = exclusive;
req.repliesPolicy = repliesPolicy;
setRequestBody(req);
}

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.markers;
package org.joinmastodon.android.api.mastodon.requests.markers;
import org.joinmastodon.android.api.ApiUtils;
import org.joinmastodon.android.api.MastodonAPIRequest;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.markers;
package org.joinmastodon.android.api.mastodon.requests.markers;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.gson.JsonObjectBuilder;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.notifications;
package org.joinmastodon.android.api.mastodon.requests.notifications;
import com.google.gson.reflect.TypeToken;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.notifications;
package org.joinmastodon.android.api.mastodon.requests.notifications;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Notification;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.notifications;
package org.joinmastodon.android.api.mastodon.requests.notifications;
import com.google.gson.reflect.TypeToken;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.notifications;
package org.joinmastodon.android.api.mastodon.requests.notifications;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.PushSubscription;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.notifications;
package org.joinmastodon.android.api.mastodon.requests.notifications;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.PushSubscription;

View File

@@ -1,7 +1,7 @@
package org.joinmastodon.android.api.requests.oauth;
package org.joinmastodon.android.api.mastodon.requests.oauth;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.api.mastodon.session.AccountSessionManager;
import org.joinmastodon.android.model.Application;
public class CreateOAuthApp extends MastodonAPIRequest<Application>{

View File

@@ -1,9 +1,9 @@
package org.joinmastodon.android.api.requests.oauth;
package org.joinmastodon.android.api.mastodon.requests.oauth;
import com.google.gson.annotations.SerializedName;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.api.mastodon.session.AccountSessionManager;
import org.joinmastodon.android.model.Token;
public class GetOauthToken extends MastodonAPIRequest<Token>{

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.oauth;
package org.joinmastodon.android.api.mastodon.requests.oauth;
import org.joinmastodon.android.api.MastodonAPIRequest;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.polls;
package org.joinmastodon.android.api.mastodon.requests.polls;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Poll;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.reports;
package org.joinmastodon.android.api.mastodon.requests.reports;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ReportReason;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.search;
package org.joinmastodon.android.api.mastodon.requests.search;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.SearchResults;

View File

@@ -1,7 +1,6 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ContentType;
import org.joinmastodon.android.model.ScheduledStatus;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
@@ -47,7 +46,6 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
public String language;
public String quoteId;
public ContentType contentType;
public static class Poll{
public ArrayList<String> options=new ArrayList<>();

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Attachment;

View File

@@ -1,8 +1,8 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.api.mastodon.requests.HeaderPaginationRequest;
import org.joinmastodon.android.model.Status;
public class GetBookmarkedStatuses extends HeaderPaginationRequest<Status>{

View File

@@ -1,8 +1,8 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.api.mastodon.requests.HeaderPaginationRequest;
import org.joinmastodon.android.model.Status;
public class GetFavoritedStatuses extends HeaderPaginationRequest<Status>{

View File

@@ -1,8 +1,8 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.api.mastodon.requests.HeaderPaginationRequest;
import org.joinmastodon.android.model.ScheduledStatus;
public class GetScheduledStatuses extends HeaderPaginationRequest<ScheduledStatus>{

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.StatusContext;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import com.google.gson.reflect.TypeToken;

View File

@@ -1,8 +1,8 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.api.mastodon.requests.HeaderPaginationRequest;
import org.joinmastodon.android.model.Account;
public class GetStatusFavorites extends HeaderPaginationRequest<Account>{

View File

@@ -1,8 +1,8 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.api.mastodon.requests.HeaderPaginationRequest;
import org.joinmastodon.android.model.Account;
public class GetStatusReblogs extends HeaderPaginationRequest<Account>{

View File

@@ -1,23 +1,18 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import org.joinmastodon.android.api.AllFieldsAreRequired;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.model.BaseModel;
import org.joinmastodon.android.model.ContentType;
public class GetStatusSourceText extends MastodonAPIRequest<GetStatusSourceText.Response>{
public GetStatusSourceText(String id){
super(HttpMethod.GET, "/statuses/"+id+"/source", Response.class);
}
@AllFieldsAreRequired
public static class Response extends BaseModel{
@RequiredField
public String id;
@RequiredField
public String text;
@RequiredField
public String spoilerText;
public ContentType contentType;
}
}

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.TranslatedStatus;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Attachment;

View File

@@ -1,11 +1,8 @@
package org.joinmastodon.android.api.requests.statuses;
package org.joinmastodon.android.api.mastodon.requests.statuses;
import android.database.Cursor;
import android.net.Uri;
import android.provider.OpenableColumns;
import android.text.TextUtils;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.api.ContentUriRequestBody;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.ProgressListener;

Some files were not shown because too many files have changed in this diff Show More