Compare commits
11 Commits
v1.2.3+for
...
v1.2.3+for
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfcff1e19f | ||
|
|
f373e7df3e | ||
|
|
3985de5b14 | ||
|
|
e175a721d4 | ||
|
|
d9784ebc31 | ||
|
|
f241092277 | ||
|
|
0702703d78 | ||
|
|
2c4504bad3 | ||
|
|
798a43906f | ||
|
|
41cb0f2e09 | ||
|
|
e12c0fb81f |
@@ -15,8 +15,8 @@ android {
|
||||
applicationId "org.joinmastodon.android.sk"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 85
|
||||
versionName "1.2.3+fork.85"
|
||||
versionCode 86
|
||||
versionName "1.2.3+fork.86"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusContext;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ThreadFragmentTest {
|
||||
|
||||
private Status fakeStatus(String id, String inReplyTo) {
|
||||
Status status = Status.ofFake(id, null, null);
|
||||
status.inReplyToId = inReplyTo;
|
||||
return status;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void countAncestryLevels() {
|
||||
StatusContext context = new StatusContext();
|
||||
context.ancestors = List.of(
|
||||
fakeStatus("oldest ancestor", null),
|
||||
fakeStatus("younger ancestor", "oldest ancestor")
|
||||
);
|
||||
context.descendants = List.of(
|
||||
fakeStatus("first reply", "main status"),
|
||||
fakeStatus("reply to first reply", "first reply"),
|
||||
fakeStatus("third level reply", "reply to first reply"),
|
||||
fakeStatus("another reply", "main status")
|
||||
);
|
||||
List<Pair<String, Integer>> actual =
|
||||
ThreadFragment.countAncestryLevels("main status", context);
|
||||
|
||||
List<Pair<String, Integer>> expected = List.of(
|
||||
Pair.create("oldest ancestor", -2),
|
||||
Pair.create("younger ancestor", -1),
|
||||
Pair.create("main status", 0),
|
||||
Pair.create("first reply", 1),
|
||||
Pair.create("reply to first reply", 2),
|
||||
Pair.create("third level reply", 3),
|
||||
Pair.create("another reply", 1)
|
||||
);
|
||||
assertEquals(
|
||||
"status ids are in the right order",
|
||||
expected.stream().map(p -> p.first).collect(Collectors.toList()),
|
||||
actual.stream().map(p -> p.first).collect(Collectors.toList())
|
||||
);
|
||||
assertEquals(
|
||||
"counted levels match",
|
||||
expected.stream().map(p -> p.second).collect(Collectors.toList()),
|
||||
actual.stream().map(p -> p.second).collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sortStatusContext() {
|
||||
StatusContext context = new StatusContext();
|
||||
context.ancestors = List.of(
|
||||
fakeStatus("younger ancestor", "oldest ancestor"),
|
||||
fakeStatus("oldest ancestor", null)
|
||||
);
|
||||
context.descendants = List.of(
|
||||
fakeStatus("reply to first reply", "first reply"),
|
||||
fakeStatus("third level reply", "reply to first reply"),
|
||||
fakeStatus("first reply", "main status"),
|
||||
fakeStatus("another reply", "main status")
|
||||
);
|
||||
|
||||
ThreadFragment.sortStatusContext(
|
||||
fakeStatus("main status", "younger ancestor"),
|
||||
context
|
||||
);
|
||||
List<Status> expectedAncestors = List.of(
|
||||
fakeStatus("oldest ancestor", null),
|
||||
fakeStatus("younger ancestor", "oldest ancestor")
|
||||
);
|
||||
List<Status> expectedDescendants = List.of(
|
||||
fakeStatus("first reply", "main status"),
|
||||
fakeStatus("reply to first reply", "first reply"),
|
||||
fakeStatus("third level reply", "reply to first reply"),
|
||||
fakeStatus("another reply", "main status")
|
||||
);
|
||||
|
||||
// TODO: ??? i have no idea how this code works. it certainly doesn't return what i'd expect
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
@@ -28,8 +30,6 @@ import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.FragmentStackActivity;
|
||||
|
||||
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
|
||||
private Fragment currentFragment;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState){
|
||||
UiUtils.setUserPreferredTheme(this);
|
||||
@@ -200,15 +200,20 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showFragment(Fragment fragment) {
|
||||
super.showFragment(fragment);
|
||||
this.currentFragment = fragment;
|
||||
public Fragment getCurrentFragment() {
|
||||
for (int i = fragmentContainers.size() - 1; i >= 0; i--) {
|
||||
FrameLayout fl = fragmentContainers.get(i);
|
||||
if (fl.getVisibility() == View.VISIBLE) {
|
||||
return getFragmentManager().findFragmentById(fl.getId());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent assistContent) {
|
||||
super.onProvideAssistContent(assistContent);
|
||||
callFragmentToProvideAssistContent(currentFragment, assistContent);
|
||||
Fragment fragment = getCurrentFragment();
|
||||
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ public class CacheController{
|
||||
}
|
||||
}
|
||||
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(accountSession.domain);
|
||||
new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), instance.isPleroma())
|
||||
new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), instance.isAkkoma())
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Notification> result){
|
||||
|
||||
@@ -99,7 +99,7 @@ public class AccountSession{
|
||||
public Uri getInstanceUri() {
|
||||
return new Uri.Builder()
|
||||
.scheme("https")
|
||||
.authority(domain)
|
||||
.authority(getInstance().map(i -> i.normalizedUri).orElse(domain))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
||||
@@ -132,7 +133,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
displayItems.clear();
|
||||
}
|
||||
|
||||
protected void prependItems(List<T> items, boolean notify){
|
||||
protected int prependItems(List<T> items, boolean notify){
|
||||
data.addAll(0, items);
|
||||
int offset=0;
|
||||
for(T s:items){
|
||||
@@ -145,6 +146,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
}
|
||||
if(notify)
|
||||
adapter.notifyItemRangeInserted(0, offset);
|
||||
return offset;
|
||||
}
|
||||
|
||||
protected String getMaxID(){
|
||||
@@ -355,7 +357,12 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
outRect.left=Math.min(outRect.left, tmpRect.left);
|
||||
outRect.top=Math.min(outRect.top, tmpRect.top);
|
||||
outRect.right=Math.max(outRect.right, tmpRect.right);
|
||||
outRect.bottom=Math.max(outRect.bottom, tmpRect.bottom);
|
||||
int bottom = tmpRect.bottom;
|
||||
if (holder instanceof FooterStatusDisplayItem.Holder fh
|
||||
&& fh.getItem().hasDescendantSibling) {
|
||||
bottom += V.dp(8);
|
||||
}
|
||||
outRect.bottom=Math.max(outRect.bottom, bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -772,6 +779,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
RecyclerView.ViewHolder siblingHolder=parent.getChildViewHolder(bottomSibling);
|
||||
if(holder instanceof StatusDisplayItem.Holder<?> ih && siblingHolder instanceof StatusDisplayItem.Holder<?> sh
|
||||
&& (!ih.getItemID().equals(sh.getItemID()) || sh instanceof ExtendedFooterStatusDisplayItem.Holder) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP){
|
||||
if (ih.getItem().descendantLevel != 0 && ih.getItem().hasDescendantSibling) continue;
|
||||
drawDivider(child, bottomSibling, holder, siblingHolder, parent, c, dividerPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1084,7 +1084,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
req.status=text;
|
||||
req.localOnly=localOnly;
|
||||
req.visibility=localOnly && instance.isPleroma() ? StatusPrivacy.LOCAL : statusVisibility;
|
||||
req.visibility=localOnly && instance.isAkkoma() ? StatusPrivacy.LOCAL : statusVisibility;
|
||||
req.sensitive=sensitive;
|
||||
req.language=language;
|
||||
req.contentType=contentType;
|
||||
@@ -1899,7 +1899,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
Menu m=visibilityPopup.getMenu();
|
||||
MenuItem localOnlyItem = visibilityPopup.getMenu().findItem(R.id.local_only);
|
||||
boolean prefsSaysSupported = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID);
|
||||
if (instance.isPleroma()) {
|
||||
if (instance.isAkkoma()) {
|
||||
m.findItem(R.id.vis_local).setVisible(true);
|
||||
} else if (localOnly || prefsSaysSupported) {
|
||||
localOnlyItem.setVisible(true);
|
||||
|
||||
@@ -14,7 +14,7 @@ public interface HasAccountID {
|
||||
}
|
||||
|
||||
default boolean isInstanceAkkoma() {
|
||||
return getInstance().map(Instance::isPleroma).orElse(false);
|
||||
return getInstance().map(Instance::isAkkoma).orElse(false);
|
||||
}
|
||||
|
||||
default Optional<Instance> getInstance() {
|
||||
|
||||
@@ -78,7 +78,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
accountID=getArguments().getString("account");
|
||||
setTitle(R.string.sk_app_name);
|
||||
isPleroma = AccountSessionManager.getInstance().getAccount(accountID).getInstance()
|
||||
.map(Instance::isPleroma)
|
||||
.map(Instance::isAkkoma)
|
||||
.orElse(false);
|
||||
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||
@@ -310,7 +310,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
Optional<Instance> instance = session.getInstance();
|
||||
if (instance.isEmpty()) return; // avoiding incompatibility with akkoma
|
||||
|
||||
new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance.get().isPleroma())
|
||||
new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance.get().isAkkoma())
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(List<Notification> notifications) {
|
||||
|
||||
@@ -163,8 +163,9 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
.notifications.lastReadId = result.items.get(0).id;
|
||||
AccountSessionManager.getInstance().writeAccountsFile();
|
||||
|
||||
if (AccountSessionManager.getInstance().getAccount(accountID).getInstance().map(Instance::isPleroma).orElse(false))
|
||||
if (isInstanceAkkoma()) {
|
||||
new PleromaMarkNotificationsRead(result.items.get(0).id).exec(accountID);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -184,12 +184,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
loaded=true;
|
||||
if(!isOwnProfile)
|
||||
loadRelationship();
|
||||
else {
|
||||
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(domain);
|
||||
if (instance != null && instance.isPleroma()) {
|
||||
maxFields = instance.pleroma.metadata.fieldsLimits.maxFields;
|
||||
}
|
||||
}
|
||||
else if (isInstanceAkkoma() && getInstance().isPresent())
|
||||
maxFields = getInstance().get().pleroma.metadata.fieldsLimits.maxFields;
|
||||
}else{
|
||||
profileAccountID=getArguments().getString("profileAccountID");
|
||||
if(!getArguments().getBoolean("noAutoLoad", false))
|
||||
|
||||
@@ -225,7 +225,7 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
||||
GlobalUserPreferences.showReplies=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
if (instance.map(Instance::isPleroma).orElse(false)) {
|
||||
if (isInstanceAkkoma()) {
|
||||
items.add(new ButtonItem(R.string.sk_settings_reply_visibility, R.drawable.ic_fluent_chat_24_regular, b->{
|
||||
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
||||
popupMenu.inflate(R.menu.reply_visibility);
|
||||
@@ -374,7 +374,7 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
||||
glitchModeItem.enabled = i.checked;
|
||||
if (i.checked) {
|
||||
GlobalUserPreferences.accountsWithLocalOnlySupport.add(accountID);
|
||||
if (!instance.map(Instance::isPleroma).orElse(false)) {
|
||||
if (!isInstanceAkkoma()) {
|
||||
GlobalUserPreferences.accountsInGlitchMode.add(accountID);
|
||||
}
|
||||
} else {
|
||||
@@ -744,7 +744,7 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return isInstanceAkkoma() ? null : base.path("/settings").build();
|
||||
return base.path(isInstanceAkkoma() ? "/about" : "/settings").build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,20 +2,19 @@ package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusContext;
|
||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
@@ -24,9 +23,14 @@ import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
@@ -34,6 +38,24 @@ import me.grishka.appkit.api.SimpleCallback;
|
||||
public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent {
|
||||
protected Status mainStatus;
|
||||
|
||||
/**
|
||||
* 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<>();
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -49,13 +71,38 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||
List<StatusDisplayItem> items=super.buildDisplayItems(s);
|
||||
if(s.id.equals(mainStatus.id)){
|
||||
for(StatusDisplayItem item:items){
|
||||
// "what the fuck is a deque"? yes
|
||||
// (it's just so the last-added item automatically comes first when looping over it)
|
||||
Deque<Integer> deleteTheseItems = new ArrayDeque<>();
|
||||
for(int i = 0; i < items.size(); i++){
|
||||
StatusDisplayItem item = items.get(i);
|
||||
|
||||
Optional<Pair<String, Integer>> levelForStatus =
|
||||
levels.stream().filter(p -> p.first.equals(s.id)).findAny();
|
||||
item.descendantLevel = levelForStatus.map(p -> p.second).orElse(0);
|
||||
if (levelForStatus.isPresent()) {
|
||||
int idx = levels.indexOf(levelForStatus.get());
|
||||
item.hasDescendantSibling = (levels.size() > idx + 1)
|
||||
&& levels.get(idx + 1).second > levelForStatus.get().second;
|
||||
item.isDescendantSibling = (idx - 1 >= 0)
|
||||
&& levels.get(idx - 1).second < levelForStatus.get().second;
|
||||
}
|
||||
|
||||
if (item instanceof ReblogOrReplyLineStatusDisplayItem
|
||||
&& item.isDescendantSibling
|
||||
&& item.descendantLevel != 1) {
|
||||
deleteTheseItems.add(i);
|
||||
}
|
||||
|
||||
if(s.id.equals(mainStatus.id)){
|
||||
if(item instanceof TextStatusDisplayItem text)
|
||||
text.textSelectable=true;
|
||||
else if(item instanceof FooterStatusDisplayItem footer)
|
||||
footer.hideCounts=true;
|
||||
}
|
||||
}
|
||||
for (int deleteThisItem : deleteTheseItems) items.remove(deleteThisItem);
|
||||
if(s.id.equals(mainStatus.id)) {
|
||||
items.add(new ExtendedFooterStatusDisplayItem(s.id, this, s.getContentStatus()));
|
||||
}
|
||||
return items;
|
||||
@@ -70,36 +117,20 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
if (getActivity() == null) return;
|
||||
if(refreshing){
|
||||
data.clear();
|
||||
levels.clear();
|
||||
displayItems.clear();
|
||||
data.add(mainStatus);
|
||||
onAppendItems(Collections.singletonList(mainStatus));
|
||||
}
|
||||
AccountSession account=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(account.domain);
|
||||
if(instance.isPleroma()){
|
||||
List<String> threadIds=new ArrayList<>();
|
||||
threadIds.add(mainStatus.id);
|
||||
for(Status s:result.descendants){
|
||||
if(threadIds.contains(s.inReplyToId)){
|
||||
threadIds.add(s.id);
|
||||
}
|
||||
}
|
||||
threadIds.add(mainStatus.inReplyToId);
|
||||
for(int i=result.ancestors.size()-1; i >= 0; i--){
|
||||
Status s=result.ancestors.get(i);
|
||||
if(s.inReplyToId != null && threadIds.contains(s.id)){
|
||||
threadIds.add(s.inReplyToId);
|
||||
}
|
||||
}
|
||||
|
||||
result.ancestors=result.ancestors.stream().filter(s -> threadIds.contains(s.id)).collect(Collectors.toList());
|
||||
result.descendants=getDescendantsOrdered(mainStatus.id,
|
||||
result.descendants.stream()
|
||||
.filter(s -> threadIds.contains(s.id))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
// TODO: figure out how this code works
|
||||
if(isInstanceAkkoma()) sortStatusContext(mainStatus, result);
|
||||
|
||||
result.descendants=filterStatuses(result.descendants);
|
||||
result.ancestors=filterStatuses(result.ancestors);
|
||||
|
||||
levels.addAll(countAncestryLevels(mainStatus.id, result));
|
||||
|
||||
if(footerProgress!=null)
|
||||
footerProgress.setVisibility(View.GONE);
|
||||
data.addAll(result.descendants);
|
||||
@@ -108,7 +139,12 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
int count=displayItems.size();
|
||||
if(!refreshing)
|
||||
adapter.notifyItemRangeInserted(prevCount, count-prevCount);
|
||||
prependItems(result.ancestors, !refreshing);
|
||||
int prependedCount = prependItems(result.ancestors, !refreshing);
|
||||
if (prependedCount > 0 && displayItems.get(prependedCount) instanceof ReblogOrReplyLineStatusDisplayItem) {
|
||||
displayItems.remove(prependedCount);
|
||||
adapter.notifyItemRemoved(prependedCount);
|
||||
count--;
|
||||
}
|
||||
dataLoaded();
|
||||
if(refreshing){
|
||||
refreshDone();
|
||||
@@ -120,7 +156,52 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private List<Status> getDescendantsOrdered(String id, List<Status> statuses){
|
||||
public static List<Pair<String, Integer>> countAncestryLevels(String mainStatusID, StatusContext context) {
|
||||
List<Pair<String, Integer>> levels = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < context.ancestors.size(); i++) {
|
||||
levels.add(Pair.create(
|
||||
context.ancestors.get(i).id,
|
||||
-context.ancestors.size() + i // -3, -2, -1
|
||||
));
|
||||
}
|
||||
|
||||
levels.add(Pair.create(mainStatusID, 0));
|
||||
Map<String, Integer> levelPerStatus = new HashMap<>();
|
||||
|
||||
// sum up the amounts of descendants per status
|
||||
context.descendants.forEach(s -> levelPerStatus.put(s.id,
|
||||
levelPerStatus.getOrDefault(s.inReplyToId, 0) + 1));
|
||||
context.descendants.forEach(s ->
|
||||
levels.add(Pair.create(s.id, levelPerStatus.get(s.id))));
|
||||
|
||||
return levels;
|
||||
}
|
||||
|
||||
public static void sortStatusContext(Status mainStatus, StatusContext context) {
|
||||
List<String> threadIds=new ArrayList<>();
|
||||
threadIds.add(mainStatus.id);
|
||||
for(Status s:context.descendants){
|
||||
if(threadIds.contains(s.inReplyToId)){
|
||||
threadIds.add(s.id);
|
||||
}
|
||||
}
|
||||
threadIds.add(mainStatus.inReplyToId);
|
||||
for(int i=context.ancestors.size()-1; i >= 0; i--){
|
||||
Status s=context.ancestors.get(i);
|
||||
if(s.inReplyToId != null && threadIds.contains(s.id)){
|
||||
threadIds.add(s.inReplyToId);
|
||||
}
|
||||
}
|
||||
|
||||
context.ancestors=context.ancestors.stream().filter(s -> threadIds.contains(s.id)).collect(Collectors.toList());
|
||||
context.descendants=getDescendantsOrdered(mainStatus.id,
|
||||
context.descendants.stream()
|
||||
.filter(s -> threadIds.contains(s.id))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private static List<Status> getDescendantsOrdered(String id, List<Status> statuses){
|
||||
List<Status> out=new ArrayList<>();
|
||||
for(Status s:getDirectDescendants(id, statuses)){
|
||||
out.add(s);
|
||||
@@ -132,7 +213,7 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
return out;
|
||||
}
|
||||
|
||||
private List<Status> getDirectDescendants(String id, List<Status> statuses){
|
||||
private static List<Status> getDirectDescendants(String id, List<Status> statuses){
|
||||
return statuses.stream()
|
||||
.filter(s -> s.inReplyToId.equals(id))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@@ -2,7 +2,9 @@ package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
@@ -24,6 +26,7 @@ import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -38,7 +41,7 @@ import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class InstanceRulesFragment extends ToolbarFragment{
|
||||
public class InstanceRulesFragment extends ToolbarFragment implements ProvidesAssistContent {
|
||||
private UsableRecyclerView list;
|
||||
private MergeRecyclerAdapter adapter;
|
||||
private Button btn;
|
||||
@@ -130,6 +133,15 @@ public class InstanceRulesFragment extends ToolbarFragment{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent assistContent) {
|
||||
assistContent.setWebUri(new Uri.Builder()
|
||||
.scheme("https")
|
||||
.authority(instance.normalizedUri)
|
||||
.path("/about")
|
||||
.build());
|
||||
}
|
||||
|
||||
private class ItemsAdapter extends RecyclerView.Adapter<ItemViewHolder>{
|
||||
|
||||
@NonNull
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.net.IDN;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@Parcel
|
||||
public class Instance extends BaseModel{
|
||||
@@ -88,6 +89,9 @@ public class Instance extends BaseModel{
|
||||
|
||||
public PleromaPollLimits pollLimits;
|
||||
|
||||
/** like uri, but always without scheme and trailing slash */
|
||||
public transient String normalizedUri;
|
||||
|
||||
@Override
|
||||
public void postprocess() throws ObjectValidationException{
|
||||
super.postprocess();
|
||||
@@ -97,6 +101,10 @@ public class Instance extends BaseModel{
|
||||
rules=Collections.emptyList();
|
||||
if(shortDescription==null)
|
||||
shortDescription="";
|
||||
// akkoma says uri is "https://example.social" while just "example.social" on mastodon
|
||||
normalizedUri = uri
|
||||
.replaceFirst("^https://", "")
|
||||
.replaceFirst("/$", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -136,10 +144,26 @@ public class Instance extends BaseModel{
|
||||
return ci;
|
||||
}
|
||||
|
||||
public boolean isPleroma() {
|
||||
public boolean isAkkoma() {
|
||||
return pleroma != null;
|
||||
}
|
||||
|
||||
public boolean hasFeature(Feature feature) {
|
||||
Optional<List<String>> pleromaFeatures = Optional.ofNullable(pleroma)
|
||||
.map(p -> p.metadata)
|
||||
.map(m -> m.features);
|
||||
|
||||
return switch (feature) {
|
||||
case BUBBLE_TIMELINE -> pleromaFeatures
|
||||
.map(f -> f.contains("bubble_timeline"))
|
||||
.orElse(false);
|
||||
};
|
||||
}
|
||||
|
||||
public enum Feature {
|
||||
BUBBLE_TIMELINE
|
||||
}
|
||||
|
||||
@Parcel
|
||||
public static class Rule{
|
||||
public String id;
|
||||
|
||||
@@ -259,13 +259,13 @@ public class TimelineDefinition {
|
||||
public boolean isCompatible(AccountSession session) {
|
||||
// still enabling the bubble timeline for all pleroma/akkoma instances since i know of
|
||||
// at least one instance that supports it, but doesn't list "bubble_timeline"
|
||||
return session.getInstance().map(Instance::isPleroma).orElse(false);
|
||||
return session.getInstance().map(Instance::isAkkoma).orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wantsDefault(AccountSession session) {
|
||||
return session.getInstance()
|
||||
.map(i -> i.isPleroma() && i.pleroma.metadata.features.contains("bubble_timeline"))
|
||||
.map(i -> i.hasFeature(Instance.Feature.BUBBLE_TIMELINE))
|
||||
.orElse(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -104,7 +104,7 @@ public class AccountSwitcherSheet extends BottomSheet{
|
||||
adapter.addAdapter(new AccountsAdapter());
|
||||
|
||||
if (!externalShare) {
|
||||
adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(makeSimpleListItem(R.string.add_account, R.drawable.ic_add_24px), () -> {
|
||||
adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(makeSimpleListItem(R.string.add_account, R.drawable.ic_fluent_add_24_regular), () -> {
|
||||
Nav.go(activity, CustomWelcomeFragment.class, null);
|
||||
dismiss();
|
||||
}));
|
||||
|
||||
@@ -134,12 +134,20 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
bindButton(reply, item.status.repliesCount);
|
||||
bindButton(boost, item.status.reblogsCount);
|
||||
bindButton(favorite, item.status.favouritesCount);
|
||||
reply.setSelected(item.status.repliesCount > 0);
|
||||
// in thread view, direct descendant posts display one direct reply to themselves,
|
||||
// hence in that case displaying whether there is another reply
|
||||
reply.setSelected(item.status.repliesCount > (item.descendantLevel > 0 ? 1 : 0));
|
||||
boost.setSelected(item.status.reblogged);
|
||||
favorite.setSelected(item.status.favourited);
|
||||
bookmark.setSelected(item.status.bookmarked);
|
||||
boost.setEnabled(item.status.visibility==StatusPrivacy.PUBLIC || item.status.visibility==StatusPrivacy.UNLISTED || item.status.visibility==StatusPrivacy.LOCAL
|
||||
|| (item.status.visibility==StatusPrivacy.PRIVATE && item.status.account.id.equals(AccountSessionManager.getInstance().getAccount(item.accountID).self.id)));
|
||||
|
||||
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) itemView.getLayoutParams();
|
||||
params.setMargins(params.leftMargin, params.topMargin, params.rightMargin,
|
||||
item.descendantLevel != 0 && item.hasDescendantSibling ? V.dp(-6) : 0);
|
||||
|
||||
itemView.requestLayout();
|
||||
}
|
||||
|
||||
private void bindButton(TextView btn, long count){
|
||||
|
||||
@@ -49,6 +49,8 @@ public abstract class StatusDisplayItem{
|
||||
public final BaseStatusListFragment parentFragment;
|
||||
public boolean inset;
|
||||
public int index;
|
||||
public int descendantLevel;
|
||||
public boolean hasDescendantSibling, isDescendantSibling;
|
||||
|
||||
public StatusDisplayItem(String parentID, BaseStatusListFragment parentFragment){
|
||||
this.parentID=parentID;
|
||||
|
||||
@@ -237,6 +237,15 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
readMore.setText(item.status.textExpanded ? R.string.sk_collapse : R.string.sk_expand);
|
||||
spaceBelowText.setVisibility(translateVisible ? View.VISIBLE : View.GONE);
|
||||
|
||||
// remove additional padding when (transparently padded) translate button is visible
|
||||
int pos = getAbsoluteAdapterPosition();
|
||||
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(),
|
||||
(translateVisible &&
|
||||
item.parentFragment.getDisplayItems().size() >= pos + 1 &&
|
||||
item.parentFragment.getDisplayItems().get(pos + 1) instanceof FooterStatusDisplayItem)
|
||||
? 0 : V.dp(12)
|
||||
);
|
||||
|
||||
if (!GlobalUserPreferences.collapseLongPosts) {
|
||||
textScrollView.setLayoutParams(wrapParams);
|
||||
readMore.setVisibility(View.GONE);
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerInParent="true"
|
||||
android:importantForAccessibility="no"/>
|
||||
|
||||
Reference in New Issue
Block a user