Compare commits
86 Commits
v1.2.3+for
...
v1.2.3+for
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
ac39f119e2 | ||
|
|
016faf3df0 | ||
|
|
b2d6879282 | ||
|
|
6926a212f4 | ||
|
|
89afc05d5c | ||
|
|
936f39161b | ||
|
|
ee20ee0722 | ||
|
|
02f9f8c8ea | ||
|
|
de3a252884 | ||
|
|
5e7a00de3e | ||
|
|
2858aeb55e | ||
|
|
357104efa9 | ||
|
|
bb8027c7ef | ||
|
|
f9dd787009 | ||
|
|
e005731ba6 | ||
|
|
18ae3f4f61 | ||
|
|
10dfe0327e | ||
|
|
1d1e921137 | ||
|
|
0985a4c968 | ||
|
|
8df589c103 | ||
|
|
71b6b2f451 | ||
|
|
d85940ded8 | ||
|
|
e9e491c0b0 | ||
|
|
c73562fb75 | ||
|
|
3feacb59c8 | ||
|
|
a033d711c1 |
@@ -15,8 +15,8 @@ android {
|
||||
applicationId "org.joinmastodon.android.sk"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 85
|
||||
versionName "1.2.3+fork.85"
|
||||
versionCode 90
|
||||
versionName "1.2.3+fork.90"
|
||||
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']
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
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 updateMainStatus() {
|
||||
ThreadFragment fragment = new ThreadFragment();
|
||||
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.updateMainStatus();
|
||||
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.updateMainStatus();
|
||||
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);
|
||||
}
|
||||
|
||||
@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"
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,8 @@
|
||||
<data android:scheme="megalodon-android-auth" android:host="callback"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".ExternalShareActivity" android:exported="true" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize">
|
||||
<activity android:name=".ExternalShareActivity" android:exported="true" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize"
|
||||
android:theme="@style/TransparentDialog">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
|
||||
@@ -3,21 +3,24 @@ package org.joinmastodon.android;
|
||||
import android.app.Fragment;
|
||||
import android.content.ClipData;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.jsoup.internal.StringUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.FragmentStackActivity;
|
||||
@@ -28,18 +31,43 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
||||
UiUtils.setUserPreferredTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
if(savedInstanceState==null){
|
||||
|
||||
Optional<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT));
|
||||
Optional<Pair<String, Optional<String>>> fediHandle = text.flatMap(UiUtils::parseFediverseHandle);
|
||||
boolean isFediUrl = text.map(UiUtils::looksLikeFediverseUrl).orElse(false);
|
||||
boolean isOpenable = isFediUrl || fediHandle.isPresent();
|
||||
|
||||
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts();
|
||||
if(sessions.isEmpty()){
|
||||
if (sessions.isEmpty()){
|
||||
Toast.makeText(this, R.string.err_not_logged_in, Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}else if(sessions.size()==1){
|
||||
} else if (isOpenable || sessions.size() > 1) {
|
||||
AccountSwitcherSheet sheet = new AccountSwitcherSheet(this, null, true, isOpenable);
|
||||
if (isOpenable) sheet.setOnClick((accountId, open) -> {
|
||||
if (open && text.isPresent()) {
|
||||
BiConsumer<Class<? extends Fragment>, Bundle> callback = (clazz, args) -> {
|
||||
if (clazz == null) {
|
||||
Toast.makeText(this, R.string.sk_open_in_app_failed, Toast.LENGTH_SHORT).show();
|
||||
// TODO: do something about the window getting leaked
|
||||
sheet.dismiss();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
args.putString("fromExternalShare", clazz.getSimpleName());
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.putExtras(args);
|
||||
finish();
|
||||
startActivity(intent);
|
||||
};
|
||||
if (isFediUrl) UiUtils.lookupURL(this, accountId, text.get(), false, callback);
|
||||
else UiUtils.lookupAccountHandle(this, accountId, fediHandle.get(), callback);
|
||||
} else {
|
||||
openComposeFragment(accountId);
|
||||
}
|
||||
});
|
||||
sheet.show();
|
||||
} else if (sessions.size() == 1) {
|
||||
openComposeFragment(sessions.get(0).getID());
|
||||
}else{
|
||||
getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000));
|
||||
UiUtils.pickAccount(this, null, R.string.choose_account, 0,
|
||||
session -> openComposeFragment(session.getID()),
|
||||
b -> b.setOnCancelListener(d -> finish())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,14 @@ package org.joinmastodon.android;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Fragment;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
@@ -20,12 +23,13 @@ import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.FragmentStackActivity;
|
||||
|
||||
public class MainActivity extends FragmentStackActivity{
|
||||
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState){
|
||||
UiUtils.setUserPreferredTheme(this);
|
||||
@@ -35,10 +39,18 @@ public class MainActivity extends FragmentStackActivity{
|
||||
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
|
||||
showFragmentClearingBackStack(new CustomWelcomeFragment());
|
||||
}else{
|
||||
AccountSessionManager.getInstance().maybeUpdateLocalInfo();
|
||||
AccountSession session;
|
||||
Bundle args=new Bundle();
|
||||
Intent intent=getIntent();
|
||||
if(intent.hasExtra("fromExternalShare")) {
|
||||
AccountSessionManager.getInstance()
|
||||
.setLastActiveAccountID(intent.getStringExtra("account"));
|
||||
AccountSessionManager.getInstance().maybeUpdateLocalInfo(
|
||||
AccountSessionManager.getInstance().getLastActiveAccount());
|
||||
showFragmentForExternalShare(intent.getExtras());
|
||||
return;
|
||||
}
|
||||
|
||||
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
|
||||
boolean hasNotification = intent.hasExtra("notification");
|
||||
if(fromNotification){
|
||||
@@ -52,6 +64,7 @@ public class MainActivity extends FragmentStackActivity{
|
||||
}else{
|
||||
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
||||
}
|
||||
AccountSessionManager.getInstance().maybeUpdateLocalInfo(session);
|
||||
args.putString("account", session.getID());
|
||||
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
|
||||
fragment.setArguments(args);
|
||||
@@ -75,11 +88,12 @@ public class MainActivity extends FragmentStackActivity{
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent){
|
||||
super.onNewIntent(intent);
|
||||
if(intent.getBooleanExtra("fromNotification", false)){
|
||||
AccountSessionManager.getInstance().maybeUpdateLocalInfo();
|
||||
if (intent.hasExtra("fromExternalShare")) showFragmentForExternalShare(intent.getExtras());
|
||||
else if (intent.getBooleanExtra("fromNotification", false)) {
|
||||
String accountID=intent.getStringExtra("accountID");
|
||||
AccountSession accountSession;
|
||||
try{
|
||||
accountSession=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
AccountSessionManager.getInstance().getAccount(accountID);
|
||||
}catch(IllegalStateException x){
|
||||
return;
|
||||
}
|
||||
@@ -103,23 +117,24 @@ public class MainActivity extends FragmentStackActivity{
|
||||
}
|
||||
|
||||
private void showFragmentForNotification(Notification notification, String accountID){
|
||||
Fragment fragment;
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putBoolean("_can_go_back", true);
|
||||
try{
|
||||
notification.postprocess();
|
||||
}catch(ObjectValidationException x){
|
||||
Log.w("MainActivity", x);
|
||||
return;
|
||||
}
|
||||
if(notification.status!=null){
|
||||
fragment=new ThreadFragment();
|
||||
args.putParcelable("status", Parcels.wrap(notification.status));
|
||||
}else{
|
||||
fragment=new ProfileFragment();
|
||||
args.putParcelable("profileAccount", Parcels.wrap(notification.account));
|
||||
}
|
||||
UiUtils.showFragmentForNotification(this, notification, accountID, null);
|
||||
}
|
||||
|
||||
private void showFragmentForExternalShare(Bundle args) {
|
||||
String clazz = args.getString("fromExternalShare");
|
||||
Fragment fragment = switch (clazz) {
|
||||
case "ThreadFragment" -> new ThreadFragment();
|
||||
case "ProfileFragment" -> new ProfileFragment();
|
||||
default -> null;
|
||||
};
|
||||
if (fragment == null) return;
|
||||
args.putBoolean("_can_go_back", true);
|
||||
fragment.setArguments(args);
|
||||
showFragment(fragment);
|
||||
}
|
||||
@@ -153,18 +168,40 @@ public class MainActivity extends FragmentStackActivity{
|
||||
(fragmentContainers.get(fragmentContainers.size() - 1)).getId()
|
||||
);
|
||||
Bundle currentArgs = currentFragment.getArguments();
|
||||
if (this.fragmentContainers.size() == 1
|
||||
&& currentArgs != null
|
||||
&& currentArgs.getBoolean("_can_go_back", false)
|
||||
&& currentArgs.containsKey("account")) {
|
||||
if (fragmentContainers.size() != 1
|
||||
|| currentArgs == null
|
||||
|| !currentArgs.getBoolean("_can_go_back", false)) {
|
||||
super.onBackPressed();
|
||||
return;
|
||||
}
|
||||
if (currentArgs.getBoolean("_finish_on_back", false)) {
|
||||
finish();
|
||||
} else if (currentArgs.containsKey("account")) {
|
||||
Bundle args = new Bundle();
|
||||
args.putString("account", currentArgs.getString("account"));
|
||||
args.putString("tab", "notifications");
|
||||
if (getIntent().getBooleanExtra("fromNotification", false)) {
|
||||
args.putString("tab", "notifications");
|
||||
}
|
||||
Fragment fragment=new HomeFragment();
|
||||
fragment.setArguments(args);
|
||||
showFragmentClearingBackStack(fragment);
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
public Fragment getCurrentFragment() {
|
||||
for (int i = fragmentContainers.size() - 1; i >= 0; i--) {
|
||||
FrameLayout fl = fragmentContainers.get(i);
|
||||
if (fl.getVisibility() == View.VISIBLE) {
|
||||
return getFragmentManager().findFragmentById(fl.getId());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent assistContent) {
|
||||
super.onProvideAssistContent(assistContent);
|
||||
Fragment fragment = getCurrentFragment();
|
||||
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.NotificationReceivedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Mention;
|
||||
import org.joinmastodon.android.model.NotificationAction;
|
||||
import org.joinmastodon.android.model.Preferences;
|
||||
import org.joinmastodon.android.model.PushNotification;
|
||||
@@ -33,6 +34,7 @@ import org.joinmastodon.android.model.StatusPrivacy;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
@@ -273,8 +275,23 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
}
|
||||
CharSequence input = remoteInput.getCharSequence(ACTION_KEY_TEXT_REPLY);
|
||||
|
||||
// copied from ComposeFragment - TODO: generalize?
|
||||
ArrayList<String> mentions=new ArrayList<>();
|
||||
Status status = notification.status;
|
||||
String ownID=AccountSessionManager.getInstance().getAccount(accountID).self.id;
|
||||
if(!status.account.id.equals(ownID))
|
||||
mentions.add('@'+status.account.acct);
|
||||
for(Mention mention:status.mentions){
|
||||
if(mention.id.equals(ownID))
|
||||
continue;
|
||||
String m='@'+mention.acct;
|
||||
if(!mentions.contains(m))
|
||||
mentions.add(m);
|
||||
}
|
||||
String initialText=mentions.isEmpty() ? "" : TextUtils.join(" ", mentions)+" ";
|
||||
|
||||
CreateStatus.Request req=new CreateStatus.Request();
|
||||
req.status = input.toString();
|
||||
req.status = initialText + input.toString();
|
||||
req.language = preferences.postingDefaultLanguage;
|
||||
req.visibility = preferences.postingDefaultVisibility;
|
||||
req.inReplyToId = notification.status.id;
|
||||
@@ -282,7 +299,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
req.spoilerText = "re: " + notification.status.spoilerText;
|
||||
}
|
||||
|
||||
new CreateStatus(req, UUID.randomUUID().toString()).setCallback(new Callback<Status>() {
|
||||
new CreateStatus(req, UUID.randomUUID().toString()).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Status status) {
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
@@ -159,7 +159,7 @@ public class CacheController{
|
||||
}
|
||||
}
|
||||
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(accountSession.domain);
|
||||
new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), instance.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<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Notification> result){
|
||||
|
||||
@@ -46,7 +46,7 @@ public class StatusInteractionController{
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
runningFavoriteRequests.remove(status.id);
|
||||
result.favouritesCount = Math.max(0, status.favouritesCount) + (favorited ? 1 : -1);
|
||||
result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1));
|
||||
cb.accept(result);
|
||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||
}
|
||||
@@ -80,7 +80,7 @@ public class StatusInteractionController{
|
||||
public void onSuccess(Status reblog){
|
||||
Status result = reblog.getContentStatus();
|
||||
runningReblogRequests.remove(status.id);
|
||||
result.reblogsCount = Math.max(0, status.reblogsCount) + (reblogged ? 1 : -1);
|
||||
result.reblogsCount = Math.max(0, status.reblogsCount + (reblogged ? 1 : -1));
|
||||
cb.accept(result);
|
||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.joinmastodon.android.api.session;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import org.joinmastodon.android.api.CacheController;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
||||
@@ -15,6 +17,7 @@ import org.joinmastodon.android.model.Token;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class AccountSession{
|
||||
public Token token;
|
||||
@@ -89,7 +92,14 @@ public class AccountSession{
|
||||
return pushSubscriptionManager;
|
||||
}
|
||||
|
||||
public Instance getInstance() {
|
||||
return AccountSessionManager.getInstance().getInstanceInfo(domain);
|
||||
public Optional<Instance> getInstance() {
|
||||
return Optional.ofNullable(AccountSessionManager.getInstance().getInstanceInfo(domain));
|
||||
}
|
||||
|
||||
public Uri getInstanceUri() {
|
||||
return new Uri.Builder()
|
||||
.scheme("https")
|
||||
.authority(getInstance().map(i -> i.normalizedUri).orElse(domain))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,14 +125,16 @@ public class AccountSessionManager{
|
||||
}
|
||||
|
||||
public synchronized void writeAccountsFile(){
|
||||
File file=new File(MastodonApp.context.getFilesDir(), "accounts.json");
|
||||
File tmpFile = new File(MastodonApp.context.getFilesDir(), "accounts.json~");
|
||||
File file = new File(MastodonApp.context.getFilesDir(), "accounts.json");
|
||||
try{
|
||||
try(FileOutputStream out=new FileOutputStream(file)){
|
||||
try(FileOutputStream out=new FileOutputStream(tmpFile)){
|
||||
SessionsStorageWrapper w=new SessionsStorageWrapper();
|
||||
w.accounts=new ArrayList<>(sessions.values());
|
||||
OutputStreamWriter writer=new OutputStreamWriter(out, StandardCharsets.UTF_8);
|
||||
MastodonAPIController.gson.toJson(w, writer);
|
||||
writer.flush();
|
||||
if (!tmpFile.renameTo(file)) Log.e(TAG, "Error renaming " + tmpFile.getPath() + " to " + file.getPath());
|
||||
}
|
||||
}catch(IOException x){
|
||||
Log.e(TAG, "Error writing accounts file", x);
|
||||
@@ -256,31 +258,35 @@ public class AccountSessionManager{
|
||||
}
|
||||
|
||||
public void maybeUpdateLocalInfo(){
|
||||
maybeUpdateLocalInfo(null);
|
||||
}
|
||||
|
||||
public void maybeUpdateLocalInfo(AccountSession activeSession){
|
||||
long now=System.currentTimeMillis();
|
||||
HashSet<String> domains=new HashSet<>();
|
||||
for(AccountSession session:sessions.values()){
|
||||
domains.add(session.domain.toLowerCase());
|
||||
// if(now-session.infoLastUpdated>24L*3600_000L){
|
||||
updateSessionPreferences(session);
|
||||
updateSessionLocalInfo(session);
|
||||
// }
|
||||
// if(now-session.filtersLastUpdated>3600_000L){
|
||||
updateSessionWordFilters(session);
|
||||
// }
|
||||
if(now-session.infoLastUpdated>24L*3600_000L || session == activeSession){
|
||||
updateSessionPreferences(session);
|
||||
updateSessionLocalInfo(session);
|
||||
}
|
||||
if(now-session.filtersLastUpdated>3600_000L || session == activeSession){
|
||||
updateSessionWordFilters(session);
|
||||
}
|
||||
updateSessionMarkers(session);
|
||||
}
|
||||
if(loadedInstances){
|
||||
maybeUpdateCustomEmojis(domains);
|
||||
maybeUpdateCustomEmojis(domains, activeSession != null ? activeSession.domain : null);
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeUpdateCustomEmojis(Set<String> domains){
|
||||
private void maybeUpdateCustomEmojis(Set<String> domains, String activeDomain){
|
||||
long now=System.currentTimeMillis();
|
||||
for(String domain:domains){
|
||||
// Long lastUpdated=instancesLastUpdated.get(domain);
|
||||
// if(lastUpdated==null || now-lastUpdated>24L*3600_000L){
|
||||
updateInstanceInfo(domain);
|
||||
// }
|
||||
Long lastUpdated=instancesLastUpdated.get(domain);
|
||||
if(lastUpdated==null || now-lastUpdated>24L*3600_000L || domain.equals(activeDomain)){
|
||||
updateInstanceInfo(domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,7 +414,9 @@ public class AccountSessionManager{
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
|
||||
InstanceInfoStorageWrapper wrapper=new InstanceInfoStorageWrapper();
|
||||
wrapper.instance = instance;
|
||||
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, domain));
|
||||
}
|
||||
})
|
||||
.execNoAuth(domain);
|
||||
@@ -419,10 +427,13 @@ public class AccountSessionManager{
|
||||
}
|
||||
|
||||
private void writeInstanceInfoFile(InstanceInfoStorageWrapper emojis, String domain){
|
||||
try(FileOutputStream out=new FileOutputStream(getInstanceInfoFile(domain))){
|
||||
File file = getInstanceInfoFile(domain);
|
||||
File tmpFile = new File(file.getPath() + "~");
|
||||
try(FileOutputStream out=new FileOutputStream(tmpFile)){
|
||||
OutputStreamWriter writer=new OutputStreamWriter(out, StandardCharsets.UTF_8);
|
||||
MastodonAPIController.gson.toJson(emojis, writer);
|
||||
writer.flush();
|
||||
if (!tmpFile.renameTo(file)) Log.e(TAG, "Error renaming " + tmpFile.getPath() + " to " + file.getPath());
|
||||
}catch(IOException x){
|
||||
Log.w(TAG, "Error writing instance info file for "+domain, x);
|
||||
}
|
||||
@@ -442,7 +453,7 @@ public class AccountSessionManager{
|
||||
}
|
||||
if(!loadedInstances){
|
||||
loadedInstances=true;
|
||||
maybeUpdateCustomEmojis(domains);
|
||||
maybeUpdateCustomEmojis(domains, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,12 +474,7 @@ public class AccountSessionManager{
|
||||
}
|
||||
|
||||
public Instance getInstanceInfo(String domain){
|
||||
Instance instance = instances.get(domain);
|
||||
if (instance == null) {
|
||||
throw new IllegalStateException("Cannot get instance for " + domain + ". Sessions: "
|
||||
+ String.join(", ", instances.keySet()));
|
||||
}
|
||||
return instance;
|
||||
return instances.get(domain);
|
||||
}
|
||||
|
||||
public void updateAccountInfo(String id, Account account){
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
@@ -130,4 +131,13 @@ public class AccountTimelineFragment extends StatusListFragment{
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return Filter.FilterContext.ACCOUNT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
// could return different uris based on filter (e.g. media -> "/media"), but i want to
|
||||
// return the remote url to the user, and i don't know whether i'd need to append
|
||||
// '#media' (akkoma/pleroma) or '/media' (glitch/mastodon) since i don't know anything
|
||||
// about the remote instance. so, just returning the base url to the user instead
|
||||
return Uri.parse(user.url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.joinmastodon.android.fragments;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
@@ -103,4 +104,9 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return isInstanceAkkoma() ? base.path("/announcements").build() : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
@@ -33,6 +34,7 @@ import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
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.HeaderStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
||||
@@ -45,6 +47,7 @@ import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
||||
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
import org.joinmastodon.android.utils.TypedObjectPool;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -68,7 +71,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends RecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop, HasFab{
|
||||
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends RecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop, HasFab, ProvidesAssistContent.ProvidesWebUri {
|
||||
protected ArrayList<StatusDisplayItem> displayItems=new ArrayList<>();
|
||||
protected DisplayItemsAdapter adapter;
|
||||
protected String accountID;
|
||||
@@ -79,6 +82,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||
protected Rect tmpRect=new Rect();
|
||||
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
|
||||
protected boolean currentlyScrolling;
|
||||
|
||||
public BaseStatusListFragment(){
|
||||
super(20);
|
||||
@@ -92,7 +96,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
UiUtils.loadMaxWidth(getContext());
|
||||
if(GlobalUserPreferences.disableMarquee){
|
||||
setTitleMarqueeEnabled(false);
|
||||
setSubtitleMarqueeEnabled(false);
|
||||
@@ -129,7 +132,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
displayItems.clear();
|
||||
}
|
||||
|
||||
protected void prependItems(List<T> items, boolean notify){
|
||||
protected int prependItems(List<T> items, boolean notify){
|
||||
data.addAll(0, items);
|
||||
int offset=0;
|
||||
for(T s:items){
|
||||
@@ -142,6 +145,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
}
|
||||
if(notify)
|
||||
adapter.notifyItemRangeInserted(0, offset);
|
||||
return offset;
|
||||
}
|
||||
|
||||
protected String getMaxID(){
|
||||
@@ -202,7 +206,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
@Override
|
||||
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
|
||||
MediaAttachmentViewController holder=findPhotoViewHolder(index);
|
||||
if(holder!=null){
|
||||
if(holder!=null && list!=null){
|
||||
transitioningHolder=holder;
|
||||
View view=transitioningHolder.photo;
|
||||
int[] pos={0, 0};
|
||||
@@ -288,6 +292,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
fab.startAnimation(animate);
|
||||
}
|
||||
|
||||
public boolean isScrolling() {
|
||||
return currentlyScrolling;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideFab() {
|
||||
View fab = getFab();
|
||||
@@ -315,7 +323,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
currentPhotoViewer.offsetView(-dx, -dy);
|
||||
|
||||
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) {
|
||||
hideFab();
|
||||
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) {
|
||||
@@ -328,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());
|
||||
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
|
||||
private Rect tmpRect=new Rect();
|
||||
@Override
|
||||
public void getSelectorBounds(View view, Rect outRect){
|
||||
boolean hasDescendant = false, hasAncestor = false, isWarning = false;
|
||||
int lastIndex = -1, firstIndex = -1;
|
||||
list.getDecoratedBoundsWithMargins(view, outRect);
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(view);
|
||||
if(holder instanceof StatusDisplayItem.Holder){
|
||||
@@ -345,18 +361,42 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
View child=list.getChildAt(i);
|
||||
holder=list.getChildViewHolder(child);
|
||||
if(holder instanceof StatusDisplayItem.Holder){
|
||||
if(holder instanceof StatusDisplayItem.Holder<?> h){
|
||||
String otherID=((StatusDisplayItem.Holder<?>) holder).getItemID();
|
||||
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);
|
||||
outRect.left=Math.min(outRect.left, tmpRect.left);
|
||||
outRect.top=Math.min(outRect.top, tmpRect.top);
|
||||
outRect.right=Math.max(outRect.right, tmpRect.right);
|
||||
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());
|
||||
@@ -570,6 +610,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
warning.getItem().status.filterRevealed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountID(){
|
||||
return accountID;
|
||||
}
|
||||
@@ -703,6 +744,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
return attachmentViewsPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent assistContent) {
|
||||
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
|
||||
}
|
||||
|
||||
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
||||
|
||||
@@ -764,6 +809,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
RecyclerView.ViewHolder siblingHolder=parent.getChildViewHolder(bottomSibling);
|
||||
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){
|
||||
if (!ih.getItem().isMainStatus && ih.getItem().hasDescendantNeighbor) continue;
|
||||
drawDivider(child, bottomSibling, holder, siblingHolder, parent, c, dividerPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetBookmarkedStatuses;
|
||||
@@ -41,4 +42,9 @@ public class BookmarkedStatusListFragment extends StatusListFragment{
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return Filter.FilterContext.ACCOUNT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return base.path("/bookmarks").build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,9 +263,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
Nav.finish(this);
|
||||
return;
|
||||
}
|
||||
if(customEmojis.isEmpty()){
|
||||
AccountSessionManager.getInstance().updateInstanceInfo(instanceDomain);
|
||||
}
|
||||
|
||||
Bundle bundle = savedInstanceState != null ? savedInstanceState : getArguments();
|
||||
if (bundle.containsKey("scheduledStatus")) scheduledStatus=Parcels.unwrap(bundle.getParcelable("scheduledStatus"));
|
||||
@@ -582,8 +579,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s){
|
||||
if(s.length()==0)
|
||||
if(s.length()==0){
|
||||
updateCharCounter();
|
||||
return;
|
||||
}
|
||||
int start=lastChangeStart;
|
||||
int count=lastChangeCount;
|
||||
// offset one char back to catch an already typed '@' or '#' or ':'
|
||||
@@ -1087,7 +1086,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
req.status=text;
|
||||
req.localOnly=localOnly;
|
||||
req.visibility=localOnly && instance.isPleroma() ? StatusPrivacy.LOCAL : statusVisibility;
|
||||
req.visibility=localOnly && instance.isAkkoma() ? StatusPrivacy.LOCAL : statusVisibility;
|
||||
req.sensitive=sensitive;
|
||||
req.language=language;
|
||||
req.contentType=contentType;
|
||||
@@ -1902,7 +1901,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
Menu m=visibilityPopup.getMenu();
|
||||
MenuItem localOnlyItem = visibilityPopup.getMenu().findItem(R.id.local_only);
|
||||
boolean prefsSaysSupported = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID);
|
||||
if (instance.isPleroma()) {
|
||||
if (instance.isAkkoma()) {
|
||||
m.findItem(R.id.vis_local).setVisible(true);
|
||||
} else if (localOnly || prefsSaysSupported) {
|
||||
localOnlyItem.setVisible(true);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetFavoritedStatuses;
|
||||
@@ -41,4 +42,11 @@ public class FavoritedStatusListFragment extends StatusListFragment{
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return Filter.FilterContext.ACCOUNT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return base.encodedPath(isInstanceAkkoma()
|
||||
? '/' + getSession().self.username + "#favorites"
|
||||
: "/favourites").build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.app.Activity;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
@@ -24,6 +25,7 @@ import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -46,7 +48,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsListFragment.AccountWrapper> implements ScrollableToTop{
|
||||
public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsListFragment.AccountWrapper> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
|
||||
private String accountID;
|
||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||
private GetAccountRelationships relationshipsRequest;
|
||||
@@ -148,6 +150,16 @@ public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsL
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountID() {
|
||||
return accountID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return base.path(isInstanceAkkoma() ? "/friend-requests" : "/follow_requests").build();
|
||||
}
|
||||
|
||||
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
|
||||
public AccountsAdapter(){
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -14,14 +15,15 @@ import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implements ScrollableToTop {
|
||||
public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
|
||||
private String nextMaxID;
|
||||
private String accountId;
|
||||
private String accountID;
|
||||
|
||||
public FollowedHashtagsFragment() {
|
||||
super(20);
|
||||
@@ -31,7 +33,7 @@ public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implemen
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Bundle args=getArguments();
|
||||
accountId=args.getString("account");
|
||||
accountID=args.getString("account");
|
||||
setTitle(R.string.sk_hashtags_you_follow);
|
||||
}
|
||||
|
||||
@@ -62,7 +64,7 @@ public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implemen
|
||||
onDataLoaded(result, nextMaxID!=null);
|
||||
}
|
||||
})
|
||||
.exec(accountId);
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,6 +77,16 @@ public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implemen
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountID() {
|
||||
return accountID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return isInstanceAkkoma() ? null : base.path("/followed_tags").build();
|
||||
}
|
||||
|
||||
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
@@ -109,7 +121,7 @@ public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implemen
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
UiUtils.openHashtagTimeline(getActivity(), accountId, item.name, item.following);
|
||||
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface HasAccountID {
|
||||
String getAccountID();
|
||||
|
||||
default AccountSession getSession() {
|
||||
return AccountSessionManager.getInstance().getAccount(getAccountID());
|
||||
}
|
||||
|
||||
default boolean isInstanceAkkoma() {
|
||||
return getInstance().map(Instance::isAkkoma).orElse(false);
|
||||
}
|
||||
|
||||
default Optional<Instance> getInstance() {
|
||||
return getSession().getInstance();
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,5 @@ public interface HasFab {
|
||||
View getFab();
|
||||
void showFab();
|
||||
void hideFab();
|
||||
boolean isScrolling();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.Menu;
|
||||
@@ -8,7 +9,6 @@ import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
@@ -159,4 +159,9 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return Filter.FilterContext.PUBLIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return base.path((isInstanceAkkoma() ? "/tag/" : "/tags") + hashtag).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.graphics.Outline;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@@ -16,6 +17,11 @@ import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
|
||||
@@ -30,16 +36,13 @@ import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.TabBar;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
import java.util.Optional;
|
||||
|
||||
import me.grishka.appkit.FragmentStackActivity;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
@@ -52,7 +55,7 @@ import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
|
||||
public class HomeFragment extends AppKitFragment implements OnBackPressedListener{
|
||||
public class HomeFragment extends AppKitFragment implements OnBackPressedListener, ProvidesAssistContent, HasAccountID {
|
||||
private FragmentRootLinearLayout content;
|
||||
private HomeTabFragment homeTabFragment;
|
||||
private NotificationsFragment notificationsFragment;
|
||||
@@ -74,8 +77,9 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
E.register(this);
|
||||
accountID=getArguments().getString("account");
|
||||
setTitle(R.string.sk_app_name);
|
||||
Instance instance = AccountSessionManager.getInstance().getAccount(accountID).getInstance();
|
||||
isPleroma = instance.isPleroma();
|
||||
isPleroma = AccountSessionManager.getInstance().getAccount(accountID).getInstance()
|
||||
.map(Instance::isAkkoma)
|
||||
.orElse(false);
|
||||
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||
setRetainInstance(true);
|
||||
@@ -222,6 +226,13 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public void setCurrentTab(@IdRes int tab){
|
||||
if(tab==currentTab)
|
||||
return;
|
||||
tabBar.selectTab(tab);
|
||||
onTabSelected(tab);
|
||||
}
|
||||
|
||||
private void onTabSelected(@IdRes int tab){
|
||||
Fragment newFragment=fragmentForTab(tab);
|
||||
if(tab==currentTab){
|
||||
@@ -233,7 +244,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
}
|
||||
getChildFragmentManager().beginTransaction().hide(fragmentForTab(currentTab)).show(newFragment).commit();
|
||||
maybeTriggerLoading(newFragment);
|
||||
if (newFragment instanceof HasFab fabulous) fabulous.showFab();
|
||||
if (newFragment instanceof HasFab fabulous && !fabulous.isScrolling()) fabulous.showFab();
|
||||
currentTab=tab;
|
||||
((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this);
|
||||
if (tab == R.id.tab_search && isPleroma) searchFragment.selectSearch();
|
||||
@@ -263,7 +274,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
||||
options.add(session.self.displayName+"\n("+session.self.username+"@"+session.domain+")");
|
||||
}
|
||||
new AccountSwitcherSheet(getActivity()).show();
|
||||
new AccountSwitcherSheet(getActivity(), this).show();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -296,10 +307,10 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
|
||||
public void updateNotificationBadge() {
|
||||
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
|
||||
Instance instance = session.getInstance();
|
||||
if (instance == null) return;
|
||||
Optional<Instance> instance = session.getInstance();
|
||||
if (instance.isEmpty()) return; // avoiding incompatibility with akkoma
|
||||
|
||||
new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance != null && instance.isPleroma())
|
||||
new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance.get().isAkkoma())
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(List<Notification> notifications) {
|
||||
@@ -336,4 +347,14 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
public void onAllNotificationsSeen(AllNotificationsSeenEvent allNotificationsSeenEvent) {
|
||||
setNotificationBadge(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountID() {
|
||||
return accountID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent assistContent) {
|
||||
callFragmentToProvideAssistContent(fragmentForTab(currentTab), assistContent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@@ -54,6 +55,7 @@ import org.joinmastodon.android.model.TimelineDefinition;
|
||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
@@ -71,7 +73,7 @@ import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener, HasFab {
|
||||
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener, HasFab, ProvidesAssistContent {
|
||||
private static final int ANNOUNCEMENTS_RESULT = 654;
|
||||
|
||||
private String accountID;
|
||||
@@ -458,6 +460,12 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||
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) {
|
||||
timelineIcon.setImageResource(timelines[i].getIcon().iconRes);
|
||||
timelineTitle.setText(timelines[i].getTitle(getContext()));
|
||||
@@ -693,6 +701,11 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||
return fab;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent assistContent) {
|
||||
callFragmentToProvideAssistContent(fragments[pager.getCurrentItem()], assistContent);
|
||||
}
|
||||
|
||||
private class HomePagerAdapter extends RecyclerView.Adapter<SimpleViewHolder> {
|
||||
@NonNull
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
@@ -285,4 +286,9 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return Filter.FilterContext.HOME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return base.path("/").build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -168,4 +168,9 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return Filter.FilterContext.HOME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return base.path("/lists/" + listID).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
|
||||
import org.joinmastodon.android.api.requests.lists.CreateList;
|
||||
import org.joinmastodon.android.api.requests.lists.GetLists;
|
||||
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
|
||||
import org.joinmastodon.android.events.ListDeletedEvent;
|
||||
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
||||
import org.joinmastodon.android.model.ListTimeline;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.views.ListTimelineEditor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class ListTimelinesFragment extends RecyclerFragment<ListTimeline> implements ScrollableToTop {
|
||||
private String accountId;
|
||||
private String profileAccountId;
|
||||
private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
|
||||
private final HashMap<String, Boolean> userInList = new HashMap<>();
|
||||
private ListsAdapter adapter;
|
||||
|
||||
public ListTimelinesFragment() {
|
||||
super(10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Bundle args=getArguments();
|
||||
accountId=args.getString("account");
|
||||
setHasOptionsMenu(true);
|
||||
E.register(this);
|
||||
|
||||
if(args.containsKey("profileAccount")){
|
||||
profileAccountId=args.getString("profileAccount");
|
||||
String profileDisplayUsername = args.getString("profileDisplayUsername");
|
||||
setTitle(getString(R.string.sk_lists_with_user, profileDisplayUsername));
|
||||
} else {
|
||||
setTitle(R.string.sk_your_lists);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_list, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.create) {
|
||||
ListTimelineEditor editor = new ListTimelineEditor(getContext());
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.sk_create_list_title)
|
||||
.setIcon(R.drawable.ic_fluent_people_add_28_regular)
|
||||
.setView(editor)
|
||||
.setPositiveButton(R.string.sk_create, (d, which) ->
|
||||
new CreateList(editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(ListTimeline list) {
|
||||
data.add(0, list);
|
||||
adapter.notifyItemRangeInserted(0, 1);
|
||||
E.post(new ListUpdatedCreatedEvent(list.id, list.title, list.repliesPolicy));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(getContext());
|
||||
}
|
||||
}).exec(accountId)
|
||||
)
|
||||
.setNegativeButton(R.string.cancel, (d, which) -> {})
|
||||
.show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void saveListMembership(String listId, boolean isMember) {
|
||||
userInList.put(listId, isMember);
|
||||
List<String> accountIdList = Collections.singletonList(profileAccountId);
|
||||
MastodonAPIRequest<Object> req = isMember ? new AddAccountsToList(listId, accountIdList) : new RemoveAccountsFromList(listId, accountIdList);
|
||||
req.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Object o) {}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(getContext());
|
||||
}
|
||||
}).exec(accountId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
userInListBefore.clear();
|
||||
userInList.clear();
|
||||
currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists())
|
||||
.setCallback(new SimpleCallback<>(this) {
|
||||
@Override
|
||||
public void onSuccess(List<ListTimeline> lists) {
|
||||
if (getActivity() == null) return;
|
||||
for (ListTimeline l : lists) userInListBefore.put(l.id, true);
|
||||
userInList.putAll(userInListBefore);
|
||||
if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
|
||||
if (profileAccountId == null) return;
|
||||
|
||||
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListTimelinesFragment.this) {
|
||||
@Override
|
||||
public void onSuccess(List<ListTimeline> allLists) {
|
||||
if (getActivity() == null) return;
|
||||
List<ListTimeline> newLists = new ArrayList<>();
|
||||
for (ListTimeline l : allLists) {
|
||||
if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
|
||||
if (!userInListBefore.containsKey(l.id)) {
|
||||
userInListBefore.put(l.id, false);
|
||||
}
|
||||
}
|
||||
userInList.putAll(userInListBefore);
|
||||
onDataLoaded(newLists, false);
|
||||
}
|
||||
}).exec(accountId);
|
||||
}
|
||||
})
|
||||
.exec(accountId);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onListDeletedEvent(ListDeletedEvent event) {
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
ListTimeline item = data.get(i);
|
||||
if (item.id.equals(event.id)) {
|
||||
data.remove(i);
|
||||
adapter.notifyItemRemoved(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
ListTimeline item = data.get(i);
|
||||
if (item.id.equals(event.id)) {
|
||||
item.title = event.title;
|
||||
item.repliesPolicy = event.repliesPolicy;
|
||||
adapter.notifyItemChanged(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter<ListViewHolder> getAdapter() {
|
||||
return adapter = new ListsAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollToTop() {
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
private class ListsAdapter extends RecyclerView.Adapter<ListViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
public ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new ListViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ListViewHolder holder, int position) {
|
||||
holder.bind(data.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return data.size();
|
||||
}
|
||||
}
|
||||
|
||||
private class ListViewHolder extends BindableViewHolder<ListTimeline> implements UsableRecyclerView.Clickable{
|
||||
private final TextView title;
|
||||
private final CheckBox listToggle;
|
||||
|
||||
public ListViewHolder(){
|
||||
super(getActivity(), R.layout.item_text, list);
|
||||
title=findViewById(R.id.title);
|
||||
listToggle=findViewById(R.id.list_toggle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(ListTimeline item) {
|
||||
title.setText(item.title);
|
||||
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_people_24_regular), null, null, null);
|
||||
if (profileAccountId != null) {
|
||||
Boolean checked = userInList.get(item.id);
|
||||
listToggle.setVisibility(View.VISIBLE);
|
||||
listToggle.setChecked(userInList.containsKey(item.id) && checked != null && checked);
|
||||
listToggle.setOnClickListener(this::onClickToggle);
|
||||
} else {
|
||||
listToggle.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void onClickToggle(View view) {
|
||||
saveListMembership(item.id, listToggle.isChecked());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountId);
|
||||
args.putString("listID", item.id);
|
||||
args.putString("listTitle", item.title);
|
||||
if (item.repliesPolicy != null) args.putInt("repliesPolicy", item.repliesPolicy.ordinal());
|
||||
Nav.go(getActivity(), ListTimelineFragment.class, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
|
||||
import org.joinmastodon.android.api.requests.lists.CreateList;
|
||||
import org.joinmastodon.android.api.requests.lists.GetLists;
|
||||
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
|
||||
import org.joinmastodon.android.events.ListDeletedEvent;
|
||||
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
||||
import org.joinmastodon.android.model.ListTimeline;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.views.ListTimelineEditor;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class ListsFragment extends RecyclerFragment<ListTimeline> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
|
||||
private String accountID;
|
||||
private String profileAccountId;
|
||||
private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
|
||||
private final HashMap<String, Boolean> userInList = new HashMap<>();
|
||||
private ListsAdapter adapter;
|
||||
|
||||
public ListsFragment() {
|
||||
super(10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Bundle args = getArguments();
|
||||
accountID = args.getString("account");
|
||||
setHasOptionsMenu(true);
|
||||
E.register(this);
|
||||
|
||||
if(args.containsKey("profileAccount")){
|
||||
profileAccountId=args.getString("profileAccount");
|
||||
String profileDisplayUsername = args.getString("profileDisplayUsername");
|
||||
setTitle(getString(R.string.sk_lists_with_user, profileDisplayUsername));
|
||||
} else {
|
||||
setTitle(R.string.sk_your_lists);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_list, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.create) {
|
||||
ListTimelineEditor editor = new ListTimelineEditor(getContext());
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.sk_create_list_title)
|
||||
.setIcon(R.drawable.ic_fluent_people_add_28_regular)
|
||||
.setView(editor)
|
||||
.setPositiveButton(R.string.sk_create, (d, which) ->
|
||||
new CreateList(editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(ListTimeline list) {
|
||||
data.add(0, list);
|
||||
adapter.notifyItemRangeInserted(0, 1);
|
||||
E.post(new ListUpdatedCreatedEvent(list.id, list.title, list.repliesPolicy));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(getContext());
|
||||
}
|
||||
}).exec(accountID)
|
||||
)
|
||||
.setNegativeButton(R.string.cancel, (d, which) -> {})
|
||||
.show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void saveListMembership(String listId, boolean isMember) {
|
||||
userInList.put(listId, isMember);
|
||||
List<String> accountIdList = Collections.singletonList(profileAccountId);
|
||||
MastodonAPIRequest<Object> req = isMember ? new AddAccountsToList(listId, accountIdList) : new RemoveAccountsFromList(listId, accountIdList);
|
||||
req.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Object o) {}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(getContext());
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
userInListBefore.clear();
|
||||
userInList.clear();
|
||||
currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists())
|
||||
.setCallback(new SimpleCallback<>(this) {
|
||||
@Override
|
||||
public void onSuccess(List<ListTimeline> lists) {
|
||||
if (getActivity() == null) return;
|
||||
for (ListTimeline l : lists) userInListBefore.put(l.id, true);
|
||||
userInList.putAll(userInListBefore);
|
||||
if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
|
||||
if (profileAccountId == null) return;
|
||||
|
||||
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListsFragment.this) {
|
||||
@Override
|
||||
public void onSuccess(List<ListTimeline> allLists) {
|
||||
if (getActivity() == null) return;
|
||||
List<ListTimeline> newLists = new ArrayList<>();
|
||||
for (ListTimeline l : allLists) {
|
||||
if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
|
||||
if (!userInListBefore.containsKey(l.id)) {
|
||||
userInListBefore.put(l.id, false);
|
||||
}
|
||||
}
|
||||
userInList.putAll(userInListBefore);
|
||||
onDataLoaded(newLists, false);
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onListDeletedEvent(ListDeletedEvent event) {
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
ListTimeline item = data.get(i);
|
||||
if (item.id.equals(event.id)) {
|
||||
data.remove(i);
|
||||
adapter.notifyItemRemoved(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
ListTimeline item = data.get(i);
|
||||
if (item.id.equals(event.id)) {
|
||||
item.title = event.title;
|
||||
item.repliesPolicy = event.repliesPolicy;
|
||||
adapter.notifyItemChanged(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter<ListViewHolder> getAdapter() {
|
||||
return adapter = new ListsAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollToTop() {
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountID() {
|
||||
return accountID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return base.path("/lists").build();
|
||||
}
|
||||
|
||||
private class ListsAdapter extends RecyclerView.Adapter<ListViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
public ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new ListViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ListViewHolder holder, int position) {
|
||||
holder.bind(data.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return data.size();
|
||||
}
|
||||
}
|
||||
|
||||
private class ListViewHolder extends BindableViewHolder<ListTimeline> implements UsableRecyclerView.Clickable{
|
||||
private final TextView title;
|
||||
private final CheckBox listToggle;
|
||||
|
||||
public ListViewHolder(){
|
||||
super(getActivity(), R.layout.item_text, list);
|
||||
title=findViewById(R.id.title);
|
||||
listToggle=findViewById(R.id.list_toggle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(ListTimeline item) {
|
||||
title.setText(item.title);
|
||||
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_people_24_regular), null, null, null);
|
||||
if (profileAccountId != null) {
|
||||
Boolean checked = userInList.get(item.id);
|
||||
listToggle.setVisibility(View.VISIBLE);
|
||||
listToggle.setChecked(userInList.containsKey(item.id) && checked != null && checked);
|
||||
listToggle.setOnClickListener(this::onClickToggle);
|
||||
} else {
|
||||
listToggle.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void onClickToggle(View view) {
|
||||
saveListMembership(item.id, listToggle.isChecked());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putString("listID", item.id);
|
||||
args.putString("listTitle", item.title);
|
||||
if (item.repliesPolicy != null) args.putInt("repliesPolicy", item.repliesPolicy.ordinal());
|
||||
Nav.go(getActivity(), ListTimelineFragment.class, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -13,6 +14,12 @@ import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
@@ -24,12 +31,7 @@ import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
@@ -37,7 +39,7 @@ import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop{
|
||||
public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop, ProvidesAssistContent {
|
||||
|
||||
private TabLayout tabLayout;
|
||||
private ViewPager2 pager;
|
||||
@@ -47,7 +49,6 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||
private NotificationsListFragment allNotificationsFragment, mentionsFragment;
|
||||
|
||||
private String accountID;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -227,6 +228,11 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent assistContent) {
|
||||
callFragmentToProvideAssistContent(getFragmentForPage(pager.getCurrentItem()), assistContent);
|
||||
}
|
||||
|
||||
private class DiscoverPagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
@@ -9,10 +10,8 @@ import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||
import org.joinmastodon.android.api.requests.notifications.PleromaMarkNotificationsRead;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.AllNotificationsSeenEvent;
|
||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||
@@ -22,6 +21,7 @@ import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Markers;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
||||
@@ -44,7 +44,6 @@ import java.util.stream.Stream;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class NotificationsListFragment extends BaseStatusListFragment<Notification>{
|
||||
@@ -156,16 +155,17 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
loadRelationships(needRelationships);
|
||||
maxID=result.maxID;
|
||||
|
||||
if(offset==0 && !result.items.isEmpty() && !result.isFromCache() && AccountSessionManager.getInstance().getAccount(accountID).markers.notifications != null){
|
||||
Markers markers = AccountSessionManager.getInstance().getAccount(accountID).markers;
|
||||
if(offset==0 && !result.items.isEmpty() && !result.isFromCache() && markers != null && markers.notifications != null){
|
||||
E.post(new AllNotificationsSeenEvent());
|
||||
new SaveMarkers(null, result.items.get(0).id).exec(accountID);
|
||||
if (AccountSessionManager.getInstance().getAccount(accountID).markers != null)
|
||||
AccountSessionManager.getInstance().getAccount(accountID).markers
|
||||
.notifications.lastReadId = result.items.get(0).id;
|
||||
AccountSessionManager.getInstance().getAccount(accountID).markers
|
||||
.notifications.lastReadId = result.items.get(0).id;
|
||||
AccountSessionManager.getInstance().writeAccountsFile();
|
||||
|
||||
if (AccountSessionManager.getInstance().getAccount(accountID).getInstance().isPleroma())
|
||||
if (isInstanceAkkoma()) {
|
||||
new PleromaMarkNotificationsRead(result.items.get(0).id).exec(accountID);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -192,23 +192,10 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
@Override
|
||||
public void onItemClick(String id){
|
||||
Notification n=getNotificationByID(id);
|
||||
if(n.status!=null){
|
||||
Status status=n.status;
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
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);
|
||||
}
|
||||
Bundle args = new Bundle();
|
||||
if(n.status != null && n.status.inReplyToAccountId != null && knownAccounts.containsKey(n.status.inReplyToAccountId))
|
||||
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(n.status.inReplyToAccountId)));
|
||||
UiUtils.showFragmentForNotification(getContext(), n, accountID, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -272,4 +259,11 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
displayItems.subList(index, lastIndex).clear();
|
||||
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return base.path(isInstanceAkkoma()
|
||||
? "/users/" + getSession().self.username + "/interactions"
|
||||
: "/notifications").build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
@@ -50,7 +51,6 @@ import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.account_list.FollowerListFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.FollowingListFragment;
|
||||
@@ -75,6 +75,7 @@ import org.joinmastodon.android.ui.views.CoverImageView;
|
||||
import org.joinmastodon.android.ui.views.LinkedTextView;
|
||||
import org.joinmastodon.android.ui.views.NestedRecyclerScrollView;
|
||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
@@ -92,6 +93,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
@@ -111,7 +113,7 @@ import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop, HasFab{
|
||||
public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop, HasFab, ProvidesAssistContent.ProvidesWebUri {
|
||||
private static final int AVATAR_RESULT=722;
|
||||
private static final int COVER_RESULT=343;
|
||||
|
||||
@@ -182,12 +184,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
loaded=true;
|
||||
if(!isOwnProfile)
|
||||
loadRelationship();
|
||||
else {
|
||||
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(domain);
|
||||
if (instance.isPleroma()) {
|
||||
maxFields = instance.pleroma.metadata.fieldsLimits.maxFields;
|
||||
}
|
||||
}
|
||||
else if (isInstanceAkkoma() && getInstance().isPresent())
|
||||
maxFields = getInstance().get().pleroma.metadata.fieldsLimits.maxFields;
|
||||
}else{
|
||||
profileAccountID=getArguments().getString("profileAccountID");
|
||||
if(!getArguments().getBoolean("noAutoLoad", false))
|
||||
@@ -712,7 +710,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
args.putString("profileAccount", profileAccountID);
|
||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||
}
|
||||
Nav.go(getActivity(), ListTimelinesFragment.class, args);
|
||||
Nav.go(getActivity(), ListsFragment.class, args);
|
||||
}else if(id==R.id.followed_hashtags){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
@@ -777,6 +775,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
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){
|
||||
int topBarsH=getToolbar().getHeight()+statusBarHeight;
|
||||
if(scrollY>avatarBorder.getTop()-topBarsH){
|
||||
@@ -1168,6 +1172,21 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
if (adapter != null) adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent assistContent) {
|
||||
callFragmentToProvideAssistContent(getFragmentForPage(pager.getCurrentItem()), assistContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountID() {
|
||||
return accountID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return Uri.parse(account.url);
|
||||
}
|
||||
|
||||
private class MetadataAdapter extends UsableRecyclerView.Adapter<BaseViewHolder> implements ImageLoaderRecyclerAdapter {
|
||||
public MetadataAdapter(){
|
||||
super(imgLoader);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
@@ -181,4 +182,10 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
// TODO: adapt when frontends finally implement a scheduled posts list
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ package org.joinmastodon.android.fragments;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
public interface ScrollableToTop{
|
||||
void scrollToTop();
|
||||
@@ -21,7 +22,7 @@ public interface ScrollableToTop{
|
||||
@Override
|
||||
public boolean onPreDraw(){
|
||||
list.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
list.scrollBy(0, V.dp(300));
|
||||
list.scrollBy(0, UiUtils.SCROLL_TO_TOP_DELTA);
|
||||
list.smoothScrollToPosition(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.LruCache;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
@@ -59,9 +59,11 @@ import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.TextInputFrameLayout;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
@@ -78,7 +80,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class SettingsFragment extends MastodonToolbarFragment{
|
||||
public class SettingsFragment extends MastodonToolbarFragment implements ProvidesAssistContent.ProvidesWebUri {
|
||||
private UsableRecyclerView list;
|
||||
private ArrayList<Item> items=new ArrayList<>();
|
||||
private ThemeItem themeItem;
|
||||
@@ -105,7 +107,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
imageCache = ImageCache.getInstance(getActivity());
|
||||
accountID=getArguments().getString("account");
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
Instance instance = session.getInstance();
|
||||
Optional<Instance> instance = session.getInstance();
|
||||
String instanceName = UiUtils.getInstanceName(accountID);
|
||||
|
||||
if(GithubSelfUpdater.needSelfUpdating()){
|
||||
@@ -223,7 +225,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
GlobalUserPreferences.showReplies=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
if (instance.isPleroma()) {
|
||||
if (isInstanceAkkoma()) {
|
||||
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.inflate(R.menu.reply_visibility);
|
||||
@@ -299,7 +301,9 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
boolean translationAvailable = instance.v2 != null && instance.v2.configuration.translation != null && instance.v2.configuration.translation.enabled;
|
||||
boolean translationAvailable = instance
|
||||
.map(i -> i.v2 != null && i.v2.configuration.translation != null && i.v2.configuration.translation.enabled)
|
||||
.orElse(false);
|
||||
items.add(new SmallTextItem(getString(translationAvailable ?
|
||||
R.string.sk_settings_translation_availability_note_available :
|
||||
R.string.sk_settings_translation_availability_note_unavailable, instanceName)));
|
||||
@@ -324,16 +328,18 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
items.add(new TextItem(R.string.sk_settings_auth, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/auth/edit"), R.drawable.ic_fluent_open_24_regular));
|
||||
|
||||
items.add(new HeaderItem(instanceName));
|
||||
items.add(new TextItem(R.string.sk_settings_rules, ()->{
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(instance));
|
||||
items.add(new TextItem(R.string.sk_settings_rules, instance.<Runnable>map(i -> () -> {
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(i));
|
||||
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||
}, R.drawable.ic_fluent_task_list_ltr_24_regular));
|
||||
}).orElse(null), R.drawable.ic_fluent_task_list_ltr_24_regular));
|
||||
items.add(new TextItem(R.string.sk_settings_about_instance , ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/about"), R.drawable.ic_fluent_info_24_regular));
|
||||
items.add(new TextItem(R.string.settings_tos, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
|
||||
items.add(new TextItem(R.string.settings_privacy_policy, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
|
||||
items.add(new TextItem(R.string.log_out, this::confirmLogOut, R.drawable.ic_fluent_sign_out_24_regular));
|
||||
if (!TextUtils.isEmpty(instance.version)) items.add(new SmallTextItem(getString(R.string.sk_settings_server_version, instance.version)));
|
||||
items.add(new SmallTextItem(instance
|
||||
.map(i -> getString(R.string.sk_settings_server_version, i.version))
|
||||
.orElse(getString(R.string.sk_instance_info_unavailable))));
|
||||
|
||||
items.add(new HeaderItem(R.string.sk_instance_features));
|
||||
items.add(new SwitchItem(R.string.sk_settings_content_types, 0, GlobalUserPreferences.accountsWithContentTypesEnabled.contains(accountID), (i)->{
|
||||
@@ -361,14 +367,16 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
b.setText(getContentTypeString(contentType));
|
||||
contentTypeMenu = popupMenu.getMenu();
|
||||
contentTypeMenu.findItem(ContentType.getContentTypeRes(contentType)).setChecked(true);
|
||||
ContentType.adaptMenuToInstance(contentTypeMenu, instance);
|
||||
instance.ifPresent(i -> ContentType.adaptMenuToInstance(contentTypeMenu, i));
|
||||
}));
|
||||
items.add(new SmallTextItem(getString(R.string.sk_settings_default_content_type_explanation)));
|
||||
items.add(new SwitchItem(R.string.sk_settings_support_local_only, 0, GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID), i->{
|
||||
glitchModeItem.enabled = i.checked;
|
||||
if (i.checked) {
|
||||
GlobalUserPreferences.accountsWithLocalOnlySupport.add(accountID);
|
||||
if (instance.pleroma == null) GlobalUserPreferences.accountsInGlitchMode.add(accountID);
|
||||
if (!isInstanceAkkoma()) {
|
||||
GlobalUserPreferences.accountsInGlitchMode.add(accountID);
|
||||
}
|
||||
} else {
|
||||
GlobalUserPreferences.accountsWithLocalOnlySupport.remove(accountID);
|
||||
GlobalUserPreferences.accountsInGlitchMode.remove(accountID);
|
||||
@@ -734,6 +742,16 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return base.path(isInstanceAkkoma() ? "/about" : "/settings").build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountID() {
|
||||
return accountID;
|
||||
}
|
||||
|
||||
private static abstract class Item{
|
||||
public abstract int getViewType();
|
||||
}
|
||||
@@ -1058,7 +1076,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
private final ImageView icon;
|
||||
private final TextView text;
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public ButtonViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_button, list);
|
||||
text=findViewById(R.id.text);
|
||||
@@ -1066,14 +1083,17 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
button=findViewById(R.id.button);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public void onBind(ButtonItem item){
|
||||
text.setText(item.text);
|
||||
if (item.icon == 0) {
|
||||
icon.setVisibility(View.GONE);
|
||||
} else {
|
||||
icon.setImageResource(item.icon);
|
||||
}
|
||||
icon.setVisibility(item.icon == 0 ? View.GONE : View.VISIBLE);
|
||||
icon.setImageResource(item.icon == 0 ? 0 : item.icon);
|
||||
// reset listeners before letting the button consumer consume the button
|
||||
// (and potentially set some listeners, but not others)
|
||||
button.setOnTouchListener(null);
|
||||
button.setOnClickListener(null);
|
||||
button.setOnLongClickListener(null);
|
||||
item.buttonConsumer.accept(button);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
@@ -24,13 +25,13 @@ import java.util.stream.Collectors;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class StatusEditHistoryFragment extends StatusListFragment{
|
||||
private String id;
|
||||
|
||||
private String id, url;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
id=getArguments().getString("id");
|
||||
url=getArguments().getString("url");
|
||||
loadData();
|
||||
}
|
||||
|
||||
@@ -162,4 +163,9 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return Uri.parse(url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.assist.AssistContent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
|
||||
@@ -28,7 +29,7 @@ import java.util.stream.Stream;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
|
||||
public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
public abstract class StatusListFragment extends BaseStatusListFragment<Status> {
|
||||
protected EventListener eventListener=new EventListener();
|
||||
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||
@@ -182,7 +183,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig){
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
if (getParentFragment() instanceof HomeTabFragment home) home.updateToolbarLogo();
|
||||
}
|
||||
|
||||
@@ -1,36 +1,54 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
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.api.requests.statuses.GetStatusByID;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
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.FooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
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.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class ThreadFragment extends StatusListFragment{
|
||||
protected Status mainStatus;
|
||||
public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent {
|
||||
protected Status mainStatus, updatedStatus;
|
||||
private final HashMap<String, NeighborAncestryInfo> ancestryMap = new HashMap<>();
|
||||
private boolean initialAnimationFinished;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -47,13 +65,44 @@ public class ThreadFragment extends StatusListFragment{
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||
List<StatusDisplayItem> items=super.buildDisplayItems(s);
|
||||
if(s.id.equals(mainStatus.id)){
|
||||
for(StatusDisplayItem item:items){
|
||||
// "what the fuck is a deque"? yes
|
||||
// (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)
|
||||
text.textSelectable=true;
|
||||
else if(item instanceof FooterStatusDisplayItem footer)
|
||||
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()));
|
||||
}
|
||||
return items;
|
||||
@@ -61,43 +110,30 @@ public class ThreadFragment extends StatusListFragment{
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
refreshMainStatus();
|
||||
currentRequest=new GetStatusContext(mainStatus.id)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(StatusContext result){
|
||||
if (getActivity() == null) return;
|
||||
if (getContext() == null) return;
|
||||
if(refreshing){
|
||||
data.clear();
|
||||
ancestryMap.clear();
|
||||
displayItems.clear();
|
||||
data.add(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());
|
||||
result.descendants=getDescendantsOrdered(mainStatus.id,
|
||||
result.descendants.stream()
|
||||
.filter(s -> threadIds.contains(s.id))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
// TODO: figure out how this code works
|
||||
if(isInstanceAkkoma()) sortStatusContext(mainStatus, result);
|
||||
|
||||
result.descendants=filterStatuses(result.descendants);
|
||||
result.ancestors=filterStatuses(result.ancestors);
|
||||
|
||||
for (NeighborAncestryInfo i : mapNeighborhoodAncestry(mainStatus, result)) {
|
||||
ancestryMap.put(i.status.id, i);
|
||||
}
|
||||
|
||||
if(footerProgress!=null)
|
||||
footerProgress.setVisibility(View.GONE);
|
||||
data.addAll(result.descendants);
|
||||
@@ -106,7 +142,12 @@ public class ThreadFragment extends StatusListFragment{
|
||||
int count=displayItems.size();
|
||||
if(!refreshing)
|
||||
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();
|
||||
if(refreshing){
|
||||
refreshDone();
|
||||
@@ -118,7 +159,94 @@ public class ThreadFragment extends StatusListFragment{
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private List<Status> getDescendantsOrdered(String id, List<Status> statuses){
|
||||
private void refreshMainStatus() {
|
||||
new GetStatusByID(mainStatus.id)
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Status status) {
|
||||
if (getContext() == null || status == null) return;
|
||||
updatedStatus = status;
|
||||
// only update main status if the initial animation is already finished.
|
||||
// otherwise, the animator will call it in onAnimationFinished
|
||||
if (initialAnimationFinished || data.size() == 1) updateMainStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
protected Object updateMainStatus() {
|
||||
// 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<>();
|
||||
for(Status s:getDirectDescendants(id, statuses)){
|
||||
out.add(s);
|
||||
@@ -130,7 +258,7 @@ public class ThreadFragment extends StatusListFragment{
|
||||
return out;
|
||||
}
|
||||
|
||||
private List<Status> getDirectDescendants(String id, List<Status> statuses){
|
||||
private static List<Status> getDirectDescendants(String id, List<Status> statuses){
|
||||
return statuses.stream()
|
||||
.filter(s -> s.inReplyToId.equals(id))
|
||||
.collect(Collectors.toList());
|
||||
@@ -159,10 +287,22 @@ public class ThreadFragment extends StatusListFragment{
|
||||
showContent();
|
||||
if(!loaded)
|
||||
footerProgress.setVisibility(View.VISIBLE);
|
||||
list.setItemAnimator(new BetterItemAnimator() {
|
||||
@Override
|
||||
public void onAnimationFinished(@NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
super.onAnimationFinished(viewHolder);
|
||||
// in case someone else is about to call updateMainStatus faster...
|
||||
initialAnimationFinished = true;
|
||||
// ...if not (someone did fetch it but the animation wasn't finished yet),
|
||||
// call it now
|
||||
if (updatedStatus != null) updateMainStatus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void onStatusCreated(StatusCreatedEvent ev){
|
||||
if(ev.status.inReplyToId!=null && getStatusByID(ev.status.inReplyToId)!=null){
|
||||
data.add(ev.status);
|
||||
onAppendItems(Collections.singletonList(ev.status));
|
||||
}
|
||||
}
|
||||
@@ -187,4 +327,34 @@ public class ThreadFragment extends StatusListFragment{
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return Filter.FilterContext.THREAD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments.account_list;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.model.Account;
|
||||
@@ -14,4 +15,11 @@ public abstract class AccountRelatedAccountListFragment extends PaginatedAccount
|
||||
account=Parcels.unwrap(getArguments().getParcelable("targetAccount"));
|
||||
setTitle("@"+account.acct);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return base.path(isInstanceAkkoma()
|
||||
? "/users/" + account.id
|
||||
: '@' + account.acct).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.fragments.account_list;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.drawable.Animatable;
|
||||
@@ -23,7 +24,8 @@ import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.ListTimelinesFragment;
|
||||
import org.joinmastodon.android.fragments.HasAccountID;
|
||||
import org.joinmastodon.android.fragments.ListsFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.RecyclerFragment;
|
||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||
@@ -34,6 +36,7 @@ import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -57,7 +60,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccountListFragment.AccountItem> {
|
||||
public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccountListFragment.AccountItem> implements ProvidesAssistContent.ProvidesWebUri {
|
||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||
protected String accountID;
|
||||
protected ArrayList<APIRequest<?>> relationshipsRequests=new ArrayList<>();
|
||||
@@ -170,6 +173,16 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
||||
super.onApplyWindowInsets(insets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountID() {
|
||||
return accountID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent assistContent) {
|
||||
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
|
||||
}
|
||||
|
||||
protected class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
public AccountsAdapter(){
|
||||
super(imgLoader);
|
||||
@@ -387,7 +400,7 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
||||
args.putString("account", accountID);
|
||||
args.putString("profileAccount", account.id);
|
||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||
Nav.go(getActivity(), ListTimelinesFragment.class, args);
|
||||
Nav.go(getActivity(), ListsFragment.class, args);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments.account_list;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
@@ -19,4 +20,10 @@ public class FollowerListFragment extends AccountRelatedAccountListFragment{
|
||||
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||
return new GetAccountFollowers(account.id, maxID, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return super.getWebUri(base).buildUpon()
|
||||
.appendPath(isInstanceAkkoma() ? "#followers" : "/followers").build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments.account_list;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
@@ -19,4 +20,10 @@ public class FollowingListFragment extends AccountRelatedAccountListFragment{
|
||||
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||
return new GetAccountFollowing(account.id, maxID, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return super.getWebUri(base).buildUpon()
|
||||
.appendPath(isInstanceAkkoma() ? "#followees" : "/following").build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments.account_list;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
@@ -18,4 +19,12 @@ public class StatusFavoritesListFragment extends StatusRelatedAccountListFragmen
|
||||
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||
return new GetStatusFavorites(status.id, maxID, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
Uri statusUri = super.getWebUri(base);
|
||||
return isInstanceAkkoma()
|
||||
? statusUri
|
||||
: statusUri.buildUpon().appendPath("favourites").build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments.account_list;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
@@ -18,4 +19,12 @@ public class StatusReblogsListFragment extends StatusRelatedAccountListFragment{
|
||||
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||
return new GetStatusReblogs(status.id, maxID, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
Uri statusUri = super.getWebUri(base);
|
||||
return isInstanceAkkoma()
|
||||
? statusUri
|
||||
: statusUri.buildUpon().appendPath("reblogs").build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments.account_list;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.model.Status;
|
||||
@@ -18,4 +19,13 @@ public abstract class StatusRelatedAccountListFragment extends PaginatedAccountL
|
||||
protected boolean hasSubtitle(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return base
|
||||
.encodedPath(isInstanceAkkoma()
|
||||
? "/notice/" + status.id
|
||||
: '@' + status.account.acct + '/' + status.id)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
@@ -52,4 +53,9 @@ public class BubbleTimelineFragment extends StatusListFragment {
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return Filter.FilterContext.PUBLIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return isInstanceAkkoma() ? base.path("/main/bubble").build() : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.joinmastodon.android.fragments.discover;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
@@ -27,6 +28,7 @@ import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -49,7 +51,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class DiscoverAccountsFragment extends RecyclerFragment<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop, IsOnTop {
|
||||
public class DiscoverAccountsFragment extends RecyclerFragment<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop, IsOnTop, ProvidesAssistContent.ProvidesWebUri {
|
||||
private String accountID;
|
||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||
private GetAccountRelationships relationshipsRequest;
|
||||
@@ -145,6 +147,16 @@ public class DiscoverAccountsFragment extends RecyclerFragment<DiscoverAccountsF
|
||||
return isRecyclerViewOnTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountID() {
|
||||
return accountID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return isInstanceAkkoma() ? null : base.path("/explore/suggestions").build();
|
||||
}
|
||||
|
||||
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
|
||||
public AccountsAdapter(){
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
@@ -26,6 +27,7 @@ import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -37,7 +39,7 @@ import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, OnBackPressedListener, IsOnTop {
|
||||
public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, OnBackPressedListener, IsOnTop, ProvidesAssistContent {
|
||||
|
||||
private TabLayout tabLayout;
|
||||
private ViewPager2 pager;
|
||||
@@ -50,7 +52,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
private ProgressBar searchProgress;
|
||||
|
||||
private DiscoverPostsFragment postsFragment;
|
||||
private TrendingHashtagsFragment hashtagsFragment;
|
||||
private DiscoverHashtagsFragment hashtagsFragment;
|
||||
private DiscoverNewsFragment newsFragment;
|
||||
private DiscoverAccountsFragment accountsFragment;
|
||||
private SearchFragment searchFragment;
|
||||
@@ -118,7 +120,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
postsFragment=new DiscoverPostsFragment();
|
||||
postsFragment.setArguments(args);
|
||||
|
||||
hashtagsFragment=new TrendingHashtagsFragment();
|
||||
hashtagsFragment=new DiscoverHashtagsFragment();
|
||||
hashtagsFragment.setArguments(args);
|
||||
|
||||
newsFragment=new DiscoverNewsFragment();
|
||||
@@ -321,6 +323,13 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
V.setVisibilityAnimated(searchClear, visible ? View.INVISIBLE : View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent assistContent) {
|
||||
callFragmentToProvideAssistContent(searchActive
|
||||
? searchFragment
|
||||
: getFragmentForPage(pager.getCurrentItem()), assistContent);
|
||||
}
|
||||
|
||||
private class DiscoverPagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.joinmastodon.android.fragments.discover;
|
||||
import static org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem.Holder.withHistoryParams;
|
||||
import static org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem.Holder.withoutHistoryParams;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -18,6 +19,7 @@ import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.HashtagChartView;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -27,11 +29,11 @@ import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class TrendingHashtagsFragment extends RecyclerFragment<Hashtag> implements ScrollableToTop, IsOnTop {
|
||||
public class DiscoverHashtagsFragment extends RecyclerFragment<Hashtag> implements ScrollableToTop, IsOnTop, ProvidesAssistContent.ProvidesWebUri {
|
||||
private String accountID;
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_HASHTAGS);
|
||||
|
||||
public TrendingHashtagsFragment(){
|
||||
public DiscoverHashtagsFragment(){
|
||||
super(10);
|
||||
}
|
||||
|
||||
@@ -76,6 +78,16 @@ public class TrendingHashtagsFragment extends RecyclerFragment<Hashtag> implemen
|
||||
return isRecyclerViewOnTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountID() {
|
||||
return accountID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return isInstanceAkkoma() ? null : base.path("/explore/tags").build();
|
||||
}
|
||||
|
||||
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
@@ -19,6 +20,7 @@ import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -35,7 +37,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class DiscoverNewsFragment extends RecyclerFragment<Card> implements ScrollableToTop, IsOnTop {
|
||||
public class DiscoverNewsFragment extends RecyclerFragment<Card> implements ScrollableToTop, IsOnTop, ProvidesAssistContent.ProvidesWebUri {
|
||||
private String accountID;
|
||||
private List<ImageLoaderRequest> imageRequests=Collections.emptyList();
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS);
|
||||
@@ -88,6 +90,16 @@ public class DiscoverNewsFragment extends RecyclerFragment<Card> implements Scro
|
||||
return isRecyclerViewOnTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountID() {
|
||||
return accountID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return isInstanceAkkoma() ? null : base.path("/explore/links").build();
|
||||
}
|
||||
|
||||
private class LinksAdapter extends UsableRecyclerView.Adapter<LinkViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
public LinksAdapter(){
|
||||
super(imgLoader);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
@@ -43,9 +44,13 @@ public class DiscoverPostsFragment extends StatusListFragment implements IsOnTop
|
||||
return isRecyclerViewOnTop(list);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return Filter.FilterContext.PUBLIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return isInstanceAkkoma() ? null : base.path("/explore/posts").build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
@@ -25,7 +26,6 @@ public class FederatedTimelineFragment extends StatusListFragment {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetPublicTimeline(false, false, refreshing ? null : maxID, count)
|
||||
@@ -52,4 +52,9 @@ public class FederatedTimelineFragment extends StatusListFragment {
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return Filter.FilterContext.PUBLIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return base.path(isInstanceAkkoma() ? "/main/all" : "/public").build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
@@ -24,7 +26,6 @@ public class LocalTimelineFragment extends StatusListFragment {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetPublicTimeline(true, false, refreshing ? null : maxID, count)
|
||||
@@ -51,4 +52,9 @@ public class LocalTimelineFragment extends StatusListFragment {
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return Filter.FilterContext.PUBLIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return base.path(isInstanceAkkoma() ? "/main/public" : "/public/local").build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
@@ -316,6 +317,14 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult> impleme
|
||||
return isRecyclerViewOnTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
Uri.Builder searchUri = base.path("/search");
|
||||
return isInstanceAkkoma()
|
||||
? searchUri.appendQueryParameter("query", currentQuery).build()
|
||||
: searchUri.build();
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ProgressVisibilityListener{
|
||||
void onProgressVisibilityChanged(boolean visible);
|
||||
|
||||
@@ -89,13 +89,6 @@ public class AccountActivationFragment extends ToolbarFragment{
|
||||
return !UiUtils.isDarkTheme();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdateToolbar(){
|
||||
super.onUpdateToolbar();
|
||||
@@ -110,7 +103,7 @@ public class AccountActivationFragment extends ToolbarFragment{
|
||||
|
||||
@Override
|
||||
public void onToolbarNavigationClick(){
|
||||
new AccountSwitcherSheet(getActivity()).show();
|
||||
new AccountSwitcherSheet(getActivity(), null).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,7 +2,9 @@ package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
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.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
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.UsableRecyclerView;
|
||||
|
||||
public class InstanceRulesFragment extends ToolbarFragment{
|
||||
public class InstanceRulesFragment extends ToolbarFragment implements ProvidesAssistContent {
|
||||
private UsableRecyclerView list;
|
||||
private MergeRecyclerAdapter adapter;
|
||||
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>{
|
||||
|
||||
@NonNull
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.SparseIntArray;
|
||||
@@ -267,4 +268,11 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
if (reportStatus != null) return Uri.parse(reportStatus.url);
|
||||
if (reportAccount != null) return Uri.parse(reportAccount.url);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.net.IDN;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@Parcel
|
||||
public class Instance extends BaseModel{
|
||||
@@ -88,6 +89,9 @@ public class Instance extends BaseModel{
|
||||
|
||||
public PleromaPollLimits pollLimits;
|
||||
|
||||
/** like uri, but always without scheme and trailing slash */
|
||||
public transient String normalizedUri;
|
||||
|
||||
@Override
|
||||
public void postprocess() throws ObjectValidationException{
|
||||
super.postprocess();
|
||||
@@ -97,6 +101,10 @@ public class Instance extends BaseModel{
|
||||
rules=Collections.emptyList();
|
||||
if(shortDescription==null)
|
||||
shortDescription="";
|
||||
// akkoma says uri is "https://example.social" while just "example.social" on mastodon
|
||||
normalizedUri = uri
|
||||
.replaceFirst("^https://", "")
|
||||
.replaceFirst("/$", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -136,10 +144,26 @@ public class Instance extends BaseModel{
|
||||
return ci;
|
||||
}
|
||||
|
||||
public boolean isPleroma() {
|
||||
public boolean isAkkoma() {
|
||||
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
|
||||
public static class Rule{
|
||||
public String id;
|
||||
|
||||
@@ -259,13 +259,14 @@ public class TimelineDefinition {
|
||||
public boolean isCompatible(AccountSession session) {
|
||||
// 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"
|
||||
return session.getInstance().isPleroma();
|
||||
return session.getInstance().map(Instance::isAkkoma).orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wantsDefault(AccountSession session) {
|
||||
Instance instance = session.getInstance();
|
||||
return instance.isPleroma() && instance.pleroma.metadata.features.contains("bubble_timeline");
|
||||
return session.getInstance()
|
||||
.map(i -> i.hasFeature(Instance.Feature.BUBBLE_TIMELINE))
|
||||
.orElse(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ package org.joinmastodon.android.ui;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
@@ -14,7 +14,7 @@ import android.view.WindowInsets;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
@@ -23,13 +23,21 @@ import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.HomeFragment;
|
||||
import org.joinmastodon.android.fragments.SplashFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.CheckableRelativeLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
@@ -49,14 +57,26 @@ import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class AccountSwitcherSheet extends BottomSheet{
|
||||
private final Activity activity;
|
||||
private final HomeFragment fragment;
|
||||
private final boolean externalShare, openInApp;
|
||||
private BiConsumer<String, Boolean> onClick;
|
||||
private UsableRecyclerView list;
|
||||
private List<WrappedAccount> accounts;
|
||||
private ListImageLoaderWrapper imgLoader;
|
||||
private AccountsAdapter accountsAdapter;
|
||||
|
||||
public AccountSwitcherSheet(@NonNull Activity activity){
|
||||
public AccountSwitcherSheet(@NonNull Activity activity, @Nullable HomeFragment fragment){
|
||||
this(activity, fragment, false, false);
|
||||
}
|
||||
|
||||
public AccountSwitcherSheet(@NonNull Activity activity, @Nullable HomeFragment fragment, boolean externalShare, boolean openInApp){
|
||||
super(activity);
|
||||
this.activity=activity;
|
||||
|
||||
this.fragment=fragment;
|
||||
this.externalShare = externalShare;
|
||||
this.openInApp = openInApp;
|
||||
this.onClick = onClick;
|
||||
|
||||
accounts=AccountSessionManager.getInstance().getLoggedInAccounts().stream().map(WrappedAccount::new).collect(Collectors.toList());
|
||||
|
||||
list=new UsableRecyclerView(activity);
|
||||
@@ -67,41 +87,63 @@ public class AccountSwitcherSheet extends BottomSheet{
|
||||
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
||||
View handle=new View(activity);
|
||||
handle.setBackgroundResource(R.drawable.bg_bottom_sheet_handle);
|
||||
handle.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(36)));
|
||||
|
||||
adapter.addAdapter(new SingleViewRecyclerAdapter(handle));
|
||||
adapter.addAdapter(new AccountsAdapter());
|
||||
AccountViewHolder holder=new AccountViewHolder();
|
||||
holder.more.setVisibility(View.GONE);
|
||||
holder.currentIcon.setVisibility(View.GONE);
|
||||
holder.name.setText(R.string.add_account);
|
||||
holder.avatar.setScaleType(ImageView.ScaleType.CENTER);
|
||||
holder.avatar.setImageResource(R.drawable.ic_fluent_add_circle_24_filled);
|
||||
holder.avatar.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, android.R.attr.textColorPrimary)));
|
||||
adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(holder.itemView, ()->{
|
||||
Nav.go(activity, CustomWelcomeFragment.class, null);
|
||||
dismiss();
|
||||
}));
|
||||
|
||||
if (externalShare) {
|
||||
FrameLayout shareHeading = new FrameLayout(activity);
|
||||
activity.getLayoutInflater().inflate(R.layout.item_external_share_heading, shareHeading);
|
||||
((TextView) shareHeading.findViewById(R.id.title)).setText(openInApp
|
||||
? R.string.sk_external_share_or_open_title
|
||||
: R.string.sk_external_share_title);
|
||||
adapter.addAdapter(new SingleViewRecyclerAdapter(shareHeading));
|
||||
|
||||
setOnDismissListener((d) -> activity.finish());
|
||||
}
|
||||
|
||||
adapter.addAdapter(accountsAdapter = new AccountsAdapter());
|
||||
|
||||
if (!externalShare) {
|
||||
adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(makeSimpleListItem(R.string.add_account, R.drawable.ic_fluent_add_24_regular), () -> {
|
||||
Nav.go(activity, CustomWelcomeFragment.class, null);
|
||||
dismiss();
|
||||
}));
|
||||
// disabled in megalodon
|
||||
// adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(makeSimpleListItem(R.string.log_out_all_accounts, R.drawable.ic_fluent_person_arrow_right_24_filled), this::confirmLogOutAll));
|
||||
}
|
||||
|
||||
list.setAdapter(adapter);
|
||||
DividerItemDecoration divider=new DividerItemDecoration(activity, R.attr.colorPollVoted, .5f, 72, 16, DividerItemDecoration.NOT_FIRST);
|
||||
divider.setDrawBelowLastItem(true);
|
||||
list.addItemDecoration(divider);
|
||||
|
||||
FrameLayout content=new FrameLayout(activity);
|
||||
content.setBackgroundResource(R.drawable.bg_bottom_sheet);
|
||||
content.addView(list);
|
||||
setContentView(content);
|
||||
setNavigationBarBackground(new ColorDrawable(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground)), !UiUtils.isDarkTheme());
|
||||
setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(activity, R.attr.colorM3Surface),
|
||||
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){
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
new M3AlertDialogBuilder(activity)
|
||||
.setTitle(R.string.log_out)
|
||||
.setMessage(R.string.confirm_log_out)
|
||||
.setMessage(activity.getString(R.string.confirm_log_out, session.getFullUsername()))
|
||||
.setPositiveButton(R.string.log_out, (dialog, which) -> logOut(accountID))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void confirmLogOutAll(){
|
||||
new M3AlertDialogBuilder(activity)
|
||||
.setMessage(R.string.confirm_log_out_all_accounts)
|
||||
.setPositiveButton(R.string.log_out, (dialog, which) -> logOutAll())
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void logOut(String accountID){
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
new RevokeOauthToken(session.app.clientId, session.app.clientSecret, session.token.accessToken)
|
||||
@@ -120,9 +162,55 @@ public class AccountSwitcherSheet extends BottomSheet{
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private void logOutAll(){
|
||||
final ProgressDialog progress=new ProgressDialog(activity);
|
||||
progress.setMessage(activity.getString(R.string.loading));
|
||||
progress.setCancelable(false);
|
||||
progress.show();
|
||||
ArrayList<AccountSession> sessions=new ArrayList<>(AccountSessionManager.getInstance().getLoggedInAccounts());
|
||||
for(AccountSession session:sessions){
|
||||
new RevokeOauthToken(session.app.clientId, session.app.clientSecret, session.token.accessToken)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Object result){
|
||||
AccountSessionManager.getInstance().removeAccount(session.getID());
|
||||
sessions.remove(session);
|
||||
if(sessions.isEmpty()){
|
||||
progress.dismiss();
|
||||
Nav.goClearingStack(activity, SplashFragment.class, null);
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
AccountSessionManager.getInstance().removeAccount(session.getID());
|
||||
sessions.remove(session);
|
||||
if(sessions.isEmpty()){
|
||||
progress.dismiss();
|
||||
Nav.goClearingStack(activity, SplashFragment.class, null);
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
})
|
||||
.exec(session.getID());
|
||||
}
|
||||
}
|
||||
|
||||
private void onLoggedOut(String accountID){
|
||||
AccountSessionManager.getInstance().removeAccount(accountID);
|
||||
dismiss();
|
||||
String activeAccountID = fragment != null
|
||||
? fragment.getAccountID()
|
||||
: AccountSessionManager.getInstance().getLastActiveAccountID();
|
||||
if (accountID.equals(activeAccountID)) {
|
||||
activity.finish();
|
||||
activity.startActivity(new Intent(activity, MainActivity.class));
|
||||
} else {
|
||||
accounts.stream().filter(w -> accountID.equals(w.session.getID())).findAny().ifPresent(w -> {
|
||||
accountsAdapter.notifyItemRemoved(accounts.indexOf(w));
|
||||
accounts.remove(w);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -140,6 +228,13 @@ public class AccountSwitcherSheet extends BottomSheet{
|
||||
}
|
||||
}
|
||||
|
||||
private View makeSimpleListItem(@StringRes int title, @DrawableRes int icon){
|
||||
TextView tv=(TextView) activity.getLayoutInflater().inflate(R.layout.item_text_with_icon, list, false);
|
||||
tv.setText(title);
|
||||
tv.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, 0, 0, 0);
|
||||
return tv;
|
||||
}
|
||||
|
||||
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
public AccountsAdapter(){
|
||||
super(imgLoader);
|
||||
@@ -173,45 +268,42 @@ public class AccountSwitcherSheet extends BottomSheet{
|
||||
}
|
||||
}
|
||||
|
||||
private class AccountViewHolder extends BindableViewHolder<AccountSession> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
|
||||
private final TextView name;
|
||||
private class AccountViewHolder extends BindableViewHolder<AccountSession> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable, UsableRecyclerView.LongClickable{
|
||||
private final TextView name, username;
|
||||
private final ImageView avatar;
|
||||
private final ImageButton more;
|
||||
private final View currentIcon;
|
||||
private final PopupMenu menu;
|
||||
private final CheckableRelativeLayout view;
|
||||
private final View radioButton, extraBtnWrap;
|
||||
private final ImageButton extraBtn;
|
||||
|
||||
public AccountViewHolder(){
|
||||
super(activity, R.layout.item_account_switcher, list);
|
||||
name=findViewById(R.id.name);
|
||||
username=findViewById(R.id.username);
|
||||
radioButton=findViewById(R.id.radiobtn);
|
||||
radioButton.setBackground(new RadioButton(activity).getButtonDrawable());
|
||||
avatar=findViewById(R.id.avatar);
|
||||
more=findViewById(R.id.more);
|
||||
currentIcon=findViewById(R.id.current);
|
||||
|
||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(OutlineProviders.RADIUS_MEDIUM));
|
||||
avatar.setClipToOutline(true);
|
||||
|
||||
menu=new PopupMenu(activity, more);
|
||||
menu.inflate(R.menu.account_switcher);
|
||||
menu.setOnMenuItemClickListener(item1 -> {
|
||||
confirmLogOut(item.getID());
|
||||
return true;
|
||||
});
|
||||
more.setOnClickListener(v->menu.show());
|
||||
view=(CheckableRelativeLayout) itemView;
|
||||
extraBtnWrap = findViewById(R.id.extra_btn_wrap);
|
||||
extraBtn = findViewById(R.id.extra_btn);
|
||||
extraBtn.setOnClickListener(this::onExtraBtnClick);
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Override
|
||||
public void onBind(AccountSession item){
|
||||
name.setText("@"+item.self.username+"@"+item.domain);
|
||||
if(AccountSessionManager.getInstance().getLastActiveAccountID().equals(item.getID())){
|
||||
more.setVisibility(View.GONE);
|
||||
currentIcon.setVisibility(View.VISIBLE);
|
||||
}else{
|
||||
more.setVisibility(View.VISIBLE);
|
||||
currentIcon.setVisibility(View.GONE);
|
||||
name.setText(item.self.displayName);
|
||||
username.setText(item.getFullUsername());
|
||||
radioButton.setVisibility(externalShare ? View.GONE : View.VISIBLE);
|
||||
extraBtnWrap.setVisibility(externalShare && openInApp ? View.VISIBLE : View.GONE);
|
||||
if (externalShare) view.setCheckable(false);
|
||||
else {
|
||||
String accountId = fragment != null
|
||||
? fragment.getAccountID()
|
||||
: AccountSessionManager.getInstance().getLastActiveAccountID();
|
||||
view.setChecked(accountId.equals(item.getID()));
|
||||
}
|
||||
menu.getMenu().findItem(R.id.log_out).setTitle(activity.getString(R.string.log_out_account, "@"+item.self.username));
|
||||
UiUtils.enablePopupMenuIcons(activity, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -226,12 +318,32 @@ public class AccountSwitcherSheet extends BottomSheet{
|
||||
setImage(index, null);
|
||||
}
|
||||
|
||||
private void onExtraBtnClick(View view) {
|
||||
setOnDismissListener(null);
|
||||
dismiss();
|
||||
onClick.accept(item.getID(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
setOnDismissListener(null);
|
||||
if (onClick != null) {
|
||||
dismiss();
|
||||
onClick.accept(item.getID(), false);
|
||||
return;
|
||||
}
|
||||
|
||||
AccountSessionManager.getInstance().setLastActiveAccountID(item.getID());
|
||||
activity.finish();
|
||||
activity.startActivity(new Intent(activity, MainActivity.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(){
|
||||
if (externalShare) return false;
|
||||
confirmLogOut(item.getID());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class WrappedAccount{
|
||||
|
||||
@@ -8,7 +8,15 @@ import android.view.ViewOutlineProvider;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class OutlineProviders{
|
||||
private static SparseArray<ViewOutlineProvider> roundedRects=new SparseArray<>();
|
||||
private static final SparseArray<ViewOutlineProvider> roundedRects=new SparseArray<>();
|
||||
private static final SparseArray<ViewOutlineProvider> topRoundedRects=new SparseArray<>();
|
||||
private static final SparseArray<ViewOutlineProvider> endRoundedRects=new SparseArray<>();
|
||||
|
||||
public static final int RADIUS_XSMALL=4;
|
||||
public static final int RADIUS_SMALL=8;
|
||||
public static final int RADIUS_MEDIUM=12;
|
||||
public static final int RADIUS_LARGE=16;
|
||||
public static final int RADIUS_XLARGE=28;
|
||||
|
||||
private OutlineProviders(){
|
||||
//no instance
|
||||
@@ -21,6 +29,12 @@ public class OutlineProviders{
|
||||
outline.setAlpha(view.getAlpha());
|
||||
}
|
||||
};
|
||||
public static final ViewOutlineProvider OVAL=new ViewOutlineProvider(){
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline){
|
||||
outline.setOval(0, 0, view.getWidth(), view.getHeight());
|
||||
}
|
||||
};
|
||||
|
||||
public static ViewOutlineProvider roundedRect(int dp){
|
||||
ViewOutlineProvider provider=roundedRects.get(dp);
|
||||
@@ -31,6 +45,24 @@ public class OutlineProviders{
|
||||
return provider;
|
||||
}
|
||||
|
||||
public static ViewOutlineProvider topRoundedRect(int dp){
|
||||
ViewOutlineProvider provider=topRoundedRects.get(dp);
|
||||
if(provider!=null)
|
||||
return provider;
|
||||
provider=new TopRoundRectOutlineProvider(V.dp(dp));
|
||||
topRoundedRects.put(dp, provider);
|
||||
return provider;
|
||||
}
|
||||
|
||||
public static ViewOutlineProvider endRoundedRect(int dp){
|
||||
ViewOutlineProvider provider=endRoundedRects.get(dp);
|
||||
if(provider!=null)
|
||||
return provider;
|
||||
provider=new EndRoundRectOutlineProvider(V.dp(dp));
|
||||
endRoundedRects.put(dp, provider);
|
||||
return provider;
|
||||
}
|
||||
|
||||
private static class RoundRectOutlineProvider extends ViewOutlineProvider{
|
||||
private final int radius;
|
||||
|
||||
@@ -43,4 +75,34 @@ public class OutlineProviders{
|
||||
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), radius);
|
||||
}
|
||||
}
|
||||
|
||||
private static class TopRoundRectOutlineProvider extends ViewOutlineProvider{
|
||||
private final int radius;
|
||||
|
||||
private TopRoundRectOutlineProvider(int radius){
|
||||
this.radius=radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline){
|
||||
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight()+radius, radius);
|
||||
}
|
||||
}
|
||||
|
||||
private static class EndRoundRectOutlineProvider extends ViewOutlineProvider{
|
||||
private final int radius;
|
||||
|
||||
private EndRoundRectOutlineProvider(int radius){
|
||||
this.radius=radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline){
|
||||
if(view.getLayoutDirection()==View.LAYOUT_DIRECTION_RTL){
|
||||
outline.setRoundRect(-radius, 0, view.getWidth(), view.getHeight(), radius);
|
||||
}else{
|
||||
outline.setRoundRect(0, 0, view.getWidth()+radius, view.getHeight(), radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +131,7 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", item.parentFragment.getAccountID());
|
||||
args.putString("id", item.status.id);
|
||||
args.putString("url", item.status.url);
|
||||
Nav.go(item.parentFragment.getActivity(), StatusEditHistoryFragment.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +56,8 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{
|
||||
private final TextView reply, boost, favorite, bookmark;
|
||||
private final ImageView share;
|
||||
private final TextView replies, boosts, favorites;
|
||||
private final View reply, boost, favorite, share, bookmark;
|
||||
private static final Animation opacityOut, opacityIn;
|
||||
|
||||
private View touchingView = null;
|
||||
@@ -91,22 +91,16 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
|
||||
public Holder(Activity activity, ViewGroup parent){
|
||||
super(activity, R.layout.display_item_footer, parent);
|
||||
reply=findViewById(R.id.reply);
|
||||
boost=findViewById(R.id.boost);
|
||||
favorite=findViewById(R.id.favorite);
|
||||
bookmark=findViewById(R.id.bookmark);
|
||||
share=findViewById(R.id.share);
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
|
||||
UiUtils.fixCompoundDrawableTintOnAndroid6(reply);
|
||||
UiUtils.fixCompoundDrawableTintOnAndroid6(boost);
|
||||
UiUtils.fixCompoundDrawableTintOnAndroid6(favorite);
|
||||
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);
|
||||
replies=findViewById(R.id.reply);
|
||||
boosts=findViewById(R.id.boost);
|
||||
favorites=findViewById(R.id.favorite);
|
||||
|
||||
reply=findViewById(R.id.reply_btn);
|
||||
boost=findViewById(R.id.boost_btn);
|
||||
favorite=findViewById(R.id.favorite_btn);
|
||||
share=findViewById(R.id.share_btn);
|
||||
bookmark=findViewById(R.id.bookmark_btn);
|
||||
|
||||
reply.setOnTouchListener(this::onButtonTouch);
|
||||
reply.setOnClickListener(this::onReplyClick);
|
||||
reply.setOnLongClickListener(this::onReplyLongClick);
|
||||
@@ -131,15 +125,30 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
|
||||
@Override
|
||||
public void onBind(FooterStatusDisplayItem item){
|
||||
bindButton(reply, item.status.repliesCount);
|
||||
bindButton(boost, item.status.reblogsCount);
|
||||
bindButton(favorite, item.status.favouritesCount);
|
||||
reply.setSelected(item.status.repliesCount > 0);
|
||||
bindButton(replies, item.status.repliesCount);
|
||||
bindButton(boosts, item.status.reblogsCount);
|
||||
bindButton(favorites, item.status.favouritesCount);
|
||||
// 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);
|
||||
favorite.setSelected(item.status.favourited);
|
||||
bookmark.setSelected(item.status.bookmarked);
|
||||
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)));
|
||||
|
||||
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(-8) : 0);
|
||||
|
||||
itemView.requestLayout();
|
||||
}
|
||||
|
||||
private void bindButton(TextView btn, long count){
|
||||
@@ -166,8 +175,9 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
} else if (action == MotionEvent.ACTION_DOWN) {
|
||||
longClickPerformed = false;
|
||||
touchingView = v;
|
||||
// 20dp to center in middle of icon, because: (icon width = 24dp) / 2 + (paddingStart = 8dp)
|
||||
v.setPivotX(V.dp(20));
|
||||
// 28dp to center in middle of icon, because:
|
||||
// (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();
|
||||
if (disabled) return true;
|
||||
v.postDelayed(longClickRunnable, ViewConfiguration.getLongPressTimeout());
|
||||
@@ -205,13 +215,13 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
onBoostLongClick(v);
|
||||
return;
|
||||
}
|
||||
boost.setSelected(!item.status.reblogged);
|
||||
boosts.setSelected(!item.status.reblogged);
|
||||
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setReblogged(item.status, !item.status.reblogged, null, r->boostConsumer(v, r));
|
||||
}
|
||||
|
||||
private void boostConsumer(View v, Status r) {
|
||||
v.startAnimation(opacityIn);
|
||||
bindButton(boost, r.reblogsCount);
|
||||
bindButton(boosts, r.reblogsCount);
|
||||
}
|
||||
|
||||
private boolean onBoostLongClick(View v){
|
||||
@@ -297,10 +307,10 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private void onFavoriteClick(View v){
|
||||
favorite.setSelected(!item.status.favourited);
|
||||
favorites.setSelected(!item.status.favourited);
|
||||
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setFavorited(item.status, !item.status.favourited, r->{
|
||||
v.startAnimation(opacityIn);
|
||||
bindButton(favorite, r.favouritesCount);
|
||||
bindButton(favorites, r.favouritesCount);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
@@ -24,8 +22,6 @@ import android.widget.PopupMenu;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
@@ -36,7 +32,7 @@ import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.fragments.ListTimelinesFragment;
|
||||
import org.joinmastodon.android.fragments.ListsFragment;
|
||||
import org.joinmastodon.android.fragments.NotificationsListFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||
@@ -44,12 +40,10 @@ import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Announcement;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.ContentType;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.model.ScheduledStatus;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
@@ -59,7 +53,6 @@ import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -284,7 +277,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
args.putString("account", item.parentFragment.getAccountID());
|
||||
args.putString("profileAccount", account.id);
|
||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||
Nav.go(item.parentFragment.getActivity(), ListTimelinesFragment.class, args);
|
||||
Nav.go(item.parentFragment.getActivity(), ListsFragment.class, args);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -73,9 +73,9 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
||||
public void updateVisibility(StatusPrivacy visibility) {
|
||||
this.visibility = visibility;
|
||||
this.iconEnd = visibility != null ? switch (visibility) {
|
||||
case PUBLIC -> R.drawable.ic_fluent_earth_20_regular;
|
||||
case UNLISTED -> R.drawable.ic_fluent_lock_open_20_regular;
|
||||
case PRIVATE -> R.drawable.ic_fluent_lock_closed_20_filled;
|
||||
case PUBLIC -> R.drawable.ic_fluent_earth_20sp_regular;
|
||||
case UNLISTED -> R.drawable.ic_fluent_lock_open_20sp_regular;
|
||||
case PRIVATE -> R.drawable.ic_fluent_lock_closed_20sp_filled;
|
||||
default -> 0;
|
||||
} : 0;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import android.view.ViewGroup;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
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.DisplayItemsParent;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.Poll;
|
||||
import org.joinmastodon.android.model.ScheduledStatus;
|
||||
@@ -49,6 +47,23 @@ public abstract class StatusDisplayItem{
|
||||
public final BaseStatusListFragment parentFragment;
|
||||
public boolean inset;
|
||||
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){
|
||||
this.parentID=parentID;
|
||||
@@ -114,7 +129,7 @@ public abstract class StatusDisplayItem{
|
||||
: fragment.getString(R.string.in_reply_to, account.displayName);
|
||||
replyLine = new ReblogOrReplyLineStatusDisplayItem(
|
||||
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);
|
||||
String fullText = fragment.getString(R.string.user_boosted, status.account.displayName);
|
||||
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));
|
||||
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
|
||||
}, fullText));
|
||||
@@ -137,7 +152,7 @@ public abstract class StatusDisplayItem{
|
||||
// post contains a hashtag the user is following
|
||||
.ifPresent(hashtag -> items.add(new ReblogOrReplyLineStatusDisplayItem(
|
||||
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 -> {
|
||||
args.putString("hashtag", hashtag.name);
|
||||
Nav.go(fragment.getActivity(), HashtagTimelineFragment.class, args);
|
||||
|
||||
@@ -65,7 +65,6 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
spoilerEmojiHelper.setText(parsedSpoilerText);
|
||||
}
|
||||
session = AccountSessionManager.getInstance().getAccount(parentFragment.getAccountID());
|
||||
UiUtils.loadMaxWidth(parentFragment.getContext());
|
||||
}
|
||||
|
||||
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);
|
||||
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(8)
|
||||
: V.dp(12);
|
||||
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), bottomPadding);
|
||||
|
||||
if (!GlobalUserPreferences.collapseLongPosts) {
|
||||
textScrollView.setLayoutParams(wrapParams);
|
||||
readMore.setVisibility(View.GONE);
|
||||
|
||||
@@ -7,6 +7,7 @@ import static org.joinmastodon.android.GlobalUserPreferences.trueBlackTheme;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Fragment;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ClipData;
|
||||
@@ -16,6 +17,7 @@ import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
@@ -36,6 +38,7 @@ import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -97,9 +100,9 @@ import org.parceler.Parcels;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.IDN;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
@@ -110,6 +113,7 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
@@ -138,15 +142,11 @@ public class UiUtils {
|
||||
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");
|
||||
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() {
|
||||
}
|
||||
|
||||
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) {
|
||||
try {
|
||||
if (GlobalUserPreferences.useCustomTabs) {
|
||||
@@ -895,6 +895,10 @@ public class UiUtils {
|
||||
|
||||
ColorPalette palette = ColorPalette.palettes.get(GlobalUserPreferences.color);
|
||||
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() {
|
||||
@@ -903,6 +907,32 @@ public class UiUtils {
|
||||
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/43456787654678
|
||||
// https://pleroma.foo.bar/users/User
|
||||
@@ -919,7 +949,7 @@ public class UiUtils {
|
||||
// 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
|
||||
public static boolean looksLikeMastodonUrl(String urlString) {
|
||||
public static boolean looksLikeFediverseUrl(String urlString) {
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(urlString);
|
||||
@@ -948,8 +978,8 @@ public class UiUtils {
|
||||
|
||||
public static String getInstanceName(String accountID) {
|
||||
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
|
||||
Instance instance = session.getInstance();
|
||||
return instance != null && !instance.title.isBlank() ? instance.title : session.domain;
|
||||
Optional<Instance> instance = session.getInstance();
|
||||
return instance.isPresent() && !instance.get().title.isBlank() ? instance.get().title : session.domain;
|
||||
}
|
||||
|
||||
public static void pickAccount(Context context, String exceptFor, @StringRes int titleRes, @DrawableRes int iconRes, Consumer<AccountSession> sessionConsumer, Consumer<AlertDialog.Builder> transformDialog) {
|
||||
@@ -1080,6 +1110,60 @@ public class UiUtils {
|
||||
}
|
||||
|
||||
public static void openURL(Context context, String accountID, String url, boolean launchBrowser) {
|
||||
lookupURL(context, accountID, url, launchBrowser, (clazz, args) -> {
|
||||
if (clazz == null) return;
|
||||
Nav.go((Activity) context, clazz, args);
|
||||
});
|
||||
}
|
||||
|
||||
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, 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();
|
||||
go.accept(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
public static void lookupURL(Context context, String accountID, String url, boolean launchBrowser, BiConsumer<Class<? extends Fragment>, Bundle> go) {
|
||||
Uri uri = Uri.parse(url);
|
||||
List<String> path = uri.getPathSegments();
|
||||
if (accountID != null && "https".equals(uri.getScheme())) {
|
||||
@@ -1091,20 +1175,21 @@ public class UiUtils {
|
||||
Bundle args = new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("status", Parcels.wrap(result));
|
||||
Nav.go((Activity) context, ThreadFragment.class, args);
|
||||
go.accept(ThreadFragment.class, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(context);
|
||||
if (launchBrowser) launchWebBrowser(context, url);
|
||||
go.accept(null, null);
|
||||
}
|
||||
})
|
||||
.wrapProgress((Activity) context, R.string.loading, true,
|
||||
d -> transformDialogForLookup(context, accountID, url, d))
|
||||
.exec(accountID);
|
||||
return;
|
||||
} else if (looksLikeMastodonUrl(url)) {
|
||||
} else if (looksLikeFediverseUrl(url)) {
|
||||
new GetSearchResults(url, null, true)
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
@@ -1113,27 +1198,26 @@ public class UiUtils {
|
||||
args.putString("account", accountID);
|
||||
if (!results.statuses.isEmpty()) {
|
||||
args.putParcelable("status", Parcels.wrap(results.statuses.get(0)));
|
||||
Nav.go((Activity) context, ThreadFragment.class, args);
|
||||
go.accept(ThreadFragment.class, args);
|
||||
return;
|
||||
}
|
||||
Optional<Account> account = results.accounts.stream()
|
||||
.filter(a -> uri.equals(Uri.parse(a.url))).findAny();
|
||||
if (account.isPresent()) {
|
||||
args.putParcelable("profileAccount", Parcels.wrap(account.get()));
|
||||
Nav.go((Activity) context, ProfileFragment.class, args);
|
||||
return;
|
||||
}
|
||||
if (launchBrowser) {
|
||||
launchWebBrowser(context, url);
|
||||
go.accept(ProfileFragment.class, args);
|
||||
return;
|
||||
}
|
||||
if (launchBrowser) launchWebBrowser(context, url);
|
||||
Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show();
|
||||
go.accept(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(context);
|
||||
if (launchBrowser) launchWebBrowser(context, url);
|
||||
go.accept(null, null);
|
||||
}
|
||||
})
|
||||
.wrapProgress((Activity) context, R.string.loading, true,
|
||||
@@ -1142,7 +1226,8 @@ public class UiUtils {
|
||||
return;
|
||||
}
|
||||
}
|
||||
launchWebBrowser(context, url);
|
||||
if (launchBrowser) launchWebBrowser(context, url);
|
||||
go.accept(null, null);
|
||||
}
|
||||
|
||||
public static void copyText(View v, String text) {
|
||||
@@ -1305,6 +1390,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));
|
||||
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.
|
||||
* Useful for buttons that perform some action that changes their state asynchronously.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.widget.Checkable;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
public class CheckableRelativeLayout extends RelativeLayout implements Checkable{
|
||||
private boolean checked, checkable = true;
|
||||
private static final int[] CHECKED_STATE_SET = {
|
||||
android.R.attr.state_checked
|
||||
};
|
||||
|
||||
public CheckableRelativeLayout(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CheckableRelativeLayout(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CheckableRelativeLayout(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChecked(boolean checked){
|
||||
this.checked=checked;
|
||||
refreshDrawableState();
|
||||
}
|
||||
|
||||
public void setCheckable(boolean checkable) {
|
||||
this.checkable = checkable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked(){
|
||||
return checked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toggle(){
|
||||
setChecked(!checked);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int[] onCreateDrawableState(int extraSpace) {
|
||||
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
|
||||
if (isChecked()) {
|
||||
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
|
||||
}
|
||||
return drawableState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info){
|
||||
super.onInitializeAccessibilityNodeInfo(info);
|
||||
info.setCheckable(checkable);
|
||||
info.setChecked(checked);
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ public class ComposeMediaLayout extends ViewGroup{
|
||||
|
||||
public ComposeMediaLayout(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
UiUtils.loadMaxWidth(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,12 +3,13 @@ package org.joinmastodon.android.ui.views;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
|
||||
public class MaxWidthFrameLayout extends FrameLayout{
|
||||
private int maxWidth;
|
||||
private int maxWidth, defaultWidth;
|
||||
|
||||
public MaxWidthFrameLayout(Context context){
|
||||
this(context, null);
|
||||
@@ -22,6 +23,7 @@ public class MaxWidthFrameLayout extends FrameLayout{
|
||||
super(context, attrs, defStyle);
|
||||
TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.MaxWidthFrameLayout);
|
||||
maxWidth=ta.getDimensionPixelSize(R.styleable.MaxWidthFrameLayout_android_maxWidth, Integer.MAX_VALUE);
|
||||
defaultWidth=ta.getDimensionPixelSize(R.styleable.MaxWidthFrameLayout_defaultWidth, -1);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
@@ -33,10 +35,19 @@ public class MaxWidthFrameLayout extends FrameLayout{
|
||||
this.maxWidth=maxWidth;
|
||||
}
|
||||
|
||||
public int getDefaultWidth() {
|
||||
return defaultWidth;
|
||||
}
|
||||
|
||||
public void setDefaultWidth(int defaultWidth) {
|
||||
this.defaultWidth = defaultWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ public class MediaGridLayout extends ViewGroup{
|
||||
|
||||
public MediaGridLayout(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
UiUtils.loadMaxWidth(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.joinmastodon.android.utils;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.joinmastodon.android.fragments.HasAccountID;
|
||||
|
||||
public interface ProvidesAssistContent {
|
||||
void onProvideAssistContent(AssistContent assistContent);
|
||||
|
||||
default boolean callFragmentToProvideAssistContent(Fragment fragment, AssistContent assistContent) {
|
||||
if (fragment instanceof ProvidesAssistContent assistiveFragment) {
|
||||
assistiveFragment.onProvideAssistContent(assistContent);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
interface ProvidesWebUri extends ProvidesAssistContent, HasAccountID {
|
||||
Uri getWebUri(Uri.Builder base);
|
||||
|
||||
default Uri.Builder getUriBuilder() {
|
||||
return getSession().getInstanceUri().buildUpon();
|
||||
}
|
||||
|
||||
default void onProvideAssistContent(AssistContent assistContent) {
|
||||
assistContent.setWebUri(getWebUri(getUriBuilder()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?android:colorAccent" android:state_selected="true"/>
|
||||
<item android:color="?android:textColorSecondary" android:state_enabled="true"/>
|
||||
<item android:color="?android:textColorSecondary" android:alpha="0.3"/>
|
||||
<item android:color="?colorIconDisabled" />
|
||||
</selector>
|
||||
@@ -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,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>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="28dp" android:height="28dp" android:viewportWidth="28" android:viewportHeight="28">
|
||||
<path android:pathData="M18.27 3.21l7.5 7.25c0.073 0.07 0.13 0.154 0.17 0.247C25.98 10.8 26 10.9 26 11c0 0.101-0.02 0.201-0.06 0.294-0.04 0.093-0.097 0.176-0.17 0.246l-7.5 7.25c-0.069 0.068-0.15 0.121-0.239 0.157-0.09 0.037-0.185 0.055-0.281 0.053-0.1 0-0.198-0.02-0.29-0.06-0.136-0.056-0.252-0.152-0.334-0.275C17.044 18.542 17 18.398 17 18.25v-3.74c-6.7 0.27-9.52 4.02-9.64 4.18-0.096 0.126-0.227 0.22-0.378 0.268-0.15 0.048-0.311 0.049-0.462 0.003-0.15-0.049-0.282-0.144-0.375-0.271C6.052 18.562 6 18.408 6 18.25c0-8.02 6.59-10.48 11-10.73V3.75c0-0.147 0.044-0.291 0.126-0.414s0.198-0.218 0.334-0.275c0.135-0.059 0.284-0.075 0.428-0.049 0.144 0.027 0.277 0.096 0.382 0.199zm0.23 10.54v2.71L24.17 11 18.5 5.52v2.73c-0.003 0.199-0.082 0.388-0.223 0.528-0.14 0.14-0.329 0.22-0.527 0.223-0.97 0-8.85 0.22-10.09 7.28 2.876-2.24 6.447-3.401 10.09-3.28 0.198 0.002 0.387 0.082 0.527 0.222s0.22 0.33 0.223 0.527zm4.223 5.473c0.14-0.14 0.329-0.22 0.527-0.223 0.198 0.003 0.387 0.083 0.527 0.223s0.22 0.33 0.223 0.527v0.5c0 1.26-0.5 2.468-1.391 3.36-0.891 0.89-2.1 1.39-3.359 1.39H7.75c-1.26 0-2.468-0.5-3.359-1.39C3.501 22.717 3 21.51 3 20.25V8.75c0-1.26 0.5-2.468 1.391-3.358C5.282 4.5 6.491 4 7.75 4h4.5c0.199 0 0.39 0.08 0.53 0.22C12.921 4.36 13 4.552 13 4.75c0 0.2-0.079 0.39-0.22 0.53-0.14 0.141-0.331 0.22-0.53 0.22h-4.5C6.889 5.503 6.064 5.846 5.455 6.455 4.845 7.065 4.503 7.89 4.5 8.751v11.5c0.003 0.86 0.346 1.686 0.955 2.295S6.889 23.498 7.75 23.5h11.5c0.861-0.002 1.686-0.345 2.295-0.954 0.61-0.61 0.952-1.434 0.955-2.296v-0.5c0.003-0.198 0.082-0.387 0.223-0.527z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -1,120 +1,161 @@
|
||||
<?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"
|
||||
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_height="48dp"
|
||||
android:paddingHorizontal="16dp">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/reply_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:minWidth="56dp">
|
||||
<TextView
|
||||
android:id="@+id/reply"
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="11sp">
|
||||
<!-- avatar width (46sp) / 2 - button width (24sp) / 2 -->
|
||||
|
||||
<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_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:drawableStart="@drawable/ic_fluent_chat_multiple_24_selector_text"
|
||||
android:drawablePadding="8dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:drawableTint="?android:textColorSecondary"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
tools:text="123"/>
|
||||
</FrameLayout>
|
||||
android:paddingVertical="12dp">
|
||||
<ImageView
|
||||
android:id="@+id/share"
|
||||
android:layout_width="24sp"
|
||||
android:layout_height="24sp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:src="@drawable/ic_fluent_share_24_regular"
|
||||
android:tint="?android:textColorSecondary"
|
||||
android:gravity="center_vertical"/>
|
||||
</FrameLayout>
|
||||
|
||||
<Space
|
||||
android:layout_width="0px"
|
||||
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>
|
||||
</LinearLayout>
|
||||
</org.joinmastodon.android.ui.views.MaxWidthFrameLayout>
|
||||
@@ -14,7 +14,7 @@
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="6dp"
|
||||
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:drawablePadding="6dp"
|
||||
android:singleLine="true"
|
||||
|
||||
@@ -1,44 +1,82 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<org.joinmastodon.android.ui.views.CheckableRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingRight="20dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerInParent="true"
|
||||
android:importantForAccessibility="no"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="24dp"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/current"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:background="@drawable/ic_fluent_checkmark_24_filled"
|
||||
android:backgroundTint="?android:textColorSecondary"
|
||||
android:contentDescription="@string/current_account"/>
|
||||
android:id="@+id/radiobtn"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_toStartOf="@+id/extra_btn_wrap"
|
||||
android:layout_alignWithParentIfMissing="true"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:duplicateParentState="true" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/more"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_fluent_more_vertical_24_regular"
|
||||
android:tint="?android:textColorSecondary"
|
||||
android:contentDescription="@string/more_options"
|
||||
android:background="?android:selectableItemBackgroundBorderless"/>
|
||||
<FrameLayout
|
||||
android:id="@id/extra_btn_wrap"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="12dp"
|
||||
android:visibility="gone">
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_gravity="center_vertical|start"
|
||||
android:background="?colorPollVoted" />
|
||||
<ImageButton
|
||||
android:id="@+id/extra_btn"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/sk_open_in_app"
|
||||
android:tooltipText="@string/sk_open_in_app"
|
||||
android:src="@drawable/ic_fluent_open_24_regular"
|
||||
android:background="?android:selectableItemBackground" />
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_toStartOf="@id/radiobtn"
|
||||
android:layout_centerInParent="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:singleLine="true"
|
||||
android:gravity="center_vertical"
|
||||
android:ellipsize="end"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</org.joinmastodon.android.ui.views.CheckableRelativeLayout>
|
||||
30
mastodon/src/main/res/layout/item_external_share_heading.xml
Normal file
30
mastodon/src/main/res/layout/item_external_share_heading.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:src="@drawable/ic_fluent_share_28_regular"
|
||||
android:scaleType="centerInside"
|
||||
android:adjustViewBounds="true"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:importantForAccessibility="no"
|
||||
tools:ignore="RtlSymmetry" />
|
||||
|
||||
<TextView
|
||||
style="@style/sheet_title"
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/sk_external_share_or_open_title"
|
||||
android:textColor="?colorM3OnSurface" />
|
||||
|
||||
</LinearLayout>
|
||||
15
mastodon/src/main/res/layout/item_text_with_icon.xml
Normal file
15
mastodon/src/main/res/layout/item_text_with_icon.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingRight="24dp"
|
||||
android:drawablePadding="24dp"
|
||||
android:drawableTint="?colorM3OnSurfaceVariant"
|
||||
tools:text="List Item"/>
|
||||
@@ -20,11 +20,20 @@
|
||||
<string name="share_toot_title">শেয়ার করুন</string>
|
||||
<string name="settings">সেটিংস</string>
|
||||
<string name="cancel">বাতিল করুন</string>
|
||||
<plurals name="followers">
|
||||
<item quantity="one">জন ফলোয়ার</item>
|
||||
<item quantity="other">জন ফলোয়ারস</item>
|
||||
</plurals>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">পোস্ট</item>
|
||||
<item quantity="other">পোস্টগুলো</item>
|
||||
</plurals>
|
||||
<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="mute_user">%s -কে মিউট করুন</string>
|
||||
<string name="unmute_user">%s -কে আনমিউট করুন</string>
|
||||
@@ -53,6 +62,22 @@
|
||||
<item quantity="one">%d দিন</item>
|
||||
<item quantity="other">%d দিন</item>
|
||||
</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="confirm_mute_title">অ্যাকাউন্টটি মিউট করুন</string>
|
||||
<string name="do_mute">মিউট করুন</string>
|
||||
@@ -88,6 +113,18 @@
|
||||
<item quantity="one">%d জন ব্যক্তি বলছেন</item>
|
||||
<item quantity="other">%d jon ব্যক্তিরা বলছেন</item>
|
||||
</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 -->
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
@@ -96,4 +133,8 @@
|
||||
<!-- %s is server domain -->
|
||||
<!-- 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. -->
|
||||
<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>
|
||||
|
||||
@@ -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="unfollow_user">%s 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="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>
|
||||
@@ -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="search_communities">Servername oder -adresse</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="edit_photo">bearbeiten</string>
|
||||
<string name="display_name">Name</string>
|
||||
@@ -200,7 +200,7 @@
|
||||
<string name="confirm_email_title">Überprüfe deinen Posteingang</string>
|
||||
<!-- %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_didnt_get">Kein Link erhalten?</string>
|
||||
<string name="confirm_email_didnt_get">Keinen Link erhalten?</string>
|
||||
<string name="resend">Erneut abschicken</string>
|
||||
<string name="open_email_app">E-Mail-App öffnen</string>
|
||||
<string name="resent_email">Bestätigung per E-Mail zugeschickt</string>
|
||||
@@ -438,8 +438,11 @@
|
||||
<string name="show">Anzeigen</string>
|
||||
<string name="hide">Ausblenden</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="learn_more">Mehr erfahren</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="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>
|
||||
|
||||
@@ -274,4 +274,22 @@
|
||||
<string name="sk_settings_confirm_before_reblog">Vor dem Teilen bestätigen</string>
|
||||
<string name="sk_reacted">hat 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>
|
||||
</resources>
|
||||
@@ -286,4 +286,10 @@
|
||||
<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_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>
|
||||
@@ -287,4 +287,10 @@
|
||||
<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_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 publications les plus récentes des personnes présentes dans la bulle de votre serveur Akkoma.</string>
|
||||
<string name="sk_timeline_bubble">Bulle</string>
|
||||
<string name="sk_instance_info_unavailable">Informations sur l\'instance temporairement indisponibles</string>
|
||||
</resources>
|
||||
@@ -90,7 +90,7 @@
|
||||
<string name="sk_loading_fediverse_resource_title">Buscando no Fediverso</string>
|
||||
<string name="sk_reblog_with_visibility">Impulsar con visibilidade</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_loading_resource_on_instance_title">Buscando en %s</string>
|
||||
<string name="sk_open_with_account">Abrir con outra conta</string>
|
||||
@@ -275,4 +275,21 @@
|
||||
<string name="sk_settings_confirm_before_reblog">Confirma antes de impulsar</string>
|
||||
<string name="sk_reacted_with">Redactado con %s</string>
|
||||
<string name="sk_reacted">redactado</string>
|
||||
<string name="sk_content_type_unspecified">Non especificado</string>
|
||||
<string name="sk_content_type_plain">Texto plano</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_settings_content_types">Activar o formato de publicacións</string>
|
||||
<string name="sk_settings_default_content_type">Tipo de contido por defecto</string>
|
||||
<string name="sk_settings_default_content_type_explanation">Isto permítelle ter un tipo de contido preseleccionado á hora de crear novas publicacións, sobrescribindo o valor establecido en \"Publicar preferencias\".</string>
|
||||
<string name="sk_content_type">Tipo de contido</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_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>
|
||||
@@ -287,4 +287,10 @@
|
||||
<string name="sk_content_type">Jenis konten</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_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>
|
||||
@@ -251,4 +251,7 @@
|
||||
<string name="sk_settings_hide_interaction">Verberg interactie knoppen</string>
|
||||
<string name="sk_follow_as">Volgen met ander account</string>
|
||||
<string name="sk_followed_as">Gevolgd met %s</string>
|
||||
<string name="sk_quoting_user">Quoting %s</string>
|
||||
<string name="sk_settings_reply_visibility">Zichtbaarheid reactie</string>
|
||||
<string name="sk_settings_reply_visibility_all">Alle reacties</string>
|
||||
</resources>
|
||||
@@ -274,4 +274,15 @@
|
||||
<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">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>
|
||||
@@ -286,4 +286,10 @@
|
||||
<string name="sk_settings_default_content_type_explanation">Це дозволяє вам попередньо вибрати тип вмісту під час написання нових дописів, замінивши значення, встановлене в «Налаштуваннях постингу».</string>
|
||||
<string name="sk_content_type_markdown">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">Це найновіші дописи людей у бульбашці вашого сервера Akkoma.</string>
|
||||
<string name="sk_timeline_bubble">Бульбашка</string>
|
||||
<string name="sk_instance_info_unavailable">Сервер тимчасово недоступний</string>
|
||||
<string name="sk_external_share_or_open_title">Поділитися або відкрити за допомогою облікового запису</string>
|
||||
</resources>
|
||||
@@ -4,32 +4,57 @@
|
||||
|
||||
<color name="m3_navigation_bar_bg">@android:color/system_neutral1_50</color>
|
||||
|
||||
<color name="m3_gray_900">@android:color/system_neutral1_900</color>
|
||||
<color name="m3_gray_800t">@android:color/system_neutral1_800</color>
|
||||
<color name="m3_gray_800">@android:color/system_neutral1_800</color>
|
||||
<color name="m3_gray_700">@android:color/system_neutral1_700</color>
|
||||
<color name="m3_gray_600">@android:color/system_neutral1_600</color>
|
||||
<color name="m3_gray_500">@android:color/system_neutral1_500</color>
|
||||
<color name="m3_gray_400">@android:color/system_neutral1_400</color>
|
||||
<color name="m3_gray_300">@android:color/system_neutral1_300</color>
|
||||
<color name="m3_gray_200">@android:color/system_neutral1_200</color>
|
||||
<color name="m3_gray_100">@android:color/system_neutral1_100</color>
|
||||
<color name="m3_gray_50t">@android:color/system_neutral1_50</color>
|
||||
<color name="m3_gray_50">@android:color/system_neutral1_50</color>
|
||||
<color name="m3_gray_25">@android:color/system_neutral1_10</color>
|
||||
<color name="m3_neutral1_900">@android:color/system_neutral1_900</color>
|
||||
<color name="m3_neutral1_800t">@android:color/system_neutral1_800</color>
|
||||
<color name="m3_neutral1_800">@android:color/system_neutral1_800</color>
|
||||
<color name="m3_neutral1_700">@android:color/system_neutral1_700</color>
|
||||
<color name="m3_neutral1_600">@android:color/system_neutral1_600</color>
|
||||
<color name="m3_neutral1_500">@android:color/system_neutral1_500</color>
|
||||
<color name="m3_neutral1_400">@android:color/system_neutral1_400</color>
|
||||
<color name="m3_neutral1_300">@android:color/system_neutral1_300</color>
|
||||
<color name="m3_neutral1_200">@android:color/system_neutral1_200</color>
|
||||
<color name="m3_neutral1_100">@android:color/system_neutral1_100</color>
|
||||
<color name="m3_neutral1_50t">@android:color/system_neutral1_50</color>
|
||||
<color name="m3_neutral1_50">@android:color/system_neutral1_50</color>
|
||||
<color name="m3_neutral1_25">@android:color/system_neutral1_10</color>
|
||||
|
||||
<color name="m3_primary_25">@android:color/system_accent1_10</color>
|
||||
<color name="m3_primary_50">@android:color/system_accent1_50</color>
|
||||
<color name="m3_primary_100">@android:color/system_accent1_100</color>
|
||||
<color name="m3_primary_200">@android:color/system_accent1_200</color>
|
||||
<color name="m3_primary_300">@android:color/system_accent1_300</color>
|
||||
<color name="m3_primary_400">@android:color/system_accent1_400</color>
|
||||
<color name="m3_primary_500">@android:color/system_accent1_500</color>
|
||||
<color name="m3_primary_600">@android:color/system_accent1_600</color>
|
||||
<color name="m3_primary_700">@android:color/system_accent1_700</color>
|
||||
<color name="m3_primary_800">@android:color/system_accent1_800</color>
|
||||
<color name="m3_primary_900">@android:color/system_accent1_900</color>
|
||||
<color name="m3_accent1_25">@android:color/system_accent1_10</color>
|
||||
<color name="m3_accent1_50">@android:color/system_accent1_50</color>
|
||||
<color name="m3_accent1_100">@android:color/system_accent1_100</color>
|
||||
<color name="m3_accent1_200">@android:color/system_accent1_200</color>
|
||||
<color name="m3_accent1_300">@android:color/system_accent1_300</color>
|
||||
<color name="m3_accent1_400">@android:color/system_accent1_400</color>
|
||||
<color name="m3_accent1_500">@android:color/system_accent1_500</color>
|
||||
<color name="m3_accent1_600">@android:color/system_accent1_600</color>
|
||||
<color name="m3_accent1_700">@android:color/system_accent1_700</color>
|
||||
<color name="m3_accent1_800">@android:color/system_accent1_800</color>
|
||||
<color name="m3_accent1_900">@android:color/system_accent1_900</color>
|
||||
|
||||
<color name="m3_neutral2_900">@android:color/system_neutral2_900</color>
|
||||
<color name="m3_neutral2_800t">@android:color/system_neutral2_800</color>
|
||||
<color name="m3_neutral2_800">@android:color/system_neutral2_800</color>
|
||||
<color name="m3_neutral2_700">@android:color/system_neutral2_700</color>
|
||||
<color name="m3_neutral2_600">@android:color/system_neutral2_600</color>
|
||||
<color name="m3_neutral2_500">@android:color/system_neutral2_500</color>
|
||||
<color name="m3_neutral2_400">@android:color/system_neutral2_400</color>
|
||||
<color name="m3_neutral2_300">@android:color/system_neutral2_300</color>
|
||||
<color name="m3_neutral2_200">@android:color/system_neutral2_200</color>
|
||||
<color name="m3_neutral2_100">@android:color/system_neutral2_100</color>
|
||||
<color name="m3_neutral2_50t">@android:color/system_neutral2_50</color>
|
||||
<color name="m3_neutral2_50">@android:color/system_neutral2_50</color>
|
||||
<color name="m3_neutral2_25">@android:color/system_neutral2_10</color>
|
||||
|
||||
<color name="m3_accent2_25">@android:color/system_accent2_10</color>
|
||||
<color name="m3_accent2_50">@android:color/system_accent2_50</color>
|
||||
<color name="m3_accent2_100">@android:color/system_accent2_100</color>
|
||||
<color name="m3_accent2_200">@android:color/system_accent2_200</color>
|
||||
<color name="m3_accent2_300">@android:color/system_accent2_300</color>
|
||||
<color name="m3_accent2_400">@android:color/system_accent2_400</color>
|
||||
<color name="m3_accent2_500">@android:color/system_accent2_500</color>
|
||||
<color name="m3_accent2_600">@android:color/system_accent2_600</color>
|
||||
<color name="m3_accent2_700">@android:color/system_accent2_700</color>
|
||||
<color name="m3_accent2_800">@android:color/system_accent2_800</color>
|
||||
<color name="m3_accent2_900">@android:color/system_accent2_900</color>
|
||||
|
||||
<!-- light theme -->
|
||||
<color name="m3_sys_light_primary">@android:color/system_accent1_600</color>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<attr name="colorAccentLightest" format="color"/>
|
||||
<attr name="profileHeaderBackground" format="color"/>
|
||||
<attr name="toolbarBackground" format="color"/>
|
||||
<attr name="colorIconDisabled" format="color"/>
|
||||
|
||||
<attr name="colorButtonBackgroundPrimaryDarkOnLight" format="color"/>
|
||||
<attr name="colorButtonBackgroundPrimaryDarkOnLightDisabled" format="color"/>
|
||||
@@ -73,6 +74,7 @@
|
||||
|
||||
<declare-styleable name="MaxWidthFrameLayout">
|
||||
<attr name="android:maxWidth" format="dimension"/>
|
||||
<attr name="defaultWidth" format="dimension" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="FloatingHintEditTextLayout">
|
||||
@@ -106,4 +108,42 @@
|
||||
<attr name="colorGray800" format="color" />
|
||||
<attr name="colorGray800t" format="color" />
|
||||
<attr name="colorGray900" format="color" />
|
||||
|
||||
<attr name="colorSecondary25" format="color" />
|
||||
<attr name="colorSecondary50" format="color" />
|
||||
<attr name="colorSecondary100" format="color" />
|
||||
<attr name="colorSecondary200" format="color" />
|
||||
<attr name="colorSecondary300" format="color" />
|
||||
<attr name="colorSecondary400" format="color" />
|
||||
<attr name="colorSecondary500" format="color" />
|
||||
<attr name="colorSecondary600" format="color" />
|
||||
<attr name="colorSecondary700" format="color" />
|
||||
<attr name="colorSecondary800" format="color" />
|
||||
<attr name="colorSecondary900" format="color" />
|
||||
|
||||
<attr name="colorTertiary25" format="color" />
|
||||
<attr name="colorTertiary50" format="color" />
|
||||
<attr name="colorTertiary100" format="color" />
|
||||
<attr name="colorTertiary200" format="color" />
|
||||
<attr name="colorTertiary300" format="color" />
|
||||
<attr name="colorTertiary400" format="color" />
|
||||
<attr name="colorTertiary500" format="color" />
|
||||
<attr name="colorTertiary600" format="color" />
|
||||
<attr name="colorTertiary700" format="color" />
|
||||
<attr name="colorTertiary800" format="color" />
|
||||
<attr name="colorTertiary900" format="color" />
|
||||
|
||||
<attr name="colorNeutral25" format="color" />
|
||||
<attr name="colorNeutral50" format="color" />
|
||||
<attr name="colorNeutral50t" format="color" />
|
||||
<attr name="colorNeutral100" format="color" />
|
||||
<attr name="colorNeutral200" format="color" />
|
||||
<attr name="colorNeutral300" format="color" />
|
||||
<attr name="colorNeutral400" format="color" />
|
||||
<attr name="colorNeutral500" format="color" />
|
||||
<attr name="colorNeutral600" format="color" />
|
||||
<attr name="colorNeutral700" format="color" />
|
||||
<attr name="colorNeutral800" format="color" />
|
||||
<attr name="colorNeutral800t" format="color" />
|
||||
<attr name="colorNeutral900" format="color" />
|
||||
</resources>
|
||||
@@ -103,31 +103,69 @@
|
||||
<!-- M3 dynamic colors -->
|
||||
<color name="m3_navigation_bar_bg">@color/gray_50</color>
|
||||
|
||||
<color name="m3_gray_900">@color/gray_900</color>
|
||||
<color name="m3_gray_800t">@color/gray_800t</color>
|
||||
<color name="m3_gray_800">@color/gray_800</color>
|
||||
<color name="m3_gray_700">@color/gray_700</color>
|
||||
<color name="m3_gray_600">@color/gray_600</color>
|
||||
<color name="m3_gray_500">@color/gray_500</color>
|
||||
<color name="m3_gray_400">@color/gray_400</color>
|
||||
<color name="m3_gray_300">@color/gray_300</color>
|
||||
<color name="m3_gray_200">@color/gray_200</color>
|
||||
<color name="m3_gray_100">@color/gray_100</color>
|
||||
<color name="m3_gray_50t">@color/gray_50t</color>
|
||||
<color name="m3_gray_50">@color/gray_50</color>
|
||||
<color name="m3_gray_25">@color/gray_25</color>
|
||||
<color name="m3_neutral1_900">@color/gray_900</color>
|
||||
<color name="m3_neutral1_800t">@color/gray_800t</color>
|
||||
<color name="m3_neutral1_800">@color/gray_800</color>
|
||||
<color name="m3_neutral1_700">@color/gray_700</color>
|
||||
<color name="m3_neutral1_600">@color/gray_600</color>
|
||||
<color name="m3_neutral1_500">@color/gray_500</color>
|
||||
<color name="m3_neutral1_400">@color/gray_400</color>
|
||||
<color name="m3_neutral1_300">@color/gray_300</color>
|
||||
<color name="m3_neutral1_200">@color/gray_200</color>
|
||||
<color name="m3_neutral1_100">@color/gray_100</color>
|
||||
<color name="m3_neutral1_50t">@color/gray_50t</color>
|
||||
<color name="m3_neutral1_50">@color/gray_50</color>
|
||||
<color name="m3_neutral1_25">@color/gray_25</color>
|
||||
|
||||
<color name="m3_primary_25">@color/primary_25</color>
|
||||
<color name="m3_primary_50">@color/primary_50</color>
|
||||
<color name="m3_primary_100">@color/primary_100</color>
|
||||
<color name="m3_primary_200">@color/primary_200</color>
|
||||
<color name="m3_primary_300">@color/primary_300</color>
|
||||
<color name="m3_primary_400">@color/primary_400</color>
|
||||
<color name="m3_primary_500">@color/primary_500</color>
|
||||
<color name="m3_primary_600">@color/primary_600</color>
|
||||
<color name="m3_primary_700">@color/primary_700</color>
|
||||
<color name="m3_primary_800">@color/primary_800</color>
|
||||
<color name="m3_primary_900">@color/primary_900</color>
|
||||
<color name="m3_neutral2_900">@color/gray_900</color>
|
||||
<color name="m3_neutral2_800t">@color/gray_800t</color>
|
||||
<color name="m3_neutral2_800">@color/gray_800</color>
|
||||
<color name="m3_neutral2_700">@color/gray_700</color>
|
||||
<color name="m3_neutral2_600">@color/gray_600</color>
|
||||
<color name="m3_neutral2_500">@color/gray_500</color>
|
||||
<color name="m3_neutral2_400">@color/gray_400</color>
|
||||
<color name="m3_neutral2_300">@color/gray_300</color>
|
||||
<color name="m3_neutral2_200">@color/gray_200</color>
|
||||
<color name="m3_neutral2_100">@color/gray_100</color>
|
||||
<color name="m3_neutral2_50t">@color/gray_50t</color>
|
||||
<color name="m3_neutral2_50">@color/gray_50</color>
|
||||
<color name="m3_neutral2_25">@color/gray_25</color>
|
||||
|
||||
<color name="m3_accent1_25">@color/primary_25</color>
|
||||
<color name="m3_accent1_50">@color/primary_50</color>
|
||||
<color name="m3_accent1_100">@color/primary_100</color>
|
||||
<color name="m3_accent1_200">@color/primary_200</color>
|
||||
<color name="m3_accent1_300">@color/primary_300</color>
|
||||
<color name="m3_accent1_400">@color/primary_400</color>
|
||||
<color name="m3_accent1_500">@color/primary_500</color>
|
||||
<color name="m3_accent1_600">@color/primary_600</color>
|
||||
<color name="m3_accent1_700">@color/primary_700</color>
|
||||
<color name="m3_accent1_800">@color/primary_800</color>
|
||||
<color name="m3_accent1_900">@color/primary_900</color>
|
||||
|
||||
<color name="m3_accent2_25">@color/primary_25</color>
|
||||
<color name="m3_accent2_50">@color/primary_50</color>
|
||||
<color name="m3_accent2_100">@color/primary_100</color>
|
||||
<color name="m3_accent2_200">@color/primary_200</color>
|
||||
<color name="m3_accent2_300">@color/primary_300</color>
|
||||
<color name="m3_accent2_400">@color/primary_400</color>
|
||||
<color name="m3_accent2_500">@color/primary_500</color>
|
||||
<color name="m3_accent2_600">@color/primary_600</color>
|
||||
<color name="m3_accent2_700">@color/primary_700</color>
|
||||
<color name="m3_accent2_800">@color/primary_800</color>
|
||||
<color name="m3_accent2_900">@color/primary_900</color>
|
||||
|
||||
<color name="m3_accent3_25">@color/primary_25</color>
|
||||
<color name="m3_accent3_50">@color/primary_50</color>
|
||||
<color name="m3_accent3_100">@color/primary_100</color>
|
||||
<color name="m3_accent3_200">@color/primary_200</color>
|
||||
<color name="m3_accent3_300">@color/primary_300</color>
|
||||
<color name="m3_accent3_400">@color/primary_400</color>
|
||||
<color name="m3_accent3_500">@color/primary_500</color>
|
||||
<color name="m3_accent3_600">@color/primary_600</color>
|
||||
<color name="m3_accent3_700">@color/primary_700</color>
|
||||
<color name="m3_accent3_800">@color/primary_800</color>
|
||||
<color name="m3_accent3_900">@color/primary_900</color>
|
||||
|
||||
<!-- light theme -->
|
||||
<color name="m3_sys_light_primary">#6750A4</color>
|
||||
|
||||
@@ -3,4 +3,5 @@
|
||||
<dimen name="text_max_height">220dp</dimen>
|
||||
<dimen name="text_collapsed_height">145dp</dimen>
|
||||
<dimen name="layout_max_width">450dp</dimen>
|
||||
<dimen name="scroll_to_top_delta">300dp</dimen>
|
||||
</resources>
|
||||
@@ -26,34 +26,115 @@
|
||||
<item name="colorGray50t">@color/gray_50t</item>
|
||||
<item name="colorGray50">@color/gray_50</item>
|
||||
<item name="colorGray25">@color/gray_25</item>
|
||||
|
||||
<!--
|
||||
custom themes generally don't have secondary/tertiary accent colors -
|
||||
falling back to primary colors
|
||||
-->
|
||||
|
||||
<item name="colorSecondary25">@color/primary_25</item>
|
||||
<item name="colorSecondary50">@color/primary_50</item>
|
||||
<item name="colorSecondary100">@color/primary_100</item>
|
||||
<item name="colorSecondary200">@color/primary_200</item>
|
||||
<item name="colorSecondary300">@color/primary_300</item>
|
||||
<item name="colorSecondary400">@color/primary_400</item>
|
||||
<item name="colorSecondary500">@color/primary_500</item>
|
||||
<item name="colorSecondary600">@color/primary_600</item>
|
||||
<item name="colorSecondary700">@color/primary_700</item>
|
||||
<item name="colorSecondary800">@color/primary_800</item>
|
||||
<item name="colorSecondary900">@color/primary_900</item>
|
||||
|
||||
<item name="colorTertiary25">@color/primary_25</item>
|
||||
<item name="colorTertiary50">@color/primary_50</item>
|
||||
<item name="colorTertiary100">@color/primary_100</item>
|
||||
<item name="colorTertiary200">@color/primary_200</item>
|
||||
<item name="colorTertiary300">@color/primary_300</item>
|
||||
<item name="colorTertiary400">@color/primary_400</item>
|
||||
<item name="colorTertiary500">@color/primary_500</item>
|
||||
<item name="colorTertiary600">@color/primary_600</item>
|
||||
<item name="colorTertiary700">@color/primary_700</item>
|
||||
<item name="colorTertiary800">@color/primary_800</item>
|
||||
<item name="colorTertiary900">@color/primary_900</item>
|
||||
|
||||
<item name="colorNeutral900">@color/gray_900</item>
|
||||
<item name="colorNeutral800t">@color/gray_800t</item>
|
||||
<item name="colorNeutral800">@color/gray_800</item>
|
||||
<item name="colorNeutral700">@color/gray_700</item>
|
||||
<item name="colorNeutral600">@color/gray_600</item>
|
||||
<item name="colorNeutral500">@color/gray_500</item>
|
||||
<item name="colorNeutral400">@color/gray_400</item>
|
||||
<item name="colorNeutral300">@color/gray_300</item>
|
||||
<item name="colorNeutral200">@color/gray_200</item>
|
||||
<item name="colorNeutral100">@color/gray_100</item>
|
||||
<item name="colorNeutral50t">@color/gray_50t</item>
|
||||
<item name="colorNeutral50">@color/gray_50</item>
|
||||
<item name="colorNeutral25">@color/gray_25</item>
|
||||
</style>
|
||||
|
||||
<style name="ColorPalette.Material3">
|
||||
<item name="colorPrimary25">@color/m3_primary_25</item>
|
||||
<item name="colorPrimary50">@color/m3_primary_50</item>
|
||||
<item name="colorPrimary100">@color/m3_primary_100</item>
|
||||
<item name="colorPrimary200">@color/m3_primary_200</item>
|
||||
<item name="colorPrimary300">@color/m3_primary_300</item>
|
||||
<item name="colorPrimary400">@color/m3_primary_400</item>
|
||||
<item name="colorPrimary500">@color/m3_primary_500</item>
|
||||
<item name="colorPrimary600">@color/m3_primary_600</item>
|
||||
<item name="colorPrimary700">@color/m3_primary_700</item>
|
||||
<item name="colorPrimary800">@color/m3_primary_800</item>
|
||||
<item name="colorPrimary900">@color/m3_primary_900</item>
|
||||
<item name="colorPrimary25">@color/m3_accent1_25</item>
|
||||
<item name="colorPrimary50">@color/m3_accent1_50</item>
|
||||
<item name="colorPrimary100">@color/m3_accent1_100</item>
|
||||
<item name="colorPrimary200">@color/m3_accent1_200</item>
|
||||
<item name="colorPrimary300">@color/m3_accent1_300</item>
|
||||
<item name="colorPrimary400">@color/m3_accent1_400</item>
|
||||
<item name="colorPrimary500">@color/m3_accent1_500</item>
|
||||
<item name="colorPrimary600">@color/m3_accent1_600</item>
|
||||
<item name="colorPrimary700">@color/m3_accent1_700</item>
|
||||
<item name="colorPrimary800">@color/m3_accent1_800</item>
|
||||
<item name="colorPrimary900">@color/m3_accent1_900</item>
|
||||
|
||||
<item name="colorGray900">@color/m3_gray_900</item>
|
||||
<item name="colorGray800t">@color/m3_gray_800t</item>
|
||||
<item name="colorGray800">@color/m3_gray_800</item>
|
||||
<item name="colorGray700">@color/m3_gray_700</item>
|
||||
<item name="colorGray600">@color/m3_gray_600</item>
|
||||
<item name="colorGray500">@color/m3_gray_500</item>
|
||||
<item name="colorGray400">@color/m3_gray_400</item>
|
||||
<item name="colorGray300">@color/m3_gray_300</item>
|
||||
<item name="colorGray200">@color/m3_gray_200</item>
|
||||
<item name="colorGray100">@color/m3_gray_100</item>
|
||||
<item name="colorGray50t">@color/m3_gray_50t</item>
|
||||
<item name="colorGray50">@color/m3_gray_50</item>
|
||||
<item name="colorGray25">@color/m3_gray_25</item>
|
||||
<item name="colorSecondary25">@color/m3_accent2_25</item>
|
||||
<item name="colorSecondary50">@color/m3_accent2_50</item>
|
||||
<item name="colorSecondary100">@color/m3_accent2_100</item>
|
||||
<item name="colorSecondary200">@color/m3_accent2_200</item>
|
||||
<item name="colorSecondary300">@color/m3_accent2_300</item>
|
||||
<item name="colorSecondary400">@color/m3_accent2_400</item>
|
||||
<item name="colorSecondary500">@color/m3_accent2_500</item>
|
||||
<item name="colorSecondary600">@color/m3_accent2_600</item>
|
||||
<item name="colorSecondary700">@color/m3_accent2_700</item>
|
||||
<item name="colorSecondary800">@color/m3_accent2_800</item>
|
||||
<item name="colorSecondary900">@color/m3_accent2_900</item>
|
||||
|
||||
<item name="colorTertiary25">@color/m3_accent3_25</item>
|
||||
<item name="colorTertiary50">@color/m3_accent3_50</item>
|
||||
<item name="colorTertiary100">@color/m3_accent3_100</item>
|
||||
<item name="colorTertiary200">@color/m3_accent3_200</item>
|
||||
<item name="colorTertiary300">@color/m3_accent3_300</item>
|
||||
<item name="colorTertiary400">@color/m3_accent3_400</item>
|
||||
<item name="colorTertiary500">@color/m3_accent3_500</item>
|
||||
<item name="colorTertiary600">@color/m3_accent3_600</item>
|
||||
<item name="colorTertiary700">@color/m3_accent3_700</item>
|
||||
<item name="colorTertiary800">@color/m3_accent3_800</item>
|
||||
<item name="colorTertiary900">@color/m3_accent3_900</item>
|
||||
|
||||
<item name="colorGray900">@color/m3_neutral1_900</item>
|
||||
<item name="colorGray800t">@color/m3_neutral1_800t</item>
|
||||
<item name="colorGray800">@color/m3_neutral1_800</item>
|
||||
<item name="colorGray700">@color/m3_neutral1_700</item>
|
||||
<item name="colorGray600">@color/m3_neutral1_600</item>
|
||||
<item name="colorGray500">@color/m3_neutral1_500</item>
|
||||
<item name="colorGray400">@color/m3_neutral1_400</item>
|
||||
<item name="colorGray300">@color/m3_neutral1_300</item>
|
||||
<item name="colorGray200">@color/m3_neutral1_200</item>
|
||||
<item name="colorGray100">@color/m3_neutral1_100</item>
|
||||
<item name="colorGray50t">@color/m3_neutral1_50t</item>
|
||||
<item name="colorGray50">@color/m3_neutral1_50</item>
|
||||
<item name="colorGray25">@color/m3_neutral1_25</item>
|
||||
|
||||
<item name="colorNeutral900">@color/m3_neutral2_900</item>
|
||||
<item name="colorNeutral800t">@color/m3_neutral2_800t</item>
|
||||
<item name="colorNeutral800">@color/m3_neutral2_800</item>
|
||||
<item name="colorNeutral700">@color/m3_neutral2_700</item>
|
||||
<item name="colorNeutral600">@color/m3_neutral2_600</item>
|
||||
<item name="colorNeutral500">@color/m3_neutral2_500</item>
|
||||
<item name="colorNeutral400">@color/m3_neutral2_400</item>
|
||||
<item name="colorNeutral300">@color/m3_neutral2_300</item>
|
||||
<item name="colorNeutral200">@color/m3_neutral2_200</item>
|
||||
<item name="colorNeutral100">@color/m3_neutral2_100</item>
|
||||
<item name="colorNeutral50t">@color/m3_neutral2_50t</item>
|
||||
<item name="colorNeutral50">@color/m3_neutral2_50</item>
|
||||
<item name="colorNeutral25">@color/m3_neutral2_25</item>
|
||||
</style>
|
||||
|
||||
<style name="ColorPalette.Material3.Dark">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user