local CoreGui = game:GetService("CoreGui") local GuiService = game:GetService("GuiService") local PlayerService = game:GetService("Players") local Modules = CoreGui.RobloxGui.Modules local LuaApp = Modules.LuaApp local LuaChat = Modules.LuaChat local Create = require(LuaChat.Create) local Constants = require(LuaChat.Constants) local DialogInfo = require(LuaChat.DialogInfo) local ConversationActions = require(LuaChat.Actions.ConversationActions) local GetMultiplePlaceInfos = require(LuaChat.Actions.GetMultiplePlaceInfos) local HeaderLoader = require(LuaChat.Components.HeaderLoader) local PlaceInfoCard = require(LuaChat.Components.PlaceInfoCard) local GameShareCard = require(LuaChat.Components.GameShareCard) local ConversationList = require(LuaChat.Components.ConversationList) local getInputEvent = require(LuaChat.Utils.getInputEvent) local truncateAssetLink = require(LuaChat.Utils.truncateAssetLink) local FlagSettings = require(LuaApp.FlagSettings) local HttpStatus = require(LuaChat.WebApi).Status local ToastModel = require(LuaChat.Models.ToastModel) local ShowToast = require(LuaChat.Actions.ShowToast) local NavigateBack = require(Modules.LuaApp.Thunks.NavigateBack) local RemoveRoute = require(LuaChat.Actions.RemoveRoute) local SetAppLoaded = require(LuaChat.Actions.SetAppLoaded) local NotificationType = require(LuaApp.Enum.NotificationType) local Intent = DialogInfo.Intent local FFlagLuaChatToSplitRbxConnections = settings():GetFFlag("LuaChatToSplitRbxConnections") local FFlagShareGameToChatStatusAnalytics = settings():GetFFlag("ShareGameToChatStatusAnalytics") local GameShareComponent = {} GameShareComponent.__index = GameShareComponent local ICON_CELL_WIDTH = 60 local SEARCH_BOX_HEIGHT = 48 local CLEAR_TEXT_WIDTH = 44 local PLACE_INFO_FRAME_HEIGHT = 84 local function requestOlderConversations(appState) -- Don't fetch older conversations if the oldest conversation has already been fetched. if appState.store:getState().ChatAppReducer.ConversationsAsync.oldestConversationIsFetched then return end -- Ask for new conversations local convoCount = 0 for _, _ in pairs(appState.store:getState().ChatAppReducer.Conversations) do convoCount = convoCount + 1 end local pageSize = Constants.PageSize.GET_CONVERSATIONS local currentPage = math.floor(convoCount / pageSize) spawn(function() appState.store:dispatch(ConversationActions.GetLocalUserConversationsAsync(currentPage + 1, pageSize)) end) end function GameShareComponent.new(appState, placeId, innerFrame) local self = {} self._analytics = appState.analytics self.appState = appState self.placeId = placeId self.placeInfo = appState.store:getState().ChatAppReducer.PlaceInfos[placeId] setmetatable(self, GameShareComponent) -- Header self.header = HeaderLoader.GetHeader(appState, Intent.GameShare) self.header:SetDefaultSubtitle() self.header:SetTitle(appState.localization:Format("Feature.Chat.Heading.ShareGameToChat")) self.header:SetBackButtonEnabled(true) -- Place Info Card Frame self.placeInfoCardFrame = Create.new"Frame" { Name = "PlaceInfoCardFrame", BackgroundColor3 = Constants.Color.GRAY5, BorderSizePixel = 0, Size = UDim2.new(1, 0, 0, PLACE_INFO_FRAME_HEIGHT), LayoutOrder = 2, } -- Search Container self.searchContainer = Create.new"Frame" { Name = "SearchContainer", BackgroundTransparency = 0, BackgroundColor3 = Constants.Color.WHITE, BorderSizePixel = 0, Size = UDim2.new(1, 0, 0, SEARCH_BOX_HEIGHT), Visible = false, LayoutOrder = 3, Create.new"ImageLabel" { Name = "SearchIcon", BackgroundTransparency = 1, Size = UDim2.new(0, 24, 0, 24), Position = UDim2.new(0, ICON_CELL_WIDTH/2, 0.5, 0), AnchorPoint = Vector2.new(0.5, 0.5), ImageColor3 = Constants.Color.GRAY3, Image = "rbxasset://textures/ui/LuaChat/icons/ic-search.png", }, Create.new"TextBox" { Name = "Search", BackgroundTransparency = 1, Size = UDim2.new(1, -CLEAR_TEXT_WIDTH-ICON_CELL_WIDTH, 1, 0), Position = UDim2.new(0, ICON_CELL_WIDTH, 0, 0), TextSize = Constants.Font.FONT_SIZE_18, TextColor3 = Constants.Color.GRAY1, Font = Enum.Font.SourceSans, Text = "", PlaceholderText = appState.localization:Format("Feature.Chat.Label.SearchForFriendsAndChat"), PlaceholderColor3 = Constants.Color.GRAY3, TextXAlignment = Enum.TextXAlignment.Left, OverlayNativeInput = true, ClearTextOnFocus = false, ClipsDescendants = true, }, Create.new"ImageButton" { Name = "Clear", BackgroundTransparency = 1, Size = UDim2.new(0, 16, 0, 16), Position = UDim2.new(1, -(CLEAR_TEXT_WIDTH/2), 0.5, 0), AnchorPoint = Vector2.new(0.5, 0.5), AutoButtonColor = false, Image = "rbxasset://textures/ui/LuaChat/icons/ic-clear-solid.png", ImageTransparency = 1, }, } self.clearSearchButton = self.searchContainer.Clear self.searchBox = self.searchContainer.Search self.SearchFilterPredicate = function(other) if self.searchBox.Text == "" then return true end return string.find(string.lower(other), string.lower(self.searchBox.Text), 1, true) ~= nil end local divider = Create.new"Frame" { Name = "Divider", BackgroundColor3 = Constants.Color.GRAY4, BorderSizePixel = 0, Size = UDim2.new(1, 0, 0, 1), LayoutOrder = 4, } -- Conversation List Frame self.conversationListFrame = Create.new "Frame" { Name = "ConversationListFrame", BackgroundTransparency = 1, BorderSizePixel = 0, LayoutOrder = 5, Size = UDim2.new(1, 0, 1, - self.header.rbx.Size.Y.Offset - SEARCH_BOX_HEIGHT - PLACE_INFO_FRAME_HEIGHT), } self.placeInfoCardFrame.Parent = innerFrame self.searchContainer.Parent = innerFrame divider.Parent = innerFrame self.conversationListFrame.Parent = innerFrame self.rbx = Create.new"ImageButton" { Active = true, AutoButtonColor = false, Size = UDim2.new(1, 0, 1, 0), BackgroundColor3 = Constants.Color.GRAY5, BorderSizePixel = 0, Create.new("UIListLayout") { Name = "ListLayout", SortOrder = "LayoutOrder", HorizontalAlignment = "Center", }, self.header.rbx, innerFrame, } if not appState.store:getState().ChatAppReducer.AppLoaded then spawn(function() appState.store:dispatch( ConversationActions.GetLocalUserConversationsAsync(1, Constants.PageSize.GET_CONVERSATIONS) ):andThen(function() appState.store:dispatch(SetAppLoaded(true)) end) end) end return self end function GameShareComponent:Start() local isLuaAppStarterScriptEnabled = FlagSettings:IsLuaAppStarterScriptEnabled() self.connections = {} -- back button local backButtonConnection if isLuaAppStarterScriptEnabled then backButtonConnection = self.header.BackButtonPressed:connect(function() self.appState.store:dispatch(NavigateBack()) GuiService:BroadcastNotification("", NotificationType.CLOSE_MODAL) end) else backButtonConnection = self.header.BackButtonPressed:connect(function() self.appState.store:dispatch(RemoveRoute(DialogInfo.Intent.GameShare)) GuiService:BroadcastNotification("", NotificationType.CLOSE_MODAL) end) end table.insert(self.connections, backButtonConnection) -- update local appStateConnection = self.appState.store.changed:connect(function(state, oldState) self:Update(state, oldState) end) table.insert(self.connections, appStateConnection) -- search clear button local clearButtonConnection = getInputEvent(self.clearSearchButton):Connect(function() self.searchBox.Text = "" end) if FFlagLuaChatToSplitRbxConnections then table.insert(self.rbx_connections, clearButtonConnection) else table.insert(self.connections, clearButtonConnection) end local function updateClearButtonVisibility() -- If we were to set the visible property of the clear button on the textbox focus lost event -- it would disable the clear button, which in turn would stop the click event -- from being able to notify the button local visible = self.searchBox:IsFocused() and (self.searchBox.Text ~= "") self.clearSearchButton.ImageTransparency = visible and 0 or 1 end -- search box local searchChangedConnection = self.searchBox:GetPropertyChangedSignal("Text"):Connect(function() if self.list then updateClearButtonVisibility() self.list:SetFilterPredicate(self.SearchFilterPredicate) self:getOlderConversationsForSearchIfNecessary() end end) table.insert(self.connections, searchChangedConnection) local focusedConnection = self.searchBox.Focused:Connect(updateClearButtonVisibility) if FFlagLuaChatToSplitRbxConnections then table.insert(self.rbx_connections, focusedConnection) else table.insert(self.connections, focusedConnection) end local focusLostConnection = self.searchBox.FocusLost:Connect(updateClearButtonVisibility) if FFlagLuaChatToSplitRbxConnections then table.insert(self.rbx_connections, focusLostConnection) else table.insert(self.connections, focusLostConnection) end if not self.placeInfo then self.appState.store:dispatch(GetMultiplePlaceInfos({self.placeId})) end if self.appState.store:getState().ChatAppReducer.AppLoaded and self.placeInfo then self:FillContent() end end function GameShareComponent:Update(newState, oldState) if (not oldState.ChatAppReducer.AppLoaded) and newState.ChatAppReducer.AppLoaded and self.placeInfo then self:FillContent() end if (not self.placeInfo) and (newState.ChatAppReducer.PlaceInfos[self.placeId]) then self.placeInfo = newState.ChatAppReducer.PlaceInfos[self.placeId] if newState.ChatAppReducer.AppLoaded then self:FillContent() end end local newPageConversationsIsFetching = newState.ChatAppReducer.ConversationsAsync.pageConversationsIsFetching local oldPageConversationsIsFetching = oldState.ChatAppReducer.ConversationsAsync.pageConversationsIsFetching if newPageConversationsIsFetching ~= oldPageConversationsIsFetching and self.list then self.list:Update(newState, oldState) self:getOlderConversationsForSearchIfNecessary() end if newState.ChatAppReducer.Conversations ~= oldState.ChatAppReducer.Conversations and self.list then self.list:Update(newState, oldState) end end function GameShareComponent:FillContent() self.searchContainer.Visible = true self:FillPlaceInfo() self:FillConversations() end function GameShareComponent:FillPlaceInfo() if self.placeInfoCard then return end self.placeInfoCard = PlaceInfoCard.new(self.appState, self.placeInfo) self.placeInfoCard.rbx.Parent = self.placeInfoCardFrame end function GameShareComponent:FillConversations() local conversations = self.appState.store:getState().ChatAppReducer.Conversations local list = ConversationList.new(self.appState, conversations, GameShareCard) list:SetSortWithConversationEntry(true) self.list = list list.rbx.Size = UDim2.new(1, 0, 1, 0) list.rbx.Parent = self.conversationListFrame local tappedConnection = list.ConversationTapped:connect(function(convoId) local messageSentLocalTime = tick() if FFlagShareGameToChatStatusAnalytics then self.appState.store:dispatch( ConversationActions.SendMessage( convoId, truncateAssetLink(self.placeInfo.url), messageSentLocalTime )):andThen(function(webStatus) self:ReportSendButtonTappedEvent(convoId, webStatus) if webStatus == HttpStatus.MODERATED then local toastModel = ToastModel.new(Constants.ToastIDs.MESSAGE_WAS_MODERATED, "Feature.Chat.Message.GameLinkWasModerated") self.appState.store:dispatch(ShowToast(toastModel)) end end) else self:ReportSendButtonTappedEvent(convoId) self.appState.store:dispatch( ConversationActions.SendMessage( convoId, truncateAssetLink(self.placeInfo.url), "Feature.Chat.Message.GameLinkWasModerated", messageSentLocalTime ) ) end end) table.insert(self.connections, tappedConnection) local requestOlderConversationConnection = list.RequestOlderConversations:connect(function() requestOlderConversations(self.appState) end) table.insert(self.connections, requestOlderConversationConnection) end function GameShareComponent:getOlderConversationsForSearchIfNecessary(appState) -- To Check: -- 1) Search is open -- 2) Not have loaded all conversations. -- 3) Not Ccrrently getting older conversations -- 4) Having enouth search items to show -- Note that we already try to load more conversations if we scroll down to the bottom of the list local state = self.appState.store:getState() local isSearchOpen = (self.searchBox.Text) ~= nil and (self.searchBox.Text ~= "") if (not isSearchOpen) or state.ChatAppReducer.ConversationsAsync.oldestConversationIsFetched or state.ChatAppReducer.ConversationsAsync.pageConversationsIsFetching then return end if self.list.rbx.CanvasSize.Y.Offset > self.list.rbx.AbsoluteSize.Y then return end requestOlderConversations(self.appState) end function GameShareComponent:ReportSendButtonTappedEvent(convoId, httpStatus) local eventName = "clickSendBtnFromGameShareCard" local eventContext = "touch" local player = PlayerService.LocalPlayer local userId = "UNKNOWN" if player then userId = tostring(player.UserId) end local additionalArgs = { uid = userId, placeid = self.placeId, cid = convoId, httpStatus = httpStatus, } self._analytics.EventStream:setRBXEvent(eventContext, eventName, additionalArgs) end function GameShareComponent:Stop() for _, connection in ipairs(self.connections) do connection:disconnect() end self.connections = {} if FFlagLuaChatToSplitRbxConnections then for _, connection in ipairs(self.rbx_connections) do connection:Disconnect() end self.rbx_connections = {} end end function GameShareComponent:Destruct() if self.list then self.list:Destruct() end if self.header then self.header:Destroy() end if self.placeInfoCard then self.placeInfoCard:Destruct() end self.rbx:Destroy() end return GameShareComponent