mirror of
https://github.com/nuclearfog/Shitter.git
synced 2025-01-31 11:25:03 +01:00
finalized ExoPlayer integration, added video viewer toolbar, bug fix
This commit is contained in:
parent
1cdf1b7d0a
commit
afef032eb0
@ -84,7 +84,8 @@
|
||||
|
||||
<activity
|
||||
android:name=".ui.activities.VideoViewer"
|
||||
android:theme="@style/Transparency" />
|
||||
android:configChanges="orientation|keyboard|screenSize"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.activities.ImageViewer"
|
||||
|
@ -93,7 +93,7 @@ public class TwitterV1Instance implements Instance {
|
||||
|
||||
@Override
|
||||
public String[] getSupportedFormats() {
|
||||
return new String[]{"image/jpeg", "image/png", "image/gif", "image/webp", "video/mp4", "video/mov", "video/3gp", "video/webm"};
|
||||
return new String[]{"image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp", "video/mp4", "video/mov", "video/3gp", "video/webm"};
|
||||
}
|
||||
|
||||
|
||||
|
@ -8,8 +8,13 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import org.nuclearfog.twidda.model.Instance;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* This class is used to upload a message
|
||||
@ -23,6 +28,11 @@ public class MessageUpdate {
|
||||
private String name = "";
|
||||
private String text = "";
|
||||
|
||||
@Nullable
|
||||
private Instance instance;
|
||||
|
||||
private Set<String> supportedFormats = new TreeSet<>();
|
||||
|
||||
/**
|
||||
* @param name screen name of the user
|
||||
*/
|
||||
@ -82,14 +92,19 @@ public class MessageUpdate {
|
||||
* @return true if file is valid
|
||||
*/
|
||||
public boolean addMedia(Context context, @NonNull Uri uri) {
|
||||
// check if file is valid
|
||||
DocumentFile file = DocumentFile.fromSingleUri(context, uri);
|
||||
if (file != null && file.length() > 0) {
|
||||
String mime = context.getContentResolver().getType(uri);
|
||||
// check if file is valid
|
||||
if (file == null || file.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
// check if file format is supported
|
||||
if (mime == null || !supportedFormats.contains(mime)) {
|
||||
return false;
|
||||
}
|
||||
this.uri = uri;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* initialize inputstream of the file to upload
|
||||
@ -97,8 +112,10 @@ public class MessageUpdate {
|
||||
* @return true if initialization succeded
|
||||
*/
|
||||
public boolean prepare(ContentResolver resolver) {
|
||||
if (uri == null)
|
||||
if (uri == null) {
|
||||
// no need to check media files if not attached
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
String mimeType = resolver.getType(uri);
|
||||
InputStream fileStream = resolver.openInputStream(uri);
|
||||
@ -112,6 +129,24 @@ public class MessageUpdate {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* set instance imformation such as status limitations
|
||||
*
|
||||
* @param instance instance imformation
|
||||
*/
|
||||
public void setInstanceInformation(Instance instance) {
|
||||
supportedFormats.addAll(Arrays.asList(instance.getSupportedFormats()));
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* get instance information
|
||||
*/
|
||||
@Nullable
|
||||
public Instance getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* close inputstream of media file
|
||||
*/
|
||||
|
@ -13,7 +13,10 @@ import org.nuclearfog.twidda.model.Instance;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* This class is used to upload status information
|
||||
@ -77,6 +80,7 @@ public class StatusUpdate {
|
||||
|
||||
private int attachment = EMPTY;
|
||||
private List<Uri> mediaUris = new ArrayList<>(5);
|
||||
private Set<String> supportedFormats = new TreeSet<>();
|
||||
private MediaStatus[] mediaUpdates = {};
|
||||
private boolean attachmentLimitReached = false;
|
||||
private boolean sensitive = false;
|
||||
@ -108,7 +112,7 @@ public class StatusUpdate {
|
||||
*/
|
||||
public int addMedia(Context context, Uri mediaUri) {
|
||||
String mime = context.getContentResolver().getType(mediaUri);
|
||||
if (mime == null || instance == null) {
|
||||
if (mime == null || instance == null || !supportedFormats.contains(mime)) {
|
||||
return MEDIA_ERROR;
|
||||
}
|
||||
// check if file is a 'gif' image
|
||||
@ -223,6 +227,7 @@ public class StatusUpdate {
|
||||
* @param instance instance imformation
|
||||
*/
|
||||
public void setInstanceInformation(Instance instance) {
|
||||
supportedFormats.addAll(Arrays.asList(instance.getSupportedFormats()));
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
@ -325,6 +330,14 @@ public class StatusUpdate {
|
||||
return attachmentLimitReached;
|
||||
}
|
||||
|
||||
/**
|
||||
* get instance information
|
||||
*/
|
||||
@Nullable
|
||||
public Instance getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if media information is attached
|
||||
*
|
||||
|
@ -22,11 +22,13 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import org.nuclearfog.twidda.R;
|
||||
import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback;
|
||||
import org.nuclearfog.twidda.backend.async.InstanceLoader;
|
||||
import org.nuclearfog.twidda.backend.async.MessageUpdater;
|
||||
import org.nuclearfog.twidda.backend.async.MessageUpdater.MessageUpdateResult;
|
||||
import org.nuclearfog.twidda.backend.helper.MessageUpdate;
|
||||
import org.nuclearfog.twidda.backend.utils.AppStyles;
|
||||
import org.nuclearfog.twidda.backend.utils.ErrorHandler;
|
||||
import org.nuclearfog.twidda.model.Instance;
|
||||
import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog;
|
||||
import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog.OnConfirmListener;
|
||||
import org.nuclearfog.twidda.ui.dialogs.ProgressDialog;
|
||||
@ -37,7 +39,7 @@ import org.nuclearfog.twidda.ui.dialogs.ProgressDialog.OnProgressStopListener;
|
||||
*
|
||||
* @author nuclearfog
|
||||
*/
|
||||
public class MessageEditor extends MediaActivity implements OnClickListener, OnConfirmListener, OnProgressStopListener, AsyncCallback<MessageUpdateResult> {
|
||||
public class MessageEditor extends MediaActivity implements OnClickListener, OnConfirmListener, OnProgressStopListener {
|
||||
|
||||
/**
|
||||
* key for the screenname if any
|
||||
@ -45,7 +47,11 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
|
||||
*/
|
||||
public static final String KEY_DM_PREFIX = "dm_prefix";
|
||||
|
||||
private MessageUpdater messageAsync;
|
||||
private AsyncCallback<Instance> instanceResult = this::onInstanceResult;
|
||||
private AsyncCallback<MessageUpdateResult> messageResult = this::onMessageResult;
|
||||
|
||||
private InstanceLoader instanceLoader;
|
||||
private MessageUpdater messageUpdater;
|
||||
|
||||
private ProgressDialog loadingCircle;
|
||||
private ConfirmDialog confirmDialog;
|
||||
@ -53,7 +59,7 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
|
||||
private EditText receiver, message;
|
||||
private ImageButton media, preview;
|
||||
|
||||
private MessageUpdate holder = new MessageUpdate();
|
||||
private MessageUpdate messageUpdate = new MessageUpdate();
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context newBase) {
|
||||
@ -74,7 +80,8 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
|
||||
message = findViewById(R.id.popup_message_text);
|
||||
AppStyles.setEditorTheme(root, background);
|
||||
|
||||
messageAsync = new MessageUpdater(this);
|
||||
messageUpdater = new MessageUpdater(this);
|
||||
instanceLoader = new InstanceLoader(this);
|
||||
loadingCircle = new ProgressDialog(this);
|
||||
confirmDialog = new ConfirmDialog(this);
|
||||
|
||||
@ -87,12 +94,23 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
|
||||
preview.setOnClickListener(this);
|
||||
loadingCircle.addOnProgressStopListener(this);
|
||||
confirmDialog.setConfirmListener(this);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
if (messageUpdate.getInstance() == null) {
|
||||
instanceLoader.execute(null, instanceResult);
|
||||
}
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (receiver.getText().length() == 0 && message.getText().length() == 0 && holder.getMediaUri() == null) {
|
||||
if (receiver.getText().length() == 0 && message.getText().length() == 0 && messageUpdate.getMediaUri() == null) {
|
||||
loadingCircle.dismiss();
|
||||
super.onBackPressed();
|
||||
} else {
|
||||
@ -103,9 +121,9 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
messageAsync.cancel();
|
||||
if (holder != null)
|
||||
holder.close();
|
||||
messageUpdater.cancel();
|
||||
if (messageUpdate != null)
|
||||
messageUpdate.close();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@ -118,7 +136,7 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
|
||||
@Override
|
||||
protected void onMediaFetched(int resultType, @NonNull Uri uri) {
|
||||
if (resultType == REQUEST_IMAGE) {
|
||||
if (holder.addMedia(this, uri)) {
|
||||
if (messageUpdate.addMedia(this, uri)) {
|
||||
preview.setVisibility(VISIBLE);
|
||||
media.setVisibility(GONE);
|
||||
} else {
|
||||
@ -132,7 +150,7 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
|
||||
public void onClick(View v) {
|
||||
// send direct message
|
||||
if (v.getId() == R.id.popup_message_send) {
|
||||
if (messageAsync.isIdle()) {
|
||||
if (messageUpdater.isIdle()) {
|
||||
sendMessage();
|
||||
}
|
||||
}
|
||||
@ -142,9 +160,9 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
|
||||
}
|
||||
// open media
|
||||
else if (v.getId() == R.id.popup_message_preview) {
|
||||
if (holder.getMediaUri() != null) {
|
||||
if (messageUpdate.getMediaUri() != null) {
|
||||
Intent image = new Intent(this, ImageViewer.class);
|
||||
image.putExtra(ImageViewer.IMAGE_URI, holder.getMediaUri());
|
||||
image.putExtra(ImageViewer.IMAGE_URI, messageUpdate.getMediaUri());
|
||||
startActivity(image);
|
||||
}
|
||||
}
|
||||
@ -153,7 +171,7 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
|
||||
|
||||
@Override
|
||||
public void stopProgress() {
|
||||
messageAsync.cancel();
|
||||
messageUpdater.cancel();
|
||||
}
|
||||
|
||||
|
||||
@ -169,9 +187,30 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check inputs and send message
|
||||
*/
|
||||
private void sendMessage() {
|
||||
String username = receiver.getText().toString();
|
||||
String message = this.message.getText().toString();
|
||||
if (!username.trim().isEmpty() && (!message.trim().isEmpty() || messageUpdate.getMediaUri() != null)) {
|
||||
if (messageUpdate.prepare(getContentResolver())) {
|
||||
messageUpdate.setReceiver(username);
|
||||
messageUpdate.setText(message);
|
||||
messageUpdater.execute(messageUpdate, messageResult);
|
||||
loadingCircle.show();
|
||||
} else {
|
||||
Toast.makeText(getApplicationContext(), R.string.error_media_init, LENGTH_SHORT).show();
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(getApplicationContext(), R.string.error_dm, LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResult(@NonNull MessageUpdateResult result) {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private void onMessageResult(@NonNull MessageUpdateResult result) {
|
||||
if (result.success) {
|
||||
Toast.makeText(getApplicationContext(), R.string.info_dm_send, Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
@ -183,22 +222,9 @@ public class MessageEditor extends MediaActivity implements OnClickListener, OnC
|
||||
}
|
||||
|
||||
/**
|
||||
* check inputs and send message
|
||||
*
|
||||
*/
|
||||
private void sendMessage() {
|
||||
String username = receiver.getText().toString();
|
||||
String message = this.message.getText().toString();
|
||||
if (!username.trim().isEmpty() && (!message.trim().isEmpty() || holder.getMediaUri() != null)) {
|
||||
if (holder.prepare(getContentResolver())) {
|
||||
holder.setReceiver(username);
|
||||
holder.setText(message);
|
||||
messageAsync.execute(holder, this);
|
||||
loadingCircle.show();
|
||||
} else {
|
||||
Toast.makeText(getApplicationContext(), R.string.error_media_init, LENGTH_SHORT).show();
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(getApplicationContext(), R.string.error_dm, LENGTH_SHORT).show();
|
||||
}
|
||||
private void onInstanceResult(Instance instance) {
|
||||
messageUpdate.setInstanceInformation(instance);
|
||||
}
|
||||
}
|
@ -127,8 +127,6 @@ public class StatusEditor extends MediaActivity implements OnClickListener, OnPr
|
||||
iconList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, true));
|
||||
iconList.setAdapter(adapter);
|
||||
|
||||
instanceLoader.execute(null, instanceResult);
|
||||
|
||||
statusText.addTextChangedListener(this);
|
||||
closeButton.setOnClickListener(this);
|
||||
preference.setOnClickListener(this);
|
||||
@ -139,12 +137,17 @@ public class StatusEditor extends MediaActivity implements OnClickListener, OnPr
|
||||
confirmDialog.setConfirmListener(this);
|
||||
loadingCircle.addOnProgressStopListener(this);
|
||||
adapter.addOnMediaClickListener(this);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (statusUpdate.getInstance() == null) {
|
||||
instanceLoader.execute(null, instanceResult);
|
||||
}
|
||||
if (settings.getLogin().getConfiguration().locationSupported()) {
|
||||
if (isLocating()) {
|
||||
locationPending.setVisibility(View.VISIBLE);
|
||||
|
@ -3,11 +3,13 @@ package org.nuclearfog.twidda.ui.activities;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@ -23,11 +25,11 @@ import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||
import com.google.android.exoplayer2.ui.StyledPlayerView;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSource;
|
||||
|
||||
import org.nuclearfog.twidda.R;
|
||||
import org.nuclearfog.twidda.backend.utils.AppStyles;
|
||||
import org.nuclearfog.twidda.backend.utils.ConnectionBuilder;
|
||||
import org.nuclearfog.twidda.config.GlobalSettings;
|
||||
|
||||
import okhttp3.Call;
|
||||
|
||||
@ -50,8 +52,8 @@ public class VideoViewer extends AppCompatActivity {
|
||||
*/
|
||||
public static final String ENABLE_VIDEO_CONTROLS = "enable_controls";
|
||||
|
||||
private GlobalSettings settings;
|
||||
private ExoPlayer player;
|
||||
private Toolbar toolbar;
|
||||
|
||||
private Uri data;
|
||||
|
||||
@ -65,19 +67,13 @@ public class VideoViewer extends AppCompatActivity {
|
||||
protected void onCreate(@Nullable Bundle b) {
|
||||
super.onCreate(b);
|
||||
setContentView(R.layout.page_video);
|
||||
|
||||
ViewGroup root = findViewById(R.id.page_video_root);
|
||||
StyledPlayerView playerView = findViewById(R.id.page_video_player);
|
||||
Toolbar toolbar = findViewById(R.id.page_video_toolbar);
|
||||
toolbar = findViewById(R.id.page_video_toolbar);
|
||||
|
||||
player = new ExoPlayer.Builder(this).build();
|
||||
settings = GlobalSettings.getInstance(this);
|
||||
|
||||
AppStyles.setTheme(root);
|
||||
if (toolbar != null) {
|
||||
setSupportActionBar(toolbar);
|
||||
toolbar.setTitle("");
|
||||
}
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
data = getIntent().getParcelableExtra(VIDEO_URI);
|
||||
boolean enableControls = getIntent().getBooleanExtra(ENABLE_VIDEO_CONTROLS, true);
|
||||
@ -86,15 +82,20 @@ public class VideoViewer extends AppCompatActivity {
|
||||
player.setRepeatMode(Player.REPEAT_MODE_ONE);
|
||||
}
|
||||
|
||||
DataSource.Factory dataSourceFactory;
|
||||
MediaItem mediaItem = MediaItem.fromUri(data);
|
||||
DataSource.Factory dataSourceFactory = new OkHttpDataSource.Factory((Call.Factory) ConnectionBuilder.create(this, 128000));
|
||||
if (data.getScheme().startsWith("http")) {
|
||||
dataSourceFactory = new OkHttpDataSource.Factory((Call.Factory) ConnectionBuilder.create(this, 128000));
|
||||
} else {
|
||||
dataSourceFactory = new DefaultDataSource.Factory(this);
|
||||
toolbar.setVisibility(View.GONE);
|
||||
}
|
||||
MediaSource mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem);
|
||||
|
||||
player.setMediaSource(mediaSource);
|
||||
playerView.setPlayer(player);
|
||||
|
||||
player.prepare();
|
||||
player.play();
|
||||
player.setPlayWhenReady(true);
|
||||
}
|
||||
|
||||
|
||||
@ -105,11 +106,21 @@ public class VideoViewer extends AppCompatActivity {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
toolbar.setVisibility(View.GONE);
|
||||
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
toolbar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.video, menu);
|
||||
AppStyles.setMenuIconColor(menu, settings.getIconColor());
|
||||
menu.findItem(R.id.menu_video_link).setVisible(data.getScheme().startsWith("http"));
|
||||
AppStyles.setMenuIconColor(menu, Color.WHITE);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
|
@ -45,26 +45,30 @@
|
||||
<Button
|
||||
android:id="@+id/confirm_no"
|
||||
style="@style/FeedbackButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/confirm_button_height"
|
||||
android:layout_margin="@dimen/confirm_button_margin"
|
||||
android:padding="@dimen/confirm_button_padding"
|
||||
android:lines="1"
|
||||
android:text="@android:string/cancel"
|
||||
android:textSize="@dimen/confirm_button_fontsize"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/confirm_yes" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/confirm_yes"
|
||||
style="@style/FeedbackButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/confirm_button_height"
|
||||
android:layout_margin="@dimen/confirm_button_margin"
|
||||
android:padding="@dimen/confirm_button_padding"
|
||||
android:lines="1"
|
||||
android:text="@android:string/ok"
|
||||
android:textSize="@dimen/confirm_button_fontsize"
|
||||
app:layout_constraintStart_toEndOf="@id/confirm_no"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
|
@ -6,5 +6,6 @@
|
||||
android:id="@+id/menu_video_link"
|
||||
android:icon="@drawable/share"
|
||||
android:title="@string/button_share"
|
||||
android:visible="true"
|
||||
app:showAsAction="always" />
|
||||
</menu>
|
@ -2,7 +2,7 @@
|
||||
<resources>
|
||||
|
||||
<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
|
||||
<item name="colorAccent">@android:color/white</item>
|
||||
<item name="android:colorAccent">@android:color/white</item>
|
||||
<item name="android:colorBackground">@color/background</item>
|
||||
<item name="android:navigationBarColor">@android:color/black</item>
|
||||
<item name="android:windowAnimationStyle">@style/TransactionPending</item>
|
||||
|
Loading…
x
Reference in New Issue
Block a user