diff --git a/iphone/Maps/Common/Statistics/StatisticsStrings.h b/iphone/Maps/Common/Statistics/StatisticsStrings.h index 04205a1591..7b21e92acd 100644 --- a/iphone/Maps/Common/Statistics/StatisticsStrings.h +++ b/iphone/Maps/Common/Statistics/StatisticsStrings.h @@ -213,6 +213,7 @@ static NSString * const kStatMapSearch = @"Map search"; static NSString * const kStatMapViewStyle = @"Map view style"; static NSString * const kStatMapViewStyleSettings = @"Map view style settings"; static NSString * const kStatMapsme = @"maps.me"; +static NSString * const kStatMapsmeGuides = @"MapsMeGuides"; static NSString * const kStatMaxim = @"Maxim"; static NSString * const kStatMenu = @"menu"; static NSString * const kStatMigrationBig2SmallMWM = @"Big mwms to small mwms migration counter"; diff --git a/iphone/Maps/Images.xcassets/Discovery/img_guide_placeholder.imageset/Contents.json b/iphone/Maps/Images.xcassets/Discovery/img_guide_placeholder.imageset/Contents.json new file mode 100644 index 0000000000..04b61f1afd --- /dev/null +++ b/iphone/Maps/Images.xcassets/Discovery/img_guide_placeholder.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Illustration.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/iphone/Maps/Images.xcassets/Discovery/img_guide_placeholder.imageset/Illustration.pdf b/iphone/Maps/Images.xcassets/Discovery/img_guide_placeholder.imageset/Illustration.pdf new file mode 100644 index 0000000000..407a5e0b96 Binary files /dev/null and b/iphone/Maps/Images.xcassets/Discovery/img_guide_placeholder.imageset/Illustration.pdf differ diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj index a480b8c827..8d1328a0a4 100644 --- a/iphone/Maps/Maps.xcodeproj/project.pbxproj +++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj @@ -568,10 +568,18 @@ BB8123CF212C264700ADE512 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB8123CD212C264700ADE512 /* Metal.framework */; }; BB8123D0212C264700ADE512 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB8123CE212C264700ADE512 /* MetalKit.framework */; }; BB8123D62130427E00ADE512 /* MetalContextFactory.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB8123D52130427E00ADE512 /* MetalContextFactory.mm */; }; + CD96C70C22A681C400DB7CFE /* DiscoveryGuideCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD96C70A22A681C400DB7CFE /* DiscoveryGuideCell.swift */; }; + CD96C70D22A681C400DB7CFE /* DiscoveryGuideCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = CD96C70B22A681C400DB7CFE /* DiscoveryGuideCell.xib */; }; + CD96C71122A6820800DB7CFE /* DiscoveryGuideCollectionHolderCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = CD96C70F22A6820800DB7CFE /* DiscoveryGuideCollectionHolderCell.xib */; }; + CD96C71422A6CBFD00DB7CFE /* MWMDiscoveryGuideViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = CD96C71322A6CBFD00DB7CFE /* MWMDiscoveryGuideViewModel.m */; }; + CD96C71722A7B5DE00DB7CFE /* MWMDiscoveryCityGalleryObjects.mm in Sources */ = {isa = PBXBuildFile; fileRef = CD96C71622A7B5DE00DB7CFE /* MWMDiscoveryCityGalleryObjects.mm */; }; + CD96C71C22A8113100DB7CFE /* MWMDiscoveryControllerViewModel.mm in Sources */ = {isa = PBXBuildFile; fileRef = CDB92CEA229E9ADF00EC757C /* MWMDiscoveryControllerViewModel.mm */; }; + CDB92CEE229E9CF900EC757C /* MWMDiscoveryMapObjects.mm in Sources */ = {isa = PBXBuildFile; fileRef = CDB92CED229E9CF900EC757C /* MWMDiscoveryMapObjects.mm */; }; + CDB92CF1229EB8A800EC757C /* MWMDiscoverySearchViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = CDB92CF0229EB8A800EC757C /* MWMDiscoverySearchViewModel.m */; }; + CDB92CF822A5350500EC757C /* MWMDiscoveryHotelViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = CDB92CF722A5350500EC757C /* MWMDiscoveryHotelViewModel.m */; }; F5BD255A0838E70EC012748E /* DiscoverySearchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5BD2ED6E94925472A9901B4 /* DiscoverySearchCell.swift */; }; F5BD29FF26AD58255766C51A /* DiscoverySpinnerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5BD246A7E6BE8CD8600EDD9 /* DiscoverySpinnerCell.swift */; }; F5BD2CA4DBEFACBC48195F39 /* DiscoveryCollectionHolderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5BD2A86D9DA2F9769D30B54 /* DiscoveryCollectionHolderCell.swift */; }; - F603E05A1FDE9410006B84D6 /* DiscoveryLocalExpertCollectionHolderCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F603E0581FDE9410006B84D6 /* DiscoveryLocalExpertCollectionHolderCell.xib */; }; F603E05E1FDE9703006B84D6 /* DiscoverySearchCollectionHolderCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F603E05C1FDE9703006B84D6 /* DiscoverySearchCollectionHolderCell.xib */; }; F607C1881C032A8800B53A87 /* resources-hdpi_clear in Resources */ = {isa = PBXBuildFile; fileRef = F607C1831C032A8800B53A87 /* resources-hdpi_clear */; }; F607C18A1C032A8800B53A87 /* resources-hdpi_dark in Resources */ = {isa = PBXBuildFile; fileRef = F607C1841C032A8800B53A87 /* resources-hdpi_dark */; }; @@ -614,8 +622,6 @@ F69018BD1E9F7CB600B3C10B /* MWMAutoupdateController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F69018BB1E9F7CB600B3C10B /* MWMAutoupdateController.xib */; }; F692F3831EA0FAF5001E82EB /* MWMAutoupdateController.mm in Sources */ = {isa = PBXBuildFile; fileRef = F69018B71E9E601400B3C10B /* MWMAutoupdateController.mm */; }; F69739B21FD197DB00FDA07D /* MWMDiscoveryTableManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = F69739B01FD197DB00FDA07D /* MWMDiscoveryTableManager.mm */; }; - F69739DC1FD6ECCE00FDA07D /* DiscoveryLocalExpertCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F69739DA1FD6ECCE00FDA07D /* DiscoveryLocalExpertCell.swift */; }; - F69739E01FD6EE1D00FDA07D /* DiscoveryLocalExpertCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F69739DE1FD6EE1D00FDA07D /* DiscoveryLocalExpertCell.xib */; }; F69CE8D61E5C49B4002B5881 /* PPHotelCarouselCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F69CE8D41E5C49B4002B5881 /* PPHotelCarouselCell.swift */; }; F69CE8DA1E5C5088002B5881 /* PPHotelCarouselCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F69CE8D81E5C5088002B5881 /* PPHotelCarouselCell.xib */; }; F69CE8DE1E5C51AB002B5881 /* CarouselElement.xib in Resources */ = {isa = PBXBuildFile; fileRef = F69CE8DC1E5C51AB002B5881 /* CarouselElement.xib */; }; @@ -1598,6 +1604,21 @@ BB8123CE212C264700ADE512 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; BB8123D42130427E00ADE512 /* MetalContextFactory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MetalContextFactory.h; sourceTree = ""; }; BB8123D52130427E00ADE512 /* MetalContextFactory.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MetalContextFactory.mm; sourceTree = ""; }; + CD96C70A22A681C400DB7CFE /* DiscoveryGuideCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryGuideCell.swift; sourceTree = ""; }; + CD96C70B22A681C400DB7CFE /* DiscoveryGuideCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DiscoveryGuideCell.xib; sourceTree = ""; }; + CD96C70F22A6820800DB7CFE /* DiscoveryGuideCollectionHolderCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DiscoveryGuideCollectionHolderCell.xib; sourceTree = ""; }; + CD96C71222A6CBFD00DB7CFE /* MWMDiscoveryGuideViewModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMDiscoveryGuideViewModel.h; sourceTree = ""; }; + CD96C71322A6CBFD00DB7CFE /* MWMDiscoveryGuideViewModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MWMDiscoveryGuideViewModel.m; sourceTree = ""; }; + CD96C71522A7B5DE00DB7CFE /* MWMDiscoveryCityGalleryObjects.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMDiscoveryCityGalleryObjects.h; sourceTree = ""; }; + CD96C71622A7B5DE00DB7CFE /* MWMDiscoveryCityGalleryObjects.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMDiscoveryCityGalleryObjects.mm; sourceTree = ""; }; + CDB92CE9229E9ADF00EC757C /* MWMDiscoveryControllerViewModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMDiscoveryControllerViewModel.h; sourceTree = ""; }; + CDB92CEA229E9ADF00EC757C /* MWMDiscoveryControllerViewModel.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMDiscoveryControllerViewModel.mm; sourceTree = ""; }; + CDB92CEC229E9CF900EC757C /* MWMDiscoveryMapObjects.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMDiscoveryMapObjects.h; sourceTree = ""; }; + CDB92CED229E9CF900EC757C /* MWMDiscoveryMapObjects.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMDiscoveryMapObjects.mm; sourceTree = ""; }; + CDB92CEF229EB8A700EC757C /* MWMDiscoverySearchViewModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMDiscoverySearchViewModel.h; sourceTree = ""; }; + CDB92CF0229EB8A800EC757C /* MWMDiscoverySearchViewModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MWMDiscoverySearchViewModel.m; sourceTree = ""; }; + CDB92CF622A5350500EC757C /* MWMDiscoveryHotelViewModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMDiscoveryHotelViewModel.h; sourceTree = ""; }; + CDB92CF722A5350500EC757C /* MWMDiscoveryHotelViewModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MWMDiscoveryHotelViewModel.m; sourceTree = ""; }; ED48BBB317C267F5003E7E92 /* ColorPickerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ColorPickerView.h; sourceTree = ""; }; ED48BBB417C267F5003E7E92 /* ColorPickerView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ColorPickerView.mm; sourceTree = ""; }; ED48BBB817C2B1E2003E7E92 /* CircleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CircleView.h; sourceTree = ""; }; @@ -1615,7 +1636,6 @@ F5BD246A7E6BE8CD8600EDD9 /* DiscoverySpinnerCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoverySpinnerCell.swift; sourceTree = ""; }; F5BD2A86D9DA2F9769D30B54 /* DiscoveryCollectionHolderCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoveryCollectionHolderCell.swift; sourceTree = ""; }; F5BD2ED6E94925472A9901B4 /* DiscoverySearchCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscoverySearchCell.swift; sourceTree = ""; }; - F603E0581FDE9410006B84D6 /* DiscoveryLocalExpertCollectionHolderCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DiscoveryLocalExpertCollectionHolderCell.xib; sourceTree = ""; }; F603E05C1FDE9703006B84D6 /* DiscoverySearchCollectionHolderCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DiscoverySearchCollectionHolderCell.xib; sourceTree = ""; }; F607C1831C032A8800B53A87 /* resources-hdpi_clear */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "resources-hdpi_clear"; path = "../../data/resources-hdpi_clear"; sourceTree = ""; }; F607C1841C032A8800B53A87 /* resources-hdpi_dark */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "resources-hdpi_dark"; path = "../../data/resources-hdpi_dark"; sourceTree = ""; }; @@ -1695,10 +1715,7 @@ F69018BB1E9F7CB600B3C10B /* MWMAutoupdateController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MWMAutoupdateController.xib; sourceTree = ""; }; F69739AF1FD197DB00FDA07D /* MWMDiscoveryTableManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMDiscoveryTableManager.h; sourceTree = ""; }; F69739B01FD197DB00FDA07D /* MWMDiscoveryTableManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMDiscoveryTableManager.mm; sourceTree = ""; }; - F69739B41FD198E300FDA07D /* DiscoveryControllerViewModel.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiscoveryControllerViewModel.hpp; sourceTree = ""; }; F69739B51FD19D9900FDA07D /* MWMDiscoveryTapDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMDiscoveryTapDelegate.h; sourceTree = ""; }; - F69739DA1FD6ECCE00FDA07D /* DiscoveryLocalExpertCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryLocalExpertCell.swift; sourceTree = ""; }; - F69739DE1FD6EE1D00FDA07D /* DiscoveryLocalExpertCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DiscoveryLocalExpertCell.xib; sourceTree = ""; }; F69CE8D41E5C49B4002B5881 /* PPHotelCarouselCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PPHotelCarouselCell.swift; sourceTree = ""; }; F69CE8D81E5C5088002B5881 /* PPHotelCarouselCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PPHotelCarouselCell.xib; sourceTree = ""; }; F69CE8DC1E5C51AB002B5881 /* CarouselElement.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CarouselElement.xib; sourceTree = ""; }; @@ -3512,6 +3529,61 @@ path = Catalog; sourceTree = ""; }; + CD96C71822A80C8400DB7CFE /* Collection Cells */ = { + isa = PBXGroup; + children = ( + CD96C70A22A681C400DB7CFE /* DiscoveryGuideCell.swift */, + CD96C70B22A681C400DB7CFE /* DiscoveryGuideCell.xib */, + 34B6FD5D2015E6BE00C18E97 /* DiscoveryBookingCell.swift */, + 34B6FD5E2015E6BF00C18E97 /* DiscoveryBookingCell.xib */, + F5BD2ED6E94925472A9901B4 /* DiscoverySearchCell.swift */, + F60C8BED1FCED15900DCF5FB /* DiscoverySearchCell.xib */, + F655C026207278300048A241 /* DiscoveryMoreCell.swift */, + F655C02820727A630048A241 /* DiscoveryMoreCell.xib */, + ); + path = "Collection Cells"; + sourceTree = ""; + }; + CD96C71922A80CCF00DB7CFE /* Table Cells */ = { + isa = PBXGroup; + children = ( + CD96C71A22A80D1600DB7CFE /* Holders */, + F6EBB26D1FD7E33300B69B6A /* DiscoveryNoResultsCell.swift */, + F6EBB2711FD7E4FD00B69B6A /* DiscoveryNoResultsCell.xib */, + F61757EB1FC73027000AD0D0 /* DiscoveryOnlineTemplateCell.swift */, + F61757EF1FC731F5000AD0D0 /* DiscoveryOnlineTemplateCell.xib */, + F5BD246A7E6BE8CD8600EDD9 /* DiscoverySpinnerCell.swift */, + F61757E71FC72CDE000AD0D0 /* DiscoverySpinnerCell.xib */, + ); + path = "Table Cells"; + sourceTree = ""; + }; + CD96C71A22A80D1600DB7CFE /* Holders */ = { + isa = PBXGroup; + children = ( + CD96C70F22A6820800DB7CFE /* DiscoveryGuideCollectionHolderCell.xib */, + 34B6FD612015F71900C18E97 /* DiscoveryBookingCollectionHolderCell.xib */, + F603E05C1FDE9703006B84D6 /* DiscoverySearchCollectionHolderCell.xib */, + F5BD2A86D9DA2F9769D30B54 /* DiscoveryCollectionHolderCell.swift */, + ); + path = Holders; + sourceTree = ""; + }; + CD96C71B22A80D3E00DB7CFE /* View Models */ = { + isa = PBXGroup; + children = ( + CDB92CE9229E9ADF00EC757C /* MWMDiscoveryControllerViewModel.h */, + CDB92CEA229E9ADF00EC757C /* MWMDiscoveryControllerViewModel.mm */, + CDB92CEF229EB8A700EC757C /* MWMDiscoverySearchViewModel.h */, + CDB92CF0229EB8A800EC757C /* MWMDiscoverySearchViewModel.m */, + CDB92CF622A5350500EC757C /* MWMDiscoveryHotelViewModel.h */, + CDB92CF722A5350500EC757C /* MWMDiscoveryHotelViewModel.m */, + CD96C71222A6CBFD00DB7CFE /* MWMDiscoveryGuideViewModel.h */, + CD96C71322A6CBFD00DB7CFE /* MWMDiscoveryGuideViewModel.m */, + ); + path = "View Models"; + sourceTree = ""; + }; F607C18B1C047FCA00B53A87 /* Segue */ = { isa = PBXGroup; children = ( @@ -4310,31 +4382,19 @@ F6E407CC1FC45ED4001F7821 /* Discovery */ = { isa = PBXGroup; children = ( - 34B6FD5D2015E6BE00C18E97 /* DiscoveryBookingCell.swift */, - 34B6FD5E2015E6BF00C18E97 /* DiscoveryBookingCell.xib */, - 34B6FD612015F71900C18E97 /* DiscoveryBookingCollectionHolderCell.xib */, - F5BD2A86D9DA2F9769D30B54 /* DiscoveryCollectionHolderCell.swift */, - F69739B41FD198E300FDA07D /* DiscoveryControllerViewModel.hpp */, - F69739DA1FD6ECCE00FDA07D /* DiscoveryLocalExpertCell.swift */, - F69739DE1FD6EE1D00FDA07D /* DiscoveryLocalExpertCell.xib */, - F603E0581FDE9410006B84D6 /* DiscoveryLocalExpertCollectionHolderCell.xib */, - F6EBB26D1FD7E33300B69B6A /* DiscoveryNoResultsCell.swift */, - F6EBB2711FD7E4FD00B69B6A /* DiscoveryNoResultsCell.xib */, - F61757EB1FC73027000AD0D0 /* DiscoveryOnlineTemplateCell.swift */, - F61757EF1FC731F5000AD0D0 /* DiscoveryOnlineTemplateCell.xib */, - F5BD2ED6E94925472A9901B4 /* DiscoverySearchCell.swift */, - F60C8BED1FCED15900DCF5FB /* DiscoverySearchCell.xib */, - F603E05C1FDE9703006B84D6 /* DiscoverySearchCollectionHolderCell.xib */, - F5BD246A7E6BE8CD8600EDD9 /* DiscoverySpinnerCell.swift */, - F61757E71FC72CDE000AD0D0 /* DiscoverySpinnerCell.xib */, + CD96C71B22A80D3E00DB7CFE /* View Models */, + CD96C71922A80CCF00DB7CFE /* Table Cells */, + CD96C71822A80C8400DB7CFE /* Collection Cells */, F6E407CD1FC45EF5001F7821 /* MWMDiscoveryController.h */, F6E407CE1FC45EF5001F7821 /* MWMDiscoveryController.mm */, F6E407D21FC4722F001F7821 /* MWMDiscoveryController.xib */, F69739AF1FD197DB00FDA07D /* MWMDiscoveryTableManager.h */, F69739B01FD197DB00FDA07D /* MWMDiscoveryTableManager.mm */, F69739B51FD19D9900FDA07D /* MWMDiscoveryTapDelegate.h */, - F655C026207278300048A241 /* DiscoveryMoreCell.swift */, - F655C02820727A630048A241 /* DiscoveryMoreCell.xib */, + CDB92CEC229E9CF900EC757C /* MWMDiscoveryMapObjects.h */, + CDB92CED229E9CF900EC757C /* MWMDiscoveryMapObjects.mm */, + CD96C71522A7B5DE00DB7CFE /* MWMDiscoveryCityGalleryObjects.h */, + CD96C71622A7B5DE00DB7CFE /* MWMDiscoveryCityGalleryObjects.mm */, ); path = Discovery; sourceTree = ""; @@ -4623,6 +4683,7 @@ 349D1AE11E2E325C004A2006 /* MWMBottomMenuViewController.xib in Resources */, 34D3B01E1E389D05004100F9 /* MWMButtonCell.xib in Resources */, 6741A98B1BF340DE002C974C /* MWMCircularProgress.xib in Resources */, + CD96C71122A6820800DB7CFE /* DiscoveryGuideCollectionHolderCell.xib in Resources */, 6741A94F1BF340DE002C974C /* MWMDefaultAlert.xib in Resources */, F6E2FE461E097BA00083EBEC /* MWMDirectionView.xib in Resources */, 6741A9951BF340DE002C974C /* MWMDownloaderDialogCell.xib in Resources */, @@ -4669,7 +4730,7 @@ 34B6FD602015E6BF00C18E97 /* DiscoveryBookingCell.xib in Resources */, 4788739020EE30B300F6826B /* LayersViewController.xib in Resources */, F6E2FDF51E097BA00083EBEC /* MWMOpeningHoursAddScheduleTableViewCell.xib in Resources */, - F603E05A1FDE9410006B84D6 /* DiscoveryLocalExpertCollectionHolderCell.xib in Resources */, + CD96C70D22A681C400DB7CFE /* DiscoveryGuideCell.xib in Resources */, F6E2FDFB1E097BA00083EBEC /* MWMOpeningHoursAllDayTableViewCell.xib in Resources */, 342639361EA0E60A0025EB89 /* local_ads_symbols.txt in Resources */, 4554B6EC1E55F0EF0084017F /* drules_proto_vehicle_clear.bin in Resources */, @@ -4748,7 +4809,6 @@ F69CE8DA1E5C5088002B5881 /* PPHotelCarouselCell.xib in Resources */, F69CE8DE1E5C51AB002B5881 /* CarouselElement.xib in Resources */, 6741A97F1BF340DE002C974C /* resources-mdpi_clear in Resources */, - F69739E01FD6EE1D00FDA07D /* DiscoveryLocalExpertCell.xib in Resources */, 6741A9901BF340DE002C974C /* resources-mdpi_dark in Resources */, 6741A9981BF340DE002C974C /* resources-xhdpi_clear in Resources */, 6741A9611BF340DE002C974C /* resources-xhdpi_dark in Resources */, @@ -4894,6 +4954,7 @@ 34AB665F1FC5AA330078E451 /* TransportTransitIntermediatePoint.swift in Sources */, 34B846A82029E8110081ECCD /* BMCDefaultViewModel.swift in Sources */, 470F5A7F2189C30800754295 /* InAppPurchase.swift in Sources */, + CD96C71422A6CBFD00DB7CFE /* MWMDiscoveryGuideViewModel.m in Sources */, 348A8DF51F66775A00D83026 /* RatingView.swift in Sources */, F63AF50F1EA6215100A1DB98 /* FilterPriceCategoryCell.swift in Sources */, 34D3AFF61E37A36A004100F9 /* UICollectionView+Cells.swift in Sources */, @@ -4930,6 +4991,7 @@ 34F4073E1E9E1AFF00E57AC0 /* MPNativeAd+MWM.mm in Sources */, F6E2FED01E097BA00083EBEC /* MWMSearchFilterViewController.mm in Sources */, 34D4FA671E265749003F53EF /* WhatsNewController.swift in Sources */, + CDB92CEE229E9CF900EC757C /* MWMDiscoveryMapObjects.mm in Sources */, 34B6FD5F2015E6BF00C18E97 /* DiscoveryBookingCell.swift in Sources */, 34D3B01B1E389D05004100F9 /* MWMButtonCell.mm in Sources */, 3486B5161E27AD3B0069C126 /* Framework.cpp in Sources */, @@ -4960,7 +5022,6 @@ 34E50DF81F6FCC96008EED49 /* UGCReviewCell.swift in Sources */, F6E2FF3F1E097BA00083EBEC /* MWMSearchTableViewController.mm in Sources */, F6E2FDE01E097BA00083EBEC /* MWMEditorViewController.mm in Sources */, - F69739DC1FD6ECCE00FDA07D /* DiscoveryLocalExpertCell.swift in Sources */, 6741A9C01BF340DE002C974C /* MWMTextView.m in Sources */, B32FE74320D2B09600EF7446 /* CatalogWebViewController.swift in Sources */, F6E2FDB61E097BA00083EBEC /* MWMEditorAdditionalNamesHeader.mm in Sources */, @@ -5031,6 +5092,7 @@ 349D1ACF1E2E325B004A2006 /* MWMBottomMenuCollectionViewCell.mm in Sources */, F6E2FF451E097BA00083EBEC /* SettingsTableViewLinkCell.swift in Sources */, 34C9BD0A1C6DBCDA000DC38D /* MWMNavigationController.m in Sources */, + CDB92CF822A5350500EC757C /* MWMDiscoveryHotelViewModel.m in Sources */, F6550C1F1FD81B3800352D88 /* RatingSummaryView+DefaultConfig.swift in Sources */, F6E2FE311E097BA00083EBEC /* MWMStreetEditorViewController.mm in Sources */, F6E2FE281E097BA00083EBEC /* MWMOpeningHoursSection.mm in Sources */, @@ -5115,6 +5177,7 @@ B33D21B820E130D000BAD749 /* BookmarksTabViewController.swift in Sources */, 3404754A1E081A4600C92850 /* AppInfo.mm in Sources */, 3358607E217632A2006D11F2 /* BookmarksSharingViewController.swift in Sources */, + CDB92CF1229EB8A800EC757C /* MWMDiscoverySearchViewModel.m in Sources */, 34AB662F1FC5AA330078E451 /* RouteManageriPhonePresentationController.swift in Sources */, 4797A4DC226F4B2900D3A984 /* DeepLinkHandler.swift in Sources */, 34AB66201FC5AA330078E451 /* RouteStartButton.swift in Sources */, @@ -5146,6 +5209,7 @@ 34E7761F1F14DB48003040B3 /* PlacePageArea.swift in Sources */, 346DB82E1E5C4F6700E3123E /* GalleryItemViewController.swift in Sources */, 340475561E081A4600C92850 /* Statistics.mm in Sources */, + CD96C71722A7B5DE00DB7CFE /* MWMDiscoveryCityGalleryObjects.mm in Sources */, F6381BF61CD12045004CA943 /* LocaleTranslator.mm in Sources */, 3444DFCD1F1760B900E73099 /* WidgetsArea.swift in Sources */, F6E2FE9A1E097BA00083EBEC /* MWMiPadPlacePageLayoutImpl.mm in Sources */, @@ -5237,6 +5301,7 @@ 349D1ADB1E2E325C004A2006 /* MWMBottomMenuView.mm in Sources */, 344BEAF61F66BDC30045DC45 /* RatingSummaryViewSettings.swift in Sources */, F6E2FD921E097BA00083EBEC /* MWMBookmarkColorViewController.mm in Sources */, + CD96C71C22A8113100DB7CFE /* MWMDiscoveryControllerViewModel.mm in Sources */, F63AF5061EA6162400A1DB98 /* FilterTypeCell.swift in Sources */, 347752881F725002000D46A3 /* UGCAddReviewRatingCell.swift in Sources */, 47E3C7332111F4D8008B3B27 /* CoverVerticalDismissalAnimator.swift in Sources */, @@ -5287,6 +5352,7 @@ 34EF94291C05A6F30050B714 /* MWMSegue.m in Sources */, 3430291D1F87BF4400D0A07C /* ReviewsViewController.swift in Sources */, F6E2FE731E097BA00083EBEC /* MWMOpeningHours.mm in Sources */, + CD96C70C22A681C400DB7CFE /* DiscoveryGuideCell.swift in Sources */, 47E3C7312111F4C2008B3B27 /* CoverVerticalPresentationAnimator.swift in Sources */, F6D67CDC2062B9C00032FD38 /* BCCreateCategoryAlert.swift in Sources */, F6E2FF601E097BA00083EBEC /* MWMSettingsViewController.mm in Sources */, diff --git a/iphone/Maps/UI/Discovery/DiscoveryBookingCell.swift b/iphone/Maps/UI/Discovery/Collection Cells/DiscoveryBookingCell.swift similarity index 100% rename from iphone/Maps/UI/Discovery/DiscoveryBookingCell.swift rename to iphone/Maps/UI/Discovery/Collection Cells/DiscoveryBookingCell.swift diff --git a/iphone/Maps/UI/Discovery/DiscoveryBookingCell.xib b/iphone/Maps/UI/Discovery/Collection Cells/DiscoveryBookingCell.xib similarity index 100% rename from iphone/Maps/UI/Discovery/DiscoveryBookingCell.xib rename to iphone/Maps/UI/Discovery/Collection Cells/DiscoveryBookingCell.xib diff --git a/iphone/Maps/UI/Discovery/Collection Cells/DiscoveryGuideCell.swift b/iphone/Maps/UI/Discovery/Collection Cells/DiscoveryGuideCell.swift new file mode 100644 index 0000000000..2f0170b7b6 --- /dev/null +++ b/iphone/Maps/UI/Discovery/Collection Cells/DiscoveryGuideCell.swift @@ -0,0 +1,100 @@ +@objc(MWMDiscoveryGuideCell) +final class DiscoveryGuideCell: UICollectionViewCell { + @IBOutlet var avatar: UIImageView! + @IBOutlet var titleLabel: UILabel! { + didSet { + titleLabel.font = UIFont.medium14() + titleLabel.textColor = UIColor.blackPrimaryText() + titleLabel.numberOfLines = 2 + } + } + + @IBOutlet var subtitleLabel: UILabel! { + didSet { + subtitleLabel.font = UIFont.regular12() + subtitleLabel.textColor = UIColor.blackSecondaryText() + subtitleLabel.numberOfLines = 1 + } + } + + @IBOutlet var proLabel: UILabel! { + didSet { + proLabel.font = UIFont.bold12() + proLabel.textColor = UIColor.white() + proLabel.backgroundColor = .clear + proLabel.text = ""; + } + } + + @IBOutlet var proContainer: UIView! { + didSet { + proLabel.backgroundColor = UIColor.ratingRed() + } + } + + @IBOutlet var detailsButton: UIButton! { + didSet { + detailsButton.setTitleColor(UIColor.linkBlue(), for: .normal) + detailsButton.setTitle(L("details"), for: .normal) + } + } + + typealias OnDetails = () -> Void + private var onDetails: OnDetails? + + override var isHighlighted: Bool { + didSet { + UIView.animate(withDuration: kDefaultAnimationDuration, + delay: 0, + options: [.allowUserInteraction, .beginFromCurrentState], + animations: { self.alpha = self.isHighlighted ? 0.3 : 1 }, + completion: nil) + } + } + + override func awakeFromNib() { + super.awakeFromNib() + layer.borderColor = UIColor.blackDividers().cgColor + } + + override func prepareForReuse() { + super.prepareForReuse() + avatar.image = UIImage(named: "img_guide_placeholder") + titleLabel.text = "" + subtitleLabel.text = "" + proLabel.text = "" + proContainer.isHidden = true + onDetails = nil + } + + private func setAvatar(_ avatarURL: String?) { + guard let avatarURL = avatarURL else { return } + if !avatarURL.isEmpty, let url = URL(string: avatarURL) { + avatar.image = UIImage(named: "img_guide_placeholder") + avatar.wi_setImage(with: url, transitionDuration: kDefaultAnimationDuration) + } else { + avatar.image = UIImage(named: "img_guide_placeholder") + } + } + + @objc func config(avatarURL: String?, + title: String, + subtitle: String, + label: String?, + onDetails: @escaping OnDetails) { + setAvatar(avatarURL) + titleLabel.text = title + subtitleLabel.text = subtitle + self.onDetails = onDetails + guard let label = label, !label.isEmpty else { + proContainer.isHidden = true + return + } + proLabel.text = label + proContainer.isHidden = false + } + + @IBAction private func detailsAction() { + onDetails?() + } +} diff --git a/iphone/Maps/UI/Discovery/Collection Cells/DiscoveryGuideCell.xib b/iphone/Maps/UI/Discovery/Collection Cells/DiscoveryGuideCell.xib new file mode 100644 index 0000000000..f2d7fa3aff --- /dev/null +++ b/iphone/Maps/UI/Discovery/Collection Cells/DiscoveryGuideCell.xib @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iphone/Maps/UI/Discovery/DiscoveryMoreCell.swift b/iphone/Maps/UI/Discovery/Collection Cells/DiscoveryMoreCell.swift similarity index 100% rename from iphone/Maps/UI/Discovery/DiscoveryMoreCell.swift rename to iphone/Maps/UI/Discovery/Collection Cells/DiscoveryMoreCell.swift diff --git a/iphone/Maps/UI/Discovery/DiscoveryMoreCell.xib b/iphone/Maps/UI/Discovery/Collection Cells/DiscoveryMoreCell.xib similarity index 100% rename from iphone/Maps/UI/Discovery/DiscoveryMoreCell.xib rename to iphone/Maps/UI/Discovery/Collection Cells/DiscoveryMoreCell.xib diff --git a/iphone/Maps/UI/Discovery/DiscoverySearchCell.swift b/iphone/Maps/UI/Discovery/Collection Cells/DiscoverySearchCell.swift similarity index 100% rename from iphone/Maps/UI/Discovery/DiscoverySearchCell.swift rename to iphone/Maps/UI/Discovery/Collection Cells/DiscoverySearchCell.swift diff --git a/iphone/Maps/UI/Discovery/DiscoverySearchCell.xib b/iphone/Maps/UI/Discovery/Collection Cells/DiscoverySearchCell.xib similarity index 100% rename from iphone/Maps/UI/Discovery/DiscoverySearchCell.xib rename to iphone/Maps/UI/Discovery/Collection Cells/DiscoverySearchCell.xib diff --git a/iphone/Maps/UI/Discovery/DiscoveryControllerViewModel.hpp b/iphone/Maps/UI/Discovery/DiscoveryControllerViewModel.hpp deleted file mode 100644 index fe83be1e16..0000000000 --- a/iphone/Maps/UI/Discovery/DiscoveryControllerViewModel.hpp +++ /dev/null @@ -1,126 +0,0 @@ -#pragma once - -#include "map/discovery/discovery_client_params.hpp" -#include "map/search_product_info.hpp" - -#include "partners_api/locals_api.hpp" - -#include "search/result.hpp" - -#include "geometry/point2d.hpp" - -#include "base/assert.hpp" - -#include - -namespace discovery -{ -class DiscoveryControllerViewModel -{ -public: - void SetSearchResults(search::Results const & res, - std::vector const & productInfo, - m2::PointD const & viewportCenter, ItemType const type) - { - switch (type) - { - case ItemType::Attractions: - m_attractions.SetResults(res, productInfo, viewportCenter); - break; - case ItemType::Cafes: - m_cafes.SetResults(res, productInfo, viewportCenter); - break; - case ItemType::Hotels: - m_hotels.SetResults(res, productInfo, viewportCenter); - break; - default: break; - } - } - - void SetExperts(std::vector const & experts) { m_experts = experts; } - - size_t GetItemsCount(ItemType const type) const - { - switch (type) - { - case ItemType::Attractions: return m_attractions.m_results.GetCount(); - case ItemType::Cafes: return m_cafes.m_results.GetCount(); - case ItemType::Hotels: return m_hotels.m_results.GetCount(); - case ItemType::LocalExperts: return m_experts.size(); - // TODO: Add correct value here. - case ItemType::Promo: return 0; - } - } - - search::Result const & GetAttractionAt(size_t const index) const - { - return m_attractions.GetSearchResultAt(index); - } - - m2::PointD const & GetAttractionReferencePoint() const { return m_attractions.m_viewportCenter; } - - search::ProductInfo const & GetAttractionProductInfoAt(size_t const index) const - { - return m_attractions.GetProductInfoAt(index); - } - - search::Result const & GetCafeAt(size_t const index) const - { - return m_cafes.GetSearchResultAt(index); - } - - m2::PointD const & GetCafeReferencePoint() const { return m_cafes.m_viewportCenter; } - - search::ProductInfo const & GetCafeProductInfoAt(size_t const index) const - { - return m_cafes.GetProductInfoAt(index); - } - - locals::LocalExpert const & GetExpertAt(size_t const index) const - { - CHECK_LESS(index, m_experts.size(), ("Incorrect experts index:", index)); - return m_experts[index]; - } - - search::Result const & GetHotelAt(size_t const index) const - { - return m_hotels.GetSearchResultAt(index); - } - - m2::PointD const & GetHotelReferencePoint() const { return m_hotels.m_viewportCenter; } - -private: - struct UISearchResults - { - m2::PointD m_viewportCenter; - search::Results m_results; - std::vector m_productInfos; - - void SetResults(search::Results const & res, - std::vector const & productInfo, - m2::PointD const & viewportCenter) - { - m_viewportCenter = viewportCenter; - m_results = res; - m_productInfos = productInfo; - } - - search::Result const & GetSearchResultAt(size_t const index) const - { - CHECK_LESS(index, m_results.GetCount(), ("Incorrect index:", index)); - return m_results[index]; - } - - search::ProductInfo const & GetProductInfoAt(size_t const index) const - { - CHECK_LESS(index, m_productInfos.size(), ("Incorrect index:", index)); - return m_productInfos[index]; - } - }; - - UISearchResults m_attractions; - UISearchResults m_cafes; - UISearchResults m_hotels; - std::vector m_experts; -}; -} // namespace discovery diff --git a/iphone/Maps/UI/Discovery/DiscoveryLocalExpertCell.swift b/iphone/Maps/UI/Discovery/DiscoveryLocalExpertCell.swift deleted file mode 100644 index a578fc4649..0000000000 --- a/iphone/Maps/UI/Discovery/DiscoveryLocalExpertCell.swift +++ /dev/null @@ -1,74 +0,0 @@ -@objc(MWMDiscoveryLocalExpertCell) -final class DiscoveryLocalExpertCell: UICollectionViewCell { - @IBOutlet private weak var avatar: UIImageView! - @IBOutlet private weak var name: UILabel! - @IBOutlet private weak var rating: RatingSummaryView! { - didSet { - rating.defaultConfig() - rating.textFont = UIFont.bold12() - rating.textSize = 12 - } - } - @IBOutlet private weak var price: UIButton! - - private lazy var formatter: NumberFormatter = { - let f = NumberFormatter() - f.numberStyle = .currency - return f - }() - - typealias Tap = () -> () - private var tap: Tap! - - override var isHighlighted: Bool { - didSet { - UIView.animate(withDuration: kDefaultAnimationDuration, - delay: 0, - options: [.allowUserInteraction, .beginFromCurrentState], - animations: { self.alpha = self.isHighlighted ? 0.3 : 1 }, - completion: nil) - } - } - - @objc func config(avatarURL: String, - name: String, - ratingValue: String, - ratingType: MWMRatingSummaryViewValueType, - price: Double, - currency: String, - tap: @escaping Tap) { - if avatarURL.count > 0, let url = URL(string: avatarURL) { - avatar.image = #imageLiteral(resourceName: "img_localsdefault") - avatar.wi_setImage(with: url, transitionDuration: kDefaultAnimationDuration) - } else { - avatar.image = #imageLiteral(resourceName: "img_localsdefault") - } - - self.name.text = name - rating.value = ratingValue - rating.type = ratingType - let str: String - if currency.count > 0, let cur = stringFor(price: price, currencyCode: currency) { - str = String(coreFormat: L("price_per_hour"), arguments:[cur]) - } else { - str = L("free") - } - - self.price.setTitle(str, for: .normal) - self.tap = tap - } - - @IBAction private func tapOnPrice() { - tap?() - } - - private func stringFor(price: Double, currencyCode: String) -> String? { - formatter.currencyCode = currencyCode - return formatter.string(for: price) - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - layer.borderColor = UIColor.blackDividers().cgColor - } -} diff --git a/iphone/Maps/UI/Discovery/DiscoveryLocalExpertCell.xib b/iphone/Maps/UI/Discovery/DiscoveryLocalExpertCell.xib deleted file mode 100644 index 14fa0484b5..0000000000 --- a/iphone/Maps/UI/Discovery/DiscoveryLocalExpertCell.xib +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iphone/Maps/UI/Discovery/DiscoveryOnlineTemplateCell.swift b/iphone/Maps/UI/Discovery/DiscoveryOnlineTemplateCell.swift deleted file mode 100644 index a4b4d5e768..0000000000 --- a/iphone/Maps/UI/Discovery/DiscoveryOnlineTemplateCell.swift +++ /dev/null @@ -1,39 +0,0 @@ -@objc(MWMDiscoveryOnlineTemplateType) -enum DiscoveryOnlineTemplateType: Int { - case locals -} - -@objc(MWMDiscoveryOnlineTemplateCell) -final class DiscoveryOnlineTemplateCell: MWMTableViewCell { - @IBOutlet private weak var spinner: UIImageView! { - didSet { - let postfix = UIColor.isNightMode() ? "_dark" : "_light" - spinner.image = UIImage(named: "Spinner" + postfix) - } - } - - @IBOutlet private weak var title: UILabel! - @IBOutlet private weak var subtitle: UILabel! - - typealias Tap = () -> () - private var tap: Tap? - - @objc func config(type: DiscoveryOnlineTemplateType, needSpinner: Bool, tap: @escaping Tap) { - switch type { - case .locals: - title.text = needSpinner ? L("discovery_button_other_loading_message") : - L("discovery_button_other_error_message") - subtitle.text = "" - } - - spinner.isHidden = !needSpinner - if (needSpinner) { - spinner.startRotation() - } - self.tap = tap - } - - @IBAction private func onTap() { - tap?() - } -} diff --git a/iphone/Maps/UI/Discovery/DiscoveryOnlineTemplateCell.xib b/iphone/Maps/UI/Discovery/DiscoveryOnlineTemplateCell.xib deleted file mode 100644 index 69653253da..0000000000 --- a/iphone/Maps/UI/Discovery/DiscoveryOnlineTemplateCell.xib +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iphone/Maps/UI/Discovery/MWMDiscoveryCityGalleryObjects.h b/iphone/Maps/UI/Discovery/MWMDiscoveryCityGalleryObjects.h new file mode 100644 index 0000000000..db4b225f12 --- /dev/null +++ b/iphone/Maps/UI/Discovery/MWMDiscoveryCityGalleryObjects.h @@ -0,0 +1,14 @@ +#include "partners_api/promo_api.hpp" + +NS_ASSUME_NONNULL_BEGIN + +@interface MWMDiscoveryCityGalleryObjects : NSObject + +- (instancetype)initWithGalleryResults:(promo::CityGallery const &)results; +- (promo::CityGallery::Item const &)galleryItemAtIndex:(NSUInteger)index; +- (NSUInteger)count; +- (nullable NSURL *)moreURL; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iphone/Maps/UI/Discovery/MWMDiscoveryCityGalleryObjects.mm b/iphone/Maps/UI/Discovery/MWMDiscoveryCityGalleryObjects.mm new file mode 100644 index 0000000000..9d148feb33 --- /dev/null +++ b/iphone/Maps/UI/Discovery/MWMDiscoveryCityGalleryObjects.mm @@ -0,0 +1,35 @@ +#import "MWMDiscoveryCityGalleryObjects.h" + +@interface MWMDiscoveryCityGalleryObjects() { + promo::CityGallery m_results; +} +@end + +@implementation MWMDiscoveryCityGalleryObjects + +- (instancetype)initWithGalleryResults:(promo::CityGallery const &)results { + self = [super init]; + if (self) { + m_results = results; + } + return self; +} + +- (promo::CityGallery::Item const &)galleryItemAtIndex:(NSUInteger)index { + CHECK_LESS(index, m_results.m_items.size(), ("Incorrect index:", index)); + return m_results.m_items[index]; +} + +- (NSUInteger)count { + return m_results.m_items.size(); +} + +- (NSURL *)moreURL { + NSString *path = @(m_results.m_moreUrl.c_str()); + if (path != nil && path.length > 0) { + return [NSURL URLWithString:path]; + } + return nil; +} + +@end diff --git a/iphone/Maps/UI/Discovery/MWMDiscoveryController.mm b/iphone/Maps/UI/Discovery/MWMDiscoveryController.mm index 8e9e3ea3b9..12812d7ff6 100644 --- a/iphone/Maps/UI/Discovery/MWMDiscoveryController.mm +++ b/iphone/Maps/UI/Discovery/MWMDiscoveryController.mm @@ -1,6 +1,9 @@ #import "MWMDiscoveryController.h" #import "Framework.h" +#import "MWMDiscoveryControllerViewModel.h" +#import "MWMDiscoveryCityGalleryObjects.h" +#import "MWMDiscoveryMapObjects.h" #import "MWMDiscoveryTableManager.h" #import "MWMDiscoveryTapDelegate.h" #import "MWMEye.h" @@ -10,8 +13,7 @@ #import "MWMSearchManager+Filter.h" #import "Statistics.h" #import "UIKitCategories.h" - -#include "DiscoveryControllerViewModel.hpp" +#import "SwiftBridge.h" #include "map/discovery/discovery_client_params.hpp" #include "map/search_product_info.hpp" @@ -49,28 +51,28 @@ struct Callback m_setSearchResults(results, productInfo, viewportCenter, type); m_refreshSection(type); } - + void operator()(uint32_t const requestId, vector const & experts) const { - CHECK(m_setLocalExperts, ()); - CHECK(m_refreshSection, ()); - m_setLocalExperts(experts); - m_refreshSection(ItemType::LocalExperts); + // TODO: Please add correct implementation here. } void operator()(uint32_t const requestId, promo::CityGallery const & experts) const { - // TODO: Please add correct implementation here. + CHECK(m_setPromoCityGallery, ()); + CHECK(m_refreshSection, ()); + m_setPromoCityGallery(experts); + m_refreshSection(ItemType::Promo); } using SetSearchResults = function const & productInfo, m2::PointD const & viewportCenter, ItemType const type)>; - using SetLocalExperts = function const & experts)>; + using SetPromoCityGallery = function; using RefreshSection = function; SetSearchResults m_setSearchResults; - SetLocalExperts m_setLocalExperts; + SetPromoCityGallery m_setPromoCityGallery; RefreshSection m_refreshSection; }; } // namespace @@ -78,54 +80,67 @@ struct Callback @interface MWMDiscoveryController () { Callback m_callback; - DiscoveryControllerViewModel m_model; } @property(weak, nonatomic) IBOutlet UITableView * tableView; @property(nonatomic) MWMDiscoveryTableManager * tableManager; +@property(nonatomic) MWMDiscoveryControllerViewModel *viewModel; @property(nonatomic) BOOL canUseNetwork; @end @implementation MWMDiscoveryController -+ (instancetype)instanceWithConnection:(BOOL)canUseNetwork -{ ++ (instancetype)instanceWithConnection:(BOOL)canUseNetwork { auto instance = [[MWMDiscoveryController alloc] initWithNibName:self.className bundle:nil]; instance.title = L(@"discovery_button_title"); instance.canUseNetwork = canUseNetwork; return instance; } -- (instancetype)initWithNibName:(NSString *)name bundle:(NSBundle *)bundle -{ +- (instancetype)initWithNibName:(NSString *)name bundle:(NSBundle *)bundle { self = [super initWithNibName:name bundle:bundle]; - if (self) - { + if (self) { + _viewModel = [[MWMDiscoveryControllerViewModel alloc] init]; auto & cb = m_callback; - cb.m_setLocalExperts = bind(&DiscoveryControllerViewModel::SetExperts, &m_model, _1); - cb.m_setSearchResults = - bind(&DiscoveryControllerViewModel::SetSearchResults, &m_model, _1, _2, _3, _4); - cb.m_refreshSection = [self](ItemType const type) { [self.tableManager reloadItem:type]; }; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-repeated-use-of-weak" + __weak __typeof__(self) weakSelf = self; + cb.m_setSearchResults = [weakSelf](search::Results const & res, vector const & productInfo, m2::PointD const & viewportCenter, ItemType const type) { + __strong __typeof__(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { return; } + MWMDiscoveryMapObjects *objects = [[MWMDiscoveryMapObjects alloc] initWithSearchResults:res + productInfos:productInfo + viewPortCenter:viewportCenter]; + [strongSelf.viewModel updateMapObjects:objects forType:type]; + }; + cb.m_setPromoCityGallery = [weakSelf](promo::CityGallery const & experts) { + __strong __typeof__(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { return; } + MWMDiscoveryCityGalleryObjects *objects = [[MWMDiscoveryCityGalleryObjects alloc] initWithGalleryResults:experts]; + [strongSelf.viewModel updateCityGalleryObjects:objects]; + }; + cb.m_refreshSection = [weakSelf](ItemType const type) { + __strong __typeof__(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { return; } + [strongSelf.tableManager reloadItem:type]; + }; +#pragma clang diagnostic pop } return self; } -- (void)viewDidLoad -{ +- (void)viewDidLoad { [super viewDidLoad]; - auto callback = [self]() -> DiscoveryControllerViewModel const & { return self->m_model; }; - self.tableManager = [[MWMDiscoveryTableManager alloc] initWithTableView:self.tableView - delegate:self - model:move(callback)]; - - auto getTypes = [](BOOL canUseNetwork) -> vector { - if (canUseNetwork) - return {ItemType::Hotels, ItemType::Attractions, ItemType::Cafes, ItemType::LocalExperts}; - return {ItemType::Hotels, ItemType::Attractions, ItemType::Cafes}; + __weak __typeof__(self) weakSelf = self; + MWMGetModelCallback callback = ^{ + return weakSelf.viewModel; }; - - vector types = getTypes(self.canUseNetwork); + self.tableManager = [[MWMDiscoveryTableManager alloc] initWithTableView:self.tableView + canUseNetwork:self.canUseNetwork + delegate:self + model:callback]; + vector types = {ItemType::Hotels, ItemType::Attractions, ItemType::Cafes, ItemType:: Promo}; [self.tableManager loadItems:types]; ClientParams p; p.m_itemTypes = move(types); @@ -138,91 +153,90 @@ struct Callback #pragma mark - MWMDiscoveryTapDelegate -- (void)showSearchResult:(const search::Result &)item -{ +- (void)showSearchResult:(const search::Result &)item { GetFramework().ShowSearchResult(item); [self.navigationController popViewControllerAnimated:YES]; } -- (void)tapOnItem:(ItemType const)type atIndex:(size_t const)index -{ +- (void)tapOnItem:(ItemType const)type atIndex:(NSInteger)index { NSString * dest = @""; NSString * event = kStatPlacepageSponsoredItemSelected; MWMEyeDiscoveryEvent eyeEvent; - switch (type) - { - case ItemType::LocalExperts: - if (index == m_model.GetItemsCount(type)) - { - [self openURLForItem:type]; - event = kStatPlacepageSponsoredMoreSelected; - eyeEvent = MWMEyeDiscoveryEventMoreLocals; - } - else - { - [self openUrl:[NSURL URLWithString:@(m_model.GetExpertAt(index).m_pageUrl.c_str())]]; - eyeEvent = MWMEyeDiscoveryEventLocals; - } - dest = kStatExternal; - break; - case ItemType::Attractions: - if (index == m_model.GetItemsCount(type)) - { - [self searchTourism]; - eyeEvent = MWMEyeDiscoveryEventMoreAttractions; - } - else - { - [self showSearchResult:m_model.GetAttractionAt(index)]; - eyeEvent = MWMEyeDiscoveryEventAttractions; - } - - dest = kStatPlacePage; - break; - case ItemType::Cafes: - if (index == m_model.GetItemsCount(type)) - { - [self searchFood]; - eyeEvent = MWMEyeDiscoveryEventMoreCafes; - } - else - { - [self showSearchResult:m_model.GetCafeAt(index)]; - eyeEvent = MWMEyeDiscoveryEventCafes; - } - - dest = kStatPlacePage; - break; - case ItemType::Hotels: - if (index == m_model.GetItemsCount(type)) - { - [self openFilters]; - event = kStatPlacepageSponsoredMoreSelected; - dest = kStatSearchFilterOpen; - eyeEvent = MWMEyeDiscoveryEventMoreHotels; - } - else - { - [self showSearchResult:m_model.GetHotelAt(index)]; + switch (type) { + case ItemType::Attractions: + if (index == [self.viewModel itemsCountForType:type]) { + [self searchTourism]; + eyeEvent = MWMEyeDiscoveryEventMoreAttractions; + } else { + [self showSearchResult:[self.viewModel.attractions searchResultAtIndex:index]]; + eyeEvent = MWMEyeDiscoveryEventAttractions; + } + dest = kStatPlacePage; - eyeEvent = MWMEyeDiscoveryEventHotels; - } - break; + break; + case ItemType::Cafes: + if (index == [self.viewModel itemsCountForType:type]) { + [self searchFood]; + eyeEvent = MWMEyeDiscoveryEventMoreCafes; + } else { + [self showSearchResult:[self.viewModel.cafes searchResultAtIndex:index]]; + eyeEvent = MWMEyeDiscoveryEventCafes; + } + + dest = kStatPlacePage; + break; + case ItemType::Hotels: + if (index == [self.viewModel itemsCountForType:type]) { + [self openFilters]; + event = kStatPlacepageSponsoredMoreSelected; + dest = kStatSearchFilterOpen; + eyeEvent = MWMEyeDiscoveryEventMoreHotels; + } else { + [self showSearchResult:[self.viewModel.hotels searchResultAtIndex:index]]; + dest = kStatPlacePage; + eyeEvent = MWMEyeDiscoveryEventHotels; + } + break; + case ItemType::Promo: + if (index == [self.viewModel itemsCountForType:type]) { + [self logEvent:kStatPlacepageSponsoredMoreSelected + type:type + index:index + destination:kStatExternal]; + } else { + [self openURLForItem:type]; + [self logEvent:event + type:type + index:index + destination:kStatExternal]; + } + return; + case ItemType::LocalExperts: + return; } - - NSAssert(dest.length > 0, @""); - [Statistics logEvent:kStatPlacepageSponsoredItemSelected - withParameters:@{ - kStatProvider: StatProvider(type), - kStatPlacement: kStatDiscovery, - kStatItem: @(index + 1), - kStatDestination: dest - }]; + + [self logEvent:event + type:type + index:index + destination:dest]; [MWMEye discoveryItemClickedWithEvent:eyeEvent]; } -- (void)openFilters -{ +- (void)logEvent:(NSString *)eventName + type:(ItemType const)type + index:(NSInteger)index + destination:(NSString *)destination { + NSAssert(destination.length > 0, @""); + [Statistics logEvent:eventName + withParameters:@{ + kStatProvider: StatProvider(type), + kStatPlacement: kStatDiscovery, + kStatItem: @(index + 1), + kStatDestination: destination + }]; +} + +- (void)openFilters { [self.navigationController popViewControllerAnimated:YES]; [[MWMSearchManager manager] showHotelFilterWithParams:nil onFinishCallback:^{ @@ -232,22 +246,19 @@ struct Callback }]; } -- (void)searchFood -{ +- (void)searchFood { [self.navigationController popViewControllerAnimated:YES]; [MWMMapViewControlsManager.manager searchTextOnMap:[L(@"eat") stringByAppendingString:@" "] forInputLocale:[NSLocale currentLocale].localeIdentifier]; } -- (void)searchTourism -{ +- (void)searchTourism { [self.navigationController popViewControllerAnimated:YES]; [MWMMapViewControlsManager.manager searchTextOnMap:[L(@"tourism") stringByAppendingString:@" "] forInputLocale:[NSLocale currentLocale].localeIdentifier]; } -- (void)routeToItem:(ItemType const)type atIndex:(size_t const)index -{ +- (void)routeToItem:(ItemType const)type atIndex:(NSInteger)index { __block m2::PointD point; __block NSString * title; __block NSString * subtitle; @@ -262,16 +273,19 @@ struct Callback title = item.GetString().empty() ? subtitle : @(item.GetString().c_str()); }; - switch (type) - { - case ItemType::Attractions: getRoutePointInfo(m_model.GetAttractionAt(index)); break; - case ItemType::Cafes: getRoutePointInfo(m_model.GetCafeAt(index)); break; - case ItemType::Hotels: getRoutePointInfo(m_model.GetHotelAt(index)); break; - case ItemType::LocalExperts: - CHECK(false, ("Attempt to route to item with type:", static_cast(type))); - break; - // TODO: Add correct code here. - case ItemType::Promo: break; + switch (type) { + case ItemType::Attractions: + getRoutePointInfo([self.viewModel.attractions searchResultAtIndex:index]); + break; + case ItemType::Cafes: + getRoutePointInfo([self.viewModel.cafes searchResultAtIndex:index]); + break; + case ItemType::Hotels: + getRoutePointInfo([self.viewModel.hotels searchResultAtIndex:index]); + break; + case ItemType::Promo: + case ItemType::LocalExperts: + return; } MWMRoutePoint * pt = [[MWMRoutePoint alloc] initWithPoint:point @@ -283,22 +297,56 @@ struct Callback [MWMRouter buildToPoint:pt bestRouter:NO]; [self.navigationController popViewControllerAnimated:YES]; - [Statistics logEvent:kStatPlacepageSponsoredItemSelected - withParameters:@{ - kStatProvider: StatProvider(type), - kStatPlacement: kStatDiscovery, - kStatItem: @(index + 1), - kStatDestination: kStatRouting - }]; + [self logEvent:kStatPlacepageSponsoredItemSelected + type:type + index:index + destination:kStatRouting]; } -- (void)openURLForItem:(discovery::ItemType const)type -{ - CHECK(type == ItemType::LocalExperts, - ("Attempt to open url for item with type:", static_cast(type))); - auto & f = GetFramework(); - auto const url = f.GetDiscoveryLocalExpertsUrl(); - [self openUrl:[NSURL URLWithString:@(url.c_str())]]; +- (void)openURLForItem:(ItemType const)type atIndex:(NSInteger)index { + switch (type) { + case ItemType::Attractions: + case ItemType::Cafes: + case ItemType::Hotels: + case ItemType::LocalExperts: + break; + case ItemType::Promo: + auto bookmarks = [[MWMBookmarksTabViewController alloc] init]; + bookmarks.activeTab = ActiveTabCatalog; + promo::CityGallery::Item const &item = [self.viewModel.guides galleryItemAtIndex:index]; + NSString *itemPath = @(item.m_url.c_str()); + if (!itemPath || itemPath.length == 0) { + return; + } + MWMCatalogWebViewController *catalog = [[MWMCatalogWebViewController alloc] init:[NSURL URLWithString:itemPath]]; + NSMutableArray * controllers = [self.navigationController.viewControllers mutableCopy]; + [controllers addObjectsFromArray:@[bookmarks, catalog]]; + [self.navigationController setViewControllers:controllers animated:YES]; + [self logEvent:kStatPlacepageSponsoredItemSelected + type:type + index:index + destination:kStatExternal]; + break; + } +} + +- (void)openURLForItem:(ItemType const)type { + switch (type) { + case ItemType::Attractions: + case ItemType::Cafes: + case ItemType::Hotels: + case ItemType::LocalExperts: + break; + case ItemType::Promo: + auto bookmarks = [[MWMBookmarksTabViewController alloc] init]; + bookmarks.activeTab = ActiveTabCatalog; + NSURL *url = [self.viewModel.guides moreURL]; + MWMCatalogWebViewController *catalog = [[MWMCatalogWebViewController alloc] init:url]; + NSMutableArray * controllers = [self.navigationController.viewControllers mutableCopy]; + [controllers addObjectsFromArray:@[bookmarks, catalog]]; + [self.navigationController setViewControllers:controllers animated:YES]; + break; + } } @end diff --git a/iphone/Maps/UI/Discovery/MWMDiscoveryMapObjects.h b/iphone/Maps/UI/Discovery/MWMDiscoveryMapObjects.h new file mode 100644 index 0000000000..1c59649745 --- /dev/null +++ b/iphone/Maps/UI/Discovery/MWMDiscoveryMapObjects.h @@ -0,0 +1,23 @@ +#include "map/search_product_info.hpp" + +#include "search/result.hpp" + +#include "geometry/point2d.hpp" + +#include + +NS_ASSUME_NONNULL_BEGIN + +@interface MWMDiscoveryMapObjects : NSObject + +- (instancetype)initWithSearchResults:(search::Results const &)results + productInfos:(std::vector const &)productInfos + viewPortCenter:(m2::PointD const &)viewportCenter; +- (search::Result const &)searchResultAtIndex:(NSUInteger)index; +- (search::ProductInfo const &)productInfoAtIndex:(NSUInteger)index; +- (m2::PointD const &)viewPortCenter; +- (NSUInteger)count; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iphone/Maps/UI/Discovery/MWMDiscoveryMapObjects.mm b/iphone/Maps/UI/Discovery/MWMDiscoveryMapObjects.mm new file mode 100644 index 0000000000..228dd830c6 --- /dev/null +++ b/iphone/Maps/UI/Discovery/MWMDiscoveryMapObjects.mm @@ -0,0 +1,43 @@ +#import "MWMDiscoveryMapObjects.h" + +@interface MWMDiscoveryMapObjects() { + search::Results m_results; + std::vector m_productInfos; + m2::PointD m_viewportCenter; +} + +@end + +@implementation MWMDiscoveryMapObjects + +- (instancetype)initWithSearchResults:(search::Results const &)results + productInfos:(std::vector const &)productInfos + viewPortCenter:(m2::PointD const &)viewportCenter { + self = [super init]; + if (self) { + m_results = results; + m_productInfos = productInfos; + m_viewportCenter = viewportCenter; + } + return self; +} + +- (search::Result const &)searchResultAtIndex:(NSUInteger)index { + CHECK_LESS(index, m_results.GetCount(), ("Incorrect index:", index)); + return m_results[index]; +} + +- (search::ProductInfo const &)productInfoAtIndex:(NSUInteger)index { + CHECK_LESS(index, m_productInfos.size(), ("Incorrect index:", index)); + return m_productInfos[index]; +} + +- (m2::PointD const &)viewPortCenter { + return m_viewportCenter; +} + +- (NSUInteger)count { + return m_results.GetCount(); +} + +@end diff --git a/iphone/Maps/UI/Discovery/MWMDiscoveryTableManager.h b/iphone/Maps/UI/Discovery/MWMDiscoveryTableManager.h index 72d3128bc6..28ea15172b 100644 --- a/iphone/Maps/UI/Discovery/MWMDiscoveryTableManager.h +++ b/iphone/Maps/UI/Discovery/MWMDiscoveryTableManager.h @@ -5,20 +5,21 @@ namespace discovery { -class DiscoveryControllerViewModel; - NSString * StatProvider(ItemType const type); } // namespace discovery -using GetModelCallback = std::function; +@class MWMDiscoveryControllerViewModel; + +typedef MWMDiscoveryControllerViewModel *(^MWMGetModelCallback)(void); @protocol MWMDiscoveryTapDelegate; @interface MWMDiscoveryTableManager : NSObject - (instancetype)initWithTableView:(UITableView *)tableView + canUseNetwork:(BOOL)canUseNetwork delegate:(id)delegate - model:(GetModelCallback &&)modelCallback; + model:(MWMGetModelCallback)modelCallback; - (void)loadItems:(std::vector const &)types; - (void)reloadItem:(discovery::ItemType const)type; diff --git a/iphone/Maps/UI/Discovery/MWMDiscoveryTableManager.mm b/iphone/Maps/UI/Discovery/MWMDiscoveryTableManager.mm index 10b520f222..ad5ef6f820 100644 --- a/iphone/Maps/UI/Discovery/MWMDiscoveryTableManager.mm +++ b/iphone/Maps/UI/Discovery/MWMDiscoveryTableManager.mm @@ -1,10 +1,12 @@ #import "MWMDiscoveryTableManager.h" #import "MWMDiscoveryTapDelegate.h" +#import "MWMDiscoveryControllerViewModel.h" +#import "MWMDiscoveryHotelViewModel.h" +#import "MWMDiscoverySearchViewModel.h" +#import "MWMDiscoveryGuideViewModel.h" #import "Statistics.h" #import "SwiftBridge.h" -#include "DiscoveryControllerViewModel.hpp" - #include "map/place_page_info.hpp" #include "partners_api/locals_api.hpp" @@ -32,41 +34,18 @@ namespace discovery { NSString * StatProvider(ItemType const type) { - switch (type) - { - case ItemType::LocalExperts: return kStatLocalsProvider; - case ItemType::Attractions: return kStatSearchAttractions; - case ItemType::Cafes: return kStatSearchRestaurants; - case ItemType::Hotels: return kStatBooking; - // TODO: Add correct stat key here. - case ItemType::Promo: return @""; + switch (type) { + case ItemType::Attractions: return kStatSearchAttractions; + case ItemType::Cafes: return kStatSearchRestaurants; + case ItemType::Hotels: return kStatBooking; + case ItemType::Promo: return kStatMapsmeGuides; + case ItemType::LocalExperts: return @""; } } - -pair FormattedRating(float const rawValue) -{ - auto const str = place_page::rating::GetRatingFormatted(rawValue); - auto const impress = static_cast(place_page::rating::GetImpress(rawValue)); - return {@(str.c_str()), impress}; -}; } // namespace discovery using namespace discovery; -namespace -{ -auto const kDefaultRowAnimation = UITableViewRowAnimationFade; - -string GetDistance(m2::PointD const & from, m2::PointD const & to) -{ - string distance; - auto const f = MercatorBounds::ToLatLon(from); - auto const t = MercatorBounds::ToLatLon(to); - measurement_utils::FormatDistance(ms::DistanceOnEarth(f.m_lat, f.m_lon, t.m_lat, t.m_lon), distance); - return distance; -} -} // namespace - @interface MWMDiscoveryCollectionView : UICollectionView @property(nonatomic) ItemType itemType; @end @@ -83,8 +62,9 @@ string GetDistance(m2::PointD const & from, m2::PointD const & to) } @property(weak, nonatomic) UITableView * tableView; -@property(nonatomic) GetModelCallback model; +@property(nonatomic) MWMGetModelCallback model; @property(weak, nonatomic) id delegate; +@property(nonatomic) BOOL canUseNetwork; @end @@ -93,15 +73,15 @@ string GetDistance(m2::PointD const & from, m2::PointD const & to) #pragma mark - Public - (instancetype)initWithTableView:(UITableView *)tableView + canUseNetwork:(BOOL)canUseNetwork delegate:(id)delegate - model:(GetModelCallback &&)modelCallback -{ + model:(MWMGetModelCallback)modelCallback { self = [super init]; - if (self) - { + if (self) { _tableView = tableView; _delegate = delegate; - _model = move(modelCallback); + _model = modelCallback; + _canUseNetwork = canUseNetwork; tableView.dataSource = self; tableView.rowHeight = UITableViewAutomaticDimension; tableView.estimatedRowHeight = 218; @@ -112,28 +92,29 @@ string GetDistance(m2::PointD const & from, m2::PointD const & to) return self; } -- (void)loadItems:(vector const &)types -{ +- (MWMDiscoveryControllerViewModel *)viewModel { + return self.model(); +} + +- (void)loadItems:(vector const &)types { m_types = types; m_loadingTypes = types; [self.tableView reloadData]; } -- (void)reloadItem:(ItemType const)type -{ - if (self.model().GetItemsCount(type) == 0) - { +- (void)reloadItem:(ItemType const)type { + if ([self.viewModel itemsCountForType:type] == 0) { [self removeItem:type]; return; } + - m_loadingTypes.erase(remove(m_loadingTypes.begin(), m_loadingTypes.end(), type), - m_loadingTypes.end()); - m_failedTypes.erase(remove(m_failedTypes.begin(), m_failedTypes.end(), type), - m_failedTypes.end()); - auto const position = [self position:type]; - [self.tableView reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:0 inSection:position]] - withRowAnimation:kDefaultRowAnimation]; + m_loadingTypes.erase(remove(m_loadingTypes.begin(), m_loadingTypes.end(), type), m_loadingTypes.end()); + m_failedTypes.erase(remove(m_failedTypes.begin(), m_failedTypes.end(), type), m_failedTypes.end()); + + NSInteger position = [self position:type]; + [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:position] + withRowAnimation:UITableViewRowAnimationFade]; [Statistics logEvent:kStatPlacepageSponsoredShow withParameters:@{ @@ -143,77 +124,68 @@ string GetDistance(m2::PointD const & from, m2::PointD const & to) }]; } -- (void)errorAtItem:(ItemType const)type -{ - CHECK(type == ItemType::LocalExperts, +- (void)errorAtItem:(ItemType const)type { + CHECK(type == ItemType::Promo, ("Error on item with type:", static_cast(type))); - m_loadingTypes.erase(remove(m_loadingTypes.begin(), m_loadingTypes.end(), type), - m_loadingTypes.end()); + m_loadingTypes.erase(remove(m_loadingTypes.begin(), m_loadingTypes.end(), type), m_loadingTypes.end()); m_failedTypes.push_back(type); - auto const position = [self position:type]; + NSInteger position = [self position:type]; [Statistics logEvent:kStatPlacepageSponsoredError - withParameters:@{kStatProvider: StatProvider(type), kStatPlacement: kStatDiscovery}]; + withParameters:@{kStatProvider: StatProvider(type), + kStatPlacement: kStatDiscovery}]; [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:position] - withRowAnimation:kDefaultRowAnimation]; + withRowAnimation:UITableViewRowAnimationFade]; } #pragma mark - Private -- (BOOL)hasOnlineSections -{ - return find(m_types.begin(), m_types.end(), ItemType::LocalExperts) != m_types.end(); +- (BOOL)hasOnlineSections { + return find(m_types.begin(), m_types.end(), ItemType::Promo) != m_types.end(); } -- (void)removeItem:(ItemType const)type -{ - auto const position = [self position:type]; +- (void)removeItem:(ItemType const)type { + NSInteger position = [self position:type]; m_types.erase(remove(m_types.begin(), m_types.end(), type), m_types.end()); - m_failedTypes.erase(remove(m_failedTypes.begin(), m_failedTypes.end(), type), - m_failedTypes.end()); - m_loadingTypes.erase(remove(m_loadingTypes.begin(), m_loadingTypes.end(), type), - m_loadingTypes.end()); + m_failedTypes.erase(remove(m_failedTypes.begin(), m_failedTypes.end(), type), m_failedTypes.end()); + m_loadingTypes.erase(remove(m_loadingTypes.begin(), m_loadingTypes.end(), type), m_loadingTypes.end()); auto indexSet = [NSIndexSet indexSetWithIndex:position]; auto tv = self.tableView; if (m_types.empty()) - [tv reloadSections:indexSet withRowAnimation:kDefaultRowAnimation]; + [tv reloadData]; else - [tv deleteSections:indexSet withRowAnimation:kDefaultRowAnimation]; + [tv deleteSections:indexSet withRowAnimation:UITableViewRowAnimationFade]; } -- (void)registerCells -{ +- (void)registerCells { auto tv = self.tableView; [tv registerWithCellClass:[MWMDiscoverySpinnerCell class]]; [tv registerWithCellClass:[MWMDiscoveryOnlineTemplateCell class]]; [tv registerWithCellClass:[MWMDiscoverySearchCollectionHolderCell class]]; - [tv registerWithCellClass:[MWMDiscoveryLocalExpertCollectionHolderCell class]]; + [tv registerWithCellClass:[MWMDiscoveryGuideCollectionHolderCell class]]; [tv registerWithCellClass:[MWMDiscoveryBookingCollectionHolderCell class]]; [tv registerWithCellClass:[MWMDiscoveryNoResultsCell class]]; } -- (NSInteger)position:(ItemType const)type -{ +- (NSInteger)position:(ItemType const)type { auto const it = find(m_types.begin(), m_types.end(), type); if (it == m_types.end()) CHECK(false, ("Incorrect item type:", static_cast(type))); - + return distance(m_types.begin(), it); } -- (MWMDiscoverySearchCollectionHolderCell *)searchCollectionHolderCell:(NSIndexPath *)indexPath -{ +- (MWMDiscoverySearchCollectionHolderCell *)searchCollectionHolderCell:(NSIndexPath *)indexPath { Class cls = [MWMDiscoverySearchCollectionHolderCell class]; - auto const type = m_types[indexPath.section]; - auto cell = static_cast( - [self.tableView dequeueReusableCellWithCellClass:cls indexPath:indexPath]); - auto collection = static_cast(cell.collectionView); - switch (type) - { - case ItemType::Attractions: [cell configAttractionsCell]; break; - case ItemType::Cafes: [cell configCafesCell]; break; + ItemType const type = m_types[indexPath.section]; + MWMDiscoverySearchCollectionHolderCell *cell = (MWMDiscoverySearchCollectionHolderCell *) + [self.tableView dequeueReusableCellWithCellClass:cls indexPath:indexPath]; + MWMDiscoveryCollectionView *collection = (MWMDiscoveryCollectionView *)cell.collectionView; + switch (type) { + case ItemType::Attractions: [cell configAttractionsCell]; break; + case ItemType::Cafes: [cell configCafesCell]; break; default: NSAssert(false, @""); return nil; } collection.delegate = self; @@ -222,26 +194,11 @@ string GetDistance(m2::PointD const & from, m2::PointD const & to) return cell; } -- (MWMDiscoveryLocalExpertCollectionHolderCell *)localExpertsCollectionHolderCell: - (NSIndexPath *)indexPath -{ - Class cls = [MWMDiscoveryLocalExpertCollectionHolderCell class]; - auto cell = static_cast( - [self.tableView dequeueReusableCellWithCellClass:cls indexPath:indexPath]); - auto collection = static_cast(cell.collectionView); - [cell config]; - collection.delegate = self; - collection.dataSource = self; - collection.itemType = ItemType::LocalExperts; - return cell; -} - -- (MWMDiscoveryBookingCollectionHolderCell *)bookingCollectionHolderCell:(NSIndexPath *)indexPath -{ +- (MWMDiscoveryBookingCollectionHolderCell *)bookingCollectionHolderCell:(NSIndexPath *)indexPath { Class cls = [MWMDiscoveryBookingCollectionHolderCell class]; - auto cell = static_cast( - [self.tableView dequeueReusableCellWithCellClass:cls indexPath:indexPath]); - auto collection = static_cast(cell.collectionView); + MWMDiscoveryBookingCollectionHolderCell *cell = (MWMDiscoveryBookingCollectionHolderCell *) + [self.tableView dequeueReusableCellWithCellClass:cls indexPath:indexPath]; + MWMDiscoveryCollectionView *collection = (MWMDiscoveryCollectionView *)cell.collectionView; [cell config]; collection.delegate = self; collection.dataSource = self; @@ -249,69 +206,87 @@ string GetDistance(m2::PointD const & from, m2::PointD const & to) return cell; } +- (MWMDiscoveryGuideCollectionHolderCell *)guideCollectionHolderCell:(NSIndexPath *)indexPath { + Class cls = [MWMDiscoveryGuideCollectionHolderCell class]; + MWMDiscoveryGuideCollectionHolderCell *cell = (MWMDiscoveryGuideCollectionHolderCell *) + [self.tableView dequeueReusableCellWithCellClass:cls indexPath:indexPath]; + MWMDiscoveryCollectionView *collection = (MWMDiscoveryCollectionView *)cell.collectionView; + [cell config]; + collection.delegate = self; + collection.dataSource = self; + collection.itemType = ItemType::Promo; + return cell; +} + +- (MWMDiscoverySpinnerCell *)spinnerCell:(NSIndexPath *)indexPath { + Class cls = [MWMDiscoverySpinnerCell class]; + return (MWMDiscoverySpinnerCell *)[self.tableView dequeueReusableCellWithCellClass:cls + indexPath:indexPath]; +} + +- (MWMDiscoveryOnlineTemplateCell *)onlineTemplateCell:(NSIndexPath *)indexPath { + Class cls = [MWMDiscoveryOnlineTemplateCell class]; + return (MWMDiscoveryOnlineTemplateCell *)[self.tableView dequeueReusableCellWithCellClass:cls + indexPath:indexPath]; +} + #pragma mark - UITableViewDataSource -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - auto constexpr kNumberOfRows = 1; - return kNumberOfRows; +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return static_cast(MAX(m_types.size(), 1)); +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + if (m_types.empty()) { + return 1; + } + ItemType const type = m_types[section]; + switch (type) { + case ItemType::Attractions: + case ItemType::Cafes: + case ItemType::Hotels: + case ItemType::Promo: + return 1; + case ItemType::LocalExperts: + return 0; + } } - (UITableViewCell *)tableView:(UITableView *)tableView - cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - if (m_types.empty()) - { + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + if (m_types.empty()) { Class cls = [MWMDiscoveryNoResultsCell class]; - return static_cast( - [tableView dequeueReusableCellWithCellClass:cls indexPath:indexPath]); + return (MWMDiscoveryNoResultsCell *)[tableView dequeueReusableCellWithCellClass:cls + indexPath:indexPath]; } + + ItemType const type = m_types[indexPath.section]; + BOOL isFailed = find(m_failedTypes.begin(), m_failedTypes.end(), type) != m_failedTypes.end(); + BOOL isLoading = find(m_loadingTypes.begin(), m_loadingTypes.end(), type) != m_loadingTypes.end(); - auto const type = m_types[indexPath.section]; - bool const isFailed = - find(m_failedTypes.begin(), m_failedTypes.end(), type) != m_failedTypes.end(); - bool const isLoading = - find(m_loadingTypes.begin(), m_loadingTypes.end(), type) != m_loadingTypes.end(); - - switch (type) - { - case ItemType::LocalExperts: - { - if (isLoading || isFailed) - { - Class cls = [MWMDiscoveryOnlineTemplateCell class]; - auto cell = static_cast( - [tableView dequeueReusableCellWithCellClass:cls indexPath:indexPath]); - [cell configWithType:MWMDiscoveryOnlineTemplateTypeLocals - needSpinner:isLoading - tap:^{ - [self.delegate openURLForItem:type]; - }]; - return cell; + switch (type) { + case ItemType::Attractions: + case ItemType::Cafes: + return isLoading ? [self spinnerCell:indexPath] : [self searchCollectionHolderCell:indexPath]; + case ItemType::Hotels: + return isLoading ? [self spinnerCell:indexPath] : [self bookingCollectionHolderCell:indexPath]; + case ItemType::Promo: { + if (isLoading || isFailed) { + MWMDiscoveryOnlineTemplateCell *cell = [self onlineTemplateCell:indexPath]; + __weak __typeof__(self) weakSelf = self; + [cell configWithType:MWMDiscoveryOnlineTemplateTypePromo + needSpinner:isLoading + canUseNetwork: self.canUseNetwork + tap:^{ + [weakSelf.delegate openURLForItem:type]; + }]; + return cell; + } + return [self guideCollectionHolderCell: indexPath]; } - return [self localExpertsCollectionHolderCell:indexPath]; + case ItemType::LocalExperts: + return [[UITableViewCell alloc] init]; } - case ItemType::Attractions: - case ItemType::Cafes: - { - if (isLoading) - { - Class cls = [MWMDiscoverySpinnerCell class]; - auto cell = static_cast( - [tableView dequeueReusableCellWithCellClass:cls indexPath:indexPath]); - return cell; - } - return [self searchCollectionHolderCell:indexPath]; - } - case ItemType::Hotels: return [self bookingCollectionHolderCell:indexPath]; - // TODO: Add correct value here. - case ItemType::Promo: return 0; - } -} - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView -{ - return static_cast(MAX(m_types.size(), 1)); } #pragma mark - UICollectionViewDelegate @@ -327,137 +302,76 @@ string GetDistance(m2::PointD const & from, m2::PointD const & to) - (NSInteger)collectionView:(MWMDiscoveryCollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - auto const count = self.model().GetItemsCount(collectionView.itemType); + NSInteger count = [self.viewModel itemsCountForType:collectionView.itemType]; return count > 0 ? count + 1 : 0; } - (UICollectionViewCell *)collectionView:(MWMDiscoveryCollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { - auto const type = collectionView.itemType; - auto const & model = self.model(); - NSString * ratingValue; - MWMRatingSummaryViewValueType ratingType; - switch (type) - { - case ItemType::Attractions: - case ItemType::Cafes: - { - if (indexPath.row == model.GetItemsCount(type)) - { - Class cls = [MWMDiscoveryMoreCell class]; - auto cell = static_cast([collectionView - dequeueReusableCellWithCellClass:cls - indexPath:indexPath]); - return cell; - } - Class cls = [MWMDiscoverySearchCell class]; - auto cell = static_cast( - [collectionView dequeueReusableCellWithCellClass:cls indexPath:indexPath]); - auto const & sr = type == ItemType::Attractions ? model.GetAttractionAt(indexPath.row) - : model.GetCafeAt(indexPath.row); - auto const & pt = type == ItemType::Attractions ? model.GetAttractionReferencePoint() - : model.GetCafeReferencePoint(); - auto const & pi = type == ItemType::Attractions ? model.GetAttractionProductInfoAt(indexPath.row) - : model.GetCafeProductInfoAt(indexPath.row); - tie(ratingValue, ratingType) = FormattedRating(pi.m_ugcRating); - - auto const readableType = classif().GetReadableObjectName(sr.GetFeatureType()); - auto const subtitle = platform::GetLocalizedTypeName(readableType); - - [cell configWithTitle:sr.GetString().empty() ? @(subtitle.c_str()) : @(sr.GetString().c_str()) - subtitle:@(subtitle.c_str()) - distance:@(GetDistance(pt, sr.GetFeatureCenter()).c_str()) - popular:sr.GetRankingInfo().m_popularity > 0 - ratingValue:ratingValue - ratingType:ratingType - tap:^{ - [self.delegate routeToItem:type atIndex:indexPath.row]; - }]; + ItemType const type = collectionView.itemType; + + if (indexPath.row == [self.viewModel itemsCountForType:type]) { + Class cls = [MWMDiscoveryMoreCell class]; + MWMDiscoveryMoreCell *cell = (MWMDiscoveryMoreCell *)[collectionView + dequeueReusableCellWithCellClass:cls + indexPath:indexPath]; return cell; } - case ItemType::LocalExperts: - { - if (indexPath.row == model.GetItemsCount(type)) - { - Class cls = [MWMDiscoveryMoreCell class]; - auto cell = static_cast([collectionView - dequeueReusableCellWithCellClass:cls - indexPath:indexPath]); + + switch (type) { + case ItemType::Attractions: + case ItemType::Cafes: { + Class cls = [MWMDiscoverySearchCell class]; + MWMDiscoverySearchCell *cell = (MWMDiscoverySearchCell *) + [collectionView dequeueReusableCellWithCellClass:cls indexPath:indexPath]; + MWMDiscoverySearchViewModel *objectVM = type == ItemType::Attractions ? [self.viewModel attractionAtIndex:indexPath.item] : [self.viewModel cafeAtIndex:indexPath.item]; + __weak __typeof__(self) weakSelf = self; + [cell configWithTitle:objectVM.title + subtitle:objectVM.subtitle + distance:objectVM.distance + popular:objectVM.isPopular + ratingValue:objectVM.ratingValue + ratingType:objectVM.ratingType + tap:^{ + [weakSelf.delegate routeToItem:type atIndex:indexPath.row]; + }]; return cell; } - - Class cls = [MWMDiscoveryLocalExpertCell class]; - auto cell = static_cast( - [collectionView dequeueReusableCellWithCellClass:cls indexPath:indexPath]); - auto const & expert = model.GetExpertAt(indexPath.row); - tie(ratingValue, ratingType) = FormattedRating(expert.m_rating); - - [cell configWithAvatarURL:@(expert.m_photoUrl.c_str()) - name:@(expert.m_name.c_str()) - ratingValue:ratingValue - ratingType:ratingType - price:expert.m_pricePerHour - currency:@(expert.m_currency.c_str()) - tap:^{ - [self.delegate tapOnItem:type atIndex:indexPath.row]; + case ItemType::Hotels: { + Class cls = [MWMDiscoveryBookingCell class]; + MWMDiscoveryBookingCell *cell = (MWMDiscoveryBookingCell *) + [collectionView dequeueReusableCellWithCellClass:cls indexPath:indexPath]; + MWMDiscoveryHotelViewModel *objectVM = [self.viewModel hotelAtIndex:indexPath.item]; + __weak __typeof__(self) weakSelf = self; + [cell configWithAvatarURL:nil + title:objectVM.title + subtitle:objectVM.subtitle + price:objectVM.price + ratingValue:objectVM.ratingValue + ratingType:objectVM.ratingType + distance:objectVM.distance + onBuildRoute:^{ + [weakSelf.delegate routeToItem:type atIndex:indexPath.row]; + }]; + return cell; + } + case ItemType::Promo: { + Class cls = [MWMDiscoveryGuideCell class]; + MWMDiscoveryGuideCell *cell = (MWMDiscoveryGuideCell *) + [collectionView dequeueReusableCellWithCellClass:cls indexPath:indexPath]; + MWMDiscoveryGuideViewModel *objectVM = [self.viewModel guideAtIndex:indexPath.item]; + __weak __typeof__(self) weakSelf = self; + [cell configWithAvatarURL:objectVM.imagePath + title:objectVM.title + subtitle:objectVM.subtitle + label:objectVM.label onDetails:^{ + [weakSelf.delegate openURLForItem:ItemType::Promo atIndex:indexPath.row]; }]; - return cell; - } - case ItemType::Hotels: - { - if (indexPath.row == model.GetItemsCount(type)) - { - Class cls = [MWMDiscoveryMoreCell class]; - auto cell = static_cast([collectionView - dequeueReusableCellWithCellClass:cls - indexPath:indexPath]); return cell; } - - Class cls = [MWMDiscoveryBookingCell class]; - auto cell = static_cast( - [collectionView dequeueReusableCellWithCellClass:cls indexPath:indexPath]); - auto const & sr = model.GetHotelAt(indexPath.row); - auto const & pt = model.GetHotelReferencePoint(); - - NSMutableString * subtitle = nil; - auto starsCount = sr.GetStarsCount(); - if (starsCount == 0) - { - auto const readableType = classif().GetReadableObjectName(sr.GetFeatureType()); - subtitle = [@(platform::GetLocalizedTypeName(readableType).c_str()) mutableCopy]; - } - else - { - subtitle = [@"" mutableCopy]; - for (int i = 0; i < starsCount; ++i) - [subtitle appendString:@"★"]; - } - - tie(ratingValue, ratingType) = FormattedRating(sr.GetHotelRating()); - - [cell configWithAvatarURL:nil - title:sr.GetString().empty() ? [subtitle copy] : @(sr.GetString().c_str()) - subtitle:[subtitle copy] - price:@(sr.GetHotelApproximatePricing().c_str()) - ratingValue:ratingValue - ratingType:ratingType - distance:@(GetDistance(pt, sr.GetFeatureCenter()).c_str()) - onBuildRoute:^{ - [self.delegate routeToItem:type atIndex:indexPath.row]; - }]; - return cell; - } - case ItemType::Promo: - { - // TODO: Add correct implementation here. - auto cls = [MWMDiscoveryMoreCell class]; - auto cell = static_cast([collectionView - dequeueReusableCellWithCellClass:cls - indexPath:indexPath]); - return cell; - } + case ItemType::LocalExperts: + return [[UICollectionViewCell alloc] init]; } } diff --git a/iphone/Maps/UI/Discovery/MWMDiscoveryTapDelegate.h b/iphone/Maps/UI/Discovery/MWMDiscoveryTapDelegate.h index 422738d02b..719350fe98 100644 --- a/iphone/Maps/UI/Discovery/MWMDiscoveryTapDelegate.h +++ b/iphone/Maps/UI/Discovery/MWMDiscoveryTapDelegate.h @@ -2,8 +2,9 @@ @protocol MWMDiscoveryTapDelegate -- (void)tapOnItem:(discovery::ItemType const)type atIndex:(size_t const)index; -- (void)routeToItem:(discovery::ItemType const)type atIndex:(size_t const)index; +- (void)tapOnItem:(discovery::ItemType const)type atIndex:(NSInteger)index; +- (void)routeToItem:(discovery::ItemType const)type atIndex:(NSInteger)index; +- (void)openURLForItem:(discovery::ItemType const)type atIndex:(NSInteger)index; - (void)openURLForItem:(discovery::ItemType const)type; @end diff --git a/iphone/Maps/UI/Discovery/DiscoveryNoResultsCell.swift b/iphone/Maps/UI/Discovery/Table Cells/DiscoveryNoResultsCell.swift similarity index 100% rename from iphone/Maps/UI/Discovery/DiscoveryNoResultsCell.swift rename to iphone/Maps/UI/Discovery/Table Cells/DiscoveryNoResultsCell.swift diff --git a/iphone/Maps/UI/Discovery/DiscoveryNoResultsCell.xib b/iphone/Maps/UI/Discovery/Table Cells/DiscoveryNoResultsCell.xib similarity index 100% rename from iphone/Maps/UI/Discovery/DiscoveryNoResultsCell.xib rename to iphone/Maps/UI/Discovery/Table Cells/DiscoveryNoResultsCell.xib diff --git a/iphone/Maps/UI/Discovery/Table Cells/DiscoveryOnlineTemplateCell.swift b/iphone/Maps/UI/Discovery/Table Cells/DiscoveryOnlineTemplateCell.swift new file mode 100644 index 0000000000..5b888cc80f --- /dev/null +++ b/iphone/Maps/UI/Discovery/Table Cells/DiscoveryOnlineTemplateCell.swift @@ -0,0 +1,67 @@ +@objc(MWMDiscoveryOnlineTemplateType) +enum DiscoveryOnlineTemplateType: Int { + case promo +} + +@objc(MWMDiscoveryOnlineTemplateCell) +final class DiscoveryOnlineTemplateCell: MWMTableViewCell { + @IBOutlet var spinner: UIImageView! { + didSet { + let postfix = UIColor.isNightMode() ? "_dark" : "_light" + spinner.image = UIImage(named: "Spinner" + postfix) + spinner.startRotation() + } + } + + @IBOutlet var containerView: UIView! { + didSet { + containerView.layer.cornerRadius = 6.0 + containerView.layer.borderWidth = 1.0 + containerView.layer.borderColor = UIColor.blackDividers()?.cgColor + } + } + @IBOutlet var header: UILabel! + @IBOutlet var title: UILabel! + @IBOutlet var subtitle: UILabel! + @IBOutlet var actionButton: UIButton! + + typealias Tap = () -> () + private var tap: Tap? + + override func awakeFromNib() { + super.awakeFromNib() + backgroundColor = .clear + } + + override func prepareForReuse() { + super.prepareForReuse() + spinner.isHidden = true + spinner.stopRotation() + } + + @objc func config(type: DiscoveryOnlineTemplateType, needSpinner: Bool, canUseNetwork: Bool, tap: @escaping Tap) { + switch type { + case .promo: + header.text = L("gallery_pp_download_guides_title").uppercased() + title.text = L("gallery_pp_download_guides_offline_title") + subtitle.text = L("gallery_pp_download_guides_offline_subtitle") + if canUseNetwork { + actionButton.setTitle(L("details"), + for: .normal) + } else { + actionButton.setTitle(L("gallery_pp_download_guides_offline_cta"), + for: .normal) + } + } + + if (needSpinner) { + spinner.isHidden = false + spinner.startRotation() + } + self.tap = tap + } + + @IBAction private func onTap() { + tap?() + } +} diff --git a/iphone/Maps/UI/Discovery/Table Cells/DiscoveryOnlineTemplateCell.xib b/iphone/Maps/UI/Discovery/Table Cells/DiscoveryOnlineTemplateCell.xib new file mode 100644 index 0000000000..cb96e240db --- /dev/null +++ b/iphone/Maps/UI/Discovery/Table Cells/DiscoveryOnlineTemplateCell.xib @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iphone/Maps/UI/Discovery/DiscoverySpinnerCell.swift b/iphone/Maps/UI/Discovery/Table Cells/DiscoverySpinnerCell.swift similarity index 100% rename from iphone/Maps/UI/Discovery/DiscoverySpinnerCell.swift rename to iphone/Maps/UI/Discovery/Table Cells/DiscoverySpinnerCell.swift diff --git a/iphone/Maps/UI/Discovery/DiscoverySpinnerCell.xib b/iphone/Maps/UI/Discovery/Table Cells/DiscoverySpinnerCell.xib similarity index 100% rename from iphone/Maps/UI/Discovery/DiscoverySpinnerCell.xib rename to iphone/Maps/UI/Discovery/Table Cells/DiscoverySpinnerCell.xib diff --git a/iphone/Maps/UI/Discovery/DiscoveryBookingCollectionHolderCell.xib b/iphone/Maps/UI/Discovery/Table Cells/Holders/DiscoveryBookingCollectionHolderCell.xib similarity index 100% rename from iphone/Maps/UI/Discovery/DiscoveryBookingCollectionHolderCell.xib rename to iphone/Maps/UI/Discovery/Table Cells/Holders/DiscoveryBookingCollectionHolderCell.xib diff --git a/iphone/Maps/UI/Discovery/DiscoveryCollectionHolderCell.swift b/iphone/Maps/UI/Discovery/Table Cells/Holders/DiscoveryCollectionHolderCell.swift similarity index 80% rename from iphone/Maps/UI/Discovery/DiscoveryCollectionHolderCell.swift rename to iphone/Maps/UI/Discovery/Table Cells/Holders/DiscoveryCollectionHolderCell.swift index 2946b40547..3aeaa17a36 100644 --- a/iphone/Maps/UI/Discovery/DiscoveryCollectionHolderCell.swift +++ b/iphone/Maps/UI/Discovery/Table Cells/Holders/DiscoveryCollectionHolderCell.swift @@ -3,15 +3,6 @@ class DiscoveryCollectionHolder: UITableViewCell { @IBOutlet fileprivate weak var header: UILabel! } -@objc(MWMDiscoveryLocalExpertCollectionHolderCell) -final class DiscoveryLocalExpertCollectionHolderCell: DiscoveryCollectionHolder { - @objc func config() { - header.text = L("discovery_button_subtitle_local_guides").uppercased() - collectionView.register(cellClass: DiscoveryLocalExpertCell.self) - collectionView.register(cellClass: DiscoveryMoreCell.self) - } -} - @objc(MWMDiscoverySearchCollectionHolderCell) final class DiscoverySearchCollectionHolderCell: DiscoveryCollectionHolder { @objc func configAttractionsCell() { @@ -37,3 +28,12 @@ final class DiscoveryBookingCollectionHolderCell: DiscoveryCollectionHolder { collectionView.register(cellClass: DiscoveryMoreCell.self) } } + +@objc(MWMDiscoveryGuideCollectionHolderCell) +final class DiscoveryGuideCollectionHolderCell: DiscoveryCollectionHolder { + @objc func config() { + header.text = L("discovery_button_subtitle_guides").uppercased() + collectionView.register(cellClass: DiscoveryGuideCell.self) + collectionView.register(cellClass: DiscoveryMoreCell.self) + } +} diff --git a/iphone/Maps/UI/Discovery/DiscoveryLocalExpertCollectionHolderCell.xib b/iphone/Maps/UI/Discovery/Table Cells/Holders/DiscoveryGuideCollectionHolderCell.xib similarity index 55% rename from iphone/Maps/UI/Discovery/DiscoveryLocalExpertCollectionHolderCell.xib rename to iphone/Maps/UI/Discovery/Table Cells/Holders/DiscoveryGuideCollectionHolderCell.xib index 04cd94b3cd..832998d35f 100644 --- a/iphone/Maps/UI/Discovery/DiscoveryLocalExpertCollectionHolderCell.xib +++ b/iphone/Maps/UI/Discovery/Table Cells/Holders/DiscoveryGuideCollectionHolderCell.xib @@ -1,26 +1,26 @@ - - + + - - + + - - - - - + + + + + - - - - - - - - + + + + + + + - - - - - - + - - + + - + diff --git a/iphone/Maps/UI/Discovery/DiscoverySearchCollectionHolderCell.xib b/iphone/Maps/UI/Discovery/Table Cells/Holders/DiscoverySearchCollectionHolderCell.xib similarity index 100% rename from iphone/Maps/UI/Discovery/DiscoverySearchCollectionHolderCell.xib rename to iphone/Maps/UI/Discovery/Table Cells/Holders/DiscoverySearchCollectionHolderCell.xib diff --git a/iphone/Maps/UI/Discovery/View Models/MWMDiscoveryControllerViewModel.h b/iphone/Maps/UI/Discovery/View Models/MWMDiscoveryControllerViewModel.h new file mode 100644 index 0000000000..3805766c14 --- /dev/null +++ b/iphone/Maps/UI/Discovery/View Models/MWMDiscoveryControllerViewModel.h @@ -0,0 +1,30 @@ +#include "map/discovery/discovery_client_params.hpp" + +NS_ASSUME_NONNULL_BEGIN + +@class MWMDiscoveryMapObjects; +@class MWMDiscoveryCityGalleryObjects; +@class MWMDiscoverySearchViewModel; +@class MWMDiscoveryHotelViewModel; +@class MWMDiscoveryGuideViewModel; + +@interface MWMDiscoveryControllerViewModel : NSObject + +@property(nonatomic, readonly) MWMDiscoveryMapObjects *attractions; +@property(nonatomic, readonly) MWMDiscoveryMapObjects *cafes; +@property(nonatomic, readonly) MWMDiscoveryMapObjects *hotels; +@property(nonatomic, readonly) MWMDiscoveryCityGalleryObjects *guides; + +- (void)updateMapObjects:(MWMDiscoveryMapObjects *)objects + forType:(discovery::ItemType const)type; +- (void)updateCityGalleryObjects:(MWMDiscoveryCityGalleryObjects *)objects; +- (NSUInteger)itemsCountForType:(discovery::ItemType const)type; + +- (MWMDiscoverySearchViewModel *)attractionAtIndex:(NSUInteger)index; +- (MWMDiscoverySearchViewModel *)cafeAtIndex:(NSUInteger)index; +- (MWMDiscoveryHotelViewModel *)hotelAtIndex:(NSUInteger)index; +- (MWMDiscoveryGuideViewModel *)guideAtIndex:(NSUInteger)index; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iphone/Maps/UI/Discovery/View Models/MWMDiscoveryControllerViewModel.mm b/iphone/Maps/UI/Discovery/View Models/MWMDiscoveryControllerViewModel.mm new file mode 100644 index 0000000000..4975debcdd --- /dev/null +++ b/iphone/Maps/UI/Discovery/View Models/MWMDiscoveryControllerViewModel.mm @@ -0,0 +1,184 @@ +#import "MWMDiscoveryControllerViewModel.h" +#import "MWMDiscoveryCityGalleryObjects.h" +#import "MWMDiscoveryMapObjects.h" +#import "MWMDiscoveryHotelViewModel.h" +#import "MWMDiscoverySearchViewModel.h" +#import "MWMDiscoveryGuideViewModel.h" +#import "MWMRatingSummaryViewValueType.h" + +#include "map/place_page_info.hpp" + +#include "platform/localization.hpp" +#include "platform/measurement_utils.hpp" + +#include "geometry/distance_on_sphere.hpp" +#include "geometry/mercator.hpp" + +using namespace discovery; + +@interface MWMDiscoveryControllerViewModel() + +@property(nonatomic, readwrite) MWMDiscoveryMapObjects *attractions; +@property(nonatomic, readwrite) MWMDiscoveryMapObjects *cafes; +@property(nonatomic, readwrite) MWMDiscoveryMapObjects *hotels; +@property(nonatomic, readwrite) MWMDiscoveryCityGalleryObjects *guides; + +@end + +@implementation MWMDiscoveryControllerViewModel + +- (void)updateMapObjects:(MWMDiscoveryMapObjects *)objects + forType:(ItemType const)type { + switch (type) { + case ItemType::Attractions: + self.attractions = objects; + break; + case ItemType::Cafes: + self.cafes = objects; + break; + case ItemType::Hotels: + self.hotels = objects; + break; + case ItemType::Promo: + break; + case ItemType::LocalExperts: + break; + } +} + +- (void)updateCityGalleryObjects:(MWMDiscoveryCityGalleryObjects *)objects { + self.guides = objects; +} + +- (NSUInteger)itemsCountForType:(ItemType const)type { + switch (type) { + case ItemType::Attractions: + return self.attractions.count; + case ItemType::Cafes: + return self.cafes.count; + case ItemType::Hotels: + return self.hotels.count; + case ItemType::Promo: + return self.guides.count; + case ItemType::LocalExperts: + return 0; + } +} + +- (MWMDiscoverySearchViewModel *)attractionAtIndex:(NSUInteger)index { + search::Result const &result = [self.attractions searchResultAtIndex:index]; + search::ProductInfo const &info = [self.attractions productInfoAtIndex:index]; + m2::PointD const ¢er = [self.attractions viewPortCenter]; + return [self searchViewModelForResult:result + productInfo:info + viewPortCenter:center]; +} + +- (MWMDiscoverySearchViewModel *)cafeAtIndex:(NSUInteger)index { + search::Result const &result = [self.cafes searchResultAtIndex:index]; + search::ProductInfo const &info = [self.cafes productInfoAtIndex:index]; + m2::PointD const ¢er = [self.cafes viewPortCenter]; + return [self searchViewModelForResult:result + productInfo:info + viewPortCenter:center]; +} + +- (MWMDiscoveryHotelViewModel *)hotelAtIndex:(NSUInteger)index { + search::Result const &result = [self.hotels searchResultAtIndex:index]; + search::ProductInfo const &info = [self.hotels productInfoAtIndex:index]; + m2::PointD const ¢er = [self.hotels viewPortCenter]; + return [self hotelViewModelForResult:result + productInfo:info + viewPortCenter:center]; +} + +- (MWMDiscoveryGuideViewModel *)guideAtIndex:(NSUInteger)index { + promo::CityGallery::Item const &item = [self.guides galleryItemAtIndex:index]; + return [self guideViewModelForItem:item]; +} + +#pragma mark - Builders + +- (MWMDiscoverySearchViewModel *)searchViewModelForResult:(search::Result const &)result + productInfo:(search::ProductInfo const &)info + viewPortCenter:(m2::PointD const &)center { + + auto const readableType = classif().GetReadableObjectName(result.GetFeatureType()); + NSString *subtitle = @(platform::GetLocalizedTypeName(readableType).c_str()); + NSString *title = result.GetString().empty() ? subtitle : @(result.GetString().c_str()); + + NSString *ratingValue = [self ratingValueForProductInfo:info]; + MWMRatingSummaryViewValueType ratingType = [self ratingTypeForProductInfo:info]; + + NSString *distance = [self distanceFrom:center + to:result.GetFeatureCenter()]; + + BOOL isPopular = result.GetRankingInfo().m_popularity > 0; + + return [[MWMDiscoverySearchViewModel alloc] initWithTitle:title + subtitle:subtitle + distance:distance + isPopular:isPopular + ratingValue:ratingValue + ratingType:ratingType]; +} + +- (MWMDiscoveryHotelViewModel *)hotelViewModelForResult:(search::Result const &)result + productInfo:(search::ProductInfo const &)info + viewPortCenter:(m2::PointD const &)center { + + auto const readableType = classif().GetReadableObjectName(result.GetFeatureType()); + NSString *subtitle = @(platform::GetLocalizedTypeName(readableType).c_str()); + NSString *title = result.GetString().empty() ? subtitle : @(result.GetString().c_str()); + NSUInteger starsCount = result.GetStarsCount(); + if (starsCount > 0) { + subtitle = [@"" stringByPaddingToLength:starsCount + withString:@"★" + startingAtIndex:0]; + } + NSString *price = @(result.GetHotelApproximatePricing().c_str()); + + NSString *ratingValue = [self ratingValueForProductInfo:info]; + MWMRatingSummaryViewValueType ratingType = [self ratingTypeForProductInfo:info]; + + NSString *distance = [self distanceFrom:center + to:result.GetFeatureCenter()]; + + BOOL isPopular = result.GetRankingInfo().m_popularity > 0; + + return [[MWMDiscoveryHotelViewModel alloc] initWithTitle:title + subtitle:subtitle + price:price + distance:distance + isPopular:isPopular + ratingValue:ratingValue + ratingType:ratingType]; +} + +- (MWMDiscoveryGuideViewModel *)guideViewModelForItem:(promo::CityGallery::Item const &)item { + return [[MWMDiscoveryGuideViewModel alloc] initWithTitle:@(item.m_name.c_str()) + subtitle:@(item.m_author.m_name.c_str()) + label:@(item.m_luxCategory.m_name.c_str()) + imageURL:@(item.m_imageUrl.c_str())]; +} + +#pragma mark - Helpers + +- (NSString *)distanceFrom:(m2::PointD const &)startPoint + to:(m2::PointD const &)endPoint { + string distance; + auto const f = MercatorBounds::ToLatLon(startPoint); + auto const t = MercatorBounds::ToLatLon(endPoint); + measurement_utils::FormatDistance(ms::DistanceOnEarth(f.m_lat, f.m_lon, t.m_lat, t.m_lon), distance); + return @(distance.c_str()); +} + +- (NSString *)ratingValueForProductInfo:(search::ProductInfo const &)info { + return @(place_page::rating::GetRatingFormatted(info.m_ugcRating).c_str()); +} + +- (MWMRatingSummaryViewValueType)ratingTypeForProductInfo:(search::ProductInfo const &)info { + return (MWMRatingSummaryViewValueType)place_page::rating::GetImpress(info.m_ugcRating); +} + +@end diff --git a/iphone/Maps/UI/Discovery/View Models/MWMDiscoveryGuideViewModel.h b/iphone/Maps/UI/Discovery/View Models/MWMDiscoveryGuideViewModel.h new file mode 100644 index 0000000000..cb5be67618 --- /dev/null +++ b/iphone/Maps/UI/Discovery/View Models/MWMDiscoveryGuideViewModel.h @@ -0,0 +1,17 @@ +NS_ASSUME_NONNULL_BEGIN + +@interface MWMDiscoveryGuideViewModel : NSObject + +@property(nonatomic, readonly) NSString *title; +@property(nonatomic, readonly) NSString *subtitle; +@property(nonatomic, nullable, readonly) NSString *label; +@property(nonatomic, nullable, readonly) NSString *imagePath; + +- (instancetype)initWithTitle:(NSString *)title + subtitle:(NSString *)subtitle + label:(nullable NSString *)label + imageURL:(nullable NSString *) imagePath; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iphone/Maps/UI/Discovery/View Models/MWMDiscoveryGuideViewModel.m b/iphone/Maps/UI/Discovery/View Models/MWMDiscoveryGuideViewModel.m new file mode 100644 index 0000000000..18cd51683d --- /dev/null +++ b/iphone/Maps/UI/Discovery/View Models/MWMDiscoveryGuideViewModel.m @@ -0,0 +1,28 @@ +#import "MWMDiscoveryGuideViewModel.h" + +@interface MWMDiscoveryGuideViewModel() + +@property(nonatomic, readwrite) NSString *title; +@property(nonatomic, readwrite) NSString *subtitle; +@property(nonatomic, readwrite) NSString *label; +@property(nonatomic, readwrite) NSString *imagePath; + +@end + +@implementation MWMDiscoveryGuideViewModel + +- (instancetype)initWithTitle:(NSString *)title + subtitle:(NSString *)subtitle + label:(NSString *)label + imageURL:(NSString *) imagePath { + self = [super init]; + if (self) { + self.title = title; + self.subtitle = subtitle; + self.label = label; + self.imagePath = imagePath; + } + return self; +} + +@end diff --git a/iphone/Maps/UI/Discovery/View Models/MWMDiscoveryHotelViewModel.h b/iphone/Maps/UI/Discovery/View Models/MWMDiscoveryHotelViewModel.h new file mode 100644 index 0000000000..150f5204b0 --- /dev/null +++ b/iphone/Maps/UI/Discovery/View Models/MWMDiscoveryHotelViewModel.h @@ -0,0 +1,25 @@ +#import "MWMRatingSummaryViewValueType.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MWMDiscoveryHotelViewModel : NSObject + +@property(nonatomic, readonly) NSString *title; +@property(nonatomic, readonly) NSString *subtitle; +@property(nonatomic, readonly) NSString *price; +@property(nonatomic, readonly) NSString *distance; +@property(nonatomic, readonly) BOOL isPopular; +@property(nonatomic, readonly) NSString *ratingValue; +@property(nonatomic, readonly) MWMRatingSummaryViewValueType ratingType; + +- (instancetype)initWithTitle:(NSString *)title + subtitle:(NSString *)subtitle + price:(NSString *)price + distance:(NSString *)distance + isPopular:(BOOL)isPopular + ratingValue:(NSString *) ratingValue + ratingType:(MWMRatingSummaryViewValueType)ratingType; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iphone/Maps/UI/Discovery/View Models/MWMDiscoveryHotelViewModel.m b/iphone/Maps/UI/Discovery/View Models/MWMDiscoveryHotelViewModel.m new file mode 100644 index 0000000000..5a30eeaf73 --- /dev/null +++ b/iphone/Maps/UI/Discovery/View Models/MWMDiscoveryHotelViewModel.m @@ -0,0 +1,37 @@ +#import "MWMDiscoveryHotelViewModel.h" + +@interface MWMDiscoveryHotelViewModel() + +@property(nonatomic, readwrite) NSString *title; +@property(nonatomic, readwrite) NSString *subtitle; +@property(nonatomic, readwrite) NSString *price; +@property(nonatomic, readwrite) NSString *distance; +@property(nonatomic, readwrite) BOOL isPopular; +@property(nonatomic, readwrite) NSString *ratingValue; +@property(nonatomic, readwrite) MWMRatingSummaryViewValueType ratingType; + +@end + +@implementation MWMDiscoveryHotelViewModel + +- (instancetype)initWithTitle:(NSString *)title + subtitle:(NSString *)subtitle + price:(NSString *)price + distance:(NSString *)distance + isPopular:(BOOL)isPopular + ratingValue:(NSString *) ratingValue + ratingType:(MWMRatingSummaryViewValueType)ratingType { + self = [super init]; + if (self) { + self.title = title; + self.subtitle = subtitle; + self.price = price; + self.distance = distance; + self.isPopular = isPopular; + self.ratingValue = ratingValue; + self.ratingType = ratingType; + } + return self; +} + +@end diff --git a/iphone/Maps/UI/Discovery/View Models/MWMDiscoverySearchViewModel.h b/iphone/Maps/UI/Discovery/View Models/MWMDiscoverySearchViewModel.h new file mode 100644 index 0000000000..48e46b19e1 --- /dev/null +++ b/iphone/Maps/UI/Discovery/View Models/MWMDiscoverySearchViewModel.h @@ -0,0 +1,22 @@ +#import "MWMRatingSummaryViewValueType.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MWMDiscoverySearchViewModel : NSObject + +@property(nonatomic, readonly) NSString *title; +@property(nonatomic, readonly) NSString *subtitle; +@property(nonatomic, readonly) NSString *distance; +@property(nonatomic, readonly) BOOL isPopular; +@property(nonatomic, readonly) NSString *ratingValue; +@property(nonatomic, readonly) MWMRatingSummaryViewValueType ratingType; + +- (instancetype)initWithTitle:(NSString *)title + subtitle:(NSString *)subtitle + distance:(NSString *)distance + isPopular:(BOOL)isPopular + ratingValue:(NSString *) ratingValue + ratingType:(MWMRatingSummaryViewValueType)ratingType; +@end + +NS_ASSUME_NONNULL_END diff --git a/iphone/Maps/UI/Discovery/View Models/MWMDiscoverySearchViewModel.m b/iphone/Maps/UI/Discovery/View Models/MWMDiscoverySearchViewModel.m new file mode 100644 index 0000000000..399d11d8fb --- /dev/null +++ b/iphone/Maps/UI/Discovery/View Models/MWMDiscoverySearchViewModel.m @@ -0,0 +1,34 @@ +#import "MWMDiscoverySearchViewModel.h" + +@interface MWMDiscoverySearchViewModel() + +@property(nonatomic, readwrite) NSString *title; +@property(nonatomic, readwrite) NSString *subtitle; +@property(nonatomic, readwrite) NSString *distance; +@property(nonatomic, readwrite) BOOL isPopular; +@property(nonatomic, readwrite) NSString *ratingValue; +@property(nonatomic, readwrite) MWMRatingSummaryViewValueType ratingType; + +@end + +@implementation MWMDiscoverySearchViewModel + +- (instancetype)initWithTitle:(NSString *)title + subtitle:(NSString *)subtitle + distance:(NSString *)distance + isPopular:(BOOL)isPopular + ratingValue:(NSString *) ratingValue + ratingType:(MWMRatingSummaryViewValueType)ratingType { + self = [super init]; + if (self) { + self.title = title; + self.subtitle = subtitle; + self.distance = distance; + self.isPopular = isPopular; + self.ratingValue = ratingValue; + self.ratingType = ratingType; + } + return self; +} + +@end