diff options
author | burivuh <burivuh@maps.me> | 2016-10-05 20:22:18 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-10-05 20:22:18 +0300 |
commit | d487f11baa9d61b59bb6e41c2639fbd320fb0a8a (patch) | |
tree | 2247a7eb6727e44a607752bdea50db4847be67e7 /android | |
parent | e75d76e880fafdaf337bc90959f38c835584c6a6 (diff) | |
parent | e666c1dad90a5db45455eb19a73e33307d39d0f8 (diff) |
Merge pull request #4367 from goblinr/MAPSME-1-external-place-page-booking
[android] place page booking
Diffstat (limited to 'android')
76 files changed, 3838 insertions, 363 deletions
diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index bec1087871..af1a0b02e9 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -71,11 +71,11 @@ <meta-data android:name="PW_APPID" - android:value="${PW_APPID}" /> + android:value="${PW_APPID}"/> <meta-data android:name="PW_PROJECT_ID" - android:value="${PW_PROJECT_ID}" /> + android:value="${PW_PROJECT_ID}"/> <!--meta-data android:name="PW_LOG_LEVEL" @@ -241,52 +241,79 @@ </activity> <activity - android:name="com.mapswithme.maps.editor.EditorActivity" - android:configChanges="orientation|screenLayout|screenSize" - android:label="@string/edit_place" - android:theme="@style/MwmTheme.EditorActivity" - android:windowSoftInputMode="adjustResize|stateHidden" - android:parentActivityName="com.mapswithme.maps.MwmActivity"> + android:name="com.mapswithme.maps.editor.EditorActivity" + android:configChanges="orientation|screenLayout|screenSize" + android:label="@string/edit_place" + android:theme="@style/MwmTheme.EditorActivity" + android:windowSoftInputMode="adjustResize|stateHidden" + android:parentActivityName="com.mapswithme.maps.MwmActivity"> + <!-- The meta-data element is needed for versions lower than 4.1 --> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value="com.mapswithme.maps.MwmActivity"/> + </activity> + + <activity + android:name="com.mapswithme.maps.editor.ProfileActivity" + android:parentActivityName="com.mapswithme.maps.settings.SettingsActivity"> + <!-- The meta-data element is needed for versions lower than 4.1 --> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value="com.mapswithme.maps.settings.SettingsActivity"/> + </activity> + + <activity + android:name="com.mapswithme.maps.editor.FeatureCategoryActivity" + android:parentActivityName="com.mapswithme.maps.MwmActivity"> + <!-- The meta-data element is needed for versions lower than 4.1 --> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value="com.mapswithme.maps.MwmActivity"/> + </activity> + + <activity + android:name="com.mapswithme.maps.editor.ReportActivity" + android:parentActivityName="com.mapswithme.maps.MwmActivity"> <!-- The meta-data element is needed for versions lower than 4.1 --> <meta-data - android:name="android.support.PARENT_ACTIVITY" - android:value="com.mapswithme.maps.MwmActivity"/> + android:name="android.support.PARENT_ACTIVITY" + android:value="com.mapswithme.maps.MwmActivity"/> </activity> <activity - android:name="com.mapswithme.maps.editor.ProfileActivity" - android:parentActivityName="com.mapswithme.maps.settings.SettingsActivity"> + android:name="com.mapswithme.maps.editor.OsmAuthActivity" + android:parentActivityName="com.mapswithme.maps.MwmActivity"> <!-- The meta-data element is needed for versions lower than 4.1 --> <meta-data - android:name="android.support.PARENT_ACTIVITY" - android:value="com.mapswithme.maps.settings.SettingsActivity"/> + android:name="android.support.PARENT_ACTIVITY" + android:value="com.mapswithme.maps.MwmActivity"/> </activity> <activity - android:name="com.mapswithme.maps.editor.FeatureCategoryActivity" - android:parentActivityName="com.mapswithme.maps.MwmActivity"> + android:name="com.mapswithme.maps.gallery.GalleryActivity" + android:parentActivityName="com.mapswithme.maps.MwmActivity"> <!-- The meta-data element is needed for versions lower than 4.1 --> <meta-data - android:name="android.support.PARENT_ACTIVITY" - android:value="com.mapswithme.maps.MwmActivity"/> + android:name="android.support.PARENT_ACTIVITY" + android:value="com.mapswithme.maps.MwmActivity"/> </activity> <activity - android:name="com.mapswithme.maps.editor.ReportActivity" - android:parentActivityName="com.mapswithme.maps.MwmActivity"> + android:name="com.mapswithme.maps.gallery.FullScreenGalleryActivity" + android:parentActivityName="com.mapswithme.maps.MwmActivity"> <!-- The meta-data element is needed for versions lower than 4.1 --> <meta-data - android:name="android.support.PARENT_ACTIVITY" - android:value="com.mapswithme.maps.MwmActivity"/> + android:name="android.support.PARENT_ACTIVITY" + android:value="com.mapswithme.maps.MwmActivity"/> </activity> <activity - android:name="com.mapswithme.maps.editor.OsmAuthActivity" - android:parentActivityName="com.mapswithme.maps.MwmActivity"> + android:name="com.mapswithme.maps.review.ReviewActivity" + android:parentActivityName="com.mapswithme.maps.MwmActivity"> <!-- The meta-data element is needed for versions lower than 4.1 --> <meta-data - android:name="android.support.PARENT_ACTIVITY" - android:value="com.mapswithme.maps.MwmActivity"/> + android:name="android.support.PARENT_ACTIVITY" + android:value="com.mapswithme.maps.MwmActivity"/> </activity> <!-- facebook --> @@ -346,17 +373,17 @@ </receiver> <!-- PushWoosh --> - <activity android:name="com.pushwoosh.richpages.RichPageActivity" /> - <activity android:name="com.pushwoosh.MessageActivity" /> - <activity android:name="com.pushwoosh.PushHandlerActivity" /> + <activity android:name="com.pushwoosh.richpages.RichPageActivity"/> + <activity android:name="com.pushwoosh.MessageActivity"/> + <activity android:name="com.pushwoosh.PushHandlerActivity"/> <receiver android:name="com.google.android.gms.gcm.GcmReceiver" android:exported="true" - android:permission="com.google.android.c2dm.permission.SEND" > + android:permission="com.google.android.c2dm.permission.SEND"> <intent-filter> - <action android:name="com.google.android.c2dm.intent.RECEIVE" /> - <category android:name="${applicationId}" /> + <action android:name="com.google.android.c2dm.intent.RECEIVE"/> + <category android:name="${applicationId}"/> </intent-filter> </receiver> @@ -378,10 +405,10 @@ <service android:name="com.pushwoosh.GCMRegistrationService" - android:exported="false" /> + android:exported="false"/> <service - android:name="com.pushwoosh.location.GeoLocationService" /> + android:name="com.pushwoosh.location.GeoLocationService"/> <!-- Catches app upgraded intent --> <receiver android:name=".background.UpgradeReceiver"> diff --git a/android/build.gradle b/android/build.gradle index 4706ea08e1..99fdd323a2 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -57,6 +57,8 @@ dependencies { // TODO remove this library when default LinearLayoutManager will be fixed. compile 'org.solovyev.android.views:linear-layout-manager:0.5@aar' compile 'com.timehop.stickyheadersrecyclerview:library:0.4.3@aar' + //Glide + compile 'com.github.bumptech.glide:glide:3.7.0' } def getDate() { @@ -93,15 +95,15 @@ android { // Crashlytics API key Properties props = new Properties() props.load(new FileInputStream("${projectDir}/fabric.properties")); - manifestPlaceholders = [ 'FABRIC_API_KEY': props['apiKey'] ] + manifestPlaceholders = ['FABRIC_API_KEY': props['apiKey']] buildConfigField 'String', 'FABRIC_API_KEY', /"${props['apiKey']}"/ // PushWoosh keys Properties pwProps = new Properties() pwProps.load(new FileInputStream("${projectDir}/pushwoosh.properties")); - manifestPlaceholders += [ 'PW_APPID': pwProps['pwAppId'] ] + manifestPlaceholders += ['PW_APPID': pwProps['pwAppId']] buildConfigField 'String', 'PW_APPID', /"${pwProps['pwAppId']}"/ - manifestPlaceholders += [ 'PW_PROJECT_ID': pwProps['pwProjectId'] ] + manifestPlaceholders += ['PW_PROJECT_ID': pwProps['pwProjectId']] } sourceSets.main { @@ -210,13 +212,6 @@ android { android.sourceSets.blackberry.assets.srcDirs = ['flavors/mwm-ttf-assets'] buildConfigField 'String', 'REVIEW_URL', '"https://appworld.blackberry.com/webstore/content/51013892"' } - - nineStore { - versionName = android.defaultConfig.versionName + '-NineStore' - android.sourceSets.blackberry.assets.srcDirs = ['flavors/mwm-ttf-assets'] - buildConfigField 'String', 'SUPPORT_MAIL', '"ninestore@mapswithme.com"' - buildConfigField 'String', 'REVIEW_URL', '"http://www.ninestore.ru/android-apps/mapswithme-maps-pro"' - } } // Currently (as of 1.2.3 gradle plugin) ABI filters aren't supported inside of product flavors, so we cannot generate splitted builds only for Google build. @@ -331,7 +326,7 @@ if (System.properties['os.name'].toLowerCase().contains('windows')) project.ext.NDK_BUILD += ".cmd" def archs = ['x86', 'armeabi-v7a-hard'] -def buildTypes = [[ndkType: 'release', cppType: "production", flags : propReleaseNdkFlags], [ndkType: 'debug', cppType: "debug", flags : propDebugNdkFlags]] +def buildTypes = [[ndkType: 'release', cppType: "production", flags: propReleaseNdkFlags], [ndkType: 'debug', cppType: "debug", flags: propDebugNdkFlags]] buildTypes.each { type -> def suffix = type.ndkType.capitalize() @@ -408,14 +403,14 @@ obbGenerate.dependsOn obbClean, obbMainGenerate, obbPatchGenerate, obbMainAlign, def createObbGenerateTask(type, data, name) { return tasks.create(name: "obb${type}Generate", type: Exec, description: 'Generate obb files') { - commandLine ((['zip', '-0', '-j', name, data]).flatten()) + commandLine((['zip', '-0', '-j', name, data]).flatten()) } } def createObbAlignTask(type, rawObb, alignedObb) { def sdkDir = "${android.getSdkDirectory().getAbsolutePath()}" def zipalignPath = sdkDir + File.separator + "build-tools" + File.separator + - propBuildToolsVersion + File.separator + "zipalign"; + propBuildToolsVersion + File.separator + "zipalign"; return tasks.create(name: "obb${type}Align", dependsOn: "obb${type}Generate", type: Exec, description: 'Align obb files') { commandLine zipalignPath, '-v', '8', rawObb, alignedObb diff --git a/android/jni/com/mapswithme/maps/Framework.cpp b/android/jni/com/mapswithme/maps/Framework.cpp index bbd1d9e42c..361dba42b5 100644 --- a/android/jni/com/mapswithme/maps/Framework.cpp +++ b/android/jni/com/mapswithme/maps/Framework.cpp @@ -476,6 +476,12 @@ void Framework::RequestBookingMinPrice(string const & hotelId, string const & cu return m_work.GetBookingApi().GetMinPrice(hotelId, currencyCode, callback); } +void Framework::RequestBookingInfo(string const & hotelId, string const & lang, + function<void(BookingApi::HotelInfo const &)> const & callback) +{ + return m_work.GetBookingApi().GetHotelInfo(hotelId, lang, callback); +} + bool Framework::HasSpaceForMigration() { return m_work.IsEnoughSpaceForMigrate(); diff --git a/android/jni/com/mapswithme/maps/Framework.hpp b/android/jni/com/mapswithme/maps/Framework.hpp index 2937dbb7bc..12fcdcd85d 100644 --- a/android/jni/com/mapswithme/maps/Framework.hpp +++ b/android/jni/com/mapswithme/maps/Framework.hpp @@ -155,6 +155,8 @@ namespace android void SetPlacePageInfo(place_page::Info const & info); place_page::Info & GetPlacePageInfo(); void RequestBookingMinPrice(string const & hotelId, string const & currency, function<void(string const &, string const &)> const & callback); + void RequestBookingInfo(string const & hotelId, string const & lang, + function<void(BookingApi::HotelInfo const &)> const & callback); bool HasSpaceForMigration(); storage::TCountryId PreMigrate(ms::LatLon const & position, storage::Storage::TChangeCountryFunction const & statusChangeListener, diff --git a/android/jni/com/mapswithme/maps/SponsoredHotel.cpp b/android/jni/com/mapswithme/maps/SponsoredHotel.cpp index b9ece774fd..cabe3fe119 100644 --- a/android/jni/com/mapswithme/maps/SponsoredHotel.cpp +++ b/android/jni/com/mapswithme/maps/SponsoredHotel.cpp @@ -2,16 +2,28 @@ #include "../core/jni_helper.hpp" #include "../platform/Platform.hpp" +#include "map/booking_api.hpp" #include "map/place_page_info.hpp" #include "std/bind.hpp" +#include "std/chrono.hpp" namespace { - jclass g_hotelClass; -jmethodID g_hotelClassCtor; +jclass g_facilityTypeClass; +jclass g_nearbyObjectClass; +jclass g_imageClass; +jclass g_reviewClass; +jclass g_hotelInfoClass; +jmethodID g_facilityConstructor; +jmethodID g_nearbyConstructor; +jmethodID g_imageConstructor; +jmethodID g_reviewConstructor; +jmethodID g_hotelInfoConstructor; +jmethodID g_hotelClassConstructor; jmethodID g_priceCallback; +jmethodID g_infoCallback; void PrepareClassRefs(JNIEnv * env, jclass hotelClass) { @@ -19,21 +31,51 @@ void PrepareClassRefs(JNIEnv * env, jclass hotelClass) return; g_hotelClass = static_cast<jclass>(env->NewGlobalRef(hotelClass)); + g_hotelInfoClass = + jni::GetGlobalClassRef(env, "com/mapswithme/maps/widget/placepage/SponsoredHotel$HotelInfo"); + g_facilityTypeClass = jni::GetGlobalClassRef( + env, "com/mapswithme/maps/widget/placepage/SponsoredHotel$FacilityType"); + g_nearbyObjectClass = jni::GetGlobalClassRef( + env, "com/mapswithme/maps/widget/placepage/SponsoredHotel$NearbyObject"); + g_reviewClass = jni::GetGlobalClassRef(env, "com/mapswithme/maps/review/Review"); + g_imageClass = jni::GetGlobalClassRef(env, "com/mapswithme/maps/gallery/Image"); + + g_facilityConstructor = + jni::GetConstructorID(env, g_facilityTypeClass, "(Ljava/lang/String;Ljava/lang/String;)V"); + g_nearbyConstructor = jni::GetConstructorID( + env, g_nearbyObjectClass, "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;DD)V"); + g_imageConstructor = + jni::GetConstructorID(env, g_imageClass, "(Ljava/lang/String;Ljava/lang/String;)V"); + g_reviewConstructor = jni::GetConstructorID(env, g_reviewClass, + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/" + "String;Ljava/lang/String;Ljava/lang/String;FJ)V"); + g_hotelInfoConstructor = jni::GetConstructorID( + env, g_hotelInfoClass, + "(Ljava/lang/String;[Lcom/mapswithme/maps/gallery/Image;[Lcom/mapswithme/maps/widget/" + "placepage/SponsoredHotel$FacilityType;[Lcom/mapswithme/maps/review/Review;[Lcom/mapswithme/" + "maps/widget/placepage/SponsoredHotel$NearbyObject;)V"); // SponsoredHotel(String rating, String price, String urlBook, String urlDescription) - g_hotelClassCtor = jni::GetConstructorID(env, g_hotelClass, "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + g_hotelClassConstructor = jni::GetConstructorID( + env, g_hotelClass, + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); // static void onPriceReceived(final String id, final String price, final String currency) - g_priceCallback = jni::GetStaticMethodID(env, g_hotelClass, "onPriceReceived", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + g_priceCallback = + jni::GetStaticMethodID(env, g_hotelClass, "onPriceReceived", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + // static void onDescriptionReceived(final String id, final String description) + g_infoCallback = jni::GetStaticMethodID( + env, g_hotelClass, "onInfoReceived", + "(Ljava/lang/String;Lcom/mapswithme/maps/widget/placepage/SponsoredHotel$HotelInfo;)V"); } -} // namespace +} // namespace -extern "C" -{ +extern "C" { // static SponsoredHotel nativeGetCurrent(); -JNIEXPORT jobject JNICALL -Java_com_mapswithme_maps_widget_placepage_SponsoredHotel_nativeGetCurrent(JNIEnv * env, jclass clazz) +JNIEXPORT jobject JNICALL Java_com_mapswithme_maps_widget_placepage_SponsoredHotel_nativeGetCurrent( + JNIEnv * env, jclass clazz) { PrepareClassRefs(env, clazz); @@ -41,31 +83,79 @@ Java_com_mapswithme_maps_widget_placepage_SponsoredHotel_nativeGetCurrent(JNIEnv if (!ppInfo.m_isSponsoredHotel) return nullptr; - return env->NewObject(g_hotelClass, g_hotelClassCtor, jni::ToJavaString(env, ppInfo.GetRatingFormatted()), - jni::ToJavaString(env, ppInfo.GetApproximatePricing()), - jni::ToJavaString(env, ppInfo.GetSponsoredBookingUrl()), - jni::ToJavaString(env, ppInfo.GetSponsoredDescriptionUrl())); + return env->NewObject(g_hotelClass, g_hotelClassConstructor, + jni::ToJavaString(env, ppInfo.GetRatingFormatted()), + jni::ToJavaString(env, ppInfo.GetApproximatePricing()), + jni::ToJavaString(env, ppInfo.GetSponsoredBookingUrl()), + jni::ToJavaString(env, ppInfo.GetSponsoredDescriptionUrl())); } // static void nativeRequestPrice(String id, String currencyCode); -JNIEXPORT void JNICALL -Java_com_mapswithme_maps_widget_placepage_SponsoredHotel_nativeRequestPrice(JNIEnv * env, jclass clazz, jstring id, jstring currencyCode) +JNIEXPORT void JNICALL Java_com_mapswithme_maps_widget_placepage_SponsoredHotel_nativeRequestPrice( + JNIEnv * env, jclass clazz, jstring id, jstring currencyCode) { PrepareClassRefs(env, clazz); string const hotelId = jni::ToNativeString(env, id); string const code = jni::ToNativeString(env, currencyCode); - g_framework->RequestBookingMinPrice(hotelId, code, [hotelId](string const & price, string const & currency) - { - GetPlatform().RunOnGuiThread([=]() - { + g_framework->RequestBookingMinPrice(hotelId, code, [hotelId](string const & price, + string const & currency) { + GetPlatform().RunOnGuiThread([=]() { JNIEnv * env = jni::GetEnv(); env->CallStaticVoidMethod(g_hotelClass, g_priceCallback, jni::ToJavaString(env, hotelId), - jni::ToJavaString(env, price), - jni::ToJavaString(env, currency)); + jni::ToJavaString(env, price), jni::ToJavaString(env, currency)); + }); + }); +} + +// static void nativeRequestInfo(String id, String locale); +JNIEXPORT void JNICALL Java_com_mapswithme_maps_widget_placepage_SponsoredHotel_nativeRequestInfo( + JNIEnv * env, jclass clazz, jstring id, jstring locale) +{ + PrepareClassRefs(env, clazz); + + string const hotelId = jni::ToNativeString(env, id); + string const code = jni::ToNativeString(env, locale); + + g_framework->RequestBookingInfo(hotelId, code, [hotelId]( + BookingApi::HotelInfo const & hotelInfo) { + GetPlatform().RunOnGuiThread([=]() { + JNIEnv * env = jni::GetEnv(); + + auto description = jni::ToJavaString(env, hotelInfo.m_description); + auto photos = + jni::ToJavaArray(env, g_imageClass, hotelInfo.m_photos, + [](JNIEnv * env, BookingApi::HotelPhotoUrls const & item) { + return env->NewObject(g_imageClass, g_imageConstructor, + jni::ToJavaString(env, item.m_original), + jni::ToJavaString(env, item.m_small)); + }); + auto facilities = + jni::ToJavaArray(env, g_facilityTypeClass, hotelInfo.m_facilities, + [](JNIEnv * env, BookingApi::Facility const & item) { + return env->NewObject(g_facilityTypeClass, g_facilityConstructor, + jni::ToJavaString(env, item.m_id), + jni::ToJavaString(env, item.m_localizedName)); + }); + auto reviews = jni::ToJavaArray( + env, g_reviewClass, hotelInfo.m_reviews, + [](JNIEnv * env, BookingApi::HotelReview const & item) { + return env->NewObject( + g_reviewClass, g_reviewConstructor, jni::ToJavaString(env, item.m_reviewNeutral), + jni::ToJavaString(env, item.m_reviewPositive), + jni::ToJavaString(env, item.m_reviewNegative), + jni::ToJavaString(env, item.m_author), jni::ToJavaString(env, item.m_authorPictUrl), + item.m_rating, + time_point_cast<milliseconds>(item.m_date).time_since_epoch().count()); + }); + auto nearby = env->NewObjectArray(0, g_nearbyObjectClass, 0); + + env->CallStaticVoidMethod(g_hotelClass, g_infoCallback, jni::ToJavaString(env, hotelId), + env->NewObject(g_hotelInfoClass, g_hotelInfoConstructor, + description, photos, facilities, reviews, nearby)); }); }); } -} // extern "C" +} // extern "C" diff --git a/android/res/drawable/bg_circle_green.xml b/android/res/drawable/bg_circle_green.xml new file mode 100644 index 0000000000..24f254e751 --- /dev/null +++ b/android/res/drawable/bg_circle_green.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + <solid android:color="@color/bg_placepage_rating_positive"/> + <size + android:height="@dimen/placepage_margin_rating" + android:width="@dimen/placepage_margin_rating"/> +</shape> diff --git a/android/res/drawable/bg_circle_green_night.xml b/android/res/drawable/bg_circle_green_night.xml new file mode 100644 index 0000000000..59127c39ac --- /dev/null +++ b/android/res/drawable/bg_circle_green_night.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + <solid android:color="@color/bg_placepage_rating_positive_night"/> + <size + android:height="@dimen/placepage_margin_rating" + android:width="@dimen/placepage_margin_rating"/> +</shape> diff --git a/android/res/drawable/bg_circle_red.xml b/android/res/drawable/bg_circle_red.xml new file mode 100644 index 0000000000..c5a44a59a0 --- /dev/null +++ b/android/res/drawable/bg_circle_red.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + <solid android:color="@color/bg_placepage_rating_negative"/> + <size + android:height="@dimen/placepage_margin_rating" + android:width="@dimen/placepage_margin_rating"/> +</shape> diff --git a/android/res/drawable/bg_circle_red_night.xml b/android/res/drawable/bg_circle_red_night.xml new file mode 100644 index 0000000000..61cb40cf65 --- /dev/null +++ b/android/res/drawable/bg_circle_red_night.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + <solid android:color="@color/bg_placepage_rating_negative_night"/> + <size + android:height="@dimen/placepage_margin_rating" + android:width="@dimen/placepage_margin_rating"/> +</shape> diff --git a/android/res/drawable/divider_transparent.xml b/android/res/drawable/divider_transparent.xml new file mode 100644 index 0000000000..377b4d7e12 --- /dev/null +++ b/android/res/drawable/divider_transparent.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <size android:width="@dimen/margin_quarter" + android:height="@dimen/margin_quarter"/> + <solid android:color="@android:color/transparent"/> +</shape> diff --git a/android/res/drawable/ic_chevron_right_white.xml b/android/res/drawable/ic_chevron_right_white.xml new file mode 100644 index 0000000000..0563f22f11 --- /dev/null +++ b/android/res/drawable/ic_chevron_right_white.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFF" + android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/> +</vector> diff --git a/android/res/drawable/ic_minus_red.xml b/android/res/drawable/ic_minus_red.xml new file mode 100644 index 0000000000..b108146e81 --- /dev/null +++ b/android/res/drawable/ic_minus_red.xml @@ -0,0 +1,8 @@ +<vector android:height="10dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0" + android:width="10dp" + xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FFF44336" + android:pathData="M19,13H5v-2h14v2z"/> +</vector> diff --git a/android/res/drawable/ic_negative_review.xml b/android/res/drawable/ic_negative_review.xml new file mode 100644 index 0000000000..a28a8a5646 --- /dev/null +++ b/android/res/drawable/ic_negative_review.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:drawable="@drawable/bg_circle_red" + android:left="@dimen/margin_eighth" + android:right="@dimen/margin_eighth" + android:bottom="@dimen/margin_eighth" + android:top="@dimen/margin_eighth"/> + <item + android:drawable="@drawable/ic_minus_red" + android:left="@dimen/margin_eighth" + android:right="@dimen/margin_eighth" + android:bottom="@dimen/margin_eighth" + android:top="@dimen/margin_eighth"/> +</layer-list> diff --git a/android/res/drawable/ic_negative_review_night.xml b/android/res/drawable/ic_negative_review_night.xml new file mode 100644 index 0000000000..475fd403dc --- /dev/null +++ b/android/res/drawable/ic_negative_review_night.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:drawable="@drawable/bg_circle_red_night" + android:left="@dimen/margin_eighth" + android:right="@dimen/margin_eighth" + android:bottom="@dimen/margin_eighth" + android:top="@dimen/margin_eighth"/> + <item + android:drawable="@drawable/ic_minus_red" + android:left="@dimen/margin_eighth" + android:right="@dimen/margin_eighth" + android:bottom="@dimen/margin_eighth" + android:top="@dimen/margin_eighth"/> +</layer-list> diff --git a/android/res/drawable/ic_plus_green.xml b/android/res/drawable/ic_plus_green.xml new file mode 100644 index 0000000000..d8e49364af --- /dev/null +++ b/android/res/drawable/ic_plus_green.xml @@ -0,0 +1,8 @@ +<vector android:height="10dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0" + android:width="10dp" + xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FF578B2D" + android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/> +</vector> diff --git a/android/res/drawable/ic_positive_review.xml b/android/res/drawable/ic_positive_review.xml new file mode 100644 index 0000000000..e3fc607c73 --- /dev/null +++ b/android/res/drawable/ic_positive_review.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:drawable="@drawable/bg_circle_green" + android:left="@dimen/margin_eighth" + android:right="@dimen/margin_eighth" + android:bottom="@dimen/margin_eighth" + android:top="@dimen/margin_eighth"/> + <item + android:drawable="@drawable/ic_plus_green" + android:left="@dimen/margin_eighth" + android:right="@dimen/margin_eighth" + android:bottom="@dimen/margin_eighth" + android:top="@dimen/margin_eighth"/> +</layer-list> diff --git a/android/res/drawable/ic_positive_review_night.xml b/android/res/drawable/ic_positive_review_night.xml new file mode 100644 index 0000000000..317ce7719c --- /dev/null +++ b/android/res/drawable/ic_positive_review_night.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:drawable="@drawable/bg_circle_green_night" + android:left="@dimen/margin_eighth" + android:right="@dimen/margin_eighth" + android:bottom="@dimen/margin_eighth" + android:top="@dimen/margin_eighth"/> + <item + android:drawable="@drawable/ic_plus_green" + android:left="@dimen/margin_eighth" + android:right="@dimen/margin_eighth" + android:bottom="@dimen/margin_eighth" + android:top="@dimen/margin_eighth"/> +</layer-list> diff --git a/android/res/layout-w600dp-port/place_page_details.xml b/android/res/layout-w600dp-port/place_page_details.xml index 790899f741..1e03e0f8e7 100644 --- a/android/res/layout-w600dp-port/place_page_details.xml +++ b/android/res/layout-w600dp-port/place_page_details.xml @@ -47,6 +47,12 @@ android:layout_toLeftOf="@id/anchor_center" android:layout_toStartOf="@id/anchor_center" android:orientation="vertical"> + <include layout="@layout/place_page_hotel_gallery"/> + + <include layout="@layout/place_page_hotel_facilities"/> + + <include layout="@layout/place_page_hotel_description"/> + <include layout="@layout/place_page_placename"/> <include layout="@layout/place_page_entrance"/> @@ -82,7 +88,13 @@ <include layout="@layout/place_page_cuisine"/> + <include layout="@layout/place_page_hotel_nearby"/> + + <include layout="@layout/place_page_hotel_rating"/> + + <!--TODO: remove this after booking_api.cpp will be done--> <include layout="@layout/place_page_more"/> + </LinearLayout> </RelativeLayout> diff --git a/android/res/layout/activity_full_screen_gallery.xml b/android/res/layout/activity_full_screen_gallery.xml new file mode 100644 index 0000000000..69411ca818 --- /dev/null +++ b/android/res/layout/activity_full_screen_gallery.xml @@ -0,0 +1,94 @@ +<?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:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/black"> + + <android.support.v4.view.ViewPager + android:id="@+id/vp__image" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + + <include + layout="@layout/toolbar_transparent"/> + + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/margin_half" + android:layout_marginEnd="@dimen/margin_base" + android:layout_marginLeft="@dimen/margin_base" + android:layout_marginRight="@dimen/margin_base" + android:layout_marginStart="@dimen/margin_base" + android:layout_marginTop="@dimen/margin_base" + android:layout_gravity="bottom"> + + <TextView + android:id="@+id/tv__description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/margin_half" + android:textColor="@color/white_primary" + android:textAppearance="@style/MwmTextAppearance.Body1" + android:maxLines="1" + tools:text="Staff, rooftop view, location, free bike…"/> + + <RelativeLayout + android:id="@+id/rl__user_block" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + <View + android:id="@+id/divider" + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="@color/divider_gallery"/> + <ImageView + android:id="@+id/iv__avatar" + android:layout_width="@dimen/track_circle_size" + android:layout_height="@dimen/track_circle_size" + android:layout_marginTop="@dimen/margin_half" + android:layout_below="@id/divider" + android:layout_marginRight="@dimen/margin_half" + android:layout_marginEnd="@dimen/margin_half" + tools:src="@drawable/img_editor_medal"/> + <TextView + android:id="@+id/tv__name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/margin_half" + android:layout_toRightOf="@id/iv__avatar" + android:layout_toEndOf="@id/iv__avatar" + android:layout_below="@id/divider" + android:maxLines="1" + android:textColor="@color/white_primary" + android:textAppearance="@style/MwmTextAppearance.Body1" + tools:text="Polina"/> + <TextView + android:id="@+id/tv__source" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toRightOf="@id/iv__avatar" + android:layout_toEndOf="@id/iv__avatar" + android:layout_below="@id/tv__name" + android:maxLines="1" + android:textColor="@color/white_primary" + android:textAppearance="@style/MwmTextAppearance.Body4" + tools:text="via Booking"/> + <TextView + android:id="@+id/tv__date" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_toRightOf="@id/tv__source" + android:layout_toEndOf="@id/tv__source" + android:layout_alignBaseline="@id/tv__source" + android:gravity="end" + android:maxLines="1" + android:textColor="@color/white_primary" + android:textAppearance="@style/MwmTextAppearance.Body4" + tools:text="Jule 8, 2016"/> + </RelativeLayout> + </LinearLayout> +</FrameLayout> diff --git a/android/res/layout/fragment_fullscreen_image.xml b/android/res/layout/fragment_fullscreen_image.xml new file mode 100644 index 0000000000..2894f973c2 --- /dev/null +++ b/android/res/layout/fragment_fullscreen_image.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/black"> + + <ImageView + android:id="@+id/iv__image" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + +</LinearLayout> diff --git a/android/res/layout/fragment_gallery.xml b/android/res/layout/fragment_gallery.xml new file mode 100644 index 0000000000..3e09892448 --- /dev/null +++ b/android/res/layout/fragment_gallery.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <android.support.v7.widget.RecyclerView + android:id="@+id/rv__gallery" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingLeft="@dimen/margin_half" + android:paddingRight="@dimen/margin_half" + android:paddingTop="@dimen/margin_half_plus" + tools:listitem="@layout/item_image"/> +</LinearLayout> diff --git a/android/res/layout/fragment_review.xml b/android/res/layout/fragment_review.xml new file mode 100644 index 0000000000..a8d0336aee --- /dev/null +++ b/android/res/layout/fragment_review.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?cardBackground"> + <android.support.v7.widget.RecyclerView + android:id="@+id/rv__review" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:listitem="@layout/item_comment"/> +</LinearLayout> diff --git a/android/res/layout/item_comment.xml b/android/res/layout/item_comment.xml new file mode 100644 index 0000000000..7497d9c945 --- /dev/null +++ b/android/res/layout/item_comment.xml @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <View + android:id="@+id/v__divider" + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_marginLeft="@dimen/margin_base" + android:layout_marginRight="@dimen/margin_base" + android:background="?dividerHorizontal"/> + + <TextView + android:id="@+id/tv__user_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/margin_base" + android:layout_marginStart="@dimen/margin_base" + android:layout_marginTop="@dimen/margin_base" + android:layout_toLeftOf="@+id/tv__user_rating" + android:layout_toStartOf="@+id/tv__user_rating" + android:layout_below="@id/v__divider" + android:textAppearance="@style/MwmTextAppearance.Body1" + tools:text="Аleksey"/> + + <TextView + android:id="@+id/tv__comment_date" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/margin_base" + android:layout_marginStart="@dimen/margin_base" + android:layout_marginBottom="@dimen/margin_base" + android:layout_below="@id/tv__user_name" + android:textAppearance="@style/MwmTextAppearance.Body4" + tools:text="March 29, 2016"/> + + <TextView + android:id="@+id/tv__user_rating" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="@dimen/margin_base" + android:layout_marginEnd="@dimen/margin_base" + android:layout_marginTop="@dimen/margin_base" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" + android:layout_below="@id/v__divider" + android:textAppearance="@style/MwmTextAppearance.Headline" + tools:text="9.2"/> + + <TextView + android:id="@+id/tv__review" + android:layout_width="match_parent" + android:layout_height="wrap_content" + 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_base" + android:layout_below="@id/tv__comment_date" + android:textAppearance="@style/MwmTextAppearance.Body3.Primary" + android:visibility="gone" + tools:text="Interesting place among SoHo, Little Italy and China town. Modern design. Great view from roof. Near subway. Free refreshment every afternoon. The staff was very friendly."/> + + <LinearLayout + android:id="@+id/ll__positive_review" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/tv__comment_date"> + <ImageView + android:layout_width="@dimen/margin_base_plus" + android:layout_height="@dimen/margin_base_plus" + android:layout_marginLeft="@dimen/margin_base" + android:layout_marginStart="@dimen/margin_base" + android:src="?ppPositive"/> + + <TextView + android:id="@+id/tv__positive_review" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/margin_double" + android:layout_marginStart="@dimen/margin_double" + android:layout_marginRight="@dimen/margin_base" + android:layout_marginEnd="@dimen/margin_base" + android:layout_marginBottom="@dimen/margin_base" + android:paddingTop="@dimen/margin_eighth" + android:textAppearance="@style/MwmTextAppearance.Body3.Primary" + tools:text="Interesting place among SoHo, Little Italy and China town. Modern design. Great view from roof. Near subway. Free refreshment every afternoon. The staff was very friendly."/> + </LinearLayout> + + <LinearLayout + android:id="@+id/ll__negative_review" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/ll__positive_review"> + <ImageView + android:id="@+id/iv__negative_review" + android:layout_width="@dimen/margin_base_plus" + android:layout_height="@dimen/margin_base_plus" + android:layout_marginBottom="@dimen/margin_base" + android:layout_marginLeft="@dimen/margin_base" + android:layout_marginStart="@dimen/margin_base" + android:src="?ppNegative"/> + + <TextView + android:id="@+id/tv__negative_review" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/margin_double" + android:layout_marginStart="@dimen/margin_double" + android:layout_marginRight="@dimen/margin_base" + android:layout_marginEnd="@dimen/margin_base" + android:layout_marginBottom="@dimen/margin_base" + android:paddingTop="@dimen/margin_eighth" + android:textAppearance="@style/MwmTextAppearance.Body3.Primary" + tools:text="Little bit noise from outsideLittle bit noise from outside"/> + </LinearLayout> + +</RelativeLayout> diff --git a/android/res/layout/item_facility.xml b/android/res/layout/item_facility.xml new file mode 100644 index 0000000000..69790a57e5 --- /dev/null +++ b/android/res/layout/item_facility.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="@dimen/height_block_base" + android:orientation="horizontal" + android:gravity="center_vertical" + android:background="?clickableBackground" + android:clickable="true" + tools:visibility="visible"> + + <ImageView + android:id="@+id/iv__icon" + style="@style/PlacePageMetadataIcon" + android:src="@drawable/ic_entrance"/> + + <TextView + android:id="@+id/tv__facility" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/MwmTextAppearance.Body3.Primary" + tools:text="Pets are allowed on request"/> +</LinearLayout> diff --git a/android/res/layout/item_gallery.xml b/android/res/layout/item_gallery.xml new file mode 100644 index 0000000000..ae725d082b --- /dev/null +++ b/android/res/layout/item_gallery.xml @@ -0,0 +1,22 @@ +<?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_width="@dimen/placepage_hotel_gallery_width" + android:layout_height="@dimen/placepage_hotel_gallery_height" + android:clickable="true" + android:focusable="true" + android:foreground="?attr/selectableItemBackground"> + + <ImageView + android:id="@+id/iv__image" + android:layout_width="@dimen/placepage_hotel_gallery_width" + android:layout_height="@dimen/placepage_hotel_gallery_height" + tools:src="@color/base_green"/> + + <TextView + android:id="@+id/tv__more" + style="@style/PlacePageGalleryText" + android:text="@string/placepage_more_button" + android:visibility="gone" + tools:visibility="visible"/> +</FrameLayout> diff --git a/android/res/layout/item_image.xml b/android/res/layout/item_image.xml new file mode 100644 index 0000000000..9cc9280539 --- /dev/null +++ b/android/res/layout/item_image.xml @@ -0,0 +1,15 @@ +<?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_width="match_parent" + android:layout_height="@dimen/gallery_image_height" + android:clickable="true" + android:focusable="true" + android:foreground="?attr/selectableItemBackground"> + + <ImageView + android:id="@+id/iv__image" + android:layout_width="match_parent" + android:layout_height="@dimen/gallery_image_height" + tools:src="@color/base_green"/> +</FrameLayout> diff --git a/android/res/layout/item_more_button.xml b/android/res/layout/item_more_button.xml new file mode 100644 index 0000000000..8854ec9750 --- /dev/null +++ b/android/res/layout/item_more_button.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextView + xmlns:android="http://schemas.android.com/apk/res/android" + style="@style/PlacePageMetadataText.Button" + android:height="@dimen/height_block_base" + android:background="?clickableBackground" + android:gravity="center" + android:text="@string/placepage_more_reviews_button"/> diff --git a/android/res/layout/item_nearby.xml b/android/res/layout/item_nearby.xml new file mode 100644 index 0000000000..da838d14bf --- /dev/null +++ b/android/res/layout/item_nearby.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + xmlns:tools="http://schemas.android.com/tools" + android:paddingTop="@dimen/margin_half_plus" + android:paddingBottom="@dimen/margin_half_plus" + android:paddingRight="@dimen/margin_base" + android:paddingEnd="@dimen/margin_base" + android:paddingLeft="@dimen/margin_base" + android:paddingStart="@dimen/margin_base" + android:clickable="true" + android:focusable="true" + android:background="?attr/selectableItemBackground" + tools:background="#4000FFFF"> + + <ImageView + android:id="@+id/iv__icon" + android:layout_width="@dimen/placepage_hotel_nearby_icon_size" + android:layout_height="@dimen/placepage_hotel_nearby_icon_size" + android:layout_centerVertical="true" + tools:src="@color/base_green"/> + + <LinearLayout + android:id="@+id/ll__info" + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/margin_double" + android:layout_marginStart="@dimen/margin_double" + android:layout_centerVertical="true" + android:layout_toRightOf="@id/iv__icon" + android:layout_toEndOf="@id/iv__icon" + android:gravity="center_vertical"> + + <TextView + android:id="@+id/tv__title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxLines="1" + android:textAppearance="@style/MwmTextAppearance.Body1" + tools:text="Bowery"/> + <TextView + android:id="@+id/tv__type" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/margin_quarter" + android:maxLines="1" + android:textAppearance="@style/MwmTextAppearance.Body3" + tools:text="Subway Station"/> + </LinearLayout> + + <TextView + android:id="@+id/tv__distance" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:layout_toRightOf="@id/ll__info" + android:layout_toEndOf="@id/ll__info" + android:maxLines="1" + android:textAppearance="@style/MwmTextAppearance.PlacePage.Accent" + android:gravity="end" + tools:text="800 ft"/> +</RelativeLayout> diff --git a/android/res/layout/item_rating.xml b/android/res/layout/item_rating.xml new file mode 100644 index 0000000000..b079ce2110 --- /dev/null +++ b/android/res/layout/item_rating.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="@dimen/placepage_margin_rating" + android:paddingBottom="@dimen/margin_base" + android:paddingLeft="@dimen/margin_base" + android:paddingRight="@dimen/margin_base" + android:background="?ppRatingBackground"> + + <TextView + android:id="@+id/tv__place_hotel_rating" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/MwmTextAppearance.Body1" + android:textColor="?ppRatingText" + tools:text="Rating: 8.7 (Excellent)"/> + + <TextView + android:id="@+id/tv__place_hotel_rating_base" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/margin_half" + android:textAppearance="@style/MwmTextAppearance.Body3" + tools:text="Based on 848 hotel reviews"/> +</LinearLayout> diff --git a/android/res/layout/place_page_details.xml b/android/res/layout/place_page_details.xml index b3b07aa77c..515eac12f6 100644 --- a/android/res/layout/place_page_details.xml +++ b/android/res/layout/place_page_details.xml @@ -27,6 +27,12 @@ android:layout_marginBottom="@dimen/margin_half" tools:visibility="gone"/> + <include layout="@layout/place_page_hotel_gallery"/> + + <include layout="@layout/place_page_hotel_facilities"/> + + <include layout="@layout/place_page_hotel_description"/> + <include layout="@layout/place_page_placename"/> <include layout="@layout/place_page_opening_hours"/> @@ -49,6 +55,11 @@ <include layout="@layout/place_page_cuisine"/> + <include layout="@layout/place_page_hotel_nearby"/> + + <include layout="@layout/place_page_hotel_rating"/> + + <!--TODO: remove this after booking_api.cpp will be done--> <include layout="@layout/place_page_more"/> <include layout="@layout/divider_horizontal"/> diff --git a/android/res/layout/place_page_hotel_description.xml b/android/res/layout/place_page_hotel_description.xml new file mode 100644 index 0000000000..9cd23bdf12 --- /dev/null +++ b/android/res/layout/place_page_hotel_description.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/ll__place_hotel_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:minHeight="@dimen/height_block_base" + android:visibility="gone" + tools:background="#20FF0000" + tools:visibility="visible"> + + <TextView + android:layout_marginLeft="@dimen/margin_base" + android:layout_marginRight="@dimen/margin_base" + android:layout_marginTop="@dimen/margin_base" + style="@style/PlacePageTitleText" + android:text="@string/details"/> + + <com.mapswithme.maps.widget.LineCountTextView + android:id="@+id/tv__place_hotel_details" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/margin_base" + android:layout_marginRight="@dimen/margin_base" + android:layout_marginTop="@dimen/margin_half_plus" + android:layout_marginBottom="@dimen/margin_half_plus" + android:textAppearance="@style/MwmTextAppearance.Body3.Primary" + android:maxLines="@integer/pp_hotel_description_lines" + tools:text="One of our top picks in New York City. This boutique hotel in the Manhattan neighborhood of Nolita features a private rooftop and rooms with free WiFi. The Bowery subway station is 1 block from this New York hotel."/> + + <TextView + android:id="@+id/tv__place_hotel_more" + style="@style/PlacePageMetadataText.Button" + android:layout_marginLeft="@dimen/margin_base" + android:layout_marginRight="@dimen/margin_base" + android:height="@dimen/height_block_base" + android:background="?clickableBackground" + android:gravity="center" + android:text="@string/placepage_more_button"/> + + <include layout="@layout/divider_horizontal"/> +</LinearLayout> diff --git a/android/res/layout/place_page_hotel_facilities.xml b/android/res/layout/place_page_hotel_facilities.xml new file mode 100644 index 0000000000..d126cbbe70 --- /dev/null +++ b/android/res/layout/place_page_hotel_facilities.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/ll__place_hotel_facilities" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:minHeight="@dimen/height_block_base" + android:visibility="gone" + tools:background="#4000FFFF" + tools:visibility="visible"> + + <TextView + android:layout_marginLeft="@dimen/margin_base" + android:layout_marginRight="@dimen/margin_base" + android:layout_marginTop="@dimen/margin_base" + style="@style/PlacePageTitleText" + android:text="@string/placepage_hotel_facilities"/> + + <com.mapswithme.maps.widget.StaticGridView + android:id="@+id/gv__place_hotel_facilities" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/margin_base" + android:layout_marginRight="@dimen/margin_base" + android:layout_marginTop="@dimen/margin_base" + android:layout_marginBottom="@dimen/margin_base" + android:numColumns="2" + tools:listitem="@layout/item_facility"/> + + <TextView + android:id="@+id/tv__place_hotel_facilities_more" + style="@style/PlacePageMetadataText.Button" + android:layout_marginLeft="@dimen/margin_base" + android:layout_marginRight="@dimen/margin_base" + android:height="@dimen/height_block_base" + android:background="?clickableBackground" + android:gravity="center" + android:text="@string/placepage_more_button"/> + + <include layout="@layout/divider_horizontal"/> +</LinearLayout> diff --git a/android/res/layout/place_page_hotel_gallery.xml b/android/res/layout/place_page_hotel_gallery.xml new file mode 100644 index 0000000000..15c541325c --- /dev/null +++ b/android/res/layout/place_page_hotel_gallery.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/ll__place_hotel_gallery" + android:layout_width="match_parent" + android:layout_height="@dimen/placepage_hotel_gallery_height" + android:orientation="horizontal" + android:minHeight="@dimen/placepage_hotel_gallery_height" + android:visibility="gone" + tools:background="#20FF0000" + tools:visibility="visible"> + + <android.support.v7.widget.RecyclerView + android:id="@+id/rv__place_hotel_gallery" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:listitem="@layout/item_gallery"/> +</LinearLayout> diff --git a/android/res/layout/place_page_hotel_nearby.xml b/android/res/layout/place_page_hotel_nearby.xml new file mode 100644 index 0000000000..ea675113cb --- /dev/null +++ b/android/res/layout/place_page_hotel_nearby.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/ll__place_hotel_nearby" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:minHeight="@dimen/height_block_base" + android:visibility="gone" + tools:background="#4000FFFF" + tools:visibility="visible"> + + <include layout="@layout/divider_horizontal"/> + + <TextView + android:layout_marginLeft="@dimen/margin_base" + android:layout_marginRight="@dimen/margin_base" + android:layout_marginTop="@dimen/margin_base" + style="@style/PlacePageTitleText" + android:text="@string/placepage_hotel_nearby"/> + + <com.mapswithme.maps.widget.StaticGridView + android:id="@+id/gv__place_hotel_nearby" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/margin_quarter" + android:numColumns="1" + tools:listitem="@layout/item_nearby"/> +</LinearLayout> diff --git a/android/res/layout/place_page_hotel_rating.xml b/android/res/layout/place_page_hotel_rating.xml new file mode 100644 index 0000000000..864e64538d --- /dev/null +++ b/android/res/layout/place_page_hotel_rating.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/ll__place_hotel_rating" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:visibility="gone" + tools:visibility="visible"> + + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="@dimen/placepage_margin_rating" + android:paddingBottom="@dimen/margin_base" + android:paddingLeft="@dimen/margin_base" + android:paddingRight="@dimen/margin_base" + android:background="?ppRatingBackground"> + + <TextView + android:id="@+id/tv__place_hotel_rating" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/MwmTextAppearance.Body1" + android:textColor="?ppRatingText" + tools:text="Rating: 8.7 (Excellent)"/> + + <TextView + android:id="@+id/tv__place_hotel_rating_base" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/margin_half" + android:textAppearance="@style/MwmTextAppearance.Body3" + tools:text="Based on 848 hotel reviews"/> + </LinearLayout> + + <com.mapswithme.maps.widget.StaticGridView + android:id="@+id/gv__place_hotel_review" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:numColumns="1" + tools:listitem="@layout/item_comment"/> + + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_marginLeft="@dimen/margin_base" + android:layout_marginRight="@dimen/margin_base" + android:background="?dividerHorizontal"/> + <TextView + android:id="@+id/tv__place_hotel_reviews_more" + style="@style/PlacePageMetadataText.Button" + android:height="@dimen/height_block_base" + android:background="?clickableBackground" + android:gravity="center" + android:text="@string/placepage_more_reviews_button"/> +</LinearLayout> diff --git a/android/res/layout/place_page_more.xml b/android/res/layout/place_page_more.xml index e93771213c..b597a2c134 100644 --- a/android/res/layout/place_page_more.xml +++ b/android/res/layout/place_page_more.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!--TODO: remove this layout after booking_api.cpp will be done--> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/ll__more" diff --git a/android/res/layout/toolbar_transparent.xml b/android/res/layout/toolbar_transparent.xml new file mode 100644 index 0000000000..9a18440d8d --- /dev/null +++ b/android/res/layout/toolbar_transparent.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.v7.widget.Toolbar + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/toolbar" + style="@style/MwmWidget.ToolbarStyle" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:theme="@style/MwmWidget.ToolbarTheme.Transparent"/> diff --git a/android/res/values-ru/strings.xml b/android/res/values-ru/strings.xml index 576d2216eb..b6453fce7c 100644 --- a/android/res/values-ru/strings.xml +++ b/android/res/values-ru/strings.xml @@ -879,6 +879,7 @@ <string name="minute">мин</string> <string name="placepage_place_description">Описание</string> <string name="placepage_more_button">Ещё</string> + <string name="placepage_more_reviews_button">Ещё отзывы</string> <string name="bookingcom_book_button">Забронировать</string> <string name="placepage_call_button">Позвонить</string> <string name="placepage_edit_bookmark_button">Редактировать метку</string> @@ -964,4 +965,8 @@ <string name="whats_new_route_profile_message">На пеших и веломаршрутах отображается профиль рельефа.</string> <string name="whats_new_booking_improve_title">Экономь на бронировании отеля</string> <string name="whats_new_booking_improve_message">Результаты поиска отелей на карте показывают ценовую категорию.\nОтелей для бронирования стало на 110 000 больше.</string> + <!-- For place page hotel facilities block --> + <string name="placepage_hotel_facilities">Удобства</string> + <!-- For place page hotel nearby block --> + <string name="placepage_hotel_nearby">Рядом</string> </resources> diff --git a/android/res/values-v16/strings.xml b/android/res/values-v16/strings.xml new file mode 100644 index 0000000000..b7ddcc4b0b --- /dev/null +++ b/android/res/values-v16/strings.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Android Strings File --> +<!-- Generated by Twine 0.6.0 --> +<!-- Language: v16 --> +<resources> + <!-- SECTION: Strings --> + + <!-- SECTION: Routing dialogs strings --> + + <!-- SECTION: Strings for downloading map from search --> +</resources> diff --git a/android/res/values-v16/styles-text.xml b/android/res/values-v16/styles-text.xml new file mode 100644 index 0000000000..03e6be2368 --- /dev/null +++ b/android/res/values-v16/styles-text.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <style name="MwmTextAppearance.PlacePage.Title" + parent="MwmTextAppearance.Body3"> + <item name="android:fontFamily">sans-serif-medium</item> + </style> +</resources> diff --git a/android/res/values-w840dp/strings.xml b/android/res/values-w840dp/strings.xml new file mode 100644 index 0000000000..388be69c77 --- /dev/null +++ b/android/res/values-w840dp/strings.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Android Strings File --> +<!-- Generated by Twine 0.6.0 --> +<!-- Language: w840dp --> +<resources> + <!-- SECTION: Strings --> + + <!-- SECTION: Routing dialogs strings --> + + <!-- SECTION: Strings for downloading map from search --> +</resources> diff --git a/android/res/values/colors.xml b/android/res/values/colors.xml index da1c4a3062..0e9f2bfe07 100644 --- a/android/res/values/colors.xml +++ b/android/res/values/colors.xml @@ -32,6 +32,10 @@ <color name="divider">#1E000000</color> <color name="divider_night">#1EFFFFFF</color> + <color name="divider_gallery">#66FFFFFF</color> + + <color name="text_placepage_rating">#568B2E</color> + <color name="text_placepage_rating_night">#46A046</color> <!-- Backgrounds --> <color name="bg_window">#FFEEEEEE</color> @@ -59,6 +63,13 @@ <color name="bg_azimut_arrow">#1D414651</color> <color name="fg_azimut_arrow">#FFFFFFFF</color> + <color name="bg_placepage_rating">#F1F8E9</color> + <color name="bg_placepage_rating_night">#1EF0FAEB</color> + <color name="bg_placepage_rating_positive">#DCEDC8</color> + <color name="bg_placepage_rating_positive_night">#1EF0FAEB</color> + <color name="bg_placepage_rating_negative">#FFCDD2</color> + <color name="bg_placepage_rating_negative_night">#1EFFEBF0</color> + <!-- Buttons --> <color name="button">@color/button_normal</color> <color name="button_night">@color/button_normal_night</color> diff --git a/android/res/values/dimens.xml b/android/res/values/dimens.xml index 6910b5b8ae..c5d743cc34 100644 --- a/android/res/values/dimens.xml +++ b/android/res/values/dimens.xml @@ -145,6 +145,16 @@ <dimen name="altitude_chart_image_height">40dp</dimen> <dimen name="altitude_chart_image_width">232dp</dimen> + <!-- Gallery--> + <dimen name="placepage_hotel_gallery_height">100dp</dimen> + <dimen name="placepage_hotel_gallery_width">150dp</dimen> + <dimen name="gallery_image_height">84dp</dimen> + <!-- Nearby--> + <dimen name="placepage_hotel_nearby_height">64dp</dimen> + <dimen name="placepage_hotel_nearby_icon_size">24dp</dimen> + + <!-- Rating--> + <dimen name="placepage_margin_rating">20dp</dimen> </resources> diff --git a/android/res/values/integer.xml b/android/res/values/integer.xml index 1eb091d22b..cebfdf8b74 100644 --- a/android/res/values/integer.xml +++ b/android/res/values/integer.xml @@ -2,6 +2,7 @@ <resources> <integer name="pp_title_lines">5</integer> <integer name="pp_buttons_max">4</integer> + <integer name="pp_hotel_description_lines">5</integer> <integer name="sharing_initial_rows">2</integer> -</resources>
\ No newline at end of file +</resources> diff --git a/android/res/values/plurals.xml b/android/res/values/plurals.xml new file mode 100644 index 0000000000..92249cb081 --- /dev/null +++ b/android/res/values/plurals.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <plurals name="place_page_booking_rating_base"> + <item quantity="zero">Based on %d hotel reviews</item> + <item quantity="one">Based on %d hotel reviews</item> + <item quantity="two">Based on %d hotel reviews</item> + <item quantity="few">Based on %d hotel reviews</item> + <item quantity="many">Based on %d hotel reviews</item> + <item quantity="other">Based on %d hotel reviews</item> + </plurals> +</resources> diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml index 605fa4643d..c2620d0202 100644 --- a/android/res/values/strings.xml +++ b/android/res/values/strings.xml @@ -883,6 +883,7 @@ <string name="minute">min</string> <string name="placepage_place_description">Description</string> <string name="placepage_more_button">More</string> + <string name="placepage_more_reviews_button">More Reviews</string> <string name="bookingcom_book_button">Book</string> <string name="placepage_call_button">Call</string> <string name="placepage_edit_bookmark_button">Edit Bookmark</string> @@ -968,4 +969,8 @@ <string name="whats_new_route_profile_message">For pedestrian and bike routes we now display the elevation profile.</string> <string name="whats_new_booking_improve_title">Save when booking hotels</string> <string name="whats_new_booking_improve_message">Search results for hotels now contain the price category.\nWe also added more than 110,000 hotels.</string> + <!-- For place page hotel facilities block --> + <string name="placepage_hotel_facilities">Facilities</string> + <!-- For place page hotel nearby block --> + <string name="placepage_hotel_nearby">Nearby</string> </resources> diff --git a/android/res/values/styles-place_page.xml b/android/res/values/styles-place_page.xml index ce734846a6..6feb7dc81a 100644 --- a/android/res/values/styles-place_page.xml +++ b/android/res/values/styles-place_page.xml @@ -59,6 +59,23 @@ <item name="android:textAppearance">@style/MwmTextAppearance.PlacePage.Accent</item> </style> + <style name="PlacePageTitleText"> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:textAppearance">@style/MwmTextAppearance.PlacePage.Title</item> + </style> + + <style name="PlacePageGalleryText"> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:layout_gravity">center</item> + <item name="android:gravity">center</item> + <item name="android:textColor">@color/white_primary</item> + <item name="android:textAllCaps">true</item> + <item name="android:drawableRight">@drawable/ic_chevron_right_white</item> + <item name="android:drawableEnd" tools:targetApi="jelly_bean_mr1">@drawable/ic_chevron_right_white</item> + </style> + <style name="PlacePageMetadataIcon"> <item name="android:layout_width">wrap_content</item> <item name="android:layout_height">wrap_content</item> @@ -66,4 +83,4 @@ <item name="android:layout_marginEnd" tools:targetApi="jelly_bean_mr1">@dimen/margin_base</item> <item name="android:layout_marginRight">@dimen/margin_base</item> </style> -</resources>
\ No newline at end of file +</resources> diff --git a/android/res/values/styles-text.xml b/android/res/values/styles-text.xml index fc7ebe23a0..dc016d2b54 100644 --- a/android/res/values/styles-text.xml +++ b/android/res/values/styles-text.xml @@ -53,6 +53,11 @@ <item name="android:textColor">?android:textColorSecondary</item> </style> + <style name="MwmTextAppearance.Body3.Primary"> + <item name="android:textSize">@dimen/text_size_body_3</item> + <item name="android:textColor">?android:textColorPrimary</item> + </style> + <style name="MwmTextAppearance.Body3.Light"> <item name="android:textColor">?android:textColorPrimaryInverse</item> </style> @@ -116,6 +121,10 @@ <item name="android:textStyle">bold</item> </style> + <style name="MwmTextAppearance.RoutingDimension.Inline" parent="MwmTextAppearance.RoutingNumber"> + <item name="android:textSize">@dimen/text_size_routing_dimension_inline</item> + </style> + <style name="MwmTextAppearance.RoutingDetail"> <item name="android:textSize">@dimen/text_size_routing_plan_detail</item> <item name="android:fontFamily" tools:ignore="NewApi">@string/robotoMedium</item> @@ -143,13 +152,14 @@ <item name="android:textSize">@dimen/text_size_nav_circle_exit</item> </style> - <style name="MwmTextAppearance.PlacePage" - parent="MwmTextAppearance.Body1"/> + <style name="MwmTextAppearance.PlacePage" parent="MwmTextAppearance.Body1"/> <style name="MwmTextAppearance.PlacePage.Accent"> <item name="android:textColor">?colorAccent</item> </style> + <style name="MwmTextAppearance.PlacePage.Title" parent="MwmTextAppearance.Body3"/> + <style name="MwmTextAppearance.Editor"> </style> @@ -161,4 +171,4 @@ <style name="MwmTextAppearance.Editor.Buttons"> <item name="android:textSize">@dimen/text_size_body_3</item> </style> -</resources>
\ No newline at end of file +</resources> diff --git a/android/res/values/styles.xml b/android/res/values/styles.xml index 843ebfb231..56c21826f4 100644 --- a/android/res/values/styles.xml +++ b/android/res/values/styles.xml @@ -16,8 +16,7 @@ <item name="android:layout_marginLeft">0dp</item> </style> - <style name="MwmWidget.MapButton" - parent="android:Widget.ImageButton"> + <style name="MwmWidget.MapButton" parent="android:Widget.ImageButton"> <item name="android:scaleType">center</item> <item name="android:layout_height">64dp</item> <item name="android:layout_width">64dp</item> @@ -53,73 +52,56 @@ </style> <style name="MwmWidget.Floating"> - <item name="android:elevation" - tools:ignore="NewApi">@dimen/appbar_elevation - </item> + <item name="android:elevation" tools:ignore="NewApi">@dimen/appbar_elevation</item> </style> <style name="MwmWidget.Floating.Panel"> <item name="android:background">?panel</item> </style> - <style - name="MwmWidget.PlacePage.EditText" - parent="Widget.AppCompat.EditText"> + <style name="MwmWidget.PlacePage.EditText" parent="Widget.AppCompat.EditText"> <item name="android:imeOptions">actionDone</item> <item name="android:textAppearance">@style/MwmTextAppearance.PlacePage</item> <item name="android:textColorHint">?secondary</item> <item name="android:textCursorDrawable">@null</item> - <item name="android:fontFamily" - tools:ignore="NewApi">@string/robotoRegular - </item> + <item name="android:fontFamily" tools:ignore="NewApi">@string/robotoRegular</item> </style> - <style - name="MwmWidget.ToolbarStyle" - parent="ThemeOverlay.AppCompat.Dark.ActionBar"> + <style name="MwmWidget.ToolbarStyle" parent="ThemeOverlay.AppCompat.Dark.ActionBar"> <item name="android:background">?colorPrimary</item> - <item name="android:elevation" - tools:ignore="NewApi">@dimen/appbar_elevation - </item> + <item name="android:elevation" tools:ignore="NewApi">@dimen/appbar_elevation</item> <item name="android:displayOptions">homeAsUp|showTitle</item> <item name="contentInsetStart">72dp</item> - <item name="android:titleTextAppearance" - tools:ignore="NewApi">@style/MwmTextAppearance.Toolbar.Title - </item> + <item name="android:titleTextAppearance" tools:ignore="NewApi">@style/MwmTextAppearance.Toolbar.Title</item> <item name="titleTextAppearance">@style/MwmTextAppearance.Toolbar.Title</item> <item name="contentInsetLeft">72dp</item> - <item name="android:contentInsetStart" - tools:ignore="NewApi">72dp - </item> - <item name="android:contentInsetLeft" - tools:ignore="NewApi">72dp - </item> + <item name="android:contentInsetStart" tools:ignore="NewApi">72dp</item> + <item name="android:contentInsetLeft" tools:ignore="NewApi">72dp</item> </style> <style name="MwmWidget.ToolbarStyle.Light"> - <item name="android:titleTextAppearance" - tools:targetApi="lollipop">@style/MwmTextAppearance.Toolbar.Title.Light - </item> + <item name="android:titleTextAppearance" tools:targetApi="lollipop">@style/MwmTextAppearance.Toolbar.Title.Light</item> <item name="titleTextAppearance">@style/MwmTextAppearance.Toolbar.Title.Light</item> </style> - <style - name="MwmWidget.ToolbarTheme" - parent="ThemeOverlay.AppCompat.Dark.ActionBar"> + <style name="MwmWidget.ToolbarTheme" parent="ThemeOverlay.AppCompat.Dark.ActionBar"> <item name="android:gravity">center_vertical</item> <item name="colorAccent">@android:color/white</item> </style> - <style - name="MwmWidget.ToolbarTheme.Light" - parent="ThemeOverlay.AppCompat.ActionBar"> + <style name="MwmWidget.ToolbarTheme.Light" parent="ThemeOverlay.AppCompat.ActionBar"> <item name="android:gravity">center_vertical</item> <item name="colorAccent">@color/bg_window_night</item> </style> - <style - name="MwmWidget.ListView" - parent="Widget.AppCompat.ListView"> + <style name="MwmWidget.ToolbarTheme.Transparent" parent="ThemeOverlay.AppCompat.Dark.ActionBar"> + <item name="android:gravity">center_vertical</item> + <item name="colorAccent">@android:color/white</item> + <item name="android:windowActionBarOverlay">true</item> + <item name="windowActionBarOverlay">true</item> + </style> + + <style name="MwmWidget.ListView" parent="Widget.AppCompat.ListView"> <item name="android:fadingEdge">none</item> <item name="android:divider">@color/divider</item> <item name="android:background">@null</item> @@ -129,8 +111,7 @@ <item name="android:cacheColorHint">@android:color/transparent</item> </style> - <style name="MwmWidget.TextView" - parent="android:Widget.TextView"> + <style name="MwmWidget.TextView" parent="android:Widget.TextView"> <item name="android:background">@android:color/transparent</item> </style> @@ -203,8 +184,7 @@ <item name="android:foreground">@drawable/shadow_top</item> </style> - <style name="MwmWidget.RatingBar" - parent="android:Widget.RatingBar"> + <style name="MwmWidget.RatingBar" parent="android:Widget.RatingBar"> <item name="android:progressDrawable">@drawable/rating_bar</item> <item name="android:indeterminateDrawable">@drawable/rating_bar</item> </style> @@ -240,9 +220,7 @@ <item name="android:textAppearance">@style/MwmTextAppearance.Toolbar.Title.Button</item> </style> - <style - name="MwmWidget.SearchNavigationButton" - parent="android:Widget.ImageButton"> + <style name="MwmWidget.SearchNavigationButton" parent="android:Widget.ImageButton"> <item name="android:scaleType">center</item> <item name="android:layout_height">44dp</item> <item name="android:layout_width">44dp</item> diff --git a/android/res/values/themes-attrs.xml b/android/res/values/themes-attrs.xml index da4c68bd8e..ea638e55b7 100644 --- a/android/res/values/themes-attrs.xml +++ b/android/res/values/themes-attrs.xml @@ -24,6 +24,10 @@ <attr name="ppPreviewHeadOpen" format="reference"/> <attr name="ppPreviewHeadClosed" format="reference"/> <attr name="ppArrowDrawable" format="reference"/> + <attr name="ppRatingBackground" format="color"/> + <attr name="ppRatingText" format="color"/> + <attr name="ppPositive" format="reference"/> + <attr name="ppNegative" format="reference"/> <attr name="routingSlot" format="reference"/> <attr name="routingSlotPressed" format="reference"/> diff --git a/android/res/values/themes-base.xml b/android/res/values/themes-base.xml index be39a44486..bc57ba64b7 100644 --- a/android/res/values/themes-base.xml +++ b/android/res/values/themes-base.xml @@ -75,6 +75,11 @@ <item name="routingButtonHint">@color/routing_button_tint</item> <item name="routingButtonPressedHint">@color/routing_button_pressed_tint</item> + + <item name="ppRatingBackground">@color/bg_placepage_rating</item> + <item name="ppRatingText">@color/text_placepage_rating</item> + <item name="ppPositive">@drawable/ic_positive_review</item> + <item name="ppNegative">@drawable/ic_negative_review</item> </style> <!-- Night theme --> @@ -152,5 +157,10 @@ <item name="routingButtonHint">@color/routing_button_tint</item> <item name="routingButtonPressedHint">@color/routing_button_pressed_tint</item> + + <item name="ppRatingBackground">@color/bg_placepage_rating_night</item> + <item name="ppRatingText">@color/text_placepage_rating_night</item> + <item name="ppPositive">@drawable/ic_positive_review_night</item> + <item name="ppNegative">@drawable/ic_negative_review_night</item> </style> </resources>
\ No newline at end of file diff --git a/android/res/values/themes.xml b/android/res/values/themes.xml index fa9c2df6fd..25c60b246b 100644 --- a/android/res/values/themes.xml +++ b/android/res/values/themes.xml @@ -153,4 +153,24 @@ <item name="newsMarker">@drawable/news_marker_night</item> </style> -</resources>
\ No newline at end of file + <style name="MwmTheme.FullScreenGalleryActivity"> + <item name="colorPrimary">@android:color/transparent</item> + <item name="android:colorPrimaryDark" tools:targetApi="lollipop">@android:color/black</item> + <item name="android:timePickerStyle" tools:targetApi="lollipop">@style/MwmWidget.Editor.TimePicker</item> + <item name="android:windowBackground">@null</item> + <item name="windowActionBar">false</item> + <item name="windowActionBarOverlay">true</item> + <item name="android:windowActionBarOverlay">true</item> + </style> + + <style name="MwmTheme.Night.FullScreenGalleryActivity"> + <item name="colorPrimary">@android:color/transparent</item> + <item name="android:colorPrimaryDark" tools:targetApi="lollipop">@android:color/black</item> + <item name="android:timePickerStyle" tools:targetApi="lollipop">@style/MwmWidget.Editor.TimePicker</item> + <item name="android:windowBackground">@null</item> + <item name="windowActionBar">false</item> + <item name="windowActionBarOverlay">true</item> + <item name="android:windowActionBarOverlay">true</item> + </style> + +</resources> diff --git a/android/src/com/mapswithme/maps/base/BaseMwmExtraTitleActivity.java b/android/src/com/mapswithme/maps/base/BaseMwmExtraTitleActivity.java new file mode 100644 index 0000000000..13b2fbbfb1 --- /dev/null +++ b/android/src/com/mapswithme/maps/base/BaseMwmExtraTitleActivity.java @@ -0,0 +1,43 @@ +package com.mapswithme.maps.base; + +import android.os.Bundle; +import android.support.annotation.CallSuper; +import android.support.v7.widget.Toolbar; + +import com.mapswithme.maps.R; +import com.mapswithme.util.UiUtils; + +public class BaseMwmExtraTitleActivity extends BaseMwmFragmentActivity +{ + protected static final String EXTRA_TITLE = "activity_title"; + + @Override + @CallSuper + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + String title = ""; + Bundle bundle = getIntent().getExtras(); + if (bundle != null) + { + title = bundle.getString(EXTRA_TITLE); + } + Toolbar toolbar = getToolbar(); + toolbar.setTitle(title); + UiUtils.showHomeUpButton(toolbar); + displayToolbarAsActionBar(); + } + + @Override + protected int getContentLayoutResId() + { + return R.layout.activity_fragment_and_toolbar; + } + + @Override + protected int getFragmentContentResId() + { + return R.id.fragment_container; + } +} diff --git a/android/src/com/mapswithme/maps/gallery/FullScreenGalleryActivity.java b/android/src/com/mapswithme/maps/gallery/FullScreenGalleryActivity.java new file mode 100644 index 0000000000..96c2d13620 --- /dev/null +++ b/android/src/com/mapswithme/maps/gallery/FullScreenGalleryActivity.java @@ -0,0 +1,204 @@ +package com.mapswithme.maps.gallery; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.graphics.drawable.RoundedBitmapDrawable; +import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory; +import android.support.v4.view.ViewPager; +import android.support.v7.widget.Toolbar; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.target.BitmapImageViewTarget; +import com.mapswithme.maps.R; +import com.mapswithme.maps.base.BaseMwmFragmentActivity; +import com.mapswithme.util.ThemeUtils; +import com.mapswithme.util.UiUtils; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class FullScreenGalleryActivity extends BaseMwmFragmentActivity + implements ViewPager.OnPageChangeListener +{ + public static final String EXTRA_IMAGES = "gallery_images"; + public static final String EXTRA_POSITION = "gallery_position"; + + private List<Image> mImages; + private int mPosition; + private View mUserBlock; + private TextView mDescription; + private TextView mUserName; + private TextView mSource; + private TextView mDate; + private ImageView mAvatar; + + private GalleryPageAdapter mGalleryPageAdapter; + + public static void start(Context context, ArrayList<Image> images, int position) + { + final Intent i = new Intent(context, FullScreenGalleryActivity.class); + i.putParcelableArrayListExtra(EXTRA_IMAGES, images); + i.putExtra(EXTRA_POSITION, position); + context.startActivity(i); + } + + @Override + protected void onCreate(Bundle savedInstanceState) + { + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + + super.onCreate(savedInstanceState); + Toolbar toolbar = getToolbar(); + toolbar.setTitle(""); + UiUtils.showHomeUpButton(toolbar); + displayToolbarAsActionBar(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + + mUserBlock = findViewById(R.id.rl__user_block); + mDescription = (TextView) findViewById(R.id.tv__description); + mUserName = (TextView) findViewById(R.id.tv__name); + mSource = (TextView) findViewById(R.id.tv__source); + mDate = (TextView) findViewById(R.id.tv__date); + mAvatar = (ImageView) findViewById(R.id.iv__avatar); + + readParameters(); + if (mImages != null) + { + mGalleryPageAdapter = new GalleryPageAdapter(getSupportFragmentManager(), mImages); + final ViewPager viewPager = (ViewPager) findViewById(R.id.vp__image); + viewPager.addOnPageChangeListener(this); + viewPager.setAdapter(mGalleryPageAdapter); + viewPager.setCurrentItem(mPosition); + viewPager.post(new Runnable() + { + @Override + public void run() + { + onPageSelected(viewPager.getCurrentItem()); + } + }); + } + } + + @Override + public int getThemeResourceId(String theme) + { + if (ThemeUtils.isDefaultTheme(theme)) + return R.style.MwmTheme_FullScreenGalleryActivity; + + if (ThemeUtils.isNightTheme(theme)) + return R.style.MwmTheme_Night_FullScreenGalleryActivity; + + throw new IllegalArgumentException("Attempt to apply unsupported theme: " + theme); + } + + @Override + protected int getContentLayoutResId() + { + return R.layout.activity_full_screen_gallery; + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) + { + } + + @Override + public void onPageSelected(int position) + { + updateInformation(mGalleryPageAdapter.getImage(position)); + } + + @Override + public void onPageScrollStateChanged(int state) + { + } + + private void readParameters() + { + Bundle extras = getIntent().getExtras(); + if (extras != null) + { + mImages = extras.getParcelableArrayList(EXTRA_IMAGES); + mPosition = extras.getInt(EXTRA_POSITION); + } + } + + private void updateInformation(@NonNull Image image) + { + UiUtils.setTextAndHideIfEmpty(mDescription, image.getDescription()); + UiUtils.setTextAndHideIfEmpty(mUserName, image.getUserName()); + UiUtils.setTextAndHideIfEmpty(mSource, image.getSource()); + updateDate(image); + updateUserAvatar(image); + updateUserBlock(); + } + + private void updateDate(Image image) + { + if (image.getDate() != null) + { + Date date = new Date(image.getDate()); + mDate.setText(DateFormat.getMediumDateFormat(this).format(date)); + UiUtils.show(mDate); + } + else + { + UiUtils.hide(mDate); + } + } + + private void updateUserAvatar(Image image) + { + if (!TextUtils.isEmpty(image.getUserAvatar())) + { + UiUtils.show(mAvatar); + Glide.with(this) + .load(image.getUserAvatar()) + .asBitmap() + .centerCrop() + .into(new BitmapImageViewTarget(mAvatar) + { + @Override + protected void setResource(Bitmap resource) + { + RoundedBitmapDrawable circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(getResources(), resource); + circularBitmapDrawable.setCircular(true); + mAvatar.setImageDrawable(circularBitmapDrawable); + } + }); + } + else + UiUtils.hide(mAvatar); + } + + private void updateUserBlock() + { + if (UiUtils.isHidden(mUserName) + && UiUtils.isHidden(mSource) + && UiUtils.isHidden(mDate) + && UiUtils.isHidden(mAvatar)) + { + UiUtils.hide(mUserBlock); + } + else + { + UiUtils.show(mUserBlock); + } + } +} diff --git a/android/src/com/mapswithme/maps/gallery/FullScreenGalleryFragment.java b/android/src/com/mapswithme/maps/gallery/FullScreenGalleryFragment.java new file mode 100644 index 0000000000..465ff4f515 --- /dev/null +++ b/android/src/com/mapswithme/maps/gallery/FullScreenGalleryFragment.java @@ -0,0 +1,50 @@ +package com.mapswithme.maps.gallery; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; +import com.mapswithme.maps.R; +import com.mapswithme.maps.base.BaseMwmFragment; + +public class FullScreenGalleryFragment extends BaseMwmFragment +{ + static final String ARGUMENT_IMAGE = "argument_image"; + + @Nullable + private Image mImage; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) + { + return inflater.inflate(R.layout.fragment_fullscreen_image, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) + { + super.onViewCreated(view, savedInstanceState); + readArguments(); + + if (mImage != null) + { + ImageView imageView = (ImageView) view.findViewById(R.id.iv__image); + Glide.with(view.getContext()) + .load(mImage.getUrl()) + .into(imageView); + } + } + + private void readArguments() + { + Bundle args = getArguments(); + if (args != null) + mImage = args.getParcelable(ARGUMENT_IMAGE); + } +} diff --git a/android/src/com/mapswithme/maps/gallery/GalleryActivity.java b/android/src/com/mapswithme/maps/gallery/GalleryActivity.java new file mode 100644 index 0000000000..bd9b7b6805 --- /dev/null +++ b/android/src/com/mapswithme/maps/gallery/GalleryActivity.java @@ -0,0 +1,29 @@ +package com.mapswithme.maps.gallery; + +import android.content.Context; +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; + +import com.mapswithme.maps.base.BaseMwmExtraTitleActivity; + +import java.util.ArrayList; + +public class GalleryActivity extends BaseMwmExtraTitleActivity +{ + public static final String EXTRA_IMAGES = "gallery_images"; + + public static void start(Context context, @NonNull ArrayList<Image> images, @NonNull String title) + { + final Intent i = new Intent(context, GalleryActivity.class); + i.putParcelableArrayListExtra(EXTRA_IMAGES, images); + i.putExtra(EXTRA_TITLE, title); + context.startActivity(i); + } + + @Override + protected Class<? extends Fragment> getFragmentClass() + { + return GalleryFragment.class; + } +} diff --git a/android/src/com/mapswithme/maps/gallery/GalleryFragment.java b/android/src/com/mapswithme/maps/gallery/GalleryFragment.java new file mode 100644 index 0000000000..c1c02c8f5d --- /dev/null +++ b/android/src/com/mapswithme/maps/gallery/GalleryFragment.java @@ -0,0 +1,65 @@ +package com.mapswithme.maps.gallery; + +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.mapswithme.maps.R; +import com.mapswithme.maps.base.BaseMwmFragment; +import com.mapswithme.maps.widget.recycler.GridDividerItemDecoration; +import com.mapswithme.maps.widget.recycler.RecyclerClickListener; + +import java.util.ArrayList; + +public class GalleryFragment extends BaseMwmFragment implements RecyclerClickListener +{ + private static final int NUM_COLUMNS = 3; + + @Nullable + private ArrayList<Image> mImages; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) + { + return inflater.inflate(R.layout.fragment_gallery, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) + { + super.onViewCreated(view, savedInstanceState); + readArguments(); + + if (mImages != null) + { + RecyclerView rvGallery = (RecyclerView) view.findViewById(R.id.rv__gallery); + rvGallery.setLayoutManager(new GridLayoutManager(getContext(), NUM_COLUMNS)); + rvGallery.setAdapter(new ImageAdapter(mImages, this)); + Drawable divider = ContextCompat.getDrawable(getContext(), R.drawable.divider_transparent); + rvGallery.addItemDecoration(new GridDividerItemDecoration(divider, divider, NUM_COLUMNS)); + } + } + + private void readArguments() + { + final Bundle arguments = getArguments(); + if (arguments == null) + return; + + mImages = arguments.getParcelableArrayList(GalleryActivity.EXTRA_IMAGES); + } + + @Override + public void onItemClick(View v, int position) + { + FullScreenGalleryActivity.start(getContext(), mImages, position); + } +} diff --git a/android/src/com/mapswithme/maps/gallery/GalleryPageAdapter.java b/android/src/com/mapswithme/maps/gallery/GalleryPageAdapter.java new file mode 100644 index 0000000000..14724038d5 --- /dev/null +++ b/android/src/com/mapswithme/maps/gallery/GalleryPageAdapter.java @@ -0,0 +1,44 @@ +package com.mapswithme.maps.gallery; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentStatePagerAdapter; + +import java.util.List; + +class GalleryPageAdapter extends FragmentStatePagerAdapter +{ + + @NonNull + private final List<Image> mImages; + + GalleryPageAdapter(@NonNull FragmentManager fm, @NonNull List<Image> images) + { + super(fm); + mImages = images; + } + + @Override + public Fragment getItem(int position) + { + Bundle args = new Bundle(); + args.putParcelable(FullScreenGalleryFragment.ARGUMENT_IMAGE, mImages.get(position)); + FullScreenGalleryFragment fragment = new FullScreenGalleryFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public int getCount() + { + return mImages.size(); + } + + @NonNull + Image getImage(int position) + { + return mImages.get(position); + } +} diff --git a/android/src/com/mapswithme/maps/gallery/Image.java b/android/src/com/mapswithme/maps/gallery/Image.java new file mode 100644 index 0000000000..8812814779 --- /dev/null +++ b/android/src/com/mapswithme/maps/gallery/Image.java @@ -0,0 +1,144 @@ +package com.mapswithme.maps.gallery; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public class Image implements Parcelable +{ + @NonNull + private final String mUrl; + @NonNull + private final String mSmallUrl; + @Nullable + private String mDescription; + @Nullable + private String mUserName; + @Nullable + private String mUserAvatar; + @Nullable + private String mSource; + @Nullable + private Long mDate; + + @SuppressWarnings("unused") + public Image(@NonNull String url, @NonNull String smallUrl) + { + this.mUrl = url; + this.mSmallUrl = smallUrl; + } + + protected Image(Parcel in) + { + mUrl = in.readString(); + mSmallUrl = in.readString(); + mDescription = in.readString(); + mUserName = in.readString(); + mUserAvatar = in.readString(); + mSource = in.readString(); + mDate = (Long) in.readValue(Long.class.getClassLoader()); + } + + @Override + public void writeToParcel(Parcel dest, int flags) + { + dest.writeString(mUrl); + dest.writeString(mSmallUrl); + dest.writeString(mDescription); + dest.writeString(mUserName); + dest.writeString(mUserAvatar); + dest.writeString(mSource); + dest.writeValue(mDate); + } + + @Override + public int describeContents() + { + return 0; + } + + public static final Creator<Image> CREATOR = new Creator<Image>() + { + @Override + public Image createFromParcel(Parcel in) + { + return new Image(in); + } + + @Override + public Image[] newArray(int size) + { + return new Image[size]; + } + }; + + @NonNull + public String getUrl() + { + return mUrl; + } + + @NonNull + public String getSmallUrl() + { + return mSmallUrl; + } + + @Nullable + public String getDescription() + { + return mDescription; + } + + public void setDescription(@Nullable String description) + { + this.mDescription = description; + } + + @Nullable + String getUserName() + { + return mUserName; + } + + @SuppressWarnings("unused") + public void setUserName(@Nullable String userName) + { + this.mUserName = userName; + } + + @Nullable + String getUserAvatar() + { + return mUserAvatar; + } + + @SuppressWarnings("unused") + public void setUserAvatar(@Nullable String userAvatar) + { + this.mUserAvatar = userAvatar; + } + + @Nullable + public String getSource() + { + return mSource; + } + + public void setSource(@Nullable String source) + { + this.mSource = source; + } + + @Nullable + public Long getDate() + { + return mDate; + } + + public void setDate(@Nullable Long date) + { + this.mDate = date; + } +} diff --git a/android/src/com/mapswithme/maps/gallery/ImageAdapter.java b/android/src/com/mapswithme/maps/gallery/ImageAdapter.java new file mode 100644 index 0000000000..ff0d977ac8 --- /dev/null +++ b/android/src/com/mapswithme/maps/gallery/ImageAdapter.java @@ -0,0 +1,79 @@ +package com.mapswithme.maps.gallery; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; +import com.mapswithme.maps.R; +import com.mapswithme.maps.widget.recycler.RecyclerClickListener; + +import java.util.ArrayList; + +class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ViewHolder> +{ + @NonNull + private final ArrayList<Image> mItems; + @Nullable + private final RecyclerClickListener mListener; + + ImageAdapter(@NonNull ArrayList<Image> images, @Nullable RecyclerClickListener listener) + { + mItems = images; + mListener = listener; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) + { + return new ViewHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_image, parent, false), mListener); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) + { + holder.bind(mItems.get(position), position); + } + + @Override + public int getItemCount() + { + return mItems.size(); + } + + static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener + { + private ImageView mImage; + private final RecyclerClickListener mListener; + private int mPosition; + + public ViewHolder(View itemView, RecyclerClickListener listener) + { + super(itemView); + mListener = listener; + itemView.setOnClickListener(this); + mImage = (ImageView) itemView.findViewById(R.id.iv__image); + } + + @Override + public void onClick(View v) + { + if (mListener != null) + mListener.onItemClick(v, mPosition); + } + + public void bind(Image image, int position) + { + mPosition = position; + Glide.with(mImage.getContext()) + .load(image.getSmallUrl()) + .centerCrop() + .into(mImage); + } + } +} diff --git a/android/src/com/mapswithme/maps/review/Review.java b/android/src/com/mapswithme/maps/review/Review.java new file mode 100644 index 0000000000..bc55fc569e --- /dev/null +++ b/android/src/com/mapswithme/maps/review/Review.java @@ -0,0 +1,121 @@ +package com.mapswithme.maps.review; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public class Review implements Parcelable +{ + @Nullable + private final String mReview; + @Nullable + private final String mReviewPositive; + @Nullable + private final String mReviewNegative; + @NonNull + private final String mAuthor; + @NonNull + private final String mAuthorAvatar; + private final float mRating; + private final long mDate; + + public Review(@Nullable String review, @Nullable String reviewPositive, + @Nullable String reviewNegative, @NonNull String author, + @NonNull String authorAvatar, + float rating, long date) + { + mReview = review; + mReviewPositive = reviewPositive; + mReviewNegative = reviewNegative; + mAuthor = author; + mAuthorAvatar = authorAvatar; + mRating = rating; + mDate = date; + } + + protected Review(Parcel in) + { + mReview = in.readString(); + mReviewPositive = in.readString(); + mReviewNegative = in.readString(); + mAuthor = in.readString(); + mAuthorAvatar = in.readString(); + mRating = in.readFloat(); + mDate = in.readLong(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) + { + dest.writeString(mReview); + dest.writeString(mReviewPositive); + dest.writeString(mReviewNegative); + dest.writeString(mAuthor); + dest.writeString(mAuthorAvatar); + dest.writeFloat(mRating); + dest.writeLong(mDate); + } + + @Override + public int describeContents() + { + return 0; + } + + public static final Creator<Review> CREATOR = new Creator<Review>() + { + @Override + public Review createFromParcel(Parcel in) + { + return new Review(in); + } + + @Override + public Review[] newArray(int size) + { + return new Review[size]; + } + }; + + @Nullable + public String getReview() + { + return mReview; + } + + @Nullable + public String getReviewPositive() + { + return mReviewPositive; + } + + @Nullable + public String getReviewNegative() + { + return mReviewNegative; + } + + @NonNull + public String getAuthor() + { + return mAuthor; + } + + @SuppressWarnings("unused") + @NonNull + public String getAuthorAvatar() + { + return mAuthorAvatar; + } + + public float getRating() + { + return mRating; + } + + public long getDate() + { + return mDate; + } +} diff --git a/android/src/com/mapswithme/maps/review/ReviewActivity.java b/android/src/com/mapswithme/maps/review/ReviewActivity.java new file mode 100644 index 0000000000..588580a961 --- /dev/null +++ b/android/src/com/mapswithme/maps/review/ReviewActivity.java @@ -0,0 +1,37 @@ +package com.mapswithme.maps.review; + +import android.content.Context; +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; + +import com.mapswithme.maps.base.BaseMwmExtraTitleActivity; + +import java.util.ArrayList; + +public class ReviewActivity extends BaseMwmExtraTitleActivity +{ + static final String EXTRA_REVIEWS = "review_items"; + static final String EXTRA_RATING = "review_rating"; + static final String EXTRA_RATING_BASE = "review_rating_base"; + static final String EXTRA_RATING_URL = "review_rating_url"; + + public static void start(Context context, @NonNull ArrayList<Review> items, + @NonNull String title, @NonNull String rating, int ratingBase, + @NonNull String url) + { + final Intent i = new Intent(context, ReviewActivity.class); + i.putParcelableArrayListExtra(EXTRA_REVIEWS, items); + i.putExtra(EXTRA_TITLE, title); + i.putExtra(EXTRA_RATING, rating); + i.putExtra(EXTRA_RATING_BASE, ratingBase); + i.putExtra(EXTRA_RATING_URL, url); + context.startActivity(i); + } + + @Override + protected Class<? extends Fragment> getFragmentClass() + { + return ReviewFragment.class; + } +} diff --git a/android/src/com/mapswithme/maps/review/ReviewAdapter.java b/android/src/com/mapswithme/maps/review/ReviewAdapter.java new file mode 100644 index 0000000000..b4ea102f97 --- /dev/null +++ b/android/src/com/mapswithme/maps/review/ReviewAdapter.java @@ -0,0 +1,217 @@ +package com.mapswithme.maps.review; + +import android.support.annotation.CallSuper; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.mapswithme.maps.R; +import com.mapswithme.maps.widget.recycler.RecyclerClickListener; +import com.mapswithme.util.UiUtils; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Locale; + +class ReviewAdapter extends RecyclerView.Adapter<ReviewAdapter.BaseViewHolder> +{ + private static final int MAX_COUNT = 15; + private static final int VIEW_TYPE_REVIEW = 0; + private static final int VIEW_TYPE_MORE = 1; + private static final int VIEW_TYPE_RATING = 2; + + @NonNull + private final ArrayList<Review> mItems; + @Nullable + private final RecyclerClickListener mListener; + @NonNull + private final String mRating; + private final int mRatingBase; + + ReviewAdapter(@NonNull ArrayList<Review> images, @Nullable RecyclerClickListener listener, + @NonNull String rating, + int ratingBase) + { + mItems = images; + mListener = listener; + mRating = rating; + mRatingBase = ratingBase; + } + + @Override + public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) + { + if (viewType == VIEW_TYPE_REVIEW) + return new ReviewHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_comment, parent, false), mListener); + + if (viewType == VIEW_TYPE_MORE) + return new MoreHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_more_button, parent, false), mListener); + + return new RatingHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_rating, parent, false), mListener); + } + + @Override + public void onBindViewHolder(BaseViewHolder holder, int position) + { + int positionNoHeader = position - 1; + + if (position == 0) + ((RatingHolder) holder).bind(mRating, mRatingBase); + else if (positionNoHeader < mItems.size()) + holder.bind(mItems.get(positionNoHeader), positionNoHeader); + else + holder.bind(null, positionNoHeader); + } + + @Override + public int getItemCount() + { + if (mItems.size() > MAX_COUNT) + // 1 overall rating item + MAX_COUNT user reviews + 1 "more reviews" item + return MAX_COUNT + 2; + + // 1 overall rating item + count of user reviews + 1 "more reviews" item + return mItems.size() + 2; + } + + @Override + public int getItemViewType(int position) + { + int positionNoHeader = position - 1; + + if (position == 0) + return VIEW_TYPE_RATING; + if (positionNoHeader == mItems.size()) + return VIEW_TYPE_MORE; + + return VIEW_TYPE_REVIEW; + } + + static abstract class BaseViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener + { + private final RecyclerClickListener mListener; + private int mPosition; + + BaseViewHolder(View itemView, RecyclerClickListener listener) + { + super(itemView); + mListener = listener; + } + + @Override + public void onClick(View v) + { + if (mListener != null) + mListener.onItemClick(v, mPosition); + } + + @CallSuper + public void bind(Review item, int position) + { + mPosition = position; + } + } + + private static class ReviewHolder extends BaseViewHolder + { + final View mDivider; + final TextView mUserName; + final TextView mCommentDate; + final TextView mRating; + final TextView mReview; + final View mPositiveReview; + final TextView mTvPositiveReview; + final View mNegativeReview; + final TextView mTvNegativeReview; + + ReviewHolder(View itemView, RecyclerClickListener listener) + { + super(itemView, listener); + mDivider = itemView.findViewById(R.id.v__divider); + mUserName = (TextView) itemView.findViewById(R.id.tv__user_name); + mCommentDate = (TextView) itemView.findViewById(R.id.tv__comment_date); + mRating = (TextView) itemView.findViewById(R.id.tv__user_rating); + mReview = (TextView) itemView.findViewById(R.id.tv__review); + mPositiveReview = itemView.findViewById(R.id.ll__positive_review); + mTvPositiveReview = (TextView) itemView.findViewById(R.id.tv__positive_review); + mNegativeReview = itemView.findViewById(R.id.ll__negative_review); + mTvNegativeReview = (TextView) itemView.findViewById(R.id.tv__negative_review); + } + + @Override + public void bind(Review item, int position) + { + super.bind(item, position); + UiUtils.showIf(position > 0, mDivider); + mUserName.setText(item.getAuthor()); + Date date = new Date(item.getDate()); + mCommentDate.setText(DateFormat.getMediumDateFormat(mCommentDate.getContext()).format(date)); + mRating.setText(String.format(Locale.getDefault(), "%.1f", item.getRating())); + if (TextUtils.isEmpty(item.getReviewPositive())) + { + UiUtils.hide(mPositiveReview); + } + else + { + UiUtils.show(mPositiveReview); + mTvPositiveReview.setText(item.getReviewPositive()); + } + if (TextUtils.isEmpty(item.getReviewNegative())) + { + UiUtils.hide(mNegativeReview); + } + else + { + UiUtils.show(mNegativeReview); + mTvNegativeReview.setText(item.getReviewNegative()); + } + if (UiUtils.isHidden(mNegativeReview) && UiUtils.isHidden(mPositiveReview)) + { + UiUtils.showIf(!TextUtils.isEmpty(item.getReview()), mReview); + } + else + { + UiUtils.hide(mReview); + } + } + } + + private static class MoreHolder extends BaseViewHolder + { + MoreHolder(View itemView, RecyclerClickListener listener) + { + super(itemView, listener); + itemView.setOnClickListener(this); + } + } + + private static class RatingHolder extends BaseViewHolder + { + final TextView mHotelRating; + final TextView mHotelRatingBase; + + RatingHolder(View itemView, RecyclerClickListener listener) + { + super(itemView, listener); + mHotelRating = (TextView) itemView.findViewById(R.id.tv__place_hotel_rating); + mHotelRatingBase = (TextView) itemView.findViewById(R.id.tv__place_hotel_rating_base); + } + + public void bind(String rating, int ratingBase) + { + mHotelRating.setText(rating); + mHotelRatingBase.setText(mHotelRatingBase.getContext().getResources() + .getQuantityString(R.plurals.place_page_booking_rating_base, + ratingBase, ratingBase)); + } + } +} diff --git a/android/src/com/mapswithme/maps/review/ReviewFragment.java b/android/src/com/mapswithme/maps/review/ReviewFragment.java new file mode 100644 index 0000000000..cfb39156e1 --- /dev/null +++ b/android/src/com/mapswithme/maps/review/ReviewFragment.java @@ -0,0 +1,76 @@ +package com.mapswithme.maps.review; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.mapswithme.maps.R; +import com.mapswithme.maps.base.BaseMwmFragment; +import com.mapswithme.maps.widget.recycler.RecyclerClickListener; + +import java.util.ArrayList; + +public class ReviewFragment extends BaseMwmFragment implements RecyclerClickListener +{ + @Nullable + private ArrayList<Review> mItems; + @Nullable + private String mRating; + private int mRatingBase; + @Nullable + private String mUrl; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) + { + return inflater.inflate(R.layout.fragment_review, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) + { + super.onViewCreated(view, savedInstanceState); + readArguments(); + + if (mItems != null && mRating != null) + { + RecyclerView rvGallery = (RecyclerView) view.findViewById(R.id.rv__review); + rvGallery.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false)); + rvGallery.setAdapter(new ReviewAdapter(mItems, this, mRating, mRatingBase)); + } + } + + private void readArguments() + { + final Bundle arguments = getArguments(); + if (arguments == null) + return; + + mItems = arguments.getParcelableArrayList(ReviewActivity.EXTRA_REVIEWS); + mRating = arguments.getString(ReviewActivity.EXTRA_RATING); + mRatingBase = arguments.getInt(ReviewActivity.EXTRA_RATING_BASE); + mUrl = arguments.getString(ReviewActivity.EXTRA_RATING_URL); + } + + @Override + public void onItemClick(View v, int position) + { + if (mUrl == null) + return; + + final Intent intent = new Intent(Intent.ACTION_VIEW); + String url = mUrl; + if (!url.startsWith("http://") && !url.startsWith("https://")) + url = "http://" + url; + intent.setData(Uri.parse(url)); + getContext().startActivity(intent); + } +} diff --git a/android/src/com/mapswithme/maps/widget/LineCountTextView.java b/android/src/com/mapswithme/maps/widget/LineCountTextView.java new file mode 100644 index 0000000000..46d3d9e644 --- /dev/null +++ b/android/src/com/mapswithme/maps/widget/LineCountTextView.java @@ -0,0 +1,56 @@ +package com.mapswithme.maps.widget; + +import android.content.Context; +import android.graphics.Canvas; +import android.text.Layout; +import android.util.AttributeSet; +import android.widget.TextView; + +public class LineCountTextView extends TextView +{ + public interface OnLineCountCalculatedListener + { + + void onLineCountCalculated(boolean grater); + } + + private OnLineCountCalculatedListener mListener; + + public LineCountTextView(Context context) + { + super(context); + } + + public LineCountTextView(Context context, AttributeSet attrs) + { + super(context, attrs); + } + + public LineCountTextView(Context context, AttributeSet attrs, int defStyleAttr) + { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onDraw(Canvas canvas) + { + super.onDraw(canvas); + Layout layout = getLayout(); + + if (layout != null) + { + int textHeight = layout.getHeight(); + int viewHeight = getHeight(); + + if (mListener != null) + { + mListener.onLineCountCalculated(textHeight > viewHeight); + } + } + } + + public void setListener(OnLineCountCalculatedListener listener) + { + mListener = listener; + } +} diff --git a/android/src/com/mapswithme/maps/widget/StaticGridView.java b/android/src/com/mapswithme/maps/widget/StaticGridView.java new file mode 100644 index 0000000000..f2a1662c16 --- /dev/null +++ b/android/src/com/mapswithme/maps/widget/StaticGridView.java @@ -0,0 +1,30 @@ +package com.mapswithme.maps.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.GridView; + +public class StaticGridView extends GridView +{ + public StaticGridView(Context context) + { + super(context); + } + + public StaticGridView(Context context, AttributeSet attrs) + { + super(context, attrs); + } + + public StaticGridView(Context context, AttributeSet attrs, int defStyleAttr) + { + super(context, attrs, defStyleAttr); + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK, MeasureSpec.AT_MOST)); + getLayoutParams().height = getMeasuredHeight(); + } +} diff --git a/android/src/com/mapswithme/maps/widget/placepage/FacilitiesAdapter.java b/android/src/com/mapswithme/maps/widget/placepage/FacilitiesAdapter.java new file mode 100644 index 0000000000..3a5aa72e7e --- /dev/null +++ b/android/src/com/mapswithme/maps/widget/placepage/FacilitiesAdapter.java @@ -0,0 +1,97 @@ +package com.mapswithme.maps.widget.placepage; + +import android.support.annotation.NonNull; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.mapswithme.maps.R; + +import java.util.ArrayList; +import java.util.List; + +class FacilitiesAdapter extends BaseAdapter +{ + static final int MAX_COUNT = 6; + + @NonNull + private List<SponsoredHotel.FacilityType> mItems = new ArrayList<>(); + private boolean isShowAll = false; + + @Override + public int getCount() + { + if (mItems.size() > MAX_COUNT && !isShowAll) + { + return MAX_COUNT; + } + return mItems.size(); + } + + @Override + public Object getItem(int position) + { + return mItems.get(position); + } + + @Override + public long getItemId(int position) + { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) + { + ViewHolder holder; + if (convertView == null) + { + convertView = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_facility, parent, false); + holder = new ViewHolder(convertView); + convertView.setTag(holder); + } + else + { + holder = (ViewHolder) convertView.getTag(); + } + + holder.bind(mItems.get(position)); + + return convertView; + } + + public void setItems(@NonNull List<SponsoredHotel.FacilityType> items) + { + this.mItems = items; + notifyDataSetChanged(); + } + + void setShowAll(boolean showAll) + { + isShowAll = showAll; + notifyDataSetChanged(); + } + + private static class ViewHolder + { + ImageView mIcon; + TextView mName; + + public ViewHolder(View view) + { + mIcon = (ImageView) view.findViewById(R.id.iv__icon); + mName = (TextView) view.findViewById(R.id.tv__facility); + } + + public void bind(SponsoredHotel.FacilityType facility) + { +// TODO map facility key to image resource id + mIcon.setImageResource(R.drawable.ic_entrance); + mName.setText(facility.getName()); + } + } +} diff --git a/android/src/com/mapswithme/maps/widget/placepage/GalleryAdapter.java b/android/src/com/mapswithme/maps/widget/placepage/GalleryAdapter.java new file mode 100644 index 0000000000..ed3cfba45e --- /dev/null +++ b/android/src/com/mapswithme/maps/widget/placepage/GalleryAdapter.java @@ -0,0 +1,205 @@ +package com.mapswithme.maps.widget.placepage; + +import android.content.Context; +import android.graphics.Bitmap; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; +import com.mapswithme.maps.R; +import com.mapswithme.maps.gallery.Image; +import com.mapswithme.maps.widget.recycler.RecyclerClickListener; +import com.mapswithme.util.UiUtils; + +import java.util.ArrayList; +import java.util.List; + +class GalleryAdapter extends RecyclerView.Adapter<GalleryAdapter.ViewHolder> +{ + static final int MAX_COUNT = 5; + + private final Context mContext; + @NonNull + private ArrayList<Image> mItems = new ArrayList<>(); + @NonNull + private final List<Item> mLoadedItems = new ArrayList<>(); + @NonNull + private final List<Item> mItemsToDownload = new ArrayList<>(); + @Nullable + private RecyclerClickListener mListener; + private final int mImageWidth; + private final int mImageHeight; + + GalleryAdapter(Context context) + { + mContext = context; + + mImageWidth = (int) context.getResources().getDimension(R.dimen.placepage_hotel_gallery_width); + mImageHeight = (int) context.getResources() + .getDimension(R.dimen.placepage_hotel_gallery_height); + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) + { + return new ViewHolder(LayoutInflater.from(mContext) + .inflate(R.layout.item_gallery, parent, false), mListener); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) + { + Item item = mLoadedItems.get(position); + item.setShowMore(position == MAX_COUNT - 1 && mItems.size() > MAX_COUNT); + holder.bind(item, position); + } + + @Override + public int getItemCount() + { + return mLoadedItems.size(); + } + + @NonNull + public ArrayList<Image> getItems() + { + return mItems; + } + + public void setItems(@NonNull ArrayList<Image> items) + { + mItems = items; + + for (Item item : mItemsToDownload) + { + item.setCanceled(true); + } + mItemsToDownload.clear(); + mLoadedItems.clear(); + loadImages(); + notifyDataSetChanged(); + } + + public void setListener(@Nullable RecyclerClickListener listener) + { + mListener = listener; + } + + private void loadImages() + { + int size = Math.min(mItems.size(), MAX_COUNT); + + for (int i = 0; i < size; i++) + { + final Item item = new Item(null); + mItemsToDownload.add(item); + Image image = mItems.get(i); + Glide.with(mContext) + .load(image.getSmallUrl()) + .asBitmap() + .centerCrop() + .into(new SimpleTarget<Bitmap>(mImageWidth, mImageHeight) + { + @Override + public void onResourceReady(Bitmap resource, + GlideAnimation<? super Bitmap> glideAnimation) + { + if (item.isCanceled()) + return; + + item.setBitmap(resource); + int size = mLoadedItems.size(); + mLoadedItems.add(item); + notifyItemInserted(size); + } + }); + } + } + + static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener + { + @NonNull + private ImageView mImage; + @NonNull + private View mMore; + @Nullable + private final RecyclerClickListener mListener; + private int mPosition; + + public ViewHolder(View itemView, @Nullable RecyclerClickListener listener) + { + super(itemView); + mListener = listener; + mImage = (ImageView) itemView.findViewById(R.id.iv__image); + mMore = itemView.findViewById(R.id.tv__more); + itemView.setOnClickListener(this); + } + + @Override + public void onClick(View v) + { + if (mListener == null) + return; + + mListener.onItemClick(v, mPosition); + } + + public void bind(Item item, int position) + { + mPosition = position; + mImage.setImageBitmap(item.getBitmap()); + UiUtils.showIf(item.isShowMore(), mMore); + } + } + + static class Item + { + @Nullable + private Bitmap mBitmap; + private boolean isShowMore; + private boolean isCanceled = false; + + Item(@Nullable Bitmap bitmap) + { + this.mBitmap = bitmap; + } + + @Nullable + Bitmap getBitmap() + { + return mBitmap; + } + + void setBitmap(@Nullable Bitmap bitmap) + { + mBitmap = bitmap; + } + + void setShowMore(boolean showMore) + { + isShowMore = showMore; + } + + boolean isShowMore() + { + return isShowMore; + } + + boolean isCanceled() + { + return isCanceled; + } + + void setCanceled(boolean canceled) + { + isCanceled = canceled; + } + } +} diff --git a/android/src/com/mapswithme/maps/widget/placepage/LeftPlacePageAnimationController.java b/android/src/com/mapswithme/maps/widget/placepage/LeftPlacePageAnimationController.java index 40f45ec182..d981692c74 100644 --- a/android/src/com/mapswithme/maps/widget/placepage/LeftPlacePageAnimationController.java +++ b/android/src/com/mapswithme/maps/widget/placepage/LeftPlacePageAnimationController.java @@ -13,7 +13,7 @@ import com.mapswithme.util.UiUtils; class LeftPlacePageAnimationController extends BasePlacePageAnimationController { - public LeftPlacePageAnimationController(@NonNull PlacePageView placePage) + LeftPlacePageAnimationController(@NonNull PlacePageView placePage) { super(placePage); } @@ -27,18 +27,21 @@ class LeftPlacePageAnimationController extends BasePlacePageAnimationController @Override protected boolean onInterceptTouchEvent(MotionEvent event) { + if (mPlacePage.isTouchGallery(event)) + return false; + switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - mIsDragging = false; - mDownCoord = event.getX(); - break; - case MotionEvent.ACTION_MOVE: - if (mDownCoord > mPlacePage.getRight()) - return false; - if (Math.abs(mDownCoord - event.getX()) > mTouchSlop) - return true; - break; + case MotionEvent.ACTION_DOWN: + mIsDragging = false; + mDownCoord = event.getX(); + break; + case MotionEvent.ACTION_MOVE: + if (mDownCoord > mPlacePage.getRight()) + return false; + if (Math.abs(mDownCoord - event.getX()) > mTouchSlop) + return true; + break; } return false; @@ -70,7 +73,7 @@ class LeftPlacePageAnimationController extends BasePlacePageAnimationController final boolean isInRange = Math.abs(distanceX) > X_MIN && Math.abs(distanceX) < X_MAX; if (!isHorizontal || !isInRange) - return false;; + return false; if (!mIsDragging) { @@ -90,13 +93,13 @@ class LeftPlacePageAnimationController extends BasePlacePageAnimationController { switch (newState) { - case HIDDEN: - hidePlacePage(); - break; - case DETAILS: - case PREVIEW: - showPlacePage(currentState, newState); - break; + case HIDDEN: + hidePlacePage(); + break; + case DETAILS: + case PREVIEW: + showPlacePage(currentState); + break; } } @@ -130,7 +133,7 @@ class LeftPlacePageAnimationController extends BasePlacePageAnimationController tracker.onTrackLeftAnimation(offset + mPlacePage.getDockedWidth()); } - private void showPlacePage(final State currentState, final State newState) + private void showPlacePage(final State currentState) { UiUtils.show(mPlacePage); if (currentState != State.HIDDEN) diff --git a/android/src/com/mapswithme/maps/widget/placepage/NearbyAdapter.java b/android/src/com/mapswithme/maps/widget/placepage/NearbyAdapter.java new file mode 100644 index 0000000000..97bdca8995 --- /dev/null +++ b/android/src/com/mapswithme/maps/widget/placepage/NearbyAdapter.java @@ -0,0 +1,134 @@ +package com.mapswithme.maps.widget.placepage; + +import android.content.res.Resources; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.mapswithme.maps.R; +import com.mapswithme.util.ThemeUtils; + +import java.util.ArrayList; +import java.util.List; + +class NearbyAdapter extends BaseAdapter +{ + @NonNull + private List<SponsoredHotel.NearbyObject> mItems = new ArrayList<>(); + @Nullable + private final OnItemClickListener mListener; + + NearbyAdapter(@Nullable OnItemClickListener listener) + { + mListener = listener; + } + + interface OnItemClickListener + { + void onItemClick(@NonNull SponsoredHotel.NearbyObject item); + } + + @Override + public int getCount() + { + return mItems.size(); + } + + @Override + public Object getItem(int position) + { + return mItems.get(position); + } + + @Override + public long getItemId(int position) + { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) + { + ViewHolder holder; + if (convertView == null) + { + convertView = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_nearby, parent, false); + holder = new ViewHolder(convertView, mListener); + convertView.setTag(holder); + } else + { + holder = (ViewHolder) convertView.getTag(); + } + + holder.bind(mItems.get(position)); + + return convertView; + } + + public void setItems(@NonNull List<SponsoredHotel.NearbyObject> items) + { + this.mItems = items; + notifyDataSetChanged(); + } + + private static class ViewHolder implements View.OnClickListener + { + @Nullable + final OnItemClickListener mListener; + @NonNull + ImageView mIcon; + @NonNull + TextView mTitle; + @NonNull + TextView mType; + @NonNull + TextView mDistance; + @Nullable + SponsoredHotel.NearbyObject mItem; + + public ViewHolder(View view, @Nullable OnItemClickListener listener) + { + mListener = listener; + mIcon = (ImageView) view.findViewById(R.id.iv__icon); + mTitle = (TextView) view.findViewById(R.id.tv__title); + mType = (TextView) view.findViewById(R.id.tv__type); + mDistance = (TextView) view.findViewById(R.id.tv__distance); + view.setOnClickListener(this); + } + + @Override + public void onClick(View v) + { + if (mListener != null && mItem != null) + mListener.onItemClick(mItem); + } + + public void bind(@NonNull SponsoredHotel.NearbyObject item) + { + mItem = item; + String packageName = mType.getContext().getPackageName(); + final boolean isNightTheme = ThemeUtils.isNightTheme(); + Resources resources = mType.getResources(); + int categoryRes = resources.getIdentifier(item.getCategory(), "string", packageName); + if (categoryRes == 0) + throw new IllegalStateException("Can't get string resource id for category:" + item.getCategory()); + + String iconId = "ic_category_" + item.getCategory(); + if (isNightTheme) + iconId = iconId + "_night"; + int iconRes = resources.getIdentifier(iconId, "drawable", packageName); + if (iconRes == 0) + throw new IllegalStateException("Can't get icon resource id:" + iconId); + mIcon.setImageResource(iconRes); + mTitle.setText(item.getTitle()); + mType.setText(categoryRes); + mDistance.setText(item.getDistance()); + } + } +} diff --git a/android/src/com/mapswithme/maps/widget/placepage/PlacePageView.java b/android/src/com/mapswithme/maps/widget/placepage/PlacePageView.java index bd14f2a079..7a679cd25e 100644 --- a/android/src/com/mapswithme/maps/widget/placepage/PlacePageView.java +++ b/android/src/com/mapswithme/maps/widget/placepage/PlacePageView.java @@ -12,6 +12,9 @@ import android.os.Build; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.text.SpannableStringBuilder; import android.text.Spanned; @@ -24,6 +27,7 @@ import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.widget.GridView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.PopupMenu; @@ -31,15 +35,6 @@ import android.widget.RelativeLayout; import android.widget.ScrollView; import android.widget.TextView; -import java.text.NumberFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Currency; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - import com.mapswithme.maps.Framework; import com.mapswithme.maps.MwmActivity; import com.mapswithme.maps.MwmApplication; @@ -57,12 +52,18 @@ import com.mapswithme.maps.editor.Editor; import com.mapswithme.maps.editor.OpeningHours; import com.mapswithme.maps.editor.data.TimeFormatUtils; import com.mapswithme.maps.editor.data.Timetable; +import com.mapswithme.maps.gallery.FullScreenGalleryActivity; +import com.mapswithme.maps.gallery.GalleryActivity; import com.mapswithme.maps.location.LocationHelper; +import com.mapswithme.maps.review.ReviewActivity; import com.mapswithme.maps.routing.RoutingController; import com.mapswithme.maps.widget.ArrowView; import com.mapswithme.maps.widget.BaseShadowController; +import com.mapswithme.maps.widget.LineCountTextView; import com.mapswithme.maps.widget.ObservableScrollView; import com.mapswithme.maps.widget.ScrollViewShadowController; +import com.mapswithme.maps.widget.recycler.DividerItemDecoration; +import com.mapswithme.maps.widget.recycler.RecyclerClickListener; import com.mapswithme.util.Graphics; import com.mapswithme.util.StringUtils; import com.mapswithme.util.ThemeUtils; @@ -73,14 +74,30 @@ import com.mapswithme.util.sharing.ShareOption; import com.mapswithme.util.statistics.AlohaHelper; import com.mapswithme.util.statistics.Statistics; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Currency; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; public class PlacePageView extends RelativeLayout - implements View.OnClickListener, - View.OnLongClickListener, - SponsoredHotel.OnPriceReceivedListener + implements View.OnClickListener, + View.OnLongClickListener, + SponsoredHotel.OnPriceReceivedListener, + SponsoredHotel.OnInfoReceivedListener, + LineCountTextView.OnLineCountCalculatedListener, + RecyclerClickListener, + NearbyAdapter.OnItemClickListener { private static final String PREF_USE_DMS = "use_dms"; +//TODO: remove this after booking_api.cpp will be done + private static final boolean USE_OLD_BOOKING = true; + private boolean mIsDocked; private boolean mIsFloating; @@ -118,7 +135,6 @@ public class PlacePageView extends RelativeLayout private View mEditPlace; private View mAddOrganisation; private View mAddPlace; - private View mMoreInfo; // Bookmark private View mBookmarkFrame; private TextView mBookmarkNote; @@ -126,6 +142,20 @@ public class PlacePageView extends RelativeLayout // Place page buttons private PlacePageButtons mButtons; private ImageView mBookmarkButtonIcon; + // Hotel + private View mHotelDescription; + private LineCountTextView mTvHotelDescription; + private View mHotelMoreDescription; + private View mHotelFacilities; + private View mHotelMoreFacilities; + private View mHotelGallery; + private RecyclerView mRvHotelGallery; + private View mHotelNearby; + private View mHotelReview; + private TextView mHotelRating; + private TextView mHotelRatingBase; +//TODO: remove this after booking_api.cpp will be done + private View mHotelMore; // Animations private BaseShadowController mShadowController; @@ -136,6 +166,14 @@ public class PlacePageView extends RelativeLayout private SponsoredHotel mSponsoredHotel; private String mSponsoredHotelPrice; private boolean mIsLatLonDms; + @NonNull + private final FacilitiesAdapter mFacilitiesAdapter = new FacilitiesAdapter(); + @NonNull + private final GalleryAdapter mGalleryAdapter; + @NonNull + private final NearbyAdapter mNearbyAdapter = new NearbyAdapter(this); + @NonNull + private final ReviewAdapter mReviewAdapter = new ReviewAdapter(); // Downloader`s stuff private DownloaderStatusIcon mDownloaderIcon; @@ -198,12 +236,17 @@ public class PlacePageView extends RelativeLayout super(context, attrs); mIsLatLonDms = MwmApplication.prefs().getBoolean(PREF_USE_DMS, false); - + mGalleryAdapter = new GalleryAdapter(context); init(attrs, defStyleAttr); } public ViewGroup GetPreview() { return mPreview; } + public boolean isTouchGallery(@NonNull MotionEvent event) + { + return UiUtils.isViewTouched(event, mHotelGallery); + } + private void initViews() { LayoutInflater.from(getContext()).inflate(R.layout.place_page, this); @@ -257,8 +300,6 @@ public class PlacePageView extends RelativeLayout mAddOrganisation.setOnClickListener(this); mAddPlace = mDetails.findViewById(R.id.ll__place_add); mAddPlace.setOnClickListener(this); - mMoreInfo = mDetails.findViewById(R.id.ll__more); - mMoreInfo.setOnClickListener(this); latlon.setOnLongClickListener(this); address.setOnLongClickListener(this); mPhone.setOnLongClickListener(this); @@ -274,6 +315,16 @@ public class PlacePageView extends RelativeLayout ViewGroup ppButtons = (ViewGroup) findViewById(R.id.pp__buttons); +// TODO: remove this after booking_api.cpp will be done + mHotelMore = findViewById(R.id.ll__more); + mHotelMore.setOnClickListener(this); + + initHotelDescriptionView(); + initHotelFacilitiesView(); + initHotelGalleryView(); + initHotelNearbyView(); + initHotelRatingView(); + mButtons = new PlacePageButtons(this, ppButtons, new PlacePageButtons.ItemListener() { @Override @@ -283,21 +334,21 @@ public class PlacePageView extends RelativeLayout switch (item) { - case BOOKING: - frame.setBackgroundResource(R.drawable.button_booking); - color = Color.WHITE; - break; - - case BOOKMARK: - mBookmarkButtonIcon = icon; - updateButtons(); - color = ThemeUtils.getColor(getContext(), R.attr.iconTint); - break; - - default: - color = ThemeUtils.getColor(getContext(), R.attr.iconTint); - icon.setColorFilter(color); - break; + case BOOKING: + frame.setBackgroundResource(R.drawable.button_booking); + color = Color.WHITE; + break; + + case BOOKMARK: + mBookmarkButtonIcon = icon; + updateButtons(); + color = ThemeUtils.getColor(getContext(), R.attr.iconTint); + break; + + default: + color = ThemeUtils.getColor(getContext(), R.attr.iconTint); + icon.setColorFilter(color); + break; } title.setTextColor(color); @@ -338,62 +389,63 @@ public class PlacePageView extends RelativeLayout hide(); break; - case ROUTE_TO: - if (RoutingController.get().isPlanning()) - { - if (RoutingController.get().setEndPoint(mMapObject)) - hide(); - } - else - { - getActivity().startLocationToPoint(Statistics.EventName.PP_ROUTE, AlohaHelper.PP_ROUTE, getMapObject()); - } - break; - - case BOOKING: - onBookingClick(true /* book */); - break; + case ROUTE_TO: + if (RoutingController.get().isPlanning()) + { + if (RoutingController.get().setEndPoint(mMapObject)) + hide(); + } + else + { + getActivity().startLocationToPoint(Statistics.EventName.PP_ROUTE, AlohaHelper.PP_ROUTE, getMapObject()); + } + break; + + case BOOKING: + onBookingClick(true /* book */); + break; } } }); mDownloaderIcon = new DownloaderStatusIcon(mPreview.findViewById(R.id.downloader_status_frame)) - .setOnIconClickListener(new OnClickListener() - { - @Override - public void onClick(View v) - { - MapManager.warn3gAndDownload(getActivity(), mCurrentCountry.id, new Runnable() - { - @Override - public void run() - { - Statistics.INSTANCE.trackEvent(Statistics.EventName.DOWNLOADER_ACTION, - Statistics.params() - .add(Statistics.EventParam.ACTION, "download") - .add(Statistics.EventParam.FROM, "placepage") - .add("is_auto", "false") - .add("scenario", (mCurrentCountry.isExpandable() ? "download_group" - : "download"))); - } - }); - } - }).setOnCancelClickListener(new OnClickListener() - { - @Override - public void onClick(View v) - { - MapManager.nativeCancel(mCurrentCountry.id); - Statistics.INSTANCE.trackEvent(Statistics.EventName.DOWNLOADER_CANCEL, - Statistics.params().add(Statistics.EventParam.FROM, "placepage")); - } - }); + .setOnIconClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + MapManager.warn3gAndDownload(getActivity(), mCurrentCountry.id, new Runnable() + { + @Override + public void run() + { + Statistics.INSTANCE.trackEvent(Statistics.EventName.DOWNLOADER_ACTION, + Statistics.params() + .add(Statistics.EventParam.ACTION, "download") + .add(Statistics.EventParam.FROM, "placepage") + .add("is_auto", "false") + .add("scenario", (mCurrentCountry.isExpandable() ? "download_group" + : "download"))); + } + }); + } + }).setOnCancelClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + MapManager.nativeCancel(mCurrentCountry.id); + Statistics.INSTANCE.trackEvent(Statistics.EventName.DOWNLOADER_CANCEL, + Statistics.params() + .add(Statistics.EventParam.FROM, "placepage")); + } + }); mDownloaderInfo = (TextView) mPreview.findViewById(R.id.tv__downloader_details); mShadowController = new ScrollViewShadowController((ObservableScrollView) mDetails) - .addBottomShadow() - .attach(); + .addBottomShadow() + .attach(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) setElevation(UiUtils.dimen(R.dimen.placepage_elevation)); @@ -401,11 +453,62 @@ public class PlacePageView extends RelativeLayout if (UiUtils.isLandscape(getContext())) mDetails.setBackgroundResource(0); - SponsoredHotel.setListener(this); + SponsoredHotel.setPriceListener(this); + SponsoredHotel.setInfoListener(this); + } + + private void initHotelRatingView() + { + mHotelReview = findViewById(R.id.ll__place_hotel_rating); + GridView gvHotelReview = (GridView) findViewById(R.id.gv__place_hotel_review); + gvHotelReview.setAdapter(mReviewAdapter); + mHotelRating = (TextView) findViewById(R.id.tv__place_hotel_rating); + mHotelRatingBase = (TextView) findViewById(R.id.tv__place_hotel_rating_base); + View hotelMoreReviews = findViewById(R.id.tv__place_hotel_reviews_more); + hotelMoreReviews.setOnClickListener(this); + } + + private void initHotelNearbyView() + { + mHotelNearby = findViewById(R.id.ll__place_hotel_nearby); + GridView gvHotelNearby = (GridView) findViewById(R.id.gv__place_hotel_nearby); + gvHotelNearby.setAdapter(mNearbyAdapter); + } + + private void initHotelGalleryView() + { + mHotelGallery = findViewById(R.id.ll__place_hotel_gallery); + mRvHotelGallery = (RecyclerView) findViewById( + R.id.rv__place_hotel_gallery); + mRvHotelGallery.setLayoutManager(new LinearLayoutManager(getContext(), + LinearLayoutManager.HORIZONTAL, false)); + mRvHotelGallery.addItemDecoration(new DividerItemDecoration(ContextCompat.getDrawable(getContext(), + R.drawable.divider_transparent))); + mGalleryAdapter.setListener(this); + mRvHotelGallery.setAdapter(mGalleryAdapter); + } + + private void initHotelFacilitiesView() + { + mHotelFacilities = findViewById(R.id.ll__place_hotel_facilities); + GridView gvHotelFacilities = (GridView) findViewById(R.id.gv__place_hotel_facilities); + mHotelMoreFacilities = findViewById(R.id.tv__place_hotel_facilities_more); + gvHotelFacilities.setAdapter(mFacilitiesAdapter); + mHotelMoreFacilities.setOnClickListener(this); + } + + private void initHotelDescriptionView() + { + mHotelDescription = findViewById(R.id.ll__place_hotel_description); + mTvHotelDescription = (LineCountTextView) findViewById(R.id.tv__place_hotel_details); + mHotelMoreDescription = findViewById(R.id.tv__place_hotel_more); + mTvHotelDescription.setListener(this); + mHotelMoreDescription.setOnClickListener(this); } @Override - public void onPriceReceived(String id, String price, String currencyCode) + public void onPriceReceived(@NonNull String id, @NonNull String price, + @NonNull String currencyCode) { if (mSponsoredHotel == null || !TextUtils.equals(id, mSponsoredHotel.getId())) return; @@ -424,6 +527,110 @@ public class PlacePageView extends RelativeLayout refreshPreview(); } + @Override + public void onInfoReceived(@NonNull String id, @NonNull SponsoredHotel.HotelInfo info) + { + if (mSponsoredHotel == null || !TextUtils.equals(id, mSponsoredHotel.getId())) + return; + + updateHotelDetails(info); + updateHotelFacilities(info); + updateHotelGallery(info); + updateHotelNearby(info); + updateHotelRating(info); + } + + private void updateHotelRating(@NonNull SponsoredHotel.HotelInfo info) + { + if (info.mReviews == null || info.mReviews.length == 0) + { + UiUtils.hide(mHotelReview); + } + else + { + UiUtils.show(mHotelReview); + mReviewAdapter.setItems(new ArrayList<>(Arrays.asList(info.mReviews))); + mHotelRating.setText(mSponsoredHotel.mRating); + mHotelRatingBase.setText(getResources().getQuantityString(R.plurals.place_page_booking_rating_base, + info.mReviews.length, info.mReviews.length)); + } + } + + private void updateHotelNearby(@NonNull SponsoredHotel.HotelInfo info) + { + if (info.mNearby == null || info.mNearby.length == 0) + { + UiUtils.hide(mHotelNearby); + } + else + { + UiUtils.show(mHotelNearby); + mNearbyAdapter.setItems(Arrays.asList(info.mNearby)); + } + } + + private void updateHotelGallery(@NonNull SponsoredHotel.HotelInfo info) + { + if (info.mPhotos == null || info.mPhotos.length == 0) + { + UiUtils.hide(mHotelGallery); + } + else + { + UiUtils.show(mHotelGallery); + mGalleryAdapter.setItems(new ArrayList<>(Arrays.asList(info.mPhotos))); + mRvHotelGallery.scrollToPosition(0); + } + } + + private void updateHotelFacilities(@NonNull SponsoredHotel.HotelInfo info) + { + if (info.mFacilities == null || info.mFacilities.length == 0) + { + UiUtils.hide(mHotelFacilities); + } + else + { + UiUtils.show(mHotelFacilities); + mFacilitiesAdapter.setShowAll(false); + mFacilitiesAdapter.setItems(Arrays.asList(info.mFacilities)); + mHotelMoreFacilities.setVisibility(info.mFacilities.length > FacilitiesAdapter.MAX_COUNT + ? VISIBLE : GONE); + } + } + + private void updateHotelDetails(@NonNull SponsoredHotel.HotelInfo info) + { + mTvHotelDescription.setMaxLines(getResources().getInteger(R.integer.pp_hotel_description_lines)); + refreshMetadataOrHide(info.mDescription, mHotelDescription, mTvHotelDescription); + mHotelMoreDescription.setVisibility(GONE); + } + + @Override + public void onLineCountCalculated(boolean grater) + { + UiUtils.showIf(grater, mHotelMoreDescription); + } + + @Override + public void onItemClick(View v, int position) + { + if (position == GalleryAdapter.MAX_COUNT - 1) + { + GalleryActivity.start(getContext(), mGalleryAdapter.getItems(), mMapObject.getTitle()); + } + else + { + FullScreenGalleryActivity.start(getContext(), mGalleryAdapter.getItems(), position); + } + } + + @Override + public void onItemClick(@NonNull SponsoredHotel.NearbyObject item) + { +// TODO go to selected object on map + } + private void onBookingClick(final boolean book) { // TODO (trashkalmar): Set correct text @@ -453,7 +660,7 @@ public class PlacePageView extends RelativeLayout try { - followUrl(book ? info.urlBook : info.urlDescription); + followUrl(book ? info.mUrlBook : info.mUrlDescription); } catch (ActivityNotFoundException e) { AlohaHelper.logException(e); @@ -482,7 +689,7 @@ public class PlacePageView extends RelativeLayout public void restore() { // if (mMapObject != null) - // FIXME query map object again + // FIXME query map object again } @Override @@ -539,7 +746,7 @@ public class PlacePageView extends RelativeLayout /** * @param mapObject new MapObject - * @param force if true, new object'll be set without comparison with the old one + * @param force if true, new object'll be set without comparison with the old one */ public void setMapObject(MapObject mapObject, boolean force) { @@ -555,10 +762,14 @@ public class PlacePageView extends RelativeLayout if (mSponsoredHotel != null) { mSponsoredHotel.updateId(mMapObject); - mSponsoredHotelPrice = mSponsoredHotel.price; + mSponsoredHotelPrice = mSponsoredHotel.mPrice; - Currency currency = Currency.getInstance(Locale.getDefault()); + Locale locale = Locale.getDefault(); + Currency currency = Currency.getInstance(locale); SponsoredHotel.requestPrice(mSponsoredHotel.getId(), currency.getCurrencyCode()); +// TODO: remove this after booking_api.cpp will be done + if (!USE_OLD_BOOKING) + SponsoredHotel.requestInfo(mSponsoredHotel.getId(), locale.toString()); } String country = MapManager.nativeGetSelectedCountry(); @@ -580,27 +791,27 @@ public class PlacePageView extends RelativeLayout switch (mMapObject.getMapObjectType()) { - case MapObject.BOOKMARK: - refreshDistanceToObject(loc); - showBookmarkDetails(); - setButtons(false, true); - break; - case MapObject.POI: - case MapObject.SEARCH: - refreshDistanceToObject(loc); - hideBookmarkDetails(); - setButtons(false, true); - break; - case MapObject.API_POINT: - refreshDistanceToObject(loc); - hideBookmarkDetails(); - setButtons(true, true); - break; - case MapObject.MY_POSITION: - refreshMyPosition(loc); - hideBookmarkDetails(); - setButtons(false, false); - break; + case MapObject.BOOKMARK: + refreshDistanceToObject(loc); + showBookmarkDetails(); + setButtons(false, true); + break; + case MapObject.POI: + case MapObject.SEARCH: + refreshDistanceToObject(loc); + hideBookmarkDetails(); + setButtons(false, true); + break; + case MapObject.API_POINT: + refreshDistanceToObject(loc); + hideBookmarkDetails(); + setButtons(true, true); + break; + case MapObject.MY_POSITION: + refreshMyPosition(loc); + hideBookmarkDetails(); + setButtons(false, false); + break; } UiThread.runLater(new Runnable() @@ -645,7 +856,7 @@ public class PlacePageView extends RelativeLayout UiUtils.showIf(sponsored, mHotelInfo); if (sponsored) { - mTvHotelRating.setText(mSponsoredHotel.rating); + mTvHotelRating.setText(mSponsoredHotel.mRating); UiUtils.setTextAndHideIfEmpty(mTvHotelPrice, mSponsoredHotelPrice); } } @@ -658,9 +869,21 @@ public class PlacePageView extends RelativeLayout { final String website = mMapObject.getMetadata(Metadata.MetadataType.FMD_WEBSITE); refreshMetadataOrHide(TextUtils.isEmpty(website) ? mMapObject.getMetadata(Metadata.MetadataType.FMD_URL) : website, mWebsite, mTvWebsite); + UiUtils.hide(mHotelDescription); + UiUtils.hide(mHotelFacilities); + UiUtils.hide(mHotelGallery); + UiUtils.hide(mHotelNearby); + UiUtils.hide(mHotelReview); +// TODO: remove this after booking_api.cpp will be done + UiUtils.hide(mHotelMore); } else + { UiUtils.hide(mWebsite); +// TODO: remove this after booking_api.cpp will be done + if (!USE_OLD_BOOKING) + UiUtils.hide(mHotelMore); + } refreshMetadataOrHide(mMapObject.getMetadata(Metadata.MetadataType.FMD_PHONE_NUMBER), mPhone, mTvPhone); refreshMetadataOrHide(mMapObject.getMetadata(Metadata.MetadataType.FMD_EMAIL), mEmail, mTvEmail); @@ -683,8 +906,6 @@ public class PlacePageView extends RelativeLayout UiUtils.showIf(Editor.nativeShouldShowAddBusiness(), mAddOrganisation); UiUtils.showIf(Editor.nativeShouldShowAddPlace(), mAddPlace); } - - UiUtils.showIf(mSponsoredHotel != null, mMoreInfo); } private void refreshOpeningHours() @@ -824,8 +1045,11 @@ public class PlacePageView extends RelativeLayout return; mTvDistance.setVisibility(View.VISIBLE); - DistanceAndAzimut distanceAndAzimuth = Framework.nativeGetDistanceAndAzimuthFromLatLon(mMapObject.getLat(), mMapObject.getLon(), - l.getLatitude(), l.getLongitude(), 0.0); + DistanceAndAzimut distanceAndAzimuth = Framework.nativeGetDistanceAndAzimuthFromLatLon(mMapObject + .getLat(), mMapObject + .getLon(), + l.getLatitude(), l + .getLongitude(), 0.0); mTvDistance.setText(distanceAndAzimuth.getDistance()); } @@ -853,16 +1077,18 @@ public class PlacePageView extends RelativeLayout public void refreshAzimuth(double northAzimuth) { if (isHidden() || - mMapObject == null || - MapObject.isOfType(MapObject.MY_POSITION, mMapObject)) + mMapObject == null || + MapObject.isOfType(MapObject.MY_POSITION, mMapObject)) return; final Location location = LocationHelper.INSTANCE.getSavedLocation(); if (location == null) return; - final double azimuth = Framework.nativeGetDistanceAndAzimuthFromLatLon(mMapObject.getLat(), mMapObject.getLon(), - location.getLatitude(), location.getLongitude(), + final double azimuth = Framework.nativeGetDistanceAndAzimuthFromLatLon(mMapObject.getLat(), mMapObject + .getLon(), + location.getLatitude(), location + .getLongitude(), northAzimuth) .getAzimuth(); if (azimuth >= 0) @@ -880,7 +1106,8 @@ public class PlacePageView extends RelativeLayout private void addOrganisation() { Statistics.INSTANCE.trackEvent(Statistics.EventName.EDITOR_ADD_CLICK, - Statistics.params().add(Statistics.EventParam.FROM, "placepage")); + Statistics.params() + .add(Statistics.EventParam.FROM, "placepage")); getActivity().showPositionChooser(true, false); } @@ -895,56 +1122,69 @@ public class PlacePageView extends RelativeLayout { switch (v.getId()) { - case R.id.ll__place_editor: - getActivity().showEditor(); - break; - case R.id.ll__add_organisation: - addOrganisation(); - break; - case R.id.ll__place_add: - addPlace(); - break; - case R.id.ll__more: - onBookingClick(false /* book */); - break; - case R.id.ll__place_latlon: - mIsLatLonDms = !mIsLatLonDms; - MwmApplication.prefs().edit().putBoolean(PREF_USE_DMS, mIsLatLonDms).commit(); - refreshLatLon(); - break; - case R.id.ll__place_phone: - Intent intent = new Intent(Intent.ACTION_DIAL); - intent.setData(Uri.parse("tel:" + mTvPhone.getText())); - try - { + case R.id.ll__place_editor: + getActivity().showEditor(); + break; + case R.id.ll__add_organisation: + addOrganisation(); + break; + case R.id.ll__place_add: + addPlace(); + break; + case R.id.ll__more: + onBookingClick(false /* book */); + break; + case R.id.ll__place_latlon: + mIsLatLonDms = !mIsLatLonDms; + MwmApplication.prefs().edit().putBoolean(PREF_USE_DMS, mIsLatLonDms).commit(); + refreshLatLon(); + break; + case R.id.ll__place_phone: + Intent intent = new Intent(Intent.ACTION_DIAL); + intent.setData(Uri.parse("tel:" + mTvPhone.getText())); + try + { + getContext().startActivity(intent); + } catch (ActivityNotFoundException e) + { + AlohaHelper.logException(e); + } + break; + case R.id.ll__place_website: + followUrl(mTvWebsite.getText().toString()); + break; + case R.id.ll__place_wiki: + // TODO: Refactor and use separate getters for Wiki and all other PP meta info too. + followUrl(mMapObject.getMetadata(Metadata.MetadataType.FMD_WIKIPEDIA)); + break; + case R.id.direction_frame: + Statistics.INSTANCE.trackEvent(Statistics.EventName.PP_DIRECTION_ARROW); + AlohaHelper.logClick(AlohaHelper.PP_DIRECTION_ARROW); + showBigDirection(); + break; + case R.id.ll__place_email: + intent = new Intent(Intent.ACTION_SENDTO); + intent.setData(Utils.buildMailUri(mTvEmail.getText().toString(), "", "")); getContext().startActivity(intent); - } catch (ActivityNotFoundException e) - { - AlohaHelper.logException(e); - } - break; - case R.id.ll__place_website: - followUrl(mTvWebsite.getText().toString()); - break; - case R.id.ll__place_wiki: - // TODO: Refactor and use separate getters for Wiki and all other PP meta info too. - followUrl(mMapObject.getMetadata(Metadata.MetadataType.FMD_WIKIPEDIA)); - break; - case R.id.direction_frame: - Statistics.INSTANCE.trackEvent(Statistics.EventName.PP_DIRECTION_ARROW); - AlohaHelper.logClick(AlohaHelper.PP_DIRECTION_ARROW); - showBigDirection(); - break; - case R.id.ll__place_email: - intent = new Intent(Intent.ACTION_SENDTO); - intent.setData(Utils.buildMailUri(mTvEmail.getText().toString(), "", "")); - getContext().startActivity(intent); - break; - case R.id.tv__bookmark_edit: - Bookmark bookmark = (Bookmark) mMapObject; - EditBookmarkFragment.editBookmark(bookmark.getCategoryId(), bookmark.getBookmarkId(), - getActivity(), getActivity().getSupportFragmentManager()); - break; + break; + case R.id.tv__bookmark_edit: + Bookmark bookmark = (Bookmark) mMapObject; + EditBookmarkFragment.editBookmark(bookmark.getCategoryId(), bookmark.getBookmarkId(), + getActivity(), getActivity().getSupportFragmentManager()); + break; + case R.id.tv__place_hotel_more: + UiUtils.hide(mHotelMoreDescription); + mTvHotelDescription.setMaxLines(Integer.MAX_VALUE); + break; + case R.id.tv__place_hotel_facilities_more: + UiUtils.hide(mHotelMoreFacilities); + mFacilitiesAdapter.setShowAll(true); + break; + case R.id.tv__place_hotel_reviews_more: + ReviewActivity.start(getContext(), mReviewAdapter.getItems(), mMapObject.getTitle(), + mSponsoredHotel.mRating, mReviewAdapter.getItems() + .size(), mSponsoredHotel.mUrlBook); + break; } } @@ -976,7 +1216,8 @@ public class PlacePageView extends RelativeLayout private void showBigDirection() { - final DirectionFragment fragment = (DirectionFragment) Fragment.instantiate(getActivity(), DirectionFragment.class.getName(), null); + final DirectionFragment fragment = (DirectionFragment) Fragment.instantiate(getActivity(), DirectionFragment.class + .getName(), null); fragment.setMapObject(mMapObject); fragment.show(getActivity().getSupportFragmentManager(), null); } @@ -993,30 +1234,30 @@ public class PlacePageView extends RelativeLayout final List<String> items = new ArrayList<>(); switch (v.getId()) { - case R.id.ll__place_latlon: - final double lat = mMapObject.getLat(); - final double lon = mMapObject.getLon(); - items.add(Framework.nativeFormatLatLon(lat, lon, false)); - items.add(Framework.nativeFormatLatLon(lat, lon, true)); - break; - case R.id.ll__place_website: - items.add(mTvWebsite.getText().toString()); - break; - case R.id.ll__place_email: - items.add(mTvEmail.getText().toString()); - break; - case R.id.ll__place_phone: - items.add(mTvPhone.getText().toString()); - break; - case R.id.ll__place_schedule: - items.add(mFullOpeningHours.getText().toString()); - break; - case R.id.ll__place_operator: - items.add(mTvOperator.getText().toString()); - break; - case R.id.ll__place_wiki: - items.add(mMapObject.getMetadata(Metadata.MetadataType.FMD_WIKIPEDIA)); - break; + case R.id.ll__place_latlon: + final double lat = mMapObject.getLat(); + final double lon = mMapObject.getLon(); + items.add(Framework.nativeFormatLatLon(lat, lon, false)); + items.add(Framework.nativeFormatLatLon(lat, lon, true)); + break; + case R.id.ll__place_website: + items.add(mTvWebsite.getText().toString()); + break; + case R.id.ll__place_email: + items.add(mTvEmail.getText().toString()); + break; + case R.id.ll__place_phone: + items.add(mTvPhone.getText().toString()); + break; + case R.id.ll__place_schedule: + items.add(mFullOpeningHours.getText().toString()); + break; + case R.id.ll__place_operator: + items.add(mTvOperator.getText().toString()); + break; + case R.id.ll__place_wiki: + items.add(mMapObject.getMetadata(Metadata.MetadataType.FMD_WIKIPEDIA)); + break; } final String copyText = getResources().getString(android.R.string.copy); diff --git a/android/src/com/mapswithme/maps/widget/placepage/ReviewAdapter.java b/android/src/com/mapswithme/maps/widget/placepage/ReviewAdapter.java new file mode 100644 index 0000000000..901acbe98a --- /dev/null +++ b/android/src/com/mapswithme/maps/widget/placepage/ReviewAdapter.java @@ -0,0 +1,136 @@ +package com.mapswithme.maps.widget.placepage; + +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import com.mapswithme.maps.R; +import com.mapswithme.maps.review.Review; +import com.mapswithme.util.UiUtils; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Locale; + +class ReviewAdapter extends BaseAdapter +{ + private static final int MAX_COUNT = 3; + + @NonNull + private ArrayList<Review> mItems = new ArrayList<>(); + + @Override + public int getCount() + { + return Math.min(mItems.size(), MAX_COUNT); + } + + @Override + public Object getItem(int position) + { + return mItems.get(position); + } + + @Override + public long getItemId(int position) + { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) + { + ViewHolder holder; + if (convertView == null) + { + convertView = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_comment, parent, false); + holder = new ViewHolder(convertView); + convertView.setTag(holder); + } + else + { + holder = (ViewHolder) convertView.getTag(); + } + + holder.bind(mItems.get(position), position > 0); + + return convertView; + } + + public void setItems(@NonNull ArrayList<Review> items) + { + this.mItems = items; + notifyDataSetChanged(); + } + + @NonNull + public ArrayList<Review> getItems() + { + return mItems; + } + + private static class ViewHolder + { + @NonNull + final View mDivider; + @NonNull + final TextView mUserName; + @NonNull + final TextView mCommentDate; + @NonNull + final TextView mRating; + @NonNull + final View mPositiveReview; + @NonNull + final TextView mTvPositiveReview; + @NonNull + final View mNegativeReview; + @NonNull + final TextView mTvNegativeReview; + + public ViewHolder(View view) + { + mDivider = view.findViewById(R.id.v__divider); + mUserName = (TextView) view.findViewById(R.id.tv__user_name); + mCommentDate = (TextView) view.findViewById(R.id.tv__comment_date); + mRating = (TextView) view.findViewById(R.id.tv__user_rating); + mPositiveReview = view.findViewById(R.id.ll__positive_review); + mTvPositiveReview = (TextView) view.findViewById(R.id.tv__positive_review); + mNegativeReview = view.findViewById(R.id.ll__negative_review); + mTvNegativeReview = (TextView) view.findViewById(R.id.tv__negative_review); + } + + public void bind(Review item, boolean isShowDivider) + { + UiUtils.showIf(isShowDivider, mDivider); + mUserName.setText(item.getAuthor()); + Date date = new Date(item.getDate()); + mCommentDate.setText(DateFormat.getMediumDateFormat(mCommentDate.getContext()).format(date)); + mRating.setText(String.format(Locale.getDefault(), "%.1f", item.getRating())); + if (TextUtils.isEmpty(item.getReviewPositive())) + { + UiUtils.hide(mPositiveReview); + } + else + { + UiUtils.show(mPositiveReview); + mTvPositiveReview.setText(item.getReviewPositive()); + } + if (TextUtils.isEmpty(item.getReviewNegative())) + { + UiUtils.hide(mNegativeReview); + } + else + { + UiUtils.show(mNegativeReview); + mTvNegativeReview.setText(item.getReviewNegative()); + } + } + } +} diff --git a/android/src/com/mapswithme/maps/widget/placepage/SponsoredHotel.java b/android/src/com/mapswithme/maps/widget/placepage/SponsoredHotel.java index 2b97298c41..1ef4c46d28 100644 --- a/android/src/com/mapswithme/maps/widget/placepage/SponsoredHotel.java +++ b/android/src/com/mapswithme/maps/widget/placepage/SponsoredHotel.java @@ -1,53 +1,194 @@ package com.mapswithme.maps.widget.placepage; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.text.TextUtils; +import com.mapswithme.maps.bookmarks.data.MapObject; +import com.mapswithme.maps.bookmarks.data.Metadata; +import com.mapswithme.maps.gallery.Image; +import com.mapswithme.maps.review.Review; + import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; -import com.mapswithme.maps.bookmarks.data.MapObject; -import com.mapswithme.maps.bookmarks.data.Metadata; - @UiThread public final class SponsoredHotel { private static class Price { - final String price; - final String currency; + @NonNull + final String mPrice; + @NonNull + final String mCurrency; + + private Price(@NonNull String price, @NonNull String currency) + { + mPrice = price; + mCurrency = currency; + } + } + + static class FacilityType + { + @NonNull + private final String mKey; + @NonNull + private final String mName; + + public FacilityType(@NonNull String key, @NonNull String name) + { + mKey = key; + mName = name; + } + + @NonNull + public String getKey() + { + return mKey; + } + + @NonNull + public String getName() + { + return mName; + } + } + + static class NearbyObject + { + @NonNull + private final String mCategory; + @NonNull + private final String mTitle; + @NonNull + private final String mDistance; + private final double mLatitude; + private final double mLongitude; + + public NearbyObject(@NonNull String category, @NonNull String title, + @NonNull String distance, double lat, double lon) + { + mCategory = category; + mTitle = title; + mDistance = distance; + mLatitude = lat; + mLongitude = lon; + } + + @NonNull + public String getCategory() + { + return mCategory; + } + + @NonNull + public String getTitle() + { + return mTitle; + } + + @NonNull + public String getDistance() + { + return mDistance; + } + + public double getLatitude() + { + return mLatitude; + } + + public double getLongitude() + { + return mLongitude; + } + } + + static class HotelInfo + { + @Nullable + final String mDescription; + @Nullable + final Image[] mPhotos; + @Nullable + final FacilityType[] mFacilities; + @Nullable + final Review[] mReviews; + @Nullable + final NearbyObject[] mNearby; - private Price(String price, String currency) + public HotelInfo(@Nullable String description, @Nullable Image[] photos, + @Nullable FacilityType[] facilities, @Nullable Review[] reviews, + @Nullable NearbyObject[] nearby) { - this.price = price; - this.currency = currency; + mDescription = description; + mPhotos = photos; + mFacilities = facilities; + mReviews = reviews; + mNearby = nearby; } } interface OnPriceReceivedListener { - void onPriceReceived(String id, String price, String currency); + /** + * This method is called from the native core on the UI thread + * when the Hotel price will be obtained + * + * @param id A hotel id + * @param price A price + * @param currency A price currency + */ + @UiThread + void onPriceReceived(@NonNull String id, @NonNull String price, @NonNull String currency); + } + + interface OnInfoReceivedListener + { + /** + * This method is called from the native core on the UI thread + * when the Hotel information will be obtained + * + * @param id A hotel id + * @param info A hotel info + */ + @UiThread + void onInfoReceived(@NonNull String id, @NonNull HotelInfo info); } // Hotel ID -> Price + @NonNull private static final Map<String, Price> sPriceCache = new HashMap<>(); - private static WeakReference<OnPriceReceivedListener> sListener; + // Hotel ID -> Description + @NonNull + private static final Map<String, HotelInfo> sInfoCache = new HashMap<>(); + @NonNull + private static WeakReference<OnPriceReceivedListener> sPriceListener = new WeakReference<>(null); + @NonNull + private static WeakReference<OnInfoReceivedListener> sInfoListener = new WeakReference<>(null); + @Nullable private String mId; - final String rating; - final String price; - final String urlBook; - final String urlDescription; + @NonNull + final String mRating; + @NonNull + final String mPrice; + @NonNull + final String mUrlBook; + @NonNull + final String mUrlDescription; - public SponsoredHotel(String rating, String price, String urlBook, String urlDescription) + public SponsoredHotel(@NonNull String rating, @NonNull String price, @NonNull String urlBook, + @NonNull String urlDescription) { - this.rating = rating; - this.price = price; - this.urlBook = urlBook; - this.urlDescription = urlDescription; + mRating = rating; + mPrice = price; + mUrlBook = urlBook; + mUrlDescription = urlDescription; } void updateId(MapObject point) @@ -55,61 +196,107 @@ public final class SponsoredHotel mId = point.getMetadata(Metadata.MetadataType.FMD_SPONSORED_ID); } + @Nullable public String getId() { return mId; } + @NonNull public String getRating() { - return rating; + return mRating; } + @NonNull public String getPrice() { - return price; + return mPrice; } + @NonNull public String getUrlBook() { - return urlBook; + return mUrlBook; } + @NonNull public String getUrlDescription() { - return urlDescription; + return mUrlDescription; + } + + static void setPriceListener(@NonNull OnPriceReceivedListener listener) + { + sPriceListener = new WeakReference<>(listener); } - public static void setListener(OnPriceReceivedListener listener) + static void setInfoListener(@NonNull OnInfoReceivedListener listener) { - sListener = new WeakReference<>(listener); + sInfoListener = new WeakReference<>(listener); } + /** + * Make request to obtain hotel price information. + * This method also checks cache for requested hotel id + * and if cache exists - call {@link #onPriceReceived(String, String, String) onPriceReceived} immediately + * + * @param id A Hotel id + * @param currencyCode A user currency + */ static void requestPrice(String id, String currencyCode) { Price p = sPriceCache.get(id); if (p != null) - onPriceReceived(id, p.price, p.currency); + onPriceReceived(id, p.mPrice, p.mCurrency); nativeRequestPrice(id, currencyCode); } - @SuppressWarnings("unused") - private static void onPriceReceived(String id, String price, String currency) + /** + * Make request to obtain hotel information. + * This method also checks cache for requested hotel id + * and if cache exists - call {@link #onInfoReceived(String, HotelInfo) onInfoReceived} immediately + * + * @param id A Hotel id + * @param locale A user locale + */ + static void requestInfo(String id, String locale) + { + HotelInfo info = sInfoCache.get(id); + if (info != null) + onInfoReceived(id, info); + + nativeRequestInfo(id, locale); + } + + private static void onPriceReceived(@NonNull String id, @NonNull String price, + @NonNull String currency) { if (TextUtils.isEmpty(price)) return; sPriceCache.put(id, new Price(price, currency)); - OnPriceReceivedListener listener = sListener.get(); - if (listener == null) - sListener = null; - else + + OnPriceReceivedListener listener = sPriceListener.get(); + if (listener != null) listener.onPriceReceived(id, price, currency); } + private static void onInfoReceived(@NonNull String id, @NonNull HotelInfo info) + { + sInfoCache.put(id, info); + + OnInfoReceivedListener listener = sInfoListener.get(); + if (listener != null) + listener.onInfoReceived(id, info); + } + @Nullable public static native SponsoredHotel nativeGetCurrent(); - private static native void nativeRequestPrice(String id, String currencyCode); + + private static native void nativeRequestPrice(@NonNull String id, @NonNull String currencyCode); + + private static native void nativeRequestInfo(@NonNull String id, @NonNull String locale); } diff --git a/android/src/com/mapswithme/maps/widget/recycler/DividerItemDecoration.java b/android/src/com/mapswithme/maps/widget/recycler/DividerItemDecoration.java new file mode 100644 index 0000000000..ea3d6f0da1 --- /dev/null +++ b/android/src/com/mapswithme/maps/widget/recycler/DividerItemDecoration.java @@ -0,0 +1,131 @@ +package com.mapswithme.maps.widget.recycler; + +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +/** + * Adds interior dividers to a RecyclerView with a LinearLayoutManager or its + * subclass. + */ +public class DividerItemDecoration extends RecyclerView.ItemDecoration +{ + + @NonNull + private final Drawable mDivider; + private int mOrientation; + + /** + * Sole constructor. Takes in a {@link Drawable} to be used as the interior + * divider. + * + * @param divider A divider {@code Drawable} to be drawn on the RecyclerView + */ + public DividerItemDecoration(@NonNull Drawable divider) + { + mDivider = divider; + } + + /** + * Draws horizontal or vertical dividers onto the parent RecyclerView. + * + * @param canvas The {@link Canvas} onto which dividers will be drawn + * @param parent The RecyclerView onto which dividers are being added + * @param state The current RecyclerView.State of the RecyclerView + */ + @Override + public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) + { + if (mOrientation == LinearLayoutManager.HORIZONTAL) + drawHorizontalDividers(canvas, parent); + else if (mOrientation == LinearLayoutManager.VERTICAL) + drawVerticalDividers(canvas, parent); + } + + /** + * Determines the size and location of offsets between items in the parent + * RecyclerView. + * + * @param outRect The {@link Rect} of offsets to be added around the child + * view + * @param view The child view to be decorated with an offset + * @param parent The RecyclerView onto which dividers are being added + * @param state The current RecyclerView.State of the RecyclerView + */ + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) + { + super.getItemOffsets(outRect, view, parent, state); + + if (parent.getChildAdapterPosition(view) == 0) + return; + + mOrientation = ((LinearLayoutManager) parent.getLayoutManager()).getOrientation(); + if (mOrientation == LinearLayoutManager.HORIZONTAL) + outRect.left = mDivider.getIntrinsicWidth(); + else if (mOrientation == LinearLayoutManager.VERTICAL) + outRect.top = mDivider.getIntrinsicHeight(); + } + + /** + * Adds dividers to a RecyclerView with a LinearLayoutManager or its + * subclass oriented horizontally. + * + * @param canvas The {@link Canvas} onto which horizontal dividers will be + * drawn + * @param parent The RecyclerView onto which horizontal dividers are being + * added + */ + private void drawHorizontalDividers(Canvas canvas, RecyclerView parent) + { + int parentTop = parent.getPaddingTop(); + int parentBottom = parent.getHeight() - parent.getPaddingBottom(); + + int childCount = parent.getChildCount(); + for (int i = 0; i < childCount - 1; i++) + { + View child = parent.getChildAt(i); + + RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); + + int parentLeft = child.getRight() + params.rightMargin; + int parentRight = parentLeft + mDivider.getIntrinsicWidth(); + + mDivider.setBounds(parentLeft, parentTop, parentRight, parentBottom); + mDivider.draw(canvas); + } + } + + /** + * Adds dividers to a RecyclerView with a LinearLayoutManager or its + * subclass oriented vertically. + * + * @param canvas The {@link Canvas} onto which vertical dividers will be + * drawn + * @param parent The RecyclerView onto which vertical dividers are being + * added + */ + private void drawVerticalDividers(Canvas canvas, RecyclerView parent) + { + int parentLeft = parent.getPaddingLeft(); + int parentRight = parent.getWidth() - parent.getPaddingRight(); + + int childCount = parent.getChildCount(); + for (int i = 0; i < childCount - 1; i++) + { + View child = parent.getChildAt(i); + + RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); + + int parentTop = child.getBottom() + params.bottomMargin; + int parentBottom = parentTop + mDivider.getIntrinsicHeight(); + + mDivider.setBounds(parentLeft, parentTop, parentRight, parentBottom); + mDivider.draw(canvas); + } + } +} diff --git a/android/src/com/mapswithme/maps/widget/recycler/GridDividerItemDecoration.java b/android/src/com/mapswithme/maps/widget/recycler/GridDividerItemDecoration.java new file mode 100644 index 0000000000..1dc7334c46 --- /dev/null +++ b/android/src/com/mapswithme/maps/widget/recycler/GridDividerItemDecoration.java @@ -0,0 +1,128 @@ +package com.mapswithme.maps.widget.recycler; + +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +/** + * Adds interior dividers to a RecyclerView with a GridLayoutManager. + */ +public class GridDividerItemDecoration extends RecyclerView.ItemDecoration +{ + + @NonNull + private final Drawable mHorizontalDivider; + @NonNull + private final Drawable mVerticalDivider; + private final int mNumColumns; + + /** + * Sole constructor. Takes in {@link Drawable} objects to be used as + * horizontal and vertical dividers. + * + * @param horizontalDivider A divider {@code Drawable} to be drawn on the + * rows of the grid of the RecyclerView + * @param verticalDivider A divider {@code Drawable} to be drawn on the + * columns of the grid of the RecyclerView + * @param numColumns The number of columns in the grid of the RecyclerView + */ + public GridDividerItemDecoration(@NonNull Drawable horizontalDivider, + @NonNull Drawable verticalDivider, int numColumns) + { + mHorizontalDivider = horizontalDivider; + mVerticalDivider = verticalDivider; + mNumColumns = numColumns; + } + + /** + * Draws horizontal and/or vertical dividers onto the parent RecyclerView. + * + * @param canvas The {@link Canvas} onto which dividers will be drawn + * @param parent The RecyclerView onto which dividers are being added + * @param state The current RecyclerView.State of the RecyclerView + */ + @Override + public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) + { + drawHorizontalDividers(canvas, parent); + drawVerticalDividers(canvas, parent); + } + + /** + * Determines the size and location of offsets between items in the parent + * RecyclerView. + * + * @param outRect The {@link Rect} of offsets to be added around the child view + * @param view The child view to be decorated with an offset + * @param parent The RecyclerView onto which dividers are being added + * @param state The current RecyclerView.State of the RecyclerView + */ + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) + { + super.getItemOffsets(outRect, view, parent, state); + + boolean childIsInLeftmostColumn = (parent.getChildAdapterPosition(view) % mNumColumns) == 0; + if (!childIsInLeftmostColumn) + outRect.left = mHorizontalDivider.getIntrinsicWidth(); + + boolean childIsInFirstRow = (parent.getChildAdapterPosition(view)) < mNumColumns; + if (!childIsInFirstRow) + outRect.top = mVerticalDivider.getIntrinsicHeight(); + } + + /** + * Adds horizontal dividers to a RecyclerView with a GridLayoutManager or + * its subclass. + * + * @param canvas The {@link Canvas} onto which dividers will be drawn + * @param parent The RecyclerView onto which dividers are being added + */ + private void drawHorizontalDividers(Canvas canvas, RecyclerView parent) + { + int parentTop = parent.getPaddingTop(); + int parentBottom = parent.getHeight() - parent.getPaddingBottom(); + + for (int i = 0; i < mNumColumns; i++) + { + View child = parent.getChildAt(i); + RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); + + int parentLeft = child.getRight() + params.rightMargin; + int parentRight = parentLeft + mHorizontalDivider.getIntrinsicWidth(); + + mHorizontalDivider.setBounds(parentLeft, parentTop, parentRight, parentBottom); + mHorizontalDivider.draw(canvas); + } + } + + /** + * Adds vertical dividers to a RecyclerView with a GridLayoutManager or its + * subclass. + * + * @param canvas The {@link Canvas} onto which dividers will be drawn + * @param parent The RecyclerView onto which dividers are being added + */ + private void drawVerticalDividers(Canvas canvas, RecyclerView parent) + { + int parentLeft = parent.getPaddingLeft(); + int parentRight = parent.getWidth() - parent.getPaddingRight(); + + int childCount = parent.getChildCount(); + int numRows = (childCount + (mNumColumns - 1)) / mNumColumns; + for (int i = 0; i < numRows - 1; i++) + { + View child = parent.getChildAt(i * mNumColumns); + RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); + + int parentTop = child.getBottom() + params.bottomMargin; + int parentBottom = parentTop + mVerticalDivider.getIntrinsicHeight(); + + mVerticalDivider.setBounds(parentLeft, parentTop, parentRight, parentBottom); + mVerticalDivider.draw(canvas); + } + } +} diff --git a/android/src/com/mapswithme/util/UiUtils.java b/android/src/com/mapswithme/util/UiUtils.java index 55c329dc93..1e9fdfb469 100644 --- a/android/src/com/mapswithme/util/UiUtils.java +++ b/android/src/com/mapswithme/util/UiUtils.java @@ -7,6 +7,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Rect; import android.net.Uri; import android.os.Build; import android.support.annotation.AnyRes; @@ -19,6 +20,7 @@ import android.support.design.widget.TextInputLayout; import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; import android.text.TextUtils; +import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.view.ViewTreeObserver; @@ -339,6 +341,24 @@ public final class UiUtils return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; } + public static boolean isViewTouched(@NonNull MotionEvent event, @NonNull View view) + { + if (UiUtils.isHidden(view)) + return false; + + int x = (int) event.getX(); + int y = (int) event.getY(); + int[] location = new int[2]; + view.getLocationOnScreen(location); + int viewX = location[0]; + int viewY = location[1]; + int width = view.getWidth(); + int height = view.getHeight(); + Rect viewRect = new Rect(viewX, viewY, viewX + width, viewY + height); + + return viewRect.contains(x, y); + } + // utility class private UiUtils() {} } |