Use Context instead of Activity
Improve docs
This commit is contained in:
parent
50e2385e82
commit
962fe9c36d
8 changed files with 138 additions and 40 deletions
|
@ -409,7 +409,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
new InfoItemDialog.Builder(activity, this, item).create().show();
|
new InfoItemDialog.Builder(activity, context, this, item).create().show();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -142,8 +142,8 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final InfoItemDialog.Builder dialogBuilder = new InfoItemDialog.Builder(
|
final InfoItemDialog.Builder dialogBuilder =
|
||||||
activity, this, item);
|
new InfoItemDialog.Builder(activity, context, this, item);
|
||||||
|
|
||||||
dialogBuilder.setAction(StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND,
|
dialogBuilder.setAction(StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND,
|
||||||
(fragment, infoItem) -> NavigationHelper.playOnBackgroundPlayer(
|
(fragment, infoItem) -> NavigationHelper.playOnBackgroundPlayer(
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
package org.schabi.newpipe.info_list;
|
package org.schabi.newpipe.info_list;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
@ -24,10 +23,18 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialog with actions for a {@link StreamInfoItem}.
|
* Dialog for a {@link StreamInfoItem}.
|
||||||
|
* The dialog'S content are actions that can be performed on the {@link StreamInfoItem}.
|
||||||
* This dialog is mostly used for longpress context menus.
|
* This dialog is mostly used for longpress context menus.
|
||||||
*/
|
*/
|
||||||
public final class InfoItemDialog {
|
public final class InfoItemDialog {
|
||||||
|
/**
|
||||||
|
* Ideally, {@link InfoItemDialog} would extend {@link AlertDialog}.
|
||||||
|
* However, extending {@link AlertDialog} requires many additional lines
|
||||||
|
* and brings more complexity to this class, especially the constructor.
|
||||||
|
* To circumvent this, an {@link AlertDialog.Builder} is used in the constructor.
|
||||||
|
* Its result is stored in this class variable to allow access via the {@link #show()} method.
|
||||||
|
*/
|
||||||
private final AlertDialog dialog;
|
private final AlertDialog dialog;
|
||||||
|
|
||||||
private InfoItemDialog(@NonNull final Activity activity,
|
private InfoItemDialog(@NonNull final Activity activity,
|
||||||
|
@ -76,38 +83,100 @@ public final class InfoItemDialog {
|
||||||
*/
|
*/
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
@NonNull private final Activity activity;
|
@NonNull private final Activity activity;
|
||||||
|
@NonNull private final Context context;
|
||||||
@NonNull private final StreamInfoItem infoItem;
|
@NonNull private final StreamInfoItem infoItem;
|
||||||
@NonNull private final Fragment fragment;
|
@NonNull private final Fragment fragment;
|
||||||
@NonNull private final List<StreamDialogEntry> entries = new ArrayList<>();
|
@NonNull private final List<StreamDialogEntry> entries = new ArrayList<>();
|
||||||
private final boolean addDefaultEntriesAutomatically;
|
private final boolean addDefaultEntriesAutomatically;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Create a Builder instance that automatically adds the some default entries
|
||||||
|
* at the top and bottom of the dialog.</p>
|
||||||
|
* The dialog has the following structure:
|
||||||
|
* <pre>
|
||||||
|
* + - - - - - - - - - - - - - - - - - - - - - -+
|
||||||
|
* | ENQUEUE |
|
||||||
|
* | ENQUEUE_HERE |
|
||||||
|
* | START_ON_BACKGROUND |
|
||||||
|
* | START_ON_POPUP |
|
||||||
|
* + - - - - - - - - - - - - - - - - - - - - - -+
|
||||||
|
* | entries added manually with |
|
||||||
|
* | addEntry() and addAllEntries() |
|
||||||
|
* + - - - - - - - - - - - - - - - - - - - - - -+
|
||||||
|
* | APPEND_PLAYLIST |
|
||||||
|
* | SHARE |
|
||||||
|
* | OPEN_IN_BROWSER |
|
||||||
|
* | PLAY_WITH_KODI |
|
||||||
|
* | MARK_AS_WATCHED |
|
||||||
|
* | SHOW_CHANNEL_DETAILS |
|
||||||
|
* + - - - - - - - - - - - - - - - - - - - - - -+
|
||||||
|
* </pre>
|
||||||
|
* Please note that some entries are not added depending on the user's preferences,
|
||||||
|
* the item's {@link StreamType} and the current player state.
|
||||||
|
*
|
||||||
|
* @param activity
|
||||||
|
* @param context
|
||||||
|
* @param fragment
|
||||||
|
* @param infoItem the item for this dialog; all entries and their actions work with
|
||||||
|
* this {@link org.schabi.newpipe.extractor.InfoItem}
|
||||||
|
*/
|
||||||
public Builder(@NonNull final Activity activity,
|
public Builder(@NonNull final Activity activity,
|
||||||
|
@NonNull final Context context,
|
||||||
@NonNull final Fragment fragment,
|
@NonNull final Fragment fragment,
|
||||||
@NonNull final StreamInfoItem infoItem) {
|
@NonNull final StreamInfoItem infoItem) {
|
||||||
this(activity, fragment, infoItem, true);
|
this(activity, context, fragment, infoItem, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Create an instance of this Builder</p>
|
* <p>Create an instance of this Builder.</p>
|
||||||
|
* <p>If {@code addDefaultEntriesAutomatically} is set to {@code true},
|
||||||
|
* some default entries are added to the top and bottom of the dialog.</p>
|
||||||
|
* The dialog has the following structure:
|
||||||
|
* <pre>
|
||||||
|
* + - - - - - - - - - - - - - - - - - - - - - -+
|
||||||
|
* | ENQUEUE |
|
||||||
|
* | ENQUEUE_HERE |
|
||||||
|
* | START_ON_BACKGROUND |
|
||||||
|
* | START_ON_POPUP |
|
||||||
|
* + - - - - - - - - - - - - - - - - - - - - - -+
|
||||||
|
* | entries added manually with |
|
||||||
|
* | addEntry() and addAllEntries() |
|
||||||
|
* + - - - - - - - - - - - - - - - - - - - - - -+
|
||||||
|
* | APPEND_PLAYLIST |
|
||||||
|
* | SHARE |
|
||||||
|
* | OPEN_IN_BROWSER |
|
||||||
|
* | PLAY_WITH_KODI |
|
||||||
|
* | MARK_AS_WATCHED |
|
||||||
|
* | SHOW_CHANNEL_DETAILS |
|
||||||
|
* + - - - - - - - - - - - - - - - - - - - - - -+
|
||||||
|
* </pre>
|
||||||
|
* Please note that some entries are not added depending on the user's preferences,
|
||||||
|
* the item's {@link StreamType} and the current player state.
|
||||||
|
*
|
||||||
* @param activity
|
* @param activity
|
||||||
|
* @param context
|
||||||
* @param fragment
|
* @param fragment
|
||||||
* @param infoItem
|
* @param infoItem
|
||||||
* @param addDefaultEntriesAutomatically whether default entries added with
|
* @param addDefaultEntriesAutomatically
|
||||||
* {@link #addDefaultEntriesAtBeginning()} and
|
* whether default entries added with {@link #addDefaultBeginningEntries()}
|
||||||
* {@link #addDefaultEntriesAtEnd()}
|
* and {@link #addDefaultEndEntries()} are added automatically when generating
|
||||||
* are added automatically when generating
|
* the {@link InfoItemDialog}.
|
||||||
* the {@link InfoItemDialog}.
|
* <br/>
|
||||||
|
* Entries added with {@link #addEntry(StreamDialogDefaultEntry)} and
|
||||||
|
* {@link #addAllEntries(StreamDialogDefaultEntry...)} are added in between.
|
||||||
*/
|
*/
|
||||||
public Builder(@NonNull final Activity activity,
|
public Builder(@NonNull final Activity activity,
|
||||||
|
@NonNull final Context context,
|
||||||
@NonNull final Fragment fragment,
|
@NonNull final Fragment fragment,
|
||||||
@NonNull final StreamInfoItem infoItem,
|
@NonNull final StreamInfoItem infoItem,
|
||||||
final boolean addDefaultEntriesAutomatically) {
|
final boolean addDefaultEntriesAutomatically) {
|
||||||
this.activity = activity;
|
this.activity = activity;
|
||||||
|
this.context = context;
|
||||||
this.fragment = fragment;
|
this.fragment = fragment;
|
||||||
this.infoItem = infoItem;
|
this.infoItem = infoItem;
|
||||||
this.addDefaultEntriesAutomatically = addDefaultEntriesAutomatically;
|
this.addDefaultEntriesAutomatically = addDefaultEntriesAutomatically;
|
||||||
if (addDefaultEntriesAutomatically) {
|
if (addDefaultEntriesAutomatically) {
|
||||||
addDefaultEntriesAtBeginning();
|
addDefaultBeginningEntries();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,12 +208,11 @@ public final class InfoItemDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addChannelDetailsEntryIfPossible() {
|
/**
|
||||||
if (!isNullOrEmpty(infoItem.getUploaderUrl())) {
|
* Adds {@link StreamDialogDefaultEntry#ENQUEUE} if the player is open and
|
||||||
addEntry(StreamDialogDefaultEntry.SHOW_CHANNEL_DETAILS);
|
* {@link StreamDialogDefaultEntry#ENQUEUE_NEXT} if there are multiple streams
|
||||||
}
|
* in the play queue.
|
||||||
}
|
*/
|
||||||
|
|
||||||
public void addEnqueueEntriesIfNeeded() {
|
public void addEnqueueEntriesIfNeeded() {
|
||||||
if (PlayerHolder.getInstance().isPlayerOpen()) {
|
if (PlayerHolder.getInstance().isPlayerOpen()) {
|
||||||
addEntry(StreamDialogDefaultEntry.ENQUEUE);
|
addEntry(StreamDialogDefaultEntry.ENQUEUE);
|
||||||
|
@ -155,6 +223,11 @@ public final class InfoItemDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the {@link StreamDialogDefaultEntry#START_HERE_ON_BACKGROUND}.
|
||||||
|
* If the {@link #infoItem} is not a pure audio (live) stream,
|
||||||
|
* {@link StreamDialogDefaultEntry#START_HERE_ON_POPUP} is added, too.
|
||||||
|
*/
|
||||||
public void addStartHereEntries() {
|
public void addStartHereEntries() {
|
||||||
addEntry(StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND);
|
addEntry(StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND);
|
||||||
if (infoItem.getStreamType() != StreamType.AUDIO_STREAM
|
if (infoItem.getStreamType() != StreamType.AUDIO_STREAM
|
||||||
|
@ -169,8 +242,8 @@ public final class InfoItemDialog {
|
||||||
*/
|
*/
|
||||||
public void addMarkAsWatchedEntryIfNeeded() {
|
public void addMarkAsWatchedEntryIfNeeded() {
|
||||||
final boolean isWatchHistoryEnabled = PreferenceManager
|
final boolean isWatchHistoryEnabled = PreferenceManager
|
||||||
.getDefaultSharedPreferences(activity)
|
.getDefaultSharedPreferences(context)
|
||||||
.getBoolean(activity.getString(R.string.enable_watch_history_key), false);
|
.getBoolean(context.getString(R.string.enable_watch_history_key), false);
|
||||||
if (isWatchHistoryEnabled
|
if (isWatchHistoryEnabled
|
||||||
&& infoItem.getStreamType() != StreamType.LIVE_STREAM
|
&& infoItem.getStreamType() != StreamType.LIVE_STREAM
|
||||||
&& infoItem.getStreamType() != StreamType.AUDIO_LIVE_STREAM) {
|
&& infoItem.getStreamType() != StreamType.AUDIO_LIVE_STREAM) {
|
||||||
|
@ -179,17 +252,26 @@ public final class InfoItemDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPlayWithKodiEntryIfNeeded() {
|
public void addPlayWithKodiEntryIfNeeded() {
|
||||||
if (KoreUtils.shouldShowPlayWithKodi(activity, infoItem.getServiceId())) {
|
if (KoreUtils.shouldShowPlayWithKodi(context, infoItem.getServiceId())) {
|
||||||
addEntry(StreamDialogDefaultEntry.PLAY_WITH_KODI);
|
addEntry(StreamDialogDefaultEntry.PLAY_WITH_KODI);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addDefaultEntriesAtBeginning() {
|
/**
|
||||||
|
* Add the entries which are usually at the top of the action list.
|
||||||
|
* <br/>
|
||||||
|
* This method adds the "enqueue" (see {@link #addEnqueueEntriesIfNeeded()})
|
||||||
|
* and "start here" (see {@link #addStartHereEntries()} entries.
|
||||||
|
*/
|
||||||
|
public void addDefaultBeginningEntries() {
|
||||||
addEnqueueEntriesIfNeeded();
|
addEnqueueEntriesIfNeeded();
|
||||||
addStartHereEntries();
|
addStartHereEntries();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addDefaultEntriesAtEnd() {
|
/**
|
||||||
|
* Add the entries which are usually at the bottom of the action list.
|
||||||
|
*/
|
||||||
|
public void addDefaultEndEntries() {
|
||||||
addAllEntries(
|
addAllEntries(
|
||||||
StreamDialogDefaultEntry.APPEND_PLAYLIST,
|
StreamDialogDefaultEntry.APPEND_PLAYLIST,
|
||||||
StreamDialogDefaultEntry.SHARE,
|
StreamDialogDefaultEntry.SHARE,
|
||||||
|
@ -197,7 +279,7 @@ public final class InfoItemDialog {
|
||||||
);
|
);
|
||||||
addPlayWithKodiEntryIfNeeded();
|
addPlayWithKodiEntryIfNeeded();
|
||||||
addMarkAsWatchedEntryIfNeeded();
|
addMarkAsWatchedEntryIfNeeded();
|
||||||
addChannelDetailsEntryIfPossible();
|
addEntry(StreamDialogDefaultEntry.SHOW_CHANNEL_DETAILS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -206,7 +288,7 @@ public final class InfoItemDialog {
|
||||||
*/
|
*/
|
||||||
public InfoItemDialog create() {
|
public InfoItemDialog create() {
|
||||||
if (addDefaultEntriesAutomatically) {
|
if (addDefaultEntriesAutomatically) {
|
||||||
addDefaultEntriesAtEnd();
|
addDefaultEndEntries();
|
||||||
}
|
}
|
||||||
return new InfoItemDialog(this.activity, this.fragment, this.infoItem, this.entries);
|
return new InfoItemDialog(this.activity, this.fragment, this.infoItem, this.entries);
|
||||||
}
|
}
|
||||||
|
|
|
@ -357,7 +357,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
||||||
val activity: Activity? = getActivity()
|
val activity: Activity? = getActivity()
|
||||||
if (context == null || context.resources == null || activity == null) return
|
if (context == null || context.resources == null || activity == null) return
|
||||||
|
|
||||||
InfoItemDialog.Builder(activity, this, item).create().show()
|
InfoItemDialog.Builder(activity, context, this, item).create().show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val listenerStreamItem = object : OnItemClickListener, OnItemLongClickListener {
|
private val listenerStreamItem = object : OnItemClickListener, OnItemLongClickListener {
|
||||||
|
|
|
@ -332,8 +332,8 @@ public class StatisticsPlaylistFragment
|
||||||
}
|
}
|
||||||
final StreamInfoItem infoItem = item.toStreamInfoItem();
|
final StreamInfoItem infoItem = item.toStreamInfoItem();
|
||||||
|
|
||||||
final InfoItemDialog.Builder dialogBuilder = new InfoItemDialog.Builder(
|
final InfoItemDialog.Builder dialogBuilder =
|
||||||
activity, this, infoItem);
|
new InfoItemDialog.Builder(activity, context, this, infoItem);
|
||||||
|
|
||||||
// set entries in the middle; the others are added automatically
|
// set entries in the middle; the others are added automatically
|
||||||
dialogBuilder.addEntry(StreamDialogDefaultEntry.DELETE);
|
dialogBuilder.addEntry(StreamDialogDefaultEntry.DELETE);
|
||||||
|
|
|
@ -747,8 +747,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
}
|
}
|
||||||
final StreamInfoItem infoItem = item.toStreamInfoItem();
|
final StreamInfoItem infoItem = item.toStreamInfoItem();
|
||||||
|
|
||||||
final InfoItemDialog.Builder dialogBuilder = new InfoItemDialog.Builder(
|
final InfoItemDialog.Builder dialogBuilder =
|
||||||
activity, this, infoItem);
|
new InfoItemDialog.Builder(activity, context, this, infoItem);
|
||||||
|
|
||||||
// add entries in the middle
|
// add entries in the middle
|
||||||
dialogBuilder.addAllEntries(
|
dialogBuilder.addAllEntries(
|
||||||
|
|
|
@ -12,6 +12,9 @@ import androidx.fragment.app.Fragment;
|
||||||
import org.schabi.newpipe.NewPipeDatabase;
|
import org.schabi.newpipe.NewPipeDatabase;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.ErrorUtil;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
||||||
import org.schabi.newpipe.local.dialog.PlaylistDialog;
|
import org.schabi.newpipe.local.dialog.PlaylistDialog;
|
||||||
|
@ -25,11 +28,23 @@ import java.util.Collections;
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* This enum provides entries that are accepted
|
||||||
|
* by the {@link org.schabi.newpipe.info_list.InfoItemDialog.Builder}.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* These entries contain a String {@link #resource} which is displayed in the dialog and
|
||||||
|
* a default {@link #action} that is executed
|
||||||
|
* when the entry is selected (via <code>onClick()</code>).
|
||||||
|
* <br/>
|
||||||
|
* They action can be overridden by using the Builder's
|
||||||
|
* {@link org.schabi.newpipe.info_list.InfoItemDialog.Builder#setAction(
|
||||||
|
* StreamDialogDefaultEntry, StreamDialogEntry.StreamDialogEntryAction)}
|
||||||
|
* method.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
public enum StreamDialogDefaultEntry {
|
public enum StreamDialogDefaultEntry {
|
||||||
//////////////////////////////////////
|
|
||||||
// enum values with DEFAULT actions //
|
|
||||||
//////////////////////////////////////
|
|
||||||
|
|
||||||
SHOW_CHANNEL_DETAILS(R.string.show_channel_details, (fragment, item) -> {
|
SHOW_CHANNEL_DETAILS(R.string.show_channel_details, (fragment, item) -> {
|
||||||
if (isNullOrEmpty(item.getUploaderUrl())) {
|
if (isNullOrEmpty(item.getUploaderUrl())) {
|
||||||
final int serviceId = item.getServiceId();
|
final int serviceId = item.getServiceId();
|
||||||
|
@ -125,6 +140,7 @@ public enum StreamDialogDefaultEntry {
|
||||||
public final int resource;
|
public final int resource;
|
||||||
@NonNull
|
@NonNull
|
||||||
public final StreamDialogEntry.StreamDialogEntryAction action;
|
public final StreamDialogEntry.StreamDialogEntryAction action;
|
||||||
|
|
||||||
StreamDialogDefaultEntry(@StringRes final int resource,
|
StreamDialogDefaultEntry(@StringRes final int resource,
|
||||||
@NonNull final StreamDialogEntry.StreamDialogEntryAction action) {
|
@NonNull final StreamDialogEntry.StreamDialogEntryAction action) {
|
||||||
this.resource = resource;
|
this.resource = resource;
|
||||||
|
@ -136,10 +152,6 @@ public enum StreamDialogDefaultEntry {
|
||||||
return new StreamDialogEntry(resource, action);
|
return new StreamDialogEntry(resource, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////
|
|
||||||
// private method to open channel fragment //
|
|
||||||
/////////////////////////////////////////////
|
|
||||||
|
|
||||||
private static void openChannelFragment(@NonNull final Fragment fragment,
|
private static void openChannelFragment(@NonNull final Fragment fragment,
|
||||||
@NonNull final StreamInfoItem item,
|
@NonNull final StreamInfoItem item,
|
||||||
final String uploaderUrl) {
|
final String uploaderUrl) {
|
||||||
|
|
|
@ -10,6 +10,10 @@ import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.ServiceList;
|
import org.schabi.newpipe.extractor.ServiceList;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Util class that provides methods which are related to the Kodi Media Center and its Kore app.
|
||||||
|
* @see <a href="https://kodi.tv/">Kodi website</a>
|
||||||
|
*/
|
||||||
public final class KoreUtils {
|
public final class KoreUtils {
|
||||||
private KoreUtils() { }
|
private KoreUtils() { }
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue