Merge branch 'main' into fork
This commit is contained in:
@@ -8,7 +8,6 @@
|
||||
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>
|
||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
|
||||
<permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.api;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonIOException;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -26,7 +27,10 @@ public class JsonObjectRequestBody extends RequestBody{
|
||||
public void writeTo(BufferedSink sink) throws IOException{
|
||||
try{
|
||||
OutputStreamWriter writer=new OutputStreamWriter(sink.outputStream(), StandardCharsets.UTF_8);
|
||||
MastodonAPIController.gson.toJson(obj, writer);
|
||||
if(obj instanceof JsonElement)
|
||||
writer.write(obj.toString());
|
||||
else
|
||||
MastodonAPIController.gson.toJson(obj, writer);
|
||||
writer.flush();
|
||||
}catch(JsonIOException x){
|
||||
throw new IOException(x);
|
||||
|
||||
@@ -365,6 +365,8 @@ public class PushSubscriptionManager{
|
||||
}
|
||||
|
||||
private static void registerAllAccountsForPush(boolean forceReRegister){
|
||||
if(!arePushNotificationsAvailable())
|
||||
return;
|
||||
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
||||
if(session.pushSubscription==null || forceReRegister)
|
||||
session.getPushSubscriptionManager().registerAccountForPush(session.pushSubscription);
|
||||
|
||||
@@ -63,36 +63,6 @@ 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");
|
||||
@@ -130,4 +100,34 @@ public class StatusInteractionController{
|
||||
status.reblogsCount--;
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,21 @@ public class IsoInstantTypeAdapter extends TypeAdapter<Instant>{
|
||||
in.nextNull();
|
||||
return null;
|
||||
}
|
||||
try{
|
||||
return DateTimeFormatter.ISO_INSTANT.parse(in.nextString(), Instant::from);
|
||||
}catch(DateTimeParseException x){
|
||||
String nextString;
|
||||
try {
|
||||
nextString = in.nextString();
|
||||
}catch(Exception e){
|
||||
return null;
|
||||
}
|
||||
|
||||
try{
|
||||
return DateTimeFormatter.ISO_INSTANT.parse(nextString, Instant::from);
|
||||
}catch(DateTimeParseException x){}
|
||||
|
||||
try{
|
||||
return DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(nextString, Instant::from);
|
||||
}catch(DateTimeParseException x){}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.joinmastodon.android.api.gson;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
public class JsonArrayBuilder{
|
||||
private JsonArray arr=new JsonArray();
|
||||
|
||||
public JsonArrayBuilder add(JsonElement el){
|
||||
arr.add(el);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JsonArrayBuilder add(String el){
|
||||
arr.add(el);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JsonArrayBuilder add(Number el){
|
||||
arr.add(el);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JsonArrayBuilder add(boolean el){
|
||||
arr.add(el);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JsonArrayBuilder add(JsonObjectBuilder el){
|
||||
arr.add(el.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
public JsonArrayBuilder add(JsonArrayBuilder el){
|
||||
arr.add(el.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
public JsonArray build(){
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.joinmastodon.android.api.gson;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public class JsonObjectBuilder{
|
||||
private JsonObject obj=new JsonObject();
|
||||
|
||||
public JsonObjectBuilder add(String key, JsonElement el){
|
||||
obj.add(key, el);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JsonObjectBuilder add(String key, String el){
|
||||
obj.addProperty(key, el);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JsonObjectBuilder add(String key, Number el){
|
||||
obj.addProperty(key, el);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JsonObjectBuilder add(String key, boolean el){
|
||||
obj.addProperty(key, el);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JsonObjectBuilder add(String key, JsonObjectBuilder el){
|
||||
obj.add(key, el.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
public JsonObjectBuilder add(String key, JsonArrayBuilder el){
|
||||
obj.add(key, el.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
public JsonObject build(){
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
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.List;
|
||||
|
||||
import okhttp3.Response;
|
||||
|
||||
public class GetFavourites extends MastodonAPIRequest<List<Status>>{
|
||||
private String maxId;
|
||||
|
||||
public GetFavourites(String maxID, String minID, int limit){
|
||||
super(HttpMethod.GET, "/favourites", 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;
|
||||
}
|
||||
}
|
||||
@@ -2,49 +2,15 @@ package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.FollowSuggestion;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.Response;
|
||||
|
||||
public class GetFollowRequests extends MastodonAPIRequest<List<Account>>{
|
||||
private String maxId;
|
||||
|
||||
public GetFollowRequests(String maxID, String minID, int limit){
|
||||
public class GetFollowRequests extends HeaderPaginationRequest<Account>{
|
||||
public GetFollowRequests(String maxID, int limit){
|
||||
super(HttpMethod.GET, "/follow_requests", 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<Account> respObj, Response httpResponse) throws IOException {
|
||||
super.validateAndPostprocessResponse(respObj, httpResponse);
|
||||
// <https://mastodon.social/api/v1/follow_requests?max_id=268962>; rel="next",
|
||||
// <https://mastodon.social/api/v1/follow_requests?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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.joinmastodon.android.api.requests.markers;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.gson.JsonObjectBuilder;
|
||||
import org.joinmastodon.android.model.Marker;
|
||||
|
||||
public class SaveMarkers extends MastodonAPIRequest<SaveMarkers.Response>{
|
||||
public SaveMarkers(String lastSeenHomePostID, String lastSeenNotificationID){
|
||||
super(HttpMethod.POST, "/markers", Response.class);
|
||||
JsonObjectBuilder builder=new JsonObjectBuilder();
|
||||
if(lastSeenHomePostID!=null)
|
||||
builder.add("home", new JsonObjectBuilder().add("last_read_id", lastSeenHomePostID));
|
||||
if(lastSeenNotificationID!=null)
|
||||
builder.add("notifications", new JsonObjectBuilder().add("last_read_id", lastSeenNotificationID));
|
||||
setRequestBody(builder.build());
|
||||
}
|
||||
|
||||
public static class Response{
|
||||
public Marker home, notifications;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
public class GetBookmarkedStatuses extends HeaderPaginationRequest<Status>{
|
||||
public GetBookmarkedStatuses(String maxID, int limit){
|
||||
super(HttpMethod.GET, "/bookmarks", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", limit+"");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
public class GetFavoritedStatuses extends HeaderPaginationRequest<Status>{
|
||||
public GetFavoritedStatuses(String maxID, int limit){
|
||||
super(HttpMethod.GET, "/favourites", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", limit+"");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.joinmastodon.android.events;
|
||||
|
||||
public class RemoveAccountPostsEvent{
|
||||
public final String accountID;
|
||||
public final String postsByAccountID;
|
||||
public final boolean isUnfollow;
|
||||
|
||||
public RemoveAccountPostsEvent(String accountID, String postsByAccountID, boolean isUnfollow){
|
||||
this.accountID=accountID;
|
||||
this.postsByAccountID=postsByAccountID;
|
||||
this.isUnfollow=isUnfollow;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import org.joinmastodon.android.model.Status;
|
||||
public class StatusCountersUpdatedEvent{
|
||||
public String id;
|
||||
public long favorites, reblogs, replies;
|
||||
public boolean favorited, reblogged, pinned;
|
||||
public boolean favorited, reblogged, bookmarked, pinned;
|
||||
|
||||
public StatusCountersUpdatedEvent(Status s){
|
||||
id=s.id;
|
||||
@@ -14,6 +14,7 @@ public class StatusCountersUpdatedEvent{
|
||||
replies=s.repliesCount;
|
||||
favorited=s.favourited;
|
||||
reblogged=s.reblogged;
|
||||
bookmarked=s.bookmarked;
|
||||
pinned=s.pinned;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@ package org.joinmastodon.android.events;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
public class StatusCreatedEvent{
|
||||
public Status status;
|
||||
public final Status status;
|
||||
public final String accountID;
|
||||
|
||||
public StatusCreatedEvent(Status status){
|
||||
public StatusCreatedEvent(Status status, String accountID){
|
||||
this.status=status;
|
||||
this.accountID=accountID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.view.View;
|
||||
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.RemoveAccountPostsEvent;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.events.StatusUnpinnedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
@@ -109,4 +110,9 @@ public class AccountTimelineFragment extends StatusListFragment{
|
||||
displayItems.subList(index, lastIndex).clear();
|
||||
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,7 +444,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.loading, false)
|
||||
.wrapProgress(getActivity(), R.string.loading, true)
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetBookmarkedStatuses;
|
||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class BookmarkedStatusListFragment extends StatusListFragment{
|
||||
private String nextMaxID;
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
setTitle(R.string.bookmarks);
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetBookmarkedStatuses(offset==0 ? null : nextMaxID, count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(HeaderPaginationList<Status> result){
|
||||
if(result.nextPageUri!=null)
|
||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||
else
|
||||
nextMaxID=null;
|
||||
onDataLoaded(result, nextMaxID!=null);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,6 @@ 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;
|
||||
@@ -52,7 +51,6 @@ import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.twitter.twittertext.Regex;
|
||||
import com.twitter.twittertext.TwitterTextEmojiRegex;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
@@ -102,7 +100,6 @@ import org.parceler.Parcels;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -133,21 +130,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
private static final Pattern AUTO_COMPLETE_PATTERN=Pattern.compile("(?<!\\w)(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+)|:([a-zA-Z0-9_]+))");
|
||||
private static final Pattern HIGHLIGHT_PATTERN=Pattern.compile("(?<!\\w)(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+))");
|
||||
|
||||
private static final String VALID_URL_PATTERN_STRING =
|
||||
"(" + // $1 total match
|
||||
"(" + Regex.URL_VALID_PRECEDING_CHARS + ")" + // $2 Preceding character
|
||||
"(" + // $3 URL
|
||||
"(https?://)" + // $4 Protocol (optional)
|
||||
"(" + Regex.URL_VALID_DOMAIN + ")" + // $5 Domain(s)
|
||||
"(?::(" + Regex.URL_VALID_PORT_NUMBER + "))?" + // $6 Port number (optional)
|
||||
"(/" +
|
||||
Regex.URL_VALID_PATH + "*+" +
|
||||
")?" + // $7 URL Path and anchor
|
||||
"(\\?" + Regex.URL_VALID_URL_QUERY_CHARS + "*" + // $8 Query String
|
||||
Regex.URL_VALID_URL_QUERY_ENDING_CHARS + ")?" +
|
||||
")" +
|
||||
")";
|
||||
private static final Pattern URL_PATTERN=Pattern.compile(VALID_URL_PATTERN_STRING, Pattern.CASE_INSENSITIVE);
|
||||
@SuppressLint("NewApi") // this class actually exists on 6.0
|
||||
private final BreakIterator breakIterator=BreakIterator.getCharacterInstance();
|
||||
|
||||
@@ -644,7 +626,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
|
||||
String countableText=TwitterTextEmojiRegex.VALID_EMOJI_PATTERN.matcher(
|
||||
MENTION_PATTERN.matcher(
|
||||
URL_PATTERN.matcher(text).replaceAll("$2xxxxxxxxxxxxxxxxxxxxxxx")
|
||||
HtmlParser.URL_PATTERN.matcher(text).replaceAll("$2xxxxxxxxxxxxxxxxxxxxxxx")
|
||||
).replaceAll("$1@$3")
|
||||
).replaceAll("x");
|
||||
charCount=0;
|
||||
@@ -738,7 +720,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
wm.removeView(sendingOverlay);
|
||||
sendingOverlay=null;
|
||||
if(editingStatus==null){
|
||||
E.post(new StatusCreatedEvent(result));
|
||||
E.post(new StatusCreatedEvent(result, accountID));
|
||||
if(replyTo!=null){
|
||||
replyTo.repliesCount++;
|
||||
E.post(new StatusCountersUpdatedEvent(replyTo));
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetFavoritedStatuses;
|
||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class FavoritedStatusListFragment extends StatusListFragment{
|
||||
private String nextMaxID;
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
setTitle(R.string.your_favorites);
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetFavoritedStatuses(offset==0 ? null : nextMaxID, count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(HeaderPaginationList<Status> result){
|
||||
if(result.nextPageUri!=null)
|
||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||
else
|
||||
nextMaxID=null;
|
||||
onDataLoaded(result, nextMaxID!=null);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetFavourites;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class FavoritesListFragment extends StatusListFragment{
|
||||
|
||||
private String accountID;
|
||||
private String lastMaxId=null;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
accountID=getArguments().getString("account");
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
setTitle(R.string.favorited_posts);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count) {
|
||||
GetFavourites b=new GetFavourites(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);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
@@ -50,7 +51,7 @@ public class FollowRequestsListFragment extends BaseRecyclerFragment<FollowReque
|
||||
private String accountID;
|
||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||
private GetAccountRelationships relationshipsRequest;
|
||||
private String lastMaxId=null;
|
||||
private String nextMaxID;
|
||||
|
||||
public FollowRequestsListFragment(){
|
||||
super(20);
|
||||
@@ -75,10 +76,14 @@ public class FollowRequestsListFragment extends BaseRecyclerFragment<FollowReque
|
||||
relationshipsRequest.cancel();
|
||||
relationshipsRequest=null;
|
||||
}
|
||||
currentRequest=new GetFollowRequests(offset>0 ? lastMaxId : null, null, count)
|
||||
currentRequest=new GetFollowRequests(offset==0 ? null : nextMaxID, count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Account> result){
|
||||
public void onSuccess(HeaderPaginationList<Account> result){
|
||||
if(result.nextPageUri!=null)
|
||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||
else
|
||||
nextMaxID=null;
|
||||
onDataLoaded(result.stream().map(AccountWrapper::new).collect(Collectors.toList()), false);
|
||||
loadRelationships();
|
||||
}
|
||||
|
||||
@@ -432,4 +432,9 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
public void onSelfUpdateStateChanged(SelfUpdateStateChangedEvent ev){
|
||||
updateUpdateState(ev.state);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldRemoveAccountPostsWhenUnfollowing(){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
|
||||
import org.joinmastodon.android.events.FollowRequestHandledEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
||||
@@ -29,8 +30,6 @@ import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
@@ -167,9 +166,9 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||
}
|
||||
|
||||
public void refreshFollowRequestsBadge() {
|
||||
new GetFollowRequests(null, null, 1).setCallback(new Callback<>() {
|
||||
new GetFollowRequests(null, 1).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(List<Account> accounts) {
|
||||
public void onSuccess(HeaderPaginationList<Account> accounts) {
|
||||
getToolbar().getMenu().findItem(R.id.follow_requests).setVisible(!accounts.isEmpty());
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,11 @@ import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.NotificationDeletedEvent;
|
||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.PaginatedResponse;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
@@ -27,6 +29,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
@@ -122,6 +125,10 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
.collect(Collectors.toSet());
|
||||
loadRelationships(needRelationships);
|
||||
maxID=result.maxID;
|
||||
|
||||
if(offset==0 && !result.items.isEmpty()){
|
||||
new SaveMarkers(null, result.items.get(0).id).exec(accountID);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -192,14 +199,23 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onNotificationDeleted(NotificationDeletedEvent ev) {
|
||||
Notification notification = getNotificationByID(ev.id);
|
||||
if(notification==null)
|
||||
public void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
|
||||
if(!ev.accountID.equals(accountID) || ev.isUnfollow)
|
||||
return;
|
||||
data.remove(notification);
|
||||
List<Notification> toRemove=Stream.concat(data.stream(), preloadedData.stream())
|
||||
.filter(n->n.account!=null && n.account.id.equals(ev.postsByAccountID))
|
||||
.collect(Collectors.toList());
|
||||
for(Notification n:toRemove){
|
||||
removeNotification(n);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeNotification(Notification n){
|
||||
data.remove(n);
|
||||
preloadedData.remove(n);
|
||||
int index=-1;
|
||||
for(int i=0;i<displayItems.size();i++){
|
||||
if(ev.id.equals(displayItems.get(i).parentID)){
|
||||
if(n.id.equals(displayItems.get(i).parentID)){
|
||||
index=i;
|
||||
break;
|
||||
}
|
||||
@@ -208,11 +224,10 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
return;
|
||||
int lastIndex;
|
||||
for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){
|
||||
if(!displayItems.get(lastIndex).parentID.equals(ev.id))
|
||||
if(!displayItems.get(lastIndex).parentID.equals(n.id))
|
||||
break;
|
||||
}
|
||||
displayItems.subList(index, lastIndex).clear();
|
||||
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
||||
}
|
||||
|
||||
private abstract class BaseViewHolder extends BindableViewHolder<AccountField>{
|
||||
private ShapeDrawable background=new ShapeDrawable();
|
||||
protected ShapeDrawable background=new ShapeDrawable();
|
||||
|
||||
public BaseViewHolder(int layout){
|
||||
super(getActivity(), layout, list);
|
||||
@@ -220,6 +220,20 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
||||
super.onBind(item);
|
||||
title.setText(item.parsedName);
|
||||
value.setText(item.parsedValue);
|
||||
if(item.verifiedAt!=null){
|
||||
background.getPaint().setColor(UiUtils.isDarkTheme() ? 0xFF49595a : 0xFFd7e3da);
|
||||
int textColor=UiUtils.isDarkTheme() ? 0xFF89bb9c : 0xFF5b8e63;
|
||||
value.setTextColor(textColor);
|
||||
value.setLinkTextColor(textColor);
|
||||
Drawable check=getResources().getDrawable(R.drawable.ic_fluent_checkmark_24_regular, getActivity().getTheme()).mutate();
|
||||
check.setTint(textColor);
|
||||
value.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, check, null);
|
||||
}else{
|
||||
background.getPaint().setColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||
value.setTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||
value.setLinkTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.colorAccent));
|
||||
value.setCompoundDrawables(null, null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -10,7 +10,6 @@ import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Outline;
|
||||
@@ -19,8 +18,6 @@ import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ImageSpan;
|
||||
@@ -286,6 +283,18 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
followersBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
||||
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
||||
|
||||
username.setOnLongClickListener(v->{
|
||||
String username=account.acct;
|
||||
if(!username.contains("@")){
|
||||
username+="@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
||||
}
|
||||
getActivity().getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, "@"+username));
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.TIRAMISU){ // Android 13+ SystemUI shows its own thing when you put things into the clipboard
|
||||
Toast.makeText(getActivity(), R.string.text_copied, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return sizeWrapper;
|
||||
}
|
||||
|
||||
@@ -453,16 +462,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
// noinspection SetTextI18n
|
||||
username.setText('@'+account.acct+(isSelf ? ('@'+AccountSessionManager.getInstance().getAccount(accountID).domain) : ""));
|
||||
}
|
||||
username.setOnLongClickListener(l->{
|
||||
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(CLIPBOARD_SERVICE);
|
||||
Vibrator v = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText("Username", '@'+account.acct+'@'+AccountSessionManager.getInstance().getAccount(accountID).domain);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Toast.makeText(getActivity(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) v.vibrate(VibrationEffect.createOneShot(50, VibrationEffect.DEFAULT_AMPLITUDE));
|
||||
else v.vibrate(50);
|
||||
return true;
|
||||
});
|
||||
CharSequence parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||
if(TextUtils.isEmpty(parsedBio)){
|
||||
bio.setVisibility(View.GONE);
|
||||
@@ -548,17 +547,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
}
|
||||
if(relationship==null && !isOwnProfile)
|
||||
return;
|
||||
inflater.inflate(R.menu.profile, menu);
|
||||
inflater.inflate(isOwnProfile ? R.menu.profile_own : R.menu.profile, menu);
|
||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getDisplayUsername()));
|
||||
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.lists_with_user, account.getDisplayUsername()));
|
||||
if(isOwnProfile){
|
||||
for(int i=0;i<menu.size();i++){
|
||||
MenuItem item=menu.getItem(i);
|
||||
item.setVisible(item.getItemId()==R.id.share || item.getItemId()==R.id.bookmarks || item.getItemId()==R.id.manage_user_lists);
|
||||
}
|
||||
menu.findItem(R.id.favorites_list).setVisible(true);
|
||||
if(isOwnProfile)
|
||||
return;
|
||||
}
|
||||
|
||||
menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
|
||||
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername()));
|
||||
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getDisplayUsername()));
|
||||
@@ -582,16 +575,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
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.favorites_list) {
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(account));
|
||||
Nav.go(getActivity(), FavoritesListFragment.class, args);
|
||||
}else if(id==R.id.mute){
|
||||
confirmToggleMuted();
|
||||
}else if(id==R.id.block){
|
||||
@@ -623,6 +606,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.loading, false)
|
||||
.exec(accountID);
|
||||
}else if(id==R.id.bookmarks){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), BookmarkedStatusListFragment.class, args);
|
||||
}else if(id==R.id.favorites){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), FavoritedStatusListFragment.class, args);
|
||||
}else if(id==R.id.manage_user_lists){
|
||||
final Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
||||
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
@@ -198,7 +199,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
@Override
|
||||
public void onDestroy(){
|
||||
super.onDestroy();
|
||||
if(needUpdateNotificationSettings){
|
||||
if(needUpdateNotificationSettings && PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().updatePushSettings(pushSubscription);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.events.StatusDeletedEvent;
|
||||
@@ -18,6 +19,8 @@ import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
@@ -134,6 +137,40 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected boolean shouldRemoveAccountPostsWhenUnfollowing(){
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
|
||||
List<Status> toRemove=Stream.concat(data.stream(), preloadedData.stream())
|
||||
.filter(s->s.account.id.equals(ev.postsByAccountID) || (s.reblog!=null && s.reblog.account.id.equals(ev.postsByAccountID)))
|
||||
.collect(Collectors.toList());
|
||||
for(Status s:toRemove){
|
||||
removeStatus(s);
|
||||
}
|
||||
}
|
||||
|
||||
protected void removeStatus(Status status){
|
||||
data.remove(status);
|
||||
preloadedData.remove(status);
|
||||
int index=-1;
|
||||
for(int i=0;i<displayItems.size();i++){
|
||||
if(status.id.equals(displayItems.get(i).parentID)){
|
||||
index=i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(index==-1)
|
||||
return;
|
||||
int lastIndex;
|
||||
for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){
|
||||
if(!displayItems.get(lastIndex).parentID.equals(status.id))
|
||||
break;
|
||||
}
|
||||
displayItems.subList(index, lastIndex).clear();
|
||||
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
||||
}
|
||||
|
||||
public class EventListener{
|
||||
|
||||
@Subscribe
|
||||
@@ -165,28 +202,13 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
Status status=getStatusByID(ev.id);
|
||||
if(status==null)
|
||||
return;
|
||||
data.remove(status);
|
||||
preloadedData.remove(status);
|
||||
int index=-1;
|
||||
for(int i=0;i<displayItems.size();i++){
|
||||
if(ev.id.equals(displayItems.get(i).parentID)){
|
||||
index=i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(index==-1)
|
||||
return;
|
||||
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);
|
||||
removeStatus(status);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onStatusCreated(StatusCreatedEvent ev){
|
||||
if(!ev.accountID.equals(accountID))
|
||||
return;
|
||||
StatusListFragment.this.onStatusCreated(ev);
|
||||
}
|
||||
|
||||
@@ -206,5 +228,14 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
|
||||
if(!ev.accountID.equals(accountID))
|
||||
return;
|
||||
if(ev.isUnfollow && !shouldRemoveAccountPostsWhenUnfollowing())
|
||||
return;
|
||||
StatusListFragment.this.onRemoveAccountPostsEvent(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import android.widget.TextView;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
@@ -131,7 +132,10 @@ public class InstanceRulesFragment extends AppKitFragment{
|
||||
|
||||
@Override
|
||||
public void onBind(Instance.Rule item){
|
||||
title.setText(item.text);
|
||||
if(item.parsedText==null){
|
||||
item.parsedText=HtmlParser.parseLinks(item.text);
|
||||
}
|
||||
title.setText(item.parsedText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,10 @@ import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||
import org.joinmastodon.android.fragments.MastodonToolbarFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
@@ -130,6 +132,7 @@ public class ReportDoneFragment extends MastodonToolbarFragment{
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
Nav.finish(ReportDoneFragment.this);
|
||||
E.post(new RemoveAccountPostsEvent(accountID, reportAccount.id, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -134,6 +134,8 @@ public class Instance extends BaseModel{
|
||||
public static class Rule{
|
||||
public String id;
|
||||
public String text;
|
||||
|
||||
public transient CharSequence parsedText;
|
||||
}
|
||||
|
||||
@Parcel
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@AllFieldsAreRequired
|
||||
public class Marker extends BaseModel{
|
||||
public String lastReadId;
|
||||
public long version;
|
||||
public Instant updatedAt;
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "Marker{"+
|
||||
"lastReadId='"+lastReadId+'\''+
|
||||
", version="+version+
|
||||
", updatedAt="+updatedAt+
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ public class Poll extends BaseModel{
|
||||
@RequiredField
|
||||
public String id;
|
||||
public Instant expiresAt;
|
||||
public boolean expired;
|
||||
private boolean expired;
|
||||
public boolean multiple;
|
||||
public int votersCount;
|
||||
public boolean voted;
|
||||
@@ -48,6 +48,10 @@ public class Poll extends BaseModel{
|
||||
'}';
|
||||
}
|
||||
|
||||
public boolean isExpired(){
|
||||
return expired || (expiresAt!=null && expiresAt.isBefore(Instant.now()));
|
||||
}
|
||||
|
||||
@Parcel
|
||||
public static class Option{
|
||||
public String title;
|
||||
|
||||
@@ -130,6 +130,7 @@ public class Status extends BaseModel implements DisplayItemsParent{
|
||||
repliesCount=ev.replies;
|
||||
favourited=ev.favorited;
|
||||
reblogged=ev.reblogged;
|
||||
bookmarked=ev.bookmarked;
|
||||
pinned=ev.pinned;
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
|
||||
private void bindButton(TextView btn, long count){
|
||||
if(GlobalUserPreferences.showInteractionCounts && count>0 && !item.hideCounts){
|
||||
btn.setText(DecimalFormat.getIntegerInstance().format(count));
|
||||
btn.setText(UiUtils.abbreviateNumber(count));
|
||||
btn.setCompoundDrawablePadding(V.dp(8));
|
||||
}else{
|
||||
btn.setText("");
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.app.ProgressDialog;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
@@ -209,6 +210,8 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
});
|
||||
}else if(id==R.id.block_domain){
|
||||
UiUtils.confirmToggleBlockDomain(activity, item.parentFragment.getAccountID(), account.getDomain(), relationship!=null && relationship.domainBlocking, ()->{});
|
||||
}else if(id==R.id.bookmark){
|
||||
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -226,6 +229,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
if(item.hasVisibilityToggle){
|
||||
visibility.setImageResource(item.status.spoilerRevealed ? R.drawable.ic_visibility_off : R.drawable.ic_visibility);
|
||||
visibility.setContentDescription(item.parentFragment.getString(item.status.spoilerRevealed ? R.string.hide_content : R.string.reveal_content));
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
|
||||
visibility.setTooltipText(visibility.getContentDescription());
|
||||
}
|
||||
}
|
||||
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0);
|
||||
if(TextUtils.isEmpty(item.extraText)){
|
||||
@@ -306,6 +312,16 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
MenuItem block=menu.findItem(R.id.block);
|
||||
MenuItem report=menu.findItem(R.id.report);
|
||||
MenuItem follow=menu.findItem(R.id.follow);
|
||||
MenuItem bookmark=menu.findItem(R.id.bookmark);
|
||||
bookmark.setVisible(false);
|
||||
/* disabled in megalodon: add/remove bookmark is already available through status footer
|
||||
if(item.status!=null){
|
||||
bookmark.setVisible(true);
|
||||
bookmark.setTitle(item.status.bookmarked ? R.string.remove_bookmark : R.string.add_bookmark);
|
||||
}else{
|
||||
bookmark.setVisible(false);
|
||||
}
|
||||
*/
|
||||
if(isOwnPost){
|
||||
mute.setVisible(false);
|
||||
block.setVisible(false);
|
||||
|
||||
@@ -38,13 +38,13 @@ public class PollFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
@Override
|
||||
public void onBind(PollFooterStatusDisplayItem item){
|
||||
String text=item.parentFragment.getResources().getQuantityString(R.plurals.x_voters, item.poll.votersCount, item.poll.votersCount);
|
||||
if(item.poll.expiresAt!=null && !item.poll.expired){
|
||||
if(item.poll.expiresAt!=null && !item.poll.isExpired()){
|
||||
text+=" · "+UiUtils.formatTimeLeft(itemView.getContext(), item.poll.expiresAt);
|
||||
}else if(item.poll.expired){
|
||||
}else if(item.poll.isExpired()){
|
||||
text+=" · "+item.parentFragment.getString(R.string.poll_closed);
|
||||
}
|
||||
this.text.setText(text);
|
||||
button.setVisibility(item.poll.expired || item.poll.voted || !item.poll.multiple ? View.GONE : View.VISIBLE);
|
||||
button.setVisibility(item.poll.isExpired() || item.poll.voted || !item.poll.multiple ? View.GONE : View.VISIBLE);
|
||||
button.setEnabled(item.poll.selectedOptions!=null && !item.poll.selectedOptions.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
|
||||
this.poll=poll;
|
||||
text=HtmlParser.parseCustomEmoji(option.title, poll.emojis);
|
||||
emojiHelper.setText(text);
|
||||
showResults=poll.expired || poll.voted;
|
||||
showResults=poll.isExpired() || poll.voted;
|
||||
if(showResults && option.votesCount!=null && poll.votersCount>0){
|
||||
votesFraction=(float)option.votesCount/(float)poll.votersCount;
|
||||
int mostVotedCount=0;
|
||||
|
||||
@@ -2,8 +2,11 @@ package org.joinmastodon.android.ui.text;
|
||||
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.twitter.twittertext.Regex;
|
||||
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.model.Mention;
|
||||
@@ -28,6 +31,21 @@ import androidx.annotation.NonNull;
|
||||
|
||||
public class HtmlParser{
|
||||
private static final String TAG="HtmlParser";
|
||||
private static final String VALID_URL_PATTERN_STRING =
|
||||
"(" + // $1 total match
|
||||
"(" + Regex.URL_VALID_PRECEDING_CHARS + ")" + // $2 Preceding character
|
||||
"(" + // $3 URL
|
||||
"(https?://)" + // $4 Protocol (optional)
|
||||
"(" + Regex.URL_VALID_DOMAIN + ")" + // $5 Domain(s)
|
||||
"(?::(" + Regex.URL_VALID_PORT_NUMBER + "))?" + // $6 Port number (optional)
|
||||
"(/" +
|
||||
Regex.URL_VALID_PATH + "*+" +
|
||||
")?" + // $7 URL Path and anchor
|
||||
"(\\?" + Regex.URL_VALID_URL_QUERY_CHARS + "*" + // $8 Query String
|
||||
Regex.URL_VALID_URL_QUERY_ENDING_CHARS + ")?" +
|
||||
")" +
|
||||
")";
|
||||
public static final Pattern URL_PATTERN=Pattern.compile(VALID_URL_PATTERN_STRING, Pattern.CASE_INSENSITIVE);
|
||||
private static Pattern EMOJI_CODE_PATTERN=Pattern.compile(":([\\w]+):");
|
||||
|
||||
private HtmlParser(){}
|
||||
@@ -172,4 +190,18 @@ public class HtmlParser{
|
||||
public static String strip(String html){
|
||||
return Jsoup.clean(html, Safelist.none());
|
||||
}
|
||||
|
||||
public static CharSequence parseLinks(String text){
|
||||
Matcher matcher=URL_PATTERN.matcher(text);
|
||||
if(!matcher.find()) // Return the original string if there are no URLs
|
||||
return text;
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder(text);
|
||||
do{
|
||||
String url=matcher.group(3);
|
||||
if(TextUtils.isEmpty(matcher.group(4)))
|
||||
url="http://"+url;
|
||||
ssb.setSpan(new LinkSpan(url, null, LinkSpan.Type.URL, null), matcher.start(3), matcher.end(3), 0);
|
||||
}while(matcher.find()); // Find more URLs
|
||||
return ssb;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.events.FollowRequestHandledEvent;
|
||||
import org.joinmastodon.android.events.NotificationDeletedEvent;
|
||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||
import org.joinmastodon.android.events.StatusDeletedEvent;
|
||||
import org.joinmastodon.android.events.StatusUnpinnedEvent;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
@@ -345,6 +346,9 @@ public class UiUtils{
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
resultCallback.accept(result);
|
||||
if(!currentlyBlocked){
|
||||
E.post(new RemoveAccountPostsEvent(accountID, account.id, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -387,6 +391,9 @@ public class UiUtils{
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
resultCallback.accept(result);
|
||||
if(!currentlyMuted){
|
||||
E.post(new RemoveAccountPostsEvent(accountID, account.id, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -524,6 +531,9 @@ public class UiUtils{
|
||||
public void onSuccess(Relationship result){
|
||||
resultCallback.accept(result);
|
||||
progressCallback.accept(false);
|
||||
if(!result.following){
|
||||
E.post(new RemoveAccountPostsEvent(accountID, account.id, true));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -660,8 +670,7 @@ public class UiUtils{
|
||||
|
||||
public static void openURL(Context context, @Nullable String accountID, String url){
|
||||
Uri uri=Uri.parse(url);
|
||||
String accountDomain=accountID != null ? AccountSessionManager.getInstance().getAccount(accountID).domain : null;
|
||||
if(accountDomain!=null && "https".equals(uri.getScheme()) && accountDomain.equalsIgnoreCase(uri.getAuthority())){
|
||||
if(accountID!=null && "https".equals(uri.getScheme()) && AccountSessionManager.getInstance().getAccount(accountID).domain.equalsIgnoreCase(uri.getAuthority())){
|
||||
List<String> path=uri.getPathSegments();
|
||||
// Match URLs like https://mastodon.social/@Gargron/108132679274083591
|
||||
if(path.size()==2 && path.get(0).matches("^@[a-zA-Z0-9_]+$") && path.get(1).matches("^[0-9]+$")){
|
||||
|
||||
@@ -8,7 +8,6 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.DragEvent;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
@@ -22,7 +21,6 @@ import androidx.annotation.RequiresApi;
|
||||
|
||||
public class ComposeEditText extends EditText{
|
||||
private SelectionListener selectionListener;
|
||||
private MediaAcceptingInputConnection inputConnectionWrapper=new MediaAcceptingInputConnection();
|
||||
|
||||
public ComposeEditText(Context context){
|
||||
super(context);
|
||||
@@ -54,11 +52,10 @@ public class ComposeEditText extends EditText{
|
||||
// Support receiving images from keyboards
|
||||
@Override
|
||||
public InputConnection onCreateInputConnection(EditorInfo outAttrs){
|
||||
final var ic = super.onCreateInputConnection(outAttrs);
|
||||
final InputConnection ic=super.onCreateInputConnection(outAttrs);
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N_MR1){
|
||||
outAttrs.contentMimeTypes=selectionListener.onGetAllowedMediaMimeTypes();
|
||||
inputConnectionWrapper.setTarget(ic);
|
||||
return inputConnectionWrapper;
|
||||
return new MediaAcceptingInputConnection(ic);
|
||||
}
|
||||
return ic;
|
||||
}
|
||||
@@ -106,8 +103,8 @@ public class ComposeEditText extends EditText{
|
||||
}
|
||||
|
||||
private class MediaAcceptingInputConnection extends InputConnectionWrapper{
|
||||
public MediaAcceptingInputConnection(){
|
||||
super(null, true);
|
||||
public MediaAcceptingInputConnection(InputConnection conn){
|
||||
super(conn, false);
|
||||
}
|
||||
|
||||
@RequiresApi(api=Build.VERSION_CODES.N_MR1)
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class SplashLogoView extends ImageView{
|
||||
private Bitmap shadow;
|
||||
private Paint paint=new Paint();
|
||||
|
||||
public SplashLogoView(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public SplashLogoView(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public SplashLogoView(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas){
|
||||
if(shadow!=null){
|
||||
paint.setColor(0xBF000000);
|
||||
canvas.drawBitmap(shadow, 0, 0, paint);
|
||||
}
|
||||
super.onDraw(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh){
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
if(w!=oldw || h!=oldh)
|
||||
updateShadow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImageDrawable(@Nullable Drawable drawable){
|
||||
super.setImageDrawable(drawable);
|
||||
updateShadow();
|
||||
}
|
||||
|
||||
private void updateShadow(){
|
||||
int w=getWidth();
|
||||
int h=getHeight();
|
||||
Drawable drawable=getDrawable();
|
||||
if(w==0 || h==0 || drawable==null)
|
||||
return;
|
||||
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||
Bitmap temp=Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8);
|
||||
shadow=Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8);
|
||||
Canvas c=new Canvas(temp);
|
||||
c.translate(getWidth()/2f-drawable.getIntrinsicWidth()/2f, getHeight()/2f-drawable.getIntrinsicHeight()/2f);
|
||||
drawable.draw(c);
|
||||
c=new Canvas(shadow);
|
||||
Paint paint=new Paint();
|
||||
paint.setShadowLayer(V.dp(2), 0, 0, 0xff000000);
|
||||
c.drawBitmap(temp, 0, 0, paint);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 43 KiB |
@@ -1,3 +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"/>
|
||||
<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.748zm2.25-0.75c-0.414 0-0.75 0.336-0.75 0.75v13.196l4.873-2.842c0.233-0.137 0.522-0.137 0.755 0l4.873 2.842V6.748c0-0.414-0.336-0.75-0.75-0.75h-9zm9-3.998C17.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="M4.53 12.97c-0.293-0.293-0.767-0.293-1.06 0-0.293 0.293-0.293 0.767 0 1.06l4.5 4.5c0.293 0.293 0.767 0.293 1.06 0l11-11c0.293-0.293 0.293-0.767 0-1.06-0.293-0.293-0.767-0.293-1.06 0L8.5 16.94l-3.97-3.97z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
27
mastodon/src/main/res/drawable/splash_logo.xml
Normal file
27
mastodon/src/main/res/drawable/splash_logo.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="257dp"
|
||||
android:height="67dp"
|
||||
android:viewportWidth="313"
|
||||
android:viewportHeight="81">
|
||||
<path
|
||||
android:pathData="M72.95,18.45C71.82,9.95 64.5,3.24 55.85,1.95C54.38,1.73 48.85,0.93 36.02,0.93H35.92C23.09,0.93 20.34,1.73 18.87,1.95C10.44,3.22 2.76,9.23 0.88,17.84C-0,22.08 -0.1,26.78 0.07,31.09C0.31,37.27 0.36,43.43 0.91,49.6C1.29,53.69 1.95,57.74 2.9,61.73C4.68,69.11 11.86,75.25 18.9,77.75C26.43,80.35 34.53,80.79 42.29,79C43.14,78.79 43.98,78.56 44.82,78.29C46.71,77.68 48.92,77 50.55,75.81C50.57,75.8 50.59,75.77 50.6,75.75C50.61,75.72 50.62,75.7 50.62,75.66V69.7C50.62,69.7 50.62,69.65 50.6,69.62C50.6,69.6 50.57,69.58 50.55,69.56C50.53,69.55 50.5,69.54 50.48,69.53C50.45,69.53 50.43,69.53 50.41,69.53C45.43,70.73 40.33,71.34 35.23,71.33C26.43,71.33 24.06,67.09 23.39,65.34C22.85,63.82 22.5,62.22 22.36,60.61C22.36,60.59 22.36,60.57 22.37,60.54C22.37,60.52 22.39,60.49 22.42,60.48C22.44,60.47 22.46,60.46 22.49,60.44H22.57C27.46,61.64 32.48,62.25 37.51,62.25C38.72,62.25 39.92,62.25 41.14,62.21C46.19,62.06 51.52,61.81 56.51,60.82C56.63,60.8 56.76,60.77 56.87,60.75C64.72,59.21 72.19,54.42 72.95,42.27C72.97,41.79 73.04,37.25 73.04,36.76C73.04,35.07 73.58,24.79 72.96,18.48L72.95,18.45Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="36.62"
|
||||
android:startY="0.93"
|
||||
android:endX="36.62"
|
||||
android:endY="80.07"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF6364FF"/>
|
||||
<item android:offset="1" android:color="#FF563ACC"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M14.81,23.2C14.81,20.72 16.77,18.72 19.2,18.72C21.62,18.72 23.58,20.73 23.58,23.2C23.58,25.67 21.62,27.68 19.2,27.68C16.77,27.68 14.81,25.67 14.81,23.2Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M80.02,27.06V47.66H72.03V27.67C72.03,23.45 70.3,21.32 66.83,21.32C63,21.32 61.07,23.87 61.07,28.87V39.82H53.14V28.87C53.14,23.84 51.24,21.32 47.38,21.32C43.92,21.32 42.18,23.45 42.18,27.67V47.65H34.21V27.06C34.21,22.86 35.25,19.51 37.35,17.03C39.53,14.54 42.37,13.29 45.89,13.29C49.97,13.29 53.07,14.9 55.11,18.11L57.11,21.52L59.1,18.11C61.14,14.91 64.23,13.29 68.32,13.29C71.84,13.29 74.69,14.55 76.86,17.03C78.96,19.51 80.01,22.83 80.01,27.06H80.02ZM107.49,37.3C109.15,35.51 109.93,33.29 109.93,30.59C109.93,27.89 109.14,25.65 107.49,23.94C105.91,22.15 103.89,21.3 101.45,21.3C99.02,21.3 97.01,22.15 95.41,23.94C93.83,25.65 93.04,27.89 93.04,30.59C93.04,33.29 93.83,35.53 95.41,37.3C97,39 99.02,39.87 101.45,39.87C103.89,39.87 105.9,39.02 107.49,37.3ZM109.93,14.12H117.8V47.06H109.93V43.18C107.55,46.41 104.26,48 99.99,48C95.71,48 92.42,46.36 89.5,43C86.64,39.64 85.18,35.48 85.18,30.61C85.18,25.74 86.65,21.65 89.5,18.29C92.43,14.93 95.92,13.23 99.99,13.23C104.06,13.23 107.55,14.81 109.93,18.02V14.14V14.12ZM144.26,29.97C146.58,31.76 147.73,34.25 147.67,37.41C147.67,40.77 146.52,43.41 144.14,45.24C141.76,47.03 138.89,47.94 135.41,47.94C129.13,47.94 124.87,45.3 122.61,40.11L129.43,35.96C130.34,38.78 132.35,40.25 135.41,40.25C138.22,40.25 139.62,39.33 139.62,37.42C139.62,36.03 137.79,34.78 134.07,33.8C132.66,33.41 131.5,33.01 130.6,32.68C129.31,32.16 128.22,31.56 127.31,30.83C125.05,29.04 123.9,26.68 123.9,23.65C123.9,20.42 124.99,17.85 127.19,16C129.45,14.09 132.19,13.18 135.48,13.18C140.73,13.18 144.56,15.48 147.07,20.16L140.37,24.1C139.4,21.86 137.74,20.74 135.48,20.74C133.11,20.74 131.95,21.65 131.95,23.44C131.95,24.83 133.78,26.08 137.5,27.06C140.37,27.72 142.63,28.7 144.26,29.97H144.27H144.26ZM169.26,22.27H162.37V35.98C162.37,37.63 162.98,38.63 164.15,39.08C165,39.4 166.71,39.47 169.27,39.34V47.05C163.98,47.71 160.14,47.17 157.88,45.41C155.62,43.7 154.53,40.53 154.53,36V22.27H149.23V14.1H154.53V7.46L162.39,4.89V14.12H169.29V22.29H169.27L169.26,22.27ZM194.34,37.1C195.92,35.4 196.71,33.22 196.71,30.58C196.71,27.94 195.92,25.78 194.34,24.05C192.74,22.35 190.79,21.48 188.42,21.48C186.04,21.48 184.09,22.33 182.49,24.05C180.97,25.84 180.18,28 180.18,30.58C180.18,33.16 180.97,35.31 182.49,37.1C184.08,38.81 186.04,39.67 188.42,39.67C190.79,39.67 192.74,38.82 194.34,37.1ZM176.96,42.96C173.85,39.6 172.32,35.52 172.32,30.58C172.32,25.63 173.85,21.62 176.96,18.26C180.07,14.9 183.91,13.19 188.42,13.19C192.92,13.19 196.77,14.9 199.87,18.26C202.97,21.62 204.57,25.77 204.57,30.58C204.57,35.39 202.97,39.6 199.87,42.96C196.76,46.32 192.98,47.96 188.42,47.96C183.85,47.96 180.06,46.32 176.96,42.96ZM230.86,37.29C232.45,35.5 233.24,33.28 233.24,30.58C233.24,27.87 232.45,25.63 230.86,23.93C229.28,22.14 227.26,21.29 224.82,21.29C222.39,21.29 220.37,22.14 218.73,23.93C217.14,25.63 216.35,27.87 216.35,30.58C216.35,33.28 217.14,35.52 218.73,37.29C220.38,38.99 222.45,39.86 224.82,39.86C227.2,39.86 229.27,39 230.86,37.29ZM233.24,0.92H241.11V47.05H233.24V43.17C230.93,46.39 227.63,47.99 223.36,47.99C219.09,47.99 215.75,46.35 212.8,42.98C209.93,39.62 208.48,35.47 208.48,30.6C208.48,25.73 209.95,21.64 212.8,18.28C215.72,14.92 219.26,13.22 223.36,13.22C227.45,13.22 230.93,14.8 233.24,18.01V0.93V0.92ZM268.74,37.07C270.32,35.36 271.12,33.18 271.12,30.54C271.12,27.9 270.32,25.74 268.74,24.01C267.15,22.31 265.21,21.45 262.82,21.45C260.43,21.45 258.5,22.3 256.9,24.01C255.37,25.8 254.58,27.96 254.58,30.54C254.58,33.12 255.37,35.28 256.9,37.07C258.48,38.77 260.44,39.64 262.82,39.64C265.2,39.64 267.14,38.78 268.74,37.07ZM251.36,42.92C248.26,39.56 246.73,35.48 246.73,30.54C246.73,25.6 248.25,21.58 251.36,18.22C254.47,14.86 258.32,13.15 262.82,13.15C267.32,13.15 271.18,14.86 274.27,18.22C277.38,21.58 278.97,25.73 278.97,30.54C278.97,35.35 277.38,39.56 274.27,42.92C271.16,46.28 267.38,47.93 262.82,47.93C258.26,47.93 254.46,46.28 251.36,42.92ZM313,26.78V47.01H305.14V27.84C305.14,25.66 304.59,24.01 303.48,22.77C302.45,21.65 300.98,21.07 299.09,21.07C294.65,21.07 292.39,23.77 292.39,29.24V47.03H284.53V14.1H292.39V17.81C294.28,14.71 297.28,13.19 301.47,13.19C304.82,13.19 307.57,14.37 309.71,16.81C311.91,19.24 313,22.54 313,26.82"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
@@ -212,6 +212,7 @@
|
||||
android:tint="@color/compose_button"
|
||||
android:tintMode="src_in"
|
||||
android:contentDescription="@string/add_media"
|
||||
android:tooltipText="@string/add_media"
|
||||
android:src="@drawable/ic_fluent_image_24_regular"/>
|
||||
|
||||
<ImageButton
|
||||
@@ -224,6 +225,7 @@
|
||||
android:tint="@color/compose_button"
|
||||
android:tintMode="src_in"
|
||||
android:contentDescription="@string/add_poll"
|
||||
android:tooltipText="@string/add_poll"
|
||||
android:src="@drawable/ic_fluent_poll_24_selector"/>
|
||||
|
||||
<ImageButton
|
||||
@@ -236,6 +238,7 @@
|
||||
android:tint="@color/compose_button"
|
||||
android:tintMode="src_in"
|
||||
android:contentDescription="@string/emoji"
|
||||
android:tooltipText="@string/emoji"
|
||||
android:src="@drawable/ic_fluent_emoji_24_selector"/>
|
||||
|
||||
<ImageButton
|
||||
@@ -248,6 +251,7 @@
|
||||
android:tint="@color/compose_button"
|
||||
android:tintMode="src_in"
|
||||
android:contentDescription="@string/content_warning"
|
||||
android:tooltipText="@string/content_warning"
|
||||
android:src="@drawable/ic_fluent_chat_warning_24_selector"/>
|
||||
|
||||
<ImageButton
|
||||
@@ -260,6 +264,7 @@
|
||||
android:tint="@color/compose_button"
|
||||
android:tintMode="src_in"
|
||||
android:contentDescription="@string/post_visibility"
|
||||
android:tooltipText="@string/post_visibility"
|
||||
android:src="@drawable/ic_fluent_earth_24_regular"/>
|
||||
|
||||
<Space
|
||||
|
||||
@@ -73,11 +73,13 @@
|
||||
android:transformPivotY="1px"
|
||||
android:background="#478E6A"/>
|
||||
|
||||
<ImageView
|
||||
<org.joinmastodon.android.ui.views.SplashLogoView
|
||||
android:layout_width="261dp"
|
||||
android:layout_height="67dp"
|
||||
android:layout_height="71dp"
|
||||
android:layout_gravity="center_horizontal|top"
|
||||
android:layout_marginTop="24dp"
|
||||
android:scaleType="center"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/splash_logo"/>
|
||||
|
||||
<LinearLayout
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
android:tint="?android:textColorSecondary"
|
||||
android:src="@drawable/ic_round_checkbox"/>
|
||||
|
||||
<TextView
|
||||
<org.joinmastodon.android.ui.views.LinkedTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
<item android:id="@+id/block_domain" android:title="@string/block_domain"/>
|
||||
<item android:id="@+id/follow" android:title="@string/follow_user"/>
|
||||
<item android:id="@+id/report" android:title="@string/report_user"/>
|
||||
<item android:id="@+id/bookmark" android:title="@string/add_bookmark"/>
|
||||
<item android:id="@+id/open_in_browser" android:title="@string/open_in_browser"/>
|
||||
</menu>
|
||||
@@ -8,11 +8,4 @@
|
||||
<item android:id="@+id/hide_boosts" android:title="@string/hide_boosts_from_user"/>
|
||||
<item android:id="@+id/manage_user_lists" android:title="@string/lists_with_user"/>
|
||||
<item android:id="@+id/open_in_browser" android:title="@string/open_in_browser"/>
|
||||
<item android:id="@+id/favorites_list" android:title="@string/favorited_posts" android:visible="false"/>
|
||||
<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>
|
||||
6
mastodon/src/main/res/menu/profile_own.xml
Normal file
6
mastodon/src/main/res/menu/profile_own.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/bookmarks" android:title="@string/bookmarks" android:icon="@drawable/ic_fluent_bookmark_multiple_24_regular" android:showAsAction="always"/>
|
||||
<item android:id="@+id/favorites" android:title="@string/your_favorites" android:icon="@drawable/ic_fluent_star_24_regular"/>
|
||||
<item android:id="@+id/share" android:title="@string/share_user" android:icon="@drawable/ic_fluent_share_24_regular"/>
|
||||
</menu>
|
||||
@@ -405,7 +405,7 @@
|
||||
<string name="check_for_update">Auf Update prüfen</string>
|
||||
<string name="no_update_available">Kein Update verfügbar</string>
|
||||
<string name="list_timelines">Listen</string>
|
||||
<string name="favorited_posts">Favorisierte Beiträge</string>
|
||||
<string name="your_favorites">Deine Favoriten</string>
|
||||
<string name="follow_requests">Folgeanfragen</string>
|
||||
<string name="accept_follow_request">Folgeanfrage akzeptieren</string>
|
||||
<string name="reject_follow_request">Folgeanfrage ablehnen</string>
|
||||
@@ -415,7 +415,6 @@
|
||||
<string name="i_agree">Ich stimme zu</string>
|
||||
<string name="settings_always_reveal_content_warnings">Inhaltswarnungen immer ausklappen</string>
|
||||
<string name="disable_marquee">Laufschrift in Titelleisten deaktivieren</string>
|
||||
<string name="copied_to_clipboard">In die Zwischenablage kopiert</string>
|
||||
<string name="empty_list">Diese Liste ist leer</string>
|
||||
<string name="instance_signup_closed">Dieser Server akzeptiert keine neuen Registrierungen.</string>
|
||||
</resources>
|
||||
|
||||
@@ -387,7 +387,7 @@
|
||||
<string name="privacy_policy_subtitle">마스토돈 앱은 아무런 데이터도 수집하지 않지만, 가입한 서버의 정책은 다를 수도 있습니다. 잠시 시간을 들여 마스토돈 앱과 서버의 개인정보 정책을 확인한 후 동의해 주세요.</string>
|
||||
<string name="i_agree">동의합니다</string>
|
||||
<string name="list_timelines">리스트</string>
|
||||
<string name="favorited_posts">좋아요</string>
|
||||
<string name="your_favorites">좋아요</string>
|
||||
<string name="follow_requests">팔로우 요청</string>
|
||||
<string name="accept_follow_request">팔로우 요청 허가</string>
|
||||
<string name="reject_follow_request">팔로우 요청 거부</string>
|
||||
|
||||
@@ -295,7 +295,6 @@
|
||||
<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="mark_media_as_sensitive">Mark media as sensitive</string>
|
||||
@@ -415,14 +414,18 @@
|
||||
<string name="privacy_policy_subtitle">Although the Megalodon app does not collect any data, the server you sign up through may have a different policy. Take a minute to review and agree to the Mastodon app privacy policy and your server\'s privacy policy.</string>
|
||||
<string name="i_agree">I Agree</string>
|
||||
<string name="list_timelines">Lists</string>
|
||||
<string name="favorited_posts">Favorited posts</string>
|
||||
<string name="follow_requests">Follow requests</string>
|
||||
<string name="accept_follow_request">Accept follow request</string>
|
||||
<string name="reject_follow_request">Reject follow request</string>
|
||||
<string name="lists_with_user">Lists with %s</string>
|
||||
<string name="empty_list">This list is empty</string>
|
||||
<string name="instance_signup_closed">This server does not accept new registrations.</string>
|
||||
<string name="text_copied">Copied to clipboard</string>
|
||||
<string name="add_bookmark">Bookmark</string>
|
||||
<string name="remove_bookmark">Remove bookmark</string>
|
||||
<string name="bookmarks">Bookmarks</string>
|
||||
<string name="your_favorites">Your Favorites</string>
|
||||
<string name="settings_always_reveal_content_warnings">Always reveal content warnings</string>
|
||||
<string name="disable_marquee">Disable scrolling text in title bars</string>
|
||||
<string name="copied_to_clipboard">Copied to clipboard</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user