Compare commits

..

40 Commits

Author SHA1 Message Date
sk
4baaa39f35 bump version 2023-06-04 23:16:32 +02:00
sk
52f025ae5a Merge remote-tracking branch 'upstream/l10n_master' 2023-06-04 23:14:49 +02:00
sk22
14b805e883 Translated using Weblate (German)
Currently translated at 100.0% (293 of 293 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-06-04 21:13:53 +00:00
sk
433a7b15fe change bubble string 2023-06-04 23:03:29 +02:00
sk
6c8cbbc34a Merge remote-tracking branch 'weblate/main' 2023-06-04 22:58:56 +02:00
sk
d4fbb298c1 use sp for reply line inline icons 2023-06-04 22:57:06 +02:00
sk
2aeb5f03d6 remove unused sp drawables 2023-06-04 22:32:54 +02:00
sk
6522403c37 fix footer text margins 2023-06-04 22:12:45 +02:00
sk
f090ca7f75 use sp for scaled footer 2023-06-04 21:08:45 +02:00
sk
2f02a238df refresh updated main status 2023-06-04 20:56:44 +02:00
sk
0d5fa97800 fix wrong index 2023-06-04 20:40:27 +02:00
sk
b102deaee1 don't let interaction counts go negative 2023-06-04 19:08:18 +02:00
Eryk Michalak
968b2ee460 Translated using Weblate (Polish)
Currently translated at 97.9% (286 of 292 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/pl/
2023-06-04 10:37:37 +00:00
Andrewblasco
890340de94 Translated using Weblate (Spanish)
Currently translated at 99.6% (291 of 292 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-06-04 10:37:37 +00:00
sk
4ca1a7b29e fix index out of bounds exception 2023-06-04 11:45:12 +02:00
sk
5432f2590c fine-tune footer layout 2023-06-04 05:00:48 +02:00
sk
60ccf5cf0a only shift selection box if footer is present 2023-06-04 04:15:15 +02:00
sk
bc717f5b10 tweak footer margins and hitboxes 2023-06-04 04:08:38 +02:00
sk
486eef21dd responsive footer width 2023-06-04 02:16:47 +02:00
sk
44a4d02815 remove redundant suppress annotation 2023-06-04 01:36:38 +02:00
sk
336a8194bd fix settings button binding not reset visibility and events 2023-06-04 01:36:05 +02:00
sk
7859f4cd05 support parsing mailto links
i mean, why not - if github decided every @username@example.social is actually
an email address, might as well support sharing that mailto link to megalodon
2023-06-03 23:39:43 +02:00
sk
37622ba9ce generalize notification handling, open reports in browser 2023-06-03 22:47:20 +02:00
sk
7a6af89375 fix unwanted fab animation when scrolling and switching tab
closes sk22#528
2023-06-03 22:07:58 +02:00
sk
056bfaacfe fix fab being hidden when scrolling to top
closes sk22#528
2023-06-03 21:54:57 +02:00
sk
6684311ec5 fix button state/char counter not updating when empty
closes sk22#537
2023-06-03 21:24:40 +02:00
sk
11943571ad fix thread replies not added to data
closes sk22#543
2023-06-03 21:10:45 +02:00
sk
f696fcd412 simplify ancestry code 2023-06-03 21:03:47 +02:00
sk
2919e109ca remove unused member 2023-06-03 20:40:29 +02:00
sk
995f478708 allow sharing @-handles with megalodon
closes sk22#540
2023-06-03 20:31:00 +02:00
sk
fb8764bcd7 refactor ancestry, fix case regarding reply line
fix case where reply line was removed despite having no direct ancestor
2023-06-02 22:08:03 +02:00
Eugen Rochko
d7f73e02c5 New translations strings.xml (German) 2023-06-02 20:22:32 +02:00
Eugen Rochko
e897b3af57 New translations strings.xml (German) 2023-06-02 19:23:23 +02:00
sk
e04fd8a004 bump version 2023-06-02 19:10:08 +02:00
sk
ada70ae1b5 Merge remote-tracking branch 'upstream/l10n_master' 2023-06-02 19:09:44 +02:00
Espasant3
5fdec0900e Translated using Weblate (Galician)
Currently translated at 100.0% (292 of 292 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/gl/
2023-06-02 17:09:12 +00:00
Eugen Rochko
fdbf331432 New translations strings.xml (Bengali) 2023-06-02 18:01:03 +02:00
Eugen Rochko
aed86ac6f0 New translations strings.xml (Bengali) 2023-06-02 16:50:10 +02:00
Eugen Rochko
3a13d4d6c0 New translations strings.xml (Bengali) 2023-06-02 05:45:51 +02:00
Eugen Rochko
f5336564d0 New translations strings.xml (Bengali) 2023-06-02 04:39:21 +02:00
44 changed files with 748 additions and 324 deletions

View File

@@ -15,8 +15,8 @@ android {
applicationId "org.joinmastodon.android.sk" applicationId "org.joinmastodon.android.sk"
minSdk 23 minSdk 23
targetSdk 33 targetSdk 33
versionCode 87 versionCode 90
versionName "1.2.3+fork.87" versionName "1.2.3+fork.90"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW'] resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
} }

View File

@@ -2,14 +2,14 @@ package org.joinmastodon.android.fragments;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import android.util.Pair; import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusUpdatedEvent;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusContext; import org.joinmastodon.android.model.StatusContext;
import org.junit.Test; import org.junit.Test;
import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
public class ThreadFragmentTest { public class ThreadFragmentTest {
@@ -20,10 +20,7 @@ public class ThreadFragmentTest {
} }
private ThreadFragment.NeighborAncestryInfo fakeInfo(Status s, Status d, Status a) { private ThreadFragment.NeighborAncestryInfo fakeInfo(Status s, Status d, Status a) {
ThreadFragment.NeighborAncestryInfo info = new ThreadFragment.NeighborAncestryInfo(s); return new ThreadFragment.NeighborAncestryInfo(s, d, a);
info.descendantNeighbor = d;
info.ancestoringNeighbor = a;
return info;
} }
@Test @Test
@@ -55,6 +52,27 @@ public class ThreadFragmentTest {
), neighbors); ), 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 @Test
public void sortStatusContext() { public void sortStatusContext() {
StatusContext context = new StatusContext(); StatusContext context = new StatusContext();

View File

@@ -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"
));
}
}

View File

@@ -6,6 +6,7 @@ import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair;
import android.widget.Toast; import android.widget.Toast;
import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSession;
@@ -19,6 +20,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.BiConsumer;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import me.grishka.appkit.FragmentStackActivity; import me.grishka.appkit.FragmentStackActivity;
@@ -31,19 +33,23 @@ public class ExternalShareActivity extends FragmentStackActivity{
if(savedInstanceState==null){ if(savedInstanceState==null){
Optional<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT)); Optional<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT));
boolean isMastodonURL = text.map(UiUtils::looksLikeMastodonUrl).orElse(false); Optional<Pair<String, Optional<String>>> fediHandle = text.flatMap(UiUtils::parseFediverseHandle);
boolean isFediUrl = text.map(UiUtils::looksLikeFediverseUrl).orElse(false);
boolean isOpenable = isFediUrl || fediHandle.isPresent();
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts(); List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts();
if(sessions.isEmpty()){ if (sessions.isEmpty()){
Toast.makeText(this, R.string.err_not_logged_in, Toast.LENGTH_SHORT).show(); Toast.makeText(this, R.string.err_not_logged_in, Toast.LENGTH_SHORT).show();
finish(); finish();
}else if(sessions.size()==1 && !isMastodonURL){ } else if (isOpenable || sessions.size() > 1) {
openComposeFragment(sessions.get(0).getID()); AccountSwitcherSheet sheet = new AccountSwitcherSheet(this, null, true, isOpenable);
}else{ if (isOpenable) sheet.setOnClick((accountId, open) -> {
new AccountSwitcherSheet(this, null, true, isMastodonURL, (accountId, open) -> {
if (open && text.isPresent()) { if (open && text.isPresent()) {
UiUtils.lookupURL(this, accountId, text.get(), false, (clazz, args) -> { BiConsumer<Class<? extends Fragment>, Bundle> callback = (clazz, args) -> {
if (clazz == null) { if (clazz == null) {
Toast.makeText(this, R.string.sk_open_in_app_failed, Toast.LENGTH_SHORT).show();
// TODO: do something about the window getting leaked
sheet.dismiss();
finish(); finish();
return; return;
} }
@@ -52,11 +58,16 @@ public class ExternalShareActivity extends FragmentStackActivity{
intent.putExtras(args); intent.putExtras(args);
finish(); finish();
startActivity(intent); startActivity(intent);
}); };
if (isFediUrl) UiUtils.lookupURL(this, accountId, text.get(), false, callback);
else UiUtils.lookupAccountHandle(this, accountId, fediHandle.get(), callback);
} else { } else {
openComposeFragment(accountId); openComposeFragment(accountId);
} }
}).show(); });
sheet.show();
} else if (sessions.size() == 1) {
openComposeFragment(sessions.get(0).getID());
} }
} }
} }

View File

@@ -117,25 +117,13 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
} }
private void showFragmentForNotification(Notification notification, String accountID){ private void showFragmentForNotification(Notification notification, String accountID){
Fragment fragment;
Bundle args=new Bundle();
args.putString("account", accountID);
args.putBoolean("_can_go_back", true);
try{ try{
notification.postprocess(); notification.postprocess();
}catch(ObjectValidationException x){ }catch(ObjectValidationException x){
Log.w("MainActivity", x); Log.w("MainActivity", x);
return; return;
} }
if(notification.status!=null){ UiUtils.showFragmentForNotification(this, notification, accountID, null);
fragment=new ThreadFragment();
args.putParcelable("status", Parcels.wrap(notification.status));
}else{
fragment=new ProfileFragment();
args.putParcelable("profileAccount", Parcels.wrap(notification.account));
}
fragment.setArguments(args);
showFragment(fragment);
} }
private void showFragmentForExternalShare(Bundle args) { private void showFragmentForExternalShare(Bundle args) {

View File

@@ -46,7 +46,7 @@ public class StatusInteractionController{
@Override @Override
public void onSuccess(Status result){ public void onSuccess(Status result){
runningFavoriteRequests.remove(status.id); runningFavoriteRequests.remove(status.id);
result.favouritesCount = Math.max(0, status.favouritesCount) + (favorited ? 1 : -1); result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1));
cb.accept(result); cb.accept(result);
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result)); if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
} }
@@ -80,7 +80,7 @@ public class StatusInteractionController{
public void onSuccess(Status reblog){ public void onSuccess(Status reblog){
Status result = reblog.getContentStatus(); Status result = reblog.getContentStatus();
runningReblogRequests.remove(status.id); runningReblogRequests.remove(status.id);
result.reblogsCount = Math.max(0, status.reblogsCount) + (reblogged ? 1 : -1); result.reblogsCount = Math.max(0, status.reblogsCount + (reblogged ? 1 : -1));
cb.accept(result); cb.accept(result);
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result)); if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
} }

View File

@@ -16,7 +16,6 @@ import android.text.TextPaint;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.view.animation.TranslateAnimation; import android.view.animation.TranslateAnimation;
import android.widget.ImageButton; import android.widget.ImageButton;
@@ -35,6 +34,7 @@ import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.BetterItemAnimator; import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
@@ -82,6 +82,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
protected HashMap<String, Relationship> relationships=new HashMap<>(); protected HashMap<String, Relationship> relationships=new HashMap<>();
protected Rect tmpRect=new Rect(); protected Rect tmpRect=new Rect();
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView); protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
protected boolean currentlyScrolling;
public BaseStatusListFragment(){ public BaseStatusListFragment(){
super(20); super(20);
@@ -95,7 +96,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
UiUtils.loadMaxWidth(getContext());
if(GlobalUserPreferences.disableMarquee){ if(GlobalUserPreferences.disableMarquee){
setTitleMarqueeEnabled(false); setTitleMarqueeEnabled(false);
setSubtitleMarqueeEnabled(false); setSubtitleMarqueeEnabled(false);
@@ -292,6 +292,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
fab.startAnimation(animate); fab.startAnimation(animate);
} }
public boolean isScrolling() {
return currentlyScrolling;
}
@Override @Override
public void hideFab() { public void hideFab() {
View fab = getFab(); View fab = getFab();
@@ -319,7 +323,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
currentPhotoViewer.offsetView(-dx, -dy); currentPhotoViewer.offsetView(-dx, -dy);
View fab = getFab(); View fab = getFab();
if (fab!=null && GlobalUserPreferences.autoHideFab) { if (fab!=null && GlobalUserPreferences.autoHideFab && dy != UiUtils.SCROLL_TO_TOP_DELTA) {
if (dy > 0 && fab.getVisibility() == View.VISIBLE) { if (dy > 0 && fab.getVisibility() == View.VISIBLE) {
hideFab(); hideFab();
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) { } else if (dy < 0 && fab.getVisibility() != View.VISIBLE) {
@@ -332,6 +336,12 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
} }
} }
} }
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
currentlyScrolling = newState != RecyclerView.SCROLL_STATE_IDLE;
}
}); });
list.addItemDecoration(new StatusListItemDecoration()); list.addItemDecoration(new StatusListItemDecoration());
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){ ((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
@@ -357,10 +367,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
if (firstIndex < 0) firstIndex = i; if (firstIndex < 0) firstIndex = i;
lastIndex = i; lastIndex = i;
StatusDisplayItem item = h.getItem(); StatusDisplayItem item = h.getItem();
hasDescendant = item.hasDescendantNeighbor(); hasDescendant = item.hasDescendantNeighbor;
// no for direct descendants because main status (right above) is // no for direct descendants because main status (right above) is
// being displayed with an extended footer - no connected layout // being displayed with an extended footer - no connected layout
hasAncestor = item.hasAncestoringNeighbor() && !item.isDirectDescendant; hasAncestor = item.hasAncestoringNeighbor && !item.isDirectDescendant;
list.getDecoratedBoundsWithMargins(child, tmpRect); list.getDecoratedBoundsWithMargins(child, tmpRect);
outRect.left=Math.min(outRect.left, tmpRect.left); outRect.left=Math.min(outRect.left, tmpRect.left);
outRect.top=Math.min(outRect.top, tmpRect.top); outRect.top=Math.min(outRect.top, tmpRect.top);
@@ -375,7 +385,9 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
} }
// shifting the selection box down // shifting the selection box down
// see also: FooterStatusDisplayItem#onBind (setMargins) // see also: FooterStatusDisplayItem#onBind (setMargins)
if (isWarning || firstIndex < 0 || lastIndex < 0) return; if (isWarning || firstIndex < 0 || lastIndex < 0 ||
!(list.getChildViewHolder(list.getChildAt(lastIndex))
instanceof FooterStatusDisplayItem.Holder)) return;
int prevIndex = firstIndex - 1, nextIndex = lastIndex + 1; int prevIndex = firstIndex - 1, nextIndex = lastIndex + 1;
boolean prevIsWarning = prevIndex > 0 && prevIndex < list.getChildCount() && boolean prevIsWarning = prevIndex > 0 && prevIndex < list.getChildCount() &&
list.getChildViewHolder(list.getChildAt(prevIndex)) list.getChildViewHolder(list.getChildAt(prevIndex))
@@ -797,7 +809,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
RecyclerView.ViewHolder siblingHolder=parent.getChildViewHolder(bottomSibling); RecyclerView.ViewHolder siblingHolder=parent.getChildViewHolder(bottomSibling);
if(holder instanceof StatusDisplayItem.Holder<?> ih && siblingHolder instanceof StatusDisplayItem.Holder<?> sh if(holder instanceof StatusDisplayItem.Holder<?> ih && siblingHolder instanceof StatusDisplayItem.Holder<?> sh
&& (!ih.getItemID().equals(sh.getItemID()) || sh instanceof ExtendedFooterStatusDisplayItem.Holder) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP){ && (!ih.getItemID().equals(sh.getItemID()) || sh instanceof ExtendedFooterStatusDisplayItem.Holder) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP){
if (!ih.getItem().isMainStatus && ih.getItem().hasDescendantNeighbor()) continue; if (!ih.getItem().isMainStatus && ih.getItem().hasDescendantNeighbor) continue;
drawDivider(child, bottomSibling, holder, siblingHolder, parent, c, dividerPaint); drawDivider(child, bottomSibling, holder, siblingHolder, parent, c, dividerPaint);
} }
} }

View File

@@ -579,8 +579,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
@Override @Override
public void afterTextChanged(Editable s){ public void afterTextChanged(Editable s){
if(s.length()==0) if(s.length()==0){
updateCharCounter();
return; return;
}
int start=lastChangeStart; int start=lastChangeStart;
int count=lastChangeCount; int count=lastChangeCount;
// offset one char back to catch an already typed '@' or '#' or ':' // offset one char back to catch an already typed '@' or '#' or ':'

View File

@@ -6,4 +6,5 @@ public interface HasFab {
View getFab(); View getFab();
void showFab(); void showFab();
void hideFab(); void hideFab();
boolean isScrolling();
} }

View File

@@ -244,7 +244,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
} }
getChildFragmentManager().beginTransaction().hide(fragmentForTab(currentTab)).show(newFragment).commit(); getChildFragmentManager().beginTransaction().hide(fragmentForTab(currentTab)).show(newFragment).commit();
maybeTriggerLoading(newFragment); maybeTriggerLoading(newFragment);
if (newFragment instanceof HasFab fabulous) fabulous.showFab(); if (newFragment instanceof HasFab fabulous && !fabulous.isScrolling()) fabulous.showFab();
currentTab=tab; currentTab=tab;
((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this); ((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this);
if (tab == R.id.tab_search && isPleroma) searchFragment.selectSearch(); if (tab == R.id.tab_search && isPleroma) searchFragment.selectSearch();

View File

@@ -460,6 +460,12 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) l.hideFab(); if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) l.hideFab();
} }
@Override
public boolean isScrolling() {
return (fragments[pager.getCurrentItem()] instanceof HasFab fabulous)
&& fabulous.isScrolling();
}
private void updateSwitcherIcon(int i) { private void updateSwitcherIcon(int i) {
timelineIcon.setImageResource(timelines[i].getIcon().iconRes); timelineIcon.setImageResource(timelines[i].getIcon().iconRes);
timelineTitle.setText(timelines[i].getTitle(getContext())); timelineTitle.setText(timelines[i].getTitle(getContext()));

View File

@@ -192,23 +192,10 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
@Override @Override
public void onItemClick(String id){ public void onItemClick(String id){
Notification n=getNotificationByID(id); Notification n=getNotificationByID(id);
if(n.status!=null){ Bundle args = new Bundle();
Status status=n.status; if(n.status != null && n.status.inReplyToAccountId != null && knownAccounts.containsKey(n.status.inReplyToAccountId))
Bundle args=new Bundle(); args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(n.status.inReplyToAccountId)));
args.putString("account", accountID); UiUtils.showFragmentForNotification(getContext(), n, accountID, args);
args.putParcelable("status", Parcels.wrap(status));
if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId))
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId)));
Nav.go(getActivity(), ThreadFragment.class, args);
}else if(n.report != null){
String domain = AccountSessionManager.getInstance().getAccount(accountID).domain;
UiUtils.launchWebBrowser(getActivity(), "https://"+domain+"/admin/reports/"+n.report.id);
}else{
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("profileAccount", Parcels.wrap(n.account));
Nav.go(getActivity(), ProfileFragment.class, args);
}
} }
@Override @Override

View File

@@ -775,6 +775,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if (getFragmentForPage(pager.getCurrentItem()) instanceof HasFab fabulous) fabulous.hideFab(); if (getFragmentForPage(pager.getCurrentItem()) instanceof HasFab fabulous) fabulous.hideFab();
} }
@Override
public boolean isScrolling() {
return getFragmentForPage(pager.getCurrentItem()) instanceof HasFab fabulous
&& fabulous.isScrolling();
}
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){ private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
int topBarsH=getToolbar().getHeight()+statusBarHeight; int topBarsH=getToolbar().getHeight()+statusBarHeight;
if(scrollY>avatarBorder.getTop()-topBarsH){ if(scrollY>avatarBorder.getTop()-topBarsH){

View File

@@ -3,7 +3,8 @@ package org.joinmastodon.android.fragments;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.utils.V;
import org.joinmastodon.android.ui.utils.UiUtils;
public interface ScrollableToTop{ public interface ScrollableToTop{
void scrollToTop(); void scrollToTop();
@@ -21,7 +22,7 @@ public interface ScrollableToTop{
@Override @Override
public boolean onPreDraw(){ public boolean onPreDraw(){
list.getViewTreeObserver().removeOnPreDrawListener(this); list.getViewTreeObserver().removeOnPreDrawListener(this);
list.scrollBy(0, V.dp(300)); list.scrollBy(0, UiUtils.SCROLL_TO_TOP_DELTA);
list.smoothScrollToPosition(0); list.smoothScrollToPosition(0);
return true; return true;
} }

View File

@@ -1076,7 +1076,6 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
private final ImageView icon; private final ImageView icon;
private final TextView text; private final TextView text;
@SuppressLint("ClickableViewAccessibility")
public ButtonViewHolder(){ public ButtonViewHolder(){
super(getActivity(), R.layout.item_settings_button, list); super(getActivity(), R.layout.item_settings_button, list);
text=findViewById(R.id.text); text=findViewById(R.id.text);
@@ -1084,14 +1083,17 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
button=findViewById(R.id.button); button=findViewById(R.id.button);
} }
@SuppressLint("ClickableViewAccessibility")
@Override @Override
public void onBind(ButtonItem item){ public void onBind(ButtonItem item){
text.setText(item.text); text.setText(item.text);
if (item.icon == 0) { icon.setVisibility(item.icon == 0 ? View.GONE : View.VISIBLE);
icon.setVisibility(View.GONE); icon.setImageResource(item.icon == 0 ? 0 : item.icon);
} else { // reset listeners before letting the button consumer consume the button
icon.setImageResource(item.icon); // (and potentially set some listeners, but not others)
} button.setOnTouchListener(null);
button.setOnClickListener(null);
button.setOnLongClickListener(null);
item.buttonConsumer.accept(button); item.buttonConsumer.accept(button);
} }
} }

View File

@@ -2,18 +2,23 @@ package org.joinmastodon.android.fragments;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.util.Pair;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
import org.joinmastodon.android.api.requests.statuses.GetStatusContext; import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent; import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusUpdatedEvent;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusContext; import org.joinmastodon.android.model.StatusContext;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
@@ -32,35 +37,18 @@ import java.util.Collections;
import java.util.Deque; import java.util.Deque;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent { public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent {
protected Status mainStatus; protected Status mainStatus, updatedStatus;
/**
* lists the hierarchy of ancestors and descendants in a thread. level 0 = the main status.
* e.g.
* <pre>
* [0] ancestor: -2 ↰
* [1] ancestor: -1 ↰
* [2] main status: 0 ↰
* [3] descendant: 1 ↰
* [4] descendant: 2 ↰
* [5] descendant: 3
* [6] descendant: 1
* [7] descendant: 1 ↰
* [8] descendant: 2
* </pre>
* confused? good. /j
*/
private final List<Pair<String, Integer>> levels = new ArrayList<>();
private final HashMap<String, NeighborAncestryInfo> ancestryMap = new HashMap<>(); private final HashMap<String, NeighborAncestryInfo> ancestryMap = new HashMap<>();
private boolean initialAnimationFinished;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
@@ -92,15 +80,17 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
NeighborAncestryInfo ancestryInfo = ancestryMap.get(s.id); NeighborAncestryInfo ancestryInfo = ancestryMap.get(s.id);
if (ancestryInfo != null) { if (ancestryInfo != null) {
item.setAncestryInfo( item.setAncestryInfo(
ancestryInfo, ancestryInfo.descendantNeighbor != null,
ancestryInfo.ancestoringNeighbor != null,
s.id.equals(mainStatus.id), s.id.equals(mainStatus.id),
ancestryInfo.getAncestoringNeighbor() Optional.ofNullable(ancestryInfo.ancestoringNeighbor)
.map(ancestor -> ancestor.id.equals(mainStatus.id)) .map(ancestor -> ancestor.id.equals(mainStatus.id))
.orElse(false) .orElse(false)
); );
} }
if (item instanceof ReblogOrReplyLineStatusDisplayItem && !item.isDirectDescendant) { if (item instanceof ReblogOrReplyLineStatusDisplayItem &&
(!item.isDirectDescendant && item.hasAncestoringNeighbor)) {
deleteTheseItems.add(i); deleteTheseItems.add(i);
} }
@@ -120,11 +110,12 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
refreshMainStatus();
currentRequest=new GetStatusContext(mainStatus.id) currentRequest=new GetStatusContext(mainStatus.id)
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(StatusContext result){ public void onSuccess(StatusContext result){
if (getActivity() == null) return; if (getContext() == null) return;
if(refreshing){ if(refreshing){
data.clear(); data.clear();
ancestryMap.clear(); ancestryMap.clear();
@@ -168,6 +159,40 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
.exec(accountID); .exec(accountID);
} }
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) { public static List<NeighborAncestryInfo> mapNeighborhoodAncestry(Status mainStatus, StatusContext context) {
List<NeighborAncestryInfo> ancestry = new ArrayList<>(); List<NeighborAncestryInfo> ancestry = new ArrayList<>();
@@ -178,22 +203,21 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
int count = statuses.size(); int count = statuses.size();
for (int index = 0; index < count; index++) { for (int index = 0; index < count; index++) {
Status current = statuses.get(index); Status current = statuses.get(index);
NeighborAncestryInfo item = new NeighborAncestryInfo(current); ancestry.add(new NeighborAncestryInfo(
current,
item.descendantNeighbor = Optional // descendant neighbor
.ofNullable(count > index + 1 ? statuses.get(index + 1) : null) Optional
.filter(s -> s.inReplyToId.equals(current.id)) .ofNullable(count > index + 1 ? statuses.get(index + 1) : null)
.orElse(null); .filter(s -> s.inReplyToId.equals(current.id))
.orElse(null),
item.ancestoringNeighbor = Optional.ofNullable(index > 0 ? ancestry.get(index - 1) : null) // ancestoring neighbor
.filter(ancestor -> ancestor Optional.ofNullable(index > 0 ? ancestry.get(index - 1) : null)
.getDescendantNeighbor() .filter(ancestor -> Optional.ofNullable(ancestor.descendantNeighbor)
.map(ancestorsDescendant -> ancestorsDescendant.id.equals(current.id)) .map(ancestorsDescendant -> ancestorsDescendant.id.equals(current.id))
.orElse(false)) .orElse(false))
.flatMap(NeighborAncestryInfo::getStatus) .map(a -> a.status)
.orElse(null); .orElse(null)
));
ancestry.add(item);
} }
return ancestry; return ancestry;
@@ -263,10 +287,22 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
showContent(); showContent();
if(!loaded) if(!loaded)
footerProgress.setVisibility(View.VISIBLE); footerProgress.setVisibility(View.VISIBLE);
list.setItemAnimator(new BetterItemAnimator() {
@Override
public void onAnimationFinished(@NonNull RecyclerView.ViewHolder viewHolder) {
super.onAnimationFinished(viewHolder);
// 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){ protected void onStatusCreated(StatusCreatedEvent ev){
if(ev.status.inReplyToId!=null && getStatusByID(ev.status.inReplyToId)!=null){ if(ev.status.inReplyToId!=null && getStatusByID(ev.status.inReplyToId)!=null){
data.add(ev.status);
onAppendItems(Collections.singletonList(ev.status)); onAppendItems(Collections.singletonList(ev.status));
} }
} }
@@ -297,31 +333,13 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
return Uri.parse(mainStatus.url); return Uri.parse(mainStatus.url);
} }
public static class NeighborAncestryInfo { protected static class NeighborAncestryInfo {
protected Status status, descendantNeighbor, ancestoringNeighbor; protected Status status, descendantNeighbor, ancestoringNeighbor;
public NeighborAncestryInfo(@NonNull Status status) { protected NeighborAncestryInfo(@NonNull Status status, Status descendantNeighbor, Status ancestoringNeighbor) {
this.status = status; this.status = status;
} this.descendantNeighbor = descendantNeighbor;
this.ancestoringNeighbor = ancestoringNeighbor;
public Optional<Status> getStatus() {
return Optional.ofNullable(status);
}
public Optional<Status> getDescendantNeighbor() {
return Optional.ofNullable(descendantNeighbor);
}
public Optional<Status> getAncestoringNeighbor() {
return Optional.ofNullable(ancestoringNeighbor);
}
public boolean hasDescendantNeighbor() {
return getDescendantNeighbor().isPresent();
}
public boolean hasAncestoringNeighbor() {
return getAncestoringNeighbor().isPresent();
} }
@Override @Override

View File

@@ -58,18 +58,18 @@ import me.grishka.appkit.views.UsableRecyclerView;
public class AccountSwitcherSheet extends BottomSheet{ public class AccountSwitcherSheet extends BottomSheet{
private final Activity activity; private final Activity activity;
private final HomeFragment fragment; private final HomeFragment fragment;
private final BiConsumer<String, Boolean> onClick;
private final boolean externalShare, openInApp; private final boolean externalShare, openInApp;
private BiConsumer<String, Boolean> onClick;
private UsableRecyclerView list; private UsableRecyclerView list;
private List<WrappedAccount> accounts; private List<WrappedAccount> accounts;
private ListImageLoaderWrapper imgLoader; private ListImageLoaderWrapper imgLoader;
private AccountsAdapter accountsAdapter; private AccountsAdapter accountsAdapter;
public AccountSwitcherSheet(@NonNull Activity activity, @Nullable HomeFragment fragment){ public AccountSwitcherSheet(@NonNull Activity activity, @Nullable HomeFragment fragment){
this(activity, fragment, false, false, null); this(activity, fragment, false, false);
} }
public AccountSwitcherSheet(@NonNull Activity activity, @Nullable HomeFragment fragment, boolean externalShare, boolean openInApp, BiConsumer<String, Boolean> onClick){ public AccountSwitcherSheet(@NonNull Activity activity, @Nullable HomeFragment fragment, boolean externalShare, boolean openInApp){
super(activity); super(activity);
this.activity=activity; this.activity=activity;
this.fragment=fragment; this.fragment=fragment;
@@ -123,6 +123,10 @@ public class AccountSwitcherSheet extends BottomSheet{
UiUtils.getThemeColor(activity, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme()); UiUtils.getThemeColor(activity, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());
} }
public void setOnClick(BiConsumer<String, Boolean> onClick) {
this.onClick = onClick;
}
private void confirmLogOut(String accountID){ private void confirmLogOut(String accountID){
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID); AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
new M3AlertDialogBuilder(activity) new M3AlertDialogBuilder(activity)

View File

@@ -56,8 +56,8 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
} }
public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{ public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{
private final TextView reply, boost, favorite, bookmark; private final TextView replies, boosts, favorites;
private final ImageView share; private final View reply, boost, favorite, share, bookmark;
private static final Animation opacityOut, opacityIn; private static final Animation opacityOut, opacityIn;
private View touchingView = null; private View touchingView = null;
@@ -91,22 +91,16 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
public Holder(Activity activity, ViewGroup parent){ public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_footer, parent); super(activity, R.layout.display_item_footer, parent);
reply=findViewById(R.id.reply); replies=findViewById(R.id.reply);
boost=findViewById(R.id.boost); boosts=findViewById(R.id.boost);
favorite=findViewById(R.id.favorite); favorites=findViewById(R.id.favorite);
bookmark=findViewById(R.id.bookmark);
share=findViewById(R.id.share); reply=findViewById(R.id.reply_btn);
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){ boost=findViewById(R.id.boost_btn);
UiUtils.fixCompoundDrawableTintOnAndroid6(reply); favorite=findViewById(R.id.favorite_btn);
UiUtils.fixCompoundDrawableTintOnAndroid6(boost); share=findViewById(R.id.share_btn);
UiUtils.fixCompoundDrawableTintOnAndroid6(favorite); bookmark=findViewById(R.id.bookmark_btn);
UiUtils.fixCompoundDrawableTintOnAndroid6(bookmark);
}
View reply=findViewById(R.id.reply_btn);
View boost=findViewById(R.id.boost_btn);
View favorite=findViewById(R.id.favorite_btn);
View share=findViewById(R.id.share_btn);
View bookmark=findViewById(R.id.bookmark_btn);
reply.setOnTouchListener(this::onButtonTouch); reply.setOnTouchListener(this::onButtonTouch);
reply.setOnClickListener(this::onReplyClick); reply.setOnClickListener(this::onReplyClick);
reply.setOnLongClickListener(this::onReplyLongClick); reply.setOnLongClickListener(this::onReplyLongClick);
@@ -131,12 +125,12 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void onBind(FooterStatusDisplayItem item){ public void onBind(FooterStatusDisplayItem item){
bindButton(reply, item.status.repliesCount); bindButton(replies, item.status.repliesCount);
bindButton(boost, item.status.reblogsCount); bindButton(boosts, item.status.reblogsCount);
bindButton(favorite, item.status.favouritesCount); bindButton(favorites, item.status.favouritesCount);
// in thread view, direct descendant posts display one direct reply to themselves, // in thread view, direct descendant posts display one direct reply to themselves,
// hence in that case displaying whether there is another reply // hence in that case displaying whether there is another reply
int compareTo = item.isMainStatus || !item.hasDescendantNeighbor() ? 0 : 1; int compareTo = item.isMainStatus || !item.hasDescendantNeighbor ? 0 : 1;
reply.setSelected(item.status.repliesCount > compareTo); reply.setSelected(item.status.repliesCount > compareTo);
boost.setSelected(item.status.reblogged); boost.setSelected(item.status.reblogged);
favorite.setSelected(item.status.favourited); favorite.setSelected(item.status.favourited);
@@ -147,7 +141,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
int nextPos = getAbsoluteAdapterPosition() + 1; int nextPos = getAbsoluteAdapterPosition() + 1;
boolean nextIsWarning = item.parentFragment.getDisplayItems().size() > nextPos && boolean nextIsWarning = item.parentFragment.getDisplayItems().size() > nextPos &&
item.parentFragment.getDisplayItems().get(nextPos) instanceof WarningFilteredStatusDisplayItem; item.parentFragment.getDisplayItems().get(nextPos) instanceof WarningFilteredStatusDisplayItem;
boolean condenseBottom = !item.isMainStatus && item.hasDescendantNeighbor() && boolean condenseBottom = !item.isMainStatus && item.hasDescendantNeighbor &&
!nextIsWarning; !nextIsWarning;
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) itemView.getLayoutParams(); ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) itemView.getLayoutParams();
@@ -181,8 +175,9 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
} else if (action == MotionEvent.ACTION_DOWN) { } else if (action == MotionEvent.ACTION_DOWN) {
longClickPerformed = false; longClickPerformed = false;
touchingView = v; touchingView = v;
// 20dp to center in middle of icon, because: (icon width = 24dp) / 2 + (paddingStart = 8dp) // 28dp to center in middle of icon, because:
v.setPivotX(V.dp(20)); // (icon width = 24dp) / 2 + (paddingStart = 8dp) + (paddingHorizontal = 8dp)
v.setPivotX(UiUtils.sp(v.getContext(), 28));
v.animate().scaleX(0.85f).scaleY(0.85f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(75).start(); v.animate().scaleX(0.85f).scaleY(0.85f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(75).start();
if (disabled) return true; if (disabled) return true;
v.postDelayed(longClickRunnable, ViewConfiguration.getLongPressTimeout()); v.postDelayed(longClickRunnable, ViewConfiguration.getLongPressTimeout());
@@ -220,13 +215,13 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
onBoostLongClick(v); onBoostLongClick(v);
return; 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)); AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setReblogged(item.status, !item.status.reblogged, null, r->boostConsumer(v, r));
} }
private void boostConsumer(View v, Status r) { private void boostConsumer(View v, Status r) {
v.startAnimation(opacityIn); v.startAnimation(opacityIn);
bindButton(boost, r.reblogsCount); bindButton(boosts, r.reblogsCount);
} }
private boolean onBoostLongClick(View v){ private boolean onBoostLongClick(View v){
@@ -312,10 +307,10 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
} }
private void onFavoriteClick(View v){ 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->{ AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setFavorited(item.status, !item.status.favourited, r->{
v.startAnimation(opacityIn); v.startAnimation(opacityIn);
bindButton(favorite, r.favouritesCount); bindButton(favorites, r.favouritesCount);
}); });
} }

View File

@@ -73,9 +73,9 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
public void updateVisibility(StatusPrivacy visibility) { public void updateVisibility(StatusPrivacy visibility) {
this.visibility = visibility; this.visibility = visibility;
this.iconEnd = visibility != null ? switch (visibility) { this.iconEnd = visibility != null ? switch (visibility) {
case PUBLIC -> R.drawable.ic_fluent_earth_20_regular; case PUBLIC -> R.drawable.ic_fluent_earth_20sp_regular;
case UNLISTED -> R.drawable.ic_fluent_lock_open_20_regular; case UNLISTED -> R.drawable.ic_fluent_lock_open_20sp_regular;
case PRIVATE -> R.drawable.ic_fluent_lock_closed_20_filled; case PRIVATE -> R.drawable.ic_fluent_lock_closed_20sp_filled;
default -> 0; default -> 0;
} : 0; } : 0;
} }

View File

@@ -47,29 +47,20 @@ public abstract class StatusDisplayItem{
public final BaseStatusListFragment parentFragment; public final BaseStatusListFragment parentFragment;
public boolean inset; public boolean inset;
public int index; public int index;
private ThreadFragment.NeighborAncestryInfo ancestryInfo;
public boolean public boolean
hasDescendantNeighbor = false,
hasAncestoringNeighbor = false,
isMainStatus = true, isMainStatus = true,
isDirectDescendant = false; isDirectDescendant = false;
public boolean hasDescendantNeighbor() {
return Optional.ofNullable(ancestryInfo)
.map(ThreadFragment.NeighborAncestryInfo::hasDescendantNeighbor)
.orElse(false);
}
public boolean hasAncestoringNeighbor() {
return Optional.ofNullable(ancestryInfo)
.map(ThreadFragment.NeighborAncestryInfo::hasAncestoringNeighbor)
.orElse(false);
}
public void setAncestryInfo( public void setAncestryInfo(
ThreadFragment.NeighborAncestryInfo ancestryInfo, boolean hasDescendantNeighbor,
boolean hasAncestoringNeighbor,
boolean isMainStatus, boolean isMainStatus,
boolean isDirectDescendant boolean isDirectDescendant
) { ) {
this.ancestryInfo = ancestryInfo; this.hasDescendantNeighbor = hasDescendantNeighbor;
this.hasAncestoringNeighbor = hasAncestoringNeighbor;
this.isMainStatus = isMainStatus; this.isMainStatus = isMainStatus;
this.isDirectDescendant = isDirectDescendant; this.isDirectDescendant = isDirectDescendant;
} }
@@ -138,7 +129,7 @@ public abstract class StatusDisplayItem{
: fragment.getString(R.string.in_reply_to, account.displayName); : fragment.getString(R.string.in_reply_to, account.displayName);
replyLine = new ReblogOrReplyLineStatusDisplayItem( replyLine = new ReblogOrReplyLineStatusDisplayItem(
parentID, fragment, text, account == null ? List.of() : account.emojis, parentID, fragment, text, account == null ? List.of() : account.emojis,
R.drawable.ic_fluent_arrow_reply_20_filled, null, null, fullText R.drawable.ic_fluent_arrow_reply_20sp_filled, null, null, fullText
); );
} }
@@ -146,7 +137,7 @@ public abstract class StatusDisplayItem{
boolean isOwnPost = AccountSessionManager.getInstance().isSelf(fragment.getAccountID(), status.account); boolean isOwnPost = AccountSessionManager.getInstance().isSelf(fragment.getAccountID(), status.account);
String fullText = fragment.getString(R.string.user_boosted, status.account.displayName); String fullText = fragment.getString(R.string.user_boosted, status.account.displayName);
String text = GlobalUserPreferences.compactReblogReplyLine && replyLine != null ? status.account.displayName : fullText; String text = GlobalUserPreferences.compactReblogReplyLine && replyLine != null ? status.account.displayName : fullText;
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, text, status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20_filled, isOwnPost ? status.visibility : null, i->{ items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, text, status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20sp_filled, isOwnPost ? status.visibility : null, i->{
args.putParcelable("profileAccount", Parcels.wrap(status.account)); args.putParcelable("profileAccount", Parcels.wrap(status.account));
Nav.go(fragment.getActivity(), ProfileFragment.class, args); Nav.go(fragment.getActivity(), ProfileFragment.class, args);
}, fullText)); }, fullText));
@@ -161,7 +152,7 @@ public abstract class StatusDisplayItem{
// post contains a hashtag the user is following // post contains a hashtag the user is following
.ifPresent(hashtag -> items.add(new ReblogOrReplyLineStatusDisplayItem( .ifPresent(hashtag -> items.add(new ReblogOrReplyLineStatusDisplayItem(
parentID, fragment, hashtag.name, List.of(), parentID, fragment, hashtag.name, List.of(),
R.drawable.ic_fluent_number_symbol_20_filled, null, R.drawable.ic_fluent_number_symbol_20sp_filled, null,
i -> { i -> {
args.putString("hashtag", hashtag.name); args.putString("hashtag", hashtag.name);
Nav.go(fragment.getActivity(), HashtagTimelineFragment.class, args); Nav.go(fragment.getActivity(), HashtagTimelineFragment.class, args);

View File

@@ -65,7 +65,6 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
spoilerEmojiHelper.setText(parsedSpoilerText); spoilerEmojiHelper.setText(parsedSpoilerText);
} }
session = AccountSessionManager.getInstance().getAccount(parentFragment.getAccountID()); session = AccountSessionManager.getInstance().getAccount(parentFragment.getAccountID());
UiUtils.loadMaxWidth(parentFragment.getContext());
} }
public void setTranslationShown(boolean translationShown) { public void setTranslationShown(boolean translationShown) {
@@ -238,13 +237,13 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
spaceBelowText.setVisibility(translateVisible ? View.VISIBLE : View.GONE); spaceBelowText.setVisibility(translateVisible ? View.VISIBLE : View.GONE);
// remove additional padding when (transparently padded) translate button is visible // remove additional padding when (transparently padded) translate button is visible
int pos = getAbsoluteAdapterPosition(); int nextPos = getAbsoluteAdapterPosition() + 1;
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), boolean nextIsFooter = item.parentFragment.getDisplayItems().size() > nextPos &&
(translateVisible && item.parentFragment.getDisplayItems().get(nextPos) instanceof FooterStatusDisplayItem;
item.parentFragment.getDisplayItems().size() >= pos + 1 && int bottomPadding = (translateVisible && nextIsFooter) ? 0
item.parentFragment.getDisplayItems().get(pos + 1) instanceof FooterStatusDisplayItem) : nextIsFooter ? V.dp(8)
? 0 : V.dp(12) : V.dp(12);
); itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), bottomPadding);
if (!GlobalUserPreferences.collapseLongPosts) { if (!GlobalUserPreferences.collapseLongPosts) {
textScrollView.setLayoutParams(wrapParams); textScrollView.setLayoutParams(wrapParams);

View File

@@ -17,6 +17,7 @@ import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@@ -37,6 +38,7 @@ import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import android.view.HapticFeedbackConstants; import android.view.HapticFeedbackConstants;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@@ -98,9 +100,9 @@ import org.parceler.Parcels;
import java.io.File; import java.io.File;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.IDN;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
@@ -140,15 +142,11 @@ public class UiUtils {
private static Handler mainHandler = new Handler(Looper.getMainLooper()); private static Handler mainHandler = new Handler(Looper.getMainLooper());
private static final DateTimeFormatter DATE_FORMATTER_SHORT_WITH_YEAR = DateTimeFormatter.ofPattern("d MMM uuuu"), DATE_FORMATTER_SHORT = DateTimeFormatter.ofPattern("d MMM"); private static final DateTimeFormatter DATE_FORMATTER_SHORT_WITH_YEAR = DateTimeFormatter.ofPattern("d MMM uuuu"), DATE_FORMATTER_SHORT = DateTimeFormatter.ofPattern("d MMM");
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT); public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT);
public static int MAX_WIDTH; public static int MAX_WIDTH, SCROLL_TO_TOP_DELTA;
private UiUtils() { private UiUtils() {
} }
public static void loadMaxWidth(Context ctx) {
if (MAX_WIDTH == 0) MAX_WIDTH = (int) ctx.getResources().getDimension(R.dimen.layout_max_width);
}
public static void launchWebBrowser(Context context, String url) { public static void launchWebBrowser(Context context, String url) {
try { try {
if (GlobalUserPreferences.useCustomTabs) { if (GlobalUserPreferences.useCustomTabs) {
@@ -897,6 +895,10 @@ public class UiUtils {
ColorPalette palette = ColorPalette.palettes.get(GlobalUserPreferences.color); ColorPalette palette = ColorPalette.palettes.get(GlobalUserPreferences.color);
if (palette != null) palette.apply(context); if (palette != null) palette.apply(context);
Resources res = context.getResources();
MAX_WIDTH = (int) res.getDimension(R.dimen.layout_max_width);
SCROLL_TO_TOP_DELTA = (int) res.getDimension(R.dimen.scroll_to_top_delta);
} }
public static boolean isDarkTheme() { public static boolean isDarkTheme() {
@@ -905,6 +907,32 @@ public class UiUtils {
return theme == GlobalUserPreferences.ThemePreference.DARK; return theme == GlobalUserPreferences.ThemePreference.DARK;
} }
public static Optional<Pair<String, Optional<String>>> parseFediverseHandle(String maybeFediHandle) {
// https://stackoverflow.com/a/26987741, except i put a + here ... v
String domainRegex = "^(((?!-))(xn--|_)?[a-z0-9-]{0,61}[a-z0-9]\\.)+(xn--)?([a-z0-9][a-z0-9\\-]{0,60}|[a-z0-9-]{1,30}\\.[a-z]{2,})$";
if (maybeFediHandle.toLowerCase().startsWith("mailto:")) {
maybeFediHandle = maybeFediHandle.substring("mailto:".length());
}
List<String> parts = Arrays.stream(maybeFediHandle.split("@"))
.filter(part -> !part.isEmpty())
.collect(Collectors.toList());
if (parts.size() == 0 || !parts.get(0).matches("^[^/\\s]+$")) {
return Optional.empty();
} else if (parts.size() == 2) {
try {
String domain = IDN.toASCII(parts.get(1));
if (!domain.matches(domainRegex)) return Optional.empty();
return Optional.of(Pair.create(parts.get(0), Optional.of(parts.get(1))));
} catch (IllegalArgumentException ignored) {
return Optional.empty();
}
} else if (maybeFediHandle.startsWith("@")) {
return Optional.of(Pair.create(parts.get(0), Optional.empty()));
} else {
return Optional.empty();
}
}
// https://mastodon.foo.bar/@User // https://mastodon.foo.bar/@User
// https://mastodon.foo.bar/@User/43456787654678 // https://mastodon.foo.bar/@User/43456787654678
// https://pleroma.foo.bar/users/User // https://pleroma.foo.bar/users/User
@@ -921,7 +949,7 @@ public class UiUtils {
// https://foo.microblog.pub/o/5b64045effd24f48a27d7059f6cb38f5 // https://foo.microblog.pub/o/5b64045effd24f48a27d7059f6cb38f5
// //
// COPIED FROM https://github.com/tuskyapp/Tusky/blob/develop/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.kt // COPIED FROM https://github.com/tuskyapp/Tusky/blob/develop/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.kt
public static boolean looksLikeMastodonUrl(String urlString) { public static boolean looksLikeFediverseUrl(String urlString) {
URI uri; URI uri;
try { try {
uri = new URI(urlString); uri = new URI(urlString);
@@ -1088,6 +1116,53 @@ public class UiUtils {
}); });
} }
public static boolean acctMatches(String accountID, String acct, String queriedUsername, @Nullable String queriedDomain) {
// check if the username matches
if (!acct.split("@")[0].equalsIgnoreCase(queriedUsername)) return false;
boolean resultOnHomeInstance = !acct.contains("@");
if (resultOnHomeInstance) {
// acct is formatted like 'someone'
// only allow home instance result if query didn't specify a domain,
// or the specified domain does, in fact, match the account session's domain
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
return queriedDomain == null || session.domain.equalsIgnoreCase(queriedDomain);
} else if (queriedDomain == null) {
// accept whatever result we have as there's no queried domain to compare to
return true;
} else {
// acct is formatted like 'someone@somewhere'
return acct.split("@")[1].equalsIgnoreCase(queriedDomain);
}
}
public static void lookupAccountHandle(Context context, String accountID, 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) { public static void lookupURL(Context context, String accountID, String url, boolean launchBrowser, BiConsumer<Class<? extends Fragment>, Bundle> go) {
Uri uri = Uri.parse(url); Uri uri = Uri.parse(url);
List<String> path = uri.getPathSegments(); List<String> path = uri.getPathSegments();
@@ -1114,7 +1189,7 @@ public class UiUtils {
d -> transformDialogForLookup(context, accountID, url, d)) d -> transformDialogForLookup(context, accountID, url, d))
.exec(accountID); .exec(accountID);
return; return;
} else if (looksLikeMastodonUrl(url)) { } else if (looksLikeFediverseUrl(url)) {
new GetSearchResults(url, null, true) new GetSearchResults(url, null, true)
.setCallback(new Callback<>() { .setCallback(new Callback<>() {
@Override @Override
@@ -1315,6 +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. * Wraps a View.OnClickListener to filter multiple clicks in succession.
* Useful for buttons that perform some action that changes their state asynchronously. * Useful for buttons that perform some action that changes their state asynchronously.

View File

@@ -23,7 +23,6 @@ public class ComposeMediaLayout extends ViewGroup{
public ComposeMediaLayout(Context context, AttributeSet attrs, int defStyle){ public ComposeMediaLayout(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle); super(context, attrs, defStyle);
UiUtils.loadMaxWidth(context);
} }
@Override @Override

View File

@@ -3,12 +3,13 @@ package org.joinmastodon.android.ui.views;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
public class MaxWidthFrameLayout extends FrameLayout{ public class MaxWidthFrameLayout extends FrameLayout{
private int maxWidth; private int maxWidth, defaultWidth;
public MaxWidthFrameLayout(Context context){ public MaxWidthFrameLayout(Context context){
this(context, null); this(context, null);
@@ -22,6 +23,7 @@ public class MaxWidthFrameLayout extends FrameLayout{
super(context, attrs, defStyle); super(context, attrs, defStyle);
TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.MaxWidthFrameLayout); TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.MaxWidthFrameLayout);
maxWidth=ta.getDimensionPixelSize(R.styleable.MaxWidthFrameLayout_android_maxWidth, Integer.MAX_VALUE); maxWidth=ta.getDimensionPixelSize(R.styleable.MaxWidthFrameLayout_android_maxWidth, Integer.MAX_VALUE);
defaultWidth=ta.getDimensionPixelSize(R.styleable.MaxWidthFrameLayout_defaultWidth, -1);
ta.recycle(); ta.recycle();
} }
@@ -33,10 +35,19 @@ public class MaxWidthFrameLayout extends FrameLayout{
this.maxWidth=maxWidth; this.maxWidth=maxWidth;
} }
public int getDefaultWidth() {
return defaultWidth;
}
public void setDefaultWidth(int defaultWidth) {
this.defaultWidth = defaultWidth;
}
@Override @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
if(MeasureSpec.getSize(widthMeasureSpec)>maxWidth){ if(MeasureSpec.getSize(widthMeasureSpec)>maxWidth){
widthMeasureSpec=maxWidth | MeasureSpec.getMode(widthMeasureSpec); int width = defaultWidth >= 0 ? defaultWidth : maxWidth;
widthMeasureSpec=width | MeasureSpec.getMode(widthMeasureSpec);
} }
super.onMeasure(widthMeasureSpec, heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} }

View File

@@ -27,7 +27,6 @@ public class MediaGridLayout extends ViewGroup{
public MediaGridLayout(Context context, AttributeSet attrs, int defStyle){ public MediaGridLayout(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle); super(context, attrs, defStyle);
UiUtils.loadMaxWidth(context);
} }
@Override @Override

View File

@@ -2,5 +2,5 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?android:colorAccent" android:state_selected="true"/> <item android:color="?android:colorAccent" android:state_selected="true"/>
<item android:color="?android:textColorSecondary" android:state_enabled="true"/> <item android:color="?android:textColorSecondary" android:state_enabled="true"/>
<item android:color="?android:textColorSecondary" android:alpha="0.3"/> <item android:color="?colorIconDisabled" />
</selector> </selector>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -1,120 +1,161 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <org.joinmastodon.android.ui.views.MaxWidthFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal" xmlns:app="http://schemas.android.com/apk/res-auto"
android:maxWidth="600sp"
app:defaultWidth="450sp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="wrap_content">
android:paddingHorizontal="16dp">
<FrameLayout <LinearLayout
android:id="@+id/reply_btn" android:orientation="horizontal"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:minWidth="56dp"> android:paddingHorizontal="11sp">
<TextView <!-- avatar width (46sp) / 2 - button width (24sp) / 2 -->
android:id="@+id/reply"
<FrameLayout
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/reply_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingVertical="12dp">
<ImageView
android:layout_width="24sp"
android:layout_height="24sp"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:duplicateParentState="true"
android:src="@drawable/ic_fluent_chat_multiple_24_selector_text"
android:tint="?android:textColorSecondary"
android:gravity="center_vertical" />
<TextView
android:id="@+id/reply"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:paddingStart="8dp"
android:minWidth="16dp"
android:gravity="center_vertical"
android:textAppearance="@style/m3_label_large"
android:maxLines="1"
android:ellipsize="end"
tools:text="123"
tools:ignore="RtlSymmetry" />
</LinearLayout>
</FrameLayout>
<FrameLayout
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/boost_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:foregroundTint="@color/boost_icon"
android:paddingVertical="12dp">
<ImageView
android:layout_width="24sp"
android:layout_height="24sp"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:duplicateParentState="true"
android:src="@drawable/ic_boost"
android:tint="@color/boost_icon"
android:gravity="center_vertical" />
<TextView
android:id="@+id/boost"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:paddingStart="8dp"
android:minWidth="16dp"
android:textColor="@color/boost_icon"
android:gravity="center_vertical"
android:textAppearance="@style/m3_label_large"
android:maxLines="1"
android:ellipsize="end"
tools:text="123"
tools:ignore="RtlSymmetry" />
</LinearLayout>
</FrameLayout>
<FrameLayout
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/favorite_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingVertical="12dp">
<ImageView
android:layout_width="24sp"
android:layout_height="24sp"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:duplicateParentState="true"
android:src="@drawable/ic_fluent_star_24_selector"
android:tint="@color/favorite_icon"
android:gravity="center_vertical" />
<TextView
android:id="@+id/favorite"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:paddingStart="8dp"
android:minWidth="16dp"
android:textColor="@color/favorite_icon"
android:gravity="center_vertical"
android:textAppearance="@style/m3_label_large"
android:maxLines="1"
android:ellipsize="end"
tools:text="123"
tools:ignore="RtlSymmetry" />
</LinearLayout>
</FrameLayout>
<FrameLayout
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/bookmark_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingVertical="12dp">
<ImageView
android:id="@+id/bookmark"
android:layout_width="24sp"
android:layout_height="24sp"
android:layout_gravity="center_vertical"
android:layout_marginHorizontal="16dp"
android:src="@drawable/ic_fluent_bookmark_24_selector"
android:tint="@color/bookmark_icon"
android:gravity="center_vertical"
android:textAppearance="@style/m3_label_large" />
</FrameLayout>
</FrameLayout>
<FrameLayout
android:id="@+id/share_btn"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center_vertical" android:paddingVertical="12dp">
android:drawableStart="@drawable/ic_fluent_chat_multiple_24_selector_text" <ImageView
android:drawablePadding="8dp" android:id="@+id/share"
android:paddingHorizontal="8dp" android:layout_width="24sp"
android:drawableTint="?android:textColorSecondary" android:layout_height="24sp"
android:gravity="center_vertical" android:layout_gravity="center_vertical"
android:textAppearance="@style/m3_label_large" android:layout_marginHorizontal="16dp"
tools:text="123"/> android:src="@drawable/ic_fluent_share_24_regular"
</FrameLayout> android:tint="?android:textColorSecondary"
android:gravity="center_vertical"/>
</FrameLayout>
<Space </LinearLayout>
android:layout_width="0px" </org.joinmastodon.android.ui.views.MaxWidthFrameLayout>
android:layout_height="1px"
android:layout_weight="1"/>
<FrameLayout
android:id="@+id/boost_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:minWidth="56dp">
<TextView
android:id="@+id/boost"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:drawableStart="@drawable/ic_boost"
android:drawablePadding="8dp"
android:paddingHorizontal="8dp"
android:drawableTint="@color/boost_icon"
android:textColor="@color/boost_icon"
android:gravity="center_vertical"
android:textAppearance="@style/m3_label_large"
tools:text="123"/>
</FrameLayout>
<Space
android:layout_width="0px"
android:layout_height="1px"
android:layout_weight="1"/>
<FrameLayout
android:id="@+id/favorite_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:minWidth="56dp">
<TextView
android:id="@+id/favorite"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:drawableStart="@drawable/ic_fluent_star_24_selector"
android:drawablePadding="8dp"
android:paddingHorizontal="8dp"
android:drawableTint="@color/favorite_icon"
android:textColor="@color/favorite_icon"
android:gravity="center_vertical"
android:textAppearance="@style/m3_label_large"
tools:text="123"/>
</FrameLayout>
<Space
android:layout_width="0px"
android:layout_height="1px"
android:layout_weight="1"/>
<FrameLayout
android:id="@+id/bookmark_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:minWidth="56dp">
<TextView
android:id="@+id/bookmark"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:drawableStart="@drawable/ic_fluent_bookmark_24_selector"
android:paddingHorizontal="8dp"
android:drawableTint="@color/bookmark_icon"
android:gravity="center_vertical"
android:textAppearance="@style/m3_label_large" />
</FrameLayout>
<Space
android:layout_width="0px"
android:layout_height="1px"
android:layout_weight="1"/>
<FrameLayout
android:id="@+id/share_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<ImageView
android:id="@+id/share"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_fluent_share_24_regular"
android:paddingHorizontal="8dp"
android:tint="?android:textColorSecondary"
android:gravity="center_vertical"/>
</FrameLayout>
</LinearLayout>

View File

@@ -14,7 +14,7 @@
android:paddingTop="16dp" android:paddingTop="16dp"
android:paddingBottom="6dp" android:paddingBottom="6dp"
android:textAppearance="@style/m3_title_small" android:textAppearance="@style/m3_title_small"
android:drawableStart="@drawable/ic_fluent_arrow_reply_20_filled" android:drawableStart="@drawable/ic_fluent_arrow_reply_20sp_filled"
android:drawableTint="?android:textColorSecondary" android:drawableTint="?android:textColorSecondary"
android:drawablePadding="6dp" android:drawablePadding="6dp"
android:singleLine="true" android:singleLine="true"

View File

@@ -20,11 +20,20 @@
<string name="share_toot_title">শেয়ার করুন</string> <string name="share_toot_title">শেয়ার করুন</string>
<string name="settings">সেটিংস</string> <string name="settings">সেটিংস</string>
<string name="cancel">বাতিল করুন</string> <string name="cancel">বাতিল করুন</string>
<plurals name="followers">
<item quantity="one">জন ফলোয়ার</item>
<item quantity="other">জন ফলোয়ারস</item>
</plurals>
<plurals name="posts"> <plurals name="posts">
<item quantity="one">পোস্ট</item> <item quantity="one">পোস্ট</item>
<item quantity="other">পোস্টগুলো</item> <item quantity="other">পোস্টগুলো</item>
</plurals> </plurals>
<string name="posts">পোস্টগুলো</string> <string name="posts">পোস্টগুলো</string>
<string name="media">মিডিয়া</string>
<string name="button_follow">ফলো করুন</string>
<string name="button_following">ফলো করছেন</string>
<string name="edit_profile">প্রোফাইল সংশোধন করুন</string>
<string name="mention_user">%s -কে পিং করুন</string>
<string name="share_user">%s -কে শেয়ার করুন</string> <string name="share_user">%s -কে শেয়ার করুন</string>
<string name="mute_user">%s -কে মিউট করুন</string> <string name="mute_user">%s -কে মিউট করুন</string>
<string name="unmute_user">%s -কে আনমিউট করুন</string> <string name="unmute_user">%s -কে আনমিউট করুন</string>
@@ -53,6 +62,22 @@
<item quantity="one">%d দিন</item> <item quantity="one">%d দিন</item>
<item quantity="other">%d দিন</item> <item quantity="other">%d দিন</item>
</plurals> </plurals>
<plurals name="x_seconds_left">
<item quantity="one">%d সেকেন্ড বাকি</item>
<item quantity="other">%d সেকেন্ড বাকি</item>
</plurals>
<plurals name="x_minutes_left">
<item quantity="one">%d মিনিট বাকি</item>
<item quantity="other">%d মিনিট বাকি</item>
</plurals>
<plurals name="x_hours_left">
<item quantity="one">%d ঘণ্টা বাকি</item>
<item quantity="other">%d ঘণ্টা বাকি</item>
</plurals>
<plurals name="x_days_left">
<item quantity="one">%d দিন বাকি</item>
<item quantity="other">%d দিন বাকি</item>
</plurals>
<string name="poll_closed">বন্ধ</string> <string name="poll_closed">বন্ধ</string>
<string name="confirm_mute_title">অ্যাকাউন্টটি মিউট করুন</string> <string name="confirm_mute_title">অ্যাকাউন্টটি মিউট করুন</string>
<string name="do_mute">মিউট করুন</string> <string name="do_mute">মিউট করুন</string>
@@ -89,6 +114,17 @@
<item quantity="other">%d jon ব্যক্তিরা বলছেন</item> <item quantity="other">%d jon ব্যক্তিরা বলছেন</item>
</plurals> </plurals>
<string name="sending_report">রিপোর্ট পাঠানো হচ্ছে…</string> <string name="sending_report">রিপোর্ট পাঠানো হচ্ছে…</string>
<string name="report_sent_title">রিপোর্ট করার জন্য আপনাকে ধন্যবাদ, আমরা এটি শীঘ্রই দেখব.</string>
<string name="report_sent_subtitle">আমরা যতক্ষণে আপনার রিপোর্ট পুনর্বিবেচনা করছি, আপনি %s এর বিরুদ্ধে ব্যবস্থা নিতে পারেন.</string>
<string name="back">ফিরে যান</string>
<string name="search_communities">সার্ভারের নাম বা লিঙ্ক</string>
<string name="instance_rules_title">সার্ভারের নিয়মাবলী</string>
<string name="signup_title">অ্যাকাউন্ট তৈরি করুন</string>
<string name="display_name">নাম</string>
<string name="username">ইউজারনেম</string>
<string name="email">ই-মেইল</string>
<string name="password">পাসওয়ার্ড</string>
<string name="confirm_password">পাসওয়ার্ড নিশ্চিত করুন</string>
<!-- %s is the email address --> <!-- %s is the email address -->
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators --> <!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<!-- %s is version like 1.2.3 --> <!-- %s is version like 1.2.3 -->

View File

@@ -166,7 +166,7 @@
<string name="report_sent_subtitle">Während wir den Vorfall überprüfen, kannst du gegen %s weitere Maßnahmen ergreifen.</string> <string name="report_sent_subtitle">Während wir den Vorfall überprüfen, kannst du gegen %s weitere Maßnahmen ergreifen.</string>
<string name="unfollow_user">%s entfolgen</string> <string name="unfollow_user">%s entfolgen</string>
<string name="unfollow">Entfolgen</string> <string name="unfollow">Entfolgen</string>
<string name="mute_user_explain">Du wirst die eigenen und geteilten Beiträge des Kontos nicht mehr sehen können. Dass du das Profil stummgeschaltet hast, erfährt die Person nicht.</string> <string name="mute_user_explain">Du wirst deren (geteilte) Beiträge auf deiner Startseite nicht mehr sehen können. Sie werden nicht erfahren, dass sie stummgeschaltet sind.</string>
<string name="block_user_explain">Dir wird es nicht länger möglich sein, die Beiträge dieses Konto zu sehen. Das blockierte Profil wird nicht mehr in der Lage sein, deine Beiträge zu sehen oder dir zu folgen. Die Person hinter dem Konto wird mitbekommen, dass du ihr Konto gesperrt hast.</string> <string name="block_user_explain">Dir wird es nicht länger möglich sein, die Beiträge dieses Konto zu sehen. Das blockierte Profil wird nicht mehr in der Lage sein, deine Beiträge zu sehen oder dir zu folgen. Die Person hinter dem Konto wird mitbekommen, dass du ihr Konto gesperrt hast.</string>
<string name="report_personal_title">Möchtest du das nicht mehr sehen?</string> <string name="report_personal_title">Möchtest du das nicht mehr sehen?</string>
<string name="report_personal_subtitle">Wenn du etwas auf Mastodon siehst, das dir nicht gefällt, kannst du die Person aus deinem Umfeld entfernen.</string> <string name="report_personal_subtitle">Wenn du etwas auf Mastodon siehst, das dir nicht gefällt, kannst du die Person aus deinem Umfeld entfernen.</string>
@@ -175,7 +175,7 @@
<string name="instance_catalog_subtitle">Wähle einen Server basierend auf deinen Interessen oder deiner Region oder einfach einen allgemeinen. Du kannst trotzdem mit jedem interagieren, egal auf welchem Server.</string> <string name="instance_catalog_subtitle">Wähle einen Server basierend auf deinen Interessen oder deiner Region oder einfach einen allgemeinen. Du kannst trotzdem mit jedem interagieren, egal auf welchem Server.</string>
<string name="search_communities">Servername oder -adresse</string> <string name="search_communities">Servername oder -adresse</string>
<string name="instance_rules_title">Server-Regeln</string> <string name="instance_rules_title">Server-Regeln</string>
<string name="instance_rules_subtitle">Mit dem Fortfahren erklärst du dich damit einverstanden, die folgenden Regeln zu befolgen, die von den %s-Moderatoren aufgestellt und umgesetzt werden.</string> <string name="instance_rules_subtitle">Solltest du fortfahren, erklärst du dich mit den Serverregeln, die die Moderator*innen von %s aufgestellt haben und durchsetzen werden, einverstanden.</string>
<string name="signup_title">Konto erstellen</string> <string name="signup_title">Konto erstellen</string>
<string name="edit_photo">bearbeiten</string> <string name="edit_photo">bearbeiten</string>
<string name="display_name">Name</string> <string name="display_name">Name</string>
@@ -200,7 +200,7 @@
<string name="confirm_email_title">Überprüfe deinen Posteingang</string> <string name="confirm_email_title">Überprüfe deinen Posteingang</string>
<!-- %s is the email address --> <!-- %s is the email address -->
<string name="confirm_email_subtitle">Klicke auf den Link, den wir dir geschickt haben, um %s zu bestätigen. Wir warten hier auf dich.</string> <string name="confirm_email_subtitle">Klicke auf den Link, den wir dir geschickt haben, um %s zu bestätigen. Wir warten hier auf dich.</string>
<string name="confirm_email_didnt_get">Kein Link erhalten?</string> <string name="confirm_email_didnt_get">Keinen Link erhalten?</string>
<string name="resend">Erneut abschicken</string> <string name="resend">Erneut abschicken</string>
<string name="open_email_app">E-Mail-App öffnen</string> <string name="open_email_app">E-Mail-App öffnen</string>
<string name="resent_email">Bestätigung per E-Mail zugeschickt</string> <string name="resent_email">Bestätigung per E-Mail zugeschickt</string>

View File

@@ -274,4 +274,22 @@
<string name="sk_settings_confirm_before_reblog">Vor dem Teilen bestätigen</string> <string name="sk_settings_confirm_before_reblog">Vor dem Teilen bestätigen</string>
<string name="sk_reacted">hat reagiert</string> <string name="sk_reacted">hat reagiert</string>
<string name="sk_reacted_with">hat mit %s reagiert</string> <string name="sk_reacted_with">hat mit %s reagiert</string>
<string name="sk_external_share_title">Mit Konto teilen</string>
<string name="sk_external_share_or_open_title">Mit Konto teilen oder öffnen</string>
<string name="sk_timeline_bubble">Bubble</string>
<string name="sk_settings_default_content_type_explanation">Vorausgewählter Inhaltstyp für neue Beiträge überschreibt den Wert, der unter „Einstellungen für Beiträge“ gesetzt ist.</string>
<string name="sk_open_in_app_failed">Konnte nicht in der App öffnen</string>
<string name="sk_content_type_unspecified">Nicht angegeben</string>
<string name="sk_content_type_plain">Nur Text</string>
<string name="sk_content_type_html">HTML</string>
<string name="sk_content_type_markdown">Markdown</string>
<string name="sk_content_type_bbcode">BBCode</string>
<string name="sk_content_type_mfm">MFM</string>
<string name="sk_content_type">Inhaltstyp</string>
<string name="sk_bubble_timeline_info_banner">Das sind die neuesten Beiträge aus dem Netzwerk, das deine Instanz-Admins kuratiert haben.</string>
<string name="sk_settings_content_types">Formatierung aktivieren</string>
<string name="sk_settings_default_content_type">Standard-Inhaltstyp</string>
<string name="sk_instance_info_unavailable">Informationen zur Instanz momentan nicht verfügbar</string>
<string name="sk_open_in_app">In App öffnen</string>
<string name="sk_settings_content_types_explanation">Dadurch lässt beim Erstellen von Beiträgen ein Inhaltstyp wie Markdown angeben. Nicht alle Instanzen unterstützen das.</string>
</resources> </resources>

View File

@@ -286,4 +286,10 @@
<string name="sk_settings_default_content_type">Contenido por defecto</string> <string name="sk_settings_default_content_type">Contenido por defecto</string>
<string name="sk_settings_content_types_explanation">Permite establecer un tipo de contenido como Markdown al crear una entrada. Ten en cuenta que no todas las instancias lo admiten.</string> <string name="sk_settings_content_types_explanation">Permite establecer un tipo de contenido como Markdown al crear una entrada. Ten en cuenta que no todas las instancias lo admiten.</string>
<string name="sk_settings_default_content_type_explanation">Permite preseleccionar un tipo de contenido al crear nuevas entradas, anulando el valor establecido en \"Preferencias de publicación\".</string> <string name="sk_settings_default_content_type_explanation">Permite preseleccionar un tipo de contenido al crear nuevas entradas, anulando el valor establecido en \"Preferencias de publicación\".</string>
<string name="sk_bubble_timeline_info_banner">Estas son las publicaciones más recientes de la gente en tu servidor de Akkoma.</string>
<string name="sk_timeline_bubble">Burbuja</string>
<string name="sk_instance_info_unavailable">Información de la instancia temporalmente no disponible</string>
<string name="sk_external_share_or_open_title">Compartir o abrir con una cuenta</string>
<string name="sk_open_in_app">Abrir en la app</string>
<string name="sk_external_share_title">Compartir con una cuenta</string>
</resources> </resources>

View File

@@ -90,7 +90,7 @@
<string name="sk_loading_fediverse_resource_title">Buscando no Fediverso</string> <string name="sk_loading_fediverse_resource_title">Buscando no Fediverso</string>
<string name="sk_reblog_with_visibility">Impulsar con visibilidade</string> <string name="sk_reblog_with_visibility">Impulsar con visibilidade</string>
<string name="sk_quote_post">Publicar acerca disto</string> <string name="sk_quote_post">Publicar acerca disto</string>
<string name="sk_undo_reblog">Desfacer o impulso</string> <string name="sk_undo_reblog">Desfacer impulso</string>
<string name="sk_copy_link_to_post">Copiar ligazón á publicación</string> <string name="sk_copy_link_to_post">Copiar ligazón á publicación</string>
<string name="sk_loading_resource_on_instance_title">Buscando en %s</string> <string name="sk_loading_resource_on_instance_title">Buscando en %s</string>
<string name="sk_open_with_account">Abrir con outra conta</string> <string name="sk_open_with_account">Abrir con outra conta</string>
@@ -286,4 +286,10 @@
<string name="sk_content_type">Tipo de contido</string> <string name="sk_content_type">Tipo de contido</string>
<string name="sk_content_type_markdown">Markdown</string> <string name="sk_content_type_markdown">Markdown</string>
<string name="sk_settings_content_types_explanation">Permite configurar un tipo de contido como Markdown ao crear unha publicación. Teña en conta que non tódalas instancias soportan isto.</string> <string name="sk_settings_content_types_explanation">Permite configurar un tipo de contido como Markdown ao crear unha publicación. Teña en conta que non tódalas instancias soportan isto.</string>
<string name="sk_bubble_timeline_info_banner">Estas son as publicacións máis recentes da xente na burbulla do seu servidor Akkoma.</string>
<string name="sk_timeline_bubble">Burbulla</string>
<string name="sk_instance_info_unavailable">Información da instancia temporalmente non dispoñible</string>
<string name="sk_open_in_app">Abrir na aplicación</string>
<string name="sk_external_share_title">Compartir coa conta</string>
<string name="sk_external_share_or_open_title">Compartir ou abrir coa conta</string>
</resources> </resources>

View File

@@ -274,4 +274,15 @@
<string name="sk_settings_confirm_before_reblog">Potwierdź przed podbiciem</string> <string name="sk_settings_confirm_before_reblog">Potwierdź przed podbiciem</string>
<string name="sk_reacted_with">zareagował(a) z %s</string> <string name="sk_reacted_with">zareagował(a) z %s</string>
<string name="sk_reacted">zareagował(a)</string> <string name="sk_reacted">zareagował(a)</string>
<string name="sk_settings_default_content_type">Domyślny rodzaj treści</string>
<string name="sk_instance_info_unavailable">Informacje o instancji są tymczasowo niedostępne</string>
<string name="sk_content_type_html">HTML</string>
<string name="sk_content_type_bbcode">BBCode</string>
<string name="sk_content_type_mfm">MFM</string>
<string name="sk_content_type">Rodzaj treści</string>
<string name="sk_content_type_unspecified">Nie określono</string>
<string name="sk_content_type_plain">Czysty tekst</string>
<string name="sk_settings_content_types">Włącz formatowanie wpisu</string>
<string name="sk_open_in_app">Otwórz w aplikacji</string>
<string name="sk_external_share_title">Udostępnij z kontem</string>
</resources> </resources>

View File

@@ -21,6 +21,7 @@
<attr name="colorAccentLightest" format="color"/> <attr name="colorAccentLightest" format="color"/>
<attr name="profileHeaderBackground" format="color"/> <attr name="profileHeaderBackground" format="color"/>
<attr name="toolbarBackground" format="color"/> <attr name="toolbarBackground" format="color"/>
<attr name="colorIconDisabled" format="color"/>
<attr name="colorButtonBackgroundPrimaryDarkOnLight" format="color"/> <attr name="colorButtonBackgroundPrimaryDarkOnLight" format="color"/>
<attr name="colorButtonBackgroundPrimaryDarkOnLightDisabled" format="color"/> <attr name="colorButtonBackgroundPrimaryDarkOnLightDisabled" format="color"/>
@@ -73,6 +74,7 @@
<declare-styleable name="MaxWidthFrameLayout"> <declare-styleable name="MaxWidthFrameLayout">
<attr name="android:maxWidth" format="dimension"/> <attr name="android:maxWidth" format="dimension"/>
<attr name="defaultWidth" format="dimension" />
</declare-styleable> </declare-styleable>
<declare-styleable name="FloatingHintEditTextLayout"> <declare-styleable name="FloatingHintEditTextLayout">

View File

@@ -3,4 +3,5 @@
<dimen name="text_max_height">220dp</dimen> <dimen name="text_max_height">220dp</dimen>
<dimen name="text_collapsed_height">145dp</dimen> <dimen name="text_collapsed_height">145dp</dimen>
<dimen name="layout_max_width">450dp</dimen> <dimen name="layout_max_width">450dp</dimen>
<dimen name="scroll_to_top_delta">300dp</dimen>
</resources> </resources>

View File

@@ -30,7 +30,7 @@
<string name="sk_user_post_notifications_off">Turned off post notifications for %s</string> <string name="sk_user_post_notifications_off">Turned off post notifications for %s</string>
<string name="sk_federated_timeline">Federation</string> <string name="sk_federated_timeline">Federation</string>
<string name="sk_federated_timeline_info_banner">These are the most recent posts by the people in your federation.</string> <string name="sk_federated_timeline_info_banner">These are the most recent posts by the people in your federation.</string>
<string name="sk_bubble_timeline_info_banner">These are the most recent posts by the people in your Akkoma server\'s bubble.</string> <string name="sk_bubble_timeline_info_banner">These are the most recent posts from the network curated by your instance admins.</string>
<string name="sk_update_available">Megalodon %s is ready to download.</string> <string name="sk_update_available">Megalodon %s is ready to download.</string>
<string name="sk_update_ready">Megalodon %s is downloaded and ready to install.</string> <string name="sk_update_ready">Megalodon %s is downloaded and ready to install.</string>
<string name="sk_check_for_update">Check for update</string> <string name="sk_check_for_update">Check for update</string>
@@ -290,6 +290,7 @@
<string name="sk_settings_default_content_type_explanation">This lets you have a content type be pre-selected when creating new posts, overriding the value set in “Posting preferences”.</string> <string name="sk_settings_default_content_type_explanation">This lets you have a content type be pre-selected when creating new posts, overriding the value set in “Posting preferences”.</string>
<string name="sk_instance_info_unavailable">Instance info temporarily unavailable</string> <string name="sk_instance_info_unavailable">Instance info temporarily unavailable</string>
<string name="sk_open_in_app">Open in app</string> <string name="sk_open_in_app">Open in app</string>
<string name="sk_open_in_app_failed">Could not open in app</string>
<string name="sk_external_share_title">Share with account</string> <string name="sk_external_share_title">Share with account</string>
<string name="sk_external_share_or_open_title">Share or open with account</string> <string name="sk_external_share_or_open_title">Share or open with account</string>
</resources> </resources>

View File

@@ -28,6 +28,7 @@
<item name="android:colorBackground">?colorGray100</item> <item name="android:colorBackground">?colorGray100</item>
<item name="android:textColorPrimary">?colorGray800</item> <item name="android:textColorPrimary">?colorGray800</item>
<item name="android:textColorSecondary">?colorGray500</item> <item name="android:textColorSecondary">?colorGray500</item>
<item name="colorIconDisabled">?colorGray300</item>
<item name="colorButtonText">?colorGray50</item> <item name="colorButtonText">?colorGray50</item>
<item name="colorSecondary">#E9EDF2</item> <item name="colorSecondary">#E9EDF2</item>
<item name="colorBackgroundLight">?colorGray50</item> <item name="colorBackgroundLight">?colorGray50</item>
@@ -127,6 +128,7 @@
<item name="android:colorBackground">?colorGray700</item> <item name="android:colorBackground">?colorGray700</item>
<item name="android:textColorPrimary">?colorGray50</item> <item name="android:textColorPrimary">?colorGray50</item>
<item name="android:textColorSecondary">?colorGray400</item> <item name="android:textColorSecondary">?colorGray400</item>
<item name="colorIconDisabled">?colorGray500</item>
<item name="colorButtonText">?colorGray800</item> <item name="colorButtonText">?colorGray800</item>
<item name="colorSecondary">#E9EDF2</item> <item name="colorSecondary">#E9EDF2</item>
<item name="colorBackgroundLight">?colorGray700</item> <item name="colorBackgroundLight">?colorGray700</item>