From 856f90dd0ca941b586264ad2f5cda9e046979c39 Mon Sep 17 00:00:00 2001 From: Arsentiy Milchakov Date: Tue, 28 Aug 2018 13:26:51 +0300 Subject: [PATCH] [tips] Tips are added --- map/CMakeLists.txt | 2 + map/framework.cpp | 18 ++++++ map/framework.hpp | 15 ++++- map/tips_api.cpp | 144 +++++++++++++++++++++++++++++++++++++++++++++ map/tips_api.hpp | 44 ++++++++++++++ 5 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 map/tips_api.cpp create mode 100644 map/tips_api.hpp diff --git a/map/CMakeLists.txt b/map/CMakeLists.txt index 667095dc12..98bb3e228d 100644 --- a/map/CMakeLists.txt +++ b/map/CMakeLists.txt @@ -94,6 +94,8 @@ set( subscription.hpp taxi_delegate.cpp taxi_delegate.hpp + tips_api.cpp + tips_api.hpp track.cpp track.hpp traffic_manager.cpp diff --git a/map/framework.cpp b/map/framework.cpp index 6aa632eacd..44b35bd789 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -3660,3 +3660,21 @@ booking::AvailabilityParams Framework::GetLastBookingAvailabilityParams() const { return m_bookingAvailabilityParams; } + +TipsApi const & Framework::GetTipsApi() const +{ + return m_tipsApi; +} + +bool Framework::HaveTransit(m2::PointD const & pt) const +{ + auto const & dataSource = m_model.GetDataSource(); + MwmSet::MwmId const mwmId = + dataSource.GetMwmIdByCountryFile(platform::CountryFile(m_infoGetter->GetRegionCountryId(pt))); + + MwmSet::MwmHandle handle = m_model.GetDataSource().GetMwmHandleById(mwmId); + if (!handle.IsAlive()) + return false; + + return handle.GetValue()->m_cont.IsExist(TRANSIT_FILE_TAG); +} diff --git a/map/framework.hpp b/map/framework.hpp index 1e7c740cfe..6e403a64dd 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -16,6 +16,7 @@ #include "map/search_api.hpp" #include "map/search_mark.hpp" #include "map/subscription.hpp" +#include "map/tips_api.hpp" #include "map/track.hpp" #include "map/traffic_manager.hpp" #include "map/transit/transit_reader.hpp" @@ -57,6 +58,8 @@ #include "partners_api/taxi_engine.hpp" #include "partners_api/viator_api.hpp" +#include "eye/eye_info.hpp" + #include "platform/country_defines.hpp" #include "platform/location.hpp" #include "platform/platform.hpp" @@ -129,7 +132,9 @@ struct FrameworkParams {} }; -class Framework : public SearchAPI::Delegate, public RoutingManager::Delegate +class Framework : public SearchAPI::Delegate, + public RoutingManager::Delegate, + public TipsApi::Delegate { DISALLOW_COPY(Framework); @@ -283,7 +288,7 @@ public: void ShowNode(storage::TCountryId const & countryId); /// Checks, whether the country which contains the specified point is loaded. - bool IsCountryLoaded(m2::PointD const & pt) const; + bool IsCountryLoaded(m2::PointD const & pt) const override; /// Checks, whether the country is loaded. bool IsCountryLoadedByName(string const & name) const; //@} @@ -876,4 +881,10 @@ public: private: std::unique_ptr m_subscription; + TipsApi m_tipsApi; + +public: + TipsApi const & GetTipsApi() const; + + bool HaveTransit(m2::PointD const & pt) const override; }; diff --git a/map/tips_api.cpp b/map/tips_api.cpp new file mode 100644 index 0000000000..6d0bd8fed0 --- /dev/null +++ b/map/tips_api.cpp @@ -0,0 +1,144 @@ +#include "map/tips_api.hpp" + +#include "eye/eye.hpp" + +#include "platform/platform.hpp" + +#include +#include + +using namespace eye; + +namespace +{ +// The app shouldn't show any screen at all more frequently than once in 3 days. +auto const kShowAnyTipPeriod = std::chrono::hours(24) * 3; +// The app shouldn't show the same screen more frequently than 1 month. +auto const kShowSameTipPeriod = std::chrono::hours(24) * 30; +// If a user clicks on the action areas (highlighted or blue button) +// the appropriate screen will never be shown again. +size_t const kActionClicksCountToDisable = 1; +// If a user clicks 3 times on the button GOT IT the appropriate screen will never be shown again. +size_t const kGotitClicksCountToDisable = 3; + +size_t ToIndex(Tips::Type type) +{ + return static_cast(type); +} + +TipsApi::Tip GetTipImpl(TipsApi::Duration showAnyTipPeriod, TipsApi::Duration showSameTipPeriod, + TipsApi::Conditions const & triggers) +{ + auto const info = Eye::Instance().GetInfo(); + auto const & tips = info->m_tips; + auto const totalTipsCount = static_cast(Tips::Type::Count); + + if (Clock::now() - tips.m_lastShown <= showAnyTipPeriod) + return {}; + + // If some tips are never shown. + if (tips.m_shownTips.size() < totalTipsCount) + { + using Candidate = std::pair; + std::array candidates; + for (size_t i = 0; i < totalTipsCount; ++i) + { + candidates[i] = {static_cast(i), true}; + } + + for (auto const & shownTip : tips.m_shownTips) + { + candidates[ToIndex(shownTip.m_type)].second = false; + } + + for (auto const & c : candidates) + { + if (c.second && triggers[ToIndex(c.first)]()) + return c.first; + } + } + + for (auto const & shownTip : tips.m_shownTips) + { + if (shownTip.m_eventCounters.Get(Tips::Event::ActionClicked) < kActionClicksCountToDisable && + shownTip.m_eventCounters.Get(Tips::Event::GotitClicked) < kGotitClicksCountToDisable && + Clock::now() - shownTip.m_lastShown > showSameTipPeriod && + triggers[ToIndex(shownTip.m_type)]()) + { + return shownTip.m_type; + } + } + + return {}; +} +} // namespace + +// static +TipsApi::Duration TipsApi::GetShowAnyTipPeriod() +{ + return kShowAnyTipPeriod; +} + +// static +TipsApi::Duration TipsApi::GetShowSameTipPeriod() +{ + return kShowSameTipPeriod; +} + +// static +size_t TipsApi::GetActionClicksCountToDisable() +{ + return kActionClicksCountToDisable; +} + +// static +size_t TipsApi::GetGotitClicksCountToDisable() +{ + return kGotitClicksCountToDisable; +} + +TipsApi::TipsApi() +{ + m_conditions = + {{ + // Condition for Tips::Type::BookmarksCatalog type. + [] { return GetPlatform().ConnectionStatus() != Platform::EConnectionType::CONNECTION_NONE; }, + // Condition for Tips::Type::BookingHotels type. + [] { return true; }, + // Condition for Tips::Type::DiscoverButton type. + [this] + { + auto const pos = m_delegate->GetCurrentPosition(); + if (!pos.is_initialized()) + return false; + + return m_delegate->IsCountryLoaded(pos.get()); + }, + // Condition for Tips::Type::MapsLayers type. + [this] + { + auto const pos = m_delegate->GetCurrentPosition(); + if (!pos.is_initialized()) + return false; + + return m_delegate->HaveTransit(pos.get()); + } + }}; +} + +void TipsApi::SetDelegate(std::unique_ptr delegate) +{ + m_delegate = std::move(delegate); +} + +TipsApi::Tip TipsApi::GetTip() const +{ + return GetTipImpl(GetShowAnyTipPeriod(), GetShowSameTipPeriod(), m_conditions); +} + +// static +TipsApi::Tip TipsApi::GetTipForTesting(Duration showAnyTipPeriod, Duration showSameTipPeriod, + Conditions const & triggers) +{ + return GetTipImpl(showAnyTipPeriod, showSameTipPeriod, triggers); +} diff --git a/map/tips_api.hpp b/map/tips_api.hpp new file mode 100644 index 0000000000..a9ed04799d --- /dev/null +++ b/map/tips_api.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "eye/eye_info.hpp" + +#include +#include +#include +#include + +#include + +class TipsApi +{ +public: + using Tip = boost::optional; + using Duration = std::chrono::duration; + using Condition = std::function; + using Conditions = std::array(eye::Tips::Type::Count)>; + + class Delegate + { + public: + virtual boost::optional GetCurrentPosition() const = 0; + virtual bool IsCountryLoaded(m2::PointD const & pt) const = 0; + virtual bool HaveTransit(m2::PointD const & pt) const = 0; + }; + + static Duration GetShowAnyTipPeriod(); + static Duration GetShowSameTipPeriod(); + static size_t GetActionClicksCountToDisable(); + static size_t GetGotitClicksCountToDisable(); + + TipsApi(); + + void SetDelegate(std::unique_ptr delegate); + Tip GetTip() const; + + static Tip GetTipForTesting(Duration showAnyTipPeriod, Duration showSameTipPeriod, + Conditions const & triggers); + +private: + std::unique_ptr m_delegate; + Conditions m_conditions; +};