[android] Added discovery placeholder when result is empty

This commit is contained in:
Александр Зацепин 2017-12-06 16:52:00 +03:00 committed by Ilya Grechuhin
parent 82eca38744
commit a16dbfc5e8
10 changed files with 218 additions and 127 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

View file

@ -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>

View file

@ -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)

View file

@ -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);
}
}

View file

@ -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()

View file

@ -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();
}