Compare commits
92 Commits
v1.2.3+for
...
v1.2.3+for
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a354ea80ab | ||
|
|
9f65b8112a | ||
|
|
6ac5d957fe | ||
|
|
4258c55b88 | ||
|
|
969f29e2e9 | ||
|
|
68921d0f0b | ||
|
|
c4ac4ee173 | ||
|
|
659b4e2fcd | ||
|
|
24e5bda8d3 | ||
|
|
02b1ad8d7a | ||
|
|
47eeb01b75 | ||
|
|
4288814138 | ||
|
|
ac4458e106 | ||
|
|
3d24b2de10 | ||
|
|
ed994b23e9 | ||
|
|
8c4678aba5 | ||
|
|
3d5fb2dfea | ||
|
|
ef6238b593 | ||
|
|
bc9bec3d66 | ||
|
|
d16e199dd1 | ||
|
|
a9c2df2e83 | ||
|
|
4673a4b9f7 | ||
|
|
d4a5286895 | ||
|
|
1b4579346b | ||
|
|
0665b8dd3b | ||
|
|
853124e2ce | ||
|
|
5dcd6e5a0d | ||
|
|
6f25c8be0f | ||
|
|
1db4b1319e | ||
|
|
76a97fcb47 | ||
|
|
4baaa39f35 | ||
|
|
52f025ae5a | ||
|
|
14b805e883 | ||
|
|
433a7b15fe | ||
|
|
6c8cbbc34a | ||
|
|
d4fbb298c1 | ||
|
|
2aeb5f03d6 | ||
|
|
6522403c37 | ||
|
|
f090ca7f75 | ||
|
|
2f02a238df | ||
|
|
0d5fa97800 | ||
|
|
b102deaee1 | ||
|
|
968b2ee460 | ||
|
|
890340de94 | ||
|
|
4ca1a7b29e | ||
|
|
5432f2590c | ||
|
|
60ccf5cf0a | ||
|
|
bc717f5b10 | ||
|
|
486eef21dd | ||
|
|
44a4d02815 | ||
|
|
336a8194bd | ||
|
|
7859f4cd05 | ||
|
|
37622ba9ce | ||
|
|
7a6af89375 | ||
|
|
056bfaacfe | ||
|
|
6684311ec5 | ||
|
|
11943571ad | ||
|
|
f696fcd412 | ||
|
|
2919e109ca | ||
|
|
995f478708 | ||
|
|
fb8764bcd7 | ||
|
|
d7f73e02c5 | ||
|
|
e897b3af57 | ||
|
|
e04fd8a004 | ||
|
|
ada70ae1b5 | ||
|
|
5fdec0900e | ||
|
|
56a93288c4 | ||
|
|
02e3421f98 | ||
|
|
fdbf331432 | ||
|
|
aed86ac6f0 | ||
|
|
3a13d4d6c0 | ||
|
|
f5336564d0 | ||
|
|
1ce49c68fe | ||
|
|
d37e880993 | ||
|
|
6fdb81a01f | ||
|
|
f9d6827572 | ||
|
|
10bf72b9ff | ||
|
|
800f929a15 | ||
|
|
bfcff1e19f | ||
|
|
f373e7df3e | ||
|
|
3985de5b14 | ||
|
|
e175a721d4 | ||
|
|
d9784ebc31 | ||
|
|
f241092277 | ||
|
|
0702703d78 | ||
|
|
2c4504bad3 | ||
|
|
07ca5a8b77 | ||
|
|
798a43906f | ||
|
|
41cb0f2e09 | ||
|
|
e12c0fb81f | ||
|
|
6926a212f4 | ||
|
|
936f39161b |
@@ -15,8 +15,8 @@ android {
|
|||||||
applicationId "org.joinmastodon.android.sk"
|
applicationId "org.joinmastodon.android.sk"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 85
|
versionCode 90
|
||||||
versionName "1.2.3+fork.85"
|
versionName "1.2.3+fork.90"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
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']
|
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']
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ dependencies {
|
|||||||
implementation 'me.grishka.litex:dynamicanimation:1.1.0-alpha03'
|
implementation 'me.grishka.litex:dynamicanimation:1.1.0-alpha03'
|
||||||
implementation 'me.grishka.litex:viewpager:1.0.0'
|
implementation 'me.grishka.litex:viewpager:1.0.0'
|
||||||
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
||||||
implementation 'me.grishka.appkit:appkit:1.2.7'
|
implementation 'me.grishka.appkit:appkit:1.2.8'
|
||||||
implementation 'com.google.code.gson:gson:2.9.0'
|
implementation 'com.google.code.gson:gson:2.9.0'
|
||||||
implementation 'org.jsoup:jsoup:1.14.3'
|
implementation 'org.jsoup:jsoup:1.14.3'
|
||||||
implementation 'com.squareup:otto:1.3.8'
|
implementation 'com.squareup:otto:1.3.8'
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
|
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.model.StatusContext;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ThreadFragmentTest {
|
||||||
|
|
||||||
|
private Status fakeStatus(String id, String inReplyTo) {
|
||||||
|
Status status = Status.ofFake(id, null, null);
|
||||||
|
status.inReplyToId = inReplyTo;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ThreadFragment.NeighborAncestryInfo fakeInfo(Status s, Status d, Status a) {
|
||||||
|
return new ThreadFragment.NeighborAncestryInfo(s, d, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mapNeighborhoodAncestry() {
|
||||||
|
StatusContext context = new StatusContext();
|
||||||
|
context.ancestors = List.of(
|
||||||
|
fakeStatus("oldest ancestor", null),
|
||||||
|
fakeStatus("younger ancestor", "oldest ancestor")
|
||||||
|
);
|
||||||
|
Status mainStatus = fakeStatus("main status", "younger ancestor");
|
||||||
|
context.descendants = List.of(
|
||||||
|
fakeStatus("first reply", "main status"),
|
||||||
|
fakeStatus("reply to first reply", "first reply"),
|
||||||
|
fakeStatus("third level reply", "reply to first reply"),
|
||||||
|
fakeStatus("another reply", "main status")
|
||||||
|
);
|
||||||
|
|
||||||
|
List<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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package org.joinmastodon.android.ui.utils;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class UiUtilsTest {
|
||||||
|
@BeforeClass
|
||||||
|
public static void createDummySession() {
|
||||||
|
Instance dummyInstance = new Instance();
|
||||||
|
dummyInstance.uri = "test.tld";
|
||||||
|
Account dummyAccount = new Account();
|
||||||
|
dummyAccount.id = "123456";
|
||||||
|
AccountSessionManager.getInstance().addAccount(dummyInstance, null, dummyAccount, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void cleanUp() {
|
||||||
|
AccountSessionManager.getInstance().removeAccount("test.tld_123456");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseFediverseHandle() {
|
||||||
|
assertEquals(
|
||||||
|
Optional.of(Pair.create("megalodon", Optional.of("floss.social"))),
|
||||||
|
UiUtils.parseFediverseHandle("megalodon@floss.social")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
Optional.of(Pair.create("megalodon", Optional.of("floss.social"))),
|
||||||
|
UiUtils.parseFediverseHandle("@megalodon@floss.social")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
Optional.of(Pair.create("megalodon", Optional.empty())),
|
||||||
|
UiUtils.parseFediverseHandle("@megalodon")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
Optional.of(Pair.create("megalodon", Optional.of("floss.social"))),
|
||||||
|
UiUtils.parseFediverseHandle("mailto:megalodon@floss.social")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
Optional.empty(),
|
||||||
|
UiUtils.parseFediverseHandle("megalodon")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
Optional.empty(),
|
||||||
|
UiUtils.parseFediverseHandle("this is not a fedi handle")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
Optional.empty(),
|
||||||
|
UiUtils.parseFediverseHandle("not@a-domain")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void acctMatches() {
|
||||||
|
assertTrue("local account, domain not specified", UiUtils.acctMatches(
|
||||||
|
"test.tld_123456",
|
||||||
|
"someone",
|
||||||
|
"someone",
|
||||||
|
null
|
||||||
|
));
|
||||||
|
|
||||||
|
assertTrue("domain not specified", UiUtils.acctMatches(
|
||||||
|
"test.tld_123456",
|
||||||
|
"someone@somewhere.social",
|
||||||
|
"someone",
|
||||||
|
null
|
||||||
|
));
|
||||||
|
|
||||||
|
assertTrue("local account, domain specified, different casing", UiUtils.acctMatches(
|
||||||
|
"test.tld_123456",
|
||||||
|
"SomeOne",
|
||||||
|
"someone",
|
||||||
|
"Test.TLD"
|
||||||
|
));
|
||||||
|
|
||||||
|
assertFalse("username doesn't match", UiUtils.acctMatches(
|
||||||
|
"test.tld_123456",
|
||||||
|
"someone-else@somewhere.social",
|
||||||
|
"someone",
|
||||||
|
"somewhere.social"
|
||||||
|
));
|
||||||
|
|
||||||
|
assertFalse("domain doesn't match", UiUtils.acctMatches(
|
||||||
|
"test.tld_123456",
|
||||||
|
"someone@somewhere.social",
|
||||||
|
"someone",
|
||||||
|
"somewhere.else"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import android.content.Intent;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.Pair;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
@@ -18,6 +19,8 @@ import org.jsoup.internal.StringUtil;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import me.grishka.appkit.FragmentStackActivity;
|
import me.grishka.appkit.FragmentStackActivity;
|
||||||
@@ -29,20 +32,24 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
|
|
||||||
String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
Optional<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT));
|
||||||
boolean isMastodonURL = UiUtils.looksLikeMastodonUrl(text);
|
Optional<Pair<String, Optional<String>>> fediHandle = text.flatMap(UiUtils::parseFediverseHandle);
|
||||||
|
boolean isFediUrl = text.map(UiUtils::looksLikeFediverseUrl).orElse(false);
|
||||||
|
boolean isOpenable = isFediUrl || fediHandle.isPresent();
|
||||||
|
|
||||||
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts();
|
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();
|
Toast.makeText(this, R.string.err_not_logged_in, Toast.LENGTH_SHORT).show();
|
||||||
finish();
|
finish();
|
||||||
}else if(sessions.size()==1 && !isMastodonURL){
|
} else if (isOpenable || sessions.size() > 1) {
|
||||||
openComposeFragment(sessions.get(0).getID());
|
AccountSwitcherSheet sheet = new AccountSwitcherSheet(this, null, true, isOpenable);
|
||||||
}else{
|
if (isOpenable) sheet.setOnClick((accountId, open) -> {
|
||||||
new AccountSwitcherSheet(this, null, true, isMastodonURL, (accountId, open) -> {
|
if (open && text.isPresent()) {
|
||||||
if (open) {
|
BiConsumer<Class<? extends Fragment>, Bundle> callback = (clazz, args) -> {
|
||||||
UiUtils.lookupURL(this, accountId, text, false, (clazz, args) -> {
|
|
||||||
if (clazz == null) {
|
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();
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -51,11 +58,16 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
|||||||
intent.putExtras(args);
|
intent.putExtras(args);
|
||||||
finish();
|
finish();
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
});
|
};
|
||||||
|
if (isFediUrl) UiUtils.lookupURL(this, accountId, text.get(), false, callback);
|
||||||
|
else UiUtils.lookupAccountHandle(this, accountId, fediHandle.get(), callback);
|
||||||
} else {
|
} else {
|
||||||
openComposeFragment(accountId);
|
openComposeFragment(accountId);
|
||||||
}
|
}
|
||||||
}).show();
|
});
|
||||||
|
sheet.show();
|
||||||
|
} else if (sessions.size() == 1) {
|
||||||
|
openComposeFragment(sessions.get(0).getID());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ public class GlobalUserPreferences{
|
|||||||
public static boolean replyLineAboveHeader;
|
public static boolean replyLineAboveHeader;
|
||||||
public static boolean compactReblogReplyLine;
|
public static boolean compactReblogReplyLine;
|
||||||
public static boolean confirmBeforeReblog;
|
public static boolean confirmBeforeReblog;
|
||||||
|
public static boolean allowRemoteLoading;
|
||||||
public static String publishButtonText;
|
public static String publishButtonText;
|
||||||
public static ThemePreference theme;
|
public static ThemePreference theme;
|
||||||
public static ColorPreference color;
|
public static ColorPreference color;
|
||||||
@@ -127,6 +128,7 @@ public class GlobalUserPreferences{
|
|||||||
replyVisibility=prefs.getString("replyVisibility", null);
|
replyVisibility=prefs.getString("replyVisibility", null);
|
||||||
accountsWithContentTypesEnabled=prefs.getStringSet("accountsWithContentTypesEnabled", new HashSet<>());
|
accountsWithContentTypesEnabled=prefs.getStringSet("accountsWithContentTypesEnabled", new HashSet<>());
|
||||||
accountsDefaultContentTypes=fromJson(prefs.getString("accountsDefaultContentTypes", null), accountsDefaultContentTypesType, new HashMap<>());
|
accountsDefaultContentTypes=fromJson(prefs.getString("accountsDefaultContentTypes", null), accountsDefaultContentTypesType, new HashMap<>());
|
||||||
|
allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PINK.name()));
|
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PINK.name()));
|
||||||
@@ -176,6 +178,7 @@ public class GlobalUserPreferences{
|
|||||||
.putString("replyVisibility", replyVisibility)
|
.putString("replyVisibility", replyVisibility)
|
||||||
.putStringSet("accountsWithContentTypesEnabled", accountsWithContentTypesEnabled)
|
.putStringSet("accountsWithContentTypesEnabled", accountsWithContentTypesEnabled)
|
||||||
.putString("accountsDefaultContentTypes", gson.toJson(accountsDefaultContentTypes))
|
.putString("accountsDefaultContentTypes", gson.toJson(accountsDefaultContentTypes))
|
||||||
|
.putBoolean("allowRemoteLoading", allowRemoteLoading)
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import android.content.pm.PackageManager;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.ObjectValidationException;
|
import org.joinmastodon.android.api.ObjectValidationException;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
@@ -28,8 +30,6 @@ import androidx.annotation.Nullable;
|
|||||||
import me.grishka.appkit.FragmentStackActivity;
|
import me.grishka.appkit.FragmentStackActivity;
|
||||||
|
|
||||||
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
|
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
|
||||||
private Fragment currentFragment;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState){
|
protected void onCreate(@Nullable Bundle savedInstanceState){
|
||||||
UiUtils.setUserPreferredTheme(this);
|
UiUtils.setUserPreferredTheme(this);
|
||||||
@@ -117,25 +117,13 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showFragmentForNotification(Notification notification, String accountID){
|
private void showFragmentForNotification(Notification notification, String accountID){
|
||||||
Fragment fragment;
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putBoolean("_can_go_back", true);
|
|
||||||
try{
|
try{
|
||||||
notification.postprocess();
|
notification.postprocess();
|
||||||
}catch(ObjectValidationException x){
|
}catch(ObjectValidationException x){
|
||||||
Log.w("MainActivity", x);
|
Log.w("MainActivity", x);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(notification.status!=null){
|
UiUtils.showFragmentForNotification(this, notification, accountID, null);
|
||||||
fragment=new ThreadFragment();
|
|
||||||
args.putParcelable("status", Parcels.wrap(notification.status));
|
|
||||||
}else{
|
|
||||||
fragment=new ProfileFragment();
|
|
||||||
args.putParcelable("profileAccount", Parcels.wrap(notification.account));
|
|
||||||
}
|
|
||||||
fragment.setArguments(args);
|
|
||||||
showFragment(fragment);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showFragmentForExternalShare(Bundle args) {
|
private void showFragmentForExternalShare(Bundle args) {
|
||||||
@@ -200,15 +188,20 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public Fragment getCurrentFragment() {
|
||||||
public void showFragment(Fragment fragment) {
|
for (int i = fragmentContainers.size() - 1; i >= 0; i--) {
|
||||||
super.showFragment(fragment);
|
FrameLayout fl = fragmentContainers.get(i);
|
||||||
this.currentFragment = fragment;
|
if (fl.getVisibility() == View.VISIBLE) {
|
||||||
|
return getFragmentManager().findFragmentById(fl.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProvideAssistContent(AssistContent assistContent) {
|
public void onProvideAssistContent(AssistContent assistContent) {
|
||||||
super.onProvideAssistContent(assistContent);
|
super.onProvideAssistContent(assistContent);
|
||||||
callFragmentToProvideAssistContent(currentFragment, assistContent);
|
Fragment fragment = getCurrentFragment();
|
||||||
|
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import me.grishka.appkit.utils.WorkerThread;
|
|||||||
|
|
||||||
public class CacheController{
|
public class CacheController{
|
||||||
private static final String TAG="CacheController";
|
private static final String TAG="CacheController";
|
||||||
private static final int DB_VERSION=3;
|
private static final int DB_VERSION=4;
|
||||||
private static final WorkerThread databaseThread=new WorkerThread("databaseThread");
|
private static final WorkerThread databaseThread=new WorkerThread("databaseThread");
|
||||||
private static final Handler uiHandler=new Handler(Looper.getMainLooper());
|
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());
|
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
|
||||||
if(!forceReload){
|
if(!forceReload){
|
||||||
SQLiteDatabase db=getOrOpenDatabase();
|
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, "`id` 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, "`time` DESC", count+"")){
|
||||||
if(cursor.getCount()==count){
|
if(cursor.getCount()==count){
|
||||||
ArrayList<Status> result=new ArrayList<>();
|
ArrayList<Status> result=new ArrayList<>();
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
@@ -112,7 +112,7 @@ public class CacheController{
|
|||||||
runOnDbThread((db)->{
|
runOnDbThread((db)->{
|
||||||
if(clear)
|
if(clear)
|
||||||
db.delete("home_timeline", null, null);
|
db.delete("home_timeline", null, null);
|
||||||
ContentValues values=new ContentValues(3);
|
ContentValues values=new ContentValues(4);
|
||||||
for(Status s:posts){
|
for(Status s:posts){
|
||||||
values.put("id", s.id);
|
values.put("id", s.id);
|
||||||
values.put("json", MastodonAPIController.gson.toJson(s));
|
values.put("json", MastodonAPIController.gson.toJson(s));
|
||||||
@@ -120,6 +120,7 @@ public class CacheController{
|
|||||||
if(s.hasGapAfter)
|
if(s.hasGapAfter)
|
||||||
flags|=POST_FLAG_GAP_AFTER;
|
flags|=POST_FLAG_GAP_AFTER;
|
||||||
values.put("flags", flags);
|
values.put("flags", flags);
|
||||||
|
values.put("time", s.createdAt.getEpochSecond());
|
||||||
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -134,7 +135,7 @@ public class CacheController{
|
|||||||
if(!forceReload){
|
if(!forceReload){
|
||||||
SQLiteDatabase db=getOrOpenDatabase();
|
SQLiteDatabase db=getOrOpenDatabase();
|
||||||
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
|
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, "`id` DESC", count+"")){
|
try(Cursor cursor=db.query(table, new String[]{"json"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`time` DESC", count+"")){
|
||||||
if(cursor.getCount()==count){
|
if(cursor.getCount()==count){
|
||||||
ArrayList<Notification> result=new ArrayList<>();
|
ArrayList<Notification> result=new ArrayList<>();
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
@@ -159,7 +160,7 @@ public class CacheController{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(accountSession.domain);
|
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.isPleroma())
|
new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), instance.isAkkoma())
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Notification> result){
|
public void onSuccess(List<Notification> result){
|
||||||
@@ -192,7 +193,7 @@ public class CacheController{
|
|||||||
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
|
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
|
||||||
if(clear)
|
if(clear)
|
||||||
db.delete(table, null, null);
|
db.delete(table, null, null);
|
||||||
ContentValues values=new ContentValues(3);
|
ContentValues values=new ContentValues(4);
|
||||||
for(Notification n:notifications){
|
for(Notification n:notifications){
|
||||||
if(n.type==null){
|
if(n.type==null){
|
||||||
continue;
|
continue;
|
||||||
@@ -200,6 +201,7 @@ public class CacheController{
|
|||||||
values.put("id", n.id);
|
values.put("id", n.id);
|
||||||
values.put("json", MastodonAPIController.gson.toJson(n));
|
values.put("json", MastodonAPIController.gson.toJson(n));
|
||||||
values.put("type", n.type.ordinal());
|
values.put("type", n.type.ordinal());
|
||||||
|
values.put("time", n.createdAt.getEpochSecond());
|
||||||
db.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
db.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -296,21 +298,24 @@ public class CacheController{
|
|||||||
CREATE TABLE `home_timeline` (
|
CREATE TABLE `home_timeline` (
|
||||||
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
||||||
`json` TEXT NOT NULL,
|
`json` TEXT NOT NULL,
|
||||||
`flags` INTEGER NOT NULL DEFAULT 0
|
`flags` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`time` INTEGER NOT NULL
|
||||||
)""");
|
)""");
|
||||||
db.execSQL("""
|
db.execSQL("""
|
||||||
CREATE TABLE `notifications_all` (
|
CREATE TABLE `notifications_all` (
|
||||||
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
||||||
`json` TEXT NOT NULL,
|
`json` TEXT NOT NULL,
|
||||||
`flags` INTEGER NOT NULL DEFAULT 0,
|
`flags` INTEGER NOT NULL DEFAULT 0,
|
||||||
`type` INTEGER NOT NULL
|
`type` INTEGER NOT NULL,
|
||||||
|
`time` INTEGER NOT NULL
|
||||||
)""");
|
)""");
|
||||||
db.execSQL("""
|
db.execSQL("""
|
||||||
CREATE TABLE `notifications_mentions` (
|
CREATE TABLE `notifications_mentions` (
|
||||||
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
||||||
`json` TEXT NOT NULL,
|
`json` TEXT NOT NULL,
|
||||||
`flags` INTEGER NOT NULL DEFAULT 0,
|
`flags` INTEGER NOT NULL DEFAULT 0,
|
||||||
`type` INTEGER NOT NULL
|
`type` INTEGER NOT NULL,
|
||||||
|
`time` INTEGER NOT NULL
|
||||||
)""");
|
)""");
|
||||||
createRecentSearchesTable(db);
|
createRecentSearchesTable(db);
|
||||||
createPostsNotificationsTable(db);
|
createPostsNotificationsTable(db);
|
||||||
@@ -318,12 +323,16 @@ public class CacheController{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
|
||||||
if(oldVersion==1){
|
if(oldVersion<2){
|
||||||
createRecentSearchesTable(db);
|
createRecentSearchesTable(db);
|
||||||
}
|
}
|
||||||
if(oldVersion==2){
|
if(oldVersion<3){
|
||||||
|
// MEGALODON-SPECIFIC
|
||||||
createPostsNotificationsTable(db);
|
createPostsNotificationsTable(db);
|
||||||
}
|
}
|
||||||
|
if(oldVersion<4){
|
||||||
|
addTimeColumns(db);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createRecentSearchesTable(SQLiteDatabase db){
|
private void createRecentSearchesTable(SQLiteDatabase db){
|
||||||
@@ -341,9 +350,21 @@ public class CacheController{
|
|||||||
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
||||||
`json` TEXT NOT NULL,
|
`json` TEXT NOT NULL,
|
||||||
`flags` INTEGER NOT NULL DEFAULT 0,
|
`flags` INTEGER NOT NULL DEFAULT 0,
|
||||||
`type` INTEGER NOT NULL
|
`type` INTEGER NOT NULL,
|
||||||
|
`time` 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
|
@FunctionalInterface
|
||||||
|
|||||||
@@ -20,9 +20,11 @@ import java.util.HashMap;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.CallSuper;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import me.grishka.appkit.api.APIRequest;
|
import me.grishka.appkit.api.APIRequest;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
@@ -44,7 +46,7 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||||||
TypeToken<T> respTypeToken;
|
TypeToken<T> respTypeToken;
|
||||||
Call okhttpCall;
|
Call okhttpCall;
|
||||||
Token token;
|
Token token;
|
||||||
boolean canceled;
|
boolean canceled, isRemote;
|
||||||
Map<String, String> headers;
|
Map<String, String> headers;
|
||||||
private ProgressDialog progressDialog;
|
private ProgressDialog progressDialog;
|
||||||
protected boolean removeUnsupportedItems;
|
protected boolean removeUnsupportedItems;
|
||||||
@@ -101,6 +103,21 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||||||
return this;
|
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){
|
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable){
|
||||||
return wrapProgress(activity, message, cancelable, null);
|
return wrapProgress(activity, message, cancelable, null);
|
||||||
}
|
}
|
||||||
@@ -167,6 +184,7 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||||||
@CallSuper
|
@CallSuper
|
||||||
public void validateAndPostprocessResponse(T respObj, Response httpResponse) throws IOException{
|
public void validateAndPostprocessResponse(T respObj, Response httpResponse) throws IOException{
|
||||||
if(respObj instanceof BaseModel){
|
if(respObj instanceof BaseModel){
|
||||||
|
((BaseModel) respObj).isRemote = isRemote;
|
||||||
((BaseModel) respObj).postprocess();
|
((BaseModel) respObj).postprocess();
|
||||||
}else if(respObj instanceof List){
|
}else if(respObj instanceof List){
|
||||||
if(removeUnsupportedItems){
|
if(removeUnsupportedItems){
|
||||||
@@ -175,6 +193,7 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||||||
Object item=itr.next();
|
Object item=itr.next();
|
||||||
if(item instanceof BaseModel){
|
if(item instanceof BaseModel){
|
||||||
try{
|
try{
|
||||||
|
((BaseModel) item).isRemote = isRemote;
|
||||||
((BaseModel) item).postprocess();
|
((BaseModel) item).postprocess();
|
||||||
}catch(ObjectValidationException x){
|
}catch(ObjectValidationException x){
|
||||||
Log.w(TAG, "Removing invalid object from list", x);
|
Log.w(TAG, "Removing invalid object from list", x);
|
||||||
@@ -182,15 +201,20 @@ 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)){
|
for(Object item:((List<?>) respObj)){
|
||||||
if(item instanceof BaseModel){
|
if(item instanceof BaseModel){
|
||||||
|
((BaseModel) item).isRemote = isRemote;
|
||||||
((BaseModel) item).postprocess();
|
((BaseModel) item).postprocess();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
for(Object item:((List<?>) respObj)){
|
for(Object item:((List<?>) respObj)){
|
||||||
if(item instanceof BaseModel)
|
if(item instanceof BaseModel) {
|
||||||
|
((BaseModel) item).isRemote = isRemote;
|
||||||
((BaseModel) item).postprocess();
|
((BaseModel) item).postprocess();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class StatusInteractionController{
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(Status result){
|
public void onSuccess(Status result){
|
||||||
runningFavoriteRequests.remove(status.id);
|
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);
|
cb.accept(result);
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ public class StatusInteractionController{
|
|||||||
public void onSuccess(Status reblog){
|
public void onSuccess(Status reblog){
|
||||||
Status result = reblog.getContentStatus();
|
Status result = reblog.getContentStatus();
|
||||||
runningReblogRequests.remove(status.id);
|
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);
|
cb.accept(result);
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.joinmastodon.android.api.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -99,7 +99,7 @@ public class AccountSession{
|
|||||||
public Uri getInstanceUri() {
|
public Uri getInstanceUri() {
|
||||||
return new Uri.Builder()
|
return new Uri.Builder()
|
||||||
.scheme("https")
|
.scheme("https")
|
||||||
.authority(domain)
|
.authority(getInstance().map(i -> i.normalizedUri).orElse(domain))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,6 +160,11 @@ public class AccountSessionManager{
|
|||||||
return sessions.get(id);
|
return sessions.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public AccountSession tryGetAccount(Account account) {
|
||||||
|
return sessions.get(account.getDomainFromURL() + "_" + account.id);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public AccountSession getLastActiveAccount(){
|
public AccountSession getLastActiveAccount(){
|
||||||
if(sessions.isEmpty() || lastActiveAccountID==null)
|
if(sessions.isEmpty() || lastActiveAccountID==null)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import org.parceler.Parcels;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
@@ -95,10 +96,11 @@ public class AccountTimelineFragment extends StatusListFragment{
|
|||||||
if(ev.status.inReplyToAccountId!=null && !ev.status.inReplyToAccountId.equals(AccountSessionManager.getInstance().getAccount(accountID).self.id))
|
if(ev.status.inReplyToAccountId!=null && !ev.status.inReplyToAccountId.equals(AccountSessionManager.getInstance().getAccount(accountID).self.id))
|
||||||
return;
|
return;
|
||||||
}else if(filter==GetAccountStatuses.Filter.MEDIA){
|
}else if(filter==GetAccountStatuses.Filter.MEDIA){
|
||||||
if(ev.status.mediaAttachments.isEmpty())
|
if(Optional.ofNullable(ev.status.mediaAttachments).map(List::isEmpty).orElse(true))
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
prependItems(Collections.singletonList(ev.status), true);
|
prependItems(Collections.singletonList(ev.status), true);
|
||||||
|
if (isOnTop()) scrollToTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onStatusUnpinned(StatusUnpinnedEvent ev){
|
protected void onStatusUnpinned(StatusUnpinnedEvent ev){
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import org.joinmastodon.android.GlobalUserPreferences;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
|
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.DisplayItemsParent;
|
import org.joinmastodon.android.model.DisplayItemsParent;
|
||||||
@@ -35,6 +34,7 @@ import org.joinmastodon.android.model.Relationship;
|
|||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
||||||
@@ -71,7 +71,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends RecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop, HasFab, ProvidesAssistContent.ProvidesWebUri {
|
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends RecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop, IsOnTop, HasFab, ProvidesAssistContent.ProvidesWebUri {
|
||||||
protected ArrayList<StatusDisplayItem> displayItems=new ArrayList<>();
|
protected ArrayList<StatusDisplayItem> displayItems=new ArrayList<>();
|
||||||
protected DisplayItemsAdapter adapter;
|
protected DisplayItemsAdapter adapter;
|
||||||
protected String accountID;
|
protected String accountID;
|
||||||
@@ -82,6 +82,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||||
protected Rect tmpRect=new Rect();
|
protected Rect tmpRect=new Rect();
|
||||||
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
|
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
|
||||||
|
protected boolean currentlyScrolling;
|
||||||
|
|
||||||
public BaseStatusListFragment(){
|
public BaseStatusListFragment(){
|
||||||
super(20);
|
super(20);
|
||||||
@@ -95,7 +96,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
UiUtils.loadMaxWidth(getContext());
|
|
||||||
if(GlobalUserPreferences.disableMarquee){
|
if(GlobalUserPreferences.disableMarquee){
|
||||||
setTitleMarqueeEnabled(false);
|
setTitleMarqueeEnabled(false);
|
||||||
setSubtitleMarqueeEnabled(false);
|
setSubtitleMarqueeEnabled(false);
|
||||||
@@ -132,7 +132,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
displayItems.clear();
|
displayItems.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void prependItems(List<T> items, boolean notify){
|
protected int prependItems(List<T> items, boolean notify){
|
||||||
data.addAll(0, items);
|
data.addAll(0, items);
|
||||||
int offset=0;
|
int offset=0;
|
||||||
for(T s:items){
|
for(T s:items){
|
||||||
@@ -145,6 +145,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
}
|
}
|
||||||
if(notify)
|
if(notify)
|
||||||
adapter.notifyItemRangeInserted(0, offset);
|
adapter.notifyItemRangeInserted(0, offset);
|
||||||
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getMaxID(){
|
protected String getMaxID(){
|
||||||
@@ -205,7 +206,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
@Override
|
@Override
|
||||||
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
|
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
|
||||||
MediaAttachmentViewController holder=findPhotoViewHolder(index);
|
MediaAttachmentViewController holder=findPhotoViewHolder(index);
|
||||||
if(holder!=null){
|
if(holder!=null && list!=null){
|
||||||
transitioningHolder=holder;
|
transitioningHolder=holder;
|
||||||
View view=transitioningHolder.photo;
|
View view=transitioningHolder.photo;
|
||||||
int[] pos={0, 0};
|
int[] pos={0, 0};
|
||||||
@@ -291,6 +292,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
fab.startAnimation(animate);
|
fab.startAnimation(animate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isScrolling() {
|
||||||
|
return currentlyScrolling;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void hideFab() {
|
public void hideFab() {
|
||||||
View fab = getFab();
|
View fab = getFab();
|
||||||
@@ -318,7 +323,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
currentPhotoViewer.offsetView(-dx, -dy);
|
currentPhotoViewer.offsetView(-dx, -dy);
|
||||||
|
|
||||||
View fab = getFab();
|
View fab = getFab();
|
||||||
if (fab!=null && GlobalUserPreferences.autoHideFab) {
|
if (fab!=null && GlobalUserPreferences.autoHideFab && dy != UiUtils.SCROLL_TO_TOP_DELTA) {
|
||||||
if (dy > 0 && fab.getVisibility() == View.VISIBLE) {
|
if (dy > 0 && fab.getVisibility() == View.VISIBLE) {
|
||||||
hideFab();
|
hideFab();
|
||||||
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) {
|
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) {
|
||||||
@@ -331,12 +336,20 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||||
|
super.onScrollStateChanged(recyclerView, newState);
|
||||||
|
currentlyScrolling = newState != RecyclerView.SCROLL_STATE_IDLE;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
list.addItemDecoration(new StatusListItemDecoration());
|
list.addItemDecoration(new StatusListItemDecoration());
|
||||||
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
|
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
|
||||||
private Rect tmpRect=new Rect();
|
private Rect tmpRect=new Rect();
|
||||||
@Override
|
@Override
|
||||||
public void getSelectorBounds(View view, Rect outRect){
|
public void getSelectorBounds(View view, Rect outRect){
|
||||||
|
boolean hasDescendant = false, hasAncestor = false, isWarning = false;
|
||||||
|
int lastIndex = -1, firstIndex = -1;
|
||||||
list.getDecoratedBoundsWithMargins(view, outRect);
|
list.getDecoratedBoundsWithMargins(view, outRect);
|
||||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(view);
|
RecyclerView.ViewHolder holder=list.getChildViewHolder(view);
|
||||||
if(holder instanceof StatusDisplayItem.Holder){
|
if(holder instanceof StatusDisplayItem.Holder){
|
||||||
@@ -348,18 +361,42 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
for(int i=0;i<list.getChildCount();i++){
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
View child=list.getChildAt(i);
|
View child=list.getChildAt(i);
|
||||||
holder=list.getChildViewHolder(child);
|
holder=list.getChildViewHolder(child);
|
||||||
if(holder instanceof StatusDisplayItem.Holder){
|
if(holder instanceof StatusDisplayItem.Holder<?> h){
|
||||||
String otherID=((StatusDisplayItem.Holder<?>) holder).getItemID();
|
String otherID=((StatusDisplayItem.Holder<?>) holder).getItemID();
|
||||||
if(otherID.equals(id)){
|
if(otherID.equals(id)){
|
||||||
|
if (firstIndex < 0) firstIndex = i;
|
||||||
|
lastIndex = i;
|
||||||
|
StatusDisplayItem item = h.getItem();
|
||||||
|
hasDescendant = item.hasDescendantNeighbor;
|
||||||
|
// no for direct descendants because main status (right above) is
|
||||||
|
// being displayed with an extended footer - no connected layout
|
||||||
|
hasAncestor = item.hasAncestoringNeighbor && !item.isDirectDescendant;
|
||||||
list.getDecoratedBoundsWithMargins(child, tmpRect);
|
list.getDecoratedBoundsWithMargins(child, tmpRect);
|
||||||
outRect.left=Math.min(outRect.left, tmpRect.left);
|
outRect.left=Math.min(outRect.left, tmpRect.left);
|
||||||
outRect.top=Math.min(outRect.top, tmpRect.top);
|
outRect.top=Math.min(outRect.top, tmpRect.top);
|
||||||
outRect.right=Math.max(outRect.right, tmpRect.right);
|
outRect.right=Math.max(outRect.right, tmpRect.right);
|
||||||
outRect.bottom=Math.max(outRect.bottom, tmpRect.bottom);
|
outRect.bottom=Math.max(outRect.bottom, tmpRect.bottom);
|
||||||
|
if (holder instanceof WarningFilteredStatusDisplayItem.Holder) {
|
||||||
|
isWarning = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// shifting the selection box down
|
||||||
|
// see also: FooterStatusDisplayItem#onBind (setMargins)
|
||||||
|
if (isWarning || firstIndex < 0 || lastIndex < 0 ||
|
||||||
|
!(list.getChildViewHolder(list.getChildAt(lastIndex))
|
||||||
|
instanceof FooterStatusDisplayItem.Holder)) return;
|
||||||
|
int prevIndex = firstIndex - 1, nextIndex = lastIndex + 1;
|
||||||
|
boolean prevIsWarning = prevIndex > 0 && prevIndex < list.getChildCount() &&
|
||||||
|
list.getChildViewHolder(list.getChildAt(prevIndex))
|
||||||
|
instanceof WarningFilteredStatusDisplayItem.Holder;
|
||||||
|
boolean nextIsWarning = nextIndex > 0 && nextIndex < list.getChildCount() &&
|
||||||
|
list.getChildViewHolder(list.getChildAt(nextIndex))
|
||||||
|
instanceof WarningFilteredStatusDisplayItem.Holder;
|
||||||
|
if (!prevIsWarning && hasAncestor) outRect.top += V.dp(4);
|
||||||
|
if (!nextIsWarning && hasDescendant) outRect.bottom += V.dp(4);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
list.setItemAnimator(new BetterItemAnimator());
|
list.setItemAnimator(new BetterItemAnimator());
|
||||||
@@ -643,6 +680,11 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
smoothScrollRecyclerViewToTop(list);
|
smoothScrollRecyclerViewToTop(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOnTop() {
|
||||||
|
return isRecyclerViewOnTop(list);
|
||||||
|
}
|
||||||
|
|
||||||
protected int getListWidthForMediaLayout(){
|
protected int getListWidthForMediaLayout(){
|
||||||
return list.getWidth();
|
return list.getWidth();
|
||||||
}
|
}
|
||||||
@@ -772,6 +814,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
RecyclerView.ViewHolder siblingHolder=parent.getChildViewHolder(bottomSibling);
|
RecyclerView.ViewHolder siblingHolder=parent.getChildViewHolder(bottomSibling);
|
||||||
if(holder instanceof StatusDisplayItem.Holder<?> ih && siblingHolder instanceof StatusDisplayItem.Holder<?> sh
|
if(holder instanceof StatusDisplayItem.Holder<?> ih && siblingHolder instanceof StatusDisplayItem.Holder<?> sh
|
||||||
&& (!ih.getItemID().equals(sh.getItemID()) || sh instanceof ExtendedFooterStatusDisplayItem.Holder) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP){
|
&& (!ih.getItemID().equals(sh.getItemID()) || sh instanceof ExtendedFooterStatusDisplayItem.Holder) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP){
|
||||||
|
if (!ih.getItem().isMainStatus && ih.getItem().hasDescendantNeighbor) continue;
|
||||||
drawDivider(child, bottomSibling, holder, siblingHolder, parent, c, dividerPaint);
|
drawDivider(child, bottomSibling, holder, siblingHolder, parent, c, dividerPaint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -579,8 +579,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable s){
|
public void afterTextChanged(Editable s){
|
||||||
if(s.length()==0)
|
if(s.length()==0){
|
||||||
|
updateCharCounter();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
int start=lastChangeStart;
|
int start=lastChangeStart;
|
||||||
int count=lastChangeCount;
|
int count=lastChangeCount;
|
||||||
// offset one char back to catch an already typed '@' or '#' or ':'
|
// offset one char back to catch an already typed '@' or '#' or ':'
|
||||||
@@ -1084,7 +1086,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
req.status=text;
|
req.status=text;
|
||||||
req.localOnly=localOnly;
|
req.localOnly=localOnly;
|
||||||
req.visibility=localOnly && instance.isPleroma() ? StatusPrivacy.LOCAL : statusVisibility;
|
req.visibility=localOnly && instance.isAkkoma() ? StatusPrivacy.LOCAL : statusVisibility;
|
||||||
req.sensitive=sensitive;
|
req.sensitive=sensitive;
|
||||||
req.language=language;
|
req.language=language;
|
||||||
req.contentType=contentType;
|
req.contentType=contentType;
|
||||||
@@ -1899,7 +1901,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
Menu m=visibilityPopup.getMenu();
|
Menu m=visibilityPopup.getMenu();
|
||||||
MenuItem localOnlyItem = visibilityPopup.getMenu().findItem(R.id.local_only);
|
MenuItem localOnlyItem = visibilityPopup.getMenu().findItem(R.id.local_only);
|
||||||
boolean prefsSaysSupported = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID);
|
boolean prefsSaysSupported = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID);
|
||||||
if (instance.isPleroma()) {
|
if (instance.isAkkoma()) {
|
||||||
m.findItem(R.id.vis_local).setVisible(true);
|
m.findItem(R.id.vis_local).setVisible(true);
|
||||||
} else if (localOnly || prefsSaysSupported) {
|
} else if (localOnly || prefsSaysSupported) {
|
||||||
localOnlyItem.setVisible(true);
|
localOnlyItem.setVisible(true);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public interface HasAccountID {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default boolean isInstanceAkkoma() {
|
default boolean isInstanceAkkoma() {
|
||||||
return getInstance().map(Instance::isPleroma).orElse(false);
|
return getInstance().map(Instance::isAkkoma).orElse(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
default Optional<Instance> getInstance() {
|
default Optional<Instance> getInstance() {
|
||||||
|
|||||||
@@ -6,4 +6,5 @@ public interface HasFab {
|
|||||||
View getFab();
|
View getFab();
|
||||||
void showFab();
|
void showFab();
|
||||||
void hideFab();
|
void hideFab();
|
||||||
|
boolean isScrolling();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
setTitle(R.string.sk_app_name);
|
setTitle(R.string.sk_app_name);
|
||||||
isPleroma = AccountSessionManager.getInstance().getAccount(accountID).getInstance()
|
isPleroma = AccountSessionManager.getInstance().getAccount(accountID).getInstance()
|
||||||
.map(Instance::isPleroma)
|
.map(Instance::isAkkoma)
|
||||||
.orElse(false);
|
.orElse(false);
|
||||||
|
|
||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||||
@@ -244,7 +244,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
}
|
}
|
||||||
getChildFragmentManager().beginTransaction().hide(fragmentForTab(currentTab)).show(newFragment).commit();
|
getChildFragmentManager().beginTransaction().hide(fragmentForTab(currentTab)).show(newFragment).commit();
|
||||||
maybeTriggerLoading(newFragment);
|
maybeTriggerLoading(newFragment);
|
||||||
if (newFragment instanceof HasFab fabulous) fabulous.showFab();
|
if (newFragment instanceof HasFab fabulous && !fabulous.isScrolling()) fabulous.showFab();
|
||||||
currentTab=tab;
|
currentTab=tab;
|
||||||
((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this);
|
((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this);
|
||||||
if (tab == R.id.tab_search && isPleroma) searchFragment.selectSearch();
|
if (tab == R.id.tab_search && isPleroma) searchFragment.selectSearch();
|
||||||
@@ -310,7 +310,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
Optional<Instance> instance = session.getInstance();
|
Optional<Instance> instance = session.getInstance();
|
||||||
if (instance.isEmpty()) return; // avoiding incompatibility with akkoma
|
if (instance.isEmpty()) return; // avoiding incompatibility with akkoma
|
||||||
|
|
||||||
new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance.get().isPleroma())
|
new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance.get().isAkkoma())
|
||||||
.setCallback(new Callback<>() {
|
.setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Notification> notifications) {
|
public void onSuccess(List<Notification> notifications) {
|
||||||
|
|||||||
@@ -460,6 +460,12 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) l.hideFab();
|
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) l.hideFab();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isScrolling() {
|
||||||
|
return (fragments[pager.getCurrentItem()] instanceof HasFab fabulous)
|
||||||
|
&& fabulous.isScrolling();
|
||||||
|
}
|
||||||
|
|
||||||
private void updateSwitcherIcon(int i) {
|
private void updateSwitcherIcon(int i) {
|
||||||
timelineIcon.setImageResource(timelines[i].getIcon().iconRes);
|
timelineIcon.setImageResource(timelines[i].getIcon().iconRes);
|
||||||
timelineTitle.setText(timelines[i].getTitle(getContext()));
|
timelineTitle.setText(timelines[i].getTitle(getContext()));
|
||||||
|
|||||||
@@ -30,4 +30,9 @@ public abstract class MastodonToolbarFragment extends ToolbarFragment{
|
|||||||
toolbar.setNavigationContentDescription(R.string.back);
|
toolbar.setNavigationContentDescription(R.string.back);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean wantsToolbarMenuIconsTinted() {
|
||||||
|
return false; // else, badged icons don't work :(
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
|
|||||||
import org.joinmastodon.android.events.AllNotificationsSeenEvent;
|
import org.joinmastodon.android.events.AllNotificationsSeenEvent;
|
||||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||||
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||||
import org.joinmastodon.android.model.Emoji;
|
import org.joinmastodon.android.model.Emoji;
|
||||||
@@ -25,6 +26,8 @@ import org.joinmastodon.android.model.Markers;
|
|||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||||
@@ -163,8 +166,9 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
.notifications.lastReadId = result.items.get(0).id;
|
.notifications.lastReadId = result.items.get(0).id;
|
||||||
AccountSessionManager.getInstance().writeAccountsFile();
|
AccountSessionManager.getInstance().writeAccountsFile();
|
||||||
|
|
||||||
if (AccountSessionManager.getInstance().getAccount(accountID).getInstance().map(Instance::isPleroma).orElse(false))
|
if (isInstanceAkkoma()) {
|
||||||
new PleromaMarkNotificationsRead(result.items.get(0).id).exec(accountID);
|
new PleromaMarkNotificationsRead(result.items.get(0).id).exec(accountID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -191,23 +195,10 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
@Override
|
@Override
|
||||||
public void onItemClick(String id){
|
public void onItemClick(String id){
|
||||||
Notification n=getNotificationByID(id);
|
Notification n=getNotificationByID(id);
|
||||||
if(n.status!=null){
|
Bundle args = new Bundle();
|
||||||
Status status=n.status;
|
if(n.status != null && n.status.inReplyToAccountId != null && knownAccounts.containsKey(n.status.inReplyToAccountId))
|
||||||
Bundle args=new Bundle();
|
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(n.status.inReplyToAccountId)));
|
||||||
args.putString("account", accountID);
|
UiUtils.showFragmentForNotification(getContext(), n, accountID, args);
|
||||||
args.putParcelable("status", Parcels.wrap(status));
|
|
||||||
if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId))
|
|
||||||
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId)));
|
|
||||||
Nav.go(getActivity(), ThreadFragment.class, args);
|
|
||||||
}else if(n.report != null){
|
|
||||||
String domain = AccountSessionManager.getInstance().getAccount(accountID).domain;
|
|
||||||
UiUtils.launchWebBrowser(getActivity(), "https://"+domain+"/admin/reports/"+n.report.id);
|
|
||||||
}else{
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putParcelable("profileAccount", Parcels.wrap(n.account));
|
|
||||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -239,6 +230,32 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copied from StatusListFragment.EventListener (just like the method above)
|
||||||
|
// (which assumes this.data to be a list of statuses...)
|
||||||
|
@Subscribe
|
||||||
|
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
|
||||||
|
for(Notification n:data){
|
||||||
|
if (n.status == null) continue;
|
||||||
|
if(n.status.getContentStatus().id.equals(ev.id)){
|
||||||
|
n.status.getContentStatus().update(ev);
|
||||||
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
|
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||||
|
if(holder instanceof FooterStatusDisplayItem.Holder footer && footer.getItem().status==n.status.getContentStatus()){
|
||||||
|
footer.rebind();
|
||||||
|
}else if(holder instanceof ExtendedFooterStatusDisplayItem.Holder footer && footer.getItem().status==n.status.getContentStatus()){
|
||||||
|
footer.rebind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(Notification n:preloadedData){
|
||||||
|
if (n.status == null) continue;
|
||||||
|
if(n.status.getContentStatus().id.equals(ev.id)){
|
||||||
|
n.status.getContentStatus().update(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
|
public void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
|
||||||
if(!ev.accountID.equals(accountID) || ev.isUnfollow)
|
if(!ev.accountID.equals(accountID) || ev.isUnfollow)
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import android.widget.Toolbar;
|
|||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||||
@@ -58,7 +59,6 @@ import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
|||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.AccountField;
|
import org.joinmastodon.android.model.AccountField;
|
||||||
import org.joinmastodon.android.model.Attachment;
|
import org.joinmastodon.android.model.Attachment;
|
||||||
import org.joinmastodon.android.model.Instance;
|
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||||
@@ -138,7 +138,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
private TextView followsYouView;
|
private TextView followsYouView;
|
||||||
private ViewGroup rolesView;
|
private ViewGroup rolesView;
|
||||||
|
|
||||||
private Account account;
|
private Account account, remoteAccount;
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private String domain;
|
private String domain;
|
||||||
private Relationship relationship;
|
private Relationship relationship;
|
||||||
@@ -177,19 +177,19 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
|
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
domain=AccountSessionManager.getInstance().getAccount(accountID).domain;
|
domain=AccountSessionManager.getInstance().getAccount(accountID).domain;
|
||||||
if(getArguments().containsKey("profileAccount")){
|
if (getArguments().containsKey("remoteAccount")) {
|
||||||
|
remoteAccount = Parcels.unwrap(getArguments().getParcelable("remoteAccount"));
|
||||||
|
if(!getArguments().getBoolean("noAutoLoad", false))
|
||||||
|
loadData();
|
||||||
|
} else if(getArguments().containsKey("profileAccount")){
|
||||||
account=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
|
account=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
|
||||||
profileAccountID=account.id;
|
profileAccountID=account.id;
|
||||||
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
|
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
|
||||||
loaded=true;
|
loaded=true;
|
||||||
if(!isOwnProfile)
|
if(!isOwnProfile)
|
||||||
loadRelationship();
|
loadRelationship();
|
||||||
else {
|
else if (isInstanceAkkoma() && getInstance().isPresent())
|
||||||
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(domain);
|
maxFields = getInstance().get().pleroma.metadata.fieldsLimits.maxFields;
|
||||||
if (instance != null && instance.isPleroma()) {
|
|
||||||
maxFields = instance.pleroma.metadata.fieldsLimits.maxFields;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else{
|
}else{
|
||||||
profileAccountID=getArguments().getString("profileAccountID");
|
profileAccountID=getArguments().getString("profileAccountID");
|
||||||
if(!getArguments().getBoolean("noAutoLoad", false))
|
if(!getArguments().getBoolean("noAutoLoad", false))
|
||||||
@@ -352,36 +352,55 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
return sizeWrapper;
|
return sizeWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onAccountLoaded(Account result) {
|
||||||
|
account=result;
|
||||||
|
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
|
||||||
|
bindHeaderView();
|
||||||
|
dataLoaded();
|
||||||
|
if(!tabLayoutMediator.isAttached())
|
||||||
|
tabLayoutMediator.attach();
|
||||||
|
if(!isOwnProfile)
|
||||||
|
loadRelationship();
|
||||||
|
else
|
||||||
|
AccountSessionManager.getInstance().updateAccountInfo(accountID, account);
|
||||||
|
if(refreshing){
|
||||||
|
refreshing=false;
|
||||||
|
refreshLayout.setRefreshing(false);
|
||||||
|
if(postsFragment.loaded)
|
||||||
|
postsFragment.onRefresh();
|
||||||
|
if(postsWithRepliesFragment.loaded)
|
||||||
|
postsWithRepliesFragment.onRefresh();
|
||||||
|
if(pinnedPostsFragment.loaded)
|
||||||
|
pinnedPostsFragment.onRefresh();
|
||||||
|
if(mediaFragment.loaded)
|
||||||
|
mediaFragment.onRefresh();
|
||||||
|
}
|
||||||
|
V.setVisibilityAnimated(fab, View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(){
|
protected void doLoadData(){
|
||||||
|
if (remoteAccount != null) {
|
||||||
|
UiUtils.lookupAccountHandle(getContext(), accountID, remoteAccount.getFullyQualifiedName(), (c, args) -> {
|
||||||
|
if (getContext() == null) return;
|
||||||
|
if (args == null || !args.containsKey("profileAccount")) {
|
||||||
|
onError(new MastodonErrorResponse(
|
||||||
|
getContext().getString(R.string.sk_error_loading_profile),
|
||||||
|
0, null
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onAccountLoaded(Parcels.unwrap(args.getParcelable("profileAccount")));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
currentRequest=new GetAccountByID(profileAccountID)
|
currentRequest=new GetAccountByID(profileAccountID)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Account result){
|
public void onSuccess(Account result){
|
||||||
if (getActivity() == null) return;
|
if (getActivity() == null) return;
|
||||||
account=result;
|
onAccountLoaded(result);
|
||||||
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
|
|
||||||
bindHeaderView();
|
|
||||||
dataLoaded();
|
|
||||||
if(!tabLayoutMediator.isAttached())
|
|
||||||
tabLayoutMediator.attach();
|
|
||||||
if(!isOwnProfile)
|
|
||||||
loadRelationship();
|
|
||||||
else
|
|
||||||
AccountSessionManager.getInstance().updateAccountInfo(accountID, account);
|
|
||||||
if(refreshing){
|
|
||||||
refreshing=false;
|
|
||||||
refreshLayout.setRefreshing(false);
|
|
||||||
if(postsFragment.loaded)
|
|
||||||
postsFragment.onRefresh();
|
|
||||||
if(postsWithRepliesFragment.loaded)
|
|
||||||
postsWithRepliesFragment.onRefresh();
|
|
||||||
if(pinnedPostsFragment.loaded)
|
|
||||||
pinnedPostsFragment.onRefresh();
|
|
||||||
if(mediaFragment.loaded)
|
|
||||||
mediaFragment.onRefresh();
|
|
||||||
}
|
|
||||||
V.setVisibilityAnimated(fab, View.VISIBLE);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
@@ -495,9 +514,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(100), V.dp(100)));
|
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(100), V.dp(100)));
|
||||||
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000));
|
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000));
|
||||||
SpannableStringBuilder ssb=new SpannableStringBuilder(account.displayName);
|
SpannableStringBuilder ssb=new SpannableStringBuilder(account.displayName);
|
||||||
HtmlParser.parseCustomEmoji(ssb, account.emojis);
|
HtmlParser.parseCustomEmoji(ssb, account.emojis);
|
||||||
name.setText(ssb);
|
name.setText(ssb);
|
||||||
setTitle(ssb);
|
setTitle(ssb);
|
||||||
|
|
||||||
if (account.roles != null && !account.roles.isEmpty()) {
|
if (account.roles != null && !account.roles.isEmpty()) {
|
||||||
rolesView.setVisibility(View.VISIBLE);
|
rolesView.setVisibility(View.VISIBLE);
|
||||||
@@ -516,13 +535,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
|
|
||||||
boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account);
|
boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account);
|
||||||
|
|
||||||
|
String acct = ((isSelf || account.isRemote)
|
||||||
|
? account.getFullyQualifiedName()
|
||||||
|
: account.acct);
|
||||||
if(account.locked){
|
if(account.locked){
|
||||||
ssb=new SpannableStringBuilder("@");
|
ssb=new SpannableStringBuilder("@");
|
||||||
ssb.append(account.acct);
|
ssb.append(acct);
|
||||||
if(isSelf){
|
|
||||||
ssb.append('@');
|
|
||||||
ssb.append(domain);
|
|
||||||
}
|
|
||||||
ssb.append(" ");
|
ssb.append(" ");
|
||||||
Drawable lock=username.getResources().getDrawable(R.drawable.ic_lock, getActivity().getTheme()).mutate();
|
Drawable lock=username.getResources().getDrawable(R.drawable.ic_lock, getActivity().getTheme()).mutate();
|
||||||
lock.setBounds(0, 0, lock.getIntrinsicWidth(), lock.getIntrinsicHeight());
|
lock.setBounds(0, 0, lock.getIntrinsicWidth(), lock.getIntrinsicHeight());
|
||||||
@@ -531,7 +549,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
username.setText(ssb);
|
username.setText(ssb);
|
||||||
}else{
|
}else{
|
||||||
// noinspection SetTextI18n
|
// noinspection SetTextI18n
|
||||||
username.setText('@'+account.acct+(isSelf ? ('@'+domain) : ""));
|
username.setText('@'+acct);
|
||||||
}
|
}
|
||||||
CharSequence parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
CharSequence parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||||
if(TextUtils.isEmpty(parsedBio)){
|
if(TextUtils.isEmpty(parsedBio)){
|
||||||
@@ -601,6 +619,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean wantsToolbarMenuIconsTinted() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
if(isOwnProfile && isInEditMode){
|
if(isOwnProfile && isInEditMode){
|
||||||
@@ -779,6 +802,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
if (getFragmentForPage(pager.getCurrentItem()) instanceof HasFab fabulous) fabulous.hideFab();
|
if (getFragmentForPage(pager.getCurrentItem()) instanceof HasFab fabulous) fabulous.hideFab();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isScrolling() {
|
||||||
|
return getFragmentForPage(pager.getCurrentItem()) instanceof HasFab fabulous
|
||||||
|
&& fabulous.isScrolling();
|
||||||
|
}
|
||||||
|
|
||||||
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
||||||
int topBarsH=getToolbar().getHeight()+statusBarHeight;
|
int topBarsH=getToolbar().getHeight()+statusBarHeight;
|
||||||
if(scrollY>avatarBorder.getTop()-topBarsH){
|
if(scrollY>avatarBorder.getTop()-topBarsH){
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ package org.joinmastodon.android.fragments;
|
|||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
public interface ScrollableToTop{
|
public interface ScrollableToTop{
|
||||||
void scrollToTop();
|
void scrollToTop();
|
||||||
@@ -21,7 +22,7 @@ public interface ScrollableToTop{
|
|||||||
@Override
|
@Override
|
||||||
public boolean onPreDraw(){
|
public boolean onPreDraw(){
|
||||||
list.getViewTreeObserver().removeOnPreDrawListener(this);
|
list.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||||
list.scrollBy(0, V.dp(300));
|
list.scrollBy(0, UiUtils.SCROLL_TO_TOP_DELTA);
|
||||||
list.smoothScrollToPosition(0);
|
list.smoothScrollToPosition(0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,13 +219,18 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
|||||||
GlobalUserPreferences.confirmBeforeReblog=i.checked;
|
GlobalUserPreferences.confirmBeforeReblog=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_allow_remote_loading, R.drawable.ic_fluent_communication_24_regular, GlobalUserPreferences.allowRemoteLoading, i->{
|
||||||
|
GlobalUserPreferences.allowRemoteLoading=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}));
|
||||||
|
items.add(new SmallTextItem(R.string.sk_settings_allow_remote_loading_explanation));
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.sk_timelines));
|
items.add(new HeaderItem(R.string.sk_timelines));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{
|
items.add(new SwitchItem(R.string.sk_settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{
|
||||||
GlobalUserPreferences.showReplies=i.checked;
|
GlobalUserPreferences.showReplies=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
if (instance.map(Instance::isPleroma).orElse(false)) {
|
if (isInstanceAkkoma()) {
|
||||||
items.add(new ButtonItem(R.string.sk_settings_reply_visibility, R.drawable.ic_fluent_chat_24_regular, b->{
|
items.add(new ButtonItem(R.string.sk_settings_reply_visibility, R.drawable.ic_fluent_chat_24_regular, b->{
|
||||||
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
||||||
popupMenu.inflate(R.menu.reply_visibility);
|
popupMenu.inflate(R.menu.reply_visibility);
|
||||||
@@ -374,7 +379,7 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
|||||||
glitchModeItem.enabled = i.checked;
|
glitchModeItem.enabled = i.checked;
|
||||||
if (i.checked) {
|
if (i.checked) {
|
||||||
GlobalUserPreferences.accountsWithLocalOnlySupport.add(accountID);
|
GlobalUserPreferences.accountsWithLocalOnlySupport.add(accountID);
|
||||||
if (!instance.map(Instance::isPleroma).orElse(false)) {
|
if (!isInstanceAkkoma()) {
|
||||||
GlobalUserPreferences.accountsInGlitchMode.add(accountID);
|
GlobalUserPreferences.accountsInGlitchMode.add(accountID);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -744,7 +749,7 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Uri getWebUri(Uri.Builder base) {
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
return isInstanceAkkoma() ? null : base.path("/settings").build();
|
return base.path(isInstanceAkkoma() ? "/about" : "/settings").build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -835,7 +840,11 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class SmallTextItem extends Item {
|
private class SmallTextItem extends Item {
|
||||||
private String text;
|
private final String text;
|
||||||
|
|
||||||
|
public SmallTextItem(@StringRes int text) {
|
||||||
|
this.text = getString(text);
|
||||||
|
}
|
||||||
|
|
||||||
public SmallTextItem(String text) {
|
public SmallTextItem(String text) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
@@ -1076,7 +1085,6 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
|||||||
private final ImageView icon;
|
private final ImageView icon;
|
||||||
private final TextView text;
|
private final TextView text;
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
public ButtonViewHolder(){
|
public ButtonViewHolder(){
|
||||||
super(getActivity(), R.layout.item_settings_button, list);
|
super(getActivity(), R.layout.item_settings_button, list);
|
||||||
text=findViewById(R.id.text);
|
text=findViewById(R.id.text);
|
||||||
@@ -1084,14 +1092,17 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
|||||||
button=findViewById(R.id.button);
|
button=findViewById(R.id.button);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
@Override
|
@Override
|
||||||
public void onBind(ButtonItem item){
|
public void onBind(ButtonItem item){
|
||||||
text.setText(item.text);
|
text.setText(item.text);
|
||||||
if (item.icon == 0) {
|
icon.setVisibility(item.icon == 0 ? View.GONE : View.VISIBLE);
|
||||||
icon.setVisibility(View.GONE);
|
icon.setImageResource(item.icon == 0 ? 0 : item.icon);
|
||||||
} else {
|
// reset listeners before letting the button consumer consume the button
|
||||||
icon.setImageResource(item.icon);
|
// (and potentially set some listeners, but not others)
|
||||||
}
|
button.setOnTouchListener(null);
|
||||||
|
button.setOnClickListener(null);
|
||||||
|
button.setOnLongClickListener(null);
|
||||||
item.buttonConsumer.accept(button);
|
item.buttonConsumer.accept(button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
|||||||
status.filterRevealed = true;
|
status.filterRevealed = true;
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putParcelable("status", Parcels.wrap(status));
|
args.putParcelable("status", Parcels.wrap(status.clone()));
|
||||||
if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId))
|
if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId))
|
||||||
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId)));
|
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId)));
|
||||||
Nav.go(getActivity(), ThreadFragment.class, args);
|
Nav.go(getActivity(), ThreadFragment.class, args);
|
||||||
|
|||||||
@@ -4,35 +4,52 @@ import android.net.Uri;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
|
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
|
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Instance;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.StatusContext;
|
import org.joinmastodon.android.model.StatusContext;
|
||||||
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent {
|
public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent {
|
||||||
protected Status mainStatus;
|
protected Status mainStatus, updatedStatus;
|
||||||
|
private final HashMap<String, NeighborAncestryInfo> ancestryMap = new HashMap<>();
|
||||||
|
protected boolean contextInitiallyRendered;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
@@ -49,13 +66,44 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
|||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||||
List<StatusDisplayItem> items=super.buildDisplayItems(s);
|
List<StatusDisplayItem> items=super.buildDisplayItems(s);
|
||||||
if(s.id.equals(mainStatus.id)){
|
// "what the fuck is a deque"? yes
|
||||||
for(StatusDisplayItem item:items){
|
// (it's just so the last-added item automatically comes first when looping over it)
|
||||||
|
Deque<Integer> deleteTheseItems = new ArrayDeque<>();
|
||||||
|
|
||||||
|
// modifying hidden filtered items if status is displayed as a warning
|
||||||
|
List<StatusDisplayItem> itemsToModify =
|
||||||
|
(items.get(0) instanceof WarningFilteredStatusDisplayItem warning)
|
||||||
|
? warning.filteredItems
|
||||||
|
: items;
|
||||||
|
|
||||||
|
for(int i = 0; i < itemsToModify.size(); i++){
|
||||||
|
StatusDisplayItem item = itemsToModify.get(i);
|
||||||
|
NeighborAncestryInfo ancestryInfo = ancestryMap.get(s.id);
|
||||||
|
if (ancestryInfo != null) {
|
||||||
|
item.setAncestryInfo(
|
||||||
|
ancestryInfo.descendantNeighbor != null,
|
||||||
|
ancestryInfo.ancestoringNeighbor != null,
|
||||||
|
s.id.equals(mainStatus.id),
|
||||||
|
Optional.ofNullable(ancestryInfo.ancestoringNeighbor)
|
||||||
|
.map(ancestor -> ancestor.id.equals(mainStatus.id))
|
||||||
|
.orElse(false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item instanceof ReblogOrReplyLineStatusDisplayItem &&
|
||||||
|
(!item.isDirectDescendant && item.hasAncestoringNeighbor)) {
|
||||||
|
deleteTheseItems.add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(s.id.equals(mainStatus.id)){
|
||||||
if(item instanceof TextStatusDisplayItem text)
|
if(item instanceof TextStatusDisplayItem text)
|
||||||
text.textSelectable=true;
|
text.textSelectable=true;
|
||||||
else if(item instanceof FooterStatusDisplayItem footer)
|
else if(item instanceof FooterStatusDisplayItem footer)
|
||||||
footer.hideCounts=true;
|
footer.hideCounts=true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
for (int deleteThisItem : deleteTheseItems) itemsToModify.remove(deleteThisItem);
|
||||||
|
if(s.id.equals(mainStatus.id)) {
|
||||||
items.add(new ExtendedFooterStatusDisplayItem(s.id, this, s.getContentStatus()));
|
items.add(new ExtendedFooterStatusDisplayItem(s.id, this, s.getContentStatus()));
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
@@ -63,43 +111,30 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
|
loadMainStatus();
|
||||||
currentRequest=new GetStatusContext(mainStatus.id)
|
currentRequest=new GetStatusContext(mainStatus.id)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(StatusContext result){
|
public void onSuccess(StatusContext result){
|
||||||
if (getActivity() == null) return;
|
if (getContext() == null) return;
|
||||||
if(refreshing){
|
if(refreshing){
|
||||||
data.clear();
|
data.clear();
|
||||||
|
ancestryMap.clear();
|
||||||
displayItems.clear();
|
displayItems.clear();
|
||||||
data.add(mainStatus);
|
data.add(mainStatus);
|
||||||
onAppendItems(Collections.singletonList(mainStatus));
|
onAppendItems(Collections.singletonList(mainStatus));
|
||||||
}
|
}
|
||||||
AccountSession account=AccountSessionManager.getInstance().getAccount(accountID);
|
|
||||||
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(account.domain);
|
|
||||||
if(instance.isPleroma()){
|
|
||||||
List<String> threadIds=new ArrayList<>();
|
|
||||||
threadIds.add(mainStatus.id);
|
|
||||||
for(Status s:result.descendants){
|
|
||||||
if(threadIds.contains(s.inReplyToId)){
|
|
||||||
threadIds.add(s.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
threadIds.add(mainStatus.inReplyToId);
|
|
||||||
for(int i=result.ancestors.size()-1; i >= 0; i--){
|
|
||||||
Status s=result.ancestors.get(i);
|
|
||||||
if(s.inReplyToId != null && threadIds.contains(s.id)){
|
|
||||||
threadIds.add(s.inReplyToId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.ancestors=result.ancestors.stream().filter(s -> threadIds.contains(s.id)).collect(Collectors.toList());
|
// TODO: figure out how this code works
|
||||||
result.descendants=getDescendantsOrdered(mainStatus.id,
|
if(isInstanceAkkoma()) sortStatusContext(mainStatus, result);
|
||||||
result.descendants.stream()
|
|
||||||
.filter(s -> threadIds.contains(s.id))
|
|
||||||
.collect(Collectors.toList()));
|
|
||||||
}
|
|
||||||
result.descendants=filterStatuses(result.descendants);
|
result.descendants=filterStatuses(result.descendants);
|
||||||
result.ancestors=filterStatuses(result.ancestors);
|
result.ancestors=filterStatuses(result.ancestors);
|
||||||
|
|
||||||
|
for (NeighborAncestryInfo i : mapNeighborhoodAncestry(mainStatus, result)) {
|
||||||
|
ancestryMap.put(i.status.id, i);
|
||||||
|
}
|
||||||
|
|
||||||
if(footerProgress!=null)
|
if(footerProgress!=null)
|
||||||
footerProgress.setVisibility(View.GONE);
|
footerProgress.setVisibility(View.GONE);
|
||||||
data.addAll(result.descendants);
|
data.addAll(result.descendants);
|
||||||
@@ -108,19 +143,120 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
|||||||
int count=displayItems.size();
|
int count=displayItems.size();
|
||||||
if(!refreshing)
|
if(!refreshing)
|
||||||
adapter.notifyItemRangeInserted(prevCount, count-prevCount);
|
adapter.notifyItemRangeInserted(prevCount, count-prevCount);
|
||||||
prependItems(result.ancestors, !refreshing);
|
int prependedCount = prependItems(result.ancestors, !refreshing);
|
||||||
|
if (prependedCount > 0 && displayItems.get(prependedCount) instanceof ReblogOrReplyLineStatusDisplayItem) {
|
||||||
|
displayItems.remove(prependedCount);
|
||||||
|
adapter.notifyItemRemoved(prependedCount);
|
||||||
|
count--;
|
||||||
|
}
|
||||||
dataLoaded();
|
dataLoaded();
|
||||||
if(refreshing){
|
if(refreshing){
|
||||||
refreshDone();
|
refreshDone();
|
||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
list.scrollToPosition(displayItems.size()-count);
|
list.scrollToPosition(displayItems.size()-count);
|
||||||
|
|
||||||
|
// no animation is going to happen, so proceeding to apply right now
|
||||||
|
if (data.size() == 1) {
|
||||||
|
contextInitiallyRendered = true;
|
||||||
|
// for the case that the main status has already finished loading
|
||||||
|
maybeApplyMainStatus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Status> getDescendantsOrdered(String id, List<Status> statuses){
|
private void loadMainStatus() {
|
||||||
|
new GetStatusByID(mainStatus.id)
|
||||||
|
.setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Status status) {
|
||||||
|
if (getContext() == null || status == null) return;
|
||||||
|
updatedStatus = status;
|
||||||
|
// for the case that the context has already loaded (and the animation has
|
||||||
|
// already finished), falling back to applying it ourselves:
|
||||||
|
maybeApplyMainStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {}
|
||||||
|
}).exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Object maybeApplyMainStatus() {
|
||||||
|
if (updatedStatus == null || !contextInitiallyRendered) return null;
|
||||||
|
|
||||||
|
// returning fired event object to facilitate testing
|
||||||
|
Object event;
|
||||||
|
if (updatedStatus.editedAt != null &&
|
||||||
|
(mainStatus.editedAt == null ||
|
||||||
|
updatedStatus.editedAt.isAfter(mainStatus.editedAt))) {
|
||||||
|
event = new StatusUpdatedEvent(updatedStatus);
|
||||||
|
} else {
|
||||||
|
event = new StatusCountersUpdatedEvent(updatedStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
mainStatus = updatedStatus;
|
||||||
|
updatedStatus = null;
|
||||||
|
E.post(event);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<NeighborAncestryInfo> mapNeighborhoodAncestry(Status mainStatus, StatusContext context) {
|
||||||
|
List<NeighborAncestryInfo> ancestry = new ArrayList<>();
|
||||||
|
|
||||||
|
List<Status> statuses = new ArrayList<>(context.ancestors);
|
||||||
|
statuses.add(mainStatus);
|
||||||
|
statuses.addAll(context.descendants);
|
||||||
|
|
||||||
|
int count = statuses.size();
|
||||||
|
for (int index = 0; index < count; index++) {
|
||||||
|
Status current = statuses.get(index);
|
||||||
|
ancestry.add(new NeighborAncestryInfo(
|
||||||
|
current,
|
||||||
|
// descendant neighbor
|
||||||
|
Optional
|
||||||
|
.ofNullable(count > index + 1 ? statuses.get(index + 1) : null)
|
||||||
|
.filter(s -> s.inReplyToId.equals(current.id))
|
||||||
|
.orElse(null),
|
||||||
|
// ancestoring neighbor
|
||||||
|
Optional.ofNullable(index > 0 ? ancestry.get(index - 1) : null)
|
||||||
|
.filter(ancestor -> Optional.ofNullable(ancestor.descendantNeighbor)
|
||||||
|
.map(ancestorsDescendant -> ancestorsDescendant.id.equals(current.id))
|
||||||
|
.orElse(false))
|
||||||
|
.map(a -> a.status)
|
||||||
|
.orElse(null)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ancestry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sortStatusContext(Status mainStatus, StatusContext context) {
|
||||||
|
List<String> threadIds=new ArrayList<>();
|
||||||
|
threadIds.add(mainStatus.id);
|
||||||
|
for(Status s:context.descendants){
|
||||||
|
if(threadIds.contains(s.inReplyToId)){
|
||||||
|
threadIds.add(s.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
threadIds.add(mainStatus.inReplyToId);
|
||||||
|
for(int i=context.ancestors.size()-1; i >= 0; i--){
|
||||||
|
Status s=context.ancestors.get(i);
|
||||||
|
if(s.inReplyToId != null && threadIds.contains(s.id)){
|
||||||
|
threadIds.add(s.inReplyToId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.ancestors=context.ancestors.stream().filter(s -> threadIds.contains(s.id)).collect(Collectors.toList());
|
||||||
|
context.descendants=getDescendantsOrdered(mainStatus.id,
|
||||||
|
context.descendants.stream()
|
||||||
|
.filter(s -> threadIds.contains(s.id))
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Status> getDescendantsOrdered(String id, List<Status> statuses){
|
||||||
List<Status> out=new ArrayList<>();
|
List<Status> out=new ArrayList<>();
|
||||||
for(Status s:getDirectDescendants(id, statuses)){
|
for(Status s:getDirectDescendants(id, statuses)){
|
||||||
out.add(s);
|
out.add(s);
|
||||||
@@ -132,7 +268,7 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Status> getDirectDescendants(String id, List<Status> statuses){
|
private static List<Status> getDirectDescendants(String id, List<Status> statuses){
|
||||||
return statuses.stream()
|
return statuses.stream()
|
||||||
.filter(s -> s.inReplyToId.equals(id))
|
.filter(s -> s.inReplyToId.equals(id))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
@@ -161,10 +297,21 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
|||||||
showContent();
|
showContent();
|
||||||
if(!loaded)
|
if(!loaded)
|
||||||
footerProgress.setVisibility(View.VISIBLE);
|
footerProgress.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
list.setItemAnimator(new BetterItemAnimator() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationFinished(@NonNull RecyclerView.ViewHolder viewHolder) {
|
||||||
|
super.onAnimationFinished(viewHolder);
|
||||||
|
contextInitiallyRendered = true;
|
||||||
|
// for the case that both requests are already done (and thus won't apply it)
|
||||||
|
maybeApplyMainStatus();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onStatusCreated(StatusCreatedEvent ev){
|
protected void onStatusCreated(StatusCreatedEvent ev){
|
||||||
if(ev.status.inReplyToId!=null && getStatusByID(ev.status.inReplyToId)!=null){
|
if(ev.status.inReplyToId!=null && getStatusByID(ev.status.inReplyToId)!=null){
|
||||||
|
data.add(ev.status);
|
||||||
onAppendItems(Collections.singletonList(ev.status));
|
onAppendItems(Collections.singletonList(ev.status));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,4 +341,41 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
|||||||
public Uri getWebUri(Uri.Builder base) {
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
return Uri.parse(mainStatus.url);
|
return Uri.parse(mainStatus.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static class NeighborAncestryInfo {
|
||||||
|
protected Status status, descendantNeighbor, ancestoringNeighbor;
|
||||||
|
|
||||||
|
protected NeighborAncestryInfo(@NonNull Status status, Status descendantNeighbor, Status ancestoringNeighbor) {
|
||||||
|
this.status = status;
|
||||||
|
this.descendantNeighbor = descendantNeighbor;
|
||||||
|
this.ancestoringNeighbor = ancestoringNeighbor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
NeighborAncestryInfo that = (NeighborAncestryInfo) o;
|
||||||
|
return status.equals(that.status)
|
||||||
|
&& Objects.equals(descendantNeighbor, that.descendantNeighbor)
|
||||||
|
&& Objects.equals(ancestoringNeighbor, that.ancestoringNeighbor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(status, descendantNeighbor, ancestoringNeighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onErrorRetryClick(){
|
||||||
|
if(preloadingFailed){
|
||||||
|
preloadingFailed=false;
|
||||||
|
V.setVisibilityAnimated(footerProgress, View.VISIBLE);
|
||||||
|
V.setVisibilityAnimated(footerError, View.GONE);
|
||||||
|
doLoadData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.onErrorRetryClick();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,27 @@ package org.joinmastodon.android.fragments.account_list;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.api.requests.accounts.GetAccountByHandle;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
public abstract class AccountRelatedAccountListFragment extends PaginatedAccountListFragment{
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public abstract class AccountRelatedAccountListFragment extends PaginatedAccountListFragment<Account> {
|
||||||
protected Account account;
|
protected Account account;
|
||||||
|
protected String initialSubtitle = "";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
account=Parcels.unwrap(getArguments().getParcelable("targetAccount"));
|
account=Parcels.unwrap(getArguments().getParcelable("targetAccount"));
|
||||||
|
if (getArguments().containsKey("remoteAccount")) {
|
||||||
|
remoteInfo = Parcels.unwrap(getArguments().getParcelable("remoteAccount"));
|
||||||
|
}
|
||||||
setTitle("@"+account.acct);
|
setTitle("@"+account.acct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,4 +33,36 @@ public abstract class AccountRelatedAccountListFragment extends PaginatedAccount
|
|||||||
? "/users/" + account.id
|
? "/users/" + account.id
|
||||||
: '@' + account.acct).build();
|
: '@' + account.acct).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRemoteDomain() {
|
||||||
|
return account.getDomainFromURL();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Account getCurrentInfo() {
|
||||||
|
return doneWithHomeInstance && remoteInfo != null ? remoteInfo : account;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MastodonAPIRequest<Account> loadRemoteInfo() {
|
||||||
|
return new GetAccountByHandle(account.acct);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AccountSession getRemoteSession() {
|
||||||
|
return Optional.ofNullable(remoteInfo)
|
||||||
|
.map(AccountSessionManager.getInstance()::tryGetAccount)
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRemoteLoadingFailed() {
|
||||||
|
super.onRemoteLoadingFailed();
|
||||||
|
String prefix = initialSubtitle == null ? "" :
|
||||||
|
initialSubtitle + " " + getContext().getString(R.string.sk_separator) + " ";
|
||||||
|
String str = prefix +
|
||||||
|
getContext().getString(R.string.sk_no_remote_info_hint, getSession().domain);
|
||||||
|
setSubtitle(str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.joinmastodon.android.fragments.account_list;
|
package org.joinmastodon.android.fragments.account_list;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.app.assist.AssistContent;
|
import android.app.assist.AssistContent;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -47,6 +48,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.CallSuper;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.APIRequest;
|
import me.grishka.appkit.api.APIRequest;
|
||||||
@@ -243,10 +245,13 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
|||||||
UiUtils.enablePopupMenuIcons(getActivity(), contextMenu);
|
UiUtils.enablePopupMenuIcons(getActivity(), contextMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
@Override
|
@Override
|
||||||
public void onBind(AccountItem item){
|
public void onBind(AccountItem item){
|
||||||
name.setText(item.parsedName);
|
name.setText(item.parsedName);
|
||||||
username.setText("@"+item.account.acct);
|
username.setText("@"+ (item.account.isRemote
|
||||||
|
? item.account.getFullyQualifiedName()
|
||||||
|
: item.account.acct));
|
||||||
bindRelationship();
|
bindRelationship();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +287,8 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
|||||||
public void onClick(){
|
public void onClick(){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putParcelable("profileAccount", Parcels.wrap(item.account));
|
if (item.account.isRemote) args.putParcelable("remoteAccount", Parcels.wrap(item.account));
|
||||||
|
else args.putParcelable("profileAccount", Parcels.wrap(item.account));
|
||||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
Nav.go(getActivity(), ProfileFragment.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,5 +429,10 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
|||||||
emojiHelper=new CustomEmojiHelper();
|
emojiHelper=new CustomEmojiHelper();
|
||||||
emojiHelper.setText(parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis));
|
emojiHelper.setText(parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object obj) {
|
||||||
|
return obj instanceof AccountItem i && i.account.url.equals(account.url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ public class FollowerListFragment extends AccountRelatedAccountListFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setSubtitle(getResources().getQuantityString(R.plurals.x_followers, (int)(account.followersCount%1000), account.followersCount));
|
setSubtitle(initialSubtitle = getResources().getQuantityString(R.plurals.x_followers, (int)(account.followersCount%1000), account.followersCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||||
return new GetAccountFollowers(account.id, maxID, count);
|
return new GetAccountFollowers(getCurrentInfo().id, maxID, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ public class FollowingListFragment extends AccountRelatedAccountListFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setSubtitle(getResources().getQuantityString(R.plurals.x_following, (int)(account.followingCount%1000), account.followingCount));
|
setSubtitle(initialSubtitle = getResources().getQuantityString(R.plurals.x_following, (int)(account.followingCount%1000), account.followingCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||||
return new GetAccountFollowing(account.id, maxID, count);
|
return new GetAccountFollowing(getCurrentInfo().id, maxID, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,33 +1,173 @@
|
|||||||
package org.joinmastodon.android.fragments.account_list;
|
package org.joinmastodon.android.fragments.account_list;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
public abstract class PaginatedAccountListFragment extends BaseAccountListFragment{
|
public abstract class PaginatedAccountListFragment<T> extends BaseAccountListFragment{
|
||||||
private String nextMaxID;
|
private String nextMaxID;
|
||||||
|
private MastodonAPIRequest<T> remoteInfoRequest;
|
||||||
|
protected boolean doneWithHomeInstance, remoteRequestFailed, startedRemoteLoading, remoteDisabled;
|
||||||
|
protected int localOffset;
|
||||||
|
protected T remoteInfo;
|
||||||
|
|
||||||
public abstract HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count);
|
public abstract HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count);
|
||||||
|
|
||||||
|
protected abstract MastodonAPIRequest<T> loadRemoteInfo();
|
||||||
|
public abstract T getCurrentInfo();
|
||||||
|
public abstract String getRemoteDomain();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
// already have remote info (e.g. from arguments), so no need to fetch it again
|
||||||
|
if (remoteInfo != null) {
|
||||||
|
onRemoteInfoLoaded(remoteInfo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteDisabled = !GlobalUserPreferences.allowRemoteLoading
|
||||||
|
|| getSession().domain.equals(getRemoteDomain());
|
||||||
|
if (!remoteDisabled) {
|
||||||
|
remoteInfoRequest = loadRemoteInfo().setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(T result) {
|
||||||
|
if (getContext() == null) return;
|
||||||
|
onRemoteInfoLoaded(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
if (getContext() == null) return;
|
||||||
|
onRemoteLoadingFailed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
remoteInfoRequest.execRemote(getRemoteDomain(), getRemoteSession());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* override to provide an ideal account session (e.g. if you're logged into the author's remote
|
||||||
|
* account) to make the remote request from. if null is provided, will try to get any session
|
||||||
|
* on the remote domain, or tries the request without authentication.
|
||||||
|
*/
|
||||||
|
protected AccountSession getRemoteSession() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onRemoteInfoLoaded(T info) {
|
||||||
|
this.remoteInfo = info;
|
||||||
|
this.remoteInfoRequest = null;
|
||||||
|
maybeStartLoadingRemote();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onRemoteLoadingFailed() {
|
||||||
|
this.remoteRequestFailed = true;
|
||||||
|
this.remoteInfo = null;
|
||||||
|
this.remoteInfoRequest = null;
|
||||||
|
if (doneWithHomeInstance) dataLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dataLoaded() {
|
||||||
|
super.dataLoaded();
|
||||||
|
footerProgress.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeStartLoadingRemote() {
|
||||||
|
if (startedRemoteLoading || remoteDisabled) return;
|
||||||
|
if (!remoteRequestFailed) {
|
||||||
|
if (data.size() == 0) showProgress();
|
||||||
|
else footerProgress.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
if (doneWithHomeInstance && remoteInfo != null) {
|
||||||
|
startedRemoteLoading = true;
|
||||||
|
loadData(localOffset, itemsPerPage * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRefresh() {
|
||||||
|
localOffset = 0;
|
||||||
|
doneWithHomeInstance = false;
|
||||||
|
startedRemoteLoading = false;
|
||||||
|
super.onRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadData(int offset, int count) {
|
||||||
|
// always subtract the amount loaded through the home instance once loading from remote
|
||||||
|
// since loadData gets called with data.size() (data includes both local and remote)
|
||||||
|
if (doneWithHomeInstance) offset -= localOffset;
|
||||||
|
super.loadData(offset, count);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=onCreateRequest(offset==0 ? null : nextMaxID, count)
|
MastodonAPIRequest<?> request = onCreateRequest(offset==0 ? null : nextMaxID, count)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(HeaderPaginationList<Account> result){
|
public void onSuccess(HeaderPaginationList<Account> result){
|
||||||
|
boolean justRefreshed = !doneWithHomeInstance && offset == 0;
|
||||||
|
Collection<AccountItem> d = justRefreshed ? List.of() : data;
|
||||||
|
|
||||||
if(result.nextPageUri!=null)
|
if(result.nextPageUri!=null)
|
||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
else
|
else
|
||||||
nextMaxID=null;
|
nextMaxID=null;
|
||||||
if (getActivity() == null) return;
|
if (getActivity() == null) return;
|
||||||
onDataLoaded(result.stream().map(AccountItem::new).collect(Collectors.toList()), nextMaxID!=null);
|
List<AccountItem> items = result.stream()
|
||||||
|
.filter(a -> d.size() > 1000 || d.stream()
|
||||||
|
.noneMatch(i -> i.account.url.equals(a.url)))
|
||||||
|
.map(AccountItem::new)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
boolean hasMore = nextMaxID != null;
|
||||||
|
|
||||||
|
if (!hasMore && !doneWithHomeInstance) {
|
||||||
|
// only runs last time data was fetched from the home instance
|
||||||
|
localOffset = d.size() + items.size();
|
||||||
|
doneWithHomeInstance = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onDataLoaded(items, hasMore);
|
||||||
|
if (doneWithHomeInstance) maybeStartLoadingRemote();
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.exec(accountID);
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
if (doneWithHomeInstance) {
|
||||||
|
onRemoteLoadingFailed();
|
||||||
|
onDataLoaded(Collections.emptyList(), false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.onError(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (doneWithHomeInstance && remoteInfo == null) return; // we are waiting
|
||||||
|
if (doneWithHomeInstance && remoteInfo != null) {
|
||||||
|
request.execRemote(getRemoteDomain(), getRemoteSession());
|
||||||
|
} else {
|
||||||
|
request.exec(accountID);
|
||||||
|
}
|
||||||
|
currentRequest = request;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -7,17 +7,23 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetStatusFavorites;
|
import org.joinmastodon.android.api.requests.statuses.GetStatusFavorites;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
public class StatusFavoritesListFragment extends StatusRelatedAccountListFragment{
|
public class StatusFavoritesListFragment extends StatusRelatedAccountListFragment{
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
updateTitle(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateTitle(Status status) {
|
||||||
setTitle(getResources().getQuantityString(R.plurals.x_favorites, (int)(status.favouritesCount%1000), status.favouritesCount));
|
setTitle(getResources().getQuantityString(R.plurals.x_favorites, (int)(status.favouritesCount%1000), status.favouritesCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||||
return new GetStatusFavorites(status.id, maxID, count);
|
return new GetStatusFavorites(getCurrentInfo().id, maxID, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -7,17 +7,23 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetStatusReblogs;
|
import org.joinmastodon.android.api.requests.statuses.GetStatusReblogs;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
public class StatusReblogsListFragment extends StatusRelatedAccountListFragment{
|
public class StatusReblogsListFragment extends StatusRelatedAccountListFragment{
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
updateTitle(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateTitle(Status status) {
|
||||||
setTitle(getResources().getQuantityString(R.plurals.x_reblogs, (int)(status.reblogsCount%1000), status.reblogsCount));
|
setTitle(getResources().getQuantityString(R.plurals.x_reblogs, (int)(status.reblogsCount%1000), status.reblogsCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||||
return new GetStatusReblogs(status.id, maxID, count);
|
return new GetStatusReblogs(getCurrentInfo().id, maxID, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -3,12 +3,27 @@ package org.joinmastodon.android.fragments.account_list;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
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.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
public abstract class StatusRelatedAccountListFragment extends PaginatedAccountListFragment{
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public abstract class StatusRelatedAccountListFragment extends PaginatedAccountListFragment<Status> {
|
||||||
protected Status status;
|
protected Status status;
|
||||||
|
|
||||||
|
protected abstract void updateTitle(Status status);
|
||||||
|
|
||||||
|
protected MastodonAPIRequest<Status> loadRemoteInfo() {
|
||||||
|
String[] parts = status.url.split("/");
|
||||||
|
if (parts.length == 0) return null;
|
||||||
|
return new GetStatusByID(parts[parts.length - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -17,7 +32,7 @@ public abstract class StatusRelatedAccountListFragment extends PaginatedAccountL
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean hasSubtitle(){
|
protected boolean hasSubtitle(){
|
||||||
return false;
|
return remoteRequestFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -28,4 +43,35 @@ public abstract class StatusRelatedAccountListFragment extends PaginatedAccountL
|
|||||||
: '@' + status.account.acct + '/' + status.id)
|
: '@' + status.account.acct + '/' + status.id)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRemoteDomain() {
|
||||||
|
return Uri.parse(status.url).getHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Status getCurrentInfo() {
|
||||||
|
return doneWithHomeInstance && remoteInfo != null ? remoteInfo : status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AccountSession getRemoteSession() {
|
||||||
|
return Optional.ofNullable(remoteInfo)
|
||||||
|
.map(s -> s.account)
|
||||||
|
.map(AccountSessionManager.getInstance()::tryGetAccount)
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRemoteInfoLoaded(Status info) {
|
||||||
|
super.onRemoteInfoLoaded(info);
|
||||||
|
updateTitle(remoteInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRemoteLoadingFailed() {
|
||||||
|
super.onRemoteLoadingFailed();
|
||||||
|
setSubtitle(getContext().getString(R.string.sk_no_remote_info_hint, getSession().domain));
|
||||||
|
updateToolbar();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.os.Bundle;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
|
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
|
||||||
import org.joinmastodon.android.fragments.IsOnTop;
|
|
||||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
@@ -17,7 +16,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
public class DiscoverPostsFragment extends StatusListFragment implements IsOnTop {
|
public class DiscoverPostsFragment extends StatusListFragment {
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_POSTS);
|
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_POSTS);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -39,11 +38,6 @@ public class DiscoverPostsFragment extends StatusListFragment implements IsOnTop
|
|||||||
bannerHelper.maybeAddBanner(contentWrap);
|
bannerHelper.maybeAddBanner(contentWrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOnTop() {
|
|
||||||
return isRecyclerViewOnTop(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Filter.FilterContext getFilterContext() {
|
protected Filter.FilterContext getFilterContext() {
|
||||||
return Filter.FilterContext.PUBLIC;
|
return Filter.FilterContext.PUBLIC;
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.fragments.IsOnTop;
|
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
@@ -43,7 +42,7 @@ import me.grishka.appkit.api.ErrorResponse;
|
|||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class SearchFragment extends BaseStatusListFragment<SearchResult> implements IsOnTop {
|
public class SearchFragment extends BaseStatusListFragment<SearchResult> {
|
||||||
private String currentQuery;
|
private String currentQuery;
|
||||||
private List<StatusDisplayItem> prevDisplayItems;
|
private List<StatusDisplayItem> prevDisplayItems;
|
||||||
private EnumSet<SearchResult.Type> currentFilter=EnumSet.allOf(SearchResult.Type.class);
|
private EnumSet<SearchResult.Type> currentFilter=EnumSet.allOf(SearchResult.Type.class);
|
||||||
@@ -312,11 +311,6 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult> impleme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOnTop() {
|
|
||||||
return isRecyclerViewOnTop(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Uri getWebUri(Uri.Builder base) {
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
Uri.Builder searchUri = base.path("/search");
|
Uri.Builder searchUri = base.path("/search");
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package org.joinmastodon.android.fragments.onboarding;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.assist.AssistContent;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
@@ -24,6 +26,7 @@ import org.joinmastodon.android.ui.DividerItemDecoration;
|
|||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||||
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -38,7 +41,7 @@ import me.grishka.appkit.utils.V;
|
|||||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class InstanceRulesFragment extends ToolbarFragment{
|
public class InstanceRulesFragment extends ToolbarFragment implements ProvidesAssistContent {
|
||||||
private UsableRecyclerView list;
|
private UsableRecyclerView list;
|
||||||
private MergeRecyclerAdapter adapter;
|
private MergeRecyclerAdapter adapter;
|
||||||
private Button btn;
|
private Button btn;
|
||||||
@@ -130,6 +133,15 @@ public class InstanceRulesFragment extends ToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProvideAssistContent(AssistContent assistContent) {
|
||||||
|
assistContent.setWebUri(new Uri.Builder()
|
||||||
|
.scheme("https")
|
||||||
|
.authority(instance.normalizedUri)
|
||||||
|
.path("/about")
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
private class ItemsAdapter extends RecyclerView.Adapter<ItemViewHolder>{
|
private class ItemsAdapter extends RecyclerView.Adapter<ItemViewHolder>{
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package org.joinmastodon.android.model;
|
package org.joinmastodon.android.model;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.ObjectValidationException;
|
import org.joinmastodon.android.api.ObjectValidationException;
|
||||||
import org.joinmastodon.android.api.RequiredField;
|
import org.joinmastodon.android.api.RequiredField;
|
||||||
import org.parceler.Parcel;
|
import org.parceler.Parcel;
|
||||||
@@ -135,6 +138,8 @@ public class Account extends BaseModel implements Searchable{
|
|||||||
|
|
||||||
public List<Role> roles;
|
public List<Role> roles;
|
||||||
|
|
||||||
|
public @Nullable String fqn; // akkoma has this, mastodon't
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getQuery() {
|
public String getQuery() {
|
||||||
return url;
|
return url;
|
||||||
@@ -162,6 +167,7 @@ public class Account extends BaseModel implements Searchable{
|
|||||||
moved.postprocess();
|
moved.postprocess();
|
||||||
if(TextUtils.isEmpty(displayName))
|
if(TextUtils.isEmpty(displayName))
|
||||||
displayName=username;
|
displayName=username;
|
||||||
|
if(fqn == null) fqn = getFullyQualifiedName();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLocal(){
|
public boolean isLocal(){
|
||||||
@@ -173,6 +179,10 @@ public class Account extends BaseModel implements Searchable{
|
|||||||
return parts.length==1 ? null : parts[1];
|
return parts.length==1 ? null : parts[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDomainFromURL() {
|
||||||
|
return Uri.parse(url).getHost();
|
||||||
|
}
|
||||||
|
|
||||||
public String getDisplayUsername(){
|
public String getDisplayUsername(){
|
||||||
return '@'+acct;
|
return '@'+acct;
|
||||||
}
|
}
|
||||||
@@ -181,6 +191,10 @@ public class Account extends BaseModel implements Searchable{
|
|||||||
return '@'+acct.split("@")[0];
|
return '@'+acct.split("@")[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getFullyQualifiedName() {
|
||||||
|
return fqn != null ? fqn : acct.split("@")[0] + "@" + getDomainFromURL();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
return "Account{"+
|
return "Account{"+
|
||||||
|
|||||||
@@ -8,8 +8,17 @@ import java.lang.reflect.Field;
|
|||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.CallSuper;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public abstract class BaseModel implements Cloneable{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* indicates the profile has been fetched from a foreign instance.
|
||||||
|
*
|
||||||
|
* @see MastodonAPIRequest#execRemote
|
||||||
|
*/
|
||||||
|
public transient boolean isRemote;
|
||||||
|
|
||||||
public abstract class BaseModel{
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
public void postprocess() throws ObjectValidationException{
|
public void postprocess() throws ObjectValidationException{
|
||||||
try{
|
try{
|
||||||
@@ -23,4 +32,14 @@ public abstract class BaseModel{
|
|||||||
}
|
}
|
||||||
}catch(IllegalAccessException ignore){}
|
}catch(IllegalAccessException ignore){}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Object clone(){
|
||||||
|
try{
|
||||||
|
return super.clone();
|
||||||
|
}catch(CloneNotSupportedException x){
|
||||||
|
throw new RuntimeException(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import java.net.IDN;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Parcel
|
@Parcel
|
||||||
public class Instance extends BaseModel{
|
public class Instance extends BaseModel{
|
||||||
@@ -88,6 +89,9 @@ public class Instance extends BaseModel{
|
|||||||
|
|
||||||
public PleromaPollLimits pollLimits;
|
public PleromaPollLimits pollLimits;
|
||||||
|
|
||||||
|
/** like uri, but always without scheme and trailing slash */
|
||||||
|
public transient String normalizedUri;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postprocess() throws ObjectValidationException{
|
public void postprocess() throws ObjectValidationException{
|
||||||
super.postprocess();
|
super.postprocess();
|
||||||
@@ -97,6 +101,10 @@ public class Instance extends BaseModel{
|
|||||||
rules=Collections.emptyList();
|
rules=Collections.emptyList();
|
||||||
if(shortDescription==null)
|
if(shortDescription==null)
|
||||||
shortDescription="";
|
shortDescription="";
|
||||||
|
// akkoma says uri is "https://example.social" while just "example.social" on mastodon
|
||||||
|
normalizedUri = uri
|
||||||
|
.replaceFirst("^https://", "")
|
||||||
|
.replaceFirst("/$", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -136,10 +144,26 @@ public class Instance extends BaseModel{
|
|||||||
return ci;
|
return ci;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPleroma() {
|
public boolean isAkkoma() {
|
||||||
return pleroma != null;
|
return pleroma != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasFeature(Feature feature) {
|
||||||
|
Optional<List<String>> pleromaFeatures = Optional.ofNullable(pleroma)
|
||||||
|
.map(p -> p.metadata)
|
||||||
|
.map(m -> m.features);
|
||||||
|
|
||||||
|
return switch (feature) {
|
||||||
|
case BUBBLE_TIMELINE -> pleromaFeatures
|
||||||
|
.map(f -> f.contains("bubble_timeline"))
|
||||||
|
.orElse(false);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Feature {
|
||||||
|
BUBBLE_TIMELINE
|
||||||
|
}
|
||||||
|
|
||||||
@Parcel
|
@Parcel
|
||||||
public static class Rule{
|
public static class Rule{
|
||||||
public String id;
|
public String id;
|
||||||
|
|||||||
@@ -30,10 +30,7 @@ public class PushSubscription extends BaseModel implements Cloneable{
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public PushSubscription clone(){
|
public PushSubscription clone(){
|
||||||
PushSubscription copy=null;
|
PushSubscription copy=(PushSubscription) super.clone();
|
||||||
try{
|
|
||||||
copy=(PushSubscription) super.clone();
|
|
||||||
}catch(CloneNotSupportedException ignore){}
|
|
||||||
copy.alerts=alerts.clone();
|
copy.alerts=alerts.clone();
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import java.lang.reflect.Type;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
@Parcel
|
@Parcel
|
||||||
public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
||||||
@RequiredField
|
@RequiredField
|
||||||
@@ -191,7 +193,6 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class StatusDeserializer implements JsonDeserializer<Status> {
|
public static class StatusDeserializer implements JsonDeserializer<Status> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Status deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
public Status deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||||
JsonObject obj = json.getAsJsonObject();
|
JsonObject obj = json.getAsJsonObject();
|
||||||
|
|||||||
@@ -259,13 +259,13 @@ public class TimelineDefinition {
|
|||||||
public boolean isCompatible(AccountSession session) {
|
public boolean isCompatible(AccountSession session) {
|
||||||
// still enabling the bubble timeline for all pleroma/akkoma instances since i know of
|
// still enabling the bubble timeline for all pleroma/akkoma instances since i know of
|
||||||
// at least one instance that supports it, but doesn't list "bubble_timeline"
|
// at least one instance that supports it, but doesn't list "bubble_timeline"
|
||||||
return session.getInstance().map(Instance::isPleroma).orElse(false);
|
return session.getInstance().map(Instance::isAkkoma).orElse(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean wantsDefault(AccountSession session) {
|
public boolean wantsDefault(AccountSession session) {
|
||||||
return session.getInstance()
|
return session.getInstance()
|
||||||
.map(i -> i.isPleroma() && i.pleroma.metadata.features.contains("bubble_timeline"))
|
.map(i -> i.hasFeature(Instance.Feature.BUBBLE_TIMELINE))
|
||||||
.orElse(false);
|
.orElse(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -58,17 +58,18 @@ import me.grishka.appkit.views.UsableRecyclerView;
|
|||||||
public class AccountSwitcherSheet extends BottomSheet{
|
public class AccountSwitcherSheet extends BottomSheet{
|
||||||
private final Activity activity;
|
private final Activity activity;
|
||||||
private final HomeFragment fragment;
|
private final HomeFragment fragment;
|
||||||
private final BiConsumer<String, Boolean> onClick;
|
|
||||||
private final boolean externalShare, openInApp;
|
private final boolean externalShare, openInApp;
|
||||||
|
private BiConsumer<String, Boolean> onClick;
|
||||||
private UsableRecyclerView list;
|
private UsableRecyclerView list;
|
||||||
private List<WrappedAccount> accounts;
|
private List<WrappedAccount> accounts;
|
||||||
private ListImageLoaderWrapper imgLoader;
|
private ListImageLoaderWrapper imgLoader;
|
||||||
|
private AccountsAdapter accountsAdapter;
|
||||||
|
|
||||||
public AccountSwitcherSheet(@NonNull Activity activity, @Nullable HomeFragment fragment){
|
public AccountSwitcherSheet(@NonNull Activity activity, @Nullable HomeFragment fragment){
|
||||||
this(activity, fragment, false, false, null);
|
this(activity, fragment, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountSwitcherSheet(@NonNull Activity activity, @Nullable HomeFragment fragment, boolean externalShare, boolean openInApp, BiConsumer<String, Boolean> onClick){
|
public AccountSwitcherSheet(@NonNull Activity activity, @Nullable HomeFragment fragment, boolean externalShare, boolean openInApp){
|
||||||
super(activity);
|
super(activity);
|
||||||
this.activity=activity;
|
this.activity=activity;
|
||||||
this.fragment=fragment;
|
this.fragment=fragment;
|
||||||
@@ -101,10 +102,10 @@ public class AccountSwitcherSheet extends BottomSheet{
|
|||||||
setOnDismissListener((d) -> activity.finish());
|
setOnDismissListener((d) -> activity.finish());
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter.addAdapter(new AccountsAdapter());
|
adapter.addAdapter(accountsAdapter = new AccountsAdapter());
|
||||||
|
|
||||||
if (!externalShare) {
|
if (!externalShare) {
|
||||||
adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(makeSimpleListItem(R.string.add_account, R.drawable.ic_add_24px), () -> {
|
adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(makeSimpleListItem(R.string.add_account, R.drawable.ic_fluent_add_24_regular), () -> {
|
||||||
Nav.go(activity, CustomWelcomeFragment.class, null);
|
Nav.go(activity, CustomWelcomeFragment.class, null);
|
||||||
dismiss();
|
dismiss();
|
||||||
}));
|
}));
|
||||||
@@ -122,6 +123,10 @@ public class AccountSwitcherSheet extends BottomSheet{
|
|||||||
UiUtils.getThemeColor(activity, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());
|
UiUtils.getThemeColor(activity, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setOnClick(BiConsumer<String, Boolean> onClick) {
|
||||||
|
this.onClick = onClick;
|
||||||
|
}
|
||||||
|
|
||||||
private void confirmLogOut(String accountID){
|
private void confirmLogOut(String accountID){
|
||||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
new M3AlertDialogBuilder(activity)
|
new M3AlertDialogBuilder(activity)
|
||||||
@@ -201,7 +206,10 @@ public class AccountSwitcherSheet extends BottomSheet{
|
|||||||
activity.finish();
|
activity.finish();
|
||||||
activity.startActivity(new Intent(activity, MainActivity.class));
|
activity.startActivity(new Intent(activity, MainActivity.class));
|
||||||
} else {
|
} else {
|
||||||
dismiss();
|
accounts.stream().filter(w -> accountID.equals(w.session.getID())).findAny().ifPresent(w -> {
|
||||||
|
accountsAdapter.notifyItemRemoved(accounts.indexOf(w));
|
||||||
|
accounts.remove(w);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.app.Dialog;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
@@ -17,7 +16,6 @@ import android.view.animation.AlphaAnimation;
|
|||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
@@ -56,8 +54,8 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{
|
public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{
|
||||||
private final TextView reply, boost, favorite, bookmark;
|
private final TextView replies, boosts, favorites;
|
||||||
private final ImageView share;
|
private final View reply, boost, favorite, share, bookmark;
|
||||||
private static final Animation opacityOut, opacityIn;
|
private static final Animation opacityOut, opacityIn;
|
||||||
|
|
||||||
private View touchingView = null;
|
private View touchingView = null;
|
||||||
@@ -91,22 +89,16 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
|
|
||||||
public Holder(Activity activity, ViewGroup parent){
|
public Holder(Activity activity, ViewGroup parent){
|
||||||
super(activity, R.layout.display_item_footer, parent);
|
super(activity, R.layout.display_item_footer, parent);
|
||||||
reply=findViewById(R.id.reply);
|
replies=findViewById(R.id.reply);
|
||||||
boost=findViewById(R.id.boost);
|
boosts=findViewById(R.id.boost);
|
||||||
favorite=findViewById(R.id.favorite);
|
favorites=findViewById(R.id.favorite);
|
||||||
bookmark=findViewById(R.id.bookmark);
|
|
||||||
share=findViewById(R.id.share);
|
reply=findViewById(R.id.reply_btn);
|
||||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
|
boost=findViewById(R.id.boost_btn);
|
||||||
UiUtils.fixCompoundDrawableTintOnAndroid6(reply);
|
favorite=findViewById(R.id.favorite_btn);
|
||||||
UiUtils.fixCompoundDrawableTintOnAndroid6(boost);
|
share=findViewById(R.id.share_btn);
|
||||||
UiUtils.fixCompoundDrawableTintOnAndroid6(favorite);
|
bookmark=findViewById(R.id.bookmark_btn);
|
||||||
UiUtils.fixCompoundDrawableTintOnAndroid6(bookmark);
|
|
||||||
}
|
|
||||||
View reply=findViewById(R.id.reply_btn);
|
|
||||||
View boost=findViewById(R.id.boost_btn);
|
|
||||||
View favorite=findViewById(R.id.favorite_btn);
|
|
||||||
View share=findViewById(R.id.share_btn);
|
|
||||||
View bookmark=findViewById(R.id.bookmark_btn);
|
|
||||||
reply.setOnTouchListener(this::onButtonTouch);
|
reply.setOnTouchListener(this::onButtonTouch);
|
||||||
reply.setOnClickListener(this::onReplyClick);
|
reply.setOnClickListener(this::onReplyClick);
|
||||||
reply.setOnLongClickListener(this::onReplyLongClick);
|
reply.setOnLongClickListener(this::onReplyLongClick);
|
||||||
@@ -131,18 +123,33 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(FooterStatusDisplayItem item){
|
public void onBind(FooterStatusDisplayItem item){
|
||||||
bindButton(reply, item.status.repliesCount);
|
bindText(replies, item.status.repliesCount);
|
||||||
bindButton(boost, item.status.reblogsCount);
|
bindText(boosts, item.status.reblogsCount);
|
||||||
bindButton(favorite, item.status.favouritesCount);
|
bindText(favorites, item.status.favouritesCount);
|
||||||
reply.setSelected(item.status.repliesCount > 0);
|
// in thread view, direct descendant posts display one direct reply to themselves,
|
||||||
|
// hence in that case displaying whether there is another reply
|
||||||
|
int compareTo = item.isMainStatus || !item.hasDescendantNeighbor ? 0 : 1;
|
||||||
|
reply.setSelected(item.status.repliesCount > compareTo);
|
||||||
boost.setSelected(item.status.reblogged);
|
boost.setSelected(item.status.reblogged);
|
||||||
favorite.setSelected(item.status.favourited);
|
favorite.setSelected(item.status.favourited);
|
||||||
bookmark.setSelected(item.status.bookmarked);
|
bookmark.setSelected(item.status.bookmarked);
|
||||||
boost.setEnabled(item.status.visibility==StatusPrivacy.PUBLIC || item.status.visibility==StatusPrivacy.UNLISTED || item.status.visibility==StatusPrivacy.LOCAL
|
boost.setEnabled(item.status.visibility==StatusPrivacy.PUBLIC || item.status.visibility==StatusPrivacy.UNLISTED || item.status.visibility==StatusPrivacy.LOCAL
|
||||||
|| (item.status.visibility==StatusPrivacy.PRIVATE && item.status.account.id.equals(AccountSessionManager.getInstance().getAccount(item.accountID).self.id)));
|
|| (item.status.visibility==StatusPrivacy.PRIVATE && item.status.account.id.equals(AccountSessionManager.getInstance().getAccount(item.accountID).self.id)));
|
||||||
|
|
||||||
|
int nextPos = getAbsoluteAdapterPosition() + 1;
|
||||||
|
boolean nextIsWarning = item.parentFragment.getDisplayItems().size() > nextPos &&
|
||||||
|
item.parentFragment.getDisplayItems().get(nextPos) instanceof WarningFilteredStatusDisplayItem;
|
||||||
|
boolean condenseBottom = !item.isMainStatus && item.hasDescendantNeighbor &&
|
||||||
|
!nextIsWarning;
|
||||||
|
|
||||||
|
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) itemView.getLayoutParams();
|
||||||
|
params.setMargins(params.leftMargin, params.topMargin, params.rightMargin,
|
||||||
|
condenseBottom ? V.dp(-5) : 0);
|
||||||
|
|
||||||
|
itemView.requestLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindButton(TextView btn, long count){
|
private void bindText(TextView btn, long count){
|
||||||
if(GlobalUserPreferences.showInteractionCounts && count>0 && !item.hideCounts){
|
if(GlobalUserPreferences.showInteractionCounts && count>0 && !item.hideCounts){
|
||||||
btn.setText(UiUtils.abbreviateNumber(count));
|
btn.setText(UiUtils.abbreviateNumber(count));
|
||||||
btn.setCompoundDrawablePadding(V.dp(8));
|
btn.setCompoundDrawablePadding(V.dp(8));
|
||||||
@@ -166,8 +173,9 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
} else if (action == MotionEvent.ACTION_DOWN) {
|
} else if (action == MotionEvent.ACTION_DOWN) {
|
||||||
longClickPerformed = false;
|
longClickPerformed = false;
|
||||||
touchingView = v;
|
touchingView = v;
|
||||||
// 20dp to center in middle of icon, because: (icon width = 24dp) / 2 + (paddingStart = 8dp)
|
// 28dp to center in middle of icon, because:
|
||||||
v.setPivotX(V.dp(20));
|
// (icon width = 24dp) / 2 + (paddingStart = 8dp) + (paddingHorizontal = 8dp)
|
||||||
|
v.setPivotX(UiUtils.sp(v.getContext(), 28));
|
||||||
v.animate().scaleX(0.85f).scaleY(0.85f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(75).start();
|
v.animate().scaleX(0.85f).scaleY(0.85f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(75).start();
|
||||||
if (disabled) return true;
|
if (disabled) return true;
|
||||||
v.postDelayed(longClickRunnable, ViewConfiguration.getLongPressTimeout());
|
v.postDelayed(longClickRunnable, ViewConfiguration.getLongPressTimeout());
|
||||||
@@ -211,7 +219,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
|
|
||||||
private void boostConsumer(View v, Status r) {
|
private void boostConsumer(View v, Status r) {
|
||||||
v.startAnimation(opacityIn);
|
v.startAnimation(opacityIn);
|
||||||
bindButton(boost, r.reblogsCount);
|
bindText(boosts, r.reblogsCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean onBoostLongClick(View v){
|
private boolean onBoostLongClick(View v){
|
||||||
@@ -300,7 +308,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
favorite.setSelected(!item.status.favourited);
|
favorite.setSelected(!item.status.favourited);
|
||||||
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setFavorited(item.status, !item.status.favourited, r->{
|
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setFavorited(item.status, !item.status.favourited, r->{
|
||||||
v.startAnimation(opacityIn);
|
v.startAnimation(opacityIn);
|
||||||
bindButton(favorite, r.favouritesCount);
|
bindText(favorites, r.favouritesCount);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,9 +73,9 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
|||||||
public void updateVisibility(StatusPrivacy visibility) {
|
public void updateVisibility(StatusPrivacy visibility) {
|
||||||
this.visibility = visibility;
|
this.visibility = visibility;
|
||||||
this.iconEnd = visibility != null ? switch (visibility) {
|
this.iconEnd = visibility != null ? switch (visibility) {
|
||||||
case PUBLIC -> R.drawable.ic_fluent_earth_20_regular;
|
case PUBLIC -> R.drawable.ic_fluent_earth_20sp_regular;
|
||||||
case UNLISTED -> R.drawable.ic_fluent_lock_open_20_regular;
|
case UNLISTED -> R.drawable.ic_fluent_lock_open_20sp_regular;
|
||||||
case PRIVATE -> R.drawable.ic_fluent_lock_closed_20_filled;
|
case PRIVATE -> R.drawable.ic_fluent_lock_closed_20sp_filled;
|
||||||
default -> 0;
|
default -> 0;
|
||||||
} : 0;
|
} : 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
||||||
@@ -22,7 +21,6 @@ import org.joinmastodon.android.model.Account;
|
|||||||
import org.joinmastodon.android.model.Attachment;
|
import org.joinmastodon.android.model.Attachment;
|
||||||
import org.joinmastodon.android.model.DisplayItemsParent;
|
import org.joinmastodon.android.model.DisplayItemsParent;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Instance;
|
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.model.Poll;
|
import org.joinmastodon.android.model.Poll;
|
||||||
import org.joinmastodon.android.model.ScheduledStatus;
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
@@ -49,6 +47,23 @@ public abstract class StatusDisplayItem{
|
|||||||
public final BaseStatusListFragment parentFragment;
|
public final BaseStatusListFragment parentFragment;
|
||||||
public boolean inset;
|
public boolean inset;
|
||||||
public int index;
|
public int index;
|
||||||
|
public boolean
|
||||||
|
hasDescendantNeighbor = false,
|
||||||
|
hasAncestoringNeighbor = false,
|
||||||
|
isMainStatus = true,
|
||||||
|
isDirectDescendant = false;
|
||||||
|
|
||||||
|
public void setAncestryInfo(
|
||||||
|
boolean hasDescendantNeighbor,
|
||||||
|
boolean hasAncestoringNeighbor,
|
||||||
|
boolean isMainStatus,
|
||||||
|
boolean isDirectDescendant
|
||||||
|
) {
|
||||||
|
this.hasDescendantNeighbor = hasDescendantNeighbor;
|
||||||
|
this.hasAncestoringNeighbor = hasAncestoringNeighbor;
|
||||||
|
this.isMainStatus = isMainStatus;
|
||||||
|
this.isDirectDescendant = isDirectDescendant;
|
||||||
|
}
|
||||||
|
|
||||||
public StatusDisplayItem(String parentID, BaseStatusListFragment parentFragment){
|
public StatusDisplayItem(String parentID, BaseStatusListFragment parentFragment){
|
||||||
this.parentID=parentID;
|
this.parentID=parentID;
|
||||||
@@ -114,7 +129,7 @@ public abstract class StatusDisplayItem{
|
|||||||
: fragment.getString(R.string.in_reply_to, account.displayName);
|
: fragment.getString(R.string.in_reply_to, account.displayName);
|
||||||
replyLine = new ReblogOrReplyLineStatusDisplayItem(
|
replyLine = new ReblogOrReplyLineStatusDisplayItem(
|
||||||
parentID, fragment, text, account == null ? List.of() : account.emojis,
|
parentID, fragment, text, account == null ? List.of() : account.emojis,
|
||||||
R.drawable.ic_fluent_arrow_reply_20_filled, null, null, fullText
|
R.drawable.ic_fluent_arrow_reply_20sp_filled, null, null, fullText
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +137,7 @@ public abstract class StatusDisplayItem{
|
|||||||
boolean isOwnPost = AccountSessionManager.getInstance().isSelf(fragment.getAccountID(), status.account);
|
boolean isOwnPost = AccountSessionManager.getInstance().isSelf(fragment.getAccountID(), status.account);
|
||||||
String fullText = fragment.getString(R.string.user_boosted, status.account.displayName);
|
String fullText = fragment.getString(R.string.user_boosted, status.account.displayName);
|
||||||
String text = GlobalUserPreferences.compactReblogReplyLine && replyLine != null ? status.account.displayName : fullText;
|
String text = GlobalUserPreferences.compactReblogReplyLine && replyLine != null ? status.account.displayName : fullText;
|
||||||
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, text, status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20_filled, isOwnPost ? status.visibility : null, i->{
|
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, text, status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20sp_filled, isOwnPost ? status.visibility : null, i->{
|
||||||
args.putParcelable("profileAccount", Parcels.wrap(status.account));
|
args.putParcelable("profileAccount", Parcels.wrap(status.account));
|
||||||
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
|
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
|
||||||
}, fullText));
|
}, fullText));
|
||||||
@@ -137,7 +152,7 @@ public abstract class StatusDisplayItem{
|
|||||||
// post contains a hashtag the user is following
|
// post contains a hashtag the user is following
|
||||||
.ifPresent(hashtag -> items.add(new ReblogOrReplyLineStatusDisplayItem(
|
.ifPresent(hashtag -> items.add(new ReblogOrReplyLineStatusDisplayItem(
|
||||||
parentID, fragment, hashtag.name, List.of(),
|
parentID, fragment, hashtag.name, List.of(),
|
||||||
R.drawable.ic_fluent_number_symbol_20_filled, null,
|
R.drawable.ic_fluent_number_symbol_20sp_filled, null,
|
||||||
i -> {
|
i -> {
|
||||||
args.putString("hashtag", hashtag.name);
|
args.putString("hashtag", hashtag.name);
|
||||||
Nav.go(fragment.getActivity(), HashtagTimelineFragment.class, args);
|
Nav.go(fragment.getActivity(), HashtagTimelineFragment.class, args);
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
spoilerEmojiHelper.setText(parsedSpoilerText);
|
spoilerEmojiHelper.setText(parsedSpoilerText);
|
||||||
}
|
}
|
||||||
session = AccountSessionManager.getInstance().getAccount(parentFragment.getAccountID());
|
session = AccountSessionManager.getInstance().getAccount(parentFragment.getAccountID());
|
||||||
UiUtils.loadMaxWidth(parentFragment.getContext());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTranslationShown(boolean translationShown) {
|
public void setTranslationShown(boolean translationShown) {
|
||||||
@@ -237,6 +236,15 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
readMore.setText(item.status.textExpanded ? R.string.sk_collapse : R.string.sk_expand);
|
readMore.setText(item.status.textExpanded ? R.string.sk_collapse : R.string.sk_expand);
|
||||||
spaceBelowText.setVisibility(translateVisible ? View.VISIBLE : View.GONE);
|
spaceBelowText.setVisibility(translateVisible ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
// remove additional padding when (transparently padded) translate button is visible
|
||||||
|
int nextPos = getAbsoluteAdapterPosition() + 1;
|
||||||
|
boolean nextIsFooter = item.parentFragment.getDisplayItems().size() > nextPos &&
|
||||||
|
item.parentFragment.getDisplayItems().get(nextPos) instanceof FooterStatusDisplayItem;
|
||||||
|
int bottomPadding = (translateVisible && nextIsFooter) ? 0
|
||||||
|
: nextIsFooter ? V.dp(6)
|
||||||
|
: V.dp(12);
|
||||||
|
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), bottomPadding);
|
||||||
|
|
||||||
if (!GlobalUserPreferences.collapseLongPosts) {
|
if (!GlobalUserPreferences.collapseLongPosts) {
|
||||||
textScrollView.setLayoutParams(wrapParams);
|
textScrollView.setLayoutParams(wrapParams);
|
||||||
readMore.setVisibility(View.GONE);
|
readMore.setVisibility(View.GONE);
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import android.graphics.Canvas;
|
|||||||
import android.graphics.CornerPathEffect;
|
import android.graphics.CornerPathEffect;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
import android.graphics.Rect;
|
import android.os.Build;
|
||||||
import android.graphics.RectF;
|
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.view.GestureDetector;
|
import android.view.GestureDetector;
|
||||||
@@ -33,7 +32,8 @@ public class ClickableLinksDelegate {
|
|||||||
hlPaint=new Paint();
|
hlPaint=new Paint();
|
||||||
hlPaint.setAntiAlias(true);
|
hlPaint.setAntiAlias(true);
|
||||||
hlPaint.setPathEffect(new CornerPathEffect(V.dp(3)));
|
hlPaint.setPathEffect(new CornerPathEffect(V.dp(3)));
|
||||||
// view.setHighlightColor(view.getResources().getColor(android.R.color.holo_blue_light));
|
hlPaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||||
|
hlPaint.setStrokeWidth(V.dp(4));
|
||||||
gestureDetector = new GestureDetector(view.getContext(), new LinkGestureListener(), view.getHandler());
|
gestureDetector = new GestureDetector(view.getContext(), new LinkGestureListener(), view.getHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ public class ClickableLinksDelegate {
|
|||||||
public void onDraw(Canvas canvas){
|
public void onDraw(Canvas canvas){
|
||||||
if(hlPath!=null){
|
if(hlPath!=null){
|
||||||
canvas.save();
|
canvas.save();
|
||||||
canvas.translate(0, view.getPaddingTop());
|
canvas.translate(view.getTotalPaddingLeft(), view.getTotalPaddingTop());
|
||||||
canvas.drawPath(hlPath, hlPaint);
|
canvas.drawPath(hlPath, hlPaint);
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
}
|
}
|
||||||
@@ -73,59 +73,29 @@ public class ClickableLinksDelegate {
|
|||||||
private class LinkGestureListener extends GestureDetector.SimpleOnGestureListener {
|
private class LinkGestureListener extends GestureDetector.SimpleOnGestureListener {
|
||||||
@Override
|
@Override
|
||||||
public boolean onDown(@NonNull MotionEvent event) {
|
public boolean onDown(@NonNull MotionEvent event) {
|
||||||
int line=-1;
|
int padLeft=view.getTotalPaddingLeft(), padRight=view.getTotalPaddingRight(), padTop=view.getTotalPaddingTop(), padBottom=view.getTotalPaddingBottom();
|
||||||
Rect rect=new Rect();
|
float x=event.getX(), y=event.getY();
|
||||||
Layout l=view.getLayout();
|
if(x<padLeft || y<padTop || x>view.getWidth()-padRight || y>view.getHeight()-padBottom)
|
||||||
for(int i=0;i<l.getLineCount();i++){
|
|
||||||
view.getLineBounds(i, rect);
|
|
||||||
if(rect.contains((int)event.getX(), (int)event.getY())){
|
|
||||||
line=i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(line==-1){
|
|
||||||
return false;
|
return false;
|
||||||
}
|
x-=padLeft;
|
||||||
|
y-=padTop;
|
||||||
|
Layout l=view.getLayout();
|
||||||
|
int line=l.getLineForVertical(Math.round(y));
|
||||||
|
int position=l.getOffsetForHorizontal(line, x);
|
||||||
|
|
||||||
CharSequence text=view.getText();
|
CharSequence text=view.getText();
|
||||||
if(text instanceof Spanned s){
|
if(text instanceof Spanned s){
|
||||||
LinkSpan[] spans=s.getSpans(0, s.length()-1, LinkSpan.class);
|
LinkSpan[] spans=s.getSpans(0, s.length()-1, LinkSpan.class);
|
||||||
if(spans.length>0){
|
for(LinkSpan span:spans){
|
||||||
for(LinkSpan span:spans){
|
int start=s.getSpanStart(span);
|
||||||
int start=s.getSpanStart(span);
|
int end=s.getSpanEnd(span);
|
||||||
int end=s.getSpanEnd(span);
|
if(start<=position && end>position){
|
||||||
int lstart=l.getLineForOffset(start);
|
selectedSpan=span;
|
||||||
int lend=l.getLineForOffset(end);
|
hlPath=new Path();
|
||||||
if(line>=lstart && line<=lend){
|
l.getSelectionPath(start, end, hlPath);
|
||||||
if(line==lstart && event.getX()-view.getPaddingLeft()<l.getPrimaryHorizontal(start)){
|
hlPaint.setColor((span.getColor() & 0x00FFFFFF) | 0x33000000);
|
||||||
continue;
|
view.invalidate();
|
||||||
}
|
return true;
|
||||||
if(line==lend && event.getX()-view.getPaddingLeft()>l.getPrimaryHorizontal(end)){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
hlPath=new Path();
|
|
||||||
selectedSpan=span;
|
|
||||||
hlPaint.setColor((span.getColor() & 0x00FFFFFF) | 0x33000000);
|
|
||||||
//l.getSelectionPath(start, end, hlPath);
|
|
||||||
for(int j=lstart;j<=lend;j++){
|
|
||||||
Rect bounds=new Rect();
|
|
||||||
l.getLineBounds(j, bounds);
|
|
||||||
//bounds.left+=view.getPaddingLeft();
|
|
||||||
if(j==lstart){
|
|
||||||
bounds.left=Math.round(l.getPrimaryHorizontal(start));
|
|
||||||
}
|
|
||||||
if(j==lend){
|
|
||||||
bounds.right=Math.round(l.getPrimaryHorizontal(end));
|
|
||||||
}else{
|
|
||||||
CharSequence lineChars=view.getText().subSequence(l.getLineStart(j), l.getLineEnd(j));
|
|
||||||
bounds.right=Math.round(view.getPaint().measureText(lineChars.toString()))/*+view.getPaddingRight()*/;
|
|
||||||
}
|
|
||||||
bounds.inset(V.dp(-2), V.dp(-2));
|
|
||||||
hlPath.addRect(new RectF(bounds), Path.Direction.CW);
|
|
||||||
}
|
|
||||||
hlPath.offset(view.getPaddingLeft(), 0);
|
|
||||||
view.invalidate();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import android.content.DialogInterface;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
@@ -37,6 +38,7 @@ import android.text.SpannableStringBuilder;
|
|||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
import android.view.HapticFeedbackConstants;
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
@@ -54,6 +56,7 @@ import org.joinmastodon.android.E;
|
|||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.MastodonApp;
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||||
import org.joinmastodon.android.api.StatusInteractionController;
|
import org.joinmastodon.android.api.StatusInteractionController;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked;
|
import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||||
@@ -98,9 +101,9 @@ import org.parceler.Parcels;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.IDN;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
@@ -140,15 +143,11 @@ public class UiUtils {
|
|||||||
private static Handler mainHandler = new Handler(Looper.getMainLooper());
|
private static Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
private static final DateTimeFormatter DATE_FORMATTER_SHORT_WITH_YEAR = DateTimeFormatter.ofPattern("d MMM uuuu"), DATE_FORMATTER_SHORT = DateTimeFormatter.ofPattern("d MMM");
|
private static final DateTimeFormatter DATE_FORMATTER_SHORT_WITH_YEAR = DateTimeFormatter.ofPattern("d MMM uuuu"), DATE_FORMATTER_SHORT = DateTimeFormatter.ofPattern("d MMM");
|
||||||
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT);
|
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT);
|
||||||
public static int MAX_WIDTH;
|
public static int MAX_WIDTH, SCROLL_TO_TOP_DELTA;
|
||||||
|
|
||||||
private UiUtils() {
|
private UiUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void loadMaxWidth(Context ctx) {
|
|
||||||
if (MAX_WIDTH == 0) MAX_WIDTH = (int) ctx.getResources().getDimension(R.dimen.layout_max_width);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void launchWebBrowser(Context context, String url) {
|
public static void launchWebBrowser(Context context, String url) {
|
||||||
try {
|
try {
|
||||||
if (GlobalUserPreferences.useCustomTabs) {
|
if (GlobalUserPreferences.useCustomTabs) {
|
||||||
@@ -897,6 +896,10 @@ public class UiUtils {
|
|||||||
|
|
||||||
ColorPalette palette = ColorPalette.palettes.get(GlobalUserPreferences.color);
|
ColorPalette palette = ColorPalette.palettes.get(GlobalUserPreferences.color);
|
||||||
if (palette != null) palette.apply(context);
|
if (palette != null) palette.apply(context);
|
||||||
|
|
||||||
|
Resources res = context.getResources();
|
||||||
|
MAX_WIDTH = (int) res.getDimension(R.dimen.layout_max_width);
|
||||||
|
SCROLL_TO_TOP_DELTA = (int) res.getDimension(R.dimen.scroll_to_top_delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isDarkTheme() {
|
public static boolean isDarkTheme() {
|
||||||
@@ -905,6 +908,32 @@ public class UiUtils {
|
|||||||
return theme == GlobalUserPreferences.ThemePreference.DARK;
|
return theme == GlobalUserPreferences.ThemePreference.DARK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Optional<Pair<String, Optional<String>>> parseFediverseHandle(String maybeFediHandle) {
|
||||||
|
// https://stackoverflow.com/a/26987741, except i put a + here ... v
|
||||||
|
String domainRegex = "^(((?!-))(xn--|_)?[a-z0-9-]{0,61}[a-z0-9]\\.)+(xn--)?([a-z0-9][a-z0-9\\-]{0,60}|[a-z0-9-]{1,30}\\.[a-z]{2,})$";
|
||||||
|
if (maybeFediHandle.toLowerCase().startsWith("mailto:")) {
|
||||||
|
maybeFediHandle = maybeFediHandle.substring("mailto:".length());
|
||||||
|
}
|
||||||
|
List<String> parts = Arrays.stream(maybeFediHandle.split("@"))
|
||||||
|
.filter(part -> !part.isEmpty())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (parts.size() == 0 || !parts.get(0).matches("^[^/\\s]+$")) {
|
||||||
|
return Optional.empty();
|
||||||
|
} else if (parts.size() == 2) {
|
||||||
|
try {
|
||||||
|
String domain = IDN.toASCII(parts.get(1));
|
||||||
|
if (!domain.matches(domainRegex)) return Optional.empty();
|
||||||
|
return Optional.of(Pair.create(parts.get(0), Optional.of(parts.get(1))));
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
} else if (maybeFediHandle.startsWith("@")) {
|
||||||
|
return Optional.of(Pair.create(parts.get(0), Optional.empty()));
|
||||||
|
} else {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// https://mastodon.foo.bar/@User
|
// https://mastodon.foo.bar/@User
|
||||||
// https://mastodon.foo.bar/@User/43456787654678
|
// https://mastodon.foo.bar/@User/43456787654678
|
||||||
// https://pleroma.foo.bar/users/User
|
// https://pleroma.foo.bar/users/User
|
||||||
@@ -921,7 +950,7 @@ public class UiUtils {
|
|||||||
// https://foo.microblog.pub/o/5b64045effd24f48a27d7059f6cb38f5
|
// https://foo.microblog.pub/o/5b64045effd24f48a27d7059f6cb38f5
|
||||||
//
|
//
|
||||||
// COPIED FROM https://github.com/tuskyapp/Tusky/blob/develop/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.kt
|
// COPIED FROM https://github.com/tuskyapp/Tusky/blob/develop/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.kt
|
||||||
public static boolean looksLikeMastodonUrl(String urlString) {
|
public static boolean looksLikeFediverseUrl(String urlString) {
|
||||||
URI uri;
|
URI uri;
|
||||||
try {
|
try {
|
||||||
uri = new URI(urlString);
|
uri = new URI(urlString);
|
||||||
@@ -1088,6 +1117,65 @@ public class UiUtils {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean acctMatches(String accountID, String acct, String queriedUsername, @Nullable String queriedDomain) {
|
||||||
|
// check if the username matches
|
||||||
|
if (!acct.split("@")[0].equalsIgnoreCase(queriedUsername)) return false;
|
||||||
|
|
||||||
|
boolean resultOnHomeInstance = !acct.contains("@");
|
||||||
|
if (resultOnHomeInstance) {
|
||||||
|
// acct is formatted like 'someone'
|
||||||
|
// only allow home instance result if query didn't specify a domain,
|
||||||
|
// or the specified domain does, in fact, match the account session's domain
|
||||||
|
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
|
return queriedDomain == null || session.domain.equalsIgnoreCase(queriedDomain);
|
||||||
|
} else if (queriedDomain == null) {
|
||||||
|
// accept whatever result we have as there's no queried domain to compare to
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// acct is formatted like 'someone@somewhere'
|
||||||
|
return acct.split("@")[1].equalsIgnoreCase(queriedDomain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void lookupAccountHandle(Context context, String accountID, String query, BiConsumer<Class<? extends Fragment>, Bundle> go) {
|
||||||
|
parseFediverseHandle(query).ifPresentOrElse(
|
||||||
|
handle -> lookupAccountHandle(context, accountID, handle, go),
|
||||||
|
() -> go.accept(null, null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public static void lookupAccountHandle(Context context, String accountID, Pair<String, Optional<String>> queryHandle, BiConsumer<Class<? extends Fragment>, Bundle> go) {
|
||||||
|
String fullHandle = ("@" + queryHandle.first) + (queryHandle.second.map(domain -> "@" + domain).orElse(""));
|
||||||
|
new GetSearchResults(fullHandle, GetSearchResults.Type.ACCOUNTS, true)
|
||||||
|
.setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SearchResults results) {
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
Optional<Account> account = results.accounts.stream()
|
||||||
|
.filter(a -> acctMatches(accountID, a.acct, queryHandle.first, queryHandle.second.orElse(null)))
|
||||||
|
.findAny();
|
||||||
|
if (account.isPresent()) {
|
||||||
|
args.putParcelable("profileAccount", Parcels.wrap(account.get()));
|
||||||
|
go.accept(ProfileFragment.class, args);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show();
|
||||||
|
args.putString("error", context.getString(R.string.sk_resource_not_found));
|
||||||
|
go.accept(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
if (error instanceof MastodonErrorResponse e) {
|
||||||
|
args.putString("error", e.error);
|
||||||
|
args.putInt("httpStatus", e.httpStatus);
|
||||||
|
}
|
||||||
|
go.accept(null, args);
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
public static void lookupURL(Context context, String accountID, String url, boolean launchBrowser, BiConsumer<Class<? extends Fragment>, Bundle> go) {
|
public static void lookupURL(Context context, String accountID, String url, boolean launchBrowser, BiConsumer<Class<? extends Fragment>, Bundle> go) {
|
||||||
Uri uri = Uri.parse(url);
|
Uri uri = Uri.parse(url);
|
||||||
List<String> path = uri.getPathSegments();
|
List<String> path = uri.getPathSegments();
|
||||||
@@ -1114,7 +1202,7 @@ public class UiUtils {
|
|||||||
d -> transformDialogForLookup(context, accountID, url, d))
|
d -> transformDialogForLookup(context, accountID, url, d))
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
return;
|
return;
|
||||||
} else if (looksLikeMastodonUrl(url)) {
|
} else if (looksLikeFediverseUrl(url)) {
|
||||||
new GetSearchResults(url, null, true)
|
new GetSearchResults(url, null, true)
|
||||||
.setCallback(new Callback<>() {
|
.setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -1315,6 +1403,33 @@ public class UiUtils {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void showFragmentForNotification(Context context, Notification n, String accountID, Bundle extras) {
|
||||||
|
if (extras == null) extras = new Bundle();
|
||||||
|
extras.putString("account", accountID);
|
||||||
|
if (n.status!=null) {
|
||||||
|
Status status=n.status;
|
||||||
|
extras.putParcelable("status", Parcels.wrap(status.clone()));
|
||||||
|
Nav.go((Activity) context, ThreadFragment.class, extras);
|
||||||
|
} else if (n.report != null) {
|
||||||
|
String domain = AccountSessionManager.getInstance().getAccount(accountID).domain;
|
||||||
|
UiUtils.launchWebBrowser(context, "https://"+domain+"/admin/reports/"+n.report.id);
|
||||||
|
} else if (n.account != null) {
|
||||||
|
extras.putString("account", accountID);
|
||||||
|
extras.putParcelable("profileAccount", Parcels.wrap(n.account));
|
||||||
|
Nav.go((Activity) context, ProfileFragment.class, extras);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scale the input value according to the device's scaled display density
|
||||||
|
* @param sp Input value in scale-independent pixels (sp)
|
||||||
|
* @return Scaled value in physical pixels (px)
|
||||||
|
*/
|
||||||
|
public static int sp(Context context, float sp){
|
||||||
|
// TODO: replace with V.sp in next AppKit version
|
||||||
|
return Math.round(sp*context.getApplicationContext().getResources().getDisplayMetrics().scaledDensity);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps a View.OnClickListener to filter multiple clicks in succession.
|
* Wraps a View.OnClickListener to filter multiple clicks in succession.
|
||||||
* Useful for buttons that perform some action that changes their state asynchronously.
|
* Useful for buttons that perform some action that changes their state asynchronously.
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ public class ComposeMediaLayout extends ViewGroup{
|
|||||||
|
|
||||||
public ComposeMediaLayout(Context context, AttributeSet attrs, int defStyle){
|
public ComposeMediaLayout(Context context, AttributeSet attrs, int defStyle){
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
UiUtils.loadMaxWidth(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ package org.joinmastodon.android.ui.views;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
|
||||||
public class MaxWidthFrameLayout extends FrameLayout{
|
public class MaxWidthFrameLayout extends FrameLayout{
|
||||||
private int maxWidth;
|
private int maxWidth, defaultWidth;
|
||||||
|
|
||||||
public MaxWidthFrameLayout(Context context){
|
public MaxWidthFrameLayout(Context context){
|
||||||
this(context, null);
|
this(context, null);
|
||||||
@@ -22,6 +23,7 @@ public class MaxWidthFrameLayout extends FrameLayout{
|
|||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.MaxWidthFrameLayout);
|
TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.MaxWidthFrameLayout);
|
||||||
maxWidth=ta.getDimensionPixelSize(R.styleable.MaxWidthFrameLayout_android_maxWidth, Integer.MAX_VALUE);
|
maxWidth=ta.getDimensionPixelSize(R.styleable.MaxWidthFrameLayout_android_maxWidth, Integer.MAX_VALUE);
|
||||||
|
defaultWidth=ta.getDimensionPixelSize(R.styleable.MaxWidthFrameLayout_defaultWidth, -1);
|
||||||
ta.recycle();
|
ta.recycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,10 +35,19 @@ public class MaxWidthFrameLayout extends FrameLayout{
|
|||||||
this.maxWidth=maxWidth;
|
this.maxWidth=maxWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getDefaultWidth() {
|
||||||
|
return defaultWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultWidth(int defaultWidth) {
|
||||||
|
this.defaultWidth = defaultWidth;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||||
if(MeasureSpec.getSize(widthMeasureSpec)>maxWidth){
|
if(MeasureSpec.getSize(widthMeasureSpec)>maxWidth){
|
||||||
widthMeasureSpec=maxWidth | MeasureSpec.getMode(widthMeasureSpec);
|
int width = defaultWidth >= 0 ? defaultWidth : maxWidth;
|
||||||
|
widthMeasureSpec=width | MeasureSpec.getMode(widthMeasureSpec);
|
||||||
}
|
}
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ public class MediaGridLayout extends ViewGroup{
|
|||||||
|
|
||||||
public MediaGridLayout(Context context, AttributeSet attrs, int defStyle){
|
public MediaGridLayout(Context context, AttributeSet attrs, int defStyle){
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
UiUtils.loadMaxWidth(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:color="?android:colorAccent" android:state_selected="true"/>
|
<item android:color="?android:colorAccent" android:state_selected="true"/>
|
||||||
<item android:color="?android:textColorSecondary" android:state_enabled="true"/>
|
<item android:color="?android:textColorSecondary" android:state_enabled="true"/>
|
||||||
<item android:color="?android:textColorSecondary" android:alpha="0.3"/>
|
<item android:color="?colorIconDisabled" />
|
||||||
</selector>
|
</selector>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<item android:drawable="@drawable/ic_fluent_megaphone_24_regular" android:left="2dp" android:right="2dp" android:top="2dp" android:bottom="2dp"/>
|
<item android:drawable="@drawable/ic_fluent_megaphone_24_regular" android:left="2dp" android:right="2dp" android:top="2dp" android:bottom="2dp"/>
|
||||||
<item android:width="14dp" android:height="14dp" android:gravity="top|right">
|
<item android:width="14dp" android:height="14dp" android:gravity="top|right">
|
||||||
<shape android:shape="oval">
|
<shape android:shape="oval">
|
||||||
<stroke android:color="?android:colorPrimary" android:width="2dp"/>
|
<stroke android:color="?appkitToolbarBackground" android:width="2dp"/>
|
||||||
<solid android:color="?android:colorAccent"/>
|
<solid android:color="?android:colorAccent"/>
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:width="20sp"
|
||||||
|
android:height="20sp"
|
||||||
|
android:drawable="@drawable/ic_fluent_arrow_repeat_all_20_filled" />
|
||||||
|
</layer-list>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:width="20sp"
|
||||||
|
android:height="20sp"
|
||||||
|
android:drawable="@drawable/ic_fluent_arrow_reply_20_filled" />
|
||||||
|
</layer-list>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M12 4.5c-4.694 0-8.5 3.806-8.5 8.5 0 2.345 0.948 4.466 2.484 6.005 0.293 0.293 0.293 0.768 0 1.06-0.294 0.293-0.769 0.293-1.061 0C3.118 18.256 2 15.758 2 13 2 7.477 6.477 3 12 3s10 4.477 10 10c0 2.758-1.118 5.256-2.923 7.065-0.292 0.293-0.767 0.293-1.06 0-0.293-0.292-0.294-0.767-0.001-1.06C19.552 17.467 20.5 15.345 20.5 13c0-4.694-3.806-8.5-8.5-8.5zM12 8c-2.761 0-5 2.239-5 5 0 1.382 0.56 2.632 1.466 3.537 0.293 0.293 0.293 0.768 0 1.06-0.292 0.294-0.767 0.294-1.06 0.001C6.229 16.423 5.5 14.796 5.5 13c0-3.59 2.91-6.5 6.5-6.5s6.5 2.91 6.5 6.5c0 1.796-0.73 3.423-1.906 4.598-0.293 0.293-0.768 0.293-1.06 0-0.293-0.293-0.293-0.768 0-1.06C16.44 15.631 17 14.381 17 13c0-2.761-2.239-5-5-5zm0 2.5c-1.38 0-2.5 1.12-2.5 2.5s1.12 2.5 2.5 2.5 2.5-1.12 2.5-2.5-1.12-2.5-2.5-2.5zM11 13c0-0.552 0.448-1 1-1s1 0.448 1 1-0.448 1-1 1-1-0.448-1-1z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:width="20sp"
|
||||||
|
android:height="20sp"
|
||||||
|
android:drawable="@drawable/ic_fluent_earth_20_regular" />
|
||||||
|
</layer-list>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:width="20sp"
|
||||||
|
android:height="20sp"
|
||||||
|
android:drawable="@drawable/ic_fluent_lock_closed_20_filled" />
|
||||||
|
</layer-list>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:width="20sp"
|
||||||
|
android:height="20sp"
|
||||||
|
android:drawable="@drawable/ic_fluent_lock_open_20_regular" />
|
||||||
|
</layer-list>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:width="20sp"
|
||||||
|
android:height="20sp"
|
||||||
|
android:drawable="@drawable/ic_fluent_number_symbol_20_filled" />
|
||||||
|
</layer-list>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<item android:drawable="@drawable/ic_fluent_person_add_24_regular" android:left="2dp" android:right="2dp" android:top="2dp" android:bottom="2dp"/>
|
<item android:drawable="@drawable/ic_fluent_person_add_24_regular" android:left="2dp" android:right="2dp" android:top="2dp" android:bottom="2dp"/>
|
||||||
<item android:width="14dp" android:height="14dp" android:gravity="top|right">
|
<item android:width="14dp" android:height="14dp" android:gravity="top|right">
|
||||||
<shape android:shape="oval">
|
<shape android:shape="oval">
|
||||||
<stroke android:color="?android:colorPrimary" android:width="2dp"/>
|
<stroke android:color="?appkitToolbarBackground" android:width="2dp"/>
|
||||||
<solid android:color="?android:colorAccent"/>
|
<solid android:color="?android:colorAccent"/>
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<item android:drawable="@drawable/ic_fluent_settings_24_regular" android:left="2dp" android:right="2dp" android:top="2dp" android:bottom="2dp"/>
|
<item android:drawable="@drawable/ic_fluent_settings_24_regular" android:left="2dp" android:right="2dp" android:top="2dp" android:bottom="2dp"/>
|
||||||
<item android:width="14dp" android:height="14dp" android:gravity="top|right">
|
<item android:width="14dp" android:height="14dp" android:gravity="top|right">
|
||||||
<shape android:shape="oval">
|
<shape android:shape="oval">
|
||||||
<stroke android:color="?android:colorPrimary" android:width="2dp"/>
|
<stroke android:color="?appkitToolbarBackground" android:width="2dp"/>
|
||||||
<solid android:color="?android:colorAccent"/>
|
<solid android:color="?android:colorAccent"/>
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -1,120 +1,161 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<org.joinmastodon.android.ui.views.MaxWidthFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:orientation="horizontal"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:maxWidth="600sp"
|
||||||
|
app:defaultWidth="450sp"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="48dp"
|
android:layout_height="wrap_content">
|
||||||
android:paddingHorizontal="16dp">
|
|
||||||
|
|
||||||
<FrameLayout
|
<LinearLayout
|
||||||
android:id="@+id/reply_btn"
|
android:orientation="horizontal"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:minWidth="56dp">
|
android:paddingHorizontal="11sp">
|
||||||
<TextView
|
<!-- avatar width (46sp) / 2 - button width (24sp) / 2 -->
|
||||||
android:id="@+id/reply"
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/reply_btn"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingVertical="12dp">
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="24sp"
|
||||||
|
android:layout_height="24sp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:duplicateParentState="true"
|
||||||
|
android:src="@drawable/ic_fluent_chat_multiple_24_selector_text"
|
||||||
|
android:tint="?android:textColorSecondary"
|
||||||
|
android:gravity="center_vertical" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/reply"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:minWidth="16dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textAppearance="@style/m3_label_large"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
tools:text="123"
|
||||||
|
tools:ignore="RtlSymmetry" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/boost_btn"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:foregroundTint="@color/boost_icon"
|
||||||
|
android:paddingVertical="12dp">
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="24sp"
|
||||||
|
android:layout_height="24sp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:duplicateParentState="true"
|
||||||
|
android:src="@drawable/ic_boost"
|
||||||
|
android:tint="@color/boost_icon"
|
||||||
|
android:gravity="center_vertical" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/boost"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:minWidth="16dp"
|
||||||
|
android:textColor="@color/boost_icon"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textAppearance="@style/m3_label_large"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
tools:text="123"
|
||||||
|
tools:ignore="RtlSymmetry" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/favorite_btn"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingVertical="12dp">
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="24sp"
|
||||||
|
android:layout_height="24sp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:duplicateParentState="true"
|
||||||
|
android:src="@drawable/ic_fluent_star_24_selector"
|
||||||
|
android:tint="@color/favorite_icon"
|
||||||
|
android:gravity="center_vertical" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/favorite"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:minWidth="16dp"
|
||||||
|
android:textColor="@color/favorite_icon"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textAppearance="@style/m3_label_large"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
tools:text="123"
|
||||||
|
tools:ignore="RtlSymmetry" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/bookmark_btn"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingVertical="12dp">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/bookmark"
|
||||||
|
android:layout_width="24sp"
|
||||||
|
android:layout_height="24sp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:src="@drawable/ic_fluent_bookmark_24_selector"
|
||||||
|
android:tint="@color/bookmark_icon"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textAppearance="@style/m3_label_large" />
|
||||||
|
</FrameLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/share_btn"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center_vertical"
|
android:paddingVertical="12dp">
|
||||||
android:drawableStart="@drawable/ic_fluent_chat_multiple_24_selector_text"
|
<ImageView
|
||||||
android:drawablePadding="8dp"
|
android:id="@+id/share"
|
||||||
android:paddingHorizontal="8dp"
|
android:layout_width="24sp"
|
||||||
android:drawableTint="?android:textColorSecondary"
|
android:layout_height="24sp"
|
||||||
android:gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:textAppearance="@style/m3_label_large"
|
android:layout_marginHorizontal="16dp"
|
||||||
tools:text="123"/>
|
android:src="@drawable/ic_fluent_share_24_regular"
|
||||||
</FrameLayout>
|
android:tint="?android:textColorSecondary"
|
||||||
|
android:gravity="center_vertical"/>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<Space
|
</LinearLayout>
|
||||||
android:layout_width="0px"
|
</org.joinmastodon.android.ui.views.MaxWidthFrameLayout>
|
||||||
android:layout_height="1px"
|
|
||||||
android:layout_weight="1"/>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/boost_btn"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:minWidth="56dp">
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/boost"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:drawableStart="@drawable/ic_boost"
|
|
||||||
android:drawablePadding="8dp"
|
|
||||||
android:paddingHorizontal="8dp"
|
|
||||||
android:drawableTint="@color/boost_icon"
|
|
||||||
android:textColor="@color/boost_icon"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:textAppearance="@style/m3_label_large"
|
|
||||||
tools:text="123"/>
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="0px"
|
|
||||||
android:layout_height="1px"
|
|
||||||
android:layout_weight="1"/>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/favorite_btn"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:minWidth="56dp">
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/favorite"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:drawableStart="@drawable/ic_fluent_star_24_selector"
|
|
||||||
android:drawablePadding="8dp"
|
|
||||||
android:paddingHorizontal="8dp"
|
|
||||||
android:drawableTint="@color/favorite_icon"
|
|
||||||
android:textColor="@color/favorite_icon"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:textAppearance="@style/m3_label_large"
|
|
||||||
tools:text="123"/>
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="0px"
|
|
||||||
android:layout_height="1px"
|
|
||||||
android:layout_weight="1"/>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/bookmark_btn"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:minWidth="56dp">
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/bookmark"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:drawableStart="@drawable/ic_fluent_bookmark_24_selector"
|
|
||||||
android:paddingHorizontal="8dp"
|
|
||||||
android:drawableTint="@color/bookmark_icon"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:textAppearance="@style/m3_label_large" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="0px"
|
|
||||||
android:layout_height="1px"
|
|
||||||
android:layout_weight="1"/>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/share_btn"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/share"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:src="@drawable/ic_fluent_share_24_regular"
|
|
||||||
android:paddingHorizontal="8dp"
|
|
||||||
android:tint="?android:textColorSecondary"
|
|
||||||
android:gravity="center_vertical"/>
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
android:paddingTop="16dp"
|
android:paddingTop="16dp"
|
||||||
android:paddingBottom="6dp"
|
android:paddingBottom="6dp"
|
||||||
android:textAppearance="@style/m3_title_small"
|
android:textAppearance="@style/m3_title_small"
|
||||||
android:drawableStart="@drawable/ic_fluent_arrow_reply_20_filled"
|
android:drawableStart="@drawable/ic_fluent_arrow_reply_20sp_filled"
|
||||||
android:drawableTint="?android:textColorSecondary"
|
android:drawableTint="?android:textColorSecondary"
|
||||||
android:drawablePadding="6dp"
|
android:drawablePadding="6dp"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
android:id="@+id/avatar"
|
android:id="@+id/avatar"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
android:layout_margin="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginVertical="12dp"
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
android:importantForAccessibility="no"/>
|
android:importantForAccessibility="no"/>
|
||||||
|
|||||||
@@ -20,11 +20,20 @@
|
|||||||
<string name="share_toot_title">শেয়ার করুন</string>
|
<string name="share_toot_title">শেয়ার করুন</string>
|
||||||
<string name="settings">সেটিংস</string>
|
<string name="settings">সেটিংস</string>
|
||||||
<string name="cancel">বাতিল করুন</string>
|
<string name="cancel">বাতিল করুন</string>
|
||||||
|
<plurals name="followers">
|
||||||
|
<item quantity="one">জন ফলোয়ার</item>
|
||||||
|
<item quantity="other">জন ফলোয়ারস</item>
|
||||||
|
</plurals>
|
||||||
<plurals name="posts">
|
<plurals name="posts">
|
||||||
<item quantity="one">পোস্ট</item>
|
<item quantity="one">পোস্ট</item>
|
||||||
<item quantity="other">পোস্টগুলো</item>
|
<item quantity="other">পোস্টগুলো</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="posts">পোস্টগুলো</string>
|
<string name="posts">পোস্টগুলো</string>
|
||||||
|
<string name="media">মিডিয়া</string>
|
||||||
|
<string name="button_follow">ফলো করুন</string>
|
||||||
|
<string name="button_following">ফলো করছেন</string>
|
||||||
|
<string name="edit_profile">প্রোফাইল সংশোধন করুন</string>
|
||||||
|
<string name="mention_user">%s -কে পিং করুন</string>
|
||||||
<string name="share_user">%s -কে শেয়ার করুন</string>
|
<string name="share_user">%s -কে শেয়ার করুন</string>
|
||||||
<string name="mute_user">%s -কে মিউট করুন</string>
|
<string name="mute_user">%s -কে মিউট করুন</string>
|
||||||
<string name="unmute_user">%s -কে আনমিউট করুন</string>
|
<string name="unmute_user">%s -কে আনমিউট করুন</string>
|
||||||
@@ -53,6 +62,22 @@
|
|||||||
<item quantity="one">%d দিন</item>
|
<item quantity="one">%d দিন</item>
|
||||||
<item quantity="other">%d দিন</item>
|
<item quantity="other">%d দিন</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<plurals name="x_seconds_left">
|
||||||
|
<item quantity="one">%d সেকেন্ড বাকি</item>
|
||||||
|
<item quantity="other">%d সেকেন্ড বাকি</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="x_minutes_left">
|
||||||
|
<item quantity="one">%d মিনিট বাকি</item>
|
||||||
|
<item quantity="other">%d মিনিট বাকি</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="x_hours_left">
|
||||||
|
<item quantity="one">%d ঘণ্টা বাকি</item>
|
||||||
|
<item quantity="other">%d ঘণ্টা বাকি</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="x_days_left">
|
||||||
|
<item quantity="one">%d দিন বাকি</item>
|
||||||
|
<item quantity="other">%d দিন বাকি</item>
|
||||||
|
</plurals>
|
||||||
<string name="poll_closed">বন্ধ</string>
|
<string name="poll_closed">বন্ধ</string>
|
||||||
<string name="confirm_mute_title">অ্যাকাউন্টটি মিউট করুন</string>
|
<string name="confirm_mute_title">অ্যাকাউন্টটি মিউট করুন</string>
|
||||||
<string name="do_mute">মিউট করুন</string>
|
<string name="do_mute">মিউট করুন</string>
|
||||||
@@ -88,6 +113,18 @@
|
|||||||
<item quantity="one">%d জন ব্যক্তি বলছেন</item>
|
<item quantity="one">%d জন ব্যক্তি বলছেন</item>
|
||||||
<item quantity="other">%d jon ব্যক্তিরা বলছেন</item>
|
<item quantity="other">%d jon ব্যক্তিরা বলছেন</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<string name="sending_report">রিপোর্ট পাঠানো হচ্ছে…</string>
|
||||||
|
<string name="report_sent_title">রিপোর্ট করার জন্য আপনাকে ধন্যবাদ, আমরা এটি শীঘ্রই দেখব.</string>
|
||||||
|
<string name="report_sent_subtitle">আমরা যতক্ষণে আপনার রিপোর্ট পুনর্বিবেচনা করছি, আপনি %s এর বিরুদ্ধে ব্যবস্থা নিতে পারেন.</string>
|
||||||
|
<string name="back">ফিরে যান</string>
|
||||||
|
<string name="search_communities">সার্ভারের নাম বা লিঙ্ক</string>
|
||||||
|
<string name="instance_rules_title">সার্ভারের নিয়মাবলী</string>
|
||||||
|
<string name="signup_title">অ্যাকাউন্ট তৈরি করুন</string>
|
||||||
|
<string name="display_name">নাম</string>
|
||||||
|
<string name="username">ইউজারনেম</string>
|
||||||
|
<string name="email">ই-মেইল</string>
|
||||||
|
<string name="password">পাসওয়ার্ড</string>
|
||||||
|
<string name="confirm_password">পাসওয়ার্ড নিশ্চিত করুন</string>
|
||||||
<!-- %s is the email address -->
|
<!-- %s is the email address -->
|
||||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||||
<!-- %s is version like 1.2.3 -->
|
<!-- %s is version like 1.2.3 -->
|
||||||
@@ -96,4 +133,8 @@
|
|||||||
<!-- %s is server domain -->
|
<!-- %s is server domain -->
|
||||||
<!-- Shown in a progress dialog when you tap "follow all" -->
|
<!-- Shown in a progress dialog when you tap "follow all" -->
|
||||||
<!-- %1$s is server domain, %2$s is email domain. You can reorder these placeholders to fit your language better. -->
|
<!-- %1$s is server domain, %2$s is email domain. You can reorder these placeholders to fit your language better. -->
|
||||||
|
<string name="welcome_to_mastodon">Mastodon - এ আপনাকে স্বাগত জানাই</string>
|
||||||
|
<string name="welcome_paragraph1">Mastodon হল একটি বিকেন্দ্রীভূত সামাজিক নেটওয়ার্ক, যার মানে কোনো একক কোম্পানি এটিকে নিয়ন্ত্রণ করে না। এটি অনেকগুলি স্বাধীনভাবে চালিত সার্ভারের সমন্বয়ে গঠিত, যেখানে সব সার্ভারগুলি একসাথে সংযুক্ত৷</string>
|
||||||
|
<string name="what_are_servers">সার্ভার কি?</string>
|
||||||
|
<string name="welcome_paragraph2"><![CDATA[প্রতিটি Mastodon অ্যাকাউন্টকে একটি সার্ভারে হোস্ট করা হয় — প্রত্যেকটির নিজস্ব মান, নিয়ম এবং প্রশাসক (অ্যাডমিন) রয়েছে। আপনি যে কোনো সার্ভারই বেছে নিন না কেন তা বিবেচ্য নয়, আপনি যেকোনো সার্ভারের লোকেদের সাথে যোগাযোগ করতে এবং তাদের ফলো করতে পারেন।]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -166,7 +166,7 @@
|
|||||||
<string name="report_sent_subtitle">Während wir den Vorfall überprüfen, kannst du gegen %s weitere Maßnahmen ergreifen.</string>
|
<string name="report_sent_subtitle">Während wir den Vorfall überprüfen, kannst du gegen %s weitere Maßnahmen ergreifen.</string>
|
||||||
<string name="unfollow_user">%s entfolgen</string>
|
<string name="unfollow_user">%s entfolgen</string>
|
||||||
<string name="unfollow">Entfolgen</string>
|
<string name="unfollow">Entfolgen</string>
|
||||||
<string name="mute_user_explain">Du wirst die eigenen und geteilten Beiträge des Kontos nicht mehr sehen können. Dass du das Profil stummgeschaltet hast, erfährt die Person nicht.</string>
|
<string name="mute_user_explain">Du wirst deren (geteilte) Beiträge auf deiner Startseite nicht mehr sehen können. Sie werden nicht erfahren, dass sie stummgeschaltet sind.</string>
|
||||||
<string name="block_user_explain">Dir wird es nicht länger möglich sein, die Beiträge dieses Konto zu sehen. Das blockierte Profil wird nicht mehr in der Lage sein, deine Beiträge zu sehen oder dir zu folgen. Die Person hinter dem Konto wird mitbekommen, dass du ihr Konto gesperrt hast.</string>
|
<string name="block_user_explain">Dir wird es nicht länger möglich sein, die Beiträge dieses Konto zu sehen. Das blockierte Profil wird nicht mehr in der Lage sein, deine Beiträge zu sehen oder dir zu folgen. Die Person hinter dem Konto wird mitbekommen, dass du ihr Konto gesperrt hast.</string>
|
||||||
<string name="report_personal_title">Möchtest du das nicht mehr sehen?</string>
|
<string name="report_personal_title">Möchtest du das nicht mehr sehen?</string>
|
||||||
<string name="report_personal_subtitle">Wenn du etwas auf Mastodon siehst, das dir nicht gefällt, kannst du die Person aus deinem Umfeld entfernen.</string>
|
<string name="report_personal_subtitle">Wenn du etwas auf Mastodon siehst, das dir nicht gefällt, kannst du die Person aus deinem Umfeld entfernen.</string>
|
||||||
@@ -175,7 +175,7 @@
|
|||||||
<string name="instance_catalog_subtitle">Wähle einen Server basierend auf deinen Interessen oder deiner Region – oder einfach einen allgemeinen. Du kannst trotzdem mit jedem interagieren, egal auf welchem Server.</string>
|
<string name="instance_catalog_subtitle">Wähle einen Server basierend auf deinen Interessen oder deiner Region – oder einfach einen allgemeinen. Du kannst trotzdem mit jedem interagieren, egal auf welchem Server.</string>
|
||||||
<string name="search_communities">Servername oder -adresse</string>
|
<string name="search_communities">Servername oder -adresse</string>
|
||||||
<string name="instance_rules_title">Server-Regeln</string>
|
<string name="instance_rules_title">Server-Regeln</string>
|
||||||
<string name="instance_rules_subtitle">Mit dem Fortfahren erklärst du dich damit einverstanden, die folgenden Regeln zu befolgen, die von den %s-Moderatoren aufgestellt und umgesetzt werden.</string>
|
<string name="instance_rules_subtitle">Solltest du fortfahren, erklärst du dich mit den Serverregeln, die die Moderator*innen von %s aufgestellt haben und durchsetzen werden, einverstanden.</string>
|
||||||
<string name="signup_title">Konto erstellen</string>
|
<string name="signup_title">Konto erstellen</string>
|
||||||
<string name="edit_photo">bearbeiten</string>
|
<string name="edit_photo">bearbeiten</string>
|
||||||
<string name="display_name">Name</string>
|
<string name="display_name">Name</string>
|
||||||
@@ -200,7 +200,7 @@
|
|||||||
<string name="confirm_email_title">Überprüfe deinen Posteingang</string>
|
<string name="confirm_email_title">Überprüfe deinen Posteingang</string>
|
||||||
<!-- %s is the email address -->
|
<!-- %s is the email address -->
|
||||||
<string name="confirm_email_subtitle">Klicke auf den Link, den wir dir geschickt haben, um %s zu bestätigen. Wir warten hier auf dich.</string>
|
<string name="confirm_email_subtitle">Klicke auf den Link, den wir dir geschickt haben, um %s zu bestätigen. Wir warten hier auf dich.</string>
|
||||||
<string name="confirm_email_didnt_get">Kein Link erhalten?</string>
|
<string name="confirm_email_didnt_get">Keinen Link erhalten?</string>
|
||||||
<string name="resend">Erneut abschicken</string>
|
<string name="resend">Erneut abschicken</string>
|
||||||
<string name="open_email_app">E-Mail-App öffnen</string>
|
<string name="open_email_app">E-Mail-App öffnen</string>
|
||||||
<string name="resent_email">Bestätigung per E-Mail zugeschickt</string>
|
<string name="resent_email">Bestätigung per E-Mail zugeschickt</string>
|
||||||
@@ -438,8 +438,11 @@
|
|||||||
<string name="show">Anzeigen</string>
|
<string name="show">Anzeigen</string>
|
||||||
<string name="hide">Ausblenden</string>
|
<string name="hide">Ausblenden</string>
|
||||||
<string name="join_default_server">%s beitreten</string>
|
<string name="join_default_server">%s beitreten</string>
|
||||||
|
<string name="pick_server">Wähle einen anderen Server</string>
|
||||||
<string name="signup_or_login">oder</string>
|
<string name="signup_or_login">oder</string>
|
||||||
<string name="learn_more">Mehr erfahren</string>
|
<string name="learn_more">Mehr erfahren</string>
|
||||||
<string name="welcome_to_mastodon">Willkommen auf Mastodon</string>
|
<string name="welcome_to_mastodon">Willkommen auf Mastodon</string>
|
||||||
|
<string name="welcome_paragraph1">Mastodon ist ein dezentrales, soziales Netzwerk. Das bedeutet, dass es nicht von einem einzigen Unternehmen kontrolliert wird. Das Netzwerk besteht aus unabhängig voneinander betriebenen Servern, die miteinander verbunden sind.</string>
|
||||||
<string name="what_are_servers">Was sind Server?</string>
|
<string name="what_are_servers">Was sind Server?</string>
|
||||||
|
<string name="welcome_paragraph2"><![CDATA[Jedes Mastodon-Konto wird auf einem Server gehostet. Jeder Server hat dabei seine eigenen Werte, Regeln und Administrator*innen. Aber egal, für welchen Server Du Dich entscheidest: Du kannst mit Leuten von anderen Servern interagieren und ihnen folgen.]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -274,4 +274,26 @@
|
|||||||
<string name="sk_settings_confirm_before_reblog">Vor dem Teilen bestätigen</string>
|
<string name="sk_settings_confirm_before_reblog">Vor dem Teilen bestätigen</string>
|
||||||
<string name="sk_reacted">hat reagiert</string>
|
<string name="sk_reacted">hat reagiert</string>
|
||||||
<string name="sk_reacted_with">hat mit %s reagiert</string>
|
<string name="sk_reacted_with">hat mit %s reagiert</string>
|
||||||
|
<string name="sk_external_share_title">Mit Konto teilen</string>
|
||||||
|
<string name="sk_external_share_or_open_title">Mit Konto teilen oder öffnen</string>
|
||||||
|
<string name="sk_timeline_bubble">Bubble</string>
|
||||||
|
<string name="sk_settings_default_content_type_explanation">Vorausgewählter Inhaltstyp für neue Beiträge – überschreibt den Wert, der unter „Einstellungen für Beiträge“ gesetzt ist.</string>
|
||||||
|
<string name="sk_open_in_app_failed">Konnte nicht in der App öffnen</string>
|
||||||
|
<string name="sk_content_type_unspecified">Nicht angegeben</string>
|
||||||
|
<string name="sk_content_type_plain">Nur Text</string>
|
||||||
|
<string name="sk_content_type_html">HTML</string>
|
||||||
|
<string name="sk_content_type_markdown">Markdown</string>
|
||||||
|
<string name="sk_content_type_bbcode">BBCode</string>
|
||||||
|
<string name="sk_content_type_mfm">MFM</string>
|
||||||
|
<string name="sk_content_type">Inhaltstyp</string>
|
||||||
|
<string name="sk_bubble_timeline_info_banner">Das sind die neuesten Beiträge aus dem Netzwerk, das deine Instanz-Admins kuratiert haben.</string>
|
||||||
|
<string name="sk_settings_content_types">Formatierung aktivieren</string>
|
||||||
|
<string name="sk_settings_default_content_type">Standard-Inhaltstyp</string>
|
||||||
|
<string name="sk_instance_info_unavailable">Informationen zur Instanz momentan nicht verfügbar</string>
|
||||||
|
<string name="sk_open_in_app">In App öffnen</string>
|
||||||
|
<string name="sk_settings_content_types_explanation">Dadurch lässt beim Erstellen von Beiträgen ein Inhaltstyp wie Markdown angeben. Nicht alle Instanzen unterstützen das.</string>
|
||||||
|
<string name="sk_settings_allow_remote_loading">Infos von Remote-Instanzen laden</string>
|
||||||
|
<string name="sk_no_remote_info_hint">keine Remote-Infos abrufbar</string>
|
||||||
|
<string name="sk_error_loading_profile">Konnte das Profil auf deiner Heim-Instanz nicht laden.</string>
|
||||||
|
<string name="sk_settings_allow_remote_loading_explanation">Für vollständigere Auflistung von Follower*innen, Likes und Boosts können die Informationen von der Ursprungs-Instanz geladen werden.</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -286,4 +286,10 @@
|
|||||||
<string name="sk_settings_default_content_type">Contenido por defecto</string>
|
<string name="sk_settings_default_content_type">Contenido por defecto</string>
|
||||||
<string name="sk_settings_content_types_explanation">Permite establecer un tipo de contenido como Markdown al crear una entrada. Ten en cuenta que no todas las instancias lo admiten.</string>
|
<string name="sk_settings_content_types_explanation">Permite establecer un tipo de contenido como Markdown al crear una entrada. Ten en cuenta que no todas las instancias lo admiten.</string>
|
||||||
<string name="sk_settings_default_content_type_explanation">Permite preseleccionar un tipo de contenido al crear nuevas entradas, anulando el valor establecido en \"Preferencias de publicación\".</string>
|
<string name="sk_settings_default_content_type_explanation">Permite preseleccionar un tipo de contenido al crear nuevas entradas, anulando el valor establecido en \"Preferencias de publicación\".</string>
|
||||||
|
<string name="sk_bubble_timeline_info_banner">Estas son las publicaciones más recientes de la gente en tu servidor de Akkoma.</string>
|
||||||
|
<string name="sk_timeline_bubble">Burbuja</string>
|
||||||
|
<string name="sk_instance_info_unavailable">Información de la instancia temporalmente no disponible</string>
|
||||||
|
<string name="sk_external_share_or_open_title">Compartir o abrir con una cuenta</string>
|
||||||
|
<string name="sk_open_in_app">Abrir en la app</string>
|
||||||
|
<string name="sk_external_share_title">Compartir con una cuenta</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -287,4 +287,11 @@
|
|||||||
<string name="sk_content_type_unspecified">Non spécifié</string>
|
<string name="sk_content_type_unspecified">Non spécifié</string>
|
||||||
<string name="sk_settings_content_types_explanation">Permet de définir un type de contenu comme Markdown lors de la création d\'un message. Gardez à l\'esprit que toutes les instances ne le prennent pas en charge.</string>
|
<string name="sk_settings_content_types_explanation">Permet de définir un type de contenu comme Markdown lors de la création d\'un message. Gardez à l\'esprit que toutes les instances ne le prennent pas en charge.</string>
|
||||||
<string name="sk_settings_default_content_type">Type de contenu par défaut</string>
|
<string name="sk_settings_default_content_type">Type de contenu par défaut</string>
|
||||||
|
<string name="sk_open_in_app">Ouvrir dans l\'application</string>
|
||||||
|
<string name="sk_external_share_title">Partager avec le compte</string>
|
||||||
|
<string name="sk_external_share_or_open_title">Partager ou ouvrir avec le compte</string>
|
||||||
|
<string name="sk_bubble_timeline_info_banner">Ce sont les messages les plus récents du réseau organisés par vos administrateurs d\'instance.</string>
|
||||||
|
<string name="sk_timeline_bubble">Bulle</string>
|
||||||
|
<string name="sk_instance_info_unavailable">Informations sur l\'instance temporairement indisponibles</string>
|
||||||
|
<string name="sk_open_in_app_failed">Impossible de l\'ouvrir dans l\'application</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
<string name="sk_loading_fediverse_resource_title">Buscando no Fediverso</string>
|
<string name="sk_loading_fediverse_resource_title">Buscando no Fediverso</string>
|
||||||
<string name="sk_reblog_with_visibility">Impulsar con visibilidade</string>
|
<string name="sk_reblog_with_visibility">Impulsar con visibilidade</string>
|
||||||
<string name="sk_quote_post">Publicar acerca disto</string>
|
<string name="sk_quote_post">Publicar acerca disto</string>
|
||||||
<string name="sk_undo_reblog">Desfacer o impulso</string>
|
<string name="sk_undo_reblog">Desfacer impulso</string>
|
||||||
<string name="sk_copy_link_to_post">Copiar ligazón á publicación</string>
|
<string name="sk_copy_link_to_post">Copiar ligazón á publicación</string>
|
||||||
<string name="sk_loading_resource_on_instance_title">Buscando en %s</string>
|
<string name="sk_loading_resource_on_instance_title">Buscando en %s</string>
|
||||||
<string name="sk_open_with_account">Abrir con outra conta</string>
|
<string name="sk_open_with_account">Abrir con outra conta</string>
|
||||||
@@ -286,4 +286,10 @@
|
|||||||
<string name="sk_content_type">Tipo de contido</string>
|
<string name="sk_content_type">Tipo de contido</string>
|
||||||
<string name="sk_content_type_markdown">Markdown</string>
|
<string name="sk_content_type_markdown">Markdown</string>
|
||||||
<string name="sk_settings_content_types_explanation">Permite configurar un tipo de contido como Markdown ao crear unha publicación. Teña en conta que non tódalas instancias soportan isto.</string>
|
<string name="sk_settings_content_types_explanation">Permite configurar un tipo de contido como Markdown ao crear unha publicación. Teña en conta que non tódalas instancias soportan isto.</string>
|
||||||
|
<string name="sk_bubble_timeline_info_banner">Estas son as publicacións máis recentes da xente na burbulla do seu servidor Akkoma.</string>
|
||||||
|
<string name="sk_timeline_bubble">Burbulla</string>
|
||||||
|
<string name="sk_instance_info_unavailable">Información da instancia temporalmente non dispoñible</string>
|
||||||
|
<string name="sk_open_in_app">Abrir na aplicación</string>
|
||||||
|
<string name="sk_external_share_title">Compartir coa conta</string>
|
||||||
|
<string name="sk_external_share_or_open_title">Compartir ou abrir coa conta</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -287,4 +287,10 @@
|
|||||||
<string name="sk_content_type">Jenis konten</string>
|
<string name="sk_content_type">Jenis konten</string>
|
||||||
<string name="sk_content_type_unspecified">Tidak ditentukan</string>
|
<string name="sk_content_type_unspecified">Tidak ditentukan</string>
|
||||||
<string name="sk_settings_content_types_explanation">Memperbolehkan menetapkan jenis konten seperti Markdown ketika membuat kiriman. Perlu diingat bahwa tidak semua server mendukung ini.</string>
|
<string name="sk_settings_content_types_explanation">Memperbolehkan menetapkan jenis konten seperti Markdown ketika membuat kiriman. Perlu diingat bahwa tidak semua server mendukung ini.</string>
|
||||||
|
<string name="sk_open_in_app">Buka dalam aplikasi</string>
|
||||||
|
<string name="sk_external_share_title">Bagikan dengan akun</string>
|
||||||
|
<string name="sk_bubble_timeline_info_banner">Ini adalah kiriman yamg paling terkini oleh orang-orang dalam gelembung server Akkoma Anda.</string>
|
||||||
|
<string name="sk_timeline_bubble">Gelembung</string>
|
||||||
|
<string name="sk_instance_info_unavailable">Info server sementara tidak tersedia</string>
|
||||||
|
<string name="sk_external_share_or_open_title">Bagikan atau buka dengan akun</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -274,4 +274,15 @@
|
|||||||
<string name="sk_settings_confirm_before_reblog">Potwierdź przed podbiciem</string>
|
<string name="sk_settings_confirm_before_reblog">Potwierdź przed podbiciem</string>
|
||||||
<string name="sk_reacted_with">zareagował(a) z %s</string>
|
<string name="sk_reacted_with">zareagował(a) z %s</string>
|
||||||
<string name="sk_reacted">zareagował(a)</string>
|
<string name="sk_reacted">zareagował(a)</string>
|
||||||
|
<string name="sk_settings_default_content_type">Domyślny rodzaj treści</string>
|
||||||
|
<string name="sk_instance_info_unavailable">Informacje o instancji są tymczasowo niedostępne</string>
|
||||||
|
<string name="sk_content_type_html">HTML</string>
|
||||||
|
<string name="sk_content_type_bbcode">BBCode</string>
|
||||||
|
<string name="sk_content_type_mfm">MFM</string>
|
||||||
|
<string name="sk_content_type">Rodzaj treści</string>
|
||||||
|
<string name="sk_content_type_unspecified">Nie określono</string>
|
||||||
|
<string name="sk_content_type_plain">Czysty tekst</string>
|
||||||
|
<string name="sk_settings_content_types">Włącz formatowanie wpisu</string>
|
||||||
|
<string name="sk_open_in_app">Otwórz w aplikacji</string>
|
||||||
|
<string name="sk_external_share_title">Udostępnij z kontem</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -10,11 +10,13 @@
|
|||||||
<string name="ok">OK</string>
|
<string name="ok">OK</string>
|
||||||
<string name="preparing_auth">Förbereder för autentisering…</string>
|
<string name="preparing_auth">Förbereder för autentisering…</string>
|
||||||
<string name="finishing_auth">Slutför autentisering…</string>
|
<string name="finishing_auth">Slutför autentisering…</string>
|
||||||
|
<string name="user_boosted">%s boostade</string>
|
||||||
<string name="in_reply_to">Som svar på %s</string>
|
<string name="in_reply_to">Som svar på %s</string>
|
||||||
<string name="notifications">Notiser</string>
|
<string name="notifications">Notiser</string>
|
||||||
<string name="user_followed_you">följde dig</string>
|
<string name="user_followed_you">följde dig</string>
|
||||||
<string name="user_sent_follow_request">skickade en förfrågning om att följa till dig</string>
|
<string name="user_sent_follow_request">skickade en förfrågning om att följa till dig</string>
|
||||||
<string name="user_favorited">favoritmarkerade dit inlägg</string>
|
<string name="user_favorited">favoritmarkerade dit inlägg</string>
|
||||||
|
<string name="notification_boosted">boostade ditt inlägg</string>
|
||||||
<string name="poll_ended">omröstning avslutad</string>
|
<string name="poll_ended">omröstning avslutad</string>
|
||||||
<string name="time_seconds">%ds</string>
|
<string name="time_seconds">%ds</string>
|
||||||
<string name="time_minutes">%dm</string>
|
<string name="time_minutes">%dm</string>
|
||||||
@@ -164,6 +166,7 @@
|
|||||||
<string name="report_sent_subtitle">Medan vi granskar detta kan du vidta åtgärder mot %s.</string>
|
<string name="report_sent_subtitle">Medan vi granskar detta kan du vidta åtgärder mot %s.</string>
|
||||||
<string name="unfollow_user">Avfölj %s</string>
|
<string name="unfollow_user">Avfölj %s</string>
|
||||||
<string name="unfollow">Avfölj</string>
|
<string name="unfollow">Avfölj</string>
|
||||||
|
<string name="mute_user_explain">Du kommer inte att se deras inlägg eller boosts i ditt hemflöde. De kommer inte veta att de har blivit tystade.</string>
|
||||||
<string name="block_user_explain">De kommer inte längre att kunna följa eller se dina inlägg, men de kan se om de har blockerats.</string>
|
<string name="block_user_explain">De kommer inte längre att kunna följa eller se dina inlägg, men de kan se om de har blockerats.</string>
|
||||||
<string name="report_personal_title">Vill du inte se det här?</string>
|
<string name="report_personal_title">Vill du inte se det här?</string>
|
||||||
<string name="report_personal_subtitle">När du ser något som du inte gillar på Mastodon kan du ta bort personen från din upplevelse.</string>
|
<string name="report_personal_subtitle">När du ser något som du inte gillar på Mastodon kan du ta bort personen från din upplevelse.</string>
|
||||||
@@ -412,7 +415,18 @@
|
|||||||
<!-- %1$s is server domain, %2$s is email domain. You can reorder these placeholders to fit your language better. -->
|
<!-- %1$s is server domain, %2$s is email domain. You can reorder these placeholders to fit your language better. -->
|
||||||
<string name="signup_email_domain_blocked">%1$s tillåter inte registrering från %2$s. Prova en annan eller <a>välj en annan server</a>.</string>
|
<string name="signup_email_domain_blocked">%1$s tillåter inte registrering från %2$s. Prova en annan eller <a>välj en annan server</a>.</string>
|
||||||
<string name="signup_username_taken">Det här användarnamnet är redan taget.</string>
|
<string name="signup_username_taken">Det här användarnamnet är redan taget.</string>
|
||||||
|
<string name="spoiler_show">Visa ändå</string>
|
||||||
|
<string name="poll_multiple_choice">Välj en eller flera</string>
|
||||||
<string name="save_changes">Spara ändringar</string>
|
<string name="save_changes">Spara ändringar</string>
|
||||||
|
<string name="profile_timeline">Tidslinje</string>
|
||||||
|
<string name="view_all">Visa alla</string>
|
||||||
|
<string name="profile_endorsed_accounts">Konton</string>
|
||||||
|
<string name="verified_link">Verifierad länk</string>
|
||||||
|
<string name="show">Visa</string>
|
||||||
|
<string name="hide">Dölj</string>
|
||||||
|
<string name="join_default_server">Gå med %s</string>
|
||||||
<string name="signup_or_login">eller</string>
|
<string name="signup_or_login">eller</string>
|
||||||
|
<string name="learn_more">Läs mer</string>
|
||||||
<string name="welcome_to_mastodon">Välkommen till Mastodon</string>
|
<string name="welcome_to_mastodon">Välkommen till Mastodon</string>
|
||||||
|
<string name="what_are_servers">Vad är servrar?</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -6,17 +6,17 @@
|
|||||||
<string name="next">Sonraki</string>
|
<string name="next">Sonraki</string>
|
||||||
<string name="loading_instance">Sunucu bilgisi alınıyor…</string>
|
<string name="loading_instance">Sunucu bilgisi alınıyor…</string>
|
||||||
<string name="error">Hata</string>
|
<string name="error">Hata</string>
|
||||||
<string name="not_a_mastodon_instance">%s bir Mastodon sunucusu gibi görünmüyor.</string>
|
<string name="not_a_mastodon_instance">%s bir Mastodon sunucusu gibi görükmüyor.</string>
|
||||||
<string name="ok">Tamam</string>
|
<string name="ok">Tamam</string>
|
||||||
<string name="preparing_auth">Kimlik doğrulama için hazırlanıyor…</string>
|
<string name="preparing_auth">Kimlik doğrulama için hazırlanıyor…</string>
|
||||||
<string name="finishing_auth">Kimlik doğrulama tamamlanıyor…</string>
|
<string name="finishing_auth">Kimlik doğrulama tamamlanıyor…</string>
|
||||||
<string name="user_boosted">%s yineledi</string>
|
<string name="user_boosted">%s paylaştı</string>
|
||||||
<string name="in_reply_to">%s için yanıt</string>
|
<string name="in_reply_to">%s için yanıt</string>
|
||||||
<string name="notifications">Bildirimler</string>
|
<string name="notifications">Bildirimler</string>
|
||||||
<string name="user_followed_you">sizi takip etti</string>
|
<string name="user_followed_you">sizi takip etti</string>
|
||||||
<string name="user_sent_follow_request">sana bir takip isteği gönderdi</string>
|
<string name="user_sent_follow_request">sana bir takip isteği gönderdi</string>
|
||||||
<string name="user_favorited">gönderinizi favorilerine ekledi</string>
|
<string name="user_favorited">gönderinizi favorilerine ekledi</string>
|
||||||
<string name="notification_boosted">gönderinizi yineledi</string>
|
<string name="notification_boosted">gönderinizi paylaştı</string>
|
||||||
<string name="poll_ended">oylama sona erdi</string>
|
<string name="poll_ended">oylama sona erdi</string>
|
||||||
<string name="time_seconds">%ds</string>
|
<string name="time_seconds">%ds</string>
|
||||||
<string name="time_minutes">%ddk</string>
|
<string name="time_minutes">%ddk</string>
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
<string name="delete">Sil</string>
|
<string name="delete">Sil</string>
|
||||||
<string name="confirm_delete_title">Gönderiyi sil</string>
|
<string name="confirm_delete_title">Gönderiyi sil</string>
|
||||||
<string name="confirm_delete">Bu gönderiyi silmek istediğinizden emin misiniz?</string>
|
<string name="confirm_delete">Bu gönderiyi silmek istediğinizden emin misiniz?</string>
|
||||||
<string name="deleting">Siliniyor...</string>
|
<string name="deleting">Siliniyor</string>
|
||||||
<string name="notification_channel_audio_player">Ses çal</string>
|
<string name="notification_channel_audio_player">Ses çal</string>
|
||||||
<string name="play">Oynat</string>
|
<string name="play">Oynat</string>
|
||||||
<string name="pause">Durdur</string>
|
<string name="pause">Durdur</string>
|
||||||
@@ -149,7 +149,7 @@
|
|||||||
<string name="report_choose_reason_subtitle">En iyi eşleşmeyi seçin</string>
|
<string name="report_choose_reason_subtitle">En iyi eşleşmeyi seçin</string>
|
||||||
<string name="report_reason_personal">Hoşuma gitmiyor</string>
|
<string name="report_reason_personal">Hoşuma gitmiyor</string>
|
||||||
<string name="report_reason_personal_subtitle">Görmek isteyeceğin bir şey değil</string>
|
<string name="report_reason_personal_subtitle">Görmek isteyeceğin bir şey değil</string>
|
||||||
<string name="report_reason_spam">Bu spam</string>
|
<string name="report_reason_spam">Spam</string>
|
||||||
<string name="report_reason_spam_subtitle">Kötü amaçlı bağlantılar, sahte etkileşim veya tekrarlayan yanıtlar</string>
|
<string name="report_reason_spam_subtitle">Kötü amaçlı bağlantılar, sahte etkileşim veya tekrarlayan yanıtlar</string>
|
||||||
<string name="report_reason_violation">Sunucu kurallarını ihlal ediyor</string>
|
<string name="report_reason_violation">Sunucu kurallarını ihlal ediyor</string>
|
||||||
<string name="report_reason_violation_subtitle">Belirli kuralları çiğnediğinin farkındasınız</string>
|
<string name="report_reason_violation_subtitle">Belirli kuralları çiğnediğinin farkındasınız</string>
|
||||||
@@ -166,7 +166,7 @@
|
|||||||
<string name="report_sent_subtitle">Biz bunu incelerken siz %s karşı önlem alabilirsiniz.</string>
|
<string name="report_sent_subtitle">Biz bunu incelerken siz %s karşı önlem alabilirsiniz.</string>
|
||||||
<string name="unfollow_user">Takipten çık %s</string>
|
<string name="unfollow_user">Takipten çık %s</string>
|
||||||
<string name="unfollow">Takipten çık</string>
|
<string name="unfollow">Takipten çık</string>
|
||||||
<string name="mute_user_explain">Ana sayfa akışınızda kişinin gönderilerini görmeyeceksiniz. Sessize alındıklarını bilemeyecekler.</string>
|
<string name="mute_user_explain">Anasayfa akışınızda kişinin gönderilerini görmeyeceksiniz. Sessize alındıklarını bilemeyecekler.</string>
|
||||||
<string name="block_user_explain">Artık sizi takip edemez ve gönderilerinizi göremezler ama engellendiklerini görebilirler.</string>
|
<string name="block_user_explain">Artık sizi takip edemez ve gönderilerinizi göremezler ama engellendiklerini görebilirler.</string>
|
||||||
<string name="report_personal_title">Bunu görmek istemiyor musun?</string>
|
<string name="report_personal_title">Bunu görmek istemiyor musun?</string>
|
||||||
<string name="report_personal_subtitle">Mastodon\'da beğenmediğiniz bir şey gördüğünüzde, o kişiyi deneyiminizden çıkarabilirsiniz.</string>
|
<string name="report_personal_subtitle">Mastodon\'da beğenmediğiniz bir şey gördüğünüzde, o kişiyi deneyiminizden çıkarabilirsiniz.</string>
|
||||||
@@ -200,10 +200,10 @@
|
|||||||
<string name="confirm_email_title">E-posta Kutunuzu Kontrol Edin</string>
|
<string name="confirm_email_title">E-posta Kutunuzu Kontrol Edin</string>
|
||||||
<!-- %s is the email address -->
|
<!-- %s is the email address -->
|
||||||
<string name="confirm_email_subtitle">%s işleminizi doğrulamak için size gönderdiğimiz linke tıklayınız. Biz burada bekleyeceğiz.</string>
|
<string name="confirm_email_subtitle">%s işleminizi doğrulamak için size gönderdiğimiz linke tıklayınız. Biz burada bekleyeceğiz.</string>
|
||||||
<string name="confirm_email_didnt_get">Link size ulaşmadı mı?</string>
|
<string name="confirm_email_didnt_get">Bağlantı size ulaşmadı mı?</string>
|
||||||
<string name="resend">Yeniden gönder</string>
|
<string name="resend">Yeniden gönder</string>
|
||||||
<string name="open_email_app">E-posta uygulamasını aç</string>
|
<string name="open_email_app">Eposta uygulamasını aç</string>
|
||||||
<string name="resent_email">Onay e-postası gönderildi</string>
|
<string name="resent_email">Onay epostası gönderildi</string>
|
||||||
<string name="compose_hint">Aklınızdan geçenleri yazın veya yapıştırın</string>
|
<string name="compose_hint">Aklınızdan geçenleri yazın veya yapıştırın</string>
|
||||||
<string name="content_warning">İçerik Uyarısı</string>
|
<string name="content_warning">İçerik Uyarısı</string>
|
||||||
<string name="add_image_description">Resim açıklaması ekle…</string>
|
<string name="add_image_description">Resim açıklaması ekle…</string>
|
||||||
@@ -243,18 +243,18 @@
|
|||||||
<string name="settings_gif">Animasyonlu avatarları ve emojileri oynat</string>
|
<string name="settings_gif">Animasyonlu avatarları ve emojileri oynat</string>
|
||||||
<string name="settings_custom_tabs">Uygulama içi tarayıcıyı kullan</string>
|
<string name="settings_custom_tabs">Uygulama içi tarayıcıyı kullan</string>
|
||||||
<string name="settings_notifications">Bildirimler</string>
|
<string name="settings_notifications">Bildirimler</string>
|
||||||
<string name="notify_me_when">Beni şu durumda bilgilendir: </string>
|
<string name="notify_me_when">Beni şu durumda bilgilendir</string>
|
||||||
<string name="notify_anyone">Herhangi biri</string>
|
<string name="notify_anyone">Herhangibiri</string>
|
||||||
<string name="notify_follower">Bir takipçim</string>
|
<string name="notify_follower">Bir takipçim</string>
|
||||||
<string name="notify_followed">Takip ettiğim biri</string>
|
<string name="notify_followed">Takip ettiğim biri</string>
|
||||||
<string name="notify_none">Bilgilendirme</string>
|
<string name="notify_none">Bilgilendirme</string>
|
||||||
<string name="notify_favorites">Gönderimi favorilerine eklediğinde</string>
|
<string name="notify_favorites">Gönderimi favorilerine eklediğinde</string>
|
||||||
<string name="notify_follow">Beni takip ettiğinde</string>
|
<string name="notify_follow">Beni takip ettiğinde</string>
|
||||||
<string name="notify_reblog">Gönderimi yinelediğinde</string>
|
<string name="notify_reblog">Gönderimi paylaştığında</string>
|
||||||
<string name="notify_mention">Benden bahsettiğinde</string>
|
<string name="notify_mention">Benden bahsettiğinde</string>
|
||||||
<string name="settings_boring">Sıkıcı bölge</string>
|
<string name="settings_boring">Sıkıcı bölge</string>
|
||||||
<string name="settings_account">Hesap ayarları</string>
|
<string name="settings_account">Hesap ayarları</string>
|
||||||
<string name="settings_contribute">Mastodon\'a katkıda bulunun</string>
|
<string name="settings_contribute">Mastodona katkıda bulunun</string>
|
||||||
<string name="settings_tos">Kullanım Şartları</string>
|
<string name="settings_tos">Kullanım Şartları</string>
|
||||||
<string name="settings_privacy_policy">Gizlilik Politikası</string>
|
<string name="settings_privacy_policy">Gizlilik Politikası</string>
|
||||||
<string name="settings_spicy">Tehlikeli bölge</string>
|
<string name="settings_spicy">Tehlikeli bölge</string>
|
||||||
@@ -271,7 +271,7 @@
|
|||||||
<string name="hide_content">İçeriği gizle</string>
|
<string name="hide_content">İçeriği gizle</string>
|
||||||
<string name="new_post">Yeni gönderi</string>
|
<string name="new_post">Yeni gönderi</string>
|
||||||
<string name="button_reply">Cevapla</string>
|
<string name="button_reply">Cevapla</string>
|
||||||
<string name="button_reblog">Yinele</string>
|
<string name="button_reblog">Yeniden Paylaş</string>
|
||||||
<string name="button_favorite">Favorile</string>
|
<string name="button_favorite">Favorile</string>
|
||||||
<string name="button_share">Paylaş</string>
|
<string name="button_share">Paylaş</string>
|
||||||
<string name="media_no_description">Açıklamasız medya</string>
|
<string name="media_no_description">Açıklamasız medya</string>
|
||||||
@@ -282,7 +282,7 @@
|
|||||||
<string name="home_timeline">Anasayfa</string>
|
<string name="home_timeline">Anasayfa</string>
|
||||||
<string name="my_profile">Profilim</string>
|
<string name="my_profile">Profilim</string>
|
||||||
<string name="media_viewer">Medya görüntüleyici</string>
|
<string name="media_viewer">Medya görüntüleyici</string>
|
||||||
<string name="follow_user">%s\'yi takip et</string>
|
<string name="follow_user">%s \'yi takip et</string>
|
||||||
<string name="unfollowed_user">%s takip edilmedi</string>
|
<string name="unfollowed_user">%s takip edilmedi</string>
|
||||||
<string name="followed_user">%s kişisini takip ediyorsunuz</string>
|
<string name="followed_user">%s kişisini takip ediyorsunuz</string>
|
||||||
<string name="following_user_requested">%s takip isteği gönderdi</string>
|
<string name="following_user_requested">%s takip isteği gönderdi</string>
|
||||||
@@ -310,11 +310,11 @@
|
|||||||
<string name="local_timeline_info_banner">Bu gönderiler seninle aynı Mastodon sunucusunda olan kişilerin paylaştığı son gönderilerdir.</string>
|
<string name="local_timeline_info_banner">Bu gönderiler seninle aynı Mastodon sunucusunda olan kişilerin paylaştığı son gönderilerdir.</string>
|
||||||
<string name="dismiss">Yoksay</string>
|
<string name="dismiss">Yoksay</string>
|
||||||
<string name="see_new_posts">Yeni gönderileri gör</string>
|
<string name="see_new_posts">Yeni gönderileri gör</string>
|
||||||
<string name="load_missing_posts">Daha fazla gönderi yükle</string>
|
<string name="load_missing_posts">Daha fazlası</string>
|
||||||
<string name="follow_back">Geri Takip Et</string>
|
<string name="follow_back">Takip edeni Takip Et</string>
|
||||||
<string name="button_follow_pending">Bekliyor</string>
|
<string name="button_follow_pending">Bekliyor</string>
|
||||||
<string name="follows_you">Seni takip ediyor</string>
|
<string name="follows_you">Seni takip ediyor</string>
|
||||||
<string name="manually_approves_followers">Takipçileri manuel kabul eder</string>
|
<string name="manually_approves_followers">Takipçileri elle kabul et</string>
|
||||||
<string name="current_account">Kullanılan hesap</string>
|
<string name="current_account">Kullanılan hesap</string>
|
||||||
<string name="log_out_account">%s oturumunu kapat</string>
|
<string name="log_out_account">%s oturumunu kapat</string>
|
||||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||||
@@ -331,8 +331,8 @@
|
|||||||
<item quantity="other">%,d favori</item>
|
<item quantity="other">%,d favori</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="x_reblogs">
|
<plurals name="x_reblogs">
|
||||||
<item quantity="one">%,d yineleme</item>
|
<item quantity="one">%,d paylaşma</item>
|
||||||
<item quantity="other">%,d yineleme</item>
|
<item quantity="other">%,d paylaşma</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="timestamp_via_app">%1$s tarihinde %2$s uygulamasıyla</string>
|
<string name="timestamp_via_app">%1$s tarihinde %2$s uygulamasıyla</string>
|
||||||
<string name="time_now">şimdi</string>
|
<string name="time_now">şimdi</string>
|
||||||
@@ -361,7 +361,7 @@
|
|||||||
<string name="edit_media_added">Medya eklendi</string>
|
<string name="edit_media_added">Medya eklendi</string>
|
||||||
<string name="edit_media_removed">Medya kaldırıldı</string>
|
<string name="edit_media_removed">Medya kaldırıldı</string>
|
||||||
<string name="edit_media_reordered">Medya düzenlendi</string>
|
<string name="edit_media_reordered">Medya düzenlendi</string>
|
||||||
<string name="edit_marked_sensitive">Hassas olarak işarlendi</string>
|
<string name="edit_marked_sensitive">Hassas olarak işaretlendi</string>
|
||||||
<string name="edit_marked_not_sensitive">Hassas değil olarak işaretlendi</string>
|
<string name="edit_marked_not_sensitive">Hassas değil olarak işaretlendi</string>
|
||||||
<string name="edit_multiple_changed">Gönderi düzenlendi</string>
|
<string name="edit_multiple_changed">Gönderi düzenlendi</string>
|
||||||
<string name="edit">Düzenle</string>
|
<string name="edit">Düzenle</string>
|
||||||
@@ -373,16 +373,16 @@
|
|||||||
<string name="file_size_gb">%.2f GB</string>
|
<string name="file_size_gb">%.2f GB</string>
|
||||||
<string name="file_upload_progress">%2$s dosyadan %1$s</string>
|
<string name="file_upload_progress">%2$s dosyadan %1$s</string>
|
||||||
<string name="file_upload_time_remaining">%s kaldı</string>
|
<string name="file_upload_time_remaining">%s kaldı</string>
|
||||||
<string name="upload_error_connection_lost">Cihazınızın internet bağlantısı koptu</string>
|
<string name="upload_error_connection_lost">Cihazınızın ağ bağlantısı koptu</string>
|
||||||
<string name="upload_processing">İşleniyor…</string>
|
<string name="upload_processing">İşleniyor…</string>
|
||||||
<!-- %s is version like 1.2.3 -->
|
<!-- %s is version like 1.2.3 -->
|
||||||
<string name="update_available">Mastodon Android uygulamasının %s versiyonu indirmeye hazır.</string>
|
<string name="update_available">Mastodon, Android uygulamasının %s versiyonu indirmeye hazır.</string>
|
||||||
<!-- %s is version like 1.2.3 -->
|
<!-- %s is version like 1.2.3 -->
|
||||||
<string name="update_ready">Mastodon Android uygulamasının %s versiyonu indirildi ve kurulmaya hazır.</string>
|
<string name="update_ready">Mastodon , Android uygulamasının %s versiyonu indirildi ve kurulmaya hazır.</string>
|
||||||
<!-- %s is file size -->
|
<!-- %s is file size -->
|
||||||
<string name="download_update">İndir (%s)</string>
|
<string name="download_update">İndir (%s)</string>
|
||||||
<string name="install_update">Kur</string>
|
<string name="install_update">Kur</string>
|
||||||
<string name="privacy_policy_title">Gizliliğiniz</string>
|
<string name="privacy_policy_title">Gizliliğiniz.</string>
|
||||||
<string name="privacy_policy_subtitle">Mastodon uygulaması herhangi bir veri toplama dahi katıldığınız sunucunun farklı bir politikası olabilir. \n\n %s politikası sizi tatmin etmiyorsa geri giderek farklı bir sunucu seçebilirsiniz.</string>
|
<string name="privacy_policy_subtitle">Mastodon uygulaması herhangi bir veri toplama dahi katıldığınız sunucunun farklı bir politikası olabilir. \n\n %s politikası sizi tatmin etmiyorsa geri giderek farklı bir sunucu seçebilirsiniz.</string>
|
||||||
<string name="i_agree">Kabul ediyorum</string>
|
<string name="i_agree">Kabul ediyorum</string>
|
||||||
<string name="empty_list">Bu liste boş</string>
|
<string name="empty_list">Bu liste boş</string>
|
||||||
@@ -392,9 +392,9 @@
|
|||||||
<string name="remove_bookmark">Yer İmi Kaldır</string>
|
<string name="remove_bookmark">Yer İmi Kaldır</string>
|
||||||
<string name="bookmarks">Yer İmleri</string>
|
<string name="bookmarks">Yer İmleri</string>
|
||||||
<string name="your_favorites">Favorilerin</string>
|
<string name="your_favorites">Favorilerin</string>
|
||||||
<string name="login_title">Hoşgeldin</string>
|
<string name="login_title">Hoşgeldiniz</string>
|
||||||
<string name="login_subtitle">Hesabınızı oluşturduğunuz sunucu ile giriş yapın.</string>
|
<string name="login_subtitle">Hesabınızı oluşturduğunuz sunucu ile giriş yapın.</string>
|
||||||
<string name="server_url">Sunucu URL\'si</string>
|
<string name="server_url">Sunucu bağlantısı</string>
|
||||||
<string name="signup_random_server_explain">Herhangi bir seçim yapmadan devam ederseniz dilinize göre bir sunucu seçeceğiz.</string>
|
<string name="signup_random_server_explain">Herhangi bir seçim yapmadan devam ederseniz dilinize göre bir sunucu seçeceğiz.</string>
|
||||||
<string name="server_filter_any_language">Herhangi Bir Dil</string>
|
<string name="server_filter_any_language">Herhangi Bir Dil</string>
|
||||||
<string name="server_filter_instant_signup">Koşulsuz Kayıt</string>
|
<string name="server_filter_instant_signup">Koşulsuz Kayıt</string>
|
||||||
@@ -419,7 +419,7 @@
|
|||||||
<string name="server_rules_disagree">Reddet</string>
|
<string name="server_rules_disagree">Reddet</string>
|
||||||
<string name="privacy_policy_explanation">Kısacası: Hiçbir veri işlemiyor ya da toplamıyoruz.</string>
|
<string name="privacy_policy_explanation">Kısacası: Hiçbir veri işlemiyor ya da toplamıyoruz.</string>
|
||||||
<!-- %s is server domain -->
|
<!-- %s is server domain -->
|
||||||
<string name="server_policy_disagree">%s\'yi reddet</string>
|
<string name="server_policy_disagree">%s \'yi reddet</string>
|
||||||
<string name="profile_bio">Hakkımda</string>
|
<string name="profile_bio">Hakkımda</string>
|
||||||
<!-- Shown in a progress dialog when you tap "follow all" -->
|
<!-- Shown in a progress dialog when you tap "follow all" -->
|
||||||
<string name="sending_follows">Hesaplar takip ediliyor…</string>
|
<string name="sending_follows">Hesaplar takip ediliyor…</string>
|
||||||
@@ -437,12 +437,12 @@
|
|||||||
<string name="verified_link">Onaylanmış bağlantı</string>
|
<string name="verified_link">Onaylanmış bağlantı</string>
|
||||||
<string name="show">Göster</string>
|
<string name="show">Göster</string>
|
||||||
<string name="hide">Gizle</string>
|
<string name="hide">Gizle</string>
|
||||||
<string name="join_default_server">%s\'e katıl</string>
|
<string name="join_default_server">%s katıl</string>
|
||||||
<string name="pick_server">Başka sunucu seç</string>
|
<string name="pick_server">Başka sunucu seç</string>
|
||||||
<string name="signup_or_login">veya</string>
|
<string name="signup_or_login">veya</string>
|
||||||
<string name="learn_more">Daha fazla bilgi edinin</string>
|
<string name="learn_more">Daha fazlası</string>
|
||||||
<string name="welcome_to_mastodon">Mastodon\'a hoş geldiniz</string>
|
<string name="welcome_to_mastodon">Mastodon\'a hoş geldiniz</string>
|
||||||
<string name="welcome_paragraph1">Mastodon merkezi olmayan bir sosyal ağdır, yani tek bir şirket tarafından kontrol edilmemektedir. Hepsi birbirine bağlı, bağımsız olarak işletilen birçok sunucudan oluşur.</string>
|
<string name="welcome_paragraph1">Mastodon , Merkezi olmayan bir sosyal ağdır, yani tek bir şirket tarafından kontrol edilmemektedir. Hepsi birbirine bağlı, bağımsız olarak işletilen birçok sunucudan oluşur.</string>
|
||||||
<string name="what_are_servers">Sunucular nelerdir?</string>
|
<string name="what_are_servers">Sunucular nelerdir?</string>
|
||||||
<string name="welcome_paragraph2"><![CDATA[Her Mastodon hesabı bir sunucuda barındırılır - her birinin kendi değerleri, kuralları ve yöneticileri vardır. Hangisini seçerseniz seçin, herhangi bir sunucudaki insanları takip edebilir ve onlarla etkileşime geçebilirsiniz.]]></string>
|
<string name="welcome_paragraph2"><![CDATA[Her Mastodon , hesabı bir sunucuda barındırılır - her birinin kendi değerleri, kuralları ve yöneticileri vardır. Hangisini seçerseniz seçin, herhangi bir sunucudaki insanları takip edebilir ve onlarla etkileşime geçebilirsiniz.]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -286,4 +286,11 @@
|
|||||||
<string name="sk_settings_default_content_type_explanation">Це дозволяє вам попередньо вибрати тип вмісту під час написання нових дописів, замінивши значення, встановлене в «Налаштуваннях постингу».</string>
|
<string name="sk_settings_default_content_type_explanation">Це дозволяє вам попередньо вибрати тип вмісту під час написання нових дописів, замінивши значення, встановлене в «Налаштуваннях постингу».</string>
|
||||||
<string name="sk_content_type_markdown">Markdown</string>
|
<string name="sk_content_type_markdown">Markdown</string>
|
||||||
<string name="sk_settings_content_types_explanation">Дозволяє налаштувати тип вмісту, наприклад, Markdown, під час написання допису. Зауважте, що не всі сервери підтримують цю функцію.</string>
|
<string name="sk_settings_content_types_explanation">Дозволяє налаштувати тип вмісту, наприклад, Markdown, під час написання допису. Зауважте, що не всі сервери підтримують цю функцію.</string>
|
||||||
|
<string name="sk_open_in_app">Відкрити у застосунку</string>
|
||||||
|
<string name="sk_external_share_title">Поділитися через обліковий запис</string>
|
||||||
|
<string name="sk_bubble_timeline_info_banner">Це найновіші дописи з мережі керованої адміністраторами вашого сервера.</string>
|
||||||
|
<string name="sk_timeline_bubble">Бульбашка</string>
|
||||||
|
<string name="sk_instance_info_unavailable">Сервер тимчасово недоступний</string>
|
||||||
|
<string name="sk_external_share_or_open_title">Поділитися або відкрити за допомогою облікового запису</string>
|
||||||
|
<string name="sk_open_in_app_failed">Не вдалося відкрити в застосунку</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
<attr name="colorAccentLightest" format="color"/>
|
<attr name="colorAccentLightest" format="color"/>
|
||||||
<attr name="profileHeaderBackground" format="color"/>
|
<attr name="profileHeaderBackground" format="color"/>
|
||||||
<attr name="toolbarBackground" format="color"/>
|
<attr name="toolbarBackground" format="color"/>
|
||||||
|
<attr name="colorIconDisabled" format="color"/>
|
||||||
|
|
||||||
<attr name="colorButtonBackgroundPrimaryDarkOnLight" format="color"/>
|
<attr name="colorButtonBackgroundPrimaryDarkOnLight" format="color"/>
|
||||||
<attr name="colorButtonBackgroundPrimaryDarkOnLightDisabled" format="color"/>
|
<attr name="colorButtonBackgroundPrimaryDarkOnLightDisabled" format="color"/>
|
||||||
@@ -73,6 +74,7 @@
|
|||||||
|
|
||||||
<declare-styleable name="MaxWidthFrameLayout">
|
<declare-styleable name="MaxWidthFrameLayout">
|
||||||
<attr name="android:maxWidth" format="dimension"/>
|
<attr name="android:maxWidth" format="dimension"/>
|
||||||
|
<attr name="defaultWidth" format="dimension" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
<declare-styleable name="FloatingHintEditTextLayout">
|
<declare-styleable name="FloatingHintEditTextLayout">
|
||||||
|
|||||||
@@ -3,4 +3,5 @@
|
|||||||
<dimen name="text_max_height">220dp</dimen>
|
<dimen name="text_max_height">220dp</dimen>
|
||||||
<dimen name="text_collapsed_height">145dp</dimen>
|
<dimen name="text_collapsed_height">145dp</dimen>
|
||||||
<dimen name="layout_max_width">450dp</dimen>
|
<dimen name="layout_max_width">450dp</dimen>
|
||||||
|
<dimen name="scroll_to_top_delta">300dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
<string name="sk_user_post_notifications_off">Turned off post notifications for %s</string>
|
<string name="sk_user_post_notifications_off">Turned off post notifications for %s</string>
|
||||||
<string name="sk_federated_timeline">Federation</string>
|
<string name="sk_federated_timeline">Federation</string>
|
||||||
<string name="sk_federated_timeline_info_banner">These are the most recent posts by the people in your federation.</string>
|
<string name="sk_federated_timeline_info_banner">These are the most recent posts by the people in your federation.</string>
|
||||||
<string name="sk_bubble_timeline_info_banner">These are the most recent posts by the people in your Akkoma server\'s bubble.</string>
|
<string name="sk_bubble_timeline_info_banner">These are the most recent posts from the network curated by your instance admins.</string>
|
||||||
<string name="sk_update_available">Megalodon %s is ready to download.</string>
|
<string name="sk_update_available">Megalodon %s is ready to download.</string>
|
||||||
<string name="sk_update_ready">Megalodon %s is downloaded and ready to install.</string>
|
<string name="sk_update_ready">Megalodon %s is downloaded and ready to install.</string>
|
||||||
<string name="sk_check_for_update">Check for update</string>
|
<string name="sk_check_for_update">Check for update</string>
|
||||||
@@ -290,6 +290,11 @@
|
|||||||
<string name="sk_settings_default_content_type_explanation">This lets you have a content type be pre-selected when creating new posts, overriding the value set in “Posting preferences”.</string>
|
<string name="sk_settings_default_content_type_explanation">This lets you have a content type be pre-selected when creating new posts, overriding the value set in “Posting preferences”.</string>
|
||||||
<string name="sk_instance_info_unavailable">Instance info temporarily unavailable</string>
|
<string name="sk_instance_info_unavailable">Instance info temporarily unavailable</string>
|
||||||
<string name="sk_open_in_app">Open in app</string>
|
<string name="sk_open_in_app">Open in app</string>
|
||||||
|
<string name="sk_open_in_app_failed">Could not open in app</string>
|
||||||
<string name="sk_external_share_title">Share with account</string>
|
<string name="sk_external_share_title">Share with account</string>
|
||||||
<string name="sk_external_share_or_open_title">Share or open with account</string>
|
<string name="sk_external_share_or_open_title">Share or open with account</string>
|
||||||
|
<string name="sk_no_remote_info_hint">remote info unavailable</string>
|
||||||
|
<string name="sk_error_loading_profile">Failed loading the profile on your home instance.</string>
|
||||||
|
<string name="sk_settings_allow_remote_loading">Load info from remote instances</string>
|
||||||
|
<string name="sk_settings_allow_remote_loading_explanation">Try fetching more accurate listings for followers, likes and boosts by loading the information from the instance of origin.</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
<item name="android:colorBackground">?colorGray100</item>
|
<item name="android:colorBackground">?colorGray100</item>
|
||||||
<item name="android:textColorPrimary">?colorGray800</item>
|
<item name="android:textColorPrimary">?colorGray800</item>
|
||||||
<item name="android:textColorSecondary">?colorGray500</item>
|
<item name="android:textColorSecondary">?colorGray500</item>
|
||||||
|
<item name="colorIconDisabled">?colorGray300</item>
|
||||||
<item name="colorButtonText">?colorGray50</item>
|
<item name="colorButtonText">?colorGray50</item>
|
||||||
<item name="colorSecondary">#E9EDF2</item>
|
<item name="colorSecondary">#E9EDF2</item>
|
||||||
<item name="colorBackgroundLight">?colorGray50</item>
|
<item name="colorBackgroundLight">?colorGray50</item>
|
||||||
@@ -127,6 +128,7 @@
|
|||||||
<item name="android:colorBackground">?colorGray700</item>
|
<item name="android:colorBackground">?colorGray700</item>
|
||||||
<item name="android:textColorPrimary">?colorGray50</item>
|
<item name="android:textColorPrimary">?colorGray50</item>
|
||||||
<item name="android:textColorSecondary">?colorGray400</item>
|
<item name="android:textColorSecondary">?colorGray400</item>
|
||||||
|
<item name="colorIconDisabled">?colorGray500</item>
|
||||||
<item name="colorButtonText">?colorGray800</item>
|
<item name="colorButtonText">?colorGray800</item>
|
||||||
<item name="colorSecondary">#E9EDF2</item>
|
<item name="colorSecondary">#E9EDF2</item>
|
||||||
<item name="colorBackgroundLight">?colorGray700</item>
|
<item name="colorBackgroundLight">?colorGray700</item>
|
||||||
|
|||||||
Reference in New Issue
Block a user