Compare commits
101 Commits
v2.0.4
...
v1.1.3+for
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10a405ef13 | ||
|
|
a4cb05080a | ||
|
|
8aeda56fc8 | ||
|
|
f073eba538 | ||
|
|
7f78431eff | ||
|
|
24c1ac042c | ||
|
|
105fe68438 | ||
|
|
46057af093 | ||
|
|
750fa4c112 | ||
|
|
c1e67c4f73 | ||
|
|
e0e48f87eb | ||
|
|
0ec14fe8fa | ||
|
|
01a2f1d95c | ||
|
|
67b3e85837 | ||
|
|
9f4d330ab1 | ||
|
|
25092fbcfb | ||
|
|
705e98729d | ||
|
|
108d16a157 | ||
|
|
e55ca6cc05 | ||
|
|
b8be1f184d | ||
|
|
aa96ec54a3 | ||
|
|
e8b43c7179 | ||
|
|
b51b4a10ee | ||
|
|
f2b9ede27c | ||
|
|
a8c7d891f1 | ||
|
|
195c4d7b6d | ||
|
|
d280dc31e8 | ||
|
|
eb0925c524 | ||
|
|
968de3664d | ||
|
|
12f7336392 | ||
|
|
3a4d13b1c6 | ||
|
|
273c841d9a | ||
|
|
0186b7f8da | ||
|
|
d33654c793 | ||
|
|
86d2312615 | ||
|
|
d1083c331b | ||
|
|
ed7242217a | ||
|
|
8fddaa8c82 | ||
|
|
00affe6e3e | ||
|
|
f21b647ee0 | ||
|
|
2a628a3791 | ||
|
|
ecd568503d | ||
|
|
f9d0632a85 | ||
|
|
11905513b7 | ||
|
|
9c89abf1c4 | ||
|
|
4d950e43ac | ||
|
|
99405f307d | ||
|
|
f1bfe05263 | ||
|
|
0f223159c0 | ||
|
|
ad9518e87c | ||
|
|
1c16cfb09e | ||
|
|
d4a4b10017 | ||
|
|
74ae5bd04e | ||
|
|
9638cf079f | ||
|
|
a6d161c1b4 | ||
|
|
1136e40eb4 | ||
|
|
98de3a2984 | ||
|
|
b08415ca8f | ||
|
|
3639c69d36 | ||
|
|
31e3a8592f | ||
|
|
39655d5278 | ||
|
|
d844a77e65 | ||
|
|
bde2e398a8 | ||
|
|
8d443b2051 | ||
|
|
33d4b678ed | ||
|
|
3becad1468 | ||
|
|
fad3ba3eae | ||
|
|
cb16f95878 | ||
|
|
4e833490ff | ||
|
|
04a973f7b0 | ||
|
|
0318169b74 | ||
|
|
972fb1e241 | ||
|
|
9beb04b01d | ||
|
|
a3bea6ad24 | ||
|
|
7996e4ee4a | ||
|
|
69c4bf4213 | ||
|
|
7cd5ca77f5 | ||
|
|
7e736d3cd3 | ||
|
|
13c2adba56 | ||
|
|
010095a50e | ||
|
|
f0cef2103f | ||
|
|
8ed731a48b | ||
|
|
8660d43cb1 | ||
|
|
0f495f620a | ||
|
|
ac81f10ea8 | ||
|
|
9aa95413e6 | ||
|
|
a0a28a0cb7 | ||
|
|
11d88aed27 | ||
|
|
899c9cdf21 | ||
|
|
919d5cffb5 | ||
|
|
12599db0ff | ||
|
|
c751c85c1c | ||
|
|
f1331a0f6d | ||
|
|
c75c9b60f9 | ||
|
|
eb3adf1dfd | ||
|
|
6533163fd0 | ||
|
|
1becad6016 | ||
|
|
d34653750e | ||
|
|
705592aefd | ||
|
|
583325d6e8 | ||
|
|
318d271127 |
34
README.md
@@ -1,11 +1,33 @@
|
||||
# Mastodon for Android
|
||||
[](https://crowdin.com/project/mastodon-for-android)
|
||||

|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android"><img src="img/google-play-badge.png" height="50"></a>
|
||||
# Mastodon for Android Fork
|
||||
|
||||
This is the repository for the official Android app for Mastodon.
|
||||
## Changes
|
||||
|
||||
Learn more about this app in the [blog post](https://blog.joinmastodon.org/2022/02/official-mastodon-for-android-app-is-coming-soon/).
|
||||
* [Enable "Unlisted" as a visibility option](https://github.com/sk22/mastodon-android-fork/tree/feature/enable-unlisted)
|
||||
([Pull request](https://github.com/mastodon/mastodon-android/pull/103)) and
|
||||
[set as default](https://github.com/sk22/mastodon-android-fork/tree/feature/enable-unlisted-as-default)
|
||||
* [Add "Federation" tab and change Discover tab order](https://github.com/sk22/mastodon-android-fork/tree/feature/add-federated-timeline) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/8))
|
||||
* [Add image description button and viewer](https://github.com/sk22/mastodon-android-fork/tree/feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129))
|
||||
* [Implement pinning posts and displaying pinned posts](https://github.com/sk22/mastodon-android-fork/tree/feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140))
|
||||
* [Display full image when adding image description](https://github.com/sk22/mastodon-android-fork/tree/feature/compose-image-description-full-image) ([Pull request](https://github.com/mastodon/mastodon-android/pull/182))
|
||||
* [Always preserve content warnings when replying](https://github.com/sk22/mastodon-android-fork/tree/feature/always-preserve-cw) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/113))
|
||||
* [Make back button return to the home tab before exiting the app](https://github.com/sk22/mastodon-android-fork/tree/feature/back-returns-home) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/118))
|
||||
* [Implement a bookmark button and list](https://github.com/sk22/mastodon-android-fork/tree/feature/bookmarks) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/22))
|
||||
* [Implement deleting and re-drafting](https://github.com/sk22/mastodon-android-fork/tree/feature/delete-redraft) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/21))
|
||||
|
||||
## Fork-specific changes
|
||||
|
||||
* Custom app name
|
||||
* Custom icon: Modulate upstream icon using ImageMagick
|
||||
|
||||
```bash
|
||||
mogrify -modulate 90,100,140 mastodon/src/main/res/mipmap-*/ic_launcher*.png
|
||||
```
|
||||
|
||||
* Custom primary color: Hue of all `primary` colors in `colors.xml` is rotated, on basis of upstream Mastodon's [old branding](https://github.com/mastodon/mastodon-android/commit/74f03026cfcfcfd23237c38ff47d2b2a98a6f92a#diff-59134ec2a1cf3761f80b0ecccbbf8b9e433d9780d2f5c5d6ac3ac8cc254e808f)
|
||||
by `109.8°` (equivalent of `161%`, done by hand using
|
||||
[PineTools](https://pinetools.com/shift-hue-color))
|
||||
|
||||
## Building
|
||||
|
||||
@@ -17,4 +39,4 @@ As this app is using Java 17 features, you need JDK 17 or newer to build it. Oth
|
||||
|
||||
## License
|
||||
|
||||
This project is released under the [GPL-3 License](./LICENSE).
|
||||
This project is released under the [GPL-3 License](./LICENSE).
|
||||
|
||||
@@ -6,11 +6,11 @@ plugins {
|
||||
android {
|
||||
compileSdk 33
|
||||
defaultConfig {
|
||||
applicationId "org.joinmastodon.android"
|
||||
applicationId "org.joinmastodon.android.sk"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 42
|
||||
versionName "1.1.3"
|
||||
versionCode 23
|
||||
versionName "1.1.3+fork.23"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
|
||||
7
mastodon/proguard-rules.pro
vendored
@@ -46,4 +46,9 @@
|
||||
|
||||
-keep class org.joinmastodon.android.AppCenterWrapper { *; }
|
||||
|
||||
-keepattributes LineNumberTable
|
||||
-keepattributes LineNumberTable
|
||||
|
||||
# Parceler library
|
||||
-keep interface org.parceler.Parcel
|
||||
-keep @org.parceler.Parcel class * { *; }
|
||||
-keep class **$$Parcelable { *; }
|
||||
|
||||
@@ -103,7 +103,7 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
||||
|
||||
private void actuallyCheckForUpdates(){
|
||||
Request req=new Request.Builder()
|
||||
.url("https://api.github.com/repos/mastodon/mastodon-android/releases/latest")
|
||||
.url("https://api.github.com/repos/sk22/mastodon-android-fork/releases/latest")
|
||||
.build();
|
||||
Call call=MastodonAPIController.getHttpClient().newCall(req);
|
||||
try(Response resp=call.execute()){
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.os.Looper;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
@@ -18,6 +19,7 @@ public class StatusInteractionController{
|
||||
private final String accountID;
|
||||
private final HashMap<String, SetStatusFavorited> runningFavoriteRequests=new HashMap<>();
|
||||
private final HashMap<String, SetStatusReblogged> runningReblogRequests=new HashMap<>();
|
||||
private final HashMap<String, SetStatusBookmarked> runningBookmarkRequests=new HashMap<>();
|
||||
|
||||
public StatusInteractionController(String accountID){
|
||||
this.accountID=accountID;
|
||||
@@ -61,6 +63,36 @@ public class StatusInteractionController{
|
||||
E.post(new StatusCountersUpdatedEvent(status));
|
||||
}
|
||||
|
||||
public void setBookmarked(Status status, boolean bookmarked){
|
||||
if(!Looper.getMainLooper().isCurrentThread())
|
||||
throw new IllegalStateException("Can only be called from main thread");
|
||||
|
||||
SetStatusBookmarked current=runningBookmarkRequests.remove(status.id);
|
||||
if(current!=null){
|
||||
current.cancel();
|
||||
}
|
||||
SetStatusBookmarked req=(SetStatusBookmarked) new SetStatusBookmarked(status.id, bookmarked)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
runningBookmarkRequests.remove(status.id);
|
||||
E.post(new StatusCountersUpdatedEvent(result));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
runningBookmarkRequests.remove(status.id);
|
||||
error.showToast(MastodonApp.context);
|
||||
status.bookmarked=!bookmarked;
|
||||
E.post(new StatusCountersUpdatedEvent(status));
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
runningBookmarkRequests.put(status.id, req);
|
||||
status.bookmarked=bookmarked;
|
||||
E.post(new StatusCountersUpdatedEvent(status));
|
||||
}
|
||||
|
||||
public void setReblogged(Status status, boolean reblogged){
|
||||
if(!Looper.getMainLooper().isCurrentThread())
|
||||
throw new IllegalStateException("Can only be called from main thread");
|
||||
|
||||
@@ -21,6 +21,7 @@ public class GetAccountStatuses extends MastodonAPIRequest<List<Status>>{
|
||||
switch(filter){
|
||||
case DEFAULT -> addQueryParameter("exclude_replies", "true");
|
||||
case INCLUDE_REPLIES -> {}
|
||||
case PINNED -> addQueryParameter("pinned", "true");
|
||||
case MEDIA -> addQueryParameter("only_media", "true");
|
||||
case NO_REBLOGS -> {
|
||||
addQueryParameter("exclude_replies", "true");
|
||||
@@ -33,6 +34,7 @@ public class GetAccountStatuses extends MastodonAPIRequest<List<Status>>{
|
||||
public enum Filter{
|
||||
DEFAULT,
|
||||
INCLUDE_REPLIES,
|
||||
PINNED,
|
||||
MEDIA,
|
||||
NO_REBLOGS,
|
||||
OWN_POSTS_AND_REPLIES
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.Response;
|
||||
|
||||
public class GetBookmarks extends MastodonAPIRequest<List<Status>>{
|
||||
private String maxId;
|
||||
|
||||
public GetBookmarks(String maxID, String minID, int limit){
|
||||
super(HttpMethod.GET, "/bookmarks", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(minID!=null)
|
||||
addQueryParameter("min_id", minID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateAndPostprocessResponse(List<Status> respObj, Response httpResponse) throws IOException {
|
||||
super.validateAndPostprocessResponse(respObj, httpResponse);
|
||||
// <https://mastodon.social/api/v1/bookmarks?max_id=268962>; rel="next",
|
||||
// <https://mastodon.social/api/v1/bookmarks?min_id=268981>; rel="prev"
|
||||
String link=httpResponse.header("link");
|
||||
// parsing link header by hand; using a library would be cleaner
|
||||
// (also, the functionality should be part of the max id logics and implemented in MastodonAPIRequest)
|
||||
if(link==null) return;
|
||||
String maxIdEq="max_id=";
|
||||
for(String s : link.split(",")) {
|
||||
if(s.contains("rel=\"next\"")) {
|
||||
int start=s.indexOf(maxIdEq)+maxIdEq.length();
|
||||
int end=s.indexOf('>');
|
||||
if(start<0 || start>end) return;
|
||||
this.maxId=s.substring(start, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getMaxId() {
|
||||
return maxId;
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,9 @@ public class CreateOAuthApp extends MastodonAPIRequest<Application>{
|
||||
}
|
||||
|
||||
private static class Request{
|
||||
public String clientName="Mastodon for Android";
|
||||
public String clientName="Mastodon for Android Fork";
|
||||
public String redirectUris=AccountSessionManager.REDIRECT_URI;
|
||||
public String scopes=AccountSessionManager.SCOPE;
|
||||
public String website="https://app.joinmastodon.org/android";
|
||||
public String website="https://github.com/sk22/mastodon-android-fork";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
public class SetStatusBookmarked extends MastodonAPIRequest<Status>{
|
||||
public SetStatusBookmarked(String id, boolean bookmarked){
|
||||
super(HttpMethod.POST, "/statuses/"+id+"/"+(bookmarked ? "bookmark" : "unbookmark"), Status.class);
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
public class SetStatusPinned extends MastodonAPIRequest<Status>{
|
||||
public SetStatusPinned(String id, boolean pinned){
|
||||
super(HttpMethod.POST, "/statuses/"+id+"/"+(pinned ? "pin" : "unpin"), Status.class);
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import org.joinmastodon.android.model.Status;
|
||||
public class StatusCountersUpdatedEvent{
|
||||
public String id;
|
||||
public int favorites, reblogs, replies;
|
||||
public boolean favorited, reblogged;
|
||||
public boolean favorited, reblogged, pinned;
|
||||
|
||||
public StatusCountersUpdatedEvent(Status s){
|
||||
id=s.id;
|
||||
@@ -14,5 +14,6 @@ public class StatusCountersUpdatedEvent{
|
||||
replies=s.repliesCount;
|
||||
favorited=s.favourited;
|
||||
reblogged=s.reblogged;
|
||||
pinned=s.pinned;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.events;
|
||||
|
||||
public class StatusUnpinnedEvent {
|
||||
public final String id;
|
||||
public final String accountID;
|
||||
|
||||
public StatusUnpinnedEvent(String id, String accountID){
|
||||
this.id=id;
|
||||
this.accountID=accountID;
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,10 @@ import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.events.StatusUnpinnedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -76,6 +78,7 @@ public class AccountTimelineFragment extends StatusListFragment{
|
||||
protected void onStatusCreated(StatusCreatedEvent ev){
|
||||
if(!AccountSessionManager.getInstance().isSelf(accountID, ev.status.account))
|
||||
return;
|
||||
if(filter==GetAccountStatuses.Filter.PINNED) return;
|
||||
if(filter==GetAccountStatuses.Filter.DEFAULT){
|
||||
// Keep replies to self, discard all other replies
|
||||
if(ev.status.inReplyToAccountId!=null && !ev.status.inReplyToAccountId.equals(AccountSessionManager.getInstance().getAccount(accountID).self.id))
|
||||
@@ -86,4 +89,24 @@ public class AccountTimelineFragment extends StatusListFragment{
|
||||
}
|
||||
prependItems(Collections.singletonList(ev.status), true);
|
||||
}
|
||||
|
||||
protected void onStatusUnpinned(StatusUnpinnedEvent ev){
|
||||
if(!ev.accountID.equals(accountID) || filter!=GetAccountStatuses.Filter.PINNED)
|
||||
return;
|
||||
|
||||
Status status=getStatusByID(ev.id);
|
||||
data.remove(status);
|
||||
preloadedData.remove(status);
|
||||
HeaderStatusDisplayItem item=findItemOfType(ev.id, HeaderStatusDisplayItem.class);
|
||||
if(item==null)
|
||||
return;
|
||||
int index=displayItems.indexOf(item);
|
||||
int lastIndex;
|
||||
for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){
|
||||
if(!displayItems.get(lastIndex).parentID.equals(ev.id))
|
||||
break;
|
||||
}
|
||||
displayItems.subList(index, lastIndex).clear();
|
||||
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetBookmarks;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class BookmarksListFragment extends StatusListFragment{
|
||||
|
||||
private String accountID;
|
||||
private Account self;
|
||||
private String lastMaxId=null;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
accountID=getArguments().getString("account");
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
self=session.self;
|
||||
setTitle(R.string.bookmarks);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count) {
|
||||
GetBookmarks b=new GetBookmarks(offset>0 ? lastMaxId : null, null, count);
|
||||
currentRequest=b.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
onDataLoaded(result, b.getMaxId()!=null);
|
||||
lastMaxId=b.getMaxId();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import android.text.Layout;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -184,7 +185,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
private ImageView sendError;
|
||||
private View sendingOverlay;
|
||||
private WindowManager wm;
|
||||
private StatusPrivacy statusVisibility=StatusPrivacy.PUBLIC;
|
||||
private StatusPrivacy statusVisibility=StatusPrivacy.UNLISTED;
|
||||
private ComposeAutocompleteSpan currentAutocompleteSpan;
|
||||
private FrameLayout mainEditTextWrap;
|
||||
private ComposeAutocompleteViewController autocompleteViewController;
|
||||
@@ -197,6 +198,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
private boolean ignoreSelectionChanges=false;
|
||||
private Runnable updateUploadEtaRunnable;
|
||||
|
||||
public static DraftMediaAttachment redraftAttachment(Attachment att) {
|
||||
DraftMediaAttachment draft=new DraftMediaAttachment();
|
||||
draft.serverAttachment=att;
|
||||
draft.description=att.description;
|
||||
draft.uri=Uri.parse(att.url);
|
||||
return draft;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -313,11 +322,18 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
pollDurationView.setOnClickListener(v->showPollDurationMenu());
|
||||
|
||||
pollOptions.clear();
|
||||
if(savedInstanceState!=null && savedInstanceState.containsKey("pollOptions")){
|
||||
ArrayList<String> restoredPollOptions=(savedInstanceState!=null ? savedInstanceState : getArguments())
|
||||
.getStringArrayList("pollOptions");
|
||||
if(restoredPollOptions!=null){
|
||||
if(savedInstanceState==null){
|
||||
// restoring from arguments
|
||||
pollDuration=getArguments().getInt("pollDuration");
|
||||
pollDurationStr=DateUtils.formatElapsedTime(pollDuration); // getResources().getQuantityString(R.plurals.x_hours, pollDuration/3600);
|
||||
}
|
||||
pollBtn.setSelected(true);
|
||||
mediaBtn.setEnabled(false);
|
||||
pollWrap.setVisibility(View.VISIBLE);
|
||||
for(String oldText:savedInstanceState.getStringArrayList("pollOptions")){
|
||||
for(String oldText:restoredPollOptions){
|
||||
DraftPollOption opt=createDraftPollOption();
|
||||
opt.edit.setText(oldText);
|
||||
}
|
||||
@@ -351,8 +367,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
spoilerBtn.setSelected(true);
|
||||
}
|
||||
|
||||
if(savedInstanceState!=null && savedInstanceState.containsKey("attachments")){
|
||||
ArrayList<Parcelable> serializedAttachments=savedInstanceState.getParcelableArrayList("attachments");
|
||||
ArrayList<Parcelable> serializedAttachments=(savedInstanceState!=null ? savedInstanceState : getArguments())
|
||||
.getParcelableArrayList("attachments");
|
||||
if(serializedAttachments!=null){
|
||||
for(Parcelable a:serializedAttachments){
|
||||
DraftMediaAttachment att=Parcels.unwrap(a);
|
||||
attachmentsView.addView(createMediaAttachmentView(att));
|
||||
@@ -499,7 +516,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
ignoreSelectionChanges=true;
|
||||
mainEditText.setSelection(mainEditText.length());
|
||||
ignoreSelectionChanges=false;
|
||||
if(!TextUtils.isEmpty(replyTo.spoilerText) && AccountSessionManager.getInstance().isSelf(accountID, replyTo.account)){
|
||||
if(!TextUtils.isEmpty(replyTo.spoilerText)){
|
||||
hasSpoiler=true;
|
||||
spoilerEdit.setVisibility(View.VISIBLE);
|
||||
spoilerEdit.setText(replyTo.spoilerText);
|
||||
@@ -727,6 +744,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
|
||||
private boolean hasDraft(){
|
||||
if(getArguments().getBoolean("hasDraft", false)) return true;
|
||||
if(editingStatus!=null){
|
||||
if(!mainEditText.getText().toString().equals(initialText))
|
||||
return true;
|
||||
@@ -1249,7 +1267,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
UiUtils.enablePopupMenuIcons(getActivity(), menu);
|
||||
m.setGroupCheckable(0, true, true);
|
||||
m.findItem(switch(statusVisibility){
|
||||
case PUBLIC, UNLISTED -> R.id.vis_public;
|
||||
case PUBLIC -> R.id.vis_public;
|
||||
case UNLISTED -> R.id.vis_unlisted;
|
||||
case PRIVATE -> R.id.vis_followers;
|
||||
case DIRECT -> R.id.vis_private;
|
||||
}).setChecked(true);
|
||||
@@ -1259,6 +1278,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
int id=item.getItemId();
|
||||
if(id==R.id.vis_public){
|
||||
statusVisibility=StatusPrivacy.PUBLIC;
|
||||
}else if(id==R.id.vis_unlisted){
|
||||
statusVisibility=StatusPrivacy.UNLISTED;
|
||||
}else if(id==R.id.vis_followers){
|
||||
statusVisibility=StatusPrivacy.PRIVATE;
|
||||
}else if(id==R.id.vis_private){
|
||||
@@ -1289,9 +1310,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
public void onSuccess(Preferences result){
|
||||
// Only override the reply visibility if our preference is more private
|
||||
if (result.postingDefaultVisibility.isLessVisibleThan(statusVisibility)) {
|
||||
// Map unlisted from the API onto public, because we don't have unlisted in the UI
|
||||
statusVisibility = switch (result.postingDefaultVisibility) {
|
||||
case PUBLIC, UNLISTED -> StatusPrivacy.PUBLIC;
|
||||
case PUBLIC -> StatusPrivacy.PUBLIC;
|
||||
case UNLISTED -> StatusPrivacy.UNLISTED;
|
||||
case PRIVATE -> StatusPrivacy.PRIVATE;
|
||||
case DIRECT -> StatusPrivacy.DIRECT;
|
||||
};
|
||||
|
||||
@@ -241,9 +241,14 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
if(currentTab==R.id.tab_profile)
|
||||
return profileFragment.onBackPressed();
|
||||
if (profileFragment.onBackPressed()) return true;
|
||||
if(currentTab==R.id.tab_search)
|
||||
return searchFragment.onBackPressed();
|
||||
if (searchFragment.onBackPressed()) return true;
|
||||
if (currentTab!=R.id.tab_home) {
|
||||
tabBar.selectTab(R.id.tab_home);
|
||||
onTabSelected(R.id.tab_home);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
private ProgressBarButton actionButton;
|
||||
private ViewPager2 pager;
|
||||
private NestedRecyclerScrollView scrollView;
|
||||
private AccountTimelineFragment postsFragment, postsWithRepliesFragment, mediaFragment;
|
||||
private AccountTimelineFragment postsFragment, postsWithRepliesFragment, pinnedPostsFragment, mediaFragment;
|
||||
private ProfileAboutFragment aboutFragment;
|
||||
private TabLayout tabbar;
|
||||
private SwipeRefreshLayout refreshLayout;
|
||||
@@ -209,14 +209,15 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
}
|
||||
};
|
||||
|
||||
tabViews=new FrameLayout[4];
|
||||
tabViews=new FrameLayout[5];
|
||||
for(int i=0;i<tabViews.length;i++){
|
||||
FrameLayout tabView=new FrameLayout(getActivity());
|
||||
tabView.setId(switch(i){
|
||||
case 0 -> R.id.profile_posts;
|
||||
case 1 -> R.id.profile_posts_with_replies;
|
||||
case 2 -> R.id.profile_media;
|
||||
case 3 -> R.id.profile_about;
|
||||
case 2 -> R.id.profile_pinned_posts;
|
||||
case 3 -> R.id.profile_media;
|
||||
case 4 -> R.id.profile_about;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+i);
|
||||
});
|
||||
tabView.setVisibility(View.GONE);
|
||||
@@ -224,7 +225,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
tabViews[i]=tabView;
|
||||
}
|
||||
|
||||
pager.setOffscreenPageLimit(4);
|
||||
pager.setOffscreenPageLimit(5);
|
||||
pager.setAdapter(new ProfilePagerAdapter());
|
||||
pager.getLayoutParams().height=getResources().getDisplayMetrics().heightPixels;
|
||||
|
||||
@@ -240,8 +241,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
tab.setText(switch(position){
|
||||
case 0 -> R.string.posts;
|
||||
case 1 -> R.string.posts_and_replies;
|
||||
case 2 -> R.string.media;
|
||||
case 3 -> R.string.profile_about;
|
||||
case 2 -> R.string.pinned_posts;
|
||||
case 3 -> R.string.media;
|
||||
case 4 -> R.string.profile_about;
|
||||
default -> throw new IllegalStateException();
|
||||
});
|
||||
}
|
||||
@@ -298,6 +300,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
postsFragment.onRefresh();
|
||||
if(postsWithRepliesFragment.loaded)
|
||||
postsWithRepliesFragment.onRefresh();
|
||||
if(pinnedPostsFragment.loaded)
|
||||
pinnedPostsFragment.onRefresh();
|
||||
if(mediaFragment.loaded)
|
||||
mediaFragment.onRefresh();
|
||||
}
|
||||
@@ -322,6 +326,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
if(postsFragment==null){
|
||||
postsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.DEFAULT, true);
|
||||
postsWithRepliesFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.INCLUDE_REPLIES, false);
|
||||
pinnedPostsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.PINNED, false);
|
||||
mediaFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.MEDIA, false);
|
||||
aboutFragment=new ProfileAboutFragment();
|
||||
aboutFragment.setFields(fields);
|
||||
@@ -402,6 +407,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
if(postsFragment!=null && postsFragment.isAdded() && childInsets!=null){
|
||||
postsFragment.onApplyWindowInsets(childInsets);
|
||||
postsWithRepliesFragment.onApplyWindowInsets(childInsets);
|
||||
pinnedPostsFragment.onApplyWindowInsets(childInsets);
|
||||
mediaFragment.onApplyWindowInsets(childInsets);
|
||||
}
|
||||
}
|
||||
@@ -524,7 +530,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
if(isOwnProfile){
|
||||
for(int i=0;i<menu.size();i++){
|
||||
MenuItem item=menu.getItem(i);
|
||||
item.setVisible(item.getItemId()==R.id.share);
|
||||
item.setVisible(item.getItemId()==R.id.share || item.getItemId()==R.id.bookmarks);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -544,11 +550,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item){
|
||||
int id=item.getItemId();
|
||||
if(id==R.id.share){
|
||||
Intent intent=new Intent(Intent.ACTION_SEND);
|
||||
if(id==R.id.share) {
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
intent.putExtra(Intent.EXTRA_TEXT, account.url);
|
||||
startActivity(Intent.createChooser(intent, item.getTitle()));
|
||||
}else if(id==R.id.bookmarks) {
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(account));
|
||||
Nav.go(getActivity(), BookmarksListFragment.class, args);
|
||||
}else if(id==R.id.mute){
|
||||
confirmToggleMuted();
|
||||
}else if(id==R.id.block){
|
||||
@@ -652,8 +663,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
return switch(page){
|
||||
case 0 -> postsFragment;
|
||||
case 1 -> postsWithRepliesFragment;
|
||||
case 2 -> mediaFragment;
|
||||
case 3 -> aboutFragment;
|
||||
case 2 -> pinnedPostsFragment;
|
||||
case 3 -> mediaFragment;
|
||||
case 4 -> aboutFragment;
|
||||
default -> throw new IllegalStateException();
|
||||
};
|
||||
}
|
||||
@@ -714,9 +726,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
invalidateOptionsMenu();
|
||||
pager.setUserInputEnabled(false);
|
||||
actionButton.setText(R.string.done);
|
||||
pager.setCurrentItem(3);
|
||||
pager.setCurrentItem(4);
|
||||
ArrayList<Animator> animators=new ArrayList<>();
|
||||
for(int i=0;i<3;i++){
|
||||
for(int i=0;i<tabViews.length-1;i++){
|
||||
animators.add(ObjectAnimator.ofFloat(tabbar.getTabAt(i).view, View.ALPHA, .3f));
|
||||
tabbar.getTabAt(i).view.setEnabled(false);
|
||||
}
|
||||
@@ -757,7 +769,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
invalidateOptionsMenu();
|
||||
ArrayList<Animator> animators=new ArrayList<>();
|
||||
actionButton.setText(R.string.edit_profile);
|
||||
for(int i=0;i<3;i++){
|
||||
for(int i=0;i<tabViews.length-1;i++){
|
||||
animators.add(ObjectAnimator.ofFloat(tabbar.getTabAt(i).view, View.ALPHA, 1f));
|
||||
}
|
||||
animators.add(ObjectAnimator.ofInt(avatar.getForeground(), "alpha", 0));
|
||||
@@ -775,7 +787,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
for(int i=0;i<3;i++){
|
||||
for(int i=0;i<tabViews.length-1;i++){
|
||||
tabbar.getTabAt(i).view.setEnabled(true);
|
||||
}
|
||||
pager.setUserInputEnabled(true);
|
||||
@@ -952,7 +964,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return loaded ? 4 : 0;
|
||||
return loaded ? tabViews.length : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.events.StatusDeletedEvent;
|
||||
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusUnpinnedEvent;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||
@@ -62,59 +63,6 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
|
||||
protected void onStatusCreated(StatusCreatedEvent ev){}
|
||||
|
||||
protected void onStatusUpdated(StatusUpdatedEvent ev){
|
||||
ArrayList<Status> statusesForDisplayItems=new ArrayList<>();
|
||||
for(int i=0;i<data.size();i++){
|
||||
Status s=data.get(i);
|
||||
if(s.reblog!=null && s.reblog.id.equals(ev.status.id)){
|
||||
s.reblog=ev.status;
|
||||
statusesForDisplayItems.add(s);
|
||||
}else if(s.id.equals(ev.status.id)){
|
||||
data.set(i, ev.status);
|
||||
statusesForDisplayItems.add(ev.status);
|
||||
}
|
||||
}
|
||||
for(int i=0;i<preloadedData.size();i++){
|
||||
Status s=preloadedData.get(i);
|
||||
if(s.reblog!=null && s.reblog.id.equals(ev.status.id)){
|
||||
s.reblog=ev.status;
|
||||
}else if(s.id.equals(ev.status.id)){
|
||||
preloadedData.set(i, ev.status);
|
||||
}
|
||||
}
|
||||
|
||||
if(statusesForDisplayItems.isEmpty())
|
||||
return;
|
||||
|
||||
for(Status s:statusesForDisplayItems){
|
||||
int i=0;
|
||||
for(StatusDisplayItem item:displayItems){
|
||||
if(item.parentID.equals(s.id)){
|
||||
int start=i;
|
||||
for(;i<displayItems.size();i++){
|
||||
if(!displayItems.get(i).parentID.equals(s.id))
|
||||
break;
|
||||
}
|
||||
List<StatusDisplayItem> postItems=displayItems.subList(start, i);
|
||||
postItems.clear();
|
||||
postItems.addAll(buildDisplayItems(s));
|
||||
int oldSize=i-start, newSize=postItems.size();
|
||||
if(oldSize==newSize){
|
||||
adapter.notifyItemRangeChanged(start, newSize);
|
||||
}else if(oldSize<newSize){
|
||||
adapter.notifyItemRangeChanged(start, oldSize);
|
||||
adapter.notifyItemRangeInserted(start+oldSize, newSize-oldSize);
|
||||
}else{
|
||||
adapter.notifyItemRangeChanged(start, newSize);
|
||||
adapter.notifyItemRangeRemoved(start+newSize, oldSize-newSize);
|
||||
}
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Status getContentStatusByID(String id){
|
||||
Status s=getStatusByID(id);
|
||||
return s==null ? null : s.getContentStatus();
|
||||
@@ -190,11 +138,6 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
StatusListFragment.this.onStatusCreated(ev);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onStatusUpdated(StatusUpdatedEvent ev){
|
||||
StatusListFragment.this.onStatusUpdated(ev);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPollUpdated(PollUpdatedEvent ev){
|
||||
if(!ev.accountID.equals(accountID))
|
||||
|
||||
@@ -51,6 +51,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
private DiscoverAccountsFragment accountsFragment;
|
||||
private SearchFragment searchFragment;
|
||||
private LocalTimelineFragment localTimelineFragment;
|
||||
private FederatedTimelineFragment federatedTimelineFragment;
|
||||
|
||||
private String accountID;
|
||||
private Runnable searchDebouncer=this::onSearchChangedDebounced;
|
||||
@@ -72,15 +73,16 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
tabLayout=view.findViewById(R.id.tabbar);
|
||||
pager=view.findViewById(R.id.pager);
|
||||
|
||||
tabViews=new FrameLayout[5];
|
||||
tabViews=new FrameLayout[6];
|
||||
for(int i=0;i<tabViews.length;i++){
|
||||
FrameLayout tabView=new FrameLayout(getActivity());
|
||||
tabView.setId(switch(i){
|
||||
case 0 -> R.id.discover_posts;
|
||||
case 1 -> R.id.discover_hashtags;
|
||||
case 2 -> R.id.discover_news;
|
||||
case 3 -> R.id.discover_local_timeline;
|
||||
case 4 -> R.id.discover_users;
|
||||
case 0 -> R.id.discover_local_timeline;
|
||||
case 1 -> R.id.discover_federated_timeline;
|
||||
case 2 -> R.id.discover_hashtags;
|
||||
case 3 -> R.id.discover_posts;
|
||||
case 4 -> R.id.discover_news;
|
||||
case 5 -> R.id.discover_users;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+i);
|
||||
});
|
||||
tabView.setVisibility(View.GONE);
|
||||
@@ -106,7 +108,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
}
|
||||
});
|
||||
|
||||
if(postsFragment==null){
|
||||
if(localTimelineFragment==null){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putBoolean("__is_tab", true);
|
||||
@@ -126,9 +128,13 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
localTimelineFragment=new LocalTimelineFragment();
|
||||
localTimelineFragment.setArguments(args);
|
||||
|
||||
federatedTimelineFragment=new FederatedTimelineFragment();
|
||||
federatedTimelineFragment.setArguments(args);
|
||||
|
||||
getChildFragmentManager().beginTransaction()
|
||||
.add(R.id.discover_posts, postsFragment)
|
||||
.add(R.id.discover_local_timeline, localTimelineFragment)
|
||||
.add(R.id.discover_federated_timeline, federatedTimelineFragment)
|
||||
.add(R.id.discover_hashtags, hashtagsFragment)
|
||||
.add(R.id.discover_news, newsFragment)
|
||||
.add(R.id.discover_users, accountsFragment)
|
||||
@@ -139,11 +145,12 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
@Override
|
||||
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
|
||||
tab.setText(switch(position){
|
||||
case 0 -> R.string.posts;
|
||||
case 1 -> R.string.hashtags;
|
||||
case 2 -> R.string.news;
|
||||
case 3 -> R.string.local_timeline;
|
||||
case 4 -> R.string.for_you;
|
||||
case 0 -> R.string.local_timeline;
|
||||
case 1 -> R.string.federated_timeline;
|
||||
case 2 -> R.string.hashtags;
|
||||
case 3 -> R.string.posts;
|
||||
case 4 -> R.string.news;
|
||||
case 5 -> R.string.for_you;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||
});
|
||||
tab.view.textView.setAllCaps(true);
|
||||
@@ -229,8 +236,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
}
|
||||
|
||||
public void loadData(){
|
||||
if(postsFragment!=null && !postsFragment.loaded && !postsFragment.dataLoading)
|
||||
postsFragment.loadData();
|
||||
if(localTimelineFragment!=null && !localTimelineFragment.loaded && !localTimelineFragment.dataLoading)
|
||||
localTimelineFragment.loadData();
|
||||
}
|
||||
|
||||
private void onSearchEditFocusChanged(View v, boolean hasFocus){
|
||||
@@ -266,11 +273,12 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
|
||||
private Fragment getFragmentForPage(int page){
|
||||
return switch(page){
|
||||
case 0 -> postsFragment;
|
||||
case 1 -> hashtagsFragment;
|
||||
case 2 -> newsFragment;
|
||||
case 3 -> localTimelineFragment;
|
||||
case 4 -> accountsFragment;
|
||||
case 0 -> localTimelineFragment;
|
||||
case 1 -> federatedTimelineFragment;
|
||||
case 2 -> hashtagsFragment;
|
||||
case 3 -> postsFragment;
|
||||
case 4 -> newsFragment;
|
||||
case 5 -> accountsFragment;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class FederatedTimelineFragment extends StatusListFragment{
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.FEDERATED_TIMELINE);
|
||||
private String maxID;
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetPublicTimeline(false, false, refreshing ? null : maxID, count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
if(!result.isEmpty())
|
||||
maxID=result.get(result.size()-1).id;
|
||||
onDataLoaded(result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList()), !result.isEmpty());
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
bannerHelper.maybeAddBanner(contentWrap);
|
||||
}
|
||||
}
|
||||
@@ -127,6 +127,7 @@ public class Status extends BaseModel implements DisplayItemsParent{
|
||||
repliesCount=ev.replies;
|
||||
favourited=ev.favorited;
|
||||
reblogged=ev.reblogged;
|
||||
pinned=ev.pinned;
|
||||
}
|
||||
|
||||
public Status getContentStatus(){
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.joinmastodon.android.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.BottomSheet;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class ImageDescriptionSheet extends BottomSheet{
|
||||
private UsableRecyclerView list;
|
||||
|
||||
public ImageDescriptionSheet(@NonNull Activity activity, Attachment attachment){
|
||||
super(activity);
|
||||
|
||||
View handleView=new View(activity);
|
||||
handleView.setBackgroundResource(R.drawable.bg_bottom_sheet_handle);
|
||||
ViewGroup handle=new FrameLayout(activity);
|
||||
handle.addView(handleView);
|
||||
handle.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(24)));
|
||||
|
||||
TextView textView = new TextView(activity);
|
||||
if (attachment.description == null || attachment.description.isEmpty()) {
|
||||
textView.setText(R.string.media_no_description);
|
||||
textView.setTypeface(null, Typeface.ITALIC);
|
||||
} else {
|
||||
textView.setText(attachment.description);
|
||||
textView.setTextIsSelectable(true);
|
||||
}
|
||||
|
||||
TextView heading=new TextView(activity);
|
||||
heading.setText(R.string.image_description);
|
||||
heading.setAllCaps(true);
|
||||
heading.setTypeface(null, Typeface.BOLD);
|
||||
heading.setPadding(0, V.dp(24), 0, V.dp(8));
|
||||
|
||||
LinearLayout linearLayout = new LinearLayout(activity);
|
||||
linearLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
linearLayout.setPadding(V.dp(24), 0, V.dp(24), 0);
|
||||
linearLayout.addView(heading);
|
||||
linearLayout.addView(textView);
|
||||
|
||||
FrameLayout layout=new FrameLayout(activity);
|
||||
layout.addView(handle);
|
||||
layout.addView(linearLayout);
|
||||
|
||||
list=new UsableRecyclerView(activity);
|
||||
list.setLayoutManager(new LinearLayoutManager(activity));
|
||||
list.setBackgroundResource(R.drawable.bg_bottom_sheet);
|
||||
list.setAdapter(new SingleViewRecyclerAdapter(layout));
|
||||
list.setClipToPadding(false);
|
||||
|
||||
setContentView(list);
|
||||
setNavigationBarBackground(new ColorDrawable(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground)), !UiUtils.isDarkTheme());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onWindowInsetsUpdated(WindowInsets insets){
|
||||
if(Build.VERSION.SDK_INT>=29){
|
||||
int tappableBottom=insets.getTappableElementInsets().bottom;
|
||||
int insetBottom=insets.getSystemWindowInsetBottom();
|
||||
if(tappableBottom==0 && insetBottom>0){
|
||||
list.setPadding(0, 0, 0, V.dp(48)-insetBottom);
|
||||
}else{
|
||||
list.setPadding(0, 0, 0, V.dp(24));
|
||||
}
|
||||
}else{
|
||||
list.setPadding(0, 0, 0, V.dp(24));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{
|
||||
private final TextView reply, boost, favorite;
|
||||
private final TextView reply, boost, favorite, bookmark;
|
||||
private final ImageView share;
|
||||
|
||||
private final View.AccessibilityDelegate buttonAccessibilityDelegate=new View.AccessibilityDelegate(){
|
||||
@@ -60,22 +60,27 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
reply=findViewById(R.id.reply);
|
||||
boost=findViewById(R.id.boost);
|
||||
favorite=findViewById(R.id.favorite);
|
||||
bookmark=findViewById(R.id.bookmark);
|
||||
share=findViewById(R.id.share);
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
|
||||
UiUtils.fixCompoundDrawableTintOnAndroid6(reply);
|
||||
UiUtils.fixCompoundDrawableTintOnAndroid6(boost);
|
||||
UiUtils.fixCompoundDrawableTintOnAndroid6(favorite);
|
||||
UiUtils.fixCompoundDrawableTintOnAndroid6(bookmark);
|
||||
}
|
||||
View reply=findViewById(R.id.reply_btn);
|
||||
View boost=findViewById(R.id.boost_btn);
|
||||
View favorite=findViewById(R.id.favorite_btn);
|
||||
View share=findViewById(R.id.share_btn);
|
||||
View bookmark=findViewById(R.id.bookmark_btn);
|
||||
reply.setOnClickListener(this::onReplyClick);
|
||||
reply.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
||||
boost.setOnClickListener(this::onBoostClick);
|
||||
boost.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
||||
favorite.setOnClickListener(this::onFavoriteClick);
|
||||
favorite.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
||||
bookmark.setOnClickListener(this::onBookmarkClick);
|
||||
bookmark.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
||||
share.setOnClickListener(this::onShareClick);
|
||||
share.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
||||
}
|
||||
@@ -87,6 +92,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
bindButton(favorite, item.status.favouritesCount);
|
||||
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.PRIVATE && item.status.account.id.equals(AccountSessionManager.getInstance().getAccount(item.accountID).self.id)));
|
||||
}
|
||||
@@ -120,6 +126,11 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
bindButton(favorite, item.status.favouritesCount);
|
||||
}
|
||||
|
||||
private void onBookmarkClick(View v){
|
||||
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked);
|
||||
bookmark.setSelected(item.status.bookmarked);
|
||||
}
|
||||
|
||||
private void onShareClick(View v){
|
||||
Intent intent=new Intent(Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
@@ -134,6 +145,8 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
return R.string.button_reblog;
|
||||
if(id==R.id.favorite_btn)
|
||||
return R.string.button_favorite;
|
||||
if(id==R.id.bookmark_btn)
|
||||
return R.string.button_bookmark;
|
||||
if(id==R.id.share_btn)
|
||||
return R.string.button_share;
|
||||
return 0;
|
||||
|
||||
@@ -163,6 +163,10 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
}else if(id==R.id.delete){
|
||||
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{});
|
||||
}else if(id==R.id.delete_and_redraft) {
|
||||
UiUtils.confirmDeleteAndRedraftPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{});
|
||||
}else if(id==R.id.pin || id==R.id.unpin){
|
||||
UiUtils.confirmPinPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, !item.status.pinned, s->{});
|
||||
}else if(id==R.id.mute){
|
||||
UiUtils.confirmToggleMuteUser(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), account, relationship!=null && relationship.muting, r->{});
|
||||
}else if(id==R.id.block){
|
||||
@@ -280,6 +284,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account);
|
||||
menu.findItem(R.id.edit).setVisible(item.status!=null && isOwnPost);
|
||||
menu.findItem(R.id.delete).setVisible(item.status!=null && isOwnPost);
|
||||
menu.findItem(R.id.delete_and_redraft).setVisible(item.status!=null && isOwnPost);
|
||||
menu.findItem(R.id.pin).setVisible(item.status!=null && isOwnPost && !item.status.pinned);
|
||||
menu.findItem(R.id.unpin).setVisible(item.status!=null && isOwnPost && item.status.pinned);
|
||||
menu.findItem(R.id.open_in_browser).setVisible(item.status!=null);
|
||||
MenuItem blockDomain=menu.findItem(R.id.block_domain);
|
||||
MenuItem mute=menu.findItem(R.id.mute);
|
||||
|
||||
@@ -19,6 +19,7 @@ import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.net.Uri;
|
||||
import android.opengl.Visibility;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.SystemClock;
|
||||
@@ -48,6 +49,7 @@ import android.widget.Toolbar;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.ui.ImageDescriptionSheet;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
|
||||
import java.io.File;
|
||||
@@ -97,6 +99,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
private TextView videoTimeView;
|
||||
private ImageButton videoPlayPauseButton;
|
||||
private View videoControls;
|
||||
private MenuItem imageDescriptionButton;
|
||||
private boolean uiVisible=true;
|
||||
private AudioManager.OnAudioFocusChangeListener audioFocusListener=this::onAudioFocusChanged;
|
||||
private Runnable uiAutoHider=()->{
|
||||
@@ -174,11 +177,24 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
toolbarWrap=uiOverlay.findViewById(R.id.toolbar_wrap);
|
||||
toolbar=uiOverlay.findViewById(R.id.toolbar);
|
||||
toolbar.setNavigationOnClickListener(v->onStartSwipeToDismissTransition(0));
|
||||
toolbar.getMenu().add(R.string.download).setIcon(R.drawable.ic_fluent_arrow_download_24_regular).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
toolbar.setOnMenuItemClickListener(item->{
|
||||
saveCurrentFile();
|
||||
return true;
|
||||
});
|
||||
imageDescriptionButton = toolbar.getMenu()
|
||||
.add(R.string.image_description)
|
||||
.setIcon(R.drawable.ic_fluent_image_alt_text_24_regular)
|
||||
.setVisible(attachments.get(pager.getCurrentItem()).description != null
|
||||
&& !attachments.get(pager.getCurrentItem()).description.isEmpty())
|
||||
.setOnMenuItemClickListener(item -> {
|
||||
new ImageDescriptionSheet(activity,attachments.get(pager.getCurrentItem())).show();
|
||||
return true;
|
||||
});
|
||||
imageDescriptionButton.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
toolbar.getMenu()
|
||||
.add(R.string.download)
|
||||
.setIcon(R.drawable.ic_fluent_arrow_download_24_regular)
|
||||
.setOnMenuItemClickListener(item -> {
|
||||
saveCurrentFile();
|
||||
return true;
|
||||
})
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
uiOverlay.setAlpha(0f);
|
||||
videoControls=uiOverlay.findViewById(R.id.video_player_controls);
|
||||
videoSeekBar=uiOverlay.findViewById(R.id.seekbar);
|
||||
@@ -374,6 +390,8 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
private void onPageChanged(int index){
|
||||
currentIndex=index;
|
||||
Attachment att=attachments.get(index);
|
||||
imageDescriptionButton.setVisible(att.description != null && !att.description.isEmpty());
|
||||
toolbar.invalidate();
|
||||
V.setVisibilityAnimated(videoControls, att.type==Attachment.Type.VIDEO ? View.VISIBLE : View.GONE);
|
||||
if(att.type==Attachment.Type.VIDEO){
|
||||
videoSeekBar.setSecondaryProgress(0);
|
||||
|
||||
@@ -36,6 +36,7 @@ public class DiscoverInfoBannerHelper{
|
||||
case TRENDING_HASHTAGS -> R.string.trending_hashtags_info_banner;
|
||||
case TRENDING_LINKS -> R.string.trending_links_info_banner;
|
||||
case LOCAL_TIMELINE -> R.string.local_timeline_info_banner;
|
||||
case FEDERATED_TIMELINE -> R.string.federated_timeline_info_banner;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -59,6 +60,7 @@ public class DiscoverInfoBannerHelper{
|
||||
TRENDING_HASHTAGS,
|
||||
TRENDING_LINKS,
|
||||
LOCAL_TIMELINE,
|
||||
FEDERATED_TIMELINE,
|
||||
// ACCOUNTS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.InsetDrawable;
|
||||
@@ -20,6 +19,7 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Parcelable;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
@@ -42,8 +42,13 @@ import org.joinmastodon.android.api.requests.accounts.SetAccountMuted;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetDomainBlocked;
|
||||
import org.joinmastodon.android.api.requests.statuses.DeleteStatus;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusPinned;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusDeletedEvent;
|
||||
import org.joinmastodon.android.events.StatusUnpinnedEvent;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||
@@ -51,8 +56,10 @@ import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.text.SpacerSpan;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
@@ -63,6 +70,8 @@ import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -75,6 +84,10 @@ import androidx.annotation.StringRes;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
@@ -391,6 +404,88 @@ public class UiUtils{
|
||||
});
|
||||
}
|
||||
|
||||
public static void confirmPinPost(Activity activity, String accountID, Status status, boolean pinned, Consumer<Status> resultCallback){
|
||||
showConfirmationAlert(activity,
|
||||
pinned ? R.string.confirm_pin_post_title : R.string.confirm_unpin_post_title,
|
||||
pinned ? R.string.confirm_pin_post : R.string.confirm_unpin_post,
|
||||
pinned ? R.string.pin_post : R.string.unpin_post,
|
||||
()->{
|
||||
new SetStatusPinned(status.id, pinned)
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Status result) {
|
||||
resultCallback.accept(result);
|
||||
E.post(new StatusCountersUpdatedEvent(result));
|
||||
if (!result.pinned)
|
||||
E.post(new StatusUnpinnedEvent(status.id, accountID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(activity);
|
||||
}
|
||||
})
|
||||
.wrapProgress(activity, pinned ? R.string.pinning : R.string.unpinning, false)
|
||||
.exec(accountID);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static void confirmDeleteAndRedraftPost(Activity activity, String accountID, Status status, Consumer<Status> resultCallback){
|
||||
showConfirmationAlert(activity, R.string.confirm_delete_and_redraft_title, R.string.confirm_delete_and_redraft, R.string.delete_and_redraft, ()->{
|
||||
new DeleteStatus(status.id)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
resultCallback.accept(result);
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().deleteStatus(status.id);
|
||||
E.post(new StatusDeletedEvent(status.id, accountID));
|
||||
UiUtils.redraftStatus(status, accountID, activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(activity);
|
||||
}
|
||||
})
|
||||
.wrapProgress(activity, R.string.deleting, false)
|
||||
.exec(accountID);
|
||||
});
|
||||
}
|
||||
|
||||
public static void redraftStatus(Status status, String accountID, Activity activity) {
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putBoolean("hasDraft", true);
|
||||
args.putString("prefilledText", HtmlParser.parse(status.content, status.emojis, status.mentions, status.tags, accountID).toString());
|
||||
args.putString("spoilerText", status.spoilerText);
|
||||
args.putSerializable("visibility", status.visibility);
|
||||
if(status.poll!=null){
|
||||
args.putInt("pollDuration", (int)status.poll.expiresAt.minus(status.createdAt.getEpochSecond(), ChronoUnit.SECONDS).getEpochSecond());
|
||||
ArrayList<String> opts=status.poll.options.stream().map(o -> o.title).collect(Collectors.toCollection(ArrayList::new));
|
||||
args.putStringArrayList("pollOptions", opts);
|
||||
}
|
||||
if(!status.mediaAttachments.isEmpty()){
|
||||
ArrayList<Parcelable> serializedAttachments=status.mediaAttachments.stream()
|
||||
.map(att -> Parcels.wrap(ComposeFragment.redraftAttachment(att)))
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
args.putParcelableArrayList("attachments", serializedAttachments);
|
||||
}
|
||||
Callback<Status> cb=new Callback<>(){
|
||||
@Override public void onError(ErrorResponse error) {
|
||||
onSuccess(null);
|
||||
error.showToast(activity);
|
||||
}
|
||||
@Override public void onSuccess(Status status) {
|
||||
if (status!=null) args.putParcelable("replyTo", Parcels.wrap(status));
|
||||
Nav.go(activity, ComposeFragment.class, args);
|
||||
}
|
||||
};
|
||||
|
||||
if(status.inReplyToId!=null) new GetStatusByID(status.inReplyToId).setCallback(cb).exec(accountID);
|
||||
else cb.onSuccess(null);
|
||||
}
|
||||
|
||||
public static void setRelationshipToActionButton(Relationship relationship, Button button){
|
||||
boolean secondaryStyle;
|
||||
if(relationship.blocking){
|
||||
|
||||
5
mastodon/src/main/res/color/bookmark_icon.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/bookmark_selected" android:state_selected="true"/>
|
||||
<item android:color="?android:textColorSecondary"/>
|
||||
</selector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M6.19 21.855c-0.495 0.357-1.187 0.002-1.187-0.61V6.25C5.003 4.455 6.458 3 8.253 3h7.498c1.795 0 3.25 1.455 3.25 3.25v14.996c0 0.611-0.692 0.966-1.188 0.609l-5.81-4.181-5.812 4.18z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M6.19 21.855c-0.495 0.357-1.187 0.002-1.187-0.61V6.25C5.003 4.455 6.458 3 8.253 3h7.498c1.795 0 3.25 1.455 3.25 3.25v14.996c0 0.611-0.692 0.966-1.188 0.609l-5.81-4.181-5.812 4.18zM17.502 6.25c0-0.966-0.783-1.75-1.75-1.75H8.253c-0.967 0-1.75 0.784-1.75 1.75v13.532l5.061-3.641c0.262-0.188 0.614-0.188 0.876 0l5.061 3.641V6.25z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--~ Copyright (c) 2022. ~ Microsoft Corporation. All rights reserved.-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/ic_fluent_bookmark_24_filled" android:state_activated="true"/>
|
||||
<item android:drawable="@drawable/ic_fluent_bookmark_24_filled" android:state_checked="true"/>
|
||||
<item android:drawable="@drawable/ic_fluent_bookmark_24_filled" android:state_selected="true"/>
|
||||
<item android:drawable="@drawable/ic_fluent_bookmark_24_regular"/>
|
||||
</selector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M4 6.748c0-1.243 1.007-2.25 2.25-2.25h9c1.243 0 2.25 1.007 2.25 2.25V21.25c0 0.268-0.143 0.517-0.376 0.65-0.233 0.134-0.52 0.133-0.751-0.002l-5.623-3.28-5.622 3.28c-0.232 0.135-0.519 0.136-0.752 0.002C4.144 21.767 4 21.52 4 21.25V6.748zM15.25 2C17.873 2 20 4.127 20 6.75v11.873c0 0.414-0.336 0.75-0.75 0.75s-0.75-0.336-0.75-0.75V6.751c0-1.796-1.455-3.25-3.25-3.25H6.637S6.75 2.942 7.434 2.42C8 2 8.602 2 8.602 2h6.648z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M1 3c0-1.105 0.895-2 2-2h7c1.105 0 2 0.895 2 2v6c0 1.105-0.895 2-2 2H3c-1.105 0-2-0.895-2-2V3zm2.5 1C3.224 4 3 4.224 3 4.5S3.224 5 3.5 5h6C9.776 5 10 4.776 10 4.5S9.776 4 9.5 4h-6zm0 3C3.224 7 3 7.224 3 7.5S3.224 8 3.5 8h6C9.776 8 10 7.776 10 7.5S9.776 7 9.5 7h-6zM4 12h1.5v6.75c0 0.208 0.036 0.408 0.103 0.594l5.823-5.701c0.833-0.816 2.142-0.854 3.02-0.116l0.128 0.116 5.822 5.702c0.067-0.186 0.104-0.386 0.104-0.595V7.25c0-0.966-0.784-1.75-1.75-1.75H13V4h5.75C20.545 4 22 5.455 22 7.25v11.5c0 1.795-1.455 3.25-3.25 3.25H7.25C5.455 22 4 20.545 4 18.75V12zm15.33 8.401l-5.805-5.686c-0.265-0.26-0.675-0.283-0.966-0.071l-0.084 0.07-5.807 5.687C6.85 20.465 7.046 20.5 7.25 20.5h11.5c0.203 0 0.399-0.035 0.58-0.099zM16.253 7.5c1.244 0 2.252 1.008 2.252 2.252 0 1.244-1.008 2.252-2.252 2.252-1.244 0-2.252-1.008-2.252-2.252C14 8.508 15.008 7.5 16.252 7.5zm0 1.5C15.837 9 15.5 9.337 15.5 9.752s0.337 0.752 0.752 0.752c0.416 0 0.752-0.336 0.752-0.752C17.004 9.337 16.667 9 16.252 9z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -73,6 +73,28 @@
|
||||
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="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:drawableStart="@drawable/ic_fluent_bookmark_24_selector"
|
||||
android:drawablePadding="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"
|
||||
|
||||
@@ -9,20 +9,21 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.joinmastodon.android.ui.views.ComposeMediaLayout
|
||||
android:layout_width="wrap_content"
|
||||
<org.joinmastodon.android.ui.views.MaxWidthFrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal">
|
||||
android:layout_gravity="center"
|
||||
android:maxWidth="400dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/photo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
</org.joinmastodon.android.ui.views.ComposeMediaLayout>
|
||||
</org.joinmastodon.android.ui.views.MaxWidthFrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
<item android:id="@+id/vis_public"
|
||||
android:icon="@drawable/ic_fluent_earth_24_filled"
|
||||
android:title="@string/visibility_public"/>
|
||||
<item android:id="@+id/vis_unlisted"
|
||||
android:icon="@drawable/ic_fluent_people_community_24_regular"
|
||||
android:title="@string/visibility_unlisted"/>
|
||||
<item android:id="@+id/vis_followers"
|
||||
android:icon="@drawable/ic_fluent_people_checkmark_24_regular"
|
||||
android:title="@string/visibility_followers_only"/>
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/edit" android:title="@string/edit"/>
|
||||
<item android:id="@+id/delete" android:title="@string/delete"/>
|
||||
<item android:id="@+id/delete_and_redraft" android:title="@string/delete_and_redraft"/>
|
||||
<item android:id="@+id/pin" android:title="@string/pin_post"/>
|
||||
<item android:id="@+id/unpin" android:title="@string/unpin_post"/>
|
||||
<item android:id="@+id/mute" android:title="@string/mute_user"/>
|
||||
<item android:id="@+id/block" android:title="@string/block_user"/>
|
||||
<item android:id="@+id/block_domain" android:title="@string/block_domain"/>
|
||||
|
||||
@@ -7,4 +7,10 @@
|
||||
<item android:id="@+id/block_domain" android:title="@string/block_domain"/>
|
||||
<item android:id="@+id/hide_boosts" android:title="@string/hide_boosts_from_user"/>
|
||||
<item android:id="@+id/open_in_browser" android:title="@string/open_in_browser"/>
|
||||
<item
|
||||
android:id="@+id/bookmarks"
|
||||
android:showAsAction="always"
|
||||
android:visible="false"
|
||||
android:icon="@drawable/ic_fluent_bookmark_multiple_24_filled"
|
||||
android:title="@string/bookmarks"/>
|
||||
</menu>
|
||||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 775 B After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 952 B After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 34 KiB |
@@ -41,6 +41,7 @@
|
||||
</plurals>
|
||||
<string name="posts">Beiträge</string>
|
||||
<string name="posts_and_replies">Beiträge und Antworten</string>
|
||||
<string name="pinned_posts">Angeheftet</string>
|
||||
<string name="media">Medien</string>
|
||||
<string name="profile_about">Über</string>
|
||||
<string name="button_follow">Folgen</string>
|
||||
@@ -120,9 +121,20 @@
|
||||
<string name="action_vote">Abstimmen</string>
|
||||
<string name="tap_to_reveal">Zum Anzeigen tippen</string>
|
||||
<string name="delete">Löschen</string>
|
||||
<string name="delete_and_redraft">Löschen und neu erstellen</string>
|
||||
<string name="confirm_delete_title">Beitrag löschen</string>
|
||||
<string name="confirm_delete_and_redraft_title">Beitrag löschen und neu erstellen</string>
|
||||
<string name="confirm_delete">Bist du dir sicher, dass du den Beitrag löschen möchtest?</string>
|
||||
<string name="confirm_delete_and_redraft">Bist du dir sicher, dass du den Beitrag löschen und neu erstellen möchtest?</string>
|
||||
<string name="deleting">Wird gelöscht…</string>
|
||||
<string name="pin_post">An Profil anheften</string>
|
||||
<string name="confirm_pin_post_title">Beitrag an Profil anheften</string>
|
||||
<string name="confirm_pin_post">Möchtest du den Beitrag an dein Profil anheften?</string>
|
||||
<string name="pinning">Wird angeheftet…</string>
|
||||
<string name="unpin_post">Von Profil lösen</string>
|
||||
<string name="confirm_unpin_post_title">Angehefteten Beitrag von Profil lösen</string>
|
||||
<string name="confirm_unpin_post">Bist du dir sicher, dass du den angehefteten Beitrag von deinem Profil lösen möchtest?</string>
|
||||
<string name="unpinning">Wird vom Profil gelöst…</string>
|
||||
<string name="notification_channel_audio_player">Audiowiedergabe</string>
|
||||
<string name="play">Abspielen</string>
|
||||
<string name="pause">Pausieren</string>
|
||||
@@ -202,6 +214,7 @@
|
||||
<string name="resent_email">Bestätigungs-E-Mail gesendet</string>
|
||||
<string name="compose_hint">Was gibt\'s Neues?</string>
|
||||
<string name="content_warning">Inhaltswarnung</string>
|
||||
<string name="image_description">Bildbeschreibung</string>
|
||||
<string name="add_image_description">Füge eine Bildbeschreibung hinzu…</string>
|
||||
<string name="retry_upload">Upload erneut versuchen</string>
|
||||
<string name="image_upload_failed">Fehler beim Hochladen des Bildes</string>
|
||||
@@ -212,6 +225,7 @@
|
||||
<string name="alt_text_subtitle">Alternativtext erscheint für blinde Menschen. Versuche, nur so viele Details einzubeziehen, um den Kontext zu verstehen.</string>
|
||||
<string name="alt_text_hint">z.B. Eine Giraffe auf einem Dreirad während sie eine Banane isst</string>
|
||||
<string name="visibility_public">Öffentlich</string>
|
||||
<string name="visibility_unlisted">Nicht gelistet</string>
|
||||
<string name="visibility_followers_only">Nur Folgende</string>
|
||||
<string name="visibility_private">Nur Leute, die ich erwähne</string>
|
||||
<string name="search_all">Alle</string>
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
</plurals>
|
||||
<string name="posts">嘟文</string>
|
||||
<string name="posts_and_replies">嘟文和回复</string>
|
||||
<string name="pinned_posts">置顶</string>
|
||||
<string name="media">媒体</string>
|
||||
<string name="profile_about">关于</string>
|
||||
<string name="button_follow">关注</string>
|
||||
@@ -64,7 +65,7 @@
|
||||
<string name="post_from_user">来自 %s 的嘟文</string>
|
||||
<string name="poll_option_hint">选项 %d</string>
|
||||
<plurals name="x_minutes">
|
||||
<item quantity="other">%d分钟</item>
|
||||
<item quantity="other">%d 分钟</item>
|
||||
</plurals>
|
||||
<plurals name="x_hours">
|
||||
<item quantity="other">%d 小时</item>
|
||||
@@ -108,9 +109,20 @@
|
||||
<string name="action_vote">投票</string>
|
||||
<string name="tap_to_reveal">点击显示</string>
|
||||
<string name="delete">删除</string>
|
||||
<string name="delete_and_redraft">删除以重新编辑</string>
|
||||
<string name="confirm_delete_title">删除嘟文</string>
|
||||
<string name="confirm_delete_and_redraft_title">删除嘟文并重新编辑</string>
|
||||
<string name="confirm_delete">确定要删除这条嘟文吗?</string>
|
||||
<string name="confirm_delete_and_redraft">确定要删除这条嘟文并重新编辑吗?</string>
|
||||
<string name="deleting">正在删除…</string>
|
||||
<string name="pin_post">置顶</string>
|
||||
<string name="confirm_pin_post_title">置顶嘟文</string>
|
||||
<string name="confirm_pin_post">确定要在你的资料页置顶这条嘟文吗?</string>
|
||||
<string name="pinning">正在置顶嘟文…</string>
|
||||
<string name="unpin_post">取消置顶</string>
|
||||
<string name="confirm_unpin_post_title">取消嘟文置顶</string>
|
||||
<string name="confirm_unpin_post">确定不再置顶这条嘟文吗?</string>
|
||||
<string name="unpinning">正在取消置顶…</string>
|
||||
<string name="notification_channel_audio_player">音频播放</string>
|
||||
<string name="play">播放</string>
|
||||
<string name="pause">暂停</string>
|
||||
@@ -188,6 +200,7 @@
|
||||
<string name="resent_email">邮件已发送</string>
|
||||
<string name="compose_hint">写下你的想法</string>
|
||||
<string name="content_warning">内容警告</string>
|
||||
<string name="image_description">图片描述</string>
|
||||
<string name="add_image_description">添加图片描述…</string>
|
||||
<string name="retry_upload">重新上传</string>
|
||||
<string name="image_upload_failed">图片上传失败</string>
|
||||
@@ -198,6 +211,7 @@
|
||||
<string name="alt_text_subtitle">备注可以为视障人士描述你的图片,请尽量只包含足以理解内容的信息。</string>
|
||||
<string name="alt_text_hint">例如,镜头前有一只狗眯着眼睛警惕地看着四周。</string>
|
||||
<string name="visibility_public">公开</string>
|
||||
<string name="visibility_unlisted">不公开</string>
|
||||
<string name="visibility_followers_only">仅关注者</string>
|
||||
<string name="visibility_private">仅提及的人</string>
|
||||
<string name="search_all">全部</string>
|
||||
@@ -257,6 +271,8 @@
|
||||
<string name="button_reblog">转发</string>
|
||||
<string name="button_favorite">喜欢</string>
|
||||
<string name="button_share">分享</string>
|
||||
<string name="button_bookmark">添加到书签</string>
|
||||
<string name="bookmarks">书签</string>
|
||||
<string name="media_no_description">没有描述的媒体</string>
|
||||
<string name="add_media">添加媒体</string>
|
||||
<string name="add_poll">发起投票</string>
|
||||
@@ -286,10 +302,12 @@
|
||||
<string name="downloading">正在下载…</string>
|
||||
<string name="no_app_to_handle_action">未找到可处理此操作的应用</string>
|
||||
<string name="local_timeline">本站时间轴</string>
|
||||
<string name="federated_timeline">联邦时间轴</string>
|
||||
<string name="trending_posts_info_banner">这是在你的Mastodon角落备受关注的内容。</string>
|
||||
<string name="trending_hashtags_info_banner">这是在你的Mastodon角落备受关注的话题。</string>
|
||||
<string name="trending_links_info_banner">这是在你的Mastodon角落分享最多的新闻故事。</string>
|
||||
<string name="local_timeline_info_banner">这是你所在的Mastodon服务器上的用户发布的最新嘟文。</string>
|
||||
<string name="federated_timeline_info_banner">这是在你的联邦宇宙中最新发布的嘟文。</string>
|
||||
<string name="dismiss">驳回</string>
|
||||
<string name="see_new_posts">查看新嘟文</string>
|
||||
<string name="load_missing_posts">加载嘟文</string>
|
||||
|
||||
@@ -19,17 +19,17 @@
|
||||
<color name="gray_50">#f5f5f9</color>
|
||||
<color name="gray_25">#f7f8fa</color>
|
||||
|
||||
<color name="primary_25">#fafaff</color>
|
||||
<color name="primary_50">#f4f3ff</color>
|
||||
<color name="primary_100">#ebebff</color>
|
||||
<color name="primary_200">#d7d7ff</color>
|
||||
<color name="primary_300">#c2c2ff</color>
|
||||
<color name="primary_400">#9999ff</color>
|
||||
<color name="primary_500">#6364ff</color>
|
||||
<color name="primary_600">#562cfc</color>
|
||||
<color name="primary_700">#431cbb</color>
|
||||
<color name="primary_800">#2f0c7a</color>
|
||||
<color name="primary_900">#17063b</color>
|
||||
<color name="primary_25">#fffafd</color>
|
||||
<color name="primary_50">#fbeaf6</color>
|
||||
<color name="primary_100">#f7d4ee</color>
|
||||
<color name="primary_200">#f4bfe7</color>
|
||||
<color name="primary_300">#f0aade</color>
|
||||
<color name="primary_400">#ec94d6</color>
|
||||
<color name="primary_500">#e780cd</color>
|
||||
<color name="primary_600">#e055bd</color>
|
||||
<color name="primary_700">#d92aad</color>
|
||||
<color name="primary_800">#ae218a</color>
|
||||
<color name="primary_900">#6d1556</color>
|
||||
<color name="error_25">#FFFBFA</color>
|
||||
<color name="error_50">#FEF3F2</color>
|
||||
<color name="error_100">#FEE4E2</color>
|
||||
@@ -92,6 +92,7 @@
|
||||
<color name="highlight_over_light">#18000000</color>
|
||||
|
||||
<color name="favorite_selected">@color/warning_500</color>
|
||||
<color name="bookmark_selected">@color/success_500</color>
|
||||
<color name="boost_selected">@color/primary_500</color>
|
||||
|
||||
<color name="shortcut_icon_background">@color/gray_100</color>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
<item name="profile_posts" type="id"/>
|
||||
<item name="profile_posts_with_replies" type="id"/>
|
||||
<item name="profile_pinned_posts" type="id"/>
|
||||
<item name="profile_media" type="id"/>
|
||||
<item name="profile_about" type="id"/>
|
||||
|
||||
@@ -12,6 +13,7 @@
|
||||
<item name="discover_news" type="id"/>
|
||||
<item name="discover_users" type="id"/>
|
||||
<item name="discover_local_timeline" type="id"/>
|
||||
<item name="discover_federated_timeline" type="id"/>
|
||||
|
||||
<item name="notifications_all" type="id"/>
|
||||
<item name="notifications_mentions" type="id"/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">Mastodon</string>
|
||||
<string name="app_name" translatable="false">Mastodos</string>
|
||||
|
||||
<string name="get_started">Get started</string>
|
||||
<string name="log_in">Log in</string>
|
||||
@@ -47,6 +47,7 @@
|
||||
</plurals>
|
||||
<string name="posts">Posts</string>
|
||||
<string name="posts_and_replies">Posts and Replies</string>
|
||||
<string name="pinned_posts">Pinned</string>
|
||||
<string name="media">Media</string>
|
||||
<string name="profile_about">About</string>
|
||||
<string name="button_follow">Follow</string>
|
||||
@@ -126,9 +127,20 @@
|
||||
<string name="action_vote">Vote</string>
|
||||
<string name="tap_to_reveal">Tap to reveal</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="delete_and_redraft">Delete and re-draft</string>
|
||||
<string name="confirm_delete_title">Delete Post</string>
|
||||
<string name="confirm_delete_and_redraft_title">Delete and re-draft Post</string>
|
||||
<string name="confirm_delete">Are you sure you want to delete this post?</string>
|
||||
<string name="confirm_delete_and_redraft">Are you sure you want to delete and re-draft this post?</string>
|
||||
<string name="deleting">Deleting…</string>
|
||||
<string name="pin_post">Pin to profile</string>
|
||||
<string name="confirm_pin_post_title">Pin post to profile</string>
|
||||
<string name="confirm_pin_post">Do you want to pin this post to your profile?</string>
|
||||
<string name="pinning">Pinning post…</string>
|
||||
<string name="unpin_post">Unpin from profile</string>
|
||||
<string name="confirm_unpin_post_title">Unpin post from profile</string>
|
||||
<string name="confirm_unpin_post">Are you sure you want to unpin this post?</string>
|
||||
<string name="unpinning">Unpinning post…</string>
|
||||
<string name="notification_channel_audio_player">Audio playback</string>
|
||||
<string name="play">Play</string>
|
||||
<string name="pause">Pause</string>
|
||||
@@ -209,6 +221,7 @@
|
||||
<string name="compose_hint">Type or paste what\'s on your mind</string>
|
||||
<string name="content_warning">Content warning</string>
|
||||
<string name="add_image_description">Add image description…</string>
|
||||
<string name="image_description">Image description</string>
|
||||
<string name="retry_upload">Retry upload</string>
|
||||
<string name="edit_image">Edit image</string>
|
||||
<string name="save">Save</string>
|
||||
@@ -216,6 +229,7 @@
|
||||
<string name="alt_text_subtitle">Alt text describes your photos for people with low or no vision. Try to only include enough detail to understand the context.</string>
|
||||
<string name="alt_text_hint">e.g. A dog looking around suspiciously with narrowed eyes at the camera.</string>
|
||||
<string name="visibility_public">Public</string>
|
||||
<string name="visibility_unlisted">Unlisted</string>
|
||||
<string name="visibility_followers_only">Followers only</string>
|
||||
<string name="visibility_private">Only people I mention</string>
|
||||
<string name="search_all">All</string>
|
||||
@@ -276,6 +290,8 @@
|
||||
<string name="button_reblog">Reblog</string>
|
||||
<string name="button_favorite">Favorite</string>
|
||||
<string name="button_share">Share</string>
|
||||
<string name="button_bookmark">Bookmark</string>
|
||||
<string name="bookmarks">Bookmarks</string>
|
||||
<string name="media_no_description">Media without description</string>
|
||||
<string name="add_media">Add media</string>
|
||||
<string name="add_poll">Add a poll</string>
|
||||
@@ -305,10 +321,12 @@
|
||||
<string name="downloading">Downloading…</string>
|
||||
<string name="no_app_to_handle_action">There\'s no app to handle this action</string>
|
||||
<string name="local_timeline">Community</string>
|
||||
<string name="federated_timeline">Federation</string>
|
||||
<string name="trending_posts_info_banner">These are the posts gaining traction in your corner of Mastodon.</string>
|
||||
<string name="trending_hashtags_info_banner">These are the hashtags gaining traction in your corner of Mastodon.</string>
|
||||
<string name="trending_links_info_banner">These are the news stories being shared the most in your corner of Mastodon.</string>
|
||||
<string name="local_timeline_info_banner">These are the most recent posts by the people who use the same Mastodon server as you.</string>
|
||||
<string name="federated_timeline_info_banner">These are the most recent posts by the people in your federation.</string>
|
||||
<string name="dismiss">Dismiss</string>
|
||||
<string name="see_new_posts">See new posts</string>
|
||||
<string name="load_missing_posts">Load missing posts</string>
|
||||
|
||||