forked from organicmaps/organicmaps
[android] Added discovery placeholder when result is empty
This commit is contained in:
parent
82eca38744
commit
a16dbfc5e8
10 changed files with 218 additions and 127 deletions
BIN
android/res/drawable-hdpi/img_cactus.png
Normal file
BIN
android/res/drawable-hdpi/img_cactus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
BIN
android/res/drawable-mdpi/img_cactus.png
Normal file
BIN
android/res/drawable-mdpi/img_cactus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
BIN
android/res/drawable-xhdpi/img_cactus.png
Normal file
BIN
android/res/drawable-xhdpi/img_cactus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
BIN
android/res/drawable-xxhdpi/img_cactus.png
Normal file
BIN
android/res/drawable-xxhdpi/img_cactus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 136 KiB |
BIN
android/res/drawable-xxxhdpi/img_cactus.png
Normal file
BIN
android/res/drawable-xxxhdpi/img_cactus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 205 KiB |
|
@ -14,96 +14,111 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:theme="@style/MwmWidget.ToolbarTheme"/>
|
||||
<RelativeLayout
|
||||
android:id="@+id/thingsToDoLayout"
|
||||
<LinearLayout
|
||||
android:id="@+id/galleriesLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/height_block_base"
|
||||
android:paddingLeft="@dimen/margin_base"
|
||||
android:paddingRight="@dimen/margin_base">
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<RelativeLayout
|
||||
android:id="@+id/thingsToDoLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/height_block_base"
|
||||
android:paddingLeft="@dimen/margin_base"
|
||||
android:paddingRight="@dimen/margin_base">
|
||||
<TextView
|
||||
android:id="@+id/thingsToDoTitle"
|
||||
android:textAppearance="@style/MwmTextAppearance.Discovery.Subtitle"
|
||||
style="@style/MwmWidget.Discovery.Subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginRight="@dimen/margin_half"
|
||||
android:layout_marginEnd="@dimen/margin_half"
|
||||
android:layout_toLeftOf="@+id/viatorLogo"
|
||||
android:layout_toStartOf="@+id/viatorLogo"
|
||||
android:text="@string/discovery_button_subtitle_things_to_do"/>
|
||||
<ImageView
|
||||
android:id="@id/viatorLogo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_half"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:src="?viatorLogo"
|
||||
android:background="?clickableBackground"/>
|
||||
</RelativeLayout>
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/thingsToDo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/margin_base_plus"
|
||||
tools:listitem="@layout/item_viator_product"/>
|
||||
<TextView
|
||||
android:id="@+id/thingsToDoTitle"
|
||||
android:id="@+id/attractionsTitle"
|
||||
android:text="@string/discovery_button_subtitle_attractions"
|
||||
android:textAppearance="@style/MwmTextAppearance.Discovery.Subtitle"
|
||||
style="@style/MwmWidget.Discovery.Subtitle"
|
||||
android:layout_marginLeft="@dimen/margin_base"
|
||||
android:layout_marginStart="@dimen/margin_base"
|
||||
android:layout_marginRight="@dimen/margin_base"
|
||||
android:layout_marginEnd="@dimen/margin_base"
|
||||
android:layout_marginBottom="@dimen/margin_half_plus"
|
||||
android:layout_gravity="start"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/attractions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginRight="@dimen/margin_half"
|
||||
android:layout_marginEnd="@dimen/margin_half"
|
||||
android:layout_toLeftOf="@+id/viatorLogo"
|
||||
android:layout_toStartOf="@+id/viatorLogo"
|
||||
android:text="@string/discovery_button_subtitle_things_to_do"/>
|
||||
<ImageView
|
||||
android:id="@id/viatorLogo"
|
||||
android:layout_marginBottom="@dimen/margin_base_plus"/>
|
||||
<TextView
|
||||
android:id="@+id/eatAndDrinkTitle"
|
||||
android:text="@string/discovery_button_subtitle_eat_and_drink"
|
||||
android:textAppearance="@style/MwmTextAppearance.Discovery.Subtitle"
|
||||
style="@style/MwmWidget.Discovery.Subtitle"
|
||||
android:layout_marginLeft="@dimen/margin_base"
|
||||
android:layout_marginStart="@dimen/margin_base"
|
||||
android:layout_marginRight="@dimen/margin_base"
|
||||
android:layout_marginEnd="@dimen/margin_base"
|
||||
android:layout_marginBottom="@dimen/margin_half_plus"
|
||||
android:layout_gravity="start"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/food"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_half"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:src="?viatorLogo"
|
||||
android:background="?clickableBackground"/>
|
||||
</RelativeLayout>
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/thingsToDo"
|
||||
android:layout_marginBottom="@dimen/margin_base_plus"/>
|
||||
<TextView
|
||||
android:id="@+id/localGuidesTitle"
|
||||
android:text="@string/discovery_button_subtitle_local_guides"
|
||||
android:textAppearance="@style/MwmTextAppearance.Discovery.Subtitle"
|
||||
style="@style/MwmWidget.Discovery.Subtitle"
|
||||
android:layout_marginLeft="@dimen/margin_base"
|
||||
android:layout_marginStart="@dimen/margin_base"
|
||||
android:layout_marginRight="@dimen/margin_base"
|
||||
android:layout_marginEnd="@dimen/margin_base"
|
||||
android:layout_marginBottom="@dimen/margin_half_plus"
|
||||
android:layout_gravity="start"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/localGuides"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
||||
<com.mapswithme.maps.widget.PlaceholderView
|
||||
android:id="@+id/placeholder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/margin_base_plus"
|
||||
tools:listitem="@layout/item_viator_product"/>
|
||||
<TextView
|
||||
android:id="@+id/attractionsTitle"
|
||||
android:text="@string/discovery_button_subtitle_attractions"
|
||||
android:textAppearance="@style/MwmTextAppearance.Discovery.Subtitle"
|
||||
style="@style/MwmWidget.Discovery.Subtitle"
|
||||
android:layout_marginLeft="@dimen/margin_base"
|
||||
android:layout_marginStart="@dimen/margin_base"
|
||||
android:layout_marginRight="@dimen/margin_base"
|
||||
android:layout_marginEnd="@dimen/margin_base"
|
||||
android:layout_marginBottom="@dimen/margin_half_plus"
|
||||
android:layout_gravity="start"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/attractions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/margin_base_plus"/>
|
||||
<TextView
|
||||
android:id="@+id/eatAndDrinkTitle"
|
||||
android:text="@string/discovery_button_subtitle_eat_and_drink"
|
||||
android:textAppearance="@style/MwmTextAppearance.Discovery.Subtitle"
|
||||
style="@style/MwmWidget.Discovery.Subtitle"
|
||||
android:layout_marginLeft="@dimen/margin_base"
|
||||
android:layout_marginStart="@dimen/margin_base"
|
||||
android:layout_marginRight="@dimen/margin_base"
|
||||
android:layout_marginEnd="@dimen/margin_base"
|
||||
android:layout_marginBottom="@dimen/margin_half_plus"
|
||||
android:layout_gravity="start"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/food"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/margin_base_plus"/>
|
||||
<TextView
|
||||
android:id="@+id/localGuidesTitle"
|
||||
android:text="@string/discovery_button_subtitle_local_guides"
|
||||
android:textAppearance="@style/MwmTextAppearance.Discovery.Subtitle"
|
||||
style="@style/MwmWidget.Discovery.Subtitle"
|
||||
android:layout_marginLeft="@dimen/margin_base"
|
||||
android:layout_marginStart="@dimen/margin_base"
|
||||
android:layout_marginRight="@dimen/margin_base"
|
||||
android:layout_marginEnd="@dimen/margin_base"
|
||||
android:layout_marginBottom="@dimen/margin_half_plus"
|
||||
android:layout_gravity="start"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/localGuides"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/margin_double_and_half"
|
||||
android:paddingRight="@dimen/margin_double_and_half"
|
||||
android:paddingTop="@dimen/placeholder_margin_top"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
||||
</LinearLayout>
|
||||
</android.support.v4.widget.NestedScrollView>
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import com.mapswithme.maps.gallery.impl.BaseItemSelectedListener;
|
|||
import com.mapswithme.maps.gallery.impl.Factory;
|
||||
import com.mapswithme.maps.search.SearchResult;
|
||||
import com.mapswithme.maps.viator.ViatorProduct;
|
||||
import com.mapswithme.maps.widget.PlaceholderView;
|
||||
import com.mapswithme.maps.widget.recycler.ItemDecoratorFactory;
|
||||
import com.mapswithme.util.ConnectionState;
|
||||
import com.mapswithme.util.Language;
|
||||
|
@ -189,33 +190,27 @@ public class DiscoveryFragment extends BaseMwmToolbarFragment implements UICallb
|
|||
|
||||
@MainThread
|
||||
@Override
|
||||
public void onAttractionsReceived(@Nullable SearchResult[] results)
|
||||
public void onAttractionsReceived(@NonNull SearchResult[] results)
|
||||
{
|
||||
if (results == null)
|
||||
return;
|
||||
|
||||
updateViewsVisibility(results, R.id.attractionsTitle, R.id.attractions);
|
||||
ItemSelectedListener<Items.SearchItem> listener = new SearchBasedListener(getActivity());
|
||||
getGallery(R.id.attractions).setAdapter(Factory.createSearchBasedAdapter(results, listener));
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@Override
|
||||
public void onCafesReceived(@Nullable SearchResult[] results)
|
||||
public void onCafesReceived(@NonNull SearchResult[] results)
|
||||
{
|
||||
if (results == null)
|
||||
return;
|
||||
|
||||
updateViewsVisibility(results, R.id.eatAndDrinkTitle, R.id.food);
|
||||
ItemSelectedListener<Items.SearchItem> listener = new SearchBasedListener(getActivity());
|
||||
getGallery(R.id.food).setAdapter(Factory.createSearchBasedAdapter(results, listener));
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@Override
|
||||
public void onViatorProductsReceived(@Nullable ViatorProduct[] products)
|
||||
public void onViatorProductsReceived(@NonNull ViatorProduct[] products)
|
||||
{
|
||||
if (products == null)
|
||||
return;
|
||||
|
||||
updateViewsVisibility(products, R.id.thingsToDoLayout, R.id.thingsToDo);
|
||||
String url = DiscoveryManager.nativeGetViatorUrl();
|
||||
ItemSelectedListener<Items.ViatorItem> listener = new BaseItemSelectedListener<>(getActivity());
|
||||
getGallery(R.id.thingsToDo).setAdapter(Factory.createViatorAdapter(products, url, listener));
|
||||
|
@ -223,9 +218,11 @@ public class DiscoveryFragment extends BaseMwmToolbarFragment implements UICallb
|
|||
|
||||
@MainThread
|
||||
@Override
|
||||
public void onLocalExpertsReceived(@Nullable LocalExpert[] experts)
|
||||
public void onLocalExpertsReceived(@NonNull LocalExpert[] experts)
|
||||
{
|
||||
updateViewsVisibility(experts, R.id.localGuidesTitle, R.id.localGuides);
|
||||
|
||||
//TODO: coming soon.
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -251,6 +248,34 @@ public class DiscoveryFragment extends BaseMwmToolbarFragment implements UICallb
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotFound()
|
||||
{
|
||||
View view = getRootView();
|
||||
|
||||
UiUtils.hide(view, R.id.galleriesLayout);
|
||||
|
||||
PlaceholderView placeholder = (PlaceholderView) view.findViewById(R.id.placeholder);
|
||||
placeholder.setContent(R.drawable.img_cactus, R.string.discovery_button_404_error_title,
|
||||
R.string.discovery_button_404_error_message);
|
||||
UiUtils.show(placeholder);
|
||||
}
|
||||
|
||||
private <T> void updateViewsVisibility(@NonNull T[] results, @IdRes int... viewsId)
|
||||
{
|
||||
for (@IdRes int id : viewsId)
|
||||
UiUtils.showIf(results.length != 0, getRootView().findViewById(id));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public View getRootView()
|
||||
{
|
||||
View view = getView();
|
||||
if (view == null)
|
||||
throw new AssertionError("Don't call getRootView when view is not created yet!");
|
||||
return view;
|
||||
}
|
||||
|
||||
private static class ViatorOfflineSelectedListener extends BaseItemSelectedListener<Items.Item>
|
||||
{
|
||||
private ViatorOfflineSelectedListener(@NonNull Activity context)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.mapswithme.maps.discovery;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.support.annotation.MainThread;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
@ -9,61 +10,80 @@ import com.mapswithme.maps.viator.ViatorProduct;
|
|||
import com.mapswithme.util.log.Logger;
|
||||
import com.mapswithme.util.log.LoggerFactory;
|
||||
|
||||
enum DiscoveryManager
|
||||
import java.util.EnumSet;
|
||||
|
||||
enum DiscoveryManager
|
||||
{
|
||||
INSTANCE;
|
||||
private static final Logger LOGGER = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC);
|
||||
private static final String TAG = DiscoveryManager.class.getSimpleName();
|
||||
private static final EnumSet<ItemType> AGGREGATE_EMPTY_RESULTS = EnumSet.noneOf(ItemType.class);
|
||||
@Nullable
|
||||
private UICallback mCallback;
|
||||
private int mRequestedTypesCount;
|
||||
|
||||
|
||||
public void discover(@NonNull DiscoveryParams params)
|
||||
{
|
||||
LOGGER.d(TAG, "discover: " + params);
|
||||
AGGREGATE_EMPTY_RESULTS.clear();
|
||||
mRequestedTypesCount = params.getItemTypes().length;
|
||||
DiscoveryManager.nativeDiscover(params);
|
||||
}
|
||||
|
||||
// Called from JNI.
|
||||
@SuppressLint("SwitchIntDef")
|
||||
@MainThread
|
||||
private void onResultReceived(@Nullable SearchResult[] results, @DiscoveryParams.ItemType int type)
|
||||
private void onResultReceived(final @NonNull SearchResult[] results,
|
||||
final @DiscoveryParams.ItemType int type)
|
||||
{
|
||||
LOGGER.d(TAG, "onResultReceived for type: " + type);
|
||||
if (mCallback == null)
|
||||
return;
|
||||
|
||||
switch (type)
|
||||
notifyUiWithCheck(results, ItemType.values()[type], new Action()
|
||||
{
|
||||
case DiscoveryParams.ITEM_TYPE_ATTRACTIONS:
|
||||
mCallback.onAttractionsReceived(results);
|
||||
break;
|
||||
case DiscoveryParams.ITEM_TYPE_CAFES:
|
||||
mCallback.onCafesReceived(results);
|
||||
break;
|
||||
case DiscoveryParams.ITEM_TYPE_HOTELS:
|
||||
case DiscoveryParams.ITEM_TYPE_LOCAL_EXPERTS:
|
||||
case DiscoveryParams.ITEM_TYPE_VIATOR:
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unsupported discovery item type: " + type);
|
||||
}
|
||||
@Override
|
||||
public void run(@NonNull UICallback callback)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case DiscoveryParams.ITEM_TYPE_ATTRACTIONS:
|
||||
callback.onAttractionsReceived(results);
|
||||
break;
|
||||
case DiscoveryParams.ITEM_TYPE_CAFES:
|
||||
callback.onCafesReceived(results);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unsupported discovery item type " +
|
||||
"'" + type + "' for search results!");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Called from JNI.
|
||||
@MainThread
|
||||
private void onViatorProductsReceived(@Nullable ViatorProduct[] products)
|
||||
private void onViatorProductsReceived(@NonNull final ViatorProduct[] products)
|
||||
{
|
||||
LOGGER.d(TAG, "onViatorProductsReceived");
|
||||
if (mCallback != null)
|
||||
mCallback.onViatorProductsReceived(products);
|
||||
notifyUiWithCheck(products, ItemType.VIATOR, new Action()
|
||||
{
|
||||
@Override
|
||||
public void run(@NonNull UICallback callback)
|
||||
{
|
||||
callback.onViatorProductsReceived(products);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Called from JNI.
|
||||
@MainThread
|
||||
private void onLocalExpertsReceived(@Nullable LocalExpert[] experts)
|
||||
private void onLocalExpertsReceived(@NonNull final LocalExpert[] experts)
|
||||
{
|
||||
LOGGER.d(TAG, "onLocalExpertsReceived");
|
||||
if (mCallback != null)
|
||||
mCallback.onLocalExpertsReceived(experts);
|
||||
notifyUiWithCheck(experts, ItemType.LOCAL_EXPERTS, new Action()
|
||||
{
|
||||
@Override
|
||||
public void run(@NonNull UICallback callback)
|
||||
{
|
||||
callback.onLocalExpertsReceived(experts);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Called from JNI.
|
||||
|
@ -75,6 +95,30 @@ enum DiscoveryManager
|
|||
mCallback.onError(ItemType.values()[type]);
|
||||
}
|
||||
|
||||
private <T> void notifyUiWithCheck(@NonNull T[] results, @NonNull ItemType type,
|
||||
@NonNull Action action)
|
||||
{
|
||||
LOGGER.d(TAG, "Results size = " + results.length + " for type: " + type);
|
||||
if (mCallback == null)
|
||||
return;
|
||||
|
||||
if (isAggregateResultsEmpty(results, type))
|
||||
{
|
||||
mCallback.onNotFound();
|
||||
return;
|
||||
}
|
||||
|
||||
action.run(mCallback);
|
||||
}
|
||||
|
||||
private <T> boolean isAggregateResultsEmpty(@NonNull T[] results, @NonNull ItemType type)
|
||||
{
|
||||
if (results.length == 0)
|
||||
AGGREGATE_EMPTY_RESULTS.add(type);
|
||||
|
||||
return mRequestedTypesCount == AGGREGATE_EMPTY_RESULTS.size();
|
||||
}
|
||||
|
||||
void attach(@NonNull UICallback callback)
|
||||
{
|
||||
LOGGER.d(TAG, "attach callback: " + callback);
|
||||
|
@ -94,4 +138,9 @@ enum DiscoveryManager
|
|||
|
||||
@NonNull
|
||||
public static native String nativeGetLocalExpertsUrl();
|
||||
|
||||
interface Action
|
||||
{
|
||||
void run(@NonNull UICallback callback);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ public final class DiscoveryParams {
|
|||
public int getItemsCount() { return mItemsCount; }
|
||||
|
||||
@NonNull
|
||||
public int[] getItemTypes() { return mItemTypes; }
|
||||
int[] getItemTypes() { return mItemTypes; }
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
|
|
|
@ -10,13 +10,15 @@ import com.mapswithme.maps.viator.ViatorProduct;
|
|||
public interface UICallback
|
||||
{
|
||||
@MainThread
|
||||
void onAttractionsReceived(@Nullable SearchResult[] results);
|
||||
void onAttractionsReceived(@NonNull SearchResult[] results);
|
||||
@MainThread
|
||||
void onCafesReceived(@Nullable SearchResult[] results);
|
||||
void onCafesReceived(@NonNull SearchResult[] results);
|
||||
@MainThread
|
||||
void onViatorProductsReceived(@Nullable ViatorProduct[] products);
|
||||
void onViatorProductsReceived(@NonNull ViatorProduct[] products);
|
||||
@MainThread
|
||||
void onLocalExpertsReceived(@Nullable LocalExpert[] experts);
|
||||
void onLocalExpertsReceived(@NonNull LocalExpert[] experts);
|
||||
@MainThread
|
||||
void onError(@NonNull ItemType type);
|
||||
@MainThread
|
||||
void onNotFound();
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue