From 7720b166c6863dbb6c67280ebf72ea04e74ad129 Mon Sep 17 00:00:00 2001 From: Emin Date: Wed, 4 Sep 2024 12:05:18 +0500 Subject: [PATCH] backup, home, categories, search UI/UX --- iphone/Maps/Maps.xcodeproj/project.pbxproj | 172 +++++++++++++++++- .../Tourism/Data/Network/DTO/AllDataDTO.swift | 10 + .../Data/Network/DTO/Auth/HashDTO.swift | 5 + .../Data/Network/DTO/CategoryDTO.swift | 6 + .../Network/DTO/Details/CoordinatesDTO.swift | 14 ++ .../Data/Network/DTO/Details/PlaceDTO.swift | 28 +++ .../Data/Network/DTO/Details/ReviewDTO.swift | 23 +++ .../Network/DTO/Details/ReviewIdsDTO.swift | 5 + .../Data/Network/DTO/Details/ReviewsDTO.swift | 5 + .../Data/Network/DTO/Details/UserDTO.swift | 17 ++ .../Data/Network/DTO/FavoritesDTO.swift | 6 + .../Data/Network/DTO/FavoritesIdsDTO.swift | 5 + .../Domain/Models/Category/Category.swift | 6 + .../Models/Category/PlaceCategory.swift | 18 ++ .../Domain/Models/Details/PlaceFull.swift | 25 +++ .../Domain/Models/Details/PlaceShort.swift | 10 + .../Domain/Models/Details/Review.swift | 12 ++ .../Domain/Models/Details/ReviewToPost.swift | 8 + .../Tourism/Domain/Models/Details/User.swift | 8 + .../Tourism/Domain/Models/PlaceLocation.swift | 7 + .../Components/AppSearchBar.swift | 55 ++++++ .../Components/HorizontalSingleChoice.swift | 80 ++++++++ .../Components/Nav/AppTopBar.swift | 1 + .../Components/Special/PlacesItem.swift | 62 +++++++ .../TextFields}/AppTextField.swift | 0 .../Categories/CategoriesViewController.swift | 87 +++++++++ .../Categories/CategoriesViewModel.swift | 39 ++++ .../Favorites/FavoritesViewController.swift | 15 ++ .../Home/Screens/Home/HomeViewModel.swift | 23 +++ .../Home/Screens/Home/HorizontalPlaces.swift | 111 +++++++++++ .../New Group/HomeViewController.swift | 113 +++++++++++- .../Profile/ProfileViewController.swift | 4 +- .../Screens/Search/SearchViewController.swift | 72 ++++++++ .../Home/Screens/Search/SearchViewModel.swift | 23 +++ .../Presentation/Home/TabBarController.swift | 32 +++- 35 files changed, 1097 insertions(+), 10 deletions(-) create mode 100644 iphone/Maps/Tourism/Data/Network/DTO/AllDataDTO.swift create mode 100644 iphone/Maps/Tourism/Data/Network/DTO/Auth/HashDTO.swift create mode 100644 iphone/Maps/Tourism/Data/Network/DTO/CategoryDTO.swift create mode 100644 iphone/Maps/Tourism/Data/Network/DTO/Details/CoordinatesDTO.swift create mode 100644 iphone/Maps/Tourism/Data/Network/DTO/Details/PlaceDTO.swift create mode 100644 iphone/Maps/Tourism/Data/Network/DTO/Details/ReviewDTO.swift create mode 100644 iphone/Maps/Tourism/Data/Network/DTO/Details/ReviewIdsDTO.swift create mode 100644 iphone/Maps/Tourism/Data/Network/DTO/Details/ReviewsDTO.swift create mode 100644 iphone/Maps/Tourism/Data/Network/DTO/Details/UserDTO.swift create mode 100644 iphone/Maps/Tourism/Data/Network/DTO/FavoritesDTO.swift create mode 100644 iphone/Maps/Tourism/Data/Network/DTO/FavoritesIdsDTO.swift create mode 100644 iphone/Maps/Tourism/Domain/Models/Category/Category.swift create mode 100644 iphone/Maps/Tourism/Domain/Models/Category/PlaceCategory.swift create mode 100644 iphone/Maps/Tourism/Domain/Models/Details/PlaceFull.swift create mode 100644 iphone/Maps/Tourism/Domain/Models/Details/PlaceShort.swift create mode 100644 iphone/Maps/Tourism/Domain/Models/Details/Review.swift create mode 100644 iphone/Maps/Tourism/Domain/Models/Details/ReviewToPost.swift create mode 100644 iphone/Maps/Tourism/Domain/Models/Details/User.swift create mode 100644 iphone/Maps/Tourism/Domain/Models/PlaceLocation.swift create mode 100644 iphone/Maps/Tourism/Presentation/Components/AppSearchBar.swift create mode 100644 iphone/Maps/Tourism/Presentation/Components/HorizontalSingleChoice.swift create mode 100644 iphone/Maps/Tourism/Presentation/Components/Special/PlacesItem.swift rename iphone/Maps/Tourism/Presentation/{Home/Screens/Profile => Components/TextFields}/AppTextField.swift (100%) create mode 100644 iphone/Maps/Tourism/Presentation/Home/Screens/Categories/CategoriesViewController.swift create mode 100644 iphone/Maps/Tourism/Presentation/Home/Screens/Categories/CategoriesViewModel.swift create mode 100644 iphone/Maps/Tourism/Presentation/Home/Screens/Favorites/FavoritesViewController.swift create mode 100644 iphone/Maps/Tourism/Presentation/Home/Screens/Home/HomeViewModel.swift create mode 100644 iphone/Maps/Tourism/Presentation/Home/Screens/Home/HorizontalPlaces.swift create mode 100644 iphone/Maps/Tourism/Presentation/Home/Screens/Search/SearchViewController.swift create mode 100644 iphone/Maps/Tourism/Presentation/Home/Screens/Search/SearchViewModel.swift diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj index d74e103be6..f168f76fd8 100644 --- a/iphone/Maps/Maps.xcodeproj/project.pbxproj +++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj @@ -305,6 +305,35 @@ 529A5F0A2C858F82004FE4A1 /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 529A5F092C858F82004FE4A1 /* SDWebImageSwiftUI */; }; 529A5F162C8595BB004FE4A1 /* PersonalData.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F112C859535004FE4A1 /* PersonalData.xcdatamodeld */; }; 529A5F192C85BFF0004FE4A1 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F182C85BFF0004FE4A1 /* ToastView.swift */; }; + 529A5F1E2C86DDE5004FE4A1 /* PlaceDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F1D2C86DDE5004FE4A1 /* PlaceDTO.swift */; }; + 529A5F202C86DE14004FE4A1 /* CoordinatesDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F1F2C86DE14004FE4A1 /* CoordinatesDTO.swift */; }; + 529A5F222C86DE50004FE4A1 /* ReviewDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F212C86DE50004FE4A1 /* ReviewDTO.swift */; }; + 529A5F242C86DE7D004FE4A1 /* ReviewIdsDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F232C86DE7D004FE4A1 /* ReviewIdsDTO.swift */; }; + 529A5F262C86DE9D004FE4A1 /* ReviewsDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F252C86DE9D004FE4A1 /* ReviewsDTO.swift */; }; + 529A5F282C86DEC5004FE4A1 /* UserDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F272C86DEC5004FE4A1 /* UserDTO.swift */; }; + 529A5F2B2C86DF2D004FE4A1 /* PlaceShort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F2A2C86DF2D004FE4A1 /* PlaceShort.swift */; }; + 529A5F2D2C86DF3B004FE4A1 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F2C2C86DF3B004FE4A1 /* User.swift */; }; + 529A5F2F2C86DF51004FE4A1 /* ReviewToPost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F2E2C86DF51004FE4A1 /* ReviewToPost.swift */; }; + 529A5F312C86DF61004FE4A1 /* Review.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F302C86DF61004FE4A1 /* Review.swift */; }; + 529A5F332C86DF6F004FE4A1 /* PlaceFull.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F322C86DF6F004FE4A1 /* PlaceFull.swift */; }; + 529A5F352C86DF99004FE4A1 /* PlaceLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F342C86DF99004FE4A1 /* PlaceLocation.swift */; }; + 529A5F372C86E02E004FE4A1 /* AllDataDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F362C86E02E004FE4A1 /* AllDataDTO.swift */; }; + 529A5F392C86E048004FE4A1 /* CategoryDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F382C86E048004FE4A1 /* CategoryDTO.swift */; }; + 529A5F3B2C86E065004FE4A1 /* FavoritesDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F3A2C86E065004FE4A1 /* FavoritesDTO.swift */; }; + 529A5F3D2C86E08E004FE4A1 /* FavoritesIdsDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F3C2C86E08E004FE4A1 /* FavoritesIdsDTO.swift */; }; + 529A5F3F2C86E09B004FE4A1 /* HashDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F3E2C86E09B004FE4A1 /* HashDTO.swift */; }; + 529A5F422C86E108004FE4A1 /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F412C86E108004FE4A1 /* Category.swift */; }; + 529A5F442C86E118004FE4A1 /* PlaceCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F432C86E118004FE4A1 /* PlaceCategory.swift */; }; + 529A5F5E2C86E37A004FE4A1 /* PlacesItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F5D2C86E37A004FE4A1 /* PlacesItem.swift */; }; + 529A5F632C86E39A004FE4A1 /* HorizontalSingleChoice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F612C86E39A004FE4A1 /* HorizontalSingleChoice.swift */; }; + 529A5F642C86E39A004FE4A1 /* AppSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F622C86E39A004FE4A1 /* AppSearchBar.swift */; }; + 529A5F682C8707CD004FE4A1 /* CategoriesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F672C8707CD004FE4A1 /* CategoriesViewController.swift */; }; + 529A5F6A2C8707F9004FE4A1 /* FavoritesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F692C8707F9004FE4A1 /* FavoritesViewController.swift */; }; + 529A5F6C2C870D45004FE4A1 /* HorizontalPlaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F6B2C870D45004FE4A1 /* HorizontalPlaces.swift */; }; + 529A5F6E2C870FAF004FE4A1 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F6D2C870FAF004FE4A1 /* HomeViewModel.swift */; }; + 529A5F702C8720A8004FE4A1 /* CategoriesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 529A5F6F2C8720A8004FE4A1 /* CategoriesViewModel.swift */; }; + 52A48ADF2C882FE40081E522 /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A48ADE2C882FE40081E522 /* SearchViewController.swift */; }; + 52A48AE12C882FEE0081E522 /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A48AE02C882FEE0081E522 /* SearchViewModel.swift */; }; 52B573EC2C61E1C10047FAC9 /* SignInViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B573EB2C61E1C10047FAC9 /* SignInViewController.swift */; }; 52B573F02C61E4110047FAC9 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B573EF2C61E4110047FAC9 /* Constants.swift */; }; 52B573F22C61E8980047FAC9 /* SignUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B573F12C61E8980047FAC9 /* SignUpViewController.swift */; }; @@ -1303,6 +1332,35 @@ 528D72A02C5BBBF700D53210 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; 529A5F122C859535004FE4A1 /* PersonalData.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = PersonalData.xcdatamodel; sourceTree = ""; }; 529A5F182C85BFF0004FE4A1 /* ToastView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; + 529A5F1D2C86DDE5004FE4A1 /* PlaceDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceDTO.swift; sourceTree = ""; }; + 529A5F1F2C86DE14004FE4A1 /* CoordinatesDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatesDTO.swift; sourceTree = ""; }; + 529A5F212C86DE50004FE4A1 /* ReviewDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewDTO.swift; sourceTree = ""; }; + 529A5F232C86DE7D004FE4A1 /* ReviewIdsDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewIdsDTO.swift; sourceTree = ""; }; + 529A5F252C86DE9D004FE4A1 /* ReviewsDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewsDTO.swift; sourceTree = ""; }; + 529A5F272C86DEC5004FE4A1 /* UserDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDTO.swift; sourceTree = ""; }; + 529A5F2A2C86DF2D004FE4A1 /* PlaceShort.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceShort.swift; sourceTree = ""; }; + 529A5F2C2C86DF3B004FE4A1 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; + 529A5F2E2C86DF51004FE4A1 /* ReviewToPost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewToPost.swift; sourceTree = ""; }; + 529A5F302C86DF61004FE4A1 /* Review.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Review.swift; sourceTree = ""; }; + 529A5F322C86DF6F004FE4A1 /* PlaceFull.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceFull.swift; sourceTree = ""; }; + 529A5F342C86DF99004FE4A1 /* PlaceLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceLocation.swift; sourceTree = ""; }; + 529A5F362C86E02E004FE4A1 /* AllDataDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllDataDTO.swift; sourceTree = ""; }; + 529A5F382C86E048004FE4A1 /* CategoryDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryDTO.swift; sourceTree = ""; }; + 529A5F3A2C86E065004FE4A1 /* FavoritesDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesDTO.swift; sourceTree = ""; }; + 529A5F3C2C86E08E004FE4A1 /* FavoritesIdsDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesIdsDTO.swift; sourceTree = ""; }; + 529A5F3E2C86E09B004FE4A1 /* HashDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashDTO.swift; sourceTree = ""; }; + 529A5F412C86E108004FE4A1 /* Category.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Category.swift; sourceTree = ""; }; + 529A5F432C86E118004FE4A1 /* PlaceCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceCategory.swift; sourceTree = ""; }; + 529A5F5D2C86E37A004FE4A1 /* PlacesItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlacesItem.swift; sourceTree = ""; }; + 529A5F612C86E39A004FE4A1 /* HorizontalSingleChoice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalSingleChoice.swift; sourceTree = ""; }; + 529A5F622C86E39A004FE4A1 /* AppSearchBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppSearchBar.swift; sourceTree = ""; }; + 529A5F672C8707CD004FE4A1 /* CategoriesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoriesViewController.swift; sourceTree = ""; }; + 529A5F692C8707F9004FE4A1 /* FavoritesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesViewController.swift; sourceTree = ""; }; + 529A5F6B2C870D45004FE4A1 /* HorizontalPlaces.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HorizontalPlaces.swift; sourceTree = ""; }; + 529A5F6D2C870FAF004FE4A1 /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; + 529A5F6F2C8720A8004FE4A1 /* CategoriesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoriesViewModel.swift; sourceTree = ""; }; + 52A48ADE2C882FE40081E522 /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = ""; }; + 52A48AE02C882FEE0081E522 /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = ""; }; 52B573EB2C61E1C10047FAC9 /* SignInViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInViewController.swift; sourceTree = ""; }; 52B573EF2C61E4110047FAC9 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 52B573F12C61E8980047FAC9 /* SignUpViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpViewController.swift; sourceTree = ""; }; @@ -2888,7 +2946,6 @@ 52522F382C6DD9DA0015709C /* ProfileViewModel.swift */, 52E95F0C2C6C797B00A3FE2E /* ProfileViewController.swift */, 3D2D79D22C7CF4F70062BC3D /* PersonalDataViewController.swift */, - 3D2D79D62C7D0ABF0062BC3D /* AppTextField.swift */, ); path = Profile; sourceTree = ""; @@ -2897,6 +2954,8 @@ isa = PBXGroup; children = ( 52522F322C6DC7A40015709C /* HomeViewController.swift */, + 529A5F6B2C870D45004FE4A1 /* HorizontalPlaces.swift */, + 529A5F6D2C870FAF004FE4A1 /* HomeViewModel.swift */, ); path = Home; sourceTree = ""; @@ -2944,10 +3003,13 @@ 5260D3C62C64B87D00C673B4 /* Models */ = { isa = PBXGroup; children = ( + 529A5F402C86E0F9004FE4A1 /* Category */, + 529A5F292C86DF21004FE4A1 /* Details */, 52522F3C2C6DDF040015709C /* Profile */, 52E95EFE2C6B32D900A3FE2E /* Responses */, 5260D3E12C66287D00C673B4 /* Auth */, 52ED919C2C71F639000EE25B /* SimpleResponse.swift */, + 529A5F342C86DF99004FE4A1 /* PlaceLocation.swift */, ); path = Models; sourceTree = ""; @@ -2995,9 +3057,13 @@ 5260D3CF2C64F7E200C673B4 /* DTO */ = { isa = PBXGroup; children = ( - 5260D3D52C64F87500C673B4 /* Place */, + 5260D3D52C64F87500C673B4 /* Details */, 5260D3D42C64F87200C673B4 /* Profile */, 5260D3D22C64F84700C673B4 /* Auth */, + 529A5F362C86E02E004FE4A1 /* AllDataDTO.swift */, + 529A5F382C86E048004FE4A1 /* CategoryDTO.swift */, + 529A5F3A2C86E065004FE4A1 /* FavoritesDTO.swift */, + 529A5F3C2C86E08E004FE4A1 /* FavoritesIdsDTO.swift */, ); path = DTO; sourceTree = ""; @@ -3008,6 +3074,7 @@ 5260D3D02C64F7F100C673B4 /* SignInRequestDTO.swift */, 5260D3D72C64F8BC00C673B4 /* AuthResponseDTO.swift */, 52ED919E2C71F718000EE25B /* SignUpRequestDTO.swift */, + 529A5F3E2C86E09B004FE4A1 /* HashDTO.swift */, ); path = Auth; sourceTree = ""; @@ -3021,11 +3088,17 @@ path = Profile; sourceTree = ""; }; - 5260D3D52C64F87500C673B4 /* Place */ = { + 5260D3D52C64F87500C673B4 /* Details */ = { isa = PBXGroup; children = ( + 529A5F1D2C86DDE5004FE4A1 /* PlaceDTO.swift */, + 529A5F212C86DE50004FE4A1 /* ReviewDTO.swift */, + 529A5F232C86DE7D004FE4A1 /* ReviewIdsDTO.swift */, + 529A5F252C86DE9D004FE4A1 /* ReviewsDTO.swift */, + 529A5F272C86DEC5004FE4A1 /* UserDTO.swift */, + 529A5F1F2C86DE14004FE4A1 /* CoordinatesDTO.swift */, ); - path = Place; + path = Details; sourceTree = ""; }; 5260D3DB2C66205700C673B4 /* Repositories */ = { @@ -3081,6 +3154,7 @@ 527D5E762C60D92900736A85 /* Components */ = { isa = PBXGroup; children = ( + 529A5F5C2C86E37A004FE4A1 /* Special */, 529A5F172C85BF99004FE4A1 /* ToastView */, 3D585BF72C768BED005DF71F /* Buttons */, 52522F442C6DFD220015709C /* Nav */, @@ -3090,6 +3164,8 @@ 52522F4B2C6E10FD0015709C /* LoadImg.swift */, 3D2D79D42C7CF6970062BC3D /* Spacers.swift */, 3D2D79DC2C7DE34B0062BC3D /* PhotoPickerView.swift */, + 529A5F622C86E39A004FE4A1 /* AppSearchBar.swift */, + 529A5F612C86E39A004FE4A1 /* HorizontalSingleChoice.swift */, ); path = Components; sourceTree = ""; @@ -3129,6 +3205,61 @@ path = ToastView; sourceTree = ""; }; + 529A5F292C86DF21004FE4A1 /* Details */ = { + isa = PBXGroup; + children = ( + 529A5F2A2C86DF2D004FE4A1 /* PlaceShort.swift */, + 529A5F2C2C86DF3B004FE4A1 /* User.swift */, + 529A5F2E2C86DF51004FE4A1 /* ReviewToPost.swift */, + 529A5F302C86DF61004FE4A1 /* Review.swift */, + 529A5F322C86DF6F004FE4A1 /* PlaceFull.swift */, + ); + path = Details; + sourceTree = ""; + }; + 529A5F402C86E0F9004FE4A1 /* Category */ = { + isa = PBXGroup; + children = ( + 529A5F412C86E108004FE4A1 /* Category.swift */, + 529A5F432C86E118004FE4A1 /* PlaceCategory.swift */, + ); + path = Category; + sourceTree = ""; + }; + 529A5F5C2C86E37A004FE4A1 /* Special */ = { + isa = PBXGroup; + children = ( + 529A5F5D2C86E37A004FE4A1 /* PlacesItem.swift */, + ); + path = Special; + sourceTree = ""; + }; + 529A5F652C8706C7004FE4A1 /* Categories */ = { + isa = PBXGroup; + children = ( + 529A5F672C8707CD004FE4A1 /* CategoriesViewController.swift */, + 529A5F6F2C8720A8004FE4A1 /* CategoriesViewModel.swift */, + ); + path = Categories; + sourceTree = ""; + }; + 529A5F662C8706D1004FE4A1 /* Favorites */ = { + isa = PBXGroup; + children = ( + 529A5F692C8707F9004FE4A1 /* FavoritesViewController.swift */, + ); + path = Favorites; + sourceTree = ""; + }; + 52A48ADB2C882FD70081E522 /* Search */ = { + isa = PBXGroup; + children = ( + 52A48ADE2C882FE40081E522 /* SearchViewController.swift */, + 52A48AE02C882FEE0081E522 /* SearchViewModel.swift */, + ); + path = Search; + sourceTree = ""; + }; 52B189972C53B9E900B5B6F9 /* Home */ = { isa = PBXGroup; children = ( @@ -3152,6 +3283,7 @@ 52B573F32C61F10B0047FAC9 /* TextFields */ = { isa = PBXGroup; children = ( + 3D2D79D62C7D0ABF0062BC3D /* AppTextField.swift */, 52B573F42C61F11E0047FAC9 /* AuhtTextField.swift */, 52B573F62C61F4D00047FAC9 /* PasswordTextField.swift */, ); @@ -3178,6 +3310,9 @@ 52E2D39B2C58E72900A8843A /* Screens */ = { isa = PBXGroup; children = ( + 52A48ADB2C882FD70081E522 /* Search */, + 529A5F662C8706D1004FE4A1 /* Favorites */, + 529A5F652C8706C7004FE4A1 /* Categories */, 52522F352C6DD9860015709C /* Home */, 52522F342C6DD9480015709C /* Profile */, ); @@ -4733,6 +4868,8 @@ 99A906E623F6F7030005872B /* OpeningHoursViewController.swift in Sources */, 343064411E9FDC7300DC7665 /* SearchIndex.swift in Sources */, CDCA273F2238087700167D87 /* MWMCarPlaySearchService.mm in Sources */, + 529A5F392C86E048004FE4A1 /* CategoryDTO.swift in Sources */, + 529A5F1E2C86DDE5004FE4A1 /* PlaceDTO.swift in Sources */, 348B926D1FF3B5E100379009 /* UIView+Animation.swift in Sources */, F6E2FDE91E097BA00083EBEC /* MWMObjectsCategorySelectorController.mm in Sources */, 34AB665F1FC5AA330078E451 /* TransportTransitIntermediatePoint.swift in Sources */, @@ -4775,6 +4912,7 @@ 337F98B421D3C9F200C8AC27 /* SearchHistoryViewController.swift in Sources */, 3404F49D2028A2430090E401 /* BMCActionsCell.swift in Sources */, F6E2FD8F1E097BA00083EBEC /* MWMNoMapsViewController.mm in Sources */, + 529A5F442C86E118004FE4A1 /* PlaceCategory.swift in Sources */, 52522F332C6DC7A40015709C /* HomeViewController.swift in Sources */, 993DF12C23F6BDB100AC231A /* Theme.swift in Sources */, 47CA68D8250044C500671019 /* BookmarksListRouter.swift in Sources */, @@ -4788,6 +4926,7 @@ 47E8163323B17734008FD836 /* MWMStorage+UI.m in Sources */, 3D2D79BA2C7C508E0062BC3D /* SingleEntityCoreDataController.swift in Sources */, 993DF11123F6BDB100AC231A /* UILabelRenderer.swift in Sources */, + 52A48AE12C882FEE0081E522 /* SearchViewModel.swift in Sources */, 34AB66471FC5AA330078E451 /* RouteManagerTableView.swift in Sources */, 52D588BA2C5CE2E800AB96B3 /* Font.swift in Sources */, 9989273C2449E60200260CE2 /* BottomMenuInteractor.swift in Sources */, @@ -4833,6 +4972,7 @@ 47E3C7292111E614008B3B27 /* FadeInAnimatedTransitioning.swift in Sources */, ED79A5D42BDF8D6100952D1F /* MetadataItem.swift in Sources */, 34AB667D1FC5AA330078E451 /* MWMRoutePreview.mm in Sources */, + 529A5F3F2C86E09B004FE4A1 /* HashDTO.swift in Sources */, 993DF11B23F6BDB100AC231A /* UIViewRenderer.swift in Sources */, 529A5F162C8595BB004FE4A1 /* PersonalData.xcdatamodeld in Sources */, 99C964302428C27A00E41723 /* PlacePageHeaderView.swift in Sources */, @@ -4843,9 +4983,11 @@ 993DF0B523F6B2EF00AC231A /* PlacePageElevationLayout.swift in Sources */, 44360A0D2A7D34990016F412 /* TransportRuler.swift in Sources */, CD6E8677226774C700D1EDF7 /* CPConstants.swift in Sources */, + 529A5F642C86E39A004FE4A1 /* AppSearchBar.swift in Sources */, 99A906DE23F6F7030005872B /* PlacePageBookmarkViewController.swift in Sources */, 52B573F92C6223CE0047FAC9 /* AuthBackButton.swift in Sources */, 52522F4A2C6DFE580015709C /* BackButtonWithText.swift in Sources */, + 529A5F2B2C86DF2D004FE4A1 /* PlaceShort.swift in Sources */, F6791B141C43DF0B007A8A6E /* MWMStartButton.m in Sources */, 9977E6A12480E1EE0073780C /* BottomMenuLayerButton.swift in Sources */, 471527372491C20500E91BBA /* SelectBookmarkGroupViewController.swift in Sources */, @@ -4854,6 +4996,7 @@ 34AC8FD11EFC02C000E7F910 /* MWMRoutePoint.mm in Sources */, CDB4D5012231412900104869 /* ListTemplateBuilder.swift in Sources */, 99A906F323FA95AB0005872B /* PlacePageStyleSheet.swift in Sources */, + 529A5F6C2C870D45004FE4A1 /* HorizontalPlaces.swift in Sources */, 52522F2E2C6C9E070015709C /* UserPreferences.swift in Sources */, 6741A9CF1BF340DE002C974C /* MWMLocationAlert.m in Sources */, F6E2FDA11E097BA00083EBEC /* MWMEditorAdditionalNamesTableViewController.mm in Sources */, @@ -4871,6 +5014,7 @@ 34AB66051FC5AA320078E451 /* MWMNavigationDashboardManager+Entity.mm in Sources */, 993DF12A23F6BDB100AC231A /* Style.swift in Sources */, 34ABA6171C2D185C00FE1BEC /* MWMAuthorizationOSMLoginViewController.mm in Sources */, + 529A5F242C86DE7D004FE4A1 /* ReviewIdsDTO.swift in Sources */, ED9966802B94FBC20083CE55 /* ColorPicker.swift in Sources */, 993DF10423F6BDB100AC231A /* UIView+styleName.swift in Sources */, 998927302449DE1500260CE2 /* TabBarArea.swift in Sources */, @@ -4885,6 +5029,7 @@ 470E1674252AD7F2002D201A /* BookmarksListInfoViewController.swift in Sources */, 47B9065521C7FA400079C85E /* NSString+MD5.m in Sources */, CDB4D5022231412900104869 /* SettingsTemplateBuilder.swift in Sources */, + 529A5F682C8707CD004FE4A1 /* CategoriesViewController.swift in Sources */, 993DF10A23F6BDB100AC231A /* UISwitchRenderer.swift in Sources */, 99C9642C2428C0F700E41723 /* PlacePageHeaderBuilder.swift in Sources */, 99C9642B2428C0F700E41723 /* PlacePageHeaderViewController.swift in Sources */, @@ -4899,13 +5044,16 @@ F6E2FF481E097BA00083EBEC /* SettingsTableViewSelectableCell.swift in Sources */, ED63CEB92BDF8F9D006155C4 /* SettingsTableViewiCloudSwitchCell.swift in Sources */, 47CA68D4250043C000671019 /* BookmarksListPresenter.swift in Sources */, + 529A5F2D2C86DF3B004FE4A1 /* User.swift in Sources */, 3D2D79D72C7D0AC00062BC3D /* AppTextField.swift in Sources */, 527D5E7F2C60E69C00736A85 /* Layouting.swift in Sources */, F6E2FF451E097BA00083EBEC /* SettingsTableViewLinkCell.swift in Sources */, 34C9BD0A1C6DBCDA000DC38D /* MWMNavigationController.m in Sources */, ED1080A72B791CFE0023F27E /* SocialMediaCollectionViewHeader.swift in Sources */, F6E2FE311E097BA00083EBEC /* MWMStreetEditorViewController.mm in Sources */, + 529A5F632C86E39A004FE4A1 /* HorizontalSingleChoice.swift in Sources */, F6E2FE281E097BA00083EBEC /* MWMOpeningHoursSection.mm in Sources */, + 529A5F372C86E02E004FE4A1 /* AllDataDTO.swift in Sources */, 52B573FE2C624A520047FAC9 /* CountryPickerUtils.swift in Sources */, 3406FA161C6E0C3300E9FAD2 /* MWMMapDownloadDialog.mm in Sources */, 340416481E7BF28E00E2B6D6 /* UIView+Snapshot.swift in Sources */, @@ -4915,8 +5063,10 @@ 34C9BD031C6DB693000DC38D /* MWMTableViewController.m in Sources */, 52E95F0D2C6C797B00A3FE2E /* ProfileViewController.swift in Sources */, F6E2FD8C1E097BA00083EBEC /* MWMNoMapsView.m in Sources */, + 529A5F312C86DF61004FE4A1 /* Review.swift in Sources */, 34D3B0361E389D05004100F9 /* MWMEditorSelectTableViewCell.m in Sources */, 990128562449A82500C72B10 /* BottomTabBarView.swift in Sources */, + 529A5F422C86E108004FE4A1 /* Category.swift in Sources */, F6E2FD711E097BA00083EBEC /* MWMMapDownloaderTableViewCell.m in Sources */, F6E2FE4F1E097BA00083EBEC /* MWMActionBarButton.m in Sources */, 47F86CFF20C936FC00FEE291 /* TabView.swift in Sources */, @@ -4926,6 +5076,7 @@ 349D1CE41E3F836900A878FD /* UIViewController+Hierarchy.swift in Sources */, 52B573F02C61E4110047FAC9 /* Constants.swift in Sources */, 8CB13C3B2BF1276A004288F2 /* CarplayPlaceholderView.swift in Sources */, + 529A5F202C86DE14004FE4A1 /* CoordinatesDTO.swift in Sources */, CDB4D4E1222D70DF00104869 /* CarPlayMapViewController.swift in Sources */, 471AB98923AA8A3500F56D49 /* IDownloaderDataSource.swift in Sources */, 52ED91B02C73030D000EE25B /* PersonalDataDTO.swift in Sources */, @@ -4942,6 +5093,7 @@ 99AAEA78244DA9810039D110 /* BottomMenuTransitioningManager.swift in Sources */, 4761BE2A252D3DB900EE2DE4 /* SubgroupCell.swift in Sources */, 1DFA2F6A20D3B57400FB2C66 /* UIColor+PartnerColor.m in Sources */, + 529A5F6E2C870FAF004FE4A1 /* HomeViewModel.swift in Sources */, 9989273B2449E60200260CE2 /* BottomMenuBuilder.swift in Sources */, 993DF10F23F6BDB100AC231A /* UIActivityIndicatorRenderer.swift in Sources */, 99A614E423CDD1D900D8D8D0 /* UIButton+RuntimeAttributes.m in Sources */, @@ -4971,7 +5123,9 @@ 993DF11923F6BDB100AC231A /* UITextFieldRenderer.swift in Sources */, 5260D3E82C66439400C673B4 /* AuthRepository.swift in Sources */, 342CC5F21C2D7730005F3FE5 /* MWMAuthorizationLoginViewController.mm in Sources */, + 529A5F5E2C86E37A004FE4A1 /* PlacesItem.swift in Sources */, 340475591E081A4600C92850 /* WebViewController.m in Sources */, + 529A5F262C86DE9D004FE4A1 /* ReviewsDTO.swift in Sources */, 3404F4992028A20D0090E401 /* BMCCategoryCell.swift in Sources */, F62607FD207B790300176C5A /* SpinnerAlert.swift in Sources */, 3444DFD21F17620C00E73099 /* MWMMapWidgetsHelper.mm in Sources */, @@ -5003,6 +5157,7 @@ F660DEE51EAF4F59004DC056 /* MWMLocationManager+SpeedAndAltitude.swift in Sources */, F6E2FDF21E097BA00083EBEC /* MWMOpeningHoursAddScheduleTableViewCell.mm in Sources */, 3304306D21D4EAFB00317CA3 /* SearchCategoryCell.swift in Sources */, + 529A5F222C86DE50004FE4A1 /* ReviewDTO.swift in Sources */, ED79A5AB2BD7AA9C00952D1F /* LoadingOverlayViewController.swift in Sources */, 34AB66111FC5AA320078E451 /* NavigationTurnsView.swift in Sources */, 475ED78624C7C7300063ADC7 /* ValueStepperViewRenderer.swift in Sources */, @@ -5020,12 +5175,14 @@ 34E776331F15FAC2003040B3 /* MWMPlacePageManagerHelper.mm in Sources */, 52ED919D2C71F639000EE25B /* SimpleResponse.swift in Sources */, 462452E92BD052C0004C85E1 /* MWMEditorSegmentedTableViewCell.mm in Sources */, + 529A5F332C86DF6F004FE4A1 /* PlaceFull.swift in Sources */, 993DF12D23F6BDB100AC231A /* GlobalStyleSheet.swift in Sources */, F6E2FF361E097BA00083EBEC /* MWMSearchSuggestionCell.mm in Sources */, 3472B5CF200F4A2B00DC6CD5 /* BackgroundFetchTask.swift in Sources */, 477219052243E79500E5B227 /* DrivingOptionsViewController.swift in Sources */, CDB4D4E4222E8FF600104869 /* CarPlayService.swift in Sources */, F6E2FF3C1E097BA00083EBEC /* MWMSearchTableView.m in Sources */, + 529A5F2F2C86DF51004FE4A1 /* ReviewToPost.swift in Sources */, 52ED91A72C72C58A000EE25B /* CurrencyPersistenceController.swift in Sources */, F6E2FF661E097BA00083EBEC /* MWMTTSSettingsViewController.mm in Sources */, 3454D7C21E07F045004AF2AD /* NSString+Categories.m in Sources */, @@ -5088,6 +5245,7 @@ 34AB66321FC5AA330078E451 /* RouteManagerHeaderView.swift in Sources */, 347040301EA6470700038379 /* BorderedButton.swift in Sources */, F6E2FF4B1E097BA00083EBEC /* SettingsTableViewSwitchCell.swift in Sources */, + 529A5F702C8720A8004FE4A1 /* CategoriesViewModel.swift in Sources */, 993DF12623F6BDB100AC231A /* SwizzleStyle.m in Sources */, 527D5E782C60D94B00736A85 /* AppButton.swift in Sources */, 993DF10E23F6BDB100AC231A /* UIButtonRenderer.swift in Sources */, @@ -5118,6 +5276,7 @@ 5260D3D82C64F8BC00C673B4 /* AuthResponseDTO.swift in Sources */, 99F8B4C623B644A6009FF0B4 /* MapStyleSheet.swift in Sources */, 52CD2D892C6F0AF200CCC439 /* ProfileRepository.swift in Sources */, + 529A5F3D2C86E08E004FE4A1 /* FavoritesIdsDTO.swift in Sources */, 99012851244732DB00C72B10 /* BottomTabBarViewController.swift in Sources */, 52522F3E2C6DDF190015709C /* PersonalData.swift in Sources */, F63AF5061EA6162400A1DB98 /* FilterTypeCell.swift in Sources */, @@ -5130,6 +5289,7 @@ 471A7BC02481C82500A0D4C1 /* BookmarkTitleCell.swift in Sources */, 52522F392C6DD9DA0015709C /* ProfileViewModel.swift in Sources */, 47F67D1521CAB21B0069754E /* MWMImageCoder.m in Sources */, + 529A5F352C86DF99004FE4A1 /* PlaceLocation.swift in Sources */, 34AB66861FC5AA330078E451 /* MWMNavigationInfoView.mm in Sources */, 34C9BD051C6DB693000DC38D /* MWMViewController.m in Sources */, 5260D3E02C6624B900C673B4 /* AuthRepositoryImpl.swift in Sources */, @@ -5178,6 +5338,7 @@ F6E2FE2B1E097BA00083EBEC /* MWMStreetEditorEditTableViewCell.m in Sources */, 3D2D79C32C7C80E60062BC3D /* PersonalDataPersistenceController.swift in Sources */, 34AB66891FC5AA330078E451 /* NavigationControlView.swift in Sources */, + 52A48ADF2C882FE40081E522 /* SearchViewController.swift in Sources */, 479EE94A2292FB03009DEBA6 /* ActivityIndicator.swift in Sources */, ED3EAC202B03C88100220A4A /* BottomTabBarButton.swift in Sources */, EDFDFB612B74E2500013A44C /* DonationView.swift in Sources */, @@ -5192,6 +5353,7 @@ 995F1613244F0AA50060631D /* BottomMenuLayersCell.swift in Sources */, 993DF10723F6BDB100AC231A /* UIColor+image.swift in Sources */, 3454D7D71E07F045004AF2AD /* UIKitCategories.m in Sources */, + 529A5F3B2C86E065004FE4A1 /* FavoritesDTO.swift in Sources */, 34AB39C21D2BD8310021857D /* MWMStopButton.m in Sources */, 3488B01A1E9D0B230068AFD8 /* UIColor+Modifications.swift in Sources */, 6741AA281BF340DE002C974C /* MWMAlert.mm in Sources */, @@ -5199,12 +5361,14 @@ 993DF11323F6BDB100AC231A /* UITableViewRenderer.swift in Sources */, 34AB66261FC5AA330078E451 /* RouteManagerDimView.swift in Sources */, EDFDFB4A2B722A310013A44C /* SocialMediaCollectionViewCell.swift in Sources */, + 529A5F282C86DEC5004FE4A1 /* UserDTO.swift in Sources */, 6741AA2B1BF340DE002C974C /* CircleView.m in Sources */, 4788739220EE326500F6826B /* VerticallyAlignedButton.swift in Sources */, EDFDFB462B7139490013A44C /* AboutInfo.swift in Sources */, 3444DFDE1F18A5AF00E73099 /* SideButtonsArea.swift in Sources */, CDCA278622451F5000167D87 /* RouteInfo.swift in Sources */, 3467CEB6202C6FA900D3C670 /* BMCNotificationsCell.swift in Sources */, + 529A5F6A2C8707F9004FE4A1 /* FavoritesViewController.swift in Sources */, 337F98B221D3BAE600C8AC27 /* SearchCategoriesViewController.swift in Sources */, F6E2FE0A1E097BA00083EBEC /* MWMOpeningHoursDeleteScheduleTableViewCell.mm in Sources */, 3454D7DA1E07F045004AF2AD /* UILabel+RuntimeAttributes.m in Sources */, diff --git a/iphone/Maps/Tourism/Data/Network/DTO/AllDataDTO.swift b/iphone/Maps/Tourism/Data/Network/DTO/AllDataDTO.swift new file mode 100644 index 0000000000..25b669a3b0 --- /dev/null +++ b/iphone/Maps/Tourism/Data/Network/DTO/AllDataDTO.swift @@ -0,0 +1,10 @@ +import Foundation + +struct AllDataDTO: Codable { + let attractions: [PlaceDTO] + let restaurants: [PlaceDTO] + let accommodations: [PlaceDTO] + let attractionsHash: String + let restaurantsHash: String + let accommodationsHash: String +} diff --git a/iphone/Maps/Tourism/Data/Network/DTO/Auth/HashDTO.swift b/iphone/Maps/Tourism/Data/Network/DTO/Auth/HashDTO.swift new file mode 100644 index 0000000000..9a156e5fd6 --- /dev/null +++ b/iphone/Maps/Tourism/Data/Network/DTO/Auth/HashDTO.swift @@ -0,0 +1,5 @@ +import Foundation + +struct HashDTO: Codable { + let hash: String +} diff --git a/iphone/Maps/Tourism/Data/Network/DTO/CategoryDTO.swift b/iphone/Maps/Tourism/Data/Network/DTO/CategoryDTO.swift new file mode 100644 index 0000000000..40147bc59c --- /dev/null +++ b/iphone/Maps/Tourism/Data/Network/DTO/CategoryDTO.swift @@ -0,0 +1,6 @@ +import Foundation + +struct CategoryDTO: Codable { + let data: [PlaceDTO] + let hash: String +} diff --git a/iphone/Maps/Tourism/Data/Network/DTO/Details/CoordinatesDTO.swift b/iphone/Maps/Tourism/Data/Network/DTO/Details/CoordinatesDTO.swift new file mode 100644 index 0000000000..0e71f63965 --- /dev/null +++ b/iphone/Maps/Tourism/Data/Network/DTO/Details/CoordinatesDTO.swift @@ -0,0 +1,14 @@ +import Foundation + +struct CoordinatesDTO: Codable { + let latitude: String? + let longitude: String? + + func toPlaceLocation(name: String) -> PlaceLocation? { + guard let latitude = latitude, let longitude = longitude, + let lat = Double(latitude), let lon = Double(longitude) else { + return nil + } + return PlaceLocation(name: name, lat: lat, lon: lon) + } +} diff --git a/iphone/Maps/Tourism/Data/Network/DTO/Details/PlaceDTO.swift b/iphone/Maps/Tourism/Data/Network/DTO/Details/PlaceDTO.swift new file mode 100644 index 0000000000..a741ec5c24 --- /dev/null +++ b/iphone/Maps/Tourism/Data/Network/DTO/Details/PlaceDTO.swift @@ -0,0 +1,28 @@ +import Foundation + +struct PlaceDTO: Codable { + let id: Int64 + let name: String + let coordinates: CoordinatesDTO? + let cover: String + let feedbacks: [ReviewDTO]? + let gallery: [String] + let rating: String + let shortDescription: String + let longDescription: String + + func toPlaceFull(isFavorite: Bool) -> PlaceFull { + return PlaceFull( + id: id, + name: name, + rating: Double(rating) ?? 0.0, + excerpt: shortDescription, + description: longDescription, + placeLocation: coordinates?.toPlaceLocation(name: name), + cover: cover, + pics: gallery, + reviews: feedbacks?.map { $0.toReview() } ?? [], + isFavorite: isFavorite + ) + } +} diff --git a/iphone/Maps/Tourism/Data/Network/DTO/Details/ReviewDTO.swift b/iphone/Maps/Tourism/Data/Network/DTO/Details/ReviewDTO.swift new file mode 100644 index 0000000000..aad1917716 --- /dev/null +++ b/iphone/Maps/Tourism/Data/Network/DTO/Details/ReviewDTO.swift @@ -0,0 +1,23 @@ +import Foundation + +struct ReviewDTO: Codable { + let id: Int64 + let markId: Int64 + let images: [String] + let message: String + let points: Int + let createdAt: String + let user: UserDTO + + func toReview() -> Review { + return Review( + id: id, + placeId: markId, + rating: points, + user: user.toUser(), + date: createdAt, + comment: message, + picsUrls: images + ) + } +} diff --git a/iphone/Maps/Tourism/Data/Network/DTO/Details/ReviewIdsDTO.swift b/iphone/Maps/Tourism/Data/Network/DTO/Details/ReviewIdsDTO.swift new file mode 100644 index 0000000000..29678fb69a --- /dev/null +++ b/iphone/Maps/Tourism/Data/Network/DTO/Details/ReviewIdsDTO.swift @@ -0,0 +1,5 @@ +import Foundation + +struct ReviewIdsDTO: Codable { + let feedbacks: [Int64] +} diff --git a/iphone/Maps/Tourism/Data/Network/DTO/Details/ReviewsDTO.swift b/iphone/Maps/Tourism/Data/Network/DTO/Details/ReviewsDTO.swift new file mode 100644 index 0000000000..fa35a6ca8f --- /dev/null +++ b/iphone/Maps/Tourism/Data/Network/DTO/Details/ReviewsDTO.swift @@ -0,0 +1,5 @@ +import Foundation + +struct ReviewsDTO: Codable { + let data: [ReviewDTO] +} diff --git a/iphone/Maps/Tourism/Data/Network/DTO/Details/UserDTO.swift b/iphone/Maps/Tourism/Data/Network/DTO/Details/UserDTO.swift new file mode 100644 index 0000000000..fd46145f72 --- /dev/null +++ b/iphone/Maps/Tourism/Data/Network/DTO/Details/UserDTO.swift @@ -0,0 +1,17 @@ +import Foundation + +struct UserDTO: Codable { + let id: Int64 + let avatar: String + let country: String + let fullName: String + + func toUser() -> User { + return User( + id: id, + name: fullName, + pfpUrl: avatar, + countryCodeName: country + ) + } +} diff --git a/iphone/Maps/Tourism/Data/Network/DTO/FavoritesDTO.swift b/iphone/Maps/Tourism/Data/Network/DTO/FavoritesDTO.swift new file mode 100644 index 0000000000..2f2f08b9c3 --- /dev/null +++ b/iphone/Maps/Tourism/Data/Network/DTO/FavoritesDTO.swift @@ -0,0 +1,6 @@ +import Foundation + +struct FavoritesDTO: Codable { + let data: [PlaceDTO] +} + diff --git a/iphone/Maps/Tourism/Data/Network/DTO/FavoritesIdsDTO.swift b/iphone/Maps/Tourism/Data/Network/DTO/FavoritesIdsDTO.swift new file mode 100644 index 0000000000..76d2285522 --- /dev/null +++ b/iphone/Maps/Tourism/Data/Network/DTO/FavoritesIdsDTO.swift @@ -0,0 +1,5 @@ +import Foundation + +struct FavoritesIdsDTO: Codable { + let marks: [Int64] +} diff --git a/iphone/Maps/Tourism/Domain/Models/Category/Category.swift b/iphone/Maps/Tourism/Domain/Models/Category/Category.swift new file mode 100644 index 0000000000..85bf6123b5 --- /dev/null +++ b/iphone/Maps/Tourism/Domain/Models/Category/Category.swift @@ -0,0 +1,6 @@ +import Foundation + +struct Category: Codable { + let value: String? + let label: String +} diff --git a/iphone/Maps/Tourism/Domain/Models/Category/PlaceCategory.swift b/iphone/Maps/Tourism/Domain/Models/Category/PlaceCategory.swift new file mode 100644 index 0000000000..5cff2cb14d --- /dev/null +++ b/iphone/Maps/Tourism/Domain/Models/Category/PlaceCategory.swift @@ -0,0 +1,18 @@ +import Foundation + +enum PlaceCategory: String, Codable { + case sights = "1" + case restaurants = "2" + case hotels = "3" + + var serverName: String { + switch self { + case .sights: + return "attractions" + case .restaurants: + return "restaurants" + case .hotels: + return "accommodations" + } + } +} diff --git a/iphone/Maps/Tourism/Domain/Models/Details/PlaceFull.swift b/iphone/Maps/Tourism/Domain/Models/Details/PlaceFull.swift new file mode 100644 index 0000000000..86f0144237 --- /dev/null +++ b/iphone/Maps/Tourism/Domain/Models/Details/PlaceFull.swift @@ -0,0 +1,25 @@ +import Foundation + +struct PlaceFull: Codable { + let id: Int64 + let name: String + let rating: Double + let excerpt: String + let description: String + let placeLocation: PlaceLocation? + let cover: String + let pics: [String] + let reviews: [Review]? + let isFavorite: Bool + + func toPlaceShort() -> PlaceShort { + return PlaceShort( + id: id, + name: name, + cover: cover, + rating: rating, + excerpt: excerpt, + isFavorite: isFavorite + ) + } +} diff --git a/iphone/Maps/Tourism/Domain/Models/Details/PlaceShort.swift b/iphone/Maps/Tourism/Domain/Models/Details/PlaceShort.swift new file mode 100644 index 0000000000..7654e2ba1b --- /dev/null +++ b/iphone/Maps/Tourism/Domain/Models/Details/PlaceShort.swift @@ -0,0 +1,10 @@ +import Foundation + +struct PlaceShort: Codable, Identifiable { + let id: Int64 + let name: String + let cover: String? + let rating: Double? + let excerpt: String? + var isFavorite: Bool +} diff --git a/iphone/Maps/Tourism/Domain/Models/Details/Review.swift b/iphone/Maps/Tourism/Domain/Models/Details/Review.swift new file mode 100644 index 0000000000..963d92d723 --- /dev/null +++ b/iphone/Maps/Tourism/Domain/Models/Details/Review.swift @@ -0,0 +1,12 @@ +import Foundation + +struct Review: Codable { + let id: Int64 + let placeId: Int64 + let rating: Int + let user: User + let date: String? + let comment: String? + let picsUrls: [String] + var deletionPlanned: Bool = false +} diff --git a/iphone/Maps/Tourism/Domain/Models/Details/ReviewToPost.swift b/iphone/Maps/Tourism/Domain/Models/Details/ReviewToPost.swift new file mode 100644 index 0000000000..fa264636fd --- /dev/null +++ b/iphone/Maps/Tourism/Domain/Models/Details/ReviewToPost.swift @@ -0,0 +1,8 @@ +import Foundation + +struct ReviewToPost: Codable { + let placeId: Int64 + let comment: String + let rating: Int + let images: [URL] // Using URL to represent file paths +} diff --git a/iphone/Maps/Tourism/Domain/Models/Details/User.swift b/iphone/Maps/Tourism/Domain/Models/Details/User.swift new file mode 100644 index 0000000000..4f6ead3349 --- /dev/null +++ b/iphone/Maps/Tourism/Domain/Models/Details/User.swift @@ -0,0 +1,8 @@ +import Foundation + +struct User: Codable { + let id: Int64 + let name: String + let pfpUrl: String? + let countryCodeName: String +} diff --git a/iphone/Maps/Tourism/Domain/Models/PlaceLocation.swift b/iphone/Maps/Tourism/Domain/Models/PlaceLocation.swift new file mode 100644 index 0000000000..58f1a6193e --- /dev/null +++ b/iphone/Maps/Tourism/Domain/Models/PlaceLocation.swift @@ -0,0 +1,7 @@ +import Foundation + +struct PlaceLocation: Codable { + let name: String + let lat: Double + let lon: Double +} diff --git a/iphone/Maps/Tourism/Presentation/Components/AppSearchBar.swift b/iphone/Maps/Tourism/Presentation/Components/AppSearchBar.swift new file mode 100644 index 0000000000..84c142730e --- /dev/null +++ b/iphone/Maps/Tourism/Presentation/Components/AppSearchBar.swift @@ -0,0 +1,55 @@ +import SwiftUI + +struct AppSearchBar: View { + @Binding var query: String + var onSearchClicked: ((String) -> Void)? + var onClearClicked: () -> Void + + @State private var isActive = false + + var body: some View { + HStack { + Image(systemName: "magnifyingglass") + .foregroundColor(Color.hint) + .onTapGesture { + onSearchClicked?(query) + } + + TextField("Search", text: $query, onCommit: { + onSearchClicked?(query) + }) + .textFieldStyle(PlainTextFieldStyle()) + .font(.medium(size: 16)) + .foregroundColor(Color.onBackground) + + if !query.isEmpty { + Button(action: onClearClicked) { + Image(systemName: "xmark.circle.fill") + .foregroundColor(Color.hint) + } + } + } + .padding() + .background(Color.surface) + .cornerRadius(16) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color.border, lineWidth: 1) + ) + .onTapGesture { + isActive = true + } + } +} + +struct AppSearchBar_Previews: PreviewProvider { + static var previews: some View { + AppSearchBar( + query: .constant(""), + onSearchClicked: { _ in }, + onClearClicked: {} + ) + .previewLayout(.sizeThatFits) + .padding() + } +} diff --git a/iphone/Maps/Tourism/Presentation/Components/HorizontalSingleChoice.swift b/iphone/Maps/Tourism/Presentation/Components/HorizontalSingleChoice.swift new file mode 100644 index 0000000000..7d59f9e600 --- /dev/null +++ b/iphone/Maps/Tourism/Presentation/Components/HorizontalSingleChoice.swift @@ -0,0 +1,80 @@ +import SwiftUI + +struct SingleChoiceItem { + let id: T + let label: String +} + +struct HorizontalSingleChoice: View { + let items: [SingleChoiceItem] + @Binding var selected: SingleChoiceItem? + var onSelectedChanged: (SingleChoiceItem) -> Void + var selectedColor: SwiftUI.Color = Color.selected + var unselectedColor: SwiftUI.Color = Color.background + + var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack() { + HorizontalSpace(width: 16) + ForEach(items, id: \.id) { item in + SingleChoiceItemView( + item: item, + isSelected: item.id == selected?.id, + onClick: { + selected = item + onSelectedChanged(item) + }, + selectedColor: selectedColor, + unselectedColor: unselectedColor + ) + HorizontalSpace(width: 8) + } + } + } + } +} + +struct SingleChoiceItemView: View { + let item: SingleChoiceItem + let isSelected: Bool + let onClick: () -> Void + let selectedColor: SwiftUI.Color + let unselectedColor: SwiftUI.Color + + var body: some View { + Text(item.label) + .font(.medium(size: 16)) + .foregroundColor(isSelected ? Color.onSelected : Color.onBackground) + .padding(12) + .background(isSelected ? selectedColor : unselectedColor) + .cornerRadius(16) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color.border, lineWidth: 1) + ) + .onTapGesture(perform: onClick) + } +} + +struct HorizontalSingleChoice_Previews: PreviewProvider { + static var previews: some View { + PreviewWrapper() + } + + struct PreviewWrapper: View { + @State private var selected: SingleChoiceItem? + let items = [ + SingleChoiceItem(id: "1", label: "Option 1"), + SingleChoiceItem(id: "2", label: "Option 2"), + SingleChoiceItem(id: "3", label: "Option 3") + ] + + var body: some View { + HorizontalSingleChoice( + items: items, + selected: $selected, + onSelectedChanged: { _ in } + ) + } + } +} diff --git a/iphone/Maps/Tourism/Presentation/Components/Nav/AppTopBar.swift b/iphone/Maps/Tourism/Presentation/Components/Nav/AppTopBar.swift index 4963664207..6a8ff8f13a 100644 --- a/iphone/Maps/Tourism/Presentation/Components/Nav/AppTopBar.swift +++ b/iphone/Maps/Tourism/Presentation/Components/Nav/AppTopBar.swift @@ -10,6 +10,7 @@ struct AppTopBar: View { HStack { if let onBackClick = onBackClick { AppBackButton(onBackClick: onBackClick) + .padding(.bottom) } Spacer() diff --git a/iphone/Maps/Tourism/Presentation/Components/Special/PlacesItem.swift b/iphone/Maps/Tourism/Presentation/Components/Special/PlacesItem.swift new file mode 100644 index 0000000000..408f41c42b --- /dev/null +++ b/iphone/Maps/Tourism/Presentation/Components/Special/PlacesItem.swift @@ -0,0 +1,62 @@ +import SwiftUI + +struct PlacesItem: View { + let place: PlaceShort + let onPlaceClick: (PlaceShort) -> Void + let onFavoriteChanged: (Bool) -> Void + + private let height: CGFloat = 130 + + var body: some View { + HStack { + LoadImageView(url: place.cover) + .frame(width: height, height: height) + .clipShape(RoundedRectangle(cornerRadius: 20)) + + VStack(alignment: .leading, spacing: 8) { + HStack { + Text(place.name) + .font(.semiBold(size: 20)) + .foregroundColor(Color.onBackground) + .lineLimit(1) + + Spacer() + + Button(action: { + onFavoriteChanged(!place.isFavorite) + }) { + Image(systemName: place.isFavorite ? "heart.fill" : "heart") + .foregroundColor(Color.heartRed) + } + } + + HStack { + Text(String(format: "%.1f", place.rating ?? 0.0)) + .font(.regular(size: 14)) + .foregroundColor(Color.onBackground) + Image(systemName: "star.fill") + .foregroundColor(Color.starYellow) + .font(.system(size: 12)) + } + + if let excerpt = place.excerpt { + Text(excerpt) + .font(.regular(size: 14)) + .foregroundColor(Color.onBackground) + .lineLimit(3) + } + } + .padding(8) + } + .frame(height: height) + .background(Color.background) + .cornerRadius(20) + .overlay( + RoundedRectangle(cornerRadius: 20) + .stroke(Color.border, lineWidth: 1) + ) + .onTapGesture { + onPlaceClick(place) + } + } +} diff --git a/iphone/Maps/Tourism/Presentation/Home/Screens/Profile/AppTextField.swift b/iphone/Maps/Tourism/Presentation/Components/TextFields/AppTextField.swift similarity index 100% rename from iphone/Maps/Tourism/Presentation/Home/Screens/Profile/AppTextField.swift rename to iphone/Maps/Tourism/Presentation/Components/TextFields/AppTextField.swift diff --git a/iphone/Maps/Tourism/Presentation/Home/Screens/Categories/CategoriesViewController.swift b/iphone/Maps/Tourism/Presentation/Home/Screens/Categories/CategoriesViewController.swift new file mode 100644 index 0000000000..6ca7ed812e --- /dev/null +++ b/iphone/Maps/Tourism/Presentation/Home/Screens/Categories/CategoriesViewController.swift @@ -0,0 +1,87 @@ +import SwiftUI + +class CategoriesViewController: UIViewController { + private var categoriesVM: CategoriesViewModel + private var searchVM: SearchViewModel + + init(categoriesVM: CategoriesViewModel, searchVM: SearchViewModel) { + self.categoriesVM = categoriesVM + self.searchVM = searchVM + super.init( + nibName: nil, + bundle: nil + ) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + integrateSwiftUIScreen( + CategoriesScreen( + categoriesVM: categoriesVM, + goToSearchScreen: { query in + let destinationVC = SearchViewController(searchVM: self.searchVM) + self.navigationController?.pushViewController(destinationVC, animated: true) + } + ) + ) + } +} + +struct CategoriesScreen: View { + @ObservedObject var categoriesVM: CategoriesViewModel + var goToSearchScreen: (String) -> Void + + var body: some View { + ScrollView { + VStack(alignment: .leading) { + VerticalSpace(height: 16) + VStack { + AppTopBar(title: L("categories")) + + AppSearchBar( + query: $categoriesVM.query, + onSearchClicked: { query in + goToSearchScreen(query) + }, + onClearClicked: { + categoriesVM.clearQuery() + } + ) + } + .padding(16) + + VStack(spacing: 20) { + HorizontalSingleChoice( + items: categoriesVM.categories, + selected: $categoriesVM.selectedCategory, + onSelectedChanged: { item in + categoriesVM.setSelectedCategory(item) + } + ) + + LazyVStack(spacing: 16) { + ForEach(categoriesVM.places) { place in + PlacesItem( + place: place, + onPlaceClick: { clickedPlace in + // Handle place click + print("Place clicked: \(clickedPlace.name)") + }, + onFavoriteChanged: { isFavorite in + categoriesVM.toggleFavorite(for: place.id, isFavorite: isFavorite) + } + ) + } + } + .padding(.horizontal, 16) + } + VerticalSpace(height: 32) + } + } + } +} diff --git a/iphone/Maps/Tourism/Presentation/Home/Screens/Categories/CategoriesViewModel.swift b/iphone/Maps/Tourism/Presentation/Home/Screens/Categories/CategoriesViewModel.swift new file mode 100644 index 0000000000..edb635adc6 --- /dev/null +++ b/iphone/Maps/Tourism/Presentation/Home/Screens/Categories/CategoriesViewModel.swift @@ -0,0 +1,39 @@ +import Combine + +class CategoriesViewModel: ObservableObject { + @Published var query = "" + + func clearQuery() { query = "" } + + var categories = [ + SingleChoiceItem(id: PlaceCategory.sights, label: L("sights")), + SingleChoiceItem(id: PlaceCategory.restaurants, label: L("restaurants")), + SingleChoiceItem(id: PlaceCategory.hotels, label: L("hotels")) + ] + + @Published var selectedCategory: SingleChoiceItem? + + func setSelectedCategory(_ item: SingleChoiceItem?) { + selectedCategory = item + } + + @Published var places: [PlaceShort] = [] + + init() { + if let firstCategory = categories.first { + self.selectedCategory = firstCategory + } + + // TODO: put real data + places = [ + PlaceShort(id: 1, name: "sight 1", cover: Constants.imageUrlExample, rating: 4.5, excerpt: "yep, just a placeyep, just a placeyep, just a placeyep, just a placeyep, just a placejust a placeyep, just a placejust a placeyep, just a placejust a placeyep, just a placejust a placeyep, just a place", isFavorite: false), + PlaceShort(id: 2, name: "sight 2", cover: Constants.imageUrlExample, rating: 4.0, excerpt: "yep, just a place", isFavorite: true) + ] + } + + func toggleFavorite(for placeId: Int64, isFavorite: Bool) { + if let index = places.firstIndex(where: { $0.id == placeId }) { + places[index].isFavorite = isFavorite + } + } +} diff --git a/iphone/Maps/Tourism/Presentation/Home/Screens/Favorites/FavoritesViewController.swift b/iphone/Maps/Tourism/Presentation/Home/Screens/Favorites/FavoritesViewController.swift new file mode 100644 index 0000000000..9f4a2ab2f9 --- /dev/null +++ b/iphone/Maps/Tourism/Presentation/Home/Screens/Favorites/FavoritesViewController.swift @@ -0,0 +1,15 @@ +import SwiftUI + +class FavoritesViewController: UIViewController { + override func viewDidLoad() { + super.viewDidLoad() + + integrateSwiftUIScreen(FavoritesScreen()) + } +} + +struct FavoritesScreen: View { + var body: some View { + Text("favorites") + } +} diff --git a/iphone/Maps/Tourism/Presentation/Home/Screens/Home/HomeViewModel.swift b/iphone/Maps/Tourism/Presentation/Home/Screens/Home/HomeViewModel.swift new file mode 100644 index 0000000000..5a5cb41ac7 --- /dev/null +++ b/iphone/Maps/Tourism/Presentation/Home/Screens/Home/HomeViewModel.swift @@ -0,0 +1,23 @@ +import Combine + +class HomeViewModel : ObservableObject { + @Published var query = "" + + func clearQuery() { query = "" } + + @Published var sights: [PlaceShort]? = nil + @Published var restaurants: [PlaceShort]? = nil + + init() { + // TODO: put real data + sights = [ + PlaceShort(id: 1, name: "sight 1", cover: Constants.imageUrlExample, rating: 4.5, excerpt: "yep, just a placeyep, just a placeyep, just a placeyep, just a placeyep, just a place", isFavorite: false), + PlaceShort(id: 2, name: "sight 2", cover: Constants.imageUrlExample, rating: 4.0, excerpt: "yep, just a place", isFavorite: true) + ] + + restaurants = [ + PlaceShort(id: 1, name: "restaurant 1", cover: Constants.imageUrlExample, rating: 4.5, excerpt: "yep, just a placeyep, just a placeyep, just a placeyep, just a placeyep, just a place", isFavorite: false), + PlaceShort(id: 2, name: "restaurant 2", cover: Constants.imageUrlExample, rating: 4.0, excerpt: "yep, just a place", isFavorite: true) + ] + } +} diff --git a/iphone/Maps/Tourism/Presentation/Home/Screens/Home/HorizontalPlaces.swift b/iphone/Maps/Tourism/Presentation/Home/Screens/Home/HorizontalPlaces.swift new file mode 100644 index 0000000000..8b9e1bc3d8 --- /dev/null +++ b/iphone/Maps/Tourism/Presentation/Home/Screens/Home/HorizontalPlaces.swift @@ -0,0 +1,111 @@ +import SwiftUI + +struct HorizontalPlaces: View { + let title: String + let items: [PlaceShort] + let onPlaceClick: (PlaceShort) -> Void + let setFavoriteChanged: (PlaceShort, Bool) -> Void + + var body: some View { + VStack(alignment: .leading) { + Text(title) + .font(.semiBold(size: 24)) + .foregroundColor(Color.onBackground) + .padding(.horizontal) + + ScrollView(.horizontal, showsIndicators: false) { + HStack { + HorizontalSpace(width: 16) + + ForEach(items, id: \.id) { place in + Place( + place: place, + onPlaceClick: { onPlaceClick(place) }, + isFavorite: place.isFavorite, + onFavoriteChanged: { isFavorite in + setFavoriteChanged(place, isFavorite) + } + ) + HorizontalSpace(width: 16) + } + } + } + } + } +} + +struct Place: View { + let place: PlaceShort + let onPlaceClick: () -> Void + let isFavorite: Bool + let onFavoriteChanged: (Bool) -> Void + + let width = 230.0 + let height = 250.0 + + var body: some View { + ZStack(alignment: .bottom) { + LoadImageView(url: place.cover) + + HStack() { + VStack(alignment: .leading) { + Text(place.name) + .font(.semiBold(size: 15)) + .foregroundColor(.white) + .lineLimit(2) + VerticalSpace(height: 4) + + HStack(alignment: .center) { + Text(String(format: "%.1f", place.rating ?? 0.0)) + .font(.semiBold(size: 15)) + .foregroundColor(.white) + Image(systemName: "star.fill") + .resizable() + .foregroundColor(Color.starYellow) + .frame(width: 10, height: 10) + } + } + .padding(12) + + Spacer() + } + .frame(width: width) + .background(SwiftUI.Color.black.opacity(0.5)) + + VStack { + HStack { + Spacer() + Button(action: { + onFavoriteChanged(!isFavorite) + }) { + Image(systemName: isFavorite ? "heart.fill" : "heart") + .foregroundColor(.white) + .padding(8) + .background(SwiftUI.Color.white.opacity(0.2)) + .clipShape(Circle()) + } + } + Spacer() + } + .padding(12) + } + .frame(width: width, height: height) + .clipShape(RoundedRectangle(cornerRadius: 16)) + .onTapGesture(perform: onPlaceClick) + } +} + + +struct HorizontalPlaces_Previews: PreviewProvider { + static var previews: some View { + HorizontalPlaces( + title: "Popular Places", + items: [ + PlaceShort(id: 1, name: "Place 1", cover: "url1", rating: 4.5, excerpt: "yep, just a placeyep, just a placeyep, just a placeyep, just a placeyep, just a place", isFavorite: false), + PlaceShort(id: 2, name: "Place 2", cover: "url2", rating: 4.0, excerpt: "yep, just a place", isFavorite: true) + ], + onPlaceClick: { _ in }, + setFavoriteChanged: { _, _ in } + ) + } +} diff --git a/iphone/Maps/Tourism/Presentation/Home/Screens/New Group/HomeViewController.swift b/iphone/Maps/Tourism/Presentation/Home/Screens/New Group/HomeViewController.swift index 457b570614..e3328c789e 100644 --- a/iphone/Maps/Tourism/Presentation/Home/Screens/New Group/HomeViewController.swift +++ b/iphone/Maps/Tourism/Presentation/Home/Screens/New Group/HomeViewController.swift @@ -1,15 +1,124 @@ import SwiftUI +import Combine class HomeViewController: UIViewController { + private var categoriesVM: CategoriesViewModel + private var searchVM: SearchViewModel + private var goToCategoriesTab: () -> Void + + init(categoriesVM: CategoriesViewModel, searchVM: SearchViewModel, goToCategoriesTab: @escaping () -> Void) { + self.categoriesVM = categoriesVM + self.searchVM = searchVM + self.goToCategoriesTab = goToCategoriesTab + super.init( + nibName: nil, + bundle: nil + ) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { super.viewDidLoad() - integrateSwiftUIScreen(HomeScreen()) + integrateSwiftUIScreen( + HomeScreen( + homeVM: HomeViewModel(), + categoriesVM: categoriesVM, + goToCategoriesTab: goToCategoriesTab, + goToSearchScreen: { query in + let destinationVC = SearchViewController(searchVM: self.searchVM) + self.navigationController?.pushViewController(destinationVC, animated: true) + } + ) + ) } } struct HomeScreen: View { + @ObservedObject var homeVM: HomeViewModel + @ObservedObject var categoriesVM: CategoriesViewModel + var goToCategoriesTab: () -> Void + var goToSearchScreen: (String) -> Void + + @State var top30: SingleChoiceItem? = SingleChoiceItem(id: 1, label: L("top30")) + var body: some View { - Text("kdfal;ksjf") + ScrollView { + VStack (alignment: .leading) { + VerticalSpace(height: 16) + VStack { + AppTopBar(title: L("tjk")) + + AppSearchBar( + query: $homeVM.query, + onSearchClicked: { query in + goToSearchScreen(query) + }, + onClearClicked: { + homeVM.clearQuery() + } + ) + } + .padding(16) + + VStack(spacing: 20) { + ScrollView(.horizontal, showsIndicators: false) { + HStack { + HorizontalSpace(width: 16) + SingleChoiceItemView( + item: top30!, + isSelected: true, + onClick: { + // nothing, just static + }, + selectedColor: Color.selected, + unselectedColor: Color.background + ) + + HorizontalSingleChoice( + items: categoriesVM.categories, + selected: $categoriesVM.selectedCategory, + onSelectedChanged: { item in + categoriesVM.setSelectedCategory(item) + goToCategoriesTab() + } + ) + } + } + + if let sights = homeVM.sights { + HorizontalPlaces( + title: L("sights"), + items: sights, + onPlaceClick: { place in + + }, + setFavoriteChanged: { place, isFavorite in + + } + ) + } + + if let restaurants = homeVM.restaurants { + HorizontalPlaces( + title: L("restaurants"), + items: restaurants, + onPlaceClick: { place in + + }, + setFavoriteChanged: { place, isFavorite in + + } + ) + } + } + } + VerticalSpace(height: 32) + } } } + diff --git a/iphone/Maps/Tourism/Presentation/Home/Screens/Profile/ProfileViewController.swift b/iphone/Maps/Tourism/Presentation/Home/Screens/Profile/ProfileViewController.swift index 043354fcef..d9404071bd 100644 --- a/iphone/Maps/Tourism/Presentation/Home/Screens/Profile/ProfileViewController.swift +++ b/iphone/Maps/Tourism/Presentation/Home/Screens/Profile/ProfileViewController.swift @@ -21,8 +21,8 @@ class ProfileViewController: UIViewController { ProfileScreen( profileVM: profileVM, onPersonalDataClick: { - let destinationVC = PersonalDataViewController(profileVM: profileVM) - self.navigationController?.pushViewController(destinationVC, animated: true) + let destinationVC = PersonalDataViewController(profileVM: profileVM) + self.navigationController?.pushViewController(destinationVC, animated: true) } ) ) diff --git a/iphone/Maps/Tourism/Presentation/Home/Screens/Search/SearchViewController.swift b/iphone/Maps/Tourism/Presentation/Home/Screens/Search/SearchViewController.swift new file mode 100644 index 0000000000..343c4a2cdb --- /dev/null +++ b/iphone/Maps/Tourism/Presentation/Home/Screens/Search/SearchViewController.swift @@ -0,0 +1,72 @@ +import SwiftUI + +class SearchViewController: UIViewController { + private var searchVM: SearchViewModel + + init(searchVM: SearchViewModel) { + self.searchVM = searchVM + super.init( + nibName: nil, + bundle: nil + ) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + integrateSwiftUIScreen(SearchScreen(searchVM: searchVM)) + } +} + +struct SearchScreen: View { + @ObservedObject var searchVM: SearchViewModel + @Environment(\.presentationMode) var presentationMode: Binding + + var body: some View { + ScrollView { + VStack(alignment: .leading) { + VerticalSpace(height: 16) + VStack { + AppTopBar( + title: L("search"), + onBackClick: { + presentationMode.wrappedValue.dismiss() + } + ) + + AppSearchBar( + query: $searchVM.query, + onClearClicked: { + searchVM.clearQuery() + } + ) + } + .padding(16) + + VStack(spacing: 20) { + + LazyVStack(spacing: 16) { + ForEach(searchVM.places) { place in + PlacesItem( + place: place, + onPlaceClick: { clickedPlace in + // Handle place click + print("Place clicked: \(clickedPlace.name)") + }, + onFavoriteChanged: { isFavorite in + searchVM.toggleFavorite(for: place.id, isFavorite: isFavorite) + } + ) + } + } + .padding(.horizontal, 16) + } + VerticalSpace(height: 32) + } + } + } +} diff --git a/iphone/Maps/Tourism/Presentation/Home/Screens/Search/SearchViewModel.swift b/iphone/Maps/Tourism/Presentation/Home/Screens/Search/SearchViewModel.swift new file mode 100644 index 0000000000..67e6853cde --- /dev/null +++ b/iphone/Maps/Tourism/Presentation/Home/Screens/Search/SearchViewModel.swift @@ -0,0 +1,23 @@ +import Combine + +class SearchViewModel: ObservableObject { + @Published var query = "" + + func clearQuery() { query = "" } + + @Published var places: [PlaceShort] = [] + + init() { + // TODO: put real data + places = [ + PlaceShort(id: 1, name: "sight 1", cover: Constants.imageUrlExample, rating: 4.5, excerpt: "yep, just a placeyep, just a placeyep, just a placeyep, just a placeyep, just a place", isFavorite: false), + PlaceShort(id: 2, name: "sight 2", cover: Constants.imageUrlExample, rating: 4.0, excerpt: "yep, just a place", isFavorite: true) + ] + } + + func toggleFavorite(for placeId: Int64, isFavorite: Bool) { + if let index = places.firstIndex(where: { $0.id == placeId }) { + places[index].isFavorite = isFavorite + } + } +} diff --git a/iphone/Maps/Tourism/Presentation/Home/TabBarController.swift b/iphone/Maps/Tourism/Presentation/Home/TabBarController.swift index d7dd922f6c..4a3c04c90e 100644 --- a/iphone/Maps/Tourism/Presentation/Home/TabBarController.swift +++ b/iphone/Maps/Tourism/Presentation/Home/TabBarController.swift @@ -6,22 +6,50 @@ class TabBarController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() + // creating tabs let homeTab = UITabBarItem(title: L("home"), image: UIImage(systemName: "house"), selectedImage: UIImage(systemName: "house.fill")) + let categoriesTab = UITabBarItem(title: L("categories"), image: UIImage(systemName: "list.bullet.rectangle"), selectedImage: UIImage(systemName: "list.bullet.rectangle.fill")) + let favoritesTab = UITabBarItem(title: L("favorites"), image: UIImage(systemName: "heart"), selectedImage: UIImage(systemName: "heart.fill")) let profileTab = UITabBarItem(title: L("tourism_profile"), image: UIImage(systemName: "person"), selectedImage: UIImage(systemName: "person.fill")) + // creating navs let homeNav = UINavigationController() + let categoriesNav = UINavigationController() + let favoritesNav = UINavigationController() let profileNav = UINavigationController() - let homeVC = HomeViewController() + // creating shared ViewModels + let categoriesVM = CategoriesViewModel() + let searchViewModel = SearchViewModel() + + // navigation functions + let goToCategoriesTab = { self.selectedIndex = 1 } + + // creating ViewControllers + let homeVC = HomeViewController( + categoriesVM: categoriesVM, + searchVM: searchViewModel, + goToCategoriesTab: goToCategoriesTab + ) + let categoriesVC = CategoriesViewController( + categoriesVM: categoriesVM, + searchVM: searchViewModel + ) + let favoritesVC = FavoritesViewController() let profileVC = ProfileViewController() + // setting up navigation homeNav.viewControllers = [homeVC] + categoriesNav.viewControllers = [categoriesVC] + favoritesNav.viewControllers = [favoritesVC] profileNav.viewControllers = [profileVC] homeNav.tabBarItem = homeTab + categoriesNav.tabBarItem = categoriesTab + favoritesNav.tabBarItem = favoritesTab profileNav.tabBarItem = profileTab - viewControllers = [homeNav, profileNav] + viewControllers = [homeNav, categoriesNav, favoritesNav, profileNav] } }