Re-wrote FlattrClickWorker
This commit is contained in:
parent
16caf87e2b
commit
b6c3dd9858
@ -165,6 +165,7 @@
|
||||
<string name="return_home_label">Return to home</string>
|
||||
<string name="flattr_auth_success">Authentication was successful! You can now flattr things within the app.</string>
|
||||
<string name="no_flattr_token_title">No Flattr token found</string>
|
||||
<string name="no_flattr_token_notification_msg">Your flattr account does not seem to be connected to AntennaPod. Tap here to authenticate.</string>
|
||||
<string name="no_flattr_token_msg">Your flattr account does not seem to be connected to AntennaPod. You can either connect your account to AntennaPod to flattr things within the app or you can visit the website of the thing to flattr it there.</string>
|
||||
<string name="authenticate_now_label">Authenticate</string>
|
||||
<string name="action_forbidden_title">Action forbidden</string>
|
||||
|
@ -1,300 +1,235 @@
|
||||
package de.danoeh.antennapod.asynctask;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.shredzone.flattr4j.exception.FlattrException;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import de.danoeh.antennapod.BuildConfig;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.FlattrAuthActivity;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.storage.DBReader;
|
||||
import de.danoeh.antennapod.storage.DBWriter;
|
||||
import de.danoeh.antennapod.util.NetworkUtils;
|
||||
import de.danoeh.antennapod.util.flattr.FlattrThing;
|
||||
import de.danoeh.antennapod.util.flattr.FlattrUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Performs a click action in a background thread.
|
||||
* <p/>
|
||||
* When started, the flattr click worker will try to flattr every item that is in the flattr queue. If no network
|
||||
* connection is available it will shut down immediately. The FlattrClickWorker can also be given one additional
|
||||
* FlattrThing which will be flattrd immediately.
|
||||
* <p/>
|
||||
* The FlattrClickWorker will display a toast notification for every item that has been flattrd. If the FlattrClickWorker failed
|
||||
* to flattr something, a notification will be displayed.
|
||||
*/
|
||||
|
||||
public class FlattrClickWorker extends AsyncTask<Void, String, Void> {
|
||||
public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorker.ExitCode> {
|
||||
protected static final String TAG = "FlattrClickWorker";
|
||||
protected Context context;
|
||||
|
||||
private final int NOTIFICATION_ID = 4;
|
||||
private static final int NOTIFICATION_ID = 4;
|
||||
|
||||
protected String errorMsg;
|
||||
protected int exitCode;
|
||||
protected ArrayList<String> flattrd;
|
||||
protected ArrayList<String> flattr_failed;
|
||||
private final Context context;
|
||||
|
||||
public static enum ExitCode {EXIT_NORMAL, NO_TOKEN, NO_NETWORK, NO_THINGS}
|
||||
|
||||
protected NotificationCompat.Builder notificationCompatBuilder;
|
||||
private Notification.BigTextStyle notificationBuilder;
|
||||
protected NotificationManager notificationManager;
|
||||
private volatile int countFailed = 0;
|
||||
private volatile int countSuccess = 0;
|
||||
|
||||
protected ProgressDialog progDialog;
|
||||
|
||||
protected final static int EXIT_DEFAULT = 0;
|
||||
protected final static int NO_TOKEN = 1;
|
||||
protected final static int ENQUEUED = 2;
|
||||
protected final static int NO_THINGS = 3;
|
||||
|
||||
public final static int ENQUEUE_ONLY = 1;
|
||||
public final static int FLATTR_TOAST = 2;
|
||||
public static final int FLATTR_NOTIFICATION = 3;
|
||||
|
||||
private int run_mode = FLATTR_NOTIFICATION;
|
||||
|
||||
private FlattrThing extra_flattr_thing; // additional urls to flattr that do *not* originate from the queue
|
||||
private volatile FlattrThing extraFlattrThing;
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* @param run_mode can be one of ENQUEUE_ONLY, FLATTR_TOAST and FLATTR_NOTIFICATION
|
||||
* Only relevant if just one thing is flattrd
|
||||
*/
|
||||
public FlattrClickWorker(Context context, int run_mode) {
|
||||
this(context);
|
||||
this.run_mode = run_mode;
|
||||
}
|
||||
private volatile FlattrException exception;
|
||||
|
||||
/**
|
||||
* Creates a new FlattrClickWorker which will only flattr all things in the queue.
|
||||
* <p/>
|
||||
* The FlattrClickWorker has to be started by calling executeAsync().
|
||||
*
|
||||
* @param context A context for accessing the database and posting notifications. Must not be null.
|
||||
*/
|
||||
public FlattrClickWorker(Context context) {
|
||||
super();
|
||||
this.context = context;
|
||||
exitCode = EXIT_DEFAULT;
|
||||
|
||||
flattrd = new ArrayList<String>();
|
||||
flattr_failed = new ArrayList<String>();
|
||||
|
||||
errorMsg = "";
|
||||
if (context == null) throw new IllegalArgumentException("context = null");
|
||||
this.context = context.getApplicationContext();
|
||||
}
|
||||
|
||||
/* only used in PreferencesActivity for flattring antennapod itself,
|
||||
* can't really enqueue this thing
|
||||
*/
|
||||
public FlattrClickWorker(Context context, FlattrThing thing) {
|
||||
/**
|
||||
* Creates a new FlattrClickWorker which will flattr all things in the queue and one additional
|
||||
* FlattrThing.
|
||||
* <p/>
|
||||
* The FlattrClickWorker has to be started by calling executeAsync().
|
||||
*
|
||||
* @param context A context for accessing the database and posting notifications. Must not be null.
|
||||
* @param extraFlattrThing The additional thing to flattr
|
||||
*/
|
||||
public FlattrClickWorker(Context context, FlattrThing extraFlattrThing) {
|
||||
this(context);
|
||||
extra_flattr_thing = thing;
|
||||
run_mode = FLATTR_TOAST;
|
||||
Log.d(TAG, "Going to flattr special thing that is not in the queue: " + thing.getTitle());
|
||||
this.extraFlattrThing = extraFlattrThing;
|
||||
}
|
||||
|
||||
protected void onNoAccessToken() {
|
||||
Log.w(TAG, "No access token was available");
|
||||
}
|
||||
|
||||
protected void onFlattrError() {
|
||||
FlattrUtils.showErrorDialog(context, errorMsg);
|
||||
}
|
||||
|
||||
protected void onFlattred() {
|
||||
String notificationTitle = context.getString(R.string.flattrd_label);
|
||||
String notificationText = "", notificationSubText = "", notificationBigText = "";
|
||||
|
||||
// text for successfully flattred items
|
||||
if (flattrd.size() == 1)
|
||||
notificationText = String.format(context.getString(R.string.flattr_click_success));
|
||||
else if (flattrd.size() > 1) // flattred pending items from queue
|
||||
notificationText = String.format(context.getString(R.string.flattr_click_success_count, flattrd.size()));
|
||||
|
||||
if (flattrd.size() > 0) {
|
||||
String acc = "";
|
||||
for (String s : flattrd)
|
||||
acc += s + '\n';
|
||||
acc = acc.substring(0, acc.length() - 2);
|
||||
|
||||
notificationBigText = String.format(context.getString(R.string.flattr_click_success_queue), acc);
|
||||
}
|
||||
|
||||
// add text for failures
|
||||
if (flattr_failed.size() > 0) {
|
||||
notificationTitle = context.getString(R.string.flattrd_failed_label);
|
||||
notificationText = String.format(context.getString(R.string.flattr_click_failure_count), flattr_failed.size())
|
||||
+ " " + notificationText;
|
||||
|
||||
notificationSubText = flattr_failed.get(0);
|
||||
|
||||
String acc = "";
|
||||
for (String s : flattr_failed)
|
||||
acc += s + '\n';
|
||||
acc = acc.substring(0, acc.length() - 2);
|
||||
|
||||
notificationBigText = String.format(context.getString(R.string.flattr_click_failure), acc)
|
||||
+ "\n" + notificationBigText;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Going to post notification: " + notificationBigText);
|
||||
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
|
||||
if (run_mode == FLATTR_NOTIFICATION || flattr_failed.size() > 0) {
|
||||
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0);
|
||||
if (android.os.Build.VERSION.SDK_INT >= 16) {
|
||||
notificationBuilder = new Notification.BigTextStyle(
|
||||
new Notification.Builder(context)
|
||||
.setOngoing(false)
|
||||
.setContentTitle(notificationTitle)
|
||||
.setContentText(notificationText)
|
||||
.setContentIntent(contentIntent)
|
||||
.setSubText(notificationSubText)
|
||||
.setSmallIcon(R.drawable.stat_notify_sync))
|
||||
.bigText(notificationText + "\n" + notificationBigText);
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
|
||||
} else {
|
||||
notificationCompatBuilder = new NotificationCompat.Builder(context) // need new notificationBuilder and cancel/renotify to get rid of progress bar
|
||||
.setContentTitle(notificationTitle)
|
||||
.setContentText(notificationText)
|
||||
.setContentIntent(contentIntent)
|
||||
.setSubText(notificationBigText)
|
||||
.setTicker(notificationTitle)
|
||||
.setSmallIcon(R.drawable.stat_notify_sync)
|
||||
.setOngoing(false);
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationCompatBuilder.build());
|
||||
}
|
||||
} else if (run_mode == FLATTR_TOAST) {
|
||||
Toast.makeText(context.getApplicationContext(),
|
||||
notificationText,
|
||||
Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
protected void onEnqueue() {
|
||||
Toast.makeText(context.getApplicationContext(),
|
||||
R.string.flattr_click_enqueued,
|
||||
Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
|
||||
protected void onSetupNotification() {
|
||||
if (android.os.Build.VERSION.SDK_INT >= 16) {
|
||||
notificationBuilder = new Notification.BigTextStyle(
|
||||
new Notification.Builder(context)
|
||||
.setContentTitle(context.getString(R.string.flattring_label))
|
||||
.setAutoCancel(true)
|
||||
.setSmallIcon(R.drawable.stat_notify_sync)
|
||||
.setProgress(0, 0, true)
|
||||
.setOngoing(true));
|
||||
} else {
|
||||
notificationCompatBuilder = new NotificationCompat.Builder(context)
|
||||
.setContentTitle(context.getString(R.string.flattring_label))
|
||||
.setAutoCancel(true)
|
||||
.setSmallIcon(R.drawable.stat_notify_sync)
|
||||
.setProgress(0, 0, true)
|
||||
.setOngoing(true);
|
||||
}
|
||||
|
||||
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Exit code was " + exitCode);
|
||||
|
||||
switch (exitCode) {
|
||||
case NO_TOKEN:
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
onNoAccessToken();
|
||||
break;
|
||||
case ENQUEUED:
|
||||
onEnqueue();
|
||||
break;
|
||||
case EXIT_DEFAULT:
|
||||
onFlattred();
|
||||
break;
|
||||
case NO_THINGS: // FlattrClickWorker called automatically somewhere to empty flattr queue
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
onSetupNotification();
|
||||
}
|
||||
|
||||
private static boolean haveInternetAccess(Context context) {
|
||||
ConnectivityManager cm =
|
||||
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
|
||||
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
|
||||
return (networkInfo != null && networkInfo.isConnectedOrConnecting());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Starting background work");
|
||||
|
||||
exitCode = EXIT_DEFAULT;
|
||||
protected ExitCode doInBackground(Void... params) {
|
||||
|
||||
if (!FlattrUtils.hasToken()) {
|
||||
exitCode = NO_TOKEN;
|
||||
} else if (DBReader.getFlattrQueueEmpty(context) && extra_flattr_thing == null) {
|
||||
exitCode = NO_THINGS;
|
||||
} else if (!haveInternetAccess(context) || run_mode == ENQUEUE_ONLY) {
|
||||
exitCode = ENQUEUED;
|
||||
} else {
|
||||
List<FlattrThing> flattrList = DBReader.getFlattrQueue(context);
|
||||
Log.d(TAG, "flattrQueue processing list with " + flattrList.size() + " items.");
|
||||
|
||||
if (extra_flattr_thing != null)
|
||||
flattrList.add(extra_flattr_thing);
|
||||
|
||||
flattrd.ensureCapacity(flattrList.size());
|
||||
|
||||
for (FlattrThing thing : flattrList) {
|
||||
try {
|
||||
Log.d(TAG, "flattrQueue processing " + thing.getTitle() + " " + thing.getPaymentLink());
|
||||
publishProgress(String.format(context.getString(R.string.flattring_thing), thing.getTitle()));
|
||||
|
||||
thing.getFlattrStatus().setUnflattred(); // pop from queue to prevent unflattrable things from getting stuck in flattr queue infinitely
|
||||
|
||||
FlattrUtils.clickUrl(context, thing.getPaymentLink());
|
||||
flattrd.add(thing.getTitle());
|
||||
|
||||
thing.getFlattrStatus().setFlattred();
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "flattrQueue processing exception at item " + thing.getTitle() + " " + e.getMessage());
|
||||
flattr_failed.ensureCapacity(flattrList.size());
|
||||
flattr_failed.add(thing.getTitle() + ": " + e.getMessage());
|
||||
}
|
||||
Log.d(TAG, "flattrQueue processing - going to write thing back to db with flattr_status " + Long.toString(thing.getFlattrStatus().toLong()));
|
||||
DBWriter.setFlattredStatus(context, thing, false);
|
||||
}
|
||||
|
||||
return ExitCode.NO_TOKEN;
|
||||
}
|
||||
|
||||
return null;
|
||||
if (!NetworkUtils.networkAvailable(context)) {
|
||||
return ExitCode.NO_NETWORK;
|
||||
}
|
||||
|
||||
final List<FlattrThing> flattrQueue = DBReader.getFlattrQueue(context);
|
||||
if (extraFlattrThing != null) {
|
||||
flattrQueue.add(extraFlattrThing);
|
||||
} else if (flattrQueue.size() == 1) {
|
||||
// if only one item is flattrd, the report can specifically mentioned that this item has failed
|
||||
extraFlattrThing = flattrQueue.get(0);
|
||||
}
|
||||
|
||||
if (flattrQueue.isEmpty()) {
|
||||
return ExitCode.NO_THINGS;
|
||||
}
|
||||
|
||||
List<Future> dbFutures = new LinkedList<Future>();
|
||||
for (FlattrThing thing : flattrQueue) {
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Processing " + thing.getTitle());
|
||||
|
||||
try {
|
||||
thing.getFlattrStatus().setUnflattred(); // pop from queue to prevent unflattrable things from getting stuck in flattr queue infinitely
|
||||
FlattrUtils.clickUrl(context, thing.getPaymentLink());
|
||||
thing.getFlattrStatus().setFlattred();
|
||||
publishProgress(R.string.flattr_click_success);
|
||||
countSuccess++;
|
||||
|
||||
} catch (FlattrException e) {
|
||||
e.printStackTrace();
|
||||
countFailed++;
|
||||
if (countFailed == 1) {
|
||||
exception = e;
|
||||
}
|
||||
}
|
||||
|
||||
Future<?> f = DBWriter.setFlattredStatus(context, thing, false);
|
||||
if (f != null) {
|
||||
dbFutures.add(f);
|
||||
}
|
||||
}
|
||||
|
||||
for (Future f : dbFutures) {
|
||||
try {
|
||||
f.get();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} catch (ExecutionException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return ExitCode.EXIT_NORMAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(String... names) {
|
||||
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0);
|
||||
if (android.os.Build.VERSION.SDK_INT >= 16) {
|
||||
notificationBuilder.setBigContentTitle(names[0]);
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
|
||||
} else {
|
||||
notificationCompatBuilder.setContentText(names[0]);
|
||||
notificationCompatBuilder.setContentIntent(contentIntent);
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationCompatBuilder.build());
|
||||
protected void onPostExecute(ExitCode exitCode) {
|
||||
super.onPostExecute(exitCode);
|
||||
switch (exitCode) {
|
||||
case EXIT_NORMAL:
|
||||
if (countFailed > 0) {
|
||||
postFlattrFailedNotification();
|
||||
}
|
||||
break;
|
||||
case NO_NETWORK:
|
||||
postToastNotification(R.string.flattr_click_enqueued);
|
||||
break;
|
||||
case NO_TOKEN:
|
||||
postNoTokenNotification();
|
||||
break;
|
||||
case NO_THINGS: // nothing to notify here
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... values) {
|
||||
super.onProgressUpdate(values);
|
||||
postToastNotification(values[0]);
|
||||
}
|
||||
|
||||
private void postToastNotification(int msg) {
|
||||
Toast.makeText(context, context.getString(msg), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void postNoTokenNotification() {
|
||||
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, new Intent(context, FlattrAuthActivity.class), 0);
|
||||
|
||||
Notification notification = new NotificationCompat.Builder(context)
|
||||
.setStyle(new NotificationCompat.BigTextStyle().bigText(context.getString(R.string.no_flattr_token_notification_msg)))
|
||||
.setContentIntent(contentIntent)
|
||||
.setContentTitle(context.getString(R.string.no_flattr_token_title))
|
||||
.setTicker(context.getString(R.string.no_flattr_token_title))
|
||||
.setSmallIcon(R.drawable.stat_notify_sync_error)
|
||||
.setOngoing(false)
|
||||
.setAutoCancel(true)
|
||||
.build();
|
||||
((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, notification);
|
||||
}
|
||||
|
||||
private void postFlattrFailedNotification() {
|
||||
if (countFailed == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0);
|
||||
String title;
|
||||
String subtext;
|
||||
|
||||
if (countFailed == 1) {
|
||||
title = context.getString(R.string.flattrd_failed_label);
|
||||
String exceptionMsg = (exception.getMessage() != null) ? exception.getMessage() : "";
|
||||
subtext = context.getString(R.string.flattr_click_failure, extraFlattrThing.getTitle())
|
||||
+ "\n" + exceptionMsg;
|
||||
} else {
|
||||
title = context.getString(R.string.flattrd_label);
|
||||
subtext = context.getString(R.string.flattr_click_success_count, countSuccess) + "\n"
|
||||
+ context.getString(R.string.flattr_click_failure_count, countFailed);
|
||||
}
|
||||
|
||||
Notification notification = new NotificationCompat.Builder(context)
|
||||
.setStyle(new NotificationCompat.BigTextStyle().bigText(subtext))
|
||||
.setContentIntent(contentIntent)
|
||||
.setContentTitle(title)
|
||||
.setTicker(title)
|
||||
.setSmallIcon(R.drawable.stat_notify_sync_error)
|
||||
.setOngoing(false)
|
||||
.setAutoCancel(true)
|
||||
.build();
|
||||
((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, notification);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts the FlattrClickWorker as an AsyncTask.
|
||||
*/
|
||||
@TargetApi(11)
|
||||
public void executeAsync() {
|
||||
FlattrUtils.hasToken();
|
||||
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
|
||||
executeOnExecutor(THREAD_POOL_EXECUTOR);
|
||||
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
} else {
|
||||
execute();
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ public final class DBTasks {
|
||||
|
||||
if (FlattrUtils.hasToken()) {
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Flattring all pending things.");
|
||||
new FlattrClickWorker(context, FlattrClickWorker.FLATTR_NOTIFICATION).executeAsync(); // flattr pending things
|
||||
new FlattrClickWorker(context).executeAsync(); // flattr pending things
|
||||
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Fetching flattr status.");
|
||||
new FlattrStatusFetcher(context).start();
|
||||
|
@ -868,7 +868,7 @@ public class DBWriter {
|
||||
adapter.setFeedItemFlattrStatus(item);
|
||||
adapter.close();
|
||||
if (startFlattrClickWorker) {
|
||||
new FlattrClickWorker(context, FlattrClickWorker.FLATTR_TOAST).executeAsync();
|
||||
new FlattrClickWorker(context).executeAsync();
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -891,7 +891,7 @@ public class DBWriter {
|
||||
adapter.setFeedFlattrStatus(feed);
|
||||
adapter.close();
|
||||
if (startFlattrClickWorker) {
|
||||
new FlattrClickWorker(context, FlattrClickWorker.FLATTR_TOAST).executeAsync();
|
||||
new FlattrClickWorker(context).executeAsync();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user