forked from organicmaps/organicmaps
[android] Added booking gallery on discovery screen
This commit is contained in:
parent
e27293a071
commit
0a71695220
21 changed files with 399 additions and 165 deletions
|
@ -20,39 +20,21 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/margin_base_plus"
|
||||
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>
|
||||
<TextView
|
||||
android:id="@+id/hotelsTitle"
|
||||
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_marginTop="@dimen/margin_half_plus"
|
||||
android:layout_marginBottom="@dimen/margin_half_plus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/discovery_button_subtitle_book_hotels"/>
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/thingsToDo"
|
||||
android:id="@+id/hotels"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/item_viator_product"/>
|
||||
|
@ -67,7 +49,6 @@
|
|||
android:layout_marginEnd="@dimen/margin_base"
|
||||
android:layout_marginBottom="@dimen/margin_half_plus"
|
||||
android:layout_marginTop="@dimen/margin_base_plus"
|
||||
android:layout_gravity="start"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
<android.support.v7.widget.RecyclerView
|
||||
|
@ -85,7 +66,6 @@
|
|||
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
|
||||
|
@ -103,7 +83,6 @@
|
|||
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
|
||||
|
|
84
android/res/layout/item_discovery_hotel_product.xml
Normal file
84
android/res/layout/item_discovery_hotel_product.xml
Normal file
|
@ -0,0 +1,84 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:padding="@dimen/margin_quarter"
|
||||
android:clipToPadding="false">
|
||||
<android.support.v7.widget.CardView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<LinearLayout
|
||||
android:layout_width="@dimen/viator_product_width"
|
||||
android:layout_height="match_parent"
|
||||
android:minHeight="@dimen/discovery_search_item_min_height"
|
||||
android:orientation="vertical">
|
||||
<LinearLayout
|
||||
android:id="@+id/infoLayout"
|
||||
android:paddingBottom="@dimen/margin_half_double_plus"
|
||||
android:foreground="?clickableBackground"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_half_double_plus"
|
||||
android:layout_marginLeft="@dimen/margin_half_plus"
|
||||
android:layout_marginRight="@dimen/margin_half_plus"
|
||||
style="@style/MwmWidget.Discovery.Item.Title"
|
||||
android:maxLines="1"
|
||||
android:minLines="1"
|
||||
android:textAppearance="@style/MwmTextAppearance.Discovery.Item.Title"
|
||||
tools:text="Vinnci Gala"/>
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_eighth"
|
||||
android:layout_marginLeft="@dimen/margin_half_plus"
|
||||
android:layout_marginRight="@dimen/margin_half_plus"
|
||||
style="@style/MwmWidget.Discovery.Item.Subtitle"
|
||||
android:ellipsize="middle"
|
||||
android:textAppearance="@style/MwmTextAppearance.Body3"
|
||||
tools:text="★ ★ ★ ★ ★"/>
|
||||
<com.mapswithme.maps.widget.RatingView
|
||||
android:id="@+id/ratingView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_eighth"
|
||||
android:layout_marginBottom="@dimen/margin_half"
|
||||
android:layout_height="@dimen/rating_view_height_small"
|
||||
android:layout_marginStart="@dimen/margin_half"
|
||||
android:layout_marginLeft="@dimen/margin_half"
|
||||
android:paddingLeft="@dimen/margin_quarter_plus"
|
||||
android:paddingStart="@dimen/margin_quarter_plus"
|
||||
android:paddingRight="@dimen/margin_quarter_plus"
|
||||
android:paddingEnd="@dimen/margin_quarter_plus"
|
||||
android:paddingTop="@dimen/margin_quarter"
|
||||
android:paddingBottom="@dimen/margin_quarter"
|
||||
android:layout_gravity="center_horizontal|start"
|
||||
android:textSize="@dimen/text_size_body_4"
|
||||
tools:visibility="visible"/>
|
||||
<TextView
|
||||
android:id="@+id/distance"
|
||||
android:layout_marginTop="@dimen/margin_eighth"
|
||||
android:layout_marginLeft="@dimen/margin_half_plus"
|
||||
android:layout_marginStart="@dimen/margin_half_plus"
|
||||
android:layout_marginRight="@dimen/margin_half_plus"
|
||||
android:layout_marginEnd="@dimen/margin_half_plus"
|
||||
android:textAppearance="@style/MwmTextAppearance.Discovery.Item.Distance"
|
||||
android:ellipsize="end"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="950 m"/>
|
||||
</LinearLayout>
|
||||
<include
|
||||
layout="@layout/item_discovery_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
||||
</android.support.v7.widget.CardView>
|
||||
</FrameLayout>
|
|
@ -9,17 +9,19 @@
|
|||
<android.support.v7.widget.CardView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<RelativeLayout
|
||||
<LinearLayout
|
||||
android:layout_width="@dimen/viator_product_width"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="match_parent"
|
||||
android:minHeight="@dimen/discovery_search_item_min_height"
|
||||
android:orientation="vertical">
|
||||
<LinearLayout
|
||||
android:id="@+id/infoLayout"
|
||||
android:layout_alignParentTop="true"
|
||||
android:paddingBottom="@dimen/margin_half_double_plus"
|
||||
android:foreground="?clickableBackground"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -56,8 +58,7 @@
|
|||
<include
|
||||
layout="@layout/item_discovery_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/infoLayout"/>
|
||||
</RelativeLayout>
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
||||
</android.support.v7.widget.CardView>
|
||||
</FrameLayout>
|
||||
|
|
|
@ -233,6 +233,6 @@
|
|||
|
||||
<!-- Discovery-->
|
||||
<dimen name="discovery_button_min_height">40dp</dimen>
|
||||
<dimen name="discovery_search_item_min_height">148dp</dimen>
|
||||
<dimen name="discovery_expert_item_min_height">196dp</dimen>
|
||||
<dimen name="discovery_search_item_min_height">166dp</dimen>
|
||||
<dimen name="discovery_expert_item_min_height">198dp</dimen>
|
||||
</resources>
|
||||
|
|
|
@ -195,6 +195,7 @@
|
|||
<style name="MwmTextAppearance.Discovery.Subtitle" parent="MwmTextAppearance.Body3">
|
||||
<item name="android:fontFamily" tools:targetApi="jelly_bean">@string/robotoMedium</item>
|
||||
<item name="android:textAllCaps">true</item>
|
||||
<item name="android:gravity">start</item>
|
||||
</style>
|
||||
|
||||
<style name="MwmTextAppearance.Discovery.Item.Title" parent="MwmTextAppearance.Body3.Primary">
|
||||
|
|
30
android/src/com/mapswithme/HotelUtils.java
Normal file
30
android/src/com/mapswithme/HotelUtils.java
Normal file
|
@ -0,0 +1,30 @@
|
|||
package com.mapswithme;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
|
||||
import com.mapswithme.maps.R;
|
||||
|
||||
public class HotelUtils
|
||||
{
|
||||
@NonNull
|
||||
public static CharSequence formatStars(int stars, @NonNull Resources resources)
|
||||
{
|
||||
if (stars <= 0)
|
||||
throw new AssertionError("Start count must be > 0");
|
||||
|
||||
stars = Math.min(stars, 5);
|
||||
// Colorize last dimmed stars
|
||||
final SpannableStringBuilder sb = new SpannableStringBuilder("★ ★ ★ ★ ★");
|
||||
if (stars < 5)
|
||||
{
|
||||
final int start = sb.length() - ((5 - stars) * 2 - 1);
|
||||
sb.setSpan(new ForegroundColorSpan(resources.getColor(R.color.search_star_dimmed)),
|
||||
start, sb.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
}
|
|
@ -29,7 +29,6 @@ import com.mapswithme.maps.gallery.Items;
|
|||
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.ToolbarController;
|
||||
import com.mapswithme.maps.widget.recycler.ItemDecoratorFactory;
|
||||
|
@ -47,18 +46,16 @@ import static com.mapswithme.util.statistics.Destination.ROUTING;
|
|||
import static com.mapswithme.util.statistics.GalleryPlacement.DISCOVERY;
|
||||
import static com.mapswithme.util.statistics.GalleryType.LOCAL_EXPERTS;
|
||||
import static com.mapswithme.util.statistics.GalleryType.SEARCH_ATTRACTIONS;
|
||||
import static com.mapswithme.util.statistics.GalleryType.SEARCH_HOTELS;
|
||||
import static com.mapswithme.util.statistics.GalleryType.SEARCH_RESTAURANTS;
|
||||
import static com.mapswithme.util.statistics.GalleryType.VIATOR;
|
||||
|
||||
public class DiscoveryFragment extends BaseMwmToolbarFragment implements UICallback
|
||||
{
|
||||
private static final int ITEMS_COUNT = 5;
|
||||
private static final int[] ITEM_TYPES = { DiscoveryParams.ITEM_TYPE_VIATOR,
|
||||
private static final int[] ITEM_TYPES = { DiscoveryParams.ITEM_TYPE_HOTELS,
|
||||
DiscoveryParams.ITEM_TYPE_ATTRACTIONS,
|
||||
DiscoveryParams.ITEM_TYPE_CAFES,
|
||||
DiscoveryParams.ITEM_TYPE_LOCAL_EXPERTS };
|
||||
@Nullable
|
||||
private BaseItemSelectedListener<Items.Item> mDefaultListener;
|
||||
private boolean mOnlineMode;
|
||||
@Nullable
|
||||
private CustomNavigateUpListener mNavigateUpListener;
|
||||
|
@ -106,9 +103,9 @@ public class DiscoveryFragment extends BaseMwmToolbarFragment implements UICallb
|
|||
return inflater.inflate(R.layout.fragment_discovery, container, false);
|
||||
}
|
||||
|
||||
private void initViatorGallery()
|
||||
private void initHotelGallery()
|
||||
{
|
||||
setLayoutManagerAndItemDecoration(getContext(), getGallery(R.id.thingsToDo));
|
||||
setLayoutManagerAndItemDecoration(getContext(), getGallery(R.id.hotels));
|
||||
}
|
||||
|
||||
private void initAttractionsGallery()
|
||||
|
@ -143,7 +140,7 @@ public class DiscoveryFragment extends BaseMwmToolbarFragment implements UICallb
|
|||
if (view == null)
|
||||
throw new AssertionError("Root view is not initialized yet!");
|
||||
|
||||
RecyclerView rv = (RecyclerView) view.findViewById(id);
|
||||
RecyclerView rv = view.findViewById(id);
|
||||
if (rv == null)
|
||||
throw new AssertionError("RecyclerView must be within root view!");
|
||||
return rv;
|
||||
|
@ -171,18 +168,7 @@ public class DiscoveryFragment extends BaseMwmToolbarFragment implements UICallb
|
|||
{
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
mToolbarController.setTitle(R.string.discovery_button_title);
|
||||
mDefaultListener = new BaseItemSelectedListener<>(getActivity());
|
||||
view.findViewById(R.id.viatorLogo).setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
Utils.openUrl(getActivity(), DiscoveryManager.nativeGetViatorUrl());
|
||||
Statistics.INSTANCE.trackGalleryEvent(Statistics.EventName.PP_SPONSOR_LOGO_SELECTED,
|
||||
VIATOR, DISCOVERY);
|
||||
}
|
||||
});
|
||||
initViatorGallery();
|
||||
initHotelGallery();
|
||||
initAttractionsGallery();
|
||||
initFoodGallery();
|
||||
initLocalExpertsGallery();
|
||||
|
@ -192,20 +178,17 @@ public class DiscoveryFragment extends BaseMwmToolbarFragment implements UICallb
|
|||
|
||||
private void requestDiscoveryInfoAndInitAdapters()
|
||||
{
|
||||
NetworkPolicy.checkNetworkPolicy(getFragmentManager(), new NetworkPolicy.NetworkPolicyListener()
|
||||
NetworkPolicy.checkNetworkPolicy(getFragmentManager(), policy ->
|
||||
{
|
||||
@Override
|
||||
public void onResult(@NonNull NetworkPolicy policy)
|
||||
{
|
||||
mOnlineMode = policy.сanUseNetwork();
|
||||
initNetworkBasedAdapters();
|
||||
requestDiscoveryInfo();
|
||||
}
|
||||
mOnlineMode = policy.сanUseNetwork();
|
||||
initNetworkBasedAdapters();
|
||||
requestDiscoveryInfo();
|
||||
});
|
||||
}
|
||||
|
||||
private void initSearchBasedAdapters()
|
||||
{
|
||||
getGallery(R.id.hotels).setAdapter(Factory.createSearchBasedLoadingAdapter());
|
||||
getGallery(R.id.attractions).setAdapter(Factory.createSearchBasedLoadingAdapter());
|
||||
getGallery(R.id.food).setAdapter(Factory.createSearchBasedLoadingAdapter());
|
||||
}
|
||||
|
@ -215,10 +198,6 @@ public class DiscoveryFragment extends BaseMwmToolbarFragment implements UICallb
|
|||
UiUtils.showIf(mOnlineMode, getRootView(), R.id.localGuidesTitle, R.id.localGuides);
|
||||
if (mOnlineMode)
|
||||
{
|
||||
RecyclerView thinsToDo = getGallery(R.id.thingsToDo);
|
||||
thinsToDo.setAdapter(Factory.createViatorLoadingAdapter(DiscoveryManager.nativeGetViatorUrl(),
|
||||
mDefaultListener));
|
||||
|
||||
RecyclerView localGuides = getGallery(R.id.localGuides);
|
||||
localGuides.setAdapter(Factory.createLocalExpertsLoadingAdapter());
|
||||
return;
|
||||
|
@ -226,18 +205,8 @@ public class DiscoveryFragment extends BaseMwmToolbarFragment implements UICallb
|
|||
|
||||
// It means that the user doesn't permit mobile network usage, so network based galleries UI
|
||||
// should be hidden in this case.
|
||||
if (ConnectionState.isMobileConnected())
|
||||
{
|
||||
UiUtils.hide(getView(), R.id.thingsToDoLayout, R.id.thingsToDo,
|
||||
R.id.localGuidesTitle, R.id.localGuides);
|
||||
}
|
||||
else
|
||||
{
|
||||
UiUtils.show(getView(), R.id.thingsToDoLayout, R.id.thingsToDo);
|
||||
RecyclerView thinsToDo = getGallery(R.id.thingsToDo);
|
||||
thinsToDo.setAdapter(Factory.createViatorOfflineAdapter(new ViatorOfflineSelectedListener
|
||||
(getActivity()), DISCOVERY));
|
||||
}
|
||||
UiUtils.showIf(ConnectionState.isMobileConnected(), getView(), R.id.localGuidesTitle,
|
||||
R.id.localGuides);
|
||||
}
|
||||
|
||||
private void requestDiscoveryInfo()
|
||||
|
@ -281,16 +250,15 @@ public class DiscoveryFragment extends BaseMwmToolbarFragment implements UICallb
|
|||
DISCOVERY));
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@Override
|
||||
public void onViatorProductsReceived(@NonNull ViatorProduct[] products)
|
||||
public void onHotelsReceived(@NonNull SearchResult[] results)
|
||||
{
|
||||
updateViewsVisibility(products, R.id.thingsToDoLayout, R.id.thingsToDo);
|
||||
String url = DiscoveryManager.nativeGetViatorUrl();
|
||||
ItemSelectedListener<Items.ViatorItem> listener
|
||||
= createOnlineProductItemListener(VIATOR);
|
||||
getGallery(R.id.thingsToDo).setAdapter(Factory.createViatorAdapter(products, url, listener,
|
||||
DISCOVERY));
|
||||
updateViewsVisibility(results, R.id.hotelsTitle, R.id.hotels);
|
||||
ItemSelectedListener<Items.SearchItem> listener =
|
||||
createSearchProductItemListener(SEARCH_HOTELS);
|
||||
getGallery(R.id.hotels).setAdapter(Factory.createHotelAdapter(results, listener,
|
||||
SEARCH_HOTELS,
|
||||
DISCOVERY));
|
||||
}
|
||||
|
||||
@MainThread
|
||||
|
@ -310,10 +278,9 @@ public class DiscoveryFragment extends BaseMwmToolbarFragment implements UICallb
|
|||
{
|
||||
switch (type)
|
||||
{
|
||||
case VIATOR:
|
||||
String url = DiscoveryManager.nativeGetViatorUrl();
|
||||
getGallery(R.id.thingsToDo).setAdapter(Factory.createViatorErrorAdapter(url, mDefaultListener));
|
||||
Statistics.INSTANCE.trackGalleryError(VIATOR, DISCOVERY, null);
|
||||
case HOTELS:
|
||||
getGallery(R.id.hotels).setAdapter(Factory.createSearchBasedErrorAdapter());
|
||||
Statistics.INSTANCE.trackGalleryError(SEARCH_HOTELS, DISCOVERY, null);
|
||||
break;
|
||||
case ATTRACTIONS:
|
||||
getGallery(R.id.attractions).setAdapter(Factory.createSearchBasedErrorAdapter());
|
||||
|
|
|
@ -50,6 +50,9 @@ enum DiscoveryManager
|
|||
case DiscoveryParams.ITEM_TYPE_CAFES:
|
||||
callback.onCafesReceived(results);
|
||||
break;
|
||||
case DiscoveryParams.ITEM_TYPE_HOTELS:
|
||||
callback.onHotelsReceived(results);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unsupported discovery item type " +
|
||||
"'" + type + "' for search results!");
|
||||
|
@ -62,28 +65,15 @@ enum DiscoveryManager
|
|||
@MainThread
|
||||
private void onViatorProductsReceived(@NonNull final ViatorProduct[] products)
|
||||
{
|
||||
notifyUiWithCheck(products, ItemType.VIATOR, new Action()
|
||||
{
|
||||
@Override
|
||||
public void run(@NonNull UICallback callback)
|
||||
{
|
||||
callback.onViatorProductsReceived(products);
|
||||
}
|
||||
});
|
||||
throw new UnsupportedOperationException("Viator is not supported!");
|
||||
}
|
||||
|
||||
// Called from JNI.
|
||||
@MainThread
|
||||
private void onLocalExpertsReceived(@NonNull final LocalExpert[] experts)
|
||||
{
|
||||
notifyUiWithCheck(experts, ItemType.LOCAL_EXPERTS, new Action()
|
||||
{
|
||||
@Override
|
||||
public void run(@NonNull UICallback callback)
|
||||
{
|
||||
callback.onLocalExpertsReceived(experts);
|
||||
}
|
||||
});
|
||||
notifyUiWithCheck(experts, ItemType.LOCAL_EXPERTS,
|
||||
callback -> callback.onLocalExpertsReceived(experts));
|
||||
}
|
||||
|
||||
// Called from JNI.
|
||||
|
|
|
@ -2,7 +2,6 @@ package com.mapswithme.maps.discovery;
|
|||
|
||||
public enum ItemType
|
||||
{
|
||||
VIATOR,
|
||||
ATTRACTIONS,
|
||||
CAFES,
|
||||
HOTELS,
|
||||
|
|
|
@ -2,20 +2,18 @@ package com.mapswithme.maps.discovery;
|
|||
|
||||
import android.support.annotation.MainThread;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.mapswithme.maps.search.SearchResult;
|
||||
import com.mapswithme.maps.viator.ViatorProduct;
|
||||
|
||||
public interface UICallback
|
||||
{
|
||||
@MainThread
|
||||
void onHotelsReceived(@NonNull SearchResult[] results);
|
||||
@MainThread
|
||||
void onAttractionsReceived(@NonNull SearchResult[] results);
|
||||
@MainThread
|
||||
void onCafesReceived(@NonNull SearchResult[] results);
|
||||
@MainThread
|
||||
void onViatorProductsReceived(@NonNull ViatorProduct[] products);
|
||||
@MainThread
|
||||
void onLocalExpertsReceived(@NonNull LocalExpert[] experts);
|
||||
@MainThread
|
||||
void onError(@NonNull ItemType type);
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
package com.mapswithme.maps.gallery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
|
||||
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
@ -15,6 +18,7 @@ import android.widget.TextView;
|
|||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.target.BitmapImageViewTarget;
|
||||
import com.mapswithme.HotelUtils;
|
||||
import com.mapswithme.maps.R;
|
||||
import com.mapswithme.maps.ugc.Impress;
|
||||
import com.mapswithme.maps.ugc.UGC;
|
||||
|
@ -177,26 +181,60 @@ public class Holders
|
|||
}
|
||||
}
|
||||
|
||||
public static final class SearchViewHolder extends BaseViewHolder<Items.SearchItem>
|
||||
public static abstract class ActionButtonViewHolder<T extends RegularAdapterStrategy.Item>
|
||||
extends BaseViewHolder<T>
|
||||
{
|
||||
@NonNull
|
||||
private final TextView mButton;
|
||||
|
||||
ActionButtonViewHolder(@NonNull View itemView, @NonNull List<T> items, @NonNull
|
||||
GalleryAdapter<?, T> adapter)
|
||||
{
|
||||
super(itemView, items, adapter);
|
||||
mButton = itemView.findViewById(R.id.button);
|
||||
mButton.setOnClickListener(this);
|
||||
itemView.findViewById(R.id.infoLayout).setOnClickListener(this);
|
||||
mButton.setText(R.string.p2p_to_here);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
int position = getAdapterPosition();
|
||||
if (position == RecyclerView.NO_POSITION || mItems.isEmpty())
|
||||
return;
|
||||
|
||||
ItemSelectedListener<T> listener = mAdapter.getListener();
|
||||
if (listener == null)
|
||||
return;
|
||||
|
||||
T item = mItems.get(position);
|
||||
switch (v.getId())
|
||||
{
|
||||
case R.id.infoLayout:
|
||||
listener.onItemSelected(item, position);
|
||||
break;
|
||||
case R.id.button:
|
||||
listener.onActionButtonSelected(item, position);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final class SearchViewHolder extends ActionButtonViewHolder<Items.SearchItem>
|
||||
{
|
||||
@NonNull
|
||||
private final TextView mSubtitle;
|
||||
@NonNull
|
||||
private final TextView mDistance;
|
||||
@NonNull
|
||||
private final TextView mButton;
|
||||
|
||||
public SearchViewHolder(@NonNull View itemView, @NonNull List<Items.SearchItem> items,
|
||||
@NonNull GalleryAdapter<?, Items.SearchItem> adapter)
|
||||
{
|
||||
super(itemView, items, adapter);
|
||||
mTitle = (TextView) itemView.findViewById(R.id.title);
|
||||
mSubtitle = (TextView) itemView.findViewById(R.id.subtitle);
|
||||
mDistance = (TextView) itemView.findViewById(R.id.distance);
|
||||
mButton = (TextView) itemView.findViewById(R.id.button);
|
||||
mButton.setOnClickListener(this);
|
||||
itemView.findViewById(R.id.infoLayout).setOnClickListener(this);
|
||||
mButton.setText(R.string.p2p_to_here);
|
||||
mTitle = itemView.findViewById(R.id.title);
|
||||
mSubtitle = itemView.findViewById(R.id.subtitle);
|
||||
mDistance = itemView.findViewById(R.id.distance);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -207,28 +245,78 @@ public class Holders
|
|||
UiUtils.setTextAndHideIfEmpty(mSubtitle, item.getSubtitle());
|
||||
UiUtils.setTextAndHideIfEmpty(mDistance, item.getDistance());
|
||||
}
|
||||
}
|
||||
|
||||
public static final class HotelViewHolder extends ActionButtonViewHolder<Items.SearchItem>
|
||||
{
|
||||
@NonNull
|
||||
private final TextView mTitle;
|
||||
@NonNull
|
||||
private final TextView mSubtitle;
|
||||
@NonNull
|
||||
private final RatingView mRatingView;
|
||||
@NonNull
|
||||
private final TextView mDistance;
|
||||
|
||||
public HotelViewHolder(@NonNull View itemView, @NonNull List<Items.SearchItem> items, @NonNull
|
||||
GalleryAdapter<?, Items.SearchItem> adapter)
|
||||
{
|
||||
super(itemView, items, adapter);
|
||||
mTitle = itemView.findViewById(R.id.title);
|
||||
mSubtitle = itemView.findViewById(R.id.subtitle);
|
||||
mRatingView = itemView.findViewById(R.id.ratingView);
|
||||
mDistance = itemView.findViewById(R.id.distance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
public void bind(@NonNull Items.SearchItem item)
|
||||
{
|
||||
int position = getAdapterPosition();
|
||||
if (position == RecyclerView.NO_POSITION || mItems.isEmpty())
|
||||
return;
|
||||
UiUtils.setTextAndHideIfEmpty(mTitle, item.getTitle());
|
||||
UiUtils.setTextAndHideIfEmpty(mSubtitle, formatDescription(item.getStars(),
|
||||
item.getFeatureType(),
|
||||
item.getPrice(),
|
||||
mSubtitle.getResources()));
|
||||
|
||||
ItemSelectedListener<Items.SearchItem> listener = mAdapter.getListener();
|
||||
if (listener == null)
|
||||
return;
|
||||
float rating = formatRating(item.getRating());
|
||||
Impress impress = Impress.values()[UGC.nativeToImpress(rating)];
|
||||
mRatingView.setRating(impress, UGC.nativeFormatRating(rating));
|
||||
UiUtils.setTextAndHideIfEmpty(mDistance, item.getDistance());
|
||||
}
|
||||
|
||||
Items.SearchItem item = mItems.get(position);
|
||||
switch (v.getId())
|
||||
//TODO: refactor rating to float in core
|
||||
private static float formatRating(@Nullable String rating)
|
||||
{
|
||||
if (TextUtils.isEmpty(rating))
|
||||
return -1;
|
||||
|
||||
try
|
||||
{
|
||||
case R.id.infoLayout:
|
||||
listener.onItemSelected(item, position);
|
||||
break;
|
||||
case R.id.button:
|
||||
listener.onActionButtonSelected(item, position);
|
||||
break;
|
||||
return Float.valueOf(rating);
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static CharSequence formatDescription(int stars, @Nullable String type,
|
||||
@Nullable String priceCategory,
|
||||
@NonNull Resources res)
|
||||
{
|
||||
final SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
if (stars > 0)
|
||||
sb.append(HotelUtils.formatStars(stars, res));
|
||||
else if (!TextUtils.isEmpty(type))
|
||||
sb.append(type);
|
||||
|
||||
if (!TextUtils.isEmpty(priceCategory))
|
||||
{
|
||||
sb.append(" • ");
|
||||
sb.append(priceCategory);
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,7 +340,6 @@ public class Holders
|
|||
mAdapter = adapter;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
public void bind(@NonNull I item)
|
||||
{
|
||||
mTitle.setText(item.getTitle());
|
||||
|
|
|
@ -132,6 +132,41 @@ public class Items
|
|||
{
|
||||
return mResult.lon;
|
||||
}
|
||||
|
||||
public int getStars()
|
||||
{
|
||||
if (mResult.description == null)
|
||||
return 0;
|
||||
|
||||
return mResult.description.stars;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getRating()
|
||||
{
|
||||
if (mResult.description == null)
|
||||
return null;
|
||||
|
||||
return mResult.description.rating;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getPrice()
|
||||
{
|
||||
if (mResult.description == null)
|
||||
return null;
|
||||
|
||||
return mResult.description.pricing;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getFeatureType()
|
||||
{
|
||||
if (mResult.description == null)
|
||||
return null;
|
||||
|
||||
return mResult.description.featureType;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Item
|
||||
|
|
|
@ -35,7 +35,7 @@ public abstract class RegularAdapterStrategy<T extends RegularAdapterStrategy.It
|
|||
switch (viewType)
|
||||
{
|
||||
case TYPE_PRODUCT:
|
||||
return createProductViewHodler(parent, viewType, adapter);
|
||||
return createProductViewHolder(parent, viewType, adapter);
|
||||
case TYPE_MORE:
|
||||
return createMoreProductsViewHolder(parent, viewType, adapter);
|
||||
default:
|
||||
|
@ -57,7 +57,7 @@ public abstract class RegularAdapterStrategy<T extends RegularAdapterStrategy.It
|
|||
}
|
||||
|
||||
@NonNull
|
||||
protected abstract Holders.BaseViewHolder<T> createProductViewHodler(@NonNull ViewGroup parent,
|
||||
protected abstract Holders.BaseViewHolder<T> createProductViewHolder(@NonNull ViewGroup parent,
|
||||
int viewType,
|
||||
@NonNull GalleryAdapter<?, T> adapter);
|
||||
@NonNull
|
||||
|
|
|
@ -78,6 +78,17 @@ public class Factory
|
|||
return new GalleryAdapter<>(new SimpleErrorAdapterStrategy(), null);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static GalleryAdapter createHotelAdapter(@NonNull SearchResult[] results,
|
||||
@Nullable ItemSelectedListener<Items
|
||||
.SearchItem> listener,
|
||||
@NonNull GalleryType type,
|
||||
@NonNull GalleryPlacement placement)
|
||||
{
|
||||
trackProductGalleryShownOrError(results, type, OFFLINE, placement);
|
||||
return new GalleryAdapter<>(new HotelAdapterStrategy(results), listener);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static GalleryAdapter createLocalExpertsAdapter(@NonNull LocalExpert[] experts,
|
||||
@Nullable String expertsUrl,
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
package com.mapswithme.maps.gallery.impl;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.mapswithme.maps.R;
|
||||
import com.mapswithme.maps.gallery.GalleryAdapter;
|
||||
import com.mapswithme.maps.gallery.Holders;
|
||||
import com.mapswithme.maps.gallery.Items;
|
||||
import com.mapswithme.maps.gallery.RegularAdapterStrategy;
|
||||
import com.mapswithme.maps.search.SearchResult;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class HotelAdapterStrategy extends RegularAdapterStrategy<Items.SearchItem>
|
||||
{
|
||||
HotelAdapterStrategy(@NonNull SearchResult[] results)
|
||||
{
|
||||
this(SearchBasedAdapterStrategy.convertItems(results), null/*TODO: coming soon*/);
|
||||
}
|
||||
|
||||
private HotelAdapterStrategy(@NonNull List<Items.SearchItem> items, @Nullable Items.SearchItem
|
||||
moreItem)
|
||||
{
|
||||
super(items, moreItem);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Holders.BaseViewHolder<Items.SearchItem> createProductViewHolder
|
||||
(@NonNull ViewGroup parent, int viewType,
|
||||
@NonNull GalleryAdapter<?, Items.SearchItem> adapter)
|
||||
{
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_discovery_hotel_product, parent, false);
|
||||
return new Holders.HotelViewHolder(view, mItems, adapter);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Holders.BaseViewHolder<Items.SearchItem> createMoreProductsViewHolder
|
||||
(@NonNull ViewGroup parent, int viewType,
|
||||
@NonNull GalleryAdapter<?, Items.SearchItem> adapter)
|
||||
{
|
||||
//TODO: filter item
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ public class LocalExpertsAdapterStrategy extends RegularAdapterStrategy<Items.Lo
|
|||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Holders.BaseViewHolder<Items.LocalExpertItem> createProductViewHodler
|
||||
protected Holders.BaseViewHolder<Items.LocalExpertItem> createProductViewHolder
|
||||
(@NonNull ViewGroup parent, int viewType, @NonNull GalleryAdapter<?, Items.LocalExpertItem> adapter)
|
||||
{
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
|
|
|
@ -31,7 +31,7 @@ class SearchBasedAdapterStrategy extends RegularAdapterStrategy<Items.SearchItem
|
|||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Holders.BaseViewHolder<Items.SearchItem> createProductViewHodler
|
||||
protected Holders.BaseViewHolder<Items.SearchItem> createProductViewHolder
|
||||
(@NonNull ViewGroup parent, int viewType, @NonNull GalleryAdapter<?, Items.SearchItem> adapter)
|
||||
{
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
|
@ -48,7 +48,7 @@ class SearchBasedAdapterStrategy extends RegularAdapterStrategy<Items.SearchItem
|
|||
}
|
||||
|
||||
@NonNull
|
||||
private static List<Items.SearchItem> convertItems(@NonNull SearchResult[] results)
|
||||
static List<Items.SearchItem> convertItems(@NonNull SearchResult[] results)
|
||||
{
|
||||
List<Items.SearchItem> viewItems = new ArrayList<>();
|
||||
for (SearchResult result : results)
|
||||
|
|
|
@ -26,7 +26,7 @@ public class ViatorAdapterStrategy
|
|||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Holders.BaseViewHolder<Items.ViatorItem> createProductViewHodler
|
||||
protected Holders.BaseViewHolder<Items.ViatorItem> createProductViewHolder
|
||||
(@NonNull ViewGroup parent, int viewType, @NonNull GalleryAdapter<?, Items.ViatorItem> adapter)
|
||||
{
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
|
|
|
@ -19,6 +19,7 @@ import android.view.ViewGroup;
|
|||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.mapswithme.HotelUtils;
|
||||
import com.mapswithme.maps.R;
|
||||
import com.mapswithme.maps.bookmarks.data.FeatureId;
|
||||
import com.mapswithme.maps.routing.RoutingController;
|
||||
|
@ -170,22 +171,13 @@ class SearchAdapter extends RecyclerView.Adapter<SearchAdapter.SearchDataViewHol
|
|||
final SpannableStringBuilder res = new SpannableStringBuilder(result.description.featureType);
|
||||
final SpannableStringBuilder tail = new SpannableStringBuilder();
|
||||
|
||||
final int stars = Math.min(result.description.stars, 5);
|
||||
int stars = result.description.stars;
|
||||
if (stars > 0 || !result.description.rating.isEmpty() || isHotelAvailable)
|
||||
{
|
||||
if (stars > 0)
|
||||
{
|
||||
// Colorize last dimmed stars
|
||||
final SpannableStringBuilder sb = new SpannableStringBuilder("★ ★ ★ ★ ★");
|
||||
if (stars < 5)
|
||||
{
|
||||
final int start = sb.length() - ((5 - stars) * 2 - 1);
|
||||
sb.setSpan(new ForegroundColorSpan(itemView.getResources().getColor(R.color.search_star_dimmed)),
|
||||
start, sb.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
tail.append(" • ");
|
||||
tail.append(sb);
|
||||
tail.append(HotelUtils.formatStars(stars, itemView.getResources()));
|
||||
}
|
||||
|
||||
if (!result.description.rating.isEmpty())
|
||||
|
|
|
@ -39,6 +39,15 @@ public enum GalleryType
|
|||
{
|
||||
return Statistics.ParamValue.SEARCH_ATTRACTIONS;
|
||||
}
|
||||
},
|
||||
SEARCH_HOTELS
|
||||
{
|
||||
@NonNull
|
||||
@Override
|
||||
public String getProvider()
|
||||
{
|
||||
return Statistics.ParamValue.BOOKING_COM;
|
||||
}
|
||||
};
|
||||
|
||||
@NonNull
|
||||
|
|
|
@ -360,7 +360,7 @@ public enum Statistics
|
|||
|
||||
public static class ParamValue
|
||||
{
|
||||
static final String BOOKING_COM = "Booking.Com";
|
||||
public static final String BOOKING_COM = "Booking.Com";
|
||||
static final String SEARCH_BOOKING_COM = "Search.Booking.Com";
|
||||
static final String OPENTABLE = "OpenTable";
|
||||
static final String VIATOR = "Viator.Com";
|
||||
|
|
Loading…
Add table
Reference in a new issue