Merge remote-tracking branch 'megalodon_main/main'

# Conflicts:
#	mastodon/build.gradle
#	mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java
#	mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/GetAccountByHandle.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/BaseAccountListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/FollowerListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/FollowingListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/PaginatedAccountListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/model/Account.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
#	mastodon/src/main/res/layout/display_item_footer.xml
#	mastodon/src/main/res/values/dimens.xml
This commit is contained in:
LucasGGamerM
2023-06-09 18:09:23 -03:00
81 changed files with 1549 additions and 819 deletions

View File

@@ -36,7 +36,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());
@@ -61,7 +61,7 @@ public class CacheController{
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
if(!forceReload){
SQLiteDatabase db=getOrOpenDatabase();
try(Cursor cursor=db.query("home_timeline", new String[]{"json", "flags"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`id` DESC", count+"")){
try(Cursor cursor=db.query("home_timeline", new String[]{"json", "flags"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`time` DESC", count+"")){
if(cursor.getCount()==count){
ArrayList<Status> result=new ArrayList<>();
cursor.moveToFirst();
@@ -112,7 +112,7 @@ public class CacheController{
runOnDbThread((db)->{
if(clear)
db.delete("home_timeline", null, null);
ContentValues values=new ContentValues(3);
ContentValues values=new ContentValues(4);
for(Status s:posts){
values.put("id", s.id);
values.put("json", MastodonAPIController.gson.toJson(s));
@@ -120,6 +120,7 @@ public class CacheController{
if(s.hasGapAfter)
flags|=POST_FLAG_GAP_AFTER;
values.put("flags", flags);
values.put("time", s.createdAt.getEpochSecond());
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
}
});
@@ -134,7 +135,7 @@ public class CacheController{
if(!forceReload){
SQLiteDatabase db=getOrOpenDatabase();
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, "`id` DESC", count+"")){
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();
@@ -192,7 +193,7 @@ public class CacheController{
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
if(clear)
db.delete(table, null, null);
ContentValues values=new ContentValues(3);
ContentValues values=new ContentValues(4);
for(Notification n:notifications){
if(n.type==null){
continue;
@@ -200,6 +201,7 @@ public class CacheController{
values.put("id", n.id);
values.put("json", MastodonAPIController.gson.toJson(n));
values.put("type", n.type.ordinal());
values.put("time", n.createdAt.getEpochSecond());
db.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_REPLACE);
}
});
@@ -296,21 +298,24 @@ public class CacheController{
CREATE TABLE `home_timeline` (
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
`json` TEXT NOT NULL,
`flags` INTEGER NOT NULL DEFAULT 0
`flags` INTEGER NOT NULL DEFAULT 0,
`time` INTEGER NOT NULL
)""");
db.execSQL("""
CREATE TABLE `notifications_all` (
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
`json` TEXT NOT NULL,
`flags` INTEGER NOT NULL DEFAULT 0,
`type` INTEGER NOT NULL
`type` INTEGER NOT NULL,
`time` INTEGER NOT NULL
)""");
db.execSQL("""
CREATE TABLE `notifications_mentions` (
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
`json` TEXT NOT NULL,
`flags` INTEGER NOT NULL DEFAULT 0,
`type` INTEGER NOT NULL
`type` INTEGER NOT NULL,
`time` INTEGER NOT NULL
)""");
createRecentSearchesTable(db);
createPostsNotificationsTable(db);
@@ -318,12 +323,16 @@ public class CacheController{
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
if(oldVersion==1){
if(oldVersion<2){
createRecentSearchesTable(db);
}
if(oldVersion==2){
if(oldVersion<3){
// MEGALODON-SPECIFIC
createPostsNotificationsTable(db);
}
if(oldVersion<4){
addTimeColumns(db);
}
}
private void createRecentSearchesTable(SQLiteDatabase db){
@@ -341,9 +350,21 @@ public class CacheController{
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
`json` TEXT NOT NULL,
`flags` INTEGER NOT NULL DEFAULT 0,
`type` INTEGER NOT NULL
`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");
}
}
@FunctionalInterface

View File

@@ -17,6 +17,7 @@ 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;
@@ -161,6 +162,11 @@ public class MastodonAPIController{
respObj=gson.fromJson(reader, req.respClass);
}
}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, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error parsing or reading body", x);
req.onError(x.getLocalizedMessage(), response.code(), x);

View File

@@ -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,9 +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;
@@ -44,10 +47,11 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
TypeToken<T> respTypeToken;
Call okhttpCall;
Token token;
boolean canceled;
boolean canceled, isRemote;
Map<String, String> headers;
private ProgressDialog progressDialog;
protected boolean removeUnsupportedItems;
@Nullable Context context;
public MastodonAPIRequest(HttpMethod method, String path, Class<T> respClass){
this.path=path;
@@ -101,6 +105,21 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
return this;
}
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)
.orElse(this.execNoAuth(domain));
}
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable){
return wrapProgress(activity, message, cancelable, null);
}
@@ -164,9 +183,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){
@@ -175,6 +205,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);
@@ -182,15 +213,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();
}
}
}
}

View File

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

View File

@@ -4,6 +4,10 @@ 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);

View File

@@ -171,6 +171,11 @@ public class AccountSessionManager{
return sessions.get(id);
}
@Nullable
public AccountSession tryGetAccount(Account account) {
return sessions.get(account.getDomainFromURL() + "_" + account.id);
}
@Nullable
public AccountSession getLastActiveAccount(){
if(sessions.isEmpty() || lastActiveAccountID==null)