| | 1 | | using Cysharp.Threading.Tasks; |
| | 2 | | using DCL; |
| | 3 | | using DCL.Browser; |
| | 4 | | using DCL.Interface; |
| | 5 | | using DCL.Social.Friends; |
| | 6 | | using DCL.Tasks; |
| | 7 | | using DCLServices.PlacesAPIService; |
| | 8 | | using DCLServices.WorldsAPIService; |
| | 9 | | using ExploreV2Analytics; |
| | 10 | | using System; |
| | 11 | | using System.Collections.Generic; |
| | 12 | | using System.Threading; |
| | 13 | | using Environment = DCL.Environment; |
| | 14 | | using static MainScripts.DCL.Controllers.HotScenes.IHotScenesController; |
| | 15 | |
|
| | 16 | | public class WorldsSubSectionComponentController : IWorldsSubSectionComponentController, IPlacesAndEventsAPIRequester |
| | 17 | | { |
| | 18 | | public event Action OnCloseExploreV2; |
| | 19 | |
|
| | 20 | | internal const int INITIAL_NUMBER_OF_ROWS = 4; |
| | 21 | | private const int PAGE_SIZE = 12; |
| | 22 | | private const string MOST_ACTIVE_FILTER_ID = "most_active"; |
| | 23 | | private const string DAO_PROPOSAL_LINK = "https://governance.decentraland.org/proposal/?id=c3216070-e822-11ed-b8f1-7 |
| | 24 | | private const string WORLDS_DOCS_LINK = "https://decentraland.org/blog/announcements/introducing-decentraland-worlds |
| | 25 | |
|
| | 26 | | internal readonly IWorldsSubSectionComponentView view; |
| | 27 | | internal readonly IWorldsAPIService worldsAPIService; |
| | 28 | | internal readonly IPlacesAPIService placesAPI; |
| | 29 | | internal readonly FriendTrackerController friendsTrackerController; |
| | 30 | | private readonly IExploreV2Analytics exploreV2Analytics; |
| | 31 | | private readonly IPlacesAnalytics placesAnalytics; |
| | 32 | | private readonly DataStore dataStore; |
| | 33 | | private readonly IUserProfileBridge userProfileBridge; |
| | 34 | | internal readonly IBrowserBridge browserBridge; |
| | 35 | |
|
| | 36 | | internal readonly PlaceAndEventsCardsReloader cardsReloader; |
| | 37 | |
|
| 15 | 38 | | internal readonly List<WorldsResponse.WorldInfo> worldsFromAPI = new (); |
| | 39 | | internal int availableUISlots; |
| 15 | 40 | | private CancellationTokenSource getWorldsCts = new (); |
| 15 | 41 | | private CancellationTokenSource showMoreCts = new (); |
| 15 | 42 | | private CancellationTokenSource disposeCts = new (); |
| | 43 | |
|
| 15 | 44 | | public WorldsSubSectionComponentController( |
| | 45 | | IWorldsSubSectionComponentView view, |
| | 46 | | IPlacesAPIService placesAPI, |
| | 47 | | IWorldsAPIService worldsAPIService, |
| | 48 | | IFriendsController friendsController, |
| | 49 | | IExploreV2Analytics exploreV2Analytics, |
| | 50 | | IPlacesAnalytics placesAnalytics, |
| | 51 | | DataStore dataStore, |
| | 52 | | IUserProfileBridge userProfileBridge, |
| | 53 | | IBrowserBridge browserBridge) |
| | 54 | | { |
| 15 | 55 | | cardsReloader = new PlaceAndEventsCardsReloader(view, this, dataStore.exploreV2); |
| | 56 | |
|
| 15 | 57 | | this.view = view; |
| 15 | 58 | | this.browserBridge = browserBridge; |
| | 59 | |
|
| 15 | 60 | | this.view.OnReady += FirstLoading; |
| | 61 | |
|
| 15 | 62 | | this.view.OnInfoClicked += ShowWorldDetailedInfo; |
| 15 | 63 | | this.view.OnJumpInClicked += JumpInToWorld; |
| 15 | 64 | | this.view.OnFavoriteClicked += View_OnFavoritesClicked; |
| 15 | 65 | | this.view.OnVoteChanged += View_OnVoteChanged; |
| 15 | 66 | | this.view.OnShowMoreWorldsClicked += ShowMoreWorlds; |
| 15 | 67 | | this.view.OnOpenWorldsInfo += OpenWorldsInfo; |
| 15 | 68 | | this.view.OnOpenWorldsDaoProposal += OpenWorldsDaoProposal; |
| | 69 | |
|
| 15 | 70 | | this.view.OnFriendHandlerAdded += View_OnFriendHandlerAdded; |
| | 71 | |
|
| 15 | 72 | | this.dataStore = dataStore; |
| 15 | 73 | | this.dataStore.channels.currentJoinChannelModal.OnChange += OnChannelToJoinChanged; |
| | 74 | |
|
| 15 | 75 | | this.placesAPI = placesAPI; |
| 15 | 76 | | this.worldsAPIService = worldsAPIService; |
| | 77 | |
|
| 15 | 78 | | friendsTrackerController = new FriendTrackerController(friendsController, view.currentFriendColors); |
| | 79 | |
|
| 15 | 80 | | this.exploreV2Analytics = exploreV2Analytics; |
| 15 | 81 | | this.placesAnalytics = placesAnalytics; |
| | 82 | |
|
| 15 | 83 | | this.userProfileBridge = userProfileBridge; |
| | 84 | |
|
| 15 | 85 | | view.ConfigurePools(); |
| 15 | 86 | | } |
| | 87 | |
|
| | 88 | | public void Dispose() |
| | 89 | | { |
| 15 | 90 | | disposeCts?.SafeCancelAndDispose(); |
| 15 | 91 | | showMoreCts?.SafeCancelAndDispose(); |
| 15 | 92 | | getWorldsCts?.SafeCancelAndDispose(); |
| | 93 | |
|
| 15 | 94 | | view.OnReady -= FirstLoading; |
| 15 | 95 | | view.OnInfoClicked -= ShowWorldDetailedInfo; |
| 15 | 96 | | view.OnJumpInClicked -= JumpInToWorld; |
| 15 | 97 | | view.OnFavoriteClicked -= View_OnFavoritesClicked; |
| 15 | 98 | | view.OnVoteChanged -= View_OnVoteChanged; |
| 15 | 99 | | view.OnWorldsSubSectionEnable -= OpenTab; |
| 15 | 100 | | view.OnSortingChanged -= ApplySorting; |
| 15 | 101 | | view.OnFriendHandlerAdded -= View_OnFriendHandlerAdded; |
| 15 | 102 | | view.OnShowMoreWorldsClicked -= ShowMoreWorlds; |
| 15 | 103 | | this.view.OnOpenWorldsInfo -= OpenWorldsInfo; |
| 15 | 104 | | this.view.OnOpenWorldsDaoProposal -= OpenWorldsDaoProposal; |
| | 105 | |
|
| 15 | 106 | | dataStore.channels.currentJoinChannelModal.OnChange -= OnChannelToJoinChanged; |
| | 107 | |
|
| 15 | 108 | | cardsReloader.Dispose(); |
| 15 | 109 | | } |
| | 110 | |
|
| | 111 | | private void View_OnVoteChanged(string worldId, bool? isUpvote) |
| | 112 | | { |
| 0 | 113 | | if (userProfileBridge.GetOwn().isGuest) |
| 0 | 114 | | dataStore.HUDs.connectWalletModalVisible.Set(true); |
| | 115 | | else |
| | 116 | | { |
| 0 | 117 | | if (isUpvote != null) |
| | 118 | | { |
| 0 | 119 | | if (isUpvote.Value) |
| 0 | 120 | | placesAnalytics.Like(worldId, IPlacesAnalytics.ActionSource.FromExplore, true); |
| | 121 | | else |
| 0 | 122 | | placesAnalytics.Dislike(worldId, IPlacesAnalytics.ActionSource.FromExplore, true); |
| | 123 | | } |
| | 124 | | else |
| 0 | 125 | | placesAnalytics.RemoveVote(worldId, IPlacesAnalytics.ActionSource.FromExplore, true); |
| | 126 | |
|
| 0 | 127 | | placesAPI.SetPlaceVote(isUpvote, worldId, disposeCts.Token); |
| | 128 | | } |
| 0 | 129 | | } |
| | 130 | |
|
| | 131 | | private void View_OnFavoritesClicked(string worldId, bool isFavorite) |
| | 132 | | { |
| 0 | 133 | | if (userProfileBridge.GetOwn().isGuest) |
| 0 | 134 | | dataStore.HUDs.connectWalletModalVisible.Set(true); |
| | 135 | | else |
| | 136 | | { |
| 0 | 137 | | if (isFavorite) |
| 0 | 138 | | placesAnalytics.AddFavorite(worldId, IPlacesAnalytics.ActionSource.FromExplore, true); |
| | 139 | | else |
| 0 | 140 | | placesAnalytics.RemoveFavorite(worldId, IPlacesAnalytics.ActionSource.FromExplore, true); |
| | 141 | |
|
| 0 | 142 | | placesAPI.SetPlaceFavorite(worldId, isFavorite, disposeCts.Token); |
| | 143 | | } |
| 0 | 144 | | } |
| | 145 | |
|
| | 146 | | private void FirstLoading() |
| | 147 | | { |
| 0 | 148 | | view.OnWorldsSubSectionEnable += OpenTab; |
| 0 | 149 | | view.OnSortingChanged += ApplySorting; |
| 0 | 150 | | cardsReloader.Initialize(); |
| 0 | 151 | | } |
| | 152 | |
|
| | 153 | | private void OpenTab() |
| | 154 | | { |
| 0 | 155 | | exploreV2Analytics.SendWorldsTabOpen(); |
| 0 | 156 | | RequestAllWorlds(); |
| 0 | 157 | | } |
| | 158 | |
|
| | 159 | | private void ApplySorting() |
| | 160 | | { |
| 0 | 161 | | placesAnalytics.SortWorlds(view.sort == MOST_ACTIVE_FILTER_ID ? IPlacesAnalytics.SortingType.MostActive : IPlace |
| 0 | 162 | | RequestAllWorlds(); |
| 0 | 163 | | } |
| | 164 | |
|
| | 165 | | internal void RequestAllWorlds() |
| | 166 | | { |
| 2 | 167 | | if (cardsReloader.CanReload()) |
| | 168 | | { |
| 2 | 169 | | availableUISlots = view.CurrentTilesPerRow * INITIAL_NUMBER_OF_ROWS; |
| 2 | 170 | | view.SetShowMoreButtonActive(false); |
| | 171 | |
|
| 2 | 172 | | cardsReloader.RequestAll(); |
| | 173 | | } |
| 2 | 174 | | } |
| | 175 | |
|
| | 176 | | public void RequestAllFromAPI() |
| | 177 | | { |
| 3 | 178 | | getWorldsCts?.SafeCancelAndDispose(); |
| 3 | 179 | | getWorldsCts = CancellationTokenSource.CreateLinkedTokenSource(disposeCts.Token); |
| | 180 | |
|
| 3 | 181 | | RequestAllFromAPIAsync(getWorldsCts.Token).Forget(); |
| 3 | 182 | | } |
| | 183 | |
|
| | 184 | | private async UniTaskVoid RequestAllFromAPIAsync(CancellationToken ct) |
| | 185 | | { |
| | 186 | | try |
| | 187 | | { |
| 3 | 188 | | (IReadOnlyList<WorldsResponse.WorldInfo> worlds, int total) firstPage = await worldsAPIService.GetWorlds(0, |
| 3 | 189 | | friendsTrackerController.RemoveAllHandlers(); |
| 3 | 190 | | worldsFromAPI.Clear(); |
| 3 | 191 | | worldsFromAPI.AddRange(firstPage.worlds); |
| 3 | 192 | | if (firstPage.total > PAGE_SIZE) |
| | 193 | | { |
| 0 | 194 | | (IReadOnlyList<WorldsResponse.WorldInfo> worlds, int total) secondPage = await worldsAPIService.GetWorld |
| 0 | 195 | | worldsFromAPI.AddRange(secondPage.worlds); |
| | 196 | | } |
| | 197 | |
|
| 3 | 198 | | view.SetWorlds(PlacesAndEventsCardsFactory.ConvertWorldsResponseToModel(worldsFromAPI, availableUISlots)); |
| 3 | 199 | | view.SetShowMoreWorldsButtonActive(worldsFromAPI.Count < firstPage.total); |
| 3 | 200 | | } |
| 0 | 201 | | catch (OperationCanceledException) { } |
| 3 | 202 | | } |
| | 203 | |
|
| | 204 | | internal void ShowMoreWorlds() |
| | 205 | | { |
| 2 | 206 | | showMoreCts?.SafeCancelAndDispose(); |
| 2 | 207 | | showMoreCts = CancellationTokenSource.CreateLinkedTokenSource(disposeCts.Token); |
| 2 | 208 | | ShowMoreWorldsAsync(showMoreCts.Token).Forget(); |
| 2 | 209 | | } |
| | 210 | |
|
| | 211 | | private async UniTask ShowMoreWorldsAsync(CancellationToken ct) |
| | 212 | | { |
| 2 | 213 | | (IReadOnlyList<WorldsResponse.WorldInfo> worlds, int total) = await worldsAPIService.GetWorlds((worldsFromAPI.Co |
| | 214 | |
|
| 2 | 215 | | worldsFromAPI.AddRange(worlds); |
| 2 | 216 | | view.AddWorlds(PlacesAndEventsCardsFactory.ConvertWorldsResponseToModel(worlds)); |
| 2 | 217 | | view.SetShowMoreWorldsButtonActive(worldsFromAPI.Count < total); |
| 2 | 218 | | } |
| | 219 | |
|
| | 220 | | internal void ShowWorldDetailedInfo(PlaceCardComponentModel worldModel) |
| | 221 | | { |
| 0 | 222 | | view.ShowWorldModal(worldModel); |
| 0 | 223 | | exploreV2Analytics.SendClickOnWorldInfo(worldModel.placeInfo.id, worldModel.placeName); |
| 0 | 224 | | dataStore.exploreV2.currentVisibleModal.Set(ExploreV2CurrentModal.Places); |
| 0 | 225 | | } |
| | 226 | |
|
| | 227 | | internal void JumpInToWorld(PlaceInfo worldFromAPI) |
| | 228 | | { |
| 0 | 229 | | Environment.i.world.teleportController.JumpInWorld(worldFromAPI.world_name); |
| 0 | 230 | | view.HideWorldModal(); |
| | 231 | |
|
| 0 | 232 | | dataStore.exploreV2.currentVisibleModal.Set(ExploreV2CurrentModal.None); |
| 0 | 233 | | OnCloseExploreV2?.Invoke(); |
| 0 | 234 | | exploreV2Analytics.SendWorldTeleport(worldFromAPI.id, worldFromAPI.title); |
| 0 | 235 | | } |
| | 236 | |
|
| | 237 | | private void View_OnFriendHandlerAdded(IFriendTrackerHandler friendsHandler) => |
| 0 | 238 | | friendsTrackerController.AddHandler(friendsHandler); |
| | 239 | |
|
| | 240 | | private void OnChannelToJoinChanged(string currentChannelId, string previousChannelId) |
| | 241 | | { |
| 0 | 242 | | if (!string.IsNullOrEmpty(currentChannelId)) |
| 0 | 243 | | return; |
| | 244 | |
|
| 0 | 245 | | view.HideWorldModal(); |
| 0 | 246 | | dataStore.exploreV2.currentVisibleModal.Set(ExploreV2CurrentModal.None); |
| 0 | 247 | | OnCloseExploreV2?.Invoke(); |
| 0 | 248 | | } |
| | 249 | |
|
| | 250 | | private void OpenWorldsDaoProposal() => |
| 0 | 251 | | browserBridge.OpenUrl(DAO_PROPOSAL_LINK); |
| | 252 | |
|
| | 253 | | private void OpenWorldsInfo() => |
| 0 | 254 | | browserBridge.OpenUrl(WORLDS_DOCS_LINK); |
| | 255 | |
|
| | 256 | | } |