chore(merging-upstream): bunch of conflicts to solve
This commit is contained in:
@@ -11,7 +11,7 @@ public class ApiUtils{
|
||||
//no instance
|
||||
}
|
||||
|
||||
public static <E extends Enum<E>> List<String> enumSetToStrings(EnumSet<E> e, Class<E> cls){
|
||||
public static <E extends Enum<E>> List<String> enumSetToStrings(EnumSet<E> e, Class<E> cls){
|
||||
return e.stream().map(ev->{
|
||||
try{
|
||||
SerializedName annotation=cls.getField(ev.name()).getAnnotation(SerializedName.class);
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||
import org.joinmastodon.android.model.FilterContext;
|
||||
import org.joinmastodon.android.model.FollowList;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.PaginatedResponse;
|
||||
import org.joinmastodon.android.model.SearchResult;
|
||||
@@ -36,6 +37,7 @@ import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import me.grishka.appkit.api.Callback;
|
||||
@@ -44,7 +46,7 @@ import me.grishka.appkit.utils.WorkerThread;
|
||||
|
||||
public class CacheController{
|
||||
private static final String TAG="CacheController";
|
||||
private static final int DB_VERSION=3;
|
||||
private static final int DB_VERSION=4;
|
||||
private static final WorkerThread databaseThread=new WorkerThread("databaseThread");
|
||||
private static final Handler uiHandler=new Handler(Looper.getMainLooper());
|
||||
|
||||
@@ -80,12 +82,11 @@ public class CacheController{
|
||||
Status status=MastodonAPIController.gson.fromJson(cursor.getString(0), Status.class);
|
||||
status.postprocess();
|
||||
int flags=cursor.getInt(1);
|
||||
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0);
|
||||
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0) ? status.id : null;
|
||||
newMaxID=status.id;
|
||||
result.add(status);
|
||||
}while(cursor.moveToNext());
|
||||
String _newMaxID=newMaxID;
|
||||
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
|
||||
uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true)));
|
||||
return;
|
||||
}
|
||||
@@ -93,13 +94,11 @@ public class CacheController{
|
||||
Log.w(TAG, "getHomeTimeline: corrupted status object in database", x);
|
||||
}
|
||||
}
|
||||
new GetHomeTimeline(maxID, null, count, null)
|
||||
new GetHomeTimeline(maxID, null, count, null, AccountSessionManager.get(accountID).getLocalPreferences().timelineReplyVisibility)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
ArrayList<Status> filtered=new ArrayList<>(result);
|
||||
AccountSessionManager.get(accountID).filterStatuses(filtered, FilterContext.HOME);
|
||||
callback.onSuccess(new CacheablePaginatedResponse<>(filtered, result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
||||
callback.onSuccess(new CacheablePaginatedResponse<>(result, result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
||||
putHomeTimeline(result, maxID==null);
|
||||
}
|
||||
|
||||
@@ -127,20 +126,45 @@ public class CacheController{
|
||||
values.put("id", s.id);
|
||||
values.put("json", MastodonAPIController.gson.toJson(s));
|
||||
int flags=0;
|
||||
if(s.hasGapAfter)
|
||||
if(Objects.equals(s.hasGapAfter, s.id))
|
||||
flags|=POST_FLAG_GAP_AFTER;
|
||||
values.put("flags", flags);
|
||||
values.put("time", s.createdAt.getEpochSecond());
|
||||
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
}
|
||||
if(!clear)
|
||||
db.delete("home_timeline", "`id` NOT IN (SELECT `id` FROM `home_timeline` ORDER BY `time` DESC LIMIT ?)", new String[]{"1000"});
|
||||
});
|
||||
}
|
||||
|
||||
public void getNotifications(String maxID, int count, boolean onlyMentions, boolean forceReload, Callback<PaginatedResponse<List<Notification>>> callback){
|
||||
public void updateStatus(Status status) {
|
||||
runOnDbThread((db)->{
|
||||
ContentValues statusUpdate=new ContentValues(1);
|
||||
statusUpdate.put("json", MastodonAPIController.gson.toJson(status));
|
||||
db.update("home_timeline", statusUpdate, "id = ?", new String[] { status.id });
|
||||
});
|
||||
}
|
||||
|
||||
public void updateNotification(Notification notification) {
|
||||
runOnDbThread((db)->{
|
||||
ContentValues notificationUpdate=new ContentValues(1);
|
||||
notificationUpdate.put("json", MastodonAPIController.gson.toJson(notification));
|
||||
String[] notificationArgs = new String[] { notification.id };
|
||||
db.update("notifications_all", notificationUpdate, "id = ?", notificationArgs);
|
||||
db.update("notifications_mentions", notificationUpdate, "id = ?", notificationArgs);
|
||||
db.update("notifications_posts", notificationUpdate, "id = ?", notificationArgs);
|
||||
|
||||
ContentValues statusUpdate=new ContentValues(1);
|
||||
statusUpdate.put("json", MastodonAPIController.gson.toJson(notification.status));
|
||||
db.update("home_timeline", statusUpdate, "id = ?", new String[] { notification.status.id });
|
||||
});
|
||||
}
|
||||
|
||||
public void getNotifications(String maxID, int count, boolean onlyMentions, boolean onlyPosts, boolean forceReload, Callback<PaginatedResponse<List<Notification>>> callback){
|
||||
cancelDelayedClose();
|
||||
databaseThread.postRunnable(()->{
|
||||
try{
|
||||
if(!onlyMentions && loadingNotifications){
|
||||
if(!onlyMentions && !onlyPosts && loadingNotifications){
|
||||
synchronized(pendingNotificationsCallbacks){
|
||||
pendingNotificationsCallbacks.add(callback);
|
||||
}
|
||||
@@ -148,7 +172,8 @@ public class CacheController{
|
||||
}
|
||||
if(!forceReload){
|
||||
SQLiteDatabase db=getOrOpenDatabase();
|
||||
try(Cursor cursor=db.query(onlyMentions ? "notifications_mentions" : "notifications_all", new String[]{"json"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`time` DESC", count+"")){
|
||||
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
|
||||
try(Cursor cursor=db.query(table, new String[]{"json"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`time` DESC", count+"")){
|
||||
if(cursor.getCount()==count){
|
||||
ArrayList<Notification> result=new ArrayList<>();
|
||||
cursor.moveToFirst();
|
||||
@@ -168,9 +193,10 @@ public class CacheController{
|
||||
Log.w(TAG, "getNotifications: corrupted notification object in database", x);
|
||||
}
|
||||
}
|
||||
if(!onlyMentions)
|
||||
if(!onlyMentions && !onlyPosts)
|
||||
loadingNotifications=true;
|
||||
new GetNotifications(maxID, count, onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class))
|
||||
boolean isAkkoma = AccountSessionManager.get(accountID).getInstance().map(Instance::isAkkoma).orElse(false);
|
||||
new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), isAkkoma)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Notification> result){
|
||||
@@ -178,7 +204,7 @@ public class CacheController{
|
||||
AccountSessionManager.get(accountID).filterStatusContainingObjects(filtered, n->n.status, FilterContext.NOTIFICATIONS);
|
||||
PaginatedResponse<List<Notification>> res=new PaginatedResponse<>(filtered, result.isEmpty() ? null : result.get(result.size()-1).id);
|
||||
callback.onSuccess(res);
|
||||
putNotifications(result, onlyMentions, maxID==null);
|
||||
putNotifications(result, onlyMentions, onlyPosts, maxID==null);
|
||||
if(!onlyMentions){
|
||||
loadingNotifications=false;
|
||||
synchronized(pendingNotificationsCallbacks){
|
||||
@@ -214,9 +240,9 @@ public class CacheController{
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private void putNotifications(List<Notification> notifications, boolean onlyMentions, boolean clear){
|
||||
private void putNotifications(List<Notification> notifications, boolean onlyMentions, boolean onlyPosts, boolean clear){
|
||||
runOnDbThread((db)->{
|
||||
String table=onlyMentions ? "notifications_mentions" : "notifications_all";
|
||||
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
|
||||
if(clear)
|
||||
db.delete(table, null, null);
|
||||
ContentValues values=new ContentValues(4);
|
||||
@@ -259,6 +285,28 @@ public class CacheController{
|
||||
|
||||
public void deleteStatus(String id){
|
||||
runOnDbThread((db)->{
|
||||
String gapId=null;
|
||||
int gapFlags=0;
|
||||
// select to-be-removed and newer row
|
||||
try(Cursor cursor=db.query("home_timeline", new String[]{"id", "flags"}, "`time`>=(SELECT `time` FROM `home_timeline` WHERE `id`=?)", new String[]{id}, null, null, "`time` ASC", "2")){
|
||||
boolean hadGapAfter=false;
|
||||
// always either one or two iterations (only one if there's no newer post)
|
||||
while(cursor.moveToNext()){
|
||||
String currentId=cursor.getString(0);
|
||||
int currentFlags=cursor.getInt(1);
|
||||
if(currentId.equals(id)){
|
||||
hadGapAfter=((currentFlags & POST_FLAG_GAP_AFTER)!=0);
|
||||
}else if(hadGapAfter){
|
||||
gapFlags=currentFlags|POST_FLAG_GAP_AFTER;
|
||||
gapId=currentId;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(gapId!=null){
|
||||
ContentValues values=new ContentValues();
|
||||
values.put("flags", gapFlags);
|
||||
db.update("home_timeline", values, "`id`=?", new String[]{gapId});
|
||||
}
|
||||
db.delete("home_timeline", "`id`=?", new String[]{id});
|
||||
});
|
||||
}
|
||||
@@ -437,6 +485,7 @@ public class CacheController{
|
||||
`time` INTEGER NOT NULL
|
||||
)""");
|
||||
createRecentSearchesTable(db);
|
||||
createPostsNotificationsTable(db);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -445,6 +494,10 @@ public class CacheController{
|
||||
createRecentSearchesTable(db);
|
||||
}
|
||||
if(oldVersion<3){
|
||||
// MEGALODON
|
||||
createPostsNotificationsTable(db);
|
||||
}
|
||||
if(oldVersion<4){
|
||||
addTimeColumns(db);
|
||||
}
|
||||
}
|
||||
@@ -458,13 +511,26 @@ public class CacheController{
|
||||
)""");
|
||||
}
|
||||
|
||||
private void createPostsNotificationsTable(SQLiteDatabase db){
|
||||
db.execSQL("""
|
||||
CREATE TABLE `notifications_posts` (
|
||||
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
||||
`json` TEXT NOT NULL,
|
||||
`flags` INTEGER NOT NULL DEFAULT 0,
|
||||
`type` INTEGER NOT NULL,
|
||||
`time` INTEGER NOT NULL
|
||||
)""");
|
||||
}
|
||||
|
||||
private void addTimeColumns(SQLiteDatabase db){
|
||||
db.execSQL("DELETE FROM `home_timeline`");
|
||||
db.execSQL("DELETE FROM `notifications_all`");
|
||||
db.execSQL("DELETE FROM `notifications_mentions`");
|
||||
db.execSQL("DELETE FROM `notifications_posts`");
|
||||
db.execSQL("ALTER TABLE `home_timeline` ADD `time` INTEGER NOT NULL DEFAULT 0");
|
||||
db.execSQL("ALTER TABLE `notifications_all` ADD `time` INTEGER NOT NULL DEFAULT 0");
|
||||
db.execSQL("ALTER TABLE `notifications_mentions` ADD `time` INTEGER NOT NULL DEFAULT 0");
|
||||
db.execSQL("ALTER TABLE `notifications_posts` ADD `time` INTEGER NOT NULL DEFAULT 0");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,16 @@ import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.api.gson.IsoInstantTypeAdapter;
|
||||
import org.joinmastodon.android.api.gson.IsoLocalDateTypeAdapter;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
@@ -38,12 +43,15 @@ import okhttp3.ResponseBody;
|
||||
|
||||
public class MastodonAPIController{
|
||||
private static final String TAG="MastodonAPIController";
|
||||
public static final Gson gson=new GsonBuilder()
|
||||
public static final Gson gsonWithoutDeserializer = new GsonBuilder()
|
||||
.disableHtmlEscaping()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.registerTypeAdapter(Instant.class, new IsoInstantTypeAdapter())
|
||||
.registerTypeAdapter(LocalDate.class, new IsoLocalDateTypeAdapter())
|
||||
.create();
|
||||
public static final Gson gson = gsonWithoutDeserializer.newBuilder()
|
||||
.registerTypeAdapter(Status.class, new Status.StatusDeserializer())
|
||||
.create();
|
||||
private static WorkerThread thread=new WorkerThread("MastodonAPIController");
|
||||
private static OkHttpClient httpClient=new OkHttpClient.Builder()
|
||||
.connectTimeout(60, TimeUnit.SECONDS)
|
||||
@@ -52,9 +60,26 @@ public class MastodonAPIController{
|
||||
.build();
|
||||
|
||||
private AccountSession session;
|
||||
private static List<String> badDomains = new ArrayList<>();
|
||||
|
||||
static{
|
||||
thread.start();
|
||||
try {
|
||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(
|
||||
MastodonApp.context.getAssets().open("blocks.txt")
|
||||
));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.isBlank() || line.startsWith("#")) continue;
|
||||
String[] parts = line.replaceAll("\"", "").split("[\s,;]");
|
||||
if (parts.length == 0) continue;
|
||||
String domain = parts[0].toLowerCase().trim();
|
||||
if (domain.isBlank()) continue;
|
||||
badDomains.add(domain);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public MastodonAPIController(@Nullable AccountSession session){
|
||||
@@ -62,14 +87,17 @@ public class MastodonAPIController{
|
||||
}
|
||||
|
||||
public <T> void submitRequest(final MastodonAPIRequest<T> req){
|
||||
final String host = req.getURL().getHost();
|
||||
final boolean isBad = host == null || badDomains.stream().anyMatch(h -> h.equalsIgnoreCase(host) || host.toLowerCase().endsWith("." + h));
|
||||
thread.postRunnable(()->{
|
||||
try{
|
||||
// if (isBad) throw new IllegalArgumentException();
|
||||
if(req.canceled)
|
||||
return;
|
||||
Request.Builder builder=new Request.Builder()
|
||||
.url(req.getURL().toString())
|
||||
.method(req.getMethod(), req.getRequestBody())
|
||||
.header("User-Agent", "MastodonAndroid/"+BuildConfig.VERSION_NAME);
|
||||
.header("User-Agent", "MoshidonAndroid/"+BuildConfig.VERSION_NAME);
|
||||
|
||||
String token=null;
|
||||
if(session!=null)
|
||||
@@ -87,13 +115,13 @@ public class MastodonAPIController{
|
||||
}
|
||||
|
||||
Request hreq=builder.build();
|
||||
Call call=httpClient.newCall(hreq);
|
||||
OkHttpClient client=req.timeout>0
|
||||
? httpClient.newBuilder().readTimeout(req.timeout, TimeUnit.MILLISECONDS).build()
|
||||
: httpClient;
|
||||
Call call=client.newCall(hreq);
|
||||
synchronized(req){
|
||||
req.okhttpCall=call;
|
||||
}
|
||||
if(req.timeout>0){
|
||||
call.timeout().timeout(req.timeout, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
if(BuildConfig.DEBUG)
|
||||
Log.d(TAG, logTag(session)+"Sending request: "+hreq);
|
||||
@@ -143,6 +171,11 @@ public class MastodonAPIController{
|
||||
respObj=null;
|
||||
}
|
||||
}catch(JsonIOException|JsonSyntaxException x){
|
||||
if (req.context != null && response.body().contentType().subtype().equals("html")) {
|
||||
UiUtils.launchWebBrowser(req.context, response.request().url().toString());
|
||||
req.cancel();
|
||||
return;
|
||||
}
|
||||
if(BuildConfig.DEBUG)
|
||||
Log.w(TAG, logTag(session)+response+" error parsing or reading body", x);
|
||||
req.onError(x.getLocalizedMessage(), response.code(), x);
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.joinmastodon.android.api;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
@@ -20,8 +21,11 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import me.grishka.appkit.api.APIRequest;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
@@ -43,11 +47,12 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
TypeToken<T> respTypeToken;
|
||||
Call okhttpCall;
|
||||
Token token;
|
||||
boolean canceled;
|
||||
boolean canceled, isRemote;
|
||||
Map<String, String> headers;
|
||||
long timeout;
|
||||
private ProgressDialog progressDialog;
|
||||
protected boolean removeUnsupportedItems;
|
||||
@Nullable Context context;
|
||||
|
||||
public MastodonAPIRequest(HttpMethod method, String path, Class<T> respClass){
|
||||
this.path=path;
|
||||
@@ -101,10 +106,30 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
return this;
|
||||
}
|
||||
|
||||
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable){
|
||||
progressDialog=new ProgressDialog(activity);
|
||||
progressDialog.setMessage(activity.getString(message));
|
||||
public MastodonAPIRequest<T> execRemote(String domain) {
|
||||
return execRemote(domain, null);
|
||||
}
|
||||
|
||||
public MastodonAPIRequest<T> execRemote(String domain, @Nullable AccountSession remoteSession) {
|
||||
this.isRemote = true;
|
||||
return Optional.ofNullable(remoteSession)
|
||||
.or(() -> AccountSessionManager.getInstance().getLoggedInAccounts().stream()
|
||||
.filter(acc -> acc.domain.equals(domain))
|
||||
.findAny())
|
||||
.map(AccountSession::getID)
|
||||
.map(this::exec)
|
||||
.orElseGet(() -> this.execNoAuth(domain));
|
||||
}
|
||||
|
||||
public MastodonAPIRequest<T> wrapProgress(Context context, @StringRes int message, boolean cancelable){
|
||||
return wrapProgress(context, message, cancelable, null);
|
||||
}
|
||||
|
||||
public MastodonAPIRequest<T> wrapProgress(Context context, @StringRes int message, boolean cancelable, Consumer<ProgressDialog> transform){
|
||||
progressDialog=new ProgressDialog(context);
|
||||
progressDialog.setMessage(context.getString(message));
|
||||
progressDialog.setCancelable(cancelable);
|
||||
if (transform != null) transform.accept(progressDialog);
|
||||
if(cancelable){
|
||||
progressDialog.setOnCancelListener(dialog->cancel());
|
||||
}
|
||||
@@ -128,8 +153,9 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
headers.put(key, value);
|
||||
}
|
||||
|
||||
protected void setTimeout(long timeout){
|
||||
public MastodonAPIRequest<T> setTimeout(long timeout){
|
||||
this.timeout=timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected String getPathPrefix(){
|
||||
@@ -165,9 +191,20 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
return this;
|
||||
}
|
||||
|
||||
public MastodonAPIRequest<T> setContext(Context context) {
|
||||
this.context = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Context getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
public void validateAndPostprocessResponse(T respObj, Response httpResponse) throws IOException{
|
||||
if(respObj instanceof BaseModel){
|
||||
((BaseModel) respObj).isRemote = isRemote;
|
||||
((BaseModel) respObj).postprocess();
|
||||
}else if(respObj instanceof List){
|
||||
if(removeUnsupportedItems){
|
||||
@@ -176,6 +213,7 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
Object item=itr.next();
|
||||
if(item instanceof BaseModel){
|
||||
try{
|
||||
((BaseModel) item).isRemote = isRemote;
|
||||
((BaseModel) item).postprocess();
|
||||
}catch(ObjectValidationException x){
|
||||
Log.w(TAG, "Removing invalid object from list", x);
|
||||
@@ -183,15 +221,20 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
}
|
||||
}
|
||||
}
|
||||
// no idea why we're post-processing twice, but well, as long
|
||||
// as upstream does it like this, i don't wanna break anything
|
||||
for(Object item:((List<?>) respObj)){
|
||||
if(item instanceof BaseModel){
|
||||
((BaseModel) item).isRemote = isRemote;
|
||||
((BaseModel) item).postprocess();
|
||||
}
|
||||
}
|
||||
}else{
|
||||
for(Object item:((List<?>) respObj)){
|
||||
if(item instanceof BaseModel)
|
||||
if(item instanceof BaseModel) {
|
||||
((BaseModel) item).isRemote = isRemote;
|
||||
((BaseModel) item).postprocess();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
|
||||
public class MastodonErrorResponse extends ErrorResponse{
|
||||
@@ -22,7 +20,7 @@ public class MastodonErrorResponse extends ErrorResponse{
|
||||
|
||||
@Override
|
||||
public void bindErrorView(View view){
|
||||
TextView text=view.findViewById(R.id.error_text);
|
||||
TextView text=view.findViewById(me.grishka.appkit.R.id.error_text);
|
||||
text.setText(error);
|
||||
}
|
||||
|
||||
|
||||
@@ -120,9 +120,21 @@ public class PushSubscriptionManager{
|
||||
return !TextUtils.isEmpty(deviceToken);
|
||||
}
|
||||
|
||||
|
||||
public void registerAccountForPush(PushSubscription subscription){
|
||||
if(TextUtils.isEmpty(deviceToken))
|
||||
throw new IllegalStateException("No device push token available");
|
||||
// this function is used for registering push notifications using FCM
|
||||
// to avoid NonFreeNet in F-Droid, this registration is disabled in it
|
||||
// see https://github.com/LucasGGamerM/moshidon/issues/206 for more context
|
||||
if(BuildConfig.BUILD_TYPE.equals("fdroidRelease") || TextUtils.isEmpty(deviceToken)){
|
||||
Log.d(TAG, "Skipping registering for FCM push notifications");
|
||||
return;
|
||||
}
|
||||
|
||||
String endpoint = "https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/";
|
||||
registerAccountForPush(subscription, endpoint);
|
||||
}
|
||||
|
||||
public void registerAccountForPush(PushSubscription subscription, String endpoint){
|
||||
MastodonAPIController.runInBackground(()->{
|
||||
Log.d(TAG, "registerAccountForPush: started for "+accountID);
|
||||
String encodedPublicKey, encodedAuthKey, pushAccountID;
|
||||
@@ -151,12 +163,17 @@ public class PushSubscriptionManager{
|
||||
Log.e(TAG, "registerAccountForPush: error generating encryption key", e);
|
||||
return;
|
||||
}
|
||||
new RegisterForPushNotifications(deviceToken,
|
||||
|
||||
//work-around for adding the randomAccountId
|
||||
String newEndpoint = endpoint;
|
||||
if (endpoint.startsWith("https://app.joinmastodon.org/relay-to/fcm/"))
|
||||
newEndpoint += pushAccountID;
|
||||
|
||||
new RegisterForPushNotifications(newEndpoint,
|
||||
encodedPublicKey,
|
||||
encodedAuthKey,
|
||||
subscription==null ? PushSubscription.Alerts.ofAll() : subscription.alerts,
|
||||
subscription==null ? PushSubscription.Policy.ALL : subscription.policy,
|
||||
pushAccountID)
|
||||
subscription==null ? PushSubscription.Policy.ALL : subscription.policy)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(PushSubscription result){
|
||||
@@ -367,7 +384,7 @@ public class PushSubscriptionManager{
|
||||
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
||||
if(session.pushSubscription==null || forceReRegister)
|
||||
session.getPushSubscriptionManager().registerAccountForPush(session.pushSubscription);
|
||||
else if(session.needUpdatePushSettings)
|
||||
else
|
||||
session.getPushSubscriptionManager().updatePushSettings(session.pushSubscription);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,26 +6,39 @@ 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.SetStatusMuted;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
||||
import org.joinmastodon.android.events.ReblogDeletedEvent;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.events.StatusDeletedEvent;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
|
||||
public class StatusInteractionController{
|
||||
private final String accountID;
|
||||
private final boolean updateCounters;
|
||||
private final HashMap<String, SetStatusFavorited> runningFavoriteRequests=new HashMap<>();
|
||||
private final HashMap<String, SetStatusReblogged> runningReblogRequests=new HashMap<>();
|
||||
private final HashMap<String, SetStatusBookmarked> runningBookmarkRequests=new HashMap<>();
|
||||
private final HashMap<String, SetStatusMuted> runningMuteRequests=new HashMap<>();
|
||||
|
||||
public StatusInteractionController(String accountID){
|
||||
public StatusInteractionController(String accountID, boolean updateCounters) {
|
||||
this.accountID=accountID;
|
||||
this.updateCounters=updateCounters;
|
||||
}
|
||||
|
||||
public void setFavorited(Status status, boolean favorited){
|
||||
public StatusInteractionController(String accountID){
|
||||
this(accountID, true);
|
||||
}
|
||||
|
||||
public void setFavorited(Status status, boolean favorited, Consumer<Status> cb){
|
||||
if(!Looper.getMainLooper().isCurrentThread())
|
||||
throw new IllegalStateException("Can only be called from main thread");
|
||||
|
||||
@@ -38,7 +51,9 @@ public class StatusInteractionController{
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
runningFavoriteRequests.remove(status.id);
|
||||
E.post(new StatusCountersUpdatedEvent(result));
|
||||
result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1));
|
||||
cb.accept(result);
|
||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,24 +61,17 @@ public class StatusInteractionController{
|
||||
runningFavoriteRequests.remove(status.id);
|
||||
error.showToast(MastodonApp.context);
|
||||
status.favourited=!favorited;
|
||||
if(favorited)
|
||||
status.favouritesCount--;
|
||||
else
|
||||
status.favouritesCount++;
|
||||
E.post(new StatusCountersUpdatedEvent(status));
|
||||
cb.accept(status);
|
||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
runningFavoriteRequests.put(status.id, req);
|
||||
status.favourited=favorited;
|
||||
if(favorited)
|
||||
status.favouritesCount++;
|
||||
else
|
||||
status.favouritesCount--;
|
||||
E.post(new StatusCountersUpdatedEvent(status));
|
||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||
}
|
||||
|
||||
public void setReblogged(Status status, boolean reblogged){
|
||||
public void setReblogged(Status status, boolean reblogged, StatusPrivacy visibility, Consumer<Status> cb){
|
||||
if(!Looper.getMainLooper().isCurrentThread())
|
||||
throw new IllegalStateException("Can only be called from main thread");
|
||||
|
||||
@@ -71,12 +79,19 @@ public class StatusInteractionController{
|
||||
if(current!=null){
|
||||
current.cancel();
|
||||
}
|
||||
SetStatusReblogged req=(SetStatusReblogged) new SetStatusReblogged(status.id, reblogged)
|
||||
SetStatusReblogged req=(SetStatusReblogged) new SetStatusReblogged(status.id, reblogged, visibility)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
public void onSuccess(Status reblog){
|
||||
Status result=reblog.getContentStatus();
|
||||
runningReblogRequests.remove(status.id);
|
||||
E.post(new StatusCountersUpdatedEvent(result));
|
||||
result.reblogsCount = Math.max(0, status.reblogsCount + (reblogged ? 1 : -1));
|
||||
cb.accept(result);
|
||||
if(updateCounters){
|
||||
E.post(new StatusCountersUpdatedEvent(result));
|
||||
if(reblogged) E.post(new StatusCreatedEvent(reblog, accountID));
|
||||
else E.post(new ReblogDeletedEvent(status.id, accountID));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,24 +99,21 @@ public class StatusInteractionController{
|
||||
runningReblogRequests.remove(status.id);
|
||||
error.showToast(MastodonApp.context);
|
||||
status.reblogged=!reblogged;
|
||||
if(reblogged)
|
||||
status.reblogsCount--;
|
||||
else
|
||||
status.reblogsCount++;
|
||||
E.post(new StatusCountersUpdatedEvent(status));
|
||||
cb.accept(status);
|
||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
runningReblogRequests.put(status.id, req);
|
||||
status.reblogged=reblogged;
|
||||
if(reblogged)
|
||||
status.reblogsCount++;
|
||||
else
|
||||
status.reblogsCount--;
|
||||
E.post(new StatusCountersUpdatedEvent(status));
|
||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||
}
|
||||
|
||||
public void setBookmarked(Status status, boolean bookmarked){
|
||||
setBookmarked(status, bookmarked, r->{});
|
||||
}
|
||||
|
||||
public void setBookmarked(Status status, boolean bookmarked, Consumer<Status> cb){
|
||||
if(!Looper.getMainLooper().isCurrentThread())
|
||||
throw new IllegalStateException("Can only be called from main thread");
|
||||
|
||||
@@ -114,7 +126,8 @@ public class StatusInteractionController{
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
runningBookmarkRequests.remove(status.id);
|
||||
E.post(new StatusCountersUpdatedEvent(result));
|
||||
cb.accept(result);
|
||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -122,12 +135,13 @@ public class StatusInteractionController{
|
||||
runningBookmarkRequests.remove(status.id);
|
||||
error.showToast(MastodonApp.context);
|
||||
status.bookmarked=!bookmarked;
|
||||
E.post(new StatusCountersUpdatedEvent(status));
|
||||
cb.accept(status);
|
||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
runningBookmarkRequests.put(status.id, req);
|
||||
status.bookmarked=bookmarked;
|
||||
E.post(new StatusCountersUpdatedEvent(status));
|
||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
|
||||
public class AuthorizeFollowRequest extends MastodonAPIRequest<Relationship>{
|
||||
public AuthorizeFollowRequest(String id){
|
||||
super(HttpMethod.POST, "/follow_requests/"+id+"/authorize", Relationship.class);
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
|
||||
public class GetAccountBlocks extends HeaderPaginationRequest<Account>{
|
||||
public GetAccountBlocks(String maxID, int limit){
|
||||
super(HttpMethod.GET, "/blocks", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", limit+"");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
|
||||
public class GetAccountByHandle extends MastodonAPIRequest<Account>{
|
||||
/**
|
||||
* note that this method usually only returns a result if the instance already knows about an
|
||||
* account - so it makes sense for looking up local users, search might be preferred otherwise
|
||||
*/
|
||||
public GetAccountByHandle(String acct){
|
||||
super(HttpMethod.GET, "/accounts/lookup", Account.class);
|
||||
addQueryParameter("acct", acct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
|
||||
public class GetAccountMutes extends HeaderPaginationRequest<Account>{
|
||||
public GetAccountMutes(String maxID, int limit){
|
||||
super(HttpMethod.GET, "/mutes/", 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.accounts;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
|
||||
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(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
|
||||
public class RejectFollowRequest extends MastodonAPIRequest<Relationship>{
|
||||
public RejectFollowRequest(String id){
|
||||
super(HttpMethod.POST, "/follow_requests/"+id+"/reject", Relationship.class);
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,13 @@ import org.joinmastodon.android.model.Relationship;
|
||||
|
||||
public class SetAccountFollowed extends MastodonAPIRequest<Relationship>{
|
||||
public SetAccountFollowed(String id, boolean followed, boolean showReblogs){
|
||||
this(id, followed, showReblogs, false);
|
||||
}
|
||||
|
||||
public SetAccountFollowed(String id, boolean followed, boolean showReblogs, boolean notify){
|
||||
super(HttpMethod.POST, "/accounts/"+id+"/"+(followed ? "follow" : "unfollow"), Relationship.class);
|
||||
if(followed)
|
||||
setRequestBody(new Request(showReblogs, null));
|
||||
setRequestBody(new Request(showReblogs, notify));
|
||||
else
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
|
||||
@@ -4,8 +4,15 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
|
||||
public class SetAccountMuted extends MastodonAPIRequest<Relationship>{
|
||||
public SetAccountMuted(String id, boolean muted){
|
||||
public SetAccountMuted(String id, boolean muted, long duration){
|
||||
super(HttpMethod.POST, "/accounts/"+id+"/"+(muted ? "mute" : "unmute"), Relationship.class);
|
||||
setRequestBody(new Object());
|
||||
setRequestBody(new Request(duration));
|
||||
}
|
||||
|
||||
private static class Request{
|
||||
public long duration;
|
||||
public Request(long duration){
|
||||
this.duration=duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
|
||||
public class SetPrivateNote extends MastodonAPIRequest<Relationship>{
|
||||
public SetPrivateNote(String id, String comment){
|
||||
super(MastodonAPIRequest.HttpMethod.POST, "/accounts/"+id+"/note", Relationship.class);
|
||||
Request req = new Request(comment);
|
||||
setRequestBody(req);
|
||||
}
|
||||
|
||||
private static class Request{
|
||||
public String comment;
|
||||
public Request(String comment){
|
||||
this.comment=comment;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.api.requests.announcements;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
public class AddAnnouncementReaction extends MastodonAPIRequest<Object> {
|
||||
public AddAnnouncementReaction(String id, String emoji) {
|
||||
super(HttpMethod.PUT, "/announcements/" + id + "/reactions/" + emoji, Object.class);
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.joinmastodon.android.api.requests.announcements;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
|
||||
public class DeleteAnnouncementReaction extends MastodonAPIRequest<Object> {
|
||||
public DeleteAnnouncementReaction(String id, String emoji) {
|
||||
super(HttpMethod.DELETE, "/announcements/" + id + "/reactions/" + emoji, Object.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.joinmastodon.android.api.requests.announcements;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
|
||||
public class DismissAnnouncement extends MastodonAPIRequest<Object>{
|
||||
public DismissAnnouncement(String id){
|
||||
super(HttpMethod.POST, "/announcements/" + id + "/dismiss", Object.class);
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.joinmastodon.android.api.requests.announcements;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Announcement;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetAnnouncements extends MastodonAPIRequest<List<Announcement>> {
|
||||
public GetAnnouncements(boolean withDismissed) {
|
||||
super(MastodonAPIRequest.HttpMethod.GET, "/announcements", new TypeToken<>(){});
|
||||
addQueryParameter("with_dismissed", withDismissed ? "true" : "false");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.joinmastodon.android.api.requests.instance;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.DomainBlock;
|
||||
import org.joinmastodon.android.model.ExtendedDescription;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetDomainBlocks extends MastodonAPIRequest<List<DomainBlock>>{
|
||||
public GetDomainBlocks(){
|
||||
super(HttpMethod.GET, "/instance/domain_blocks", new TypeToken<>(){});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.joinmastodon.android.api.requests.instance;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.ExtendedDescription;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
|
||||
public class GetExtendedDescription extends MastodonAPIRequest<ExtendedDescription>{
|
||||
public GetExtendedDescription(){
|
||||
super(HttpMethod.GET, "/instance/extended_description", ExtendedDescription.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,4 +7,15 @@ public class GetInstance extends MastodonAPIRequest<Instance>{
|
||||
public GetInstance(){
|
||||
super(HttpMethod.GET, "/instance", Instance.class);
|
||||
}
|
||||
|
||||
public static class V2 extends MastodonAPIRequest<Instance.V2>{
|
||||
public V2(){
|
||||
super(HttpMethod.GET, "/instance", Instance.V2.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPathPrefix() {
|
||||
return "/api/v2";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.joinmastodon.android.api.requests.instance;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.WeeklyActivity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetWeeklyActivity extends MastodonAPIRequest<List<WeeklyActivity>>{
|
||||
public GetWeeklyActivity(){
|
||||
super(HttpMethod.GET, "/instance/activity", new TypeToken<>(){});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.joinmastodon.android.api.requests.lists;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import java.util.List;
|
||||
|
||||
public class AddList extends MastodonAPIRequest<Object> {
|
||||
public AddList(String listName){
|
||||
super(HttpMethod.POST, "/lists", Object.class);
|
||||
Request req = new Request();
|
||||
req.title = listName;
|
||||
setRequestBody(req);
|
||||
}
|
||||
|
||||
public static class Request{
|
||||
public String title;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.joinmastodon.android.api.requests.lists;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import java.util.List;
|
||||
|
||||
public class EditListName extends MastodonAPIRequest<Object> {
|
||||
public EditListName(String newListName, String listId){
|
||||
super(HttpMethod.PUT, "/lists/"+listId, Object.class);
|
||||
Request req = new Request();
|
||||
req.title = newListName;
|
||||
setRequestBody(req);
|
||||
}
|
||||
|
||||
public static class Request{
|
||||
public String title;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.joinmastodon.android.api.requests.lists;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.ListTimeline;
|
||||
|
||||
public class GetList extends MastodonAPIRequest<ListTimeline> {
|
||||
public GetList(String id) {
|
||||
super(HttpMethod.GET, "/lists/" + id, ListTimeline.class);
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,15 @@ import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.FollowList;
|
||||
import org.joinmastodon.android.model.ListTimeline;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetLists extends MastodonAPIRequest<List<FollowList>>{
|
||||
public GetLists(){
|
||||
super(HttpMethod.GET, "/lists", new TypeToken<>(){});
|
||||
}
|
||||
public GetLists() {
|
||||
super(HttpMethod.GET, "/lists", new TypeToken<>(){});
|
||||
}
|
||||
public GetLists(String accountID) {
|
||||
super(HttpMethod.GET, "/accounts/"+accountID+"/lists", new TypeToken<>(){});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.joinmastodon.android.api.requests.lists;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import java.util.List;
|
||||
|
||||
public class RemoveList extends MastodonAPIRequest<Object> {
|
||||
public RemoveList(String listId){
|
||||
super(HttpMethod.DELETE, "/lists/"+listId, Object.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.joinmastodon.android.api.requests.notifications;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.ApiUtils;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
public class DismissNotification extends MastodonAPIRequest<Object>{
|
||||
public DismissNotification(String id){
|
||||
super(HttpMethod.POST, "/notifications/" + (id != null ? id + "/dismiss" : "clear"), Object.class);
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.joinmastodon.android.api.requests.notifications;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.ApiUtils;
|
||||
@@ -11,18 +10,24 @@ import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
public class GetNotifications extends MastodonAPIRequest<List<Notification>>{
|
||||
public GetNotifications(String maxID, int limit, EnumSet<Notification.Type> includeTypes){
|
||||
public GetNotifications(String maxID, int limit, EnumSet<Notification.Type> includeTypes, boolean isPleromaInstance){
|
||||
super(HttpMethod.GET, "/notifications", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
if(includeTypes!=null){
|
||||
for(String type:ApiUtils.enumSetToStrings(includeTypes, Notification.Type.class)){
|
||||
addQueryParameter("types[]", type);
|
||||
}
|
||||
for(String type:ApiUtils.enumSetToStrings(EnumSet.complementOf(includeTypes), Notification.Type.class)){
|
||||
addQueryParameter("exclude_types[]", type);
|
||||
if(!isPleromaInstance) {
|
||||
for(String type:ApiUtils.enumSetToStrings(includeTypes, Notification.Type.class)){
|
||||
addQueryParameter("types[]", type);
|
||||
}
|
||||
for(String type:ApiUtils.enumSetToStrings(EnumSet.complementOf(includeTypes), Notification.Type.class)){
|
||||
addQueryParameter("exclude_types[]", type);
|
||||
}
|
||||
}else{
|
||||
for(String type:ApiUtils.enumSetToStrings(includeTypes, Notification.Type.class)){
|
||||
addQueryParameter("include_types[]", type);
|
||||
}
|
||||
}
|
||||
}
|
||||
removeUnsupportedItems=true;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.joinmastodon.android.api.requests.notifications;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
|
||||
public class PleromaMarkNotificationsRead extends MastodonAPIRequest<List<Notification>> {
|
||||
private final String maxID;
|
||||
public PleromaMarkNotificationsRead(String maxID) {
|
||||
super(HttpMethod.POST, "/pleroma/notifications/read", new TypeToken<>(){});
|
||||
this.maxID = maxID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestBody getRequestBody() {
|
||||
MultipartBody.Builder builder=new MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM);
|
||||
if(!TextUtils.isEmpty(maxID))
|
||||
builder.addFormDataPart("max_id", maxID);
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.PushSubscription;
|
||||
|
||||
public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscription>{
|
||||
public RegisterForPushNotifications(String deviceToken, String encryptionKey, String authKey, PushSubscription.Alerts alerts, PushSubscription.Policy policy, String accountID){
|
||||
public RegisterForPushNotifications(String endpoint, String encryptionKey, String authKey, PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
||||
super(HttpMethod.POST, "/push/subscription", PushSubscription.class);
|
||||
Request r=new Request();
|
||||
r.subscription.endpoint="https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID;
|
||||
r.subscription.endpoint=endpoint;
|
||||
r.data.alerts=alerts;
|
||||
r.policy=policy;
|
||||
r.subscription.keys.p256dh=encryptionKey;
|
||||
|
||||
@@ -11,9 +11,9 @@ public class CreateOAuthApp extends MastodonAPIRequest<Application>{
|
||||
}
|
||||
|
||||
private static class Request{
|
||||
public String clientName="Mastodon for Android";
|
||||
public String clientName="Moshidon";
|
||||
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/LucasGGamerM/moshidon";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 AddStatusReaction extends MastodonAPIRequest<Status> {
|
||||
public AddStatusReaction(String id, String emoji) {
|
||||
super(HttpMethod.POST, "/statuses/" + id + "/react/" + emoji, 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.AkkomaTranslation;
|
||||
|
||||
public class AkkomaTranslateStatus extends MastodonAPIRequest<AkkomaTranslation>{
|
||||
public AkkomaTranslateStatus(String id, String lang){
|
||||
super(HttpMethod.GET, "/statuses/"+id+"/translations/"+lang.toUpperCase(), AkkomaTranslation.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.ContentType;
|
||||
import org.joinmastodon.android.model.ScheduledStatus;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
|
||||
@@ -9,12 +11,27 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CreateStatus extends MastodonAPIRequest<Status>{
|
||||
public static long EPOCH_OF_THE_YEAR_FIVE_THOUSAND=95617584000000L;
|
||||
public static final Instant DRAFTS_AFTER_INSTANT=Instant.ofEpochMilli(EPOCH_OF_THE_YEAR_FIVE_THOUSAND - 1) /* end of 4999 */;
|
||||
|
||||
public static Instant getDraftInstant() {
|
||||
return DRAFTS_AFTER_INSTANT.plusMillis(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public CreateStatus(CreateStatus.Request req, String uuid){
|
||||
super(HttpMethod.POST, "/statuses", Status.class);
|
||||
setRequestBody(req);
|
||||
addHeader("Idempotency-Key", uuid);
|
||||
}
|
||||
|
||||
public static class Scheduled extends MastodonAPIRequest<ScheduledStatus>{
|
||||
public Scheduled(CreateStatus.Request req, String uuid){
|
||||
super(HttpMethod.POST, "/statuses", ScheduledStatus.class);
|
||||
setRequestBody(req);
|
||||
addHeader("Idempotency-Key", uuid);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Request{
|
||||
public String status;
|
||||
public List<MediaAttribute> mediaAttributes;
|
||||
@@ -22,11 +39,17 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
|
||||
public Poll poll;
|
||||
public String inReplyToId;
|
||||
public boolean sensitive;
|
||||
public boolean localOnly;
|
||||
public String spoilerText;
|
||||
public StatusPrivacy visibility;
|
||||
public Instant scheduledAt;
|
||||
public String language;
|
||||
|
||||
public String quoteId;
|
||||
public ContentType contentType;
|
||||
|
||||
public boolean preview;
|
||||
|
||||
public static class Poll{
|
||||
public ArrayList<String> options=new ArrayList<>();
|
||||
public int expiresIn;
|
||||
|
||||
@@ -7,4 +7,10 @@ public class DeleteStatus extends MastodonAPIRequest<Status>{
|
||||
public DeleteStatus(String id){
|
||||
super(HttpMethod.DELETE, "/statuses/"+id, Status.class);
|
||||
}
|
||||
|
||||
public static class Scheduled extends MastodonAPIRequest<Object> {
|
||||
public Scheduled(String id) {
|
||||
super(HttpMethod.DELETE, "/scheduled_statuses/"+id, Object.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 DeleteStatusReaction extends MastodonAPIRequest<Status> {
|
||||
public DeleteStatusReaction(String id, String emoji) {
|
||||
super(HttpMethod.POST, "/statuses/" + id + "/unreact/" + emoji, Status.class);
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
}
|
||||
@@ -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.ScheduledStatus;
|
||||
|
||||
public class GetScheduledStatuses extends HeaderPaginationRequest<ScheduledStatus>{
|
||||
public GetScheduledStatuses(String maxID, int limit){
|
||||
super(HttpMethod.GET, "/scheduled_statuses", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", limit+"");
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,22 @@ package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
import org.joinmastodon.android.model.BaseModel;
|
||||
import org.joinmastodon.android.model.ContentType;
|
||||
|
||||
public class GetStatusSourceText extends MastodonAPIRequest<GetStatusSourceText.Response>{
|
||||
public GetStatusSourceText(String id){
|
||||
super(HttpMethod.GET, "/statuses/"+id+"/source", Response.class);
|
||||
}
|
||||
|
||||
@AllFieldsAreRequired
|
||||
public static class Response extends BaseModel{
|
||||
@RequiredField
|
||||
public String id;
|
||||
@RequiredField
|
||||
public String text;
|
||||
@RequiredField
|
||||
public String spoilerText;
|
||||
public ContentType contentType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 PleromaAddStatusReaction extends MastodonAPIRequest<Status> {
|
||||
public PleromaAddStatusReaction(String id, String emoji) {
|
||||
super(HttpMethod.PUT, "/pleroma/statuses/" + id + "/reactions/" + emoji, Status.class);
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
public class PleromaDeleteStatusReaction extends MastodonAPIRequest<Status> {
|
||||
public PleromaDeleteStatusReaction(String id, String emoji) {
|
||||
super(HttpMethod.DELETE, "/pleroma/statuses/" + id + "/reactions/" + emoji, Status.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.EmojiReaction;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PleromaGetStatusReactions extends MastodonAPIRequest<List<EmojiReaction>> {
|
||||
public PleromaGetStatusReactions(String id, String emoji) {
|
||||
super(HttpMethod.GET, "/pleroma/statuses/" + id + "/reactions/" + (emoji != null ? emoji : ""), new TypeToken<>(){});
|
||||
}
|
||||
}
|
||||
@@ -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 SetStatusMuted extends MastodonAPIRequest<Status>{
|
||||
public SetStatusMuted(String id, boolean muted){
|
||||
super(HttpMethod.POST, "/statuses/"+id+"/"+(muted ? "mute" : "unmute"), 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());
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,17 @@ package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
|
||||
public class SetStatusReblogged extends MastodonAPIRequest<Status>{
|
||||
public SetStatusReblogged(String id, boolean reblogged){
|
||||
public SetStatusReblogged(String id, boolean reblogged, StatusPrivacy visibility){
|
||||
super(HttpMethod.POST, "/statuses/"+id+"/"+(reblogged ? "reblog" : "unreblog"), Status.class);
|
||||
setRequestBody(new Object());
|
||||
Request req = new Request();
|
||||
req.visibility = visibility;
|
||||
setRequestBody(req);
|
||||
}
|
||||
|
||||
public static class Request {
|
||||
public StatusPrivacy visibility;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.joinmastodon.android.api.requests.tags;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetFollowedHashtags extends HeaderPaginationRequest<Hashtag> {
|
||||
public GetFollowedHashtags() {
|
||||
this(null, null, -1, null);
|
||||
}
|
||||
|
||||
public GetFollowedHashtags(String maxID, String minID, int limit, String sinceID){
|
||||
super(HttpMethod.GET, "/followed_tags", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(minID!=null)
|
||||
addQueryParameter("min_id", minID);
|
||||
if(sinceID!=null)
|
||||
addQueryParameter("since_id", sinceID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.api.requests.tags;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
|
||||
public class GetHashtag extends MastodonAPIRequest<Hashtag> {
|
||||
public GetHashtag(String name){
|
||||
super(HttpMethod.GET, "/tags/"+name, Hashtag.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.api.requests.tags;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
|
||||
public class SetHashtagFollowed extends MastodonAPIRequest<Hashtag>{
|
||||
public SetHashtagFollowed(String name, boolean followed){
|
||||
super(HttpMethod.POST, "/tags/"+name+"/"+(followed ? "follow" : "unfollow"), Hashtag.class);
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.joinmastodon.android.api.requests.timelines;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetBubbleTimeline extends MastodonAPIRequest<List<Status>> {
|
||||
public GetBubbleTimeline(String maxID, int limit, String replyVisibility) {
|
||||
super(HttpMethod.GET, "/timelines/bubble", new TypeToken<>(){});
|
||||
if(!TextUtils.isEmpty(maxID))
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", limit+"");
|
||||
if(replyVisibility != null)
|
||||
addQueryParameter("reply_visibility", replyVisibility);
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,26 @@ import org.joinmastodon.android.model.Status;
|
||||
import java.util.List;
|
||||
|
||||
public class GetHashtagTimeline extends MastodonAPIRequest<List<Status>>{
|
||||
public GetHashtagTimeline(String hashtag, String maxID, String minID, int limit){
|
||||
public GetHashtagTimeline(String hashtag, String maxID, String minID, int limit, List<String> containsAny, List<String> containsAll, List<String> containsNone, boolean localOnly, String replyVisibility){
|
||||
super(HttpMethod.GET, "/timelines/tag/"+hashtag, new TypeToken<>(){});
|
||||
if (localOnly)
|
||||
addQueryParameter("local", "true");
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(minID!=null)
|
||||
addQueryParameter("min_id", minID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
if(containsAny!=null)
|
||||
for (String tag : containsAny)
|
||||
addQueryParameter("any[]", tag);
|
||||
if(containsAll!=null)
|
||||
for (String tag : containsAll)
|
||||
addQueryParameter("all[]", tag);
|
||||
if(containsNone!=null)
|
||||
for (String tag : containsNone)
|
||||
addQueryParameter("none[]", tag);
|
||||
if(replyVisibility != null)
|
||||
addQueryParameter("reply_visibility", replyVisibility);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import org.joinmastodon.android.model.Status;
|
||||
import java.util.List;
|
||||
|
||||
public class GetHomeTimeline extends MastodonAPIRequest<List<Status>>{
|
||||
public GetHomeTimeline(String maxID, String minID, int limit, String sinceID){
|
||||
public GetHomeTimeline(String maxID, String minID, int limit, String sinceID, String replyVisibility){
|
||||
super(HttpMethod.GET, "/timelines/home", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
@@ -18,5 +18,7 @@ public class GetHomeTimeline extends MastodonAPIRequest<List<Status>>{
|
||||
addQueryParameter("since_id", sinceID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
if(replyVisibility != null)
|
||||
addQueryParameter("reply_visibility", replyVisibility);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,18 @@ import org.joinmastodon.android.model.Status;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetListTimeline extends MastodonAPIRequest<List<Status>>{
|
||||
public GetListTimeline(String listID, String maxID, String minID, int limit, String sinceID){
|
||||
super(HttpMethod.GET, "/timelines/list/"+listID, new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(minID!=null)
|
||||
addQueryParameter("min_id", minID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
if(sinceID!=null)
|
||||
addQueryParameter("since_id", sinceID);
|
||||
}
|
||||
public class GetListTimeline extends MastodonAPIRequest<List<Status>> {
|
||||
public GetListTimeline(String listID, String maxID, String minID, int limit, String sinceID, String replyVisibility) {
|
||||
super(HttpMethod.GET, "/timelines/list/"+listID, new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(minID!=null)
|
||||
addQueryParameter("min_id", minID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
if(sinceID!=null)
|
||||
addQueryParameter("since_id", sinceID);
|
||||
if(replyVisibility != null)
|
||||
addQueryParameter("reply_visibility", replyVisibility);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.joinmastodon.android.model.Status;
|
||||
import java.util.List;
|
||||
|
||||
public class GetPublicTimeline extends MastodonAPIRequest<List<Status>>{
|
||||
public GetPublicTimeline(boolean local, boolean remote, String maxID, String minID, int limit, String sinceID){
|
||||
public GetPublicTimeline(boolean local, boolean remote, String maxID, String minID, int limit, String sinceID, String replyVisibility){
|
||||
super(HttpMethod.GET, "/timelines/public", new TypeToken<>(){});
|
||||
if(local)
|
||||
addQueryParameter("local", "true");
|
||||
@@ -24,5 +24,7 @@ public class GetPublicTimeline extends MastodonAPIRequest<List<Status>>{
|
||||
addQueryParameter("since_id", sinceID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", limit+"");
|
||||
if(replyVisibility != null)
|
||||
addQueryParameter("reply_visibility", replyVisibility);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,95 @@
|
||||
package org.joinmastodon.android.api.session;
|
||||
|
||||
import static org.joinmastodon.android.GlobalUserPreferences.fromJson;
|
||||
import static org.joinmastodon.android.GlobalUserPreferences.enumValue;
|
||||
import static org.joinmastodon.android.api.MastodonAPIController.gson;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.ContentType;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.PushSubscription;
|
||||
import org.joinmastodon.android.model.TimelineDefinition;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class AccountLocalPreferences{
|
||||
private final SharedPreferences prefs;
|
||||
|
||||
public boolean showInteractionCounts;
|
||||
public boolean customEmojiInNames;
|
||||
public boolean showCWs;
|
||||
public boolean revealCWs;
|
||||
public boolean hideSensitiveMedia;
|
||||
public boolean serverSideFiltersSupported;
|
||||
|
||||
public AccountLocalPreferences(SharedPreferences prefs){
|
||||
// MEGALODON
|
||||
public boolean showReplies;
|
||||
public boolean showBoosts;
|
||||
public ArrayList<String> recentLanguages;
|
||||
public boolean bottomEncoding;
|
||||
public ContentType defaultContentType;
|
||||
public boolean contentTypesEnabled;
|
||||
public ArrayList<TimelineDefinition> timelines;
|
||||
public boolean localOnlySupported;
|
||||
public boolean glitchInstance;
|
||||
public String publishButtonText;
|
||||
public String timelineReplyVisibility; // akkoma-only
|
||||
public boolean keepOnlyLatestNotification;
|
||||
public boolean emojiReactionsEnabled;
|
||||
public ShowEmojiReactions showEmojiReactions;
|
||||
public ColorPreference color;
|
||||
public ArrayList<Emoji> recentCustomEmoji;
|
||||
|
||||
private final static Type recentLanguagesType=new TypeToken<ArrayList<String>>() {}.getType();
|
||||
private final static Type timelinesType=new TypeToken<ArrayList<TimelineDefinition>>() {}.getType();
|
||||
private final static Type recentCustomEmojiType=new TypeToken<ArrayList<Emoji>>() {}.getType();
|
||||
|
||||
// MOSHIDON
|
||||
// private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType();
|
||||
// public Map<String, Integer> recentEmojis;
|
||||
private final static Type notificationFiltersType = new TypeToken<PushSubscription.Alerts>() {}.getType();
|
||||
public PushSubscription.Alerts notificationFilters;
|
||||
|
||||
public AccountLocalPreferences(SharedPreferences prefs, AccountSession session){
|
||||
this.prefs=prefs;
|
||||
showInteractionCounts=prefs.getBoolean("interactionCounts", true);
|
||||
showInteractionCounts=prefs.getBoolean("interactionCounts", false);
|
||||
customEmojiInNames=prefs.getBoolean("emojiInNames", true);
|
||||
showCWs=prefs.getBoolean("showCWs", true);
|
||||
revealCWs=prefs.getBoolean("revealCWs", false);
|
||||
hideSensitiveMedia=prefs.getBoolean("hideSensitive", true);
|
||||
serverSideFiltersSupported=prefs.getBoolean("serverSideFilters", false);
|
||||
|
||||
// MEGALODON
|
||||
showReplies=prefs.getBoolean("showReplies", true);
|
||||
showBoosts=prefs.getBoolean("showBoosts", true);
|
||||
recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new ArrayList<>());
|
||||
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
|
||||
defaultContentType=enumValue(ContentType.class, prefs.getString("defaultContentType", ContentType.PLAIN.name()));
|
||||
contentTypesEnabled=prefs.getBoolean("contentTypesEnabled", true);
|
||||
timelines=fromJson(prefs.getString("timelines", null), timelinesType, TimelineDefinition.getDefaultTimelines(session.getID()));
|
||||
localOnlySupported=prefs.getBoolean("localOnlySupported", false);
|
||||
glitchInstance=prefs.getBoolean("glitchInstance", false);
|
||||
publishButtonText=prefs.getString("publishButtonText", null);
|
||||
timelineReplyVisibility=prefs.getString("timelineReplyVisibility", null);
|
||||
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
||||
emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma());
|
||||
showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name()));
|
||||
color=prefs.contains("color") ? ColorPreference.valueOf(prefs.getString("color", null)) : null;
|
||||
recentCustomEmoji=fromJson(prefs.getString("recentCustomEmoji", null), recentCustomEmojiType, new ArrayList<>());
|
||||
|
||||
// MOSHIDON
|
||||
// recentEmojis=fromJson(prefs.getString("recentEmojis", "{}"), recentEmojisType, new HashMap<>());
|
||||
notificationFilters=fromJson(prefs.getString("notificationFilters", gson.toJson(PushSubscription.Alerts.ofAll())), notificationFiltersType, PushSubscription.Alerts.ofAll());
|
||||
}
|
||||
|
||||
public long getNotificationsPauseEndTime(){
|
||||
@@ -28,13 +100,73 @@ public class AccountLocalPreferences{
|
||||
prefs.edit().putLong("notificationsPauseTime", time).apply();
|
||||
}
|
||||
|
||||
public ColorPreference getCurrentColor(){
|
||||
return color!=null ? color : GlobalUserPreferences.color!=null ? GlobalUserPreferences.color : ColorPreference.MATERIAL3;
|
||||
}
|
||||
|
||||
public void save(){
|
||||
prefs.edit()
|
||||
.putBoolean("interactionCounts", showInteractionCounts)
|
||||
.putBoolean("emojiInNames", customEmojiInNames)
|
||||
.putBoolean("showCWs", showCWs)
|
||||
.putBoolean("revealCWs", revealCWs)
|
||||
.putBoolean("hideSensitive", hideSensitiveMedia)
|
||||
.putBoolean("serverSideFilters", serverSideFiltersSupported)
|
||||
|
||||
// MEGALODON
|
||||
.putBoolean("showReplies", showReplies)
|
||||
.putBoolean("showBoosts", showBoosts)
|
||||
.putString("recentLanguages", gson.toJson(recentLanguages))
|
||||
.putBoolean("bottomEncoding", bottomEncoding)
|
||||
.putString("defaultContentType", defaultContentType==null ? null : defaultContentType.name())
|
||||
.putBoolean("contentTypesEnabled", contentTypesEnabled)
|
||||
.putString("timelines", gson.toJson(timelines))
|
||||
.putBoolean("localOnlySupported", localOnlySupported)
|
||||
.putBoolean("glitchInstance", glitchInstance)
|
||||
.putString("publishButtonText", publishButtonText)
|
||||
.putString("timelineReplyVisibility", timelineReplyVisibility)
|
||||
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
|
||||
.putBoolean("emojiReactionsEnabled", emojiReactionsEnabled)
|
||||
.putString("showEmojiReactions", showEmojiReactions.name())
|
||||
.putString("color", color!=null ? color.name() : null)
|
||||
.putString("recentCustomEmoji", gson.toJson(recentCustomEmoji))
|
||||
|
||||
// MOSHIDON
|
||||
// .putString("recentEmojis", gson.toJson(recentEmojis))
|
||||
.putString("notificationFilters", gson.toJson(notificationFilters))
|
||||
.apply();
|
||||
}
|
||||
|
||||
public enum ColorPreference{
|
||||
MATERIAL3,
|
||||
PURPLE,
|
||||
PINK,
|
||||
GREEN,
|
||||
BLUE,
|
||||
BROWN,
|
||||
RED,
|
||||
YELLOW,
|
||||
NORD,
|
||||
WHITE;
|
||||
|
||||
public @StringRes int getName() {
|
||||
return switch(this){
|
||||
case MATERIAL3 -> R.string.sk_color_palette_material3;
|
||||
case PINK -> R.string.sk_color_palette_pink;
|
||||
case PURPLE -> R.string.sk_color_palette_purple;
|
||||
case GREEN -> R.string.sk_color_palette_green;
|
||||
case BLUE -> R.string.sk_color_palette_blue;
|
||||
case BROWN -> R.string.sk_color_palette_brown;
|
||||
case RED -> R.string.sk_color_palette_red;
|
||||
case YELLOW -> R.string.sk_color_palette_yellow;
|
||||
case NORD -> R.string.mo_color_palette_nord;
|
||||
case WHITE -> R.string.mo_color_palette_black_and_white;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum ShowEmojiReactions{
|
||||
HIDE_EMPTY,
|
||||
ONLY_OPENED,
|
||||
ALWAYS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.joinmastodon.android.api.session;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -24,6 +25,7 @@ import org.joinmastodon.android.model.Application;
|
||||
import org.joinmastodon.android.model.FilterAction;
|
||||
import org.joinmastodon.android.model.FilterContext;
|
||||
import org.joinmastodon.android.model.FilterResult;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.FollowList;
|
||||
import org.joinmastodon.android.model.LegacyFilter;
|
||||
import org.joinmastodon.android.model.Preferences;
|
||||
@@ -36,6 +38,8 @@ import org.joinmastodon.android.utils.ObjectIdComparator;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
@@ -62,7 +66,7 @@ public class AccountSession{
|
||||
public AccountActivationInfo activationInfo;
|
||||
public Preferences preferences;
|
||||
private transient MastodonAPIController apiController;
|
||||
private transient StatusInteractionController statusInteractionController;
|
||||
private transient StatusInteractionController statusInteractionController, remoteStatusInteractionController;
|
||||
private transient CacheController cacheController;
|
||||
private transient PushSubscriptionManager pushSubscriptionManager;
|
||||
private transient SharedPreferences prefs;
|
||||
@@ -98,6 +102,12 @@ public class AccountSession{
|
||||
return statusInteractionController;
|
||||
}
|
||||
|
||||
public StatusInteractionController getRemoteStatusInteractionController(){
|
||||
if(remoteStatusInteractionController==null)
|
||||
remoteStatusInteractionController=new StatusInteractionController(getID(), false);
|
||||
return remoteStatusInteractionController;
|
||||
}
|
||||
|
||||
public CacheController getCacheController(){
|
||||
if(cacheController==null)
|
||||
cacheController=new CacheController(getID());
|
||||
@@ -114,12 +124,22 @@ public class AccountSession{
|
||||
return '@'+self.username+'@'+domain;
|
||||
}
|
||||
|
||||
public void preferencesFromAccountSource(Account account) {
|
||||
if (account != null && account.source != null && preferences != null) {
|
||||
if (account.source.privacy != null)
|
||||
preferences.postingDefaultVisibility = account.source.privacy;
|
||||
if (account.source.language != null)
|
||||
preferences.postingDefaultLanguage = account.source.language;
|
||||
}
|
||||
}
|
||||
|
||||
public void reloadPreferences(Consumer<Preferences> callback){
|
||||
new GetPreferences()
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Preferences result){
|
||||
preferences=result;
|
||||
preferencesFromAccountSource(self);
|
||||
if(callback!=null)
|
||||
callback.accept(result);
|
||||
AccountSessionManager.getInstance().writeAccountsFile();
|
||||
@@ -128,6 +148,9 @@ public class AccountSession{
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
Log.w(TAG, "Failed to load preferences for account "+getID()+": "+error);
|
||||
if (preferences==null)
|
||||
preferences=new Preferences();
|
||||
preferencesFromAccountSource(self);
|
||||
}
|
||||
})
|
||||
.exec(getID());
|
||||
@@ -198,7 +221,7 @@ public class AccountSession{
|
||||
|
||||
public void savePreferencesIfPending(){
|
||||
if(preferencesNeedSaving){
|
||||
new UpdateAccountCredentialsPreferences(preferences, null, self.discoverable, self.source.indexable)
|
||||
new UpdateAccountCredentialsPreferences(preferences, self.locked, self.discoverable, self.source.indexable)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Account result){
|
||||
@@ -218,57 +241,112 @@ public class AccountSession{
|
||||
|
||||
public AccountLocalPreferences getLocalPreferences(){
|
||||
if(localPreferences==null)
|
||||
localPreferences=new AccountLocalPreferences(getRawLocalPreferences());
|
||||
localPreferences=new AccountLocalPreferences(getRawLocalPreferences(), this);
|
||||
return localPreferences;
|
||||
}
|
||||
|
||||
public void filterStatuses(List<Status> statuses, FilterContext context){
|
||||
filterStatusContainingObjects(statuses, Function.identity(), context);
|
||||
filterStatuses(statuses, context, null);
|
||||
}
|
||||
|
||||
public void filterStatuses(List<Status> statuses, FilterContext context, Account profile){
|
||||
filterStatusContainingObjects(statuses, Function.identity(), context, profile);
|
||||
}
|
||||
|
||||
public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context){
|
||||
if(getLocalPreferences().serverSideFiltersSupported){
|
||||
// Even with server-side filters, clients are expected to remove statuses that match a filter that hides them
|
||||
objects.removeIf(o->{
|
||||
Status s=extractor.apply(o);
|
||||
if(s==null)
|
||||
return false;
|
||||
if(s.filtered==null)
|
||||
return false;
|
||||
for(FilterResult filter:s.filtered){
|
||||
if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
if(wordFilters==null)
|
||||
return;
|
||||
for(T obj:objects){
|
||||
filterStatusContainingObjects(objects, extractor, context, null);
|
||||
}
|
||||
|
||||
private boolean statusIsOnOwnProfile(Status s, Account profile){
|
||||
return self != null && profile != null && s.account != null
|
||||
&& Objects.equals(self.id, profile.id) && Objects.equals(self.id, s.account.id);
|
||||
}
|
||||
|
||||
private boolean isFilteredType(Status s){
|
||||
AccountLocalPreferences localPreferences = getLocalPreferences();
|
||||
return (!localPreferences.showReplies && s.inReplyToId != null)
|
||||
|| (!localPreferences.showBoosts && s.reblog != null);
|
||||
}
|
||||
|
||||
public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context, Account profile){
|
||||
AccountLocalPreferences localPreferences = getLocalPreferences();
|
||||
if(!localPreferences.serverSideFiltersSupported) for(T obj:objects){
|
||||
Status s=extractor.apply(obj);
|
||||
if(s!=null && s.filtered!=null){
|
||||
getLocalPreferences().serverSideFiltersSupported=true;
|
||||
getLocalPreferences().save();
|
||||
return;
|
||||
localPreferences.serverSideFiltersSupported=true;
|
||||
localPreferences.save();
|
||||
break;
|
||||
}
|
||||
}
|
||||
objects.removeIf(o->{
|
||||
Status s=extractor.apply(o);
|
||||
if(s==null)
|
||||
return false;
|
||||
for(LegacyFilter filter:wordFilters){
|
||||
|
||||
List<T> removeUs=new ArrayList<>();
|
||||
for(int i=0; i<objects.size(); i++){
|
||||
T o=objects.get(i);
|
||||
if(filterStatusContainingObject(o, extractor, context, profile)){
|
||||
Status s=extractor.apply(o);
|
||||
removeUs.add(o);
|
||||
if(s!=null && s.hasGapAfter!=null && i>0){
|
||||
// oops, we're about to remove an item that has a gap after...
|
||||
// gotta find the previous status that's not also about to be removed
|
||||
for(int j=i-1; j>=0; j--){
|
||||
T p=objects.get(j);
|
||||
Status prev=extractor.apply(objects.get(j));
|
||||
if(prev!=null && !removeUs.contains(p)){
|
||||
prev.hasGapAfter=s.hasGapAfter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
objects.removeAll(removeUs);
|
||||
}
|
||||
|
||||
public <T> boolean filterStatusContainingObject(T object, Function<T, Status> extractor, FilterContext context, Account profile){
|
||||
Status s=extractor.apply(object);
|
||||
if(s==null)
|
||||
return false;
|
||||
// don't hide own posts in own profile
|
||||
if(statusIsOnOwnProfile(s, profile))
|
||||
return false;
|
||||
if(isFilteredType(s) && (context == FilterContext.HOME || context == FilterContext.PUBLIC))
|
||||
return true;
|
||||
// Even with server-side filters, clients are expected to remove statuses that match a filter that hides them
|
||||
if(getLocalPreferences().serverSideFiltersSupported){
|
||||
for(FilterResult filter : s.filtered){
|
||||
if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE)
|
||||
return true;
|
||||
}
|
||||
}else if(wordFilters!=null){
|
||||
for(LegacyFilter filter : wordFilters){
|
||||
if(filter.context.contains(context) && filter.matches(s) && filter.isActive())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void updateAccountInfo(){
|
||||
AccountSessionManager.getInstance().updateSessionLocalInfo(this);
|
||||
}
|
||||
|
||||
public Optional<Instance> getInstance() {
|
||||
return Optional.ofNullable(AccountSessionManager.getInstance().getInstanceInfo(domain));
|
||||
}
|
||||
|
||||
public Uri getInstanceUri() {
|
||||
return new Uri.Builder()
|
||||
.scheme("https")
|
||||
.authority(getInstance().map(i -> i.normalizedUri).orElse(domain))
|
||||
.build();
|
||||
}
|
||||
|
||||
public String getDefaultAvatarUrl() {
|
||||
return getInstance()
|
||||
.map(instance->"https://"+domain+(instance.isAkkoma() ? "/images/avi.png" : "/avatars/original/missing.png"))
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
public boolean isNotificationsMentionsOnly(){
|
||||
return getRawLocalPreferences().getBoolean("notificationsMentionsOnly", false);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.joinmastodon.android.api.session;
|
||||
|
||||
import static org.unifiedpush.android.connector.UnifiedPush.getDistributor;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.ComponentName;
|
||||
@@ -15,6 +17,7 @@ import android.util.Log;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
@@ -33,6 +36,7 @@ import org.joinmastodon.android.model.EmojiCategory;
|
||||
import org.joinmastodon.android.model.LegacyFilter;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Token;
|
||||
import org.unifiedpush.android.connector.UnifiedPush;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
@@ -47,6 +51,7 @@ import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -59,7 +64,7 @@ import me.grishka.appkit.api.ErrorResponse;
|
||||
public class AccountSessionManager{
|
||||
private static final String TAG="AccountSessionManager";
|
||||
public static final String SCOPE="read write follow push";
|
||||
public static final String REDIRECT_URI="mastodon-android-auth://callback";
|
||||
public static final String REDIRECT_URI = getRedirectURI();
|
||||
|
||||
private static final AccountSessionManager instance=new AccountSessionManager();
|
||||
|
||||
@@ -78,6 +83,17 @@ public class AccountSessionManager{
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static String getRedirectURI() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("moshidon-android-");
|
||||
if (BuildConfig.BUILD_TYPE.equals("debug") || BuildConfig.BUILD_TYPE.equals("nightly")) {
|
||||
builder.append(BuildConfig.BUILD_TYPE);
|
||||
builder.append('-');
|
||||
}
|
||||
builder.append("auth://callback");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private AccountSessionManager(){
|
||||
prefs=MastodonApp.context.getSharedPreferences("account_manager", Context.MODE_PRIVATE);
|
||||
File file=new File(MastodonApp.context.getFilesDir(), "accounts.json");
|
||||
@@ -99,27 +115,43 @@ public class AccountSessionManager{
|
||||
}
|
||||
|
||||
public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){
|
||||
Context context = MastodonApp.context;
|
||||
instances.put(instance.uri, instance);
|
||||
AccountSession session=new AccountSession(token, self, app, instance.uri, activationInfo==null, activationInfo);
|
||||
sessions.put(session.getID(), session);
|
||||
lastActiveAccountID=session.getID();
|
||||
writeAccountsFile();
|
||||
updateInstanceEmojis(instance, instance.uri);
|
||||
if(PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||
|
||||
// write initial instance info to file immediately to avoid sessions without instance info
|
||||
InstanceInfoStorageWrapper wrapper = new InstanceInfoStorageWrapper();
|
||||
wrapper.instance = instance;
|
||||
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, instance.uri));
|
||||
|
||||
updateMoreInstanceInfo(instance, instance.uri);
|
||||
if (!UnifiedPush.getDistributor(context).isEmpty()) {
|
||||
UnifiedPush.registerApp(
|
||||
context,
|
||||
session.getID(),
|
||||
new ArrayList<>(),
|
||||
context.getPackageName()
|
||||
);
|
||||
} else if(PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||
session.getPushSubscriptionManager().registerAccountForPush(null);
|
||||
}
|
||||
maybeUpdateShortcuts();
|
||||
}
|
||||
|
||||
public synchronized void writeAccountsFile(){
|
||||
File file=new File(MastodonApp.context.getFilesDir(), "accounts.json");
|
||||
File tmpFile = new File(MastodonApp.context.getFilesDir(), "accounts.json~");
|
||||
File file = new File(MastodonApp.context.getFilesDir(), "accounts.json");
|
||||
try{
|
||||
try(FileOutputStream out=new FileOutputStream(file)){
|
||||
try(FileOutputStream out=new FileOutputStream(tmpFile)){
|
||||
SessionsStorageWrapper w=new SessionsStorageWrapper();
|
||||
w.accounts=new ArrayList<>(sessions.values());
|
||||
OutputStreamWriter writer=new OutputStreamWriter(out, StandardCharsets.UTF_8);
|
||||
MastodonAPIController.gson.toJson(w, writer);
|
||||
writer.flush();
|
||||
if (!tmpFile.renameTo(file)) Log.e(TAG, "Error renaming " + tmpFile.getPath() + " to " + file.getPath());
|
||||
}
|
||||
}catch(IOException x){
|
||||
Log.e(TAG, "Error writing accounts file", x);
|
||||
@@ -149,6 +181,15 @@ public class AccountSessionManager{
|
||||
return sessions.get(id);
|
||||
}
|
||||
|
||||
public static Optional<AccountSession> getOptional(String id) {
|
||||
return Optional.ofNullable(getInstance().tryGetAccount(id));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AccountSession tryGetAccount(Account account) {
|
||||
return sessions.get(account.getDomainFromURL() + "_" + account.id);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AccountSession getLastActiveAccount(){
|
||||
if(sessions.isEmpty() || lastActiveAccountID==null)
|
||||
@@ -225,7 +266,7 @@ public class AccountSessionManager{
|
||||
.path("/oauth/authorize")
|
||||
.appendQueryParameter("response_type", "code")
|
||||
.appendQueryParameter("client_id", result.clientId)
|
||||
.appendQueryParameter("redirect_uri", "mastodon-android-auth://callback")
|
||||
.appendQueryParameter("redirect_uri", REDIRECT_URI)
|
||||
.appendQueryParameter("scope", SCOPE)
|
||||
.build();
|
||||
|
||||
@@ -258,27 +299,32 @@ public class AccountSessionManager{
|
||||
}
|
||||
|
||||
public void maybeUpdateLocalInfo(){
|
||||
maybeUpdateLocalInfo(null);
|
||||
}
|
||||
|
||||
public void maybeUpdateLocalInfo(AccountSession activeSession){
|
||||
long now=System.currentTimeMillis();
|
||||
HashSet<String> domains=new HashSet<>();
|
||||
for(AccountSession session:sessions.values()){
|
||||
domains.add(session.domain.toLowerCase());
|
||||
if(now-session.infoLastUpdated>24L*3600_000L){
|
||||
if(session == activeSession || now-session.infoLastUpdated>24L*3600_000L){
|
||||
session.reloadPreferences(null);
|
||||
updateSessionLocalInfo(session);
|
||||
}
|
||||
if(!session.getLocalPreferences().serverSideFiltersSupported && now-session.filtersLastUpdated>3600_000L){
|
||||
if(session == activeSession || (session.getLocalPreferences().serverSideFiltersSupported && now-session.filtersLastUpdated>3600_000L)){
|
||||
updateSessionWordFilters(session);
|
||||
}
|
||||
}
|
||||
if(loadedInstances){
|
||||
maybeUpdateCustomEmojis(domains);
|
||||
maybeUpdateCustomEmojis(domains, activeSession != null ? activeSession.domain : null);
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeUpdateCustomEmojis(Set<String> domains){
|
||||
private void maybeUpdateCustomEmojis(Set<String> domains, String activeDomain){
|
||||
long now=System.currentTimeMillis();
|
||||
for(String domain:domains){
|
||||
Long lastUpdated=instancesLastUpdated.get(domain);
|
||||
if(lastUpdated==null || now-lastUpdated>24L*3600_000L){
|
||||
if(domain.equals(activeDomain) || lastUpdated==null || now-lastUpdated>24L*3600_000L){
|
||||
updateInstanceInfo(domain);
|
||||
}
|
||||
}
|
||||
@@ -291,6 +337,7 @@ public class AccountSessionManager{
|
||||
public void onSuccess(Account result){
|
||||
session.self=result;
|
||||
session.infoLastUpdated=System.currentTimeMillis();
|
||||
session.preferencesFromAccountSource(result);
|
||||
writeAccountsFile();
|
||||
}
|
||||
|
||||
@@ -326,7 +373,7 @@ public class AccountSessionManager{
|
||||
@Override
|
||||
public void onSuccess(Instance instance){
|
||||
instances.put(domain, instance);
|
||||
updateInstanceEmojis(instance, domain);
|
||||
updateMoreInstanceInfo(instance, domain);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -337,6 +384,21 @@ public class AccountSessionManager{
|
||||
.execNoAuth(domain);
|
||||
}
|
||||
|
||||
public void updateMoreInstanceInfo(Instance instance, String domain) {
|
||||
new GetInstance.V2().setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Instance.V2 v2) {
|
||||
if (instance != null) instance.v2 = v2;
|
||||
updateInstanceEmojis(instance, domain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse errorResponse) {
|
||||
updateInstanceEmojis(instance, domain);
|
||||
}
|
||||
}).execNoAuth(instance.uri);
|
||||
}
|
||||
|
||||
private void updateInstanceEmojis(Instance instance, String domain){
|
||||
new GetCustomEmojis()
|
||||
.setCallback(new Callback<>(){
|
||||
@@ -354,7 +416,9 @@ public class AccountSessionManager{
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
|
||||
InstanceInfoStorageWrapper wrapper=new InstanceInfoStorageWrapper();
|
||||
wrapper.instance = instance;
|
||||
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, domain));
|
||||
}
|
||||
})
|
||||
.execNoAuth(domain);
|
||||
@@ -365,10 +429,13 @@ public class AccountSessionManager{
|
||||
}
|
||||
|
||||
private void writeInstanceInfoFile(InstanceInfoStorageWrapper emojis, String domain){
|
||||
try(FileOutputStream out=new FileOutputStream(getInstanceInfoFile(domain))){
|
||||
File file = getInstanceInfoFile(domain);
|
||||
File tmpFile = new File(file.getPath() + "~");
|
||||
try(FileOutputStream out=new FileOutputStream(tmpFile)){
|
||||
OutputStreamWriter writer=new OutputStreamWriter(out, StandardCharsets.UTF_8);
|
||||
MastodonAPIController.gson.toJson(emojis, writer);
|
||||
writer.flush();
|
||||
if (!tmpFile.renameTo(file)) Log.e(TAG, "Error renaming " + tmpFile.getPath() + " to " + file.getPath());
|
||||
}catch(IOException x){
|
||||
Log.w(TAG, "Error writing instance info file for "+domain, x);
|
||||
}
|
||||
@@ -388,7 +455,7 @@ public class AccountSessionManager{
|
||||
}
|
||||
if(!loadedInstances){
|
||||
loadedInstances=true;
|
||||
maybeUpdateCustomEmojis(domains);
|
||||
maybeUpdateCustomEmojis(domains, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user