fedilab-Android-App/app/src/main/java/app/fedilab/android/mastodon/helper/CrossActionHelper.java

566 lines
28 KiB
Java

package app.fedilab.android.mastodon.helper;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.preference.PreferenceManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.io.IOException;
import java.net.IDN;
import java.util.ArrayList;
import java.util.List;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.mastodon.activities.BaseActivity;
import app.fedilab.android.mastodon.activities.ComposeActivity;
import app.fedilab.android.mastodon.client.endpoints.MastodonSearchService;
import app.fedilab.android.mastodon.client.entities.api.Results;
import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.Account;
import app.fedilab.android.mastodon.client.entities.app.BaseAccount;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.ui.drawer.AccountsSearchAdapter;
import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.SearchVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.StatusesVM;
import es.dmoral.toasty.Toasty;
import okhttp3.OkHttpClient;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class CrossActionHelper {
/**
* Allow to do the action with another account from db
*
* @param context Context
* @param actionType enum TypeOfCrossAction
* @param targetedAccount mastodon account that is targeted
* @param targetedStatus status that is targeted
*/
public static void doCrossAction(@NonNull Context context, @NonNull TypeOfCrossAction actionType, app.fedilab.android.mastodon.client.entities.api.Account targetedAccount, Status targetedStatus) {
final SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
new Thread(() -> {
try {
List<BaseAccount> accounts = new Account(context).getCrossAccounts();
if (accounts.size() == 1) {
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> fetchRemote(context, actionType, accounts.get(0), targetedAccount, targetedStatus);
mainHandler.post(myRunnable);
} else {
List<app.fedilab.android.mastodon.client.entities.api.Account> accountList = new ArrayList<>();
for (BaseAccount account : accounts) {
account.mastodon_account.acct += "@" + account.instance;
accountList.add(account.mastodon_account);
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
AlertDialog.Builder builderSingle = new MaterialAlertDialogBuilder(context);
builderSingle.setTitle(context.getString(R.string.choose_accounts));
final AccountsSearchAdapter accountsSearchAdapter = new AccountsSearchAdapter(context, accountList);
final BaseAccount[] accountArray = new BaseAccount[accounts.size()];
int i = 0;
for (BaseAccount account : accounts) {
accountArray[i] = account;
i++;
}
builderSingle.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
builderSingle.setAdapter(accountsSearchAdapter, (dialog, which) -> {
boolean confirmFav = sharedpreferences.getBoolean(context.getString(R.string.SET_NOTIF_VALIDATION_FAV), false);
boolean confirmBoost = sharedpreferences.getBoolean(context.getString(R.string.SET_NOTIF_VALIDATION), true);
BaseAccount selectedAccount = accountArray[which];
if ((actionType == TypeOfCrossAction.REBLOG_ACTION && confirmBoost) || (actionType == TypeOfCrossAction.FAVOURITE_ACTION && confirmFav)) {
AlertDialog.Builder alt_bld = new MaterialAlertDialogBuilder(context);
if (actionType == TypeOfCrossAction.REBLOG_ACTION) {
alt_bld.setMessage(context.getString(R.string.reblog_add));
} else {
alt_bld.setMessage(context.getString(R.string.favourite_add));
}
alt_bld.setPositiveButton(R.string.yes, (dia, id) -> {
fetchRemote(context, actionType, selectedAccount, targetedAccount, targetedStatus);
dialog.dismiss();
});
alt_bld.setNegativeButton(R.string.cancel, (dia, id) -> dialog.dismiss());
AlertDialog alert = alt_bld.create();
alert.show();
} else {
fetchRemote(context, actionType, selectedAccount, targetedAccount, targetedStatus);
}
dialog.dismiss();
});
builderSingle.show();
};
mainHandler.post(myRunnable);
}
} catch (DBException e) {
e.printStackTrace();
}
}).start();
}
/**
* Fetch and federate the remote account or status
*/
private static void fetchRemote(@NonNull Context context, @NonNull TypeOfCrossAction actionType, @NonNull BaseAccount ownerAccount, app.fedilab.android.mastodon.client.entities.api.Account targetedAccount, Status targetedStatus) {
SearchVM searchVM = new ViewModelProvider((ViewModelStoreOwner) context).get("crossactions", SearchVM.class);
if (actionType == TypeOfCrossAction.COMPOSE) {
applyAction(context, actionType, ownerAccount, null, null);
} else if (targetedAccount != null) {
String search;
if (targetedAccount.acct.contains("@")) { //Not from same instance
search = targetedAccount.acct;
} else {
search = targetedAccount.acct + "@" + BaseMainActivity.currentInstance;
}
searchVM.search(ownerAccount.instance, ownerAccount.token, search, null, "accounts", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> {
if (results.accounts != null && results.accounts.size() > 0) {
app.fedilab.android.mastodon.client.entities.api.Account account = results.accounts.get(0);
applyAction(context, actionType, ownerAccount, account, null);
} else {
Toasty.info(context, context.getString(R.string.toast_error_search), Toasty.LENGTH_SHORT).show();
}
});
} else if (targetedStatus != null) {
searchVM.search(ownerAccount.instance, ownerAccount.token, targetedStatus.uri, null, "statuses", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> {
if (results != null && results.statuses != null && results.statuses.size() > 0) {
Status status = results.statuses.get(0);
applyAction(context, actionType, ownerAccount, null, status);
} else {
Toasty.info(context, context.getString(R.string.toast_error_search), Toasty.LENGTH_SHORT).show();
}
});
} else {
Toasty.info(context, context.getString(R.string.toast_error_search), Toasty.LENGTH_SHORT).show();
}
}
/**
* Do action when status or account has been fetched
*/
private static void applyAction(@NonNull Context context, @NonNull TypeOfCrossAction actionType, @NonNull BaseAccount ownerAccount, app.fedilab.android.mastodon.client.entities.api.Account targetedAccount, Status targetedStatus) {
AccountsVM accountsVM = null;
StatusesVM statusesVM = null;
if (targetedAccount != null) {
accountsVM = new ViewModelProvider((ViewModelStoreOwner) context).get("crossactions", AccountsVM.class);
}
if (targetedStatus != null) {
statusesVM = new ViewModelProvider((ViewModelStoreOwner) context).get("crossactions", StatusesVM.class);
}
switch (actionType) {
case MUTE_ACTION -> {
assert accountsVM != null;
accountsVM.mute(ownerAccount.instance, ownerAccount.token, targetedAccount.id, true, 0)
.observe((LifecycleOwner) context, relationShip -> Toasty.info(context, context.getString(R.string.toast_mute), Toasty.LENGTH_SHORT).show());
}
case UNMUTE_ACTION -> {
assert accountsVM != null;
accountsVM.unmute(ownerAccount.instance, ownerAccount.token, targetedAccount.id)
.observe((LifecycleOwner) context, relationShip -> Toasty.info(context, context.getString(R.string.toast_unmute), Toasty.LENGTH_SHORT).show());
}
case BLOCK_ACTION -> {
assert accountsVM != null;
accountsVM.block(ownerAccount.instance, ownerAccount.token, targetedAccount.id)
.observe((LifecycleOwner) context, relationShip -> Toasty.info(context, context.getString(R.string.toast_block), Toasty.LENGTH_SHORT).show());
}
case UNBLOCK_ACTION -> {
assert accountsVM != null;
accountsVM.unblock(ownerAccount.instance, ownerAccount.token, targetedAccount.id)
.observe((LifecycleOwner) context, relationShip -> Toasty.info(context, context.getString(R.string.toast_unblock), Toasty.LENGTH_SHORT).show());
}
case FOLLOW_ACTION -> {
assert accountsVM != null;
accountsVM.follow(ownerAccount.instance, ownerAccount.token, targetedAccount.id, true, false, null)
.observe((LifecycleOwner) context, relationShip -> Toasty.info(context, context.getString(R.string.toast_follow), Toasty.LENGTH_SHORT).show());
}
case UNFOLLOW_ACTION -> {
assert accountsVM != null;
accountsVM.unfollow(ownerAccount.instance, ownerAccount.token, targetedAccount.id)
.observe((LifecycleOwner) context, relationShip -> Toasty.info(context, context.getString(R.string.toast_unfollow), Toasty.LENGTH_SHORT).show());
}
case FAVOURITE_ACTION -> {
assert statusesVM != null;
statusesVM.favourite(ownerAccount.instance, ownerAccount.token, targetedStatus.id)
.observe((LifecycleOwner) context, status -> Toasty.info(context, context.getString(R.string.toast_favourite), Toasty.LENGTH_SHORT).show());
}
case UNFAVOURITE_ACTION -> {
assert statusesVM != null;
statusesVM.unFavourite(ownerAccount.instance, ownerAccount.token, targetedStatus.id)
.observe((LifecycleOwner) context, status -> Toasty.info(context, context.getString(R.string.toast_unfavourite), Toasty.LENGTH_SHORT).show());
}
case BOOKMARK_ACTION -> {
assert statusesVM != null;
statusesVM.bookmark(ownerAccount.instance, ownerAccount.token, targetedStatus.id)
.observe((LifecycleOwner) context, status -> Toasty.info(context, context.getString(R.string.toast_bookmark), Toasty.LENGTH_SHORT).show());
}
case UNBOOKMARK_ACTION -> {
assert statusesVM != null;
statusesVM.unBookmark(ownerAccount.instance, ownerAccount.token, targetedStatus.id)
.observe((LifecycleOwner) context, status -> Toasty.info(context, context.getString(R.string.toast_unbookmark), Toasty.LENGTH_SHORT).show());
}
case REBLOG_ACTION -> {
assert statusesVM != null;
statusesVM.reblog(ownerAccount.instance, ownerAccount.token, targetedStatus.id, null)
.observe((LifecycleOwner) context, status -> Toasty.info(context, context.getString(R.string.toast_reblog), Toasty.LENGTH_SHORT).show());
}
case UNREBLOG_ACTION -> {
assert statusesVM != null;
statusesVM.unReblog(ownerAccount.instance, ownerAccount.token, targetedStatus.id)
.observe((LifecycleOwner) context, status -> Toasty.info(context, context.getString(R.string.toast_unreblog), Toasty.LENGTH_SHORT).show());
}
case REPLY_ACTION -> {
Intent intent = new Intent(context, ComposeActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_STATUS_REPLY, targetedStatus);
args.putSerializable(Helper.ARG_ACCOUNT, ownerAccount);
new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
context.startActivity(intent);
});
}
case COMPOSE -> {
Intent intentCompose = new Intent(context, ComposeActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_ACCOUNT, ownerAccount);
new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentCompose.putExtras(bundle);
context.startActivity(intentCompose);
});
}
}
}
private static MastodonSearchService init(Context context, String instance) {
final OkHttpClient okHttpClient = Helper.myOkHttpClient(context);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + (instance != null ? IDN.toASCII(instance, IDN.ALLOW_UNASSIGNED) : null) + "/api/v2/")
.addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder()))
.client(okHttpClient)
.build();
return retrofit.create(MastodonSearchService.class);
}
/**
* Fetch and federate the remote status
*/
public static void fetchRemoteStatus(@NonNull Context context, @NonNull BaseAccount ownerAccount, String url, Callback callback) {
MastodonSearchService mastodonSearchService = init(context, BaseMainActivity.currentInstance);
new Thread(() -> {
Call<Results> resultsCall = mastodonSearchService.search(ownerAccount.token, url, null, "statuses", false, true, false, 0, null, null, 1);
Results results = null;
if (resultsCall != null) {
try {
Response<Results> resultsResponse = resultsCall.execute();
if (resultsResponse.isSuccessful()) {
results = resultsResponse.body();
if (results != null) {
if (results.statuses == null) {
results.statuses = new ArrayList<>();
}
if (results.accounts == null) {
results.accounts = new ArrayList<>();
}
if (results.hashtags == null) {
results.hashtags = new ArrayList<>();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Results finalResults = results;
Runnable myRunnable = () -> {
if (finalResults != null && finalResults.statuses != null && finalResults.statuses.size() > 0) {
callback.federatedStatus(finalResults.statuses.get(0));
}
};
mainHandler.post(myRunnable);
}).start();
}
/**
* Fetch and federate the remote status
*/
public static void fetchRemoteAccount(@NonNull Context context, @NonNull BaseAccount ownerAccount, String targetedAcct, Callback callback) {
MastodonSearchService mastodonSearchService = init(context, BaseMainActivity.currentInstance);
String search;
if (targetedAcct.contains("@")) { //Not from same instance
search = targetedAcct;
} else {
search = targetedAcct + "@" + BaseMainActivity.currentInstance;
}
new Thread(() -> {
Call<Results> resultsCall = mastodonSearchService.search(ownerAccount.token, search, null, "accounts", false, true, false, 0, null, null, 1);
Results results = null;
if (resultsCall != null) {
try {
Response<Results> resultsResponse = resultsCall.execute();
if (resultsResponse.isSuccessful()) {
results = resultsResponse.body();
if (results != null) {
if (results.statuses == null) {
results.statuses = new ArrayList<>();
}
if (results.accounts == null) {
results.accounts = new ArrayList<>();
}
if (results.hashtags == null) {
results.hashtags = new ArrayList<>();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Results finalResults = results;
Runnable myRunnable = () -> {
if (finalResults != null && finalResults.accounts != null && finalResults.accounts.size() > 0) {
callback.federatedAccount(finalResults.accounts.get(0));
}
};
mainHandler.post(myRunnable);
}).start();
}
/**
* Fetch and federate the remote status
*/
public static void fetchRemoteAccount(@NonNull Context context, String acct, Callback callback) {
MastodonSearchService mastodonSearchService = init(context, BaseMainActivity.currentInstance);
new Thread(() -> {
Call<Results> resultsCall = mastodonSearchService.search(BaseMainActivity.currentToken, acct, null, "accounts", false, true, false, 0, null, null, 1);
Results results = null;
if (resultsCall != null) {
try {
Response<Results> resultsResponse = resultsCall.execute();
if (resultsResponse.isSuccessful()) {
results = resultsResponse.body();
if (results != null) {
if (results.statuses == null) {
results.statuses = new ArrayList<>();
}
if (results.accounts == null) {
results.accounts = new ArrayList<>();
}
if (results.hashtags == null) {
results.hashtags = new ArrayList<>();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Results finalResults = results;
Runnable myRunnable = () -> {
if (finalResults != null && finalResults.accounts != null && finalResults.accounts.size() > 0) {
callback.federatedAccount(finalResults.accounts.get(0));
}
};
mainHandler.post(myRunnable);
}).start();
}
public static void doCrossShare(final Context context, final Bundle bundle) {
List<BaseAccount> accounts;
try {
accounts = new Account(context).getCrossAccounts();
List<app.fedilab.android.mastodon.client.entities.api.Account> accountList = new ArrayList<>();
for (BaseAccount account : accounts) {
account.mastodon_account.acct += "@" + account.instance;
accountList.add(account.mastodon_account);
}
if (accounts.size() == 1) {
Intent intentToot = new Intent(context, ComposeActivity.class);
intentToot.putExtras(bundle);
context.startActivity(intentToot);
((BaseActivity) context).finish();
} else {
AlertDialog.Builder builderSingle = new MaterialAlertDialogBuilder(context);
builderSingle.setTitle(context.getString(R.string.choose_accounts));
final AccountsSearchAdapter accountsSearchAdapter = new AccountsSearchAdapter(context, accountList);
final BaseAccount[] accountArray = new BaseAccount[accounts.size()];
int i = 0;
for (BaseAccount account : accounts) {
accountArray[i] = account;
i++;
}
builderSingle.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
builderSingle.setAdapter(accountsSearchAdapter, (dialog, which) -> {
final BaseAccount account = accountArray[which];
Intent intentToot = new Intent(context, ComposeActivity.class);
bundle.putSerializable(Helper.ARG_ACCOUNT, account);
new CachedBundle(context).insertBundle(bundle, Helper.getCurrentAccount(context), bundleId -> {
Bundle bundleCached = new Bundle();
bundleCached.putLong(Helper.ARG_INTENT_ID, bundleId);
intentToot.putExtras(bundleCached);
context.startActivity(intentToot);
((BaseActivity) context).finish();
dialog.dismiss();
});
});
builderSingle.show();
}
} catch (DBException e) {
e.printStackTrace();
}
}
/**
* Fetch and federate the remote status
*/
public static void fetchStatusInRemoteInstance(@NonNull Context context, String url, String instance, Callback callback) {
MastodonSearchService mastodonSearchService = init(context, instance);
new Thread(() -> {
Call<Results> resultsCall = mastodonSearchService.search(null, url, null, "statuses", null, null, null, null, null, null, null);
Results results = null;
if (resultsCall != null) {
try {
Response<Results> resultsResponse = resultsCall.execute();
if (resultsResponse.isSuccessful()) {
results = resultsResponse.body();
if (results != null) {
if (results.statuses == null) {
results.statuses = new ArrayList<>();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Results finalResults = results;
Runnable myRunnable = () -> {
if (finalResults != null && finalResults.statuses != null && finalResults.statuses.size() > 0) {
callback.federatedStatus(finalResults.statuses.get(0));
}
};
mainHandler.post(myRunnable);
}).start();
}
/**
* Fetch and federate the remote status
*/
public static void fetchAccountInRemoteInstance(@NonNull Context context, String acct, String instance, Callback callback) {
MastodonSearchService mastodonSearchService = init(context, instance);
new Thread(() -> {
Call<Results> resultsCall = mastodonSearchService.search(null, acct, null, "accounts", null, null, null, null, null, null, 1);
Results results = null;
if (resultsCall != null) {
try {
Response<Results> resultsResponse = resultsCall.execute();
if (resultsResponse.isSuccessful()) {
results = resultsResponse.body();
if (results != null) {
if (results.accounts == null) {
results.accounts = new ArrayList<>();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Results finalResults = results;
Runnable myRunnable = () -> {
if (finalResults != null && finalResults.accounts != null && finalResults.accounts.size() > 0) {
callback.federatedAccount(finalResults.accounts.get(0));
}
};
mainHandler.post(myRunnable);
}).start();
}
public enum TypeOfCrossAction {
FOLLOW_ACTION,
UNFOLLOW_ACTION,
MUTE_ACTION,
UNMUTE_ACTION,
BLOCK_ACTION,
UNBLOCK_ACTION,
FAVOURITE_ACTION,
UNFAVOURITE_ACTION,
BOOKMARK_ACTION,
UNBOOKMARK_ACTION,
REBLOG_ACTION,
UNREBLOG_ACTION,
REPLY_ACTION,
COMPOSE
}
public interface Callback {
void federatedStatus(Status status);
void federatedAccount(app.fedilab.android.mastodon.client.entities.api.Account account);
}
}