This commit is contained in:
lx
2026-07-06 14:01:11 -04:00
commit c4f97d729d
16468 changed files with 935321 additions and 0 deletions
@@ -0,0 +1,7 @@
local CorePackages = game:GetService("CorePackages")
--TODO: Currently Under Migration to CorePackages
local Action = require(CorePackages.AppTempCommon.Common.Action)
--
return Action("GetRecentlyPlayedGames", function()
return {}
end)
@@ -0,0 +1,36 @@
return function()
describe("require", function()
it("should create without errors", function()
require(script.Parent.GetRecentlyPlayedGames)
end)
it("should set the name", function()
local action = require(script.Parent.GetRecentlyPlayedGames)
expect(action.name).to.equal("GetRecentlyPlayedGames")
end)
end)
describe("call", function()
it("should return a table when called as a function", function()
local action = require(script.Parent.GetRecentlyPlayedGames)
action = action({})
expect(action).to.be.a("table")
end)
it("should set the type", function()
local action = require(script.Parent.GetRecentlyPlayedGames)
action = action({})
expect(action.type).to.equal("GetRecentlyPlayedGames")
end)
it("should set the type and name to be equal", function()
local action = require(script.Parent.GetRecentlyPlayedGames)
local actionItem = action({})
expect(actionItem.type).to.equal(action.name)
end)
end)
end
@@ -0,0 +1,10 @@
local CorePackages = game:GetService("CorePackages")
--TODO: Currently Under Migration to CorePackages
local Action = require(CorePackages.AppTempCommon.Common.Action)
--
return Action("SetRecentlyPlayedGames", function(gameSortData)
return {
gameSortData = gameSortData,
}
end)
@@ -0,0 +1,44 @@
return function()
describe("require", function()
it("should create without errors", function()
require(script.Parent.SetRecentlyPlayedGames)
end)
it("should set the name", function()
local action = require(script.Parent.SetRecentlyPlayedGames)
expect(action.name).to.equal("SetRecentlyPlayedGames")
end)
end)
describe("call", function()
it("should return a table when called as a function", function()
local action = require(script.Parent.SetRecentlyPlayedGames)
action = action({})
expect(action).to.be.a("table")
end)
it("should set the type", function()
local action = require(script.Parent.SetRecentlyPlayedGames)
action = action({})
expect(action.type).to.equal("SetRecentlyPlayedGames")
end)
it("should set the games", function()
local action = require(script.Parent.SetRecentlyPlayedGames)
local gameSortData = {}
action = action(gameSortData)
expect(action.gameSortData).to.be.a("table")
end)
it("should set the type and name to be equal", function()
local action = require(script.Parent.SetRecentlyPlayedGames)
local actionItem = action({})
expect(actionItem.type).to.equal(action.name)
end)
end)
end
@@ -0,0 +1,9 @@
local CorePackages = game:GetService("CorePackages")
--TODO: Currently Under Migration to CorePackages
local Action = require(CorePackages.AppTempCommon.Common.Action)
--
return Action("SetRecentlyPlayedGamesFetching", function(fetching)
return {
fetching = fetching,
}
end)
@@ -0,0 +1,43 @@
return function()
describe("require", function()
it("should create without errors", function()
require(script.Parent.SetRecentlyPlayedGamesFetching)
end)
it("should set the name", function()
local action = require(script.Parent.SetRecentlyPlayedGamesFetching)
expect(action.name).to.equal("SetRecentlyPlayedGamesFetching")
end)
end)
describe("call", function()
it("should return a table when called as a function", function()
local action = require(script.Parent.SetRecentlyPlayedGamesFetching)
action = action({})
expect(action).to.be.a("table")
end)
it("should set the type", function()
local action = require(script.Parent.SetRecentlyPlayedGamesFetching)
action = action({})
expect(action.type).to.equal("SetRecentlyPlayedGamesFetching")
end)
it("should set fetching status.", function()
local action = require(script.Parent.SetRecentlyPlayedGamesFetching)
action = action(true)
expect(action.fetching).to.equal(true)
end)
it("should set the type and name to be equal", function()
local action = require(script.Parent.SetRecentlyPlayedGamesFetching)
local actionItem = action({})
expect(actionItem.type).to.equal(action.name)
end)
end)
end
@@ -0,0 +1,11 @@
--[[
Returns a blank selector image.
]]
return function()
local BlankSelectionImage = Instance.new("ImageLabel")
BlankSelectionImage.Name = "BlankSelectorImage"
BlankSelectionImage.Position = UDim2.new(0, 0, 0, 0)
BlankSelectionImage.Size = UDim2.new(1, 0, 1, 0)
BlankSelectionImage.BackgroundTransparency = 1
return BlankSelectionImage
end
@@ -0,0 +1,17 @@
--[[
Returns a selection image with rounded corners.
]]
local Settings = script.Parent.Parent
local Constants = require(Settings.Pages.LeaveGameScreen.Constants)
return function()
local RoundedSelectionImage = Instance.new("ImageLabel")
RoundedSelectionImage.Name = "SelectorImage"
RoundedSelectionImage.Image = Constants.Image.BUTTON_SELECTOR
RoundedSelectionImage.Position = UDim2.new(0, -7, 0, -7)
RoundedSelectionImage.Size = UDim2.new(1, 14, 1, 14)
RoundedSelectionImage.BackgroundTransparency = 1
RoundedSelectionImage.ScaleType = Enum.ScaleType.Slice
RoundedSelectionImage.SliceCenter = Rect.new(31, 31, 63, 63)
return RoundedSelectionImage
end
@@ -0,0 +1,97 @@
--[[
Creates a Roact component that is a blue rounded button
Props:
position : UDim2 - Position of the button.
zIndex : number - Determines the order of UI element rendering.
anchorPoint : UDim2 - The anchor point of the button.
objRef : Ref - The ref of this button.
]]
local GuiService = game:GetService("GuiService")
local RunService = game:GetService("RunService")
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Settings = script.Parent.Parent
local Modules = Settings.Parent
local Common = Modules.Common
local Analytics = require(Common.Analytics)
local Constants = require(Settings.Pages.LeaveGameScreen.Constants)
local RoundedSelectionImage = require(Settings.Common.RoundedSelectionImage)
local RobloxTranslator = require(Modules.RobloxTranslator)
local LeaveButton = Roact.PureComponent:extend("LeaveButton")
local LEAVE_GAME_FRAME_WAITS = 2
local LEAVE_GAME_BUTTON_LABEL = "Feature.SettingsHub.Label.LeaveButton"
-- NOTE: event context might need to be changed for other platforms.
local EVENT_CONTEXT = "XboxOne"
function LeaveButton:init()
self.analytics = Analytics.new()
self.onSelectionGained = function()
self:setState({
focused = true,
})
end
self.onSelectionLost = function()
self:setState({
focused = false,
})
end
self.leaveFunc = function()
GuiService.SelectedCoreObject = nil -- deselects the button and prevents spamming the popup to save in studio when using gamepad
-- need to wait for render frames so on slower devices the leave button highlight will update
-- otherwise, since on slow devices it takes so long to leave you are left wondering if you pressed the button
for i = 1, LEAVE_GAME_FRAME_WAITS do
RunService.RenderStepped:wait()
end
local eventName = "LeaveGame"
self.analytics.EventStream:setRBXEventStream(EVENT_CONTEXT, eventName)
game:Shutdown()
end
end
function LeaveButton:render()
local size = UDim2.new(0, 320, 0, 80)
local position = self.props.position or UDim2.new(0.5, 0, 0.5, 0)
local anchorPoint = self.props.anchorPoint or Vector2.new(0.5, 0.5)
local zIndex = self.props.zIndex
local text = RobloxTranslator:FormatByKey(LEAVE_GAME_BUTTON_LABEL)
local textColor = Constants.Color.WHITE
local selector = RoundedSelectionImage()
if self.state.focused then
textColor = Constants.Color.DARK
end
local label = Roact.createElement("TextLabel", {
Text = text,
Size = UDim2.new(1, 0, 1, 0),
Position = UDim2.new(0.5, 0, 0.5, 0),
AnchorPoint = Vector2.new(0.5, 0.5),
Font = Enum.Font.SourceSans,
TextSize = Constants.TextSize.BUTTON,
TextXAlignment = Enum.TextXAlignment.Center,
TextColor3 = textColor,
BackgroundTransparency = 1,
ZIndex = zIndex + 1,
})
return Roact.createElement("ImageButton", {
Image = Constants.Image.BUTTON,
Size = size,
Position = position,
AnchorPoint = anchorPoint,
[Roact.Ref] = self.props.objRef,
[Roact.Event.SelectionGained] = self.onSelectionGained,
[Roact.Event.SelectionLost] = self.onSelectionLost,
[Roact.Event.Activated] = self.leaveFunc,
ImageColor3 = Constants.Color.BLUE,
ScaleType = Enum.ScaleType.Slice,
SliceCenter = Rect.new(8, 8, 9, 9),
SelectionImageObject = selector,
BackgroundTransparency = 1,
ZIndex = zIndex,
},{
Label = label,
})
end
return LeaveButton
@@ -0,0 +1,105 @@
--[[
A Roact play next game icon. When activated, the player will join that game.
Props:
universeId : number - The universe id of the game.
placeId: number - The place id of the game.
imageToken: string - The image token of the game thumbnail.
size : UDim2 - The size of the game icon.
position : position - The position of the game icon.
layoutOrder : number - The sort order of the game icon.
zIndex : number - Determines the order of UI element rendering.
focused : bool - Determines if the game icon focused.
onSelectionGained : function() - The callback function for selection gained.
objRef : Ref - The ref of the game icon.
]]
local CorePackages = game:GetService("CorePackages")
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local Roact = require(CorePackages.Roact)
local Otter = require(CorePackages.Otter)
local Settings = script.Parent.Parent.Parent
local Modules = Settings.Parent
local Common = Modules.Common
local Analytics = require(Common.Analytics)
local BlankSelectionImage = require(Settings.Common.BlankSelectionImage)
local Utility = require(Settings.Utility)
local GameIcon = Roact.PureComponent:extend("GameIcon")
local NORMAL_SIZE = 200
local FOCUSED_SIZE = 232
-- NOTE: event context might need to be changed for other platforms.
local EVENT_CONTEXT = "XboxOne"
function GameIcon:init()
self.ref = Roact.createRef()
self.analytics = Analytics.new()
self.motorOptions = {
dampingRatio = 1,
frequency = 5,
}
self.PlayNextGame = function()
if PlatformService then
local eventName = "PlayNextGame"
local args = {
UniverseID = self.props.universeId,
PlaceId = self.props.placeId
}
self.analytics.EventStream:setRBXEventStream(EVENT_CONTEXT, eventName, args)
PlatformService:PlayNextGame(self.props.placeId)
end
end
end
function GameIcon:render()
local layoutOrder = self.props.layoutOrder
local size = UDim2.new(0, NORMAL_SIZE, 0, NORMAL_SIZE)
local position = self.props.position
local image = string.format("https://games.roblox.com/v1/games/game-thumbnail?imageToken=%s&height=250&width=250", self.props.imageToken)
local zIndex = self.props.zIndex
local selector = BlankSelectionImage()
return Roact.createElement("ImageButton",
{
Size = size,
Position = position,
AnchorPoint = Vector2.new(0.5, 0.5),
Image = image,
BackgroundColor3 = Color3.fromRGB(0,0,0),
BackgroundTransparency = 0.8,
BorderSizePixel = 0,
LayoutOrder = layoutOrder,
SelectionImageObject = selector,
[Roact.Event.SelectionGained] = self.props.onSelectionGained,
[Roact.Event.Activated] = function()
self.PlayNextGame(self.props.placeId)
end,
[Roact.Ref] = self.ref,
ZIndex = zIndex,
})
end
function GameIcon:didMount()
self.motor = Otter.createSingleMotor(NORMAL_SIZE)
self.motor:onStep(function(value)
local t = value/100
if self.ref.current ~= nil then
local size = Utility:Lerp(t, FOCUSED_SIZE, NORMAL_SIZE)
size = Utility:Round(size)
self.ref.current.Size = UDim2.new(0, size, 0, size)
end
end)
self.motor:start()
self.motor:setGoal(Otter.instant(100))
end
function GameIcon:didUpdate(previousProps, previousState)
if self.props.focused == previousProps.focused then
return
end
local targetPercentage = self.props.focused and 0 or 100
self.motor:setGoal(Otter.spring(targetPercentage, self.motorOptions))
end
return GameIcon
@@ -0,0 +1,28 @@
--[[
A Roact play next game selector.
Props:
position : UDim2 - Position of the button.
anchorPoint : UDim2 - The anchor point of the button.
imageTransparency : number - The transparency of the image.
]]
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Settings = script.Parent.Parent.Parent
local Constants = require(Settings.Pages.LeaveGameScreen.Constants)
local GameSelector = Roact.PureComponent:extend("GameSelector")
function GameSelector:render()
return Roact.createElement("ImageLabel", {
Size = UDim2.new(0, 248, 0, 269),
Position = self.props.position,
AnchorPoint = self.props.anchorPoint,
Image = Constants.Image.PLAY_NEXT_SELECTOR,
ImageTransparency = self.props.imageTransparency,
BackgroundTransparency = 1,
ZIndex = self.props.ZIndex,
})
end
return GameSelector
@@ -0,0 +1,123 @@
--[[
A scrolling games list
Props:
focused : bool - Determines if the game list focused.
zIndex : number - Determines the order of UI element rendering.
onMoveSelection : function - The callback function for on the game selection changed.
gamesList : array - An array of games. each element in the array is a map that must contain:
{
universeId : number,
placeId : number,
imageToken : string,
}
]]
local GuiService = game:GetService("GuiService")
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Settings = script.Parent.Parent.Parent
local Modules = Settings.Parent
local Common = Modules.Common
local Analytics = require(Common.Analytics)
local GameIcon = require(Settings.Components.PlayNextGame.GameIcon)
local GamesScrollList = Roact.PureComponent:extend("GamesScrollList")
-- NOTE: event context might need to be changed for other platforms.
local EVENT_CONTEXT = "XboxOne"
function GamesScrollList:init()
self.pageLayout = Roact.createRef()
self.analytics = Analytics.new()
self.setSelection = function(index)
if self.state.selectionIndex ~= index then
local eventName = "MoveNextGameSelection"
self.analytics.EventStream:setRBXEventStream(EVENT_CONTEXT, eventName)
self:setState({
selectionIndex = index
})
end
self.props.onMoveSelection(index)
end
end
function GamesScrollList:render()
local focused = self.props.focused
local gamesList = self.props.gamesList
local zIndex = self.props.zIndex
local children = {}
children.layout = Roact.createElement("UIPageLayout", {
Animated = true,
Circular = true,
EasingDirection = Enum.EasingDirection.InOut,
EasingStyle = Enum.EasingStyle.Quad,
Padding = UDim.new(0, 20),
TweenTime = 0.125,
FillDirection = Enum.FillDirection.Horizontal,
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Center,
SortOrder = Enum.SortOrder.LayoutOrder,
GamepadInputEnabled = true,
[Roact.Ref] = self.pageLayout,
[Roact.Event.PageEnter] = function(rbx)
self.pageLayout.current:ApplyLayout()
end
})
-- NOTE:
-- we want to display 5, but it looks janky when we wraps. So double the elements to
-- fake the look of it wrapping. We can discuss with product about this. Maybe we can have
-- 6 or 7 games instead or duplicating the games.
--
for i in ipairs(gamesList) do
children[tostring(i)] = Roact.createElement(GameIcon, {
universeId = gamesList[i].universeId,
placeId = gamesList[i].placeId,
imageToken = gamesList[i].imageToken,
layoutOrder = i - 1,
focused = focused and self.state.selectionIndex == i,
onSelectionGained = function()
self.setSelection(i)
end,
zIndex = zIndex,
})
end
return Roact.createElement("Frame", {
Size = UDim2.new(0, 1100, 1, 0),
Position = UDim2.new(0.5, 0, 0.5, 0),
AnchorPoint = Vector2.new(0.5, 0.5),
BackgroundTransparency = 1,
ClipsDescendants = true,
}, {
FocusedItem = Roact.createElement("Frame", {
Size = UDim2.new(0, 200, 0, 200),
Position = UDim2.new(0.5, 0, 0.5, 0),
AnchorPoint = Vector2.new(0.5, 0.5),
BackgroundTransparency = 1,
}, children)
})
end
function GamesScrollList:didMount()
self.pageLayout.current:JumpToIndex(0)
-- UIPageLayout relies on child add order. This force applies the layout
-- will not be needed with Quantum GUI
self.pageLayout.current:ApplyLayout()
if self.props.focused then
delay(0, function()
GuiService.SelectedCoreObject = self.pageLayout.current.CurrentPage
end)
end
end
function GamesScrollList:didUpdate(previousProps, previousState)
if self.props.focused == previousProps.focused then
return
end
if self.props.focused then
GuiService.SelectedCoreObject = self.pageLayout.current.CurrentPage
end
end
return GamesScrollList
@@ -0,0 +1,286 @@
--[[
A Roact play next game list section of the leave game screen.
Props:
screenId : Variant the screen id of this section.
zIndex : number - Determines the order of UI element rendering.
focused : bool - Determines if this section is focused.
onLeaveNextGameList : function() - The callback function for when the selection leaves this section
]]
local GuiService = game:GetService("GuiService")
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Otter = require(CorePackages.Otter)
local Settings = script.Parent.Parent.Parent
local Modules = Settings.Parent
local Constants = require(Settings.Pages.LeaveGameScreen.Constants)
local GamesScrollList = require(Settings.Components.PlayNextGame.GamesScrollList)
local GameSelector = require(Settings.Components.PlayNextGame.GameSelector)
local Utility = require(Settings.Utility)
local RobloxTranslator = require(Modules.RobloxTranslator)
local PlayNextGameXbox = Roact.PureComponent:extend("PlayNextGameXbox")
local FRAME_HIDE_HEIGHT = 375
local FRAME_SHOW_HEIGHT = 547
local BG_HIDE_TRANSPARENCY = 1
local BG_SHOW_TRANSPARENCY = 0.5
local SELECTOR_HIDE_TRANSPARENCY = 1
local SELECTOR_SHOW_TRANSPARENCY = 0
local RECENTLY_PLAYED_LABEL = "Feature.SettingsHub.Label.RecentPlayed"
function PlayNextGameXbox:init()
self.screenId = self.props.screenId
self.ref = Roact.createRef()
self.selectorRef = Roact.createRef()
-- Initialize parameters for the spring motion
self.motorOptions = {
dampingRatio = 1,
frequency = 5,
}
self.onMoveSelection = function(index)
if self.state.currentSelection ~= index then
self:setState({
currentSelection = index
})
end
end
self.state =
{
currentSelection = 1,
moveSelection = true,
}
end
function PlayNextGameXbox:render()
local props = self.props
local focused = props.focused
local backgroundTransparency = 1
local selectorTransparency = focused and 0 or 1
local gamesdata = self.props.gamesData
local size = UDim2.new(1, 0, 0, FRAME_HIDE_HEIGHT)
local zIndex = self.props.zIndex + 1
local indicatorArrow;
local gameTitle;
local gameStats;
local gameSelector;
local dividerSize = UDim2.new(0, 864, 0, 2)
if focused then
local upVotes = gamesdata[self.state.currentSelection].totalUpVotes
local downVotes = gamesdata[self.state.currentSelection].totalDownVotes
local gameRating = 0
if upVotes + downVotes ~= 0 then
gameRating = math.floor(((upVotes / (upVotes + downVotes)) * 100) + 0.5);
end
gameTitle = Roact.createElement("TextLabel", {
Size = UDim2.new(1, 0, 0, 60),
Position = UDim2.new(0.5, 0, 0, 355),
AnchorPoint = Vector2.new(0.5, 0),
Text = gamesdata[self.state.currentSelection].name,
Font = Enum.Font.SourceSans,
FontSize = Enum.FontSize.Size48,
TextColor3 = Constants.Color.WHITE,
BackgroundTransparency = 1,
TextWrapped = true,
ZIndex = zIndex,
})
gameStats = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, 28),
Position = UDim2.new(0.5, 0, 0, 420),
AnchorPoint = Vector2.new(0.5, 0),
BackgroundTransparency = 1,
},{
UIListLayout = Roact.createElement("UIListLayout", {
Padding = UDim.new(0, 15),
SortOrder = Enum.SortOrder.LayoutOrder,
FillDirection = Enum.FillDirection.Horizontal,
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Center,
}),
GameRating = Roact.createElement("Frame", {
Size = UDim2.new(0, 90, 0, 30),
BackgroundTransparency = 1,
LayoutOrder = 1,
},{
Icon = Roact.createElement("ImageLabel", {
Size = UDim2.new(0, 28, 0, 28),
Position = UDim2.new(0, 0, 0.5, 0),
AnchorPoint = Vector2.new(0, 0.5),
Image = Constants.Image.THUMB_ICON,
BackgroundTransparency = 1,
ZIndex = zIndex,
}),
Text = Roact.createElement("TextLabel", {
Size = UDim2.new(0, 60, 0, 30),
Position = UDim2.new(0, 36, 0.5, 0),
AnchorPoint = Vector2.new(0, 0.5),
Text = tostring(gameRating)..' %',
Font = Enum.Font.SourceSans,
FontSize = Enum.FontSize.Size24,
TextColor3 = Constants.Color.WHITE,
BackgroundTransparency = 1,
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Center,
ZIndex = zIndex,
}),
}),
NumOfPlayers = Roact.createElement("Frame", {
Size = UDim2.new(0, 100, 0, 30),
BackgroundTransparency = 1,
LayoutOrder = 2,
},{
Icon = Roact.createElement("ImageLabel", {
Size = UDim2.new(0, 28, 0, 28),
Position = UDim2.new(0, 0, 0.5, 0),
AnchorPoint = Vector2.new(0, 0.5),
Image = Constants.Image.PLAYER_NUMBER_ICON,
BackgroundTransparency = 1,
ZIndex = zIndex,
}),
Text = Roact.createElement("TextLabel", {
Size = UDim2.new(0, 60, 1, 0),
Position = UDim2.new(0, 36, 0.5, 0),
AnchorPoint = Vector2.new(0, 0.5),
Text = tostring(gamesdata[self.state.currentSelection].playerCount),
Font = Enum.Font.SourceSans,
FontSize = Enum.FontSize.Size24,
TextColor3 = Constants.Color.WHITE,
BackgroundTransparency = 1,
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Center,
ZIndex = zIndex,
}),
}),
})
gameSelector = Roact.createElement(GameSelector, {
position = UDim2.new(0.5, 0, 0, 192),
anchorPoint = Vector2.new(0.5, 0.5),
imageTransparency = selectorTransparency,
objRef = self.selectorRef,
onActivated = self.onSelectGame,
ZIndex = zIndex + 1,
})
else
indicatorArrow = Roact.createElement("ImageLabel", {
Image = Constants.Image.DOWN_ARROW,
Size = UDim2.new(0, 24, 0, 12),
AnchorPoint = Vector2.new(0.5, 0),
Position = UDim2.new(0.5, 0, 0, 40),
BackgroundTransparency = 1,
ZIndex = zIndex,
})
end
return Roact.createElement("Frame", {
Size = size,
AnchorPoint = Vector2.new(0.5, 1),
Position = UDim2.new(0.5, 0, 1, 0),
BackgroundTransparency = backgroundTransparency,
BackgroundColor3 = Color3.fromRGB(1, 1, 1),
BorderSizePixel = 0,
ZIndex = zIndex,
[Roact.Ref] = self.ref,
},{
LeftDivider = Roact.createElement("Frame", {
Size = dividerSize,
AnchorPoint = Vector2.new(0, 0),
Position = UDim2.new(0, 0, 0, 0),
BorderSizePixel = 0,
BackgroundColor3 = Constants.Color.WHITE,
ZIndex = zIndex,
}),
RightDivider = Roact.createElement("Frame", {
Size = dividerSize,
AnchorPoint = Vector2.new(1, 0),
Position = UDim2.new(1, 0, 0, 0),
BorderSizePixel = 0,
BackgroundColor3 = Constants.Color.WHITE,
ZIndex = zIndex,
}),
NextGameListLabel = Roact.createElement("TextLabel", {
Size = UDim2.new(1, 0, 0, 30),
Position = UDim2.new(0.5, 0, 0, -3),
AnchorPoint = Vector2.new(0.5, 0.5),
--TODO: Need to be localized.
Text = RobloxTranslator:FormatByKey(RECENTLY_PLAYED_LABEL),
Font = Enum.Font.SourceSans,
FontSize = Enum.FontSize.Size24,
TextColor3 = Constants.Color.WHITE,
BackgroundTransparency = 1,
TextWrapped = true,
TextXAlignment = "Center",
TextYAlignment = "Center",
ZIndex = zIndex + 1,
}),
IndicatorArrow = indicatorArrow,
GamesList = Roact.createElement("Frame",{
Position = UDim2.new(0.5, 0, 0, 66),
AnchorPoint = Vector2.new(0.5, 0),
Size = UDim2.new(1, 0, 0, 232),
BackgroundTransparency = 1,
},{
GamesScrollList = Roact.createElement(GamesScrollList,{
focused = focused,
onMoveSelection = self.onMoveSelection,
gamesList = gamesdata,
zIndex = zIndex,
}),
}),
GameTitle = gameTitle,
GameStats = gameStats,
GameSelector = gameSelector,
Redirect = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, 10),
Position = UDim2.new(0.5, 0, 0, -100),
AnchorPoint = Vector2.new(0.5, 0),
Selectable = true,
BackgroundTransparency = 1,
[Roact.Event.SelectionGained] = function()
GuiService.SelectedCoreObject = nil
self.props.onLeaveNextGameList()
end
}),
})
end
function PlayNextGameXbox:didMount()
delay(0, function()
if self.props.focused and self.ref.current then
GuiService:AddSelectionParent(self.screenId, self.ref.current)
end
end)
self.motor = Otter.createSingleMotor(0)
self.motor:onStep(function(value)
local t = value/100
if self.ref.current ~= nil then
self.ref.current.Size = UDim2.new(1, 0, 0, Utility:Round(Utility:Lerp(t, FRAME_HIDE_HEIGHT, FRAME_SHOW_HEIGHT)))
self.ref.current.BackgroundTransparency = Utility:Lerp(t, BG_HIDE_TRANSPARENCY, BG_SHOW_TRANSPARENCY)
end
if self.selectorRef.current ~= nil then
self.selectorRef.current.ImageTransparency = Utility:Lerp(t, SELECTOR_HIDE_TRANSPARENCY, SELECTOR_SHOW_TRANSPARENCY)
end
end)
self.motor:start()
end
function PlayNextGameXbox:didUpdate(previousProps, previousState)
if self.props.focused == previousProps.focused then
return
end
GuiService:RemoveSelectionGroup(self.screenId)
if self.props.focused and self.ref.current then
GuiService:AddSelectionParent(self.screenId, self.ref.current)
end
local targetPercentage = self.props.focused and 100 or 0
self.motor:setGoal(Otter.spring(targetPercentage, self.motorOptions))
end
function PlayNextGameXbox:willUnmount()
GuiService:RemoveSelectionGroup(self.screenId)
self.motor:destroy()
end
return PlayNextGameXbox
@@ -0,0 +1,78 @@
--[[
Creates a Roact component that is a grey rounded button
Props:
position : UDim2 - Position of the button.
zIndex : number - Determines the order of UI element rendering.
anchorPoint : UDim2 - The anchor point of the button.
onResume : function() - Fires when the button is activated.
]]
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Settings = script.Parent.Parent
local Modules = Settings.Parent
local Constants = require(Settings.Pages.LeaveGameScreen.Constants)
local RoundedSelectionImage = require(Settings.Common.RoundedSelectionImage)
local RobloxTranslator = require(Modules.RobloxTranslator)
local ResumeButton = Roact.PureComponent:extend("ResumeButton")
local RESUME_GAME_BUTTON_LABEL = "Feature.SettingsHub.Label.DontLeaveButton"
function ResumeButton:init()
self.onSelectionGained = function()
self:setState({
focused = true
})
end
self.onSelectionLost = function()
self:setState({
focused = false
})
end
end
function ResumeButton:render()
local size = UDim2.new(0, 320, 0, 80)
local position = self.props.position or UDim2.new(0.5, 0, 0.5, 0)
local anchorPoint = self.props.anchorPoint or Vector2.new(0.5, 0.5)
local zIndex = self.props.zIndex
local text = RobloxTranslator:FormatByKey(RESUME_GAME_BUTTON_LABEL)
local buttonColor = Constants.Color.GREY
local textColor = Constants.Color.WHITE
local selector = RoundedSelectionImage()
if self.state.focused then
buttonColor = Constants.Color.BLUE
textColor = Constants.Color.DARK
end
local label = Roact.createElement("TextLabel",{
Text = text,
Size = UDim2.new(1, 0, 1, 0),
Position = UDim2.new(0.5, 0, 0.5, 0),
AnchorPoint = Vector2.new(0.5, 0.5),
Font = Enum.Font.SourceSans,
TextSize = Constants.TextSize.BUTTON,
TextXAlignment = Enum.TextXAlignment.Center,
TextColor3 = textColor,
BackgroundTransparency = 1,
ZIndex = zIndex + 1
})
return Roact.createElement("ImageButton",{
Image = Constants.Image.BUTTON,
Size = size,
Position = position,
AnchorPoint = anchorPoint,
[Roact.Event.SelectionGained] = self.onSelectionGained,
[Roact.Event.SelectionLost] = self.onSelectionLost,
[Roact.Event.Activated] = self.props.onResume,
ImageColor3 = buttonColor,
ScaleType = Enum.ScaleType.Slice,
SliceCenter = Rect.new(8, 8, 9, 9),
SelectionImageObject = selector,
BackgroundTransparency = 1,
ZIndex = zIndex
},{
Label = label,
})
end
return ResumeButton
@@ -0,0 +1,44 @@
--[[
{
universeId : number,
placeId : number,
name : string,
playerCount : number,
imageToken : string,
totalUpVotes : number,
totalDownVotes : number,
}
]]
local Game = {}
function Game.new()
local self = {}
return self
end
function Game.fromGameCache(gameCache)
local self = Game.new()
self.universeId = gameCache.UniverseId
self.placeId = gameCache.PlaceId
self.name = gameCache.Name
self.playerCount = gameCache.PlayerCount
self.imageToken = gameCache.ImageToken
self.totalUpVotes = gameCache.VoteData.UpVotes
self.totalDownVotes = gameCache.VoteData.DownVotes
return self
end
function Game.fromJsonData(gameJson)
local self = Game.new()
self.universeId = gameJson.universeId
self.placeId = gameJson.placeId
self.name = gameJson.name
self.playerCount = gameJson.playerCount
self.imageToken = gameJson.imageToken
self.totalUpVotes = gameJson.totalUpVotes
self.totalDownVotes = gameJson.totalDownVotes
return self
end
return Game
@@ -0,0 +1,654 @@
--[[
Filename: Help.lua
Written by: jeditkacheff
Version 1.0
Description: Takes care of the help page in Settings Menu
--]]
------------ FFLAGS -------------------
local success, result = pcall(function() return settings():GetFFlag('UseNotificationsLocalization') end)
local FFlagUseNotificationsLocalization = success and result
-------------- CONSTANTS --------------
local KEYBOARD_MOUSE_TAG = "KeyboardMouse"
local TOUCH_TAG = "Touch"
local GAMEPAD_TAG = "Gamepad"
local PC_TABLE_SPACING = 4
local XBOX_CONTROLLER_IMAGE_OFFSET = 30
local TEXT_EDGE_DISTANCE = 20
-------------- SERVICES --------------
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local UserInputService = game:GetService("UserInputService")
local GuiService = game:GetService("GuiService")
local TextService = game:GetService("TextService")
local Settings = UserSettings()
local GameSettings = Settings.GameSettings
----------- UTILITIES --------------
local utility = require(RobloxGui.Modules.Settings.Utility)
------------ Variables -------------------
local PageInstance = nil
RobloxGui:WaitForChild("Modules"):WaitForChild("TenFootInterface")
local isTenFootInterface = require(RobloxGui.Modules.TenFootInterface):IsEnabled()
----------- CLASS DECLARATION --------------
local function Initialize()
local settingsPageFactory = require(RobloxGui.Modules.Settings.SettingsPageFactory)
local this = settingsPageFactory:CreateNewPage()
this.HelpPages = {}
this.HelpPageContents = {}
this.ActiveHelpScheme = nil
local lastInputType = nil
function this:GetCurrentInputType()
if lastInputType == nil then -- we don't know what controls the user has, just use reasonable defaults
local platform = UserInputService:GetPlatform()
if platform == Enum.Platform.XBoxOne or platform == Enum.Platform.WiiU then
return GAMEPAD_TAG
elseif platform == Enum.Platform.Windows or platform == Enum.Platform.OSX then
return KEYBOARD_MOUSE_TAG
else
return TOUCH_TAG
end
end
if lastInputType == Enum.UserInputType.Keyboard or lastInputType == Enum.UserInputType.MouseMovement or
lastInputType == Enum.UserInputType.MouseButton1 or lastInputType == Enum.UserInputType.MouseButton2 or
lastInputType == Enum.UserInputType.MouseButton3 or lastInputType == Enum.UserInputType.MouseWheel then
return KEYBOARD_MOUSE_TAG
elseif lastInputType == Enum.UserInputType.Touch then
return TOUCH_TAG
elseif lastInputType == Enum.UserInputType.Gamepad1 or lastInputType == Enum.UserInputType.Gamepad2 or
lastInputType == Enum.UserInputType.Gamepad3 or lastInputType == Enum.UserInputType.Gamepad4 then
return GAMEPAD_TAG
end
return KEYBOARD_MOUSE_TAG
end
local function createPCHelp(parentFrame)
local function createPCGroup(title, actionInputBindings)
local textIndent = 9
local pcGroupFrame = utility:Create'Frame'
{
Size = UDim2.new(1/3,-PC_TABLE_SPACING,1,0),
BackgroundTransparency = 1,
Name = "PCGroupFrame" .. tostring(title)
};
local pcGroupTitle = utility:Create'TextLabel'
{
Position = UDim2.new(0,textIndent,0,0),
Size = UDim2.new(1,-textIndent,0,30),
BackgroundTransparency = 1,
Text = title,
Font = Enum.Font.SourceSansBold,
FontSize = Enum.FontSize.Size18,
TextColor3 = Color3.new(1,1,1),
TextXAlignment = Enum.TextXAlignment.Left,
Name = "PCGroupTitle" .. tostring(title),
ZIndex = 2,
Parent = pcGroupFrame
};
local count = 0
local frameHeight = 42
local spacing = 2
local offset = pcGroupTitle.Size.Y.Offset
for i = 1, #actionInputBindings do
for actionName, inputName in pairs(actionInputBindings[i]) do
local actionInputFrame = utility:Create'Frame'
{
Size = UDim2.new(1,0,0,frameHeight),
Position = UDim2.new(0,0,0, offset + ((frameHeight + spacing) * count)),
BackgroundTransparency = 0.65,
BorderSizePixel = 0,
ZIndex = 2,
Name = "ActionInputBinding" .. tostring(actionName),
Parent = pcGroupFrame
};
local nameLabel = utility:Create'TextLabel'
{
Size = UDim2.new(0.4,-textIndent,0,frameHeight),
Position = UDim2.new(0,textIndent,0,0),
BackgroundTransparency = 1,
Text = actionName,
Font = Enum.Font.SourceSansBold,
FontSize = Enum.FontSize.Size18,
TextColor3 = Color3.new(1,1,1),
TextXAlignment = Enum.TextXAlignment.Left,
Name = actionName .. "Label",
ZIndex = 2,
Parent = actionInputFrame,
TextWrapped = true,
TextScaled = true
};
do
local textSizeConstraint = Instance.new("UITextSizeConstraint",nameLabel)
textSizeConstraint.MaxTextSize = 18
end
local inputLabel = utility:Create'TextLabel'
{
Size = UDim2.new(0.5,0,0,frameHeight),
Position = UDim2.new(0.5,-4,0,0),
BackgroundTransparency = 1,
Text = inputName,
Font = Enum.Font.SourceSans,
FontSize = Enum.FontSize.Size18,
TextColor3 = Color3.new(1,1,1),
TextXAlignment = Enum.TextXAlignment.Left,
Name = inputName .. "Label",
ZIndex = 2,
Parent = actionInputFrame,
TextWrapped = true,
TextScaled = true
};
do
local textSizeConstraint = Instance.new("UITextSizeConstraint",inputLabel)
textSizeConstraint.MaxTextSize = 18
end
count = count + 1
end
end
pcGroupFrame.Size = UDim2.new(pcGroupFrame.Size.X.Scale,pcGroupFrame.Size.X.Offset,
0, offset + ((frameHeight + spacing) * count))
return pcGroupFrame
end
local rowOffset = 50
local isOSX = UserInputService:GetPlatform() == Enum.Platform.OSX
local charMoveFrame = createPCGroup( "Character Movement", {[1] = {["Move Forward"] = "W/Up Arrow"},
[2] = {["Move Backward"] = "S/Down Arrow"},
[3] = {["Move Left"] = "A/Left Arrow"},
[4] = {["Move Right"] = "D/Right Arrow"},
[5] = {["Jump"] = "Space"}} )
charMoveFrame.Parent = parentFrame
local accessoriesFrame = createPCGroup("Accessories", {
[1] = {["Equip Tools"] = "1,2,3..."},
[2] = {["Unequip Tools"] = "1,2,3..."},
[3] = {["Drop Tool"] = "Backspace"},
[4] = {["Use Tool"] = "Left Mouse Button"} })
accessoriesFrame.Position = UDim2.new(1/3,PC_TABLE_SPACING,0,0)
accessoriesFrame.Parent = parentFrame
local miscFrame = nil
miscFrame = createPCGroup("Misc", {
[1] = {["Screenshot"] = "Print Screen"},
[2] = {["Record Video"] = isOSX and "F12/fn + F12" or "F12"},
[3] = {["Dev Console"] = isOSX and "F9/fn + F9" or "F9"},
[4] = {["Mouselock"] = "Shift"},
[5] = {["Graphics Level"] = isOSX and "F10/fn + F10" or "F10"},
[6] = {["Fullscreen"] = isOSX and "F11/fn + F11" or "F11"},
[7] = {["Perf. Stats"] = isOSX and "Fn+Opt+Cmd+F7" or "Ctrl + Shift + F7"},
}
)
miscFrame.Position = UDim2.new(2/3,PC_TABLE_SPACING * 2,0,0)
miscFrame.Parent = parentFrame
local camFrame = createPCGroup("Camera Movement", { [1] = {["Rotate"] = "Right Mouse Button"},
[2] = {["Zoom In/Out"] = "Mouse Wheel"},
[3] = {["Zoom In"] = "I"},
[4] = {["Zoom Out"] = "O"} })
camFrame.Position = UDim2.new(0,0,charMoveFrame.Size.Y.Scale,charMoveFrame.Size.Y.Offset + rowOffset)
camFrame.Parent = parentFrame
local menuFrame = createPCGroup("Menu Items", { [1] = {["Roblox Menu"] = "ESC"},
[2] = {["Backpack"] = "~"},
[3] = {["Playerlist"] = "TAB"},
[4] = {["Chat"] = "/"} })
menuFrame.Position = UDim2.new(1/3,PC_TABLE_SPACING,charMoveFrame.Size.Y.Scale,charMoveFrame.Size.Y.Offset + rowOffset)
menuFrame.Parent = parentFrame
parentFrame.Size = UDim2.new(parentFrame.Size.X.Scale, parentFrame.Size.X.Offset, 0,
menuFrame.Size.Y.Offset + menuFrame.Position.Y.Offset)
end
local function createGamepadHelp(parentFrame)
local gamepadImage = nil
local imageSize = nil
gamepadImage = "rbxasset://textures/ui/Settings/Help/GenericController.png"
imageSize = UDim2.new(0, 473, 0, 287)
local imagePosition = UDim2.new(0.5, -imageSize.X.Offset/2, 0.5, -imageSize.Y.Offset/2)
if UserInputService:GetPlatform() == Enum.Platform.XBoxOne or UserInputService:GetPlatform() == Enum.Platform.XBox360 then
gamepadImage = "rbxasset://textures/ui/Settings/Help/XboxController.png"
imageSize = UDim2.new(0, 745, 0, 452)
imagePosition = UDim2.new(0.5, (-imageSize.X.Offset/2) + XBOX_CONTROLLER_IMAGE_OFFSET, 0.5, -imageSize.Y.Offset/2 + 7)
elseif UserInputService:GetPlatform() == Enum.Platform.PS4 or UserInputService:GetPlatform() == Enum.Platform.PS3 then
gamepadImage = "rbxasset://textures/ui/Settings/Help/PSController.png"
end
local gamepadImageLabel = utility:Create'ImageLabel'
{
Name = "GamepadImage",
Size = imageSize,
Position = imagePosition,
Image = gamepadImage,
BackgroundTransparency = 1,
ZIndex = 2,
Parent = parentFrame
};
parentFrame.Size = UDim2.new(parentFrame.Size.X.Scale, parentFrame.Size.X.Offset, 0, gamepadImageLabel.Size.Y.Offset + 100)
local gamepadFontSize = isTenFootInterface and Enum.FontSize.Size36 or Enum.FontSize.Size24
local textVerticalSize = (gamepadFontSize == Enum.FontSize.Size36) and 36 or 24
local function createGamepadLabel(text, position, size, rightAligned)
local nameLabel = nil
if FFlagUseNotificationsLocalization == true then
nameLabel = utility:Create'TextLabel'{
Position = position,
Size = size,
BackgroundTransparency = 1,
Text = text,
TextXAlignment = rightAligned and Enum.TextXAlignment.Right or Enum.TextXAlignment.Left,
AnchorPoint = rightAligned and Vector2.new(1, 0.5) or Vector2.new(0, 0.5),
Font = Enum.Font.SourceSansBold,
FontSize = gamepadFontSize,
TextColor3 = Color3.new(1,1,1),
Name = text .. "Label",
ZIndex = 2,
Parent = gamepadImageLabel,
TextScaled = true,
TextWrapped = true
};
else
nameLabel = utility:Create'TextLabel'{
Position = position,
Size = size,
BackgroundTransparency = 1,
Text = text,
TextXAlignment = rightAligned and Enum.TextXAlignment.Right or Enum.TextXAlignment.Left,
AnchorPoint = rightAligned and Vector2.new(1, 0.5) or Vector2.new(0, 0.5),
Font = Enum.Font.SourceSansBold,
FontSize = gamepadFontSize,
TextColor3 = Color3.new(1,1,1),
Name = text .. "Label",
ZIndex = 2,
Parent = gamepadImageLabel
};
end
nameLabel.TextWrapped = true
local textSize = TextService:GetTextSize(text, textVerticalSize, Enum.Font.SourceSansBold, Vector2.new(0, 0))
local minSizeXOffset = textSize.X
local distanceToCenter = math.abs(position.X.Offset)
local parentGui = (gamepadImage == "rbxasset://textures/ui/Settings/Help/XboxController.png") and RobloxGui or parentFrame
local function updateNameLabelSize()
local nameLabelSizeXOffset = nameLabel.Size.X.Offset
if gamepadImage == "rbxasset://textures/ui/Settings/Help/XboxController.png" then
nameLabelSizeXOffset = rightAligned and
RobloxGui.AbsoluteSize.X/2 + XBOX_CONTROLLER_IMAGE_OFFSET - distanceToCenter - TEXT_EDGE_DISTANCE or
RobloxGui.AbsoluteSize.X/2 - XBOX_CONTROLLER_IMAGE_OFFSET - distanceToCenter - TEXT_EDGE_DISTANCE
else
nameLabelSizeXOffset = parentFrame.AbsoluteSize.X/2 - distanceToCenter
end
if nameLabelSizeXOffset < minSizeXOffset then
nameLabel.Size = UDim2.new(nameLabel.Size.X.Scale, nameLabelSizeXOffset, nameLabel.Size.Y.Scale, textVerticalSize * 2)
nameLabel.TextScaled = true
else
nameLabel.Size = UDim2.new(nameLabel.Size.X.Scale, nameLabelSizeXOffset, nameLabel.Size.Y.Scale, textVerticalSize)
nameLabel.FontSize = gamepadFontSize
nameLabel.TextScaled = false
end
end
local nameLabelChangeCn = parentGui:GetPropertyChangedSignal('AbsoluteSize'):connect(function()
updateNameLabelSize()
end)
updateNameLabelSize()
end
if gamepadImage == "rbxasset://textures/ui/Settings/Help/XboxController.png" then
createGamepadLabel("Switch Tool", UDim2.new(0.5, -390, 0, 0), UDim2.new(0, 100, 0, textVerticalSize), true)
createGamepadLabel("Game Menu Toggle", UDim2.new(0.5, -390, 0.15, 0), UDim2.new(0, 164, 0, textVerticalSize), true)
createGamepadLabel("Move", UDim2.new(0.5, -390, 0.31, 0), UDim2.new(0, 46, 0, textVerticalSize), true)
createGamepadLabel("Menu Navigation", UDim2.new(0.5, -390, 0.46, 0), UDim2.new(0, 164, 0, textVerticalSize), true)
createGamepadLabel("Use Tool", UDim2.new(0.5, 330, 0, 0), UDim2.new(0, 73, 0, textVerticalSize))
createGamepadLabel("Roblox Menu", UDim2.new(0.5, 330, 0.15, 0), UDim2.new(0, 122, 0, textVerticalSize))
createGamepadLabel("Back", UDim2.new(0.5, 330, 0.31, 0), UDim2.new(0, 43, 0, textVerticalSize))
createGamepadLabel("Jump", UDim2.new(0.5, 330, 0.46, 0), UDim2.new(0, 49, 0, textVerticalSize))
createGamepadLabel("Rotate Camera", UDim2.new(0.5, 380, 0.62, 0), UDim2.new(0, 132, 0, textVerticalSize))
createGamepadLabel("Camera Zoom", UDim2.new(0.5, 380, 0.77, 0), UDim2.new(0, 122, 0, textVerticalSize))
else
createGamepadLabel("Switch Tool", UDim2.new(0.5, -250, 0, 0), UDim2.new(0, 100, 0, textVerticalSize), true)
createGamepadLabel("Game Menu Toggle", UDim2.new(0.5, -250, 0.15, 0), UDim2.new(0, 164, 0, textVerticalSize), true)
createGamepadLabel("Move", UDim2.new(0.5, -250, 0.31, 0), UDim2.new(0, 46, 0, textVerticalSize), true)
createGamepadLabel("Menu Navigation", UDim2.new(0.5, -250, 0.46, 0), UDim2.new(0, 143, 0, textVerticalSize), true)
createGamepadLabel("Use Tool", UDim2.new(0.5, 215, 0, 0), UDim2.new(0, 73, 0, textVerticalSize))
createGamepadLabel("Roblox Menu", UDim2.new(0.5, 215, 0.15, 0), UDim2.new(0, 122, 0, textVerticalSize))
createGamepadLabel("Back", UDim2.new(0.5, 215, 0.31, 0), UDim2.new(0, 43, 0, textVerticalSize))
createGamepadLabel("Jump", UDim2.new(0.5, 215, 0.46, 0), UDim2.new(0, 49, 0, textVerticalSize))
createGamepadLabel("Rotate Camera", UDim2.new(0.5, 255, 0.62, 0), UDim2.new(0, 132, 0, textVerticalSize))
createGamepadLabel("Camera Zoom", UDim2.new(0.5, 255, 0.77, 0), UDim2.new(0, 122, 0, textVerticalSize))
end
-- NOTE: On consoles we put the dev console in the settings menu. Only place
-- owners can see this for now.
end
local function updateTouchLayout(scheme) -- adjust layout to work well with the various touch control schemes
this.ActiveHelpScheme = scheme
local isPortrait = utility:IsPortrait()
local helpFrame = this.HelpPages[TOUCH_TAG]
if helpFrame then
local helpElements = this.HelpPageContents[TOUCH_TAG]
local function hideUneeded(list)
if list then
for name, item in pairs(helpElements) do
item.Visible = not list[name]
end
end
end
local hidden
if scheme == Enum.TouchMovementMode.DynamicThumbstick then
-- show that movement is done by dragging
-- show that tapping on bottom of the screen is to jump
-- show that tapping on the top of the screen is to use tools
-- show that dragging on the top of the screen is to pan camera
hidden = {MoveImageCTM = true}
helpElements["MoveLabel"].Position = isPortrait and UDim2.new(0.25,-helpElements["MoveLabel"].AbsoluteSize.x/2,0.75,-50) or UDim2.new(0.15,-helpElements["MoveLabel"].AbsoluteSize.x/2,0.85,-helpElements["MoveLabel"].AbsoluteSize.y)
helpElements["JumpLabel"].Position = isPortrait and UDim2.new(0.75,-helpElements["JumpLabel"].AbsoluteSize.x/2,0.75,-50) or UDim2.new(0.85,-60,0.85,-helpElements["JumpLabel"].AbsoluteSize.y)
helpElements["RotateLabel"].Position = isPortrait and UDim2.new(1,-helpElements["RotateLabel"].AbsoluteSize.x-20,0.02,0) or UDim2.new(0.85,-helpElements["RotateLabel"].AbsoluteSize.x/2,0.02,0)
helpElements["UseToolLabel"].Position = isPortrait and UDim2.new(0.5,-helpElements["UseToolLabel"].AbsoluteSize.x/2,0.5,-100) or UDim2.new(0.5,-helpElements["UseToolLabel"].AbsoluteSize.x/2,0.5,-60)
helpElements["EquipLabel"].Position = isPortrait and UDim2.new(0.5,-60,0.75,50) or UDim2.new(0.5,-60,0.64,0)
helpElements["ZoomLabel"].Position = isPortrait and UDim2.new(0,20,0.02,0) or UDim2.new(0.15,-60,0.02,0)
elseif scheme == Enum.TouchMovementMode.ClickToMove then
-- show that dragging on the screen is to pan the camera
-- show that tapping is to move
hidden = {BottomHalfDisplay = true, MoveImageDTS = true, JumpLabel = true} -- cant manually jump on ctm
helpElements["MoveLabel"].Position = isPortrait and UDim2.new(0.25,-helpElements["MoveLabel"].AbsoluteSize.x/2,0.5,0) or UDim2.new(0.25,-helpElements["MoveLabel"].AbsoluteSize.x/2,0.5,40)
helpElements["RotateLabel"].Position = isPortrait and UDim2.new(1,-helpElements["RotateLabel"].AbsoluteSize.x-20,0.02,0) or UDim2.new(0.5,-60,0.02,0)
helpElements["UseToolLabel"].Position = isPortrait and UDim2.new(0.75,-helpElements["UseToolLabel"].AbsoluteSize.x/2,0.5,0) or UDim2.new(0.85,-60,0.02,0)
helpElements["EquipLabel"].Position = isPortrait and UDim2.new(0.5,-60,0.75,50) or UDim2.new(0.5,-60,0.64,0)
helpElements["ZoomLabel"].Position = isPortrait and UDim2.new(0,20,0.02,0) or UDim2.new(0.15,-60,0.02,0)
else
-- keep the default style, but take portrait mode into account
hidden = {BottomHalfDisplay = true, MoveImageDTS = true, MoveImageCTM = true, JumpImage = true} -- if theres a jump button we don't need to do touch gestures, same with thumbstick
helpElements["MoveLabel"].Position = isPortrait and UDim2.new(0.06,0,1,-120) or UDim2.new(0.06,0,0.58,0)
helpElements["JumpLabel"].Position = isPortrait and UDim2.new(0.94,-helpElements["JumpLabel"].AbsoluteSize.x,1,-120) or UDim2.new(0.8,0,0.58,0)
helpElements["RotateLabel"].Position = isPortrait and UDim2.new(1,-helpElements["RotateLabel"].AbsoluteSize.x-20,0.02,0) or UDim2.new(0.5,-60,0.02,0)
helpElements["UseToolLabel"].Position = isPortrait and UDim2.new(.5,-helpElements["UseToolLabel"].AbsoluteSize.x/2,0.25,50) or UDim2.new(0.85,-60,0.02,0)
helpElements["EquipLabel"].Position = isPortrait and UDim2.new(0.5,-60,0.75,50) or UDim2.new(0.5,-60,0.64,0)
helpElements["ZoomLabel"].Position = isPortrait and UDim2.new(0,20,0.02,0) or UDim2.new(0.15,-60,0.02,0)
end
hideUneeded(hidden)
end
end
local function createTouchHelp(parentFrame)
local createdElements = {} -- dictionary of buttons created
local smallScreen = utility:IsSmallTouchScreen()
local ySize = GuiService:GetScreenResolution().y - 350
if smallScreen then
ySize = GuiService:GetScreenResolution().y - 100
end
parentFrame.Size = UDim2.new(1,0,0,ySize)
local function createDisplayFrame(name, position, size, transparency, color, parent)
local frame = utility:Create'Frame'
{
BackgroundColor3 = color,
Position = position,
Size = size,
BackgroundTransparency = transparency,
Name = name,
ZIndex = 1,
Parent = parent
};
return frame
end
local function createTouchLabel(text, position, size, parent)
local nameLabel = utility:Create'TextLabel'
{
Position = position,
Size = size,
BackgroundTransparency = 1,
Text = text,
Font = Enum.Font.SourceSansBold,
FontSize = Enum.FontSize.Size14,
TextColor3 = Color3.new(1,1,1),
Name = text .. "Label",
ZIndex = 3,
Parent = parent,
TextScaled = true,
TextWrapped = true
};
if not smallScreen then
nameLabel.FontSize = Enum.FontSize.Size18
nameLabel.Size = UDim2.new(nameLabel.Size.X.Scale, nameLabel.Size.X.Offset, nameLabel.Size.Y.Scale, nameLabel.Size.Y.Offset + 4)
end
local nameBackgroundImage = utility:Create'ImageLabel'
{
Name = text .. "BackgroundImage",
Size = UDim2.new(1.25,0,1.25,0),
Position = UDim2.new(-0.125,0,-0.065,0),
BackgroundTransparency = 1,
Image = "rbxasset://textures/ui/Settings/Radial/RadialLabel.png",
ScaleType = Enum.ScaleType.Slice,
SliceCenter = Rect.new(12,2,65,21),
ZIndex = 2,
Parent = nameLabel
};
local textSizeConstraint = Instance.new("UITextSizeConstraint",nameLabel)
textSizeConstraint.MaxTextSize = 18
return nameLabel
end
local function createTouchGestureImage(name, image, position, size, parent)
local gestureImage = utility:Create'ImageLabel'
{
Name = name,
Size = size,
Position = position,
BackgroundTransparency = 1,
Image = image,
ZIndex = 2,
Parent = parent
};
return gestureImage
end
local xSizeOffset = 30
local ySize = 25
if smallScreen then xSizeOffset = 0 end
createdElements["BottomHalfDisplay"] = createDisplayFrame("BottomHalfFrame", UDim2.new(0,0,0.5,-16), UDim2.new(1,0,1,0), 0.35, Color3.new(0,0,0), parentFrame)
-- movement stuff
createdElements["MoveLabel"] = createTouchLabel("Move", UDim2.new(0.06,0,0.58,0), UDim2.new(0,77 + xSizeOffset,0,ySize), parentFrame)
createdElements["MoveImageDTS"] = createTouchGestureImage("MoveImageDTS", "rbxasset://textures/ui/Settings/Help/RotateCameraGesture.png", UDim2.new(0.5,-32,1,3), UDim2.new(0,65,0,48), createdElements["MoveLabel"])
createdElements["MoveImageCTM"] = createTouchGestureImage("MoveImageCTM", "rbxasset://textures/ui/Settings/Help/UseToolGesture.png", UDim2.new(0.5,-19,1,3), UDim2.new(0,38,0,52), createdElements["MoveLabel"])
-- jumping stuff
createdElements["JumpLabel"] = createTouchLabel("Jump", UDim2.new(0.8,0,0.58,0), UDim2.new(0,77 + xSizeOffset,0,ySize), parentFrame)
createdElements["JumpImage"] = createTouchGestureImage("JumpImage", "rbxasset://textures/ui/Settings/Help/UseToolGesture.png", UDim2.new(0.5,-19,1,3), UDim2.new(0,38,0,52), createdElements["JumpLabel"])
createdElements["EquipLabel"] = createTouchLabel("Equip/Unequip Tools", UDim2.new(0.5,-60,0.64,0), UDim2.new(0,120 + xSizeOffset,0,ySize), parentFrame)
createdElements["ZoomLabel"] = createTouchLabel("Zoom In/Out", UDim2.new(0.15,-60,0.02,0), UDim2.new(0,120,0,ySize), parentFrame)
createdElements["ZoomImage"] = createTouchGestureImage("ZoomImage", "rbxasset://textures/ui/Settings/Help/ZoomGesture.png", UDim2.new(0.5,-26,1,3), UDim2.new(0,53,0,59), createdElements["ZoomLabel"])
createdElements["RotateLabel"] = createTouchLabel("Rotate Camera", UDim2.new(0.5,-60,0.02,0), UDim2.new(0,120,0,ySize), parentFrame)
createdElements["RotateImage"] = createTouchGestureImage("RotateImage", "rbxasset://textures/ui/Settings/Help/RotateCameraGesture.png", UDim2.new(0.5,-32,1,3), UDim2.new(0,65,0,48), createdElements["RotateLabel"])
createdElements["UseToolLabel"] = createTouchLabel("Use Tool", UDim2.new(0.85,-60,0.02,0), UDim2.new(0,120,0,ySize), parentFrame)
createdElements["ToolImage"] = createTouchGestureImage("ToolImage", "rbxasset://textures/ui/Settings/Help/UseToolGesture.png", UDim2.new(0.5,-19,1,3), UDim2.new(0,38,0,52), createdElements["UseToolLabel"])
return createdElements
end
local function createHelpDisplay(typeOfHelp)
local helpContents = nil
local helpFrame = utility:Create'Frame'
{
Size = UDim2.new(1,0,1,0),
BackgroundTransparency = 1,
Name = "HelpFrame" .. tostring(typeOfHelp)
};
if typeOfHelp == KEYBOARD_MOUSE_TAG then
createPCHelp(helpFrame)
elseif typeOfHelp == GAMEPAD_TAG then
createGamepadHelp(helpFrame)
elseif typeOfHelp == TOUCH_TAG then
helpContents = createTouchHelp(helpFrame)
end
return helpFrame, helpContents
end
local function displayHelp(currentPage)
for i, helpPage in pairs(this.HelpPages) do
if helpPage == currentPage then
helpPage.Parent = this.Page
this.Page.Size = helpPage.Size
if isTenFootInterface then
this.Page.Size = UDim2.new(helpPage.Size.X.Scale, helpPage.Size.X.Offset, helpPage.Size.Y.Scale, 0)
end
else
helpPage.Parent = nil
end
end
if UserInputService:GetPlatform() == Enum.Platform.XBoxOne then
this.HubRef.PageViewClipper.ClipsDescendants = false
this.HubRef.PageView.ClipsDescendants = false
end
end
local function switchToHelp(typeOfHelp)
local helpPage = this.HelpPages[typeOfHelp]
if helpPage then
displayHelp(helpPage)
if typeOfHelp == TOUCH_TAG then
-- update for the control scheme
local scheme = GameSettings.TouchMovementMode
if this.ActiveHelpScheme ~= scheme then
updateTouchLayout(scheme)
end
end
else
this.HelpPages[typeOfHelp], this.HelpPageContents[typeOfHelp] = createHelpDisplay(typeOfHelp)
switchToHelp(typeOfHelp)
end
end
local function showTypeOfHelp()
switchToHelp(this:GetCurrentInputType())
end
local function adjustForScreenLayout(givenSize) -- portrait mode was causing the help frame to be either too tall or short when changed between landscape mode and portrait.
if this:GetCurrentInputType() == TOUCH_TAG then
local scheme = GameSettings.TouchMovementMode
local smallScreen = utility:IsSmallTouchScreen()
local size = givenSize or GuiService:GetScreenResolution()
local ySize = size.y - 350
if smallScreen then
ySize = size.y - 100
end
this.HelpPages[TOUCH_TAG].Size = UDim2.new(1,0,0,ySize)
updateTouchLayout(scheme)
end
end
------ TAB CUSTOMIZATION -------
this.TabHeader.Name = "HelpTab"
this.TabHeader.Icon.Image = "rbxasset://textures/ui/Settings/MenuBarIcons/HelpTab.png"
if FFlagUseNotificationsLocalization then
this.TabHeader.Title.Text = "Help"
else
this.TabHeader.Icon.Title.Text = "Help"
end
------ PAGE CUSTOMIZATION -------
this.Page.Name = "Help"
UserInputService.InputBegan:connect(function(inputObject)
local inputType = inputObject.UserInputType
if inputType ~= Enum.UserInputType.Focus and inputType ~= Enum.UserInputType.None then
lastInputType = inputObject.UserInputType
showTypeOfHelp()
end
end)
utility:OnResized(this, function(newSize, isPortrait)
if this.HelpPages[TOUCH_TAG] then
adjustForScreenLayout(newSize)
end
end)
return this
end
----------- Public Facing API Additions --------------
do
PageInstance = Initialize()
PageInstance.Displayed.Event:connect(function()
local isPortrait = utility:IsPortrait()
if PageInstance:GetCurrentInputType() == TOUCH_TAG then
if PageInstance.HubRef.BottomButtonFrame and not utility:IsSmallTouchScreen() and not isPortrait then
PageInstance.HubRef.BottomButtonFrame.Visible = false
end
end
if PageInstance.HubRef.VersionContainer then
PageInstance.HubRef.VersionContainer.Visible = true
end
end)
PageInstance.Hidden.Event:connect(function()
PageInstance.HubRef.PageViewClipper.ClipsDescendants = true
PageInstance.HubRef.PageView.ClipsDescendants = true
PageInstance.HubRef:ShowShield()
local isPortrait = utility:IsPortrait()
if PageInstance:GetCurrentInputType() == TOUCH_TAG then
if PageInstance.HubRef.BottomButtonFrame and not utility:IsSmallTouchScreen() and not isPortrait then
PageInstance.HubRef.BottomButtonFrame.Visible = true
end
end
if PageInstance.HubRef.VersionContainer then
PageInstance.HubRef.VersionContainer.Visible = false
end
end)
end
return PageInstance
@@ -0,0 +1,90 @@
--[[
Filename: Home.lua
Written by: jeditkacheff
Version 1.0
Description: Takes care of the home page in Settings Menu
--]]
local BUTTON_OFFSET = 20
local BUTTON_SPACING = 10
-------------- SERVICES --------------
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local GuiService = game:GetService("GuiService")
----------- UTILITIES --------------
local utility = require(RobloxGui.Modules.Settings.Utility)
------------ Variables -------------------
local PageInstance = nil
local success, result = pcall(function() return settings():GetFFlag('UseNotificationsLocalization') end)
local FFlagUseNotificationsLocalization = success and result
----------- CLASS DECLARATION --------------
local function Initialize()
local settingsPageFactory = require(RobloxGui.Modules.Settings.SettingsPageFactory)
local this = settingsPageFactory:CreateNewPage()
------ TAB CUSTOMIZATION -------
this.TabHeader.Name = "HomeTab"
this.TabHeader.Icon.Image = "rbxasset://textures/ui/Settings/MenuBarIcons/HomeTab.png"
this.TabHeader.Icon.Size = UDim2.new(0,32,0,30)
this.TabHeader.Icon.Position = UDim2.new(0,5,0.5,-15)
if FFlagUseNotificationsLocalization then
this.TabHeader.Title.Text = "Home"
else
this.TabHeader.Icon.Title.Text = "Home"
end
this.TabHeader.Size = UDim2.new(0,100,1,0)
------ PAGE CUSTOMIZATION -------
this.Page.Name = "Home"
local resumeGameFunc = function()
this.HubRef:SetVisibility(false)
end
this.ResumeButton = utility:MakeStyledButton("ResumeButton", "Resume Game", UDim2.new(0, 200, 0, 50), resumeGameFunc)
this.ResumeButton.Position = UDim2.new(0.5,-100,0,BUTTON_OFFSET)
this.ResumeButton.Parent = this.Page
local resetFunc = function()
this.HubRef:SwitchToPage(this.HubRef.ResetCharacterPage, false, 1)
end
local resetButton = utility:MakeStyledButton("ResetButton", "Reset Character", UDim2.new(0, 200, 0, 50), resetFunc)
resetButton.Position = UDim2.new(0.5,-100,0,this.ResumeButton.AbsolutePosition.Y + this.ResumeButton.AbsoluteSize.Y + BUTTON_SPACING)
resetButton.Parent = this.Page
local leaveGameFunc = function()
this.HubRef:SwitchToPage(this.HubRef.LeaveGamePage, false, 1)
end
local leaveButton = utility:MakeStyledButton("LeaveButton", "Leave Game", UDim2.new(0, 200, 0, 50), leaveGameFunc)
leaveButton.Position = UDim2.new(0.5,-100,0,resetButton.AbsolutePosition.Y + resetButton.AbsoluteSize.Y + BUTTON_SPACING)
leaveButton.Parent = this.Page
this.Page.Size = UDim2.new(1,0,0,leaveButton.AbsolutePosition.Y + leaveButton.AbsoluteSize.Y)
return this
end
----------- Public Facing API Additions --------------
do
PageInstance = Initialize()
PageInstance.Displayed.Event:connect(function()
if not utility:UsesSelectedObject() then return end
GuiService.SelectedCoreObject = PageInstance.ResumeButton
end)
end
return PageInstance
@@ -0,0 +1,142 @@
--[[
Filename: LeaveGame.lua
Written by: jeditkacheff
Version 1.0
Description: Takes care of the leave game in Settings Menu
--]]
-------------- CONSTANTS -------------
local LEAVE_GAME_ACTION = "LeaveGameCancelAction"
local LEAVE_GAME_FRAME_WAITS = 2
-------------- SERVICES --------------
local CoreGui = game:GetService("CoreGui")
local ContextActionService = game:GetService("ContextActionService")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local GuiService = game:GetService("GuiService")
local RunService = game:GetService("RunService")
----------- UTILITIES --------------
local utility = require(RobloxGui.Modules.Settings.Utility)
------------ Variables -------------------
local PageInstance = nil
RobloxGui:WaitForChild("Modules"):WaitForChild("TenFootInterface")
local isTenFootInterface = require(RobloxGui.Modules.TenFootInterface):IsEnabled()
----------- CLASS DECLARATION --------------
local function Initialize()
local settingsPageFactory = require(RobloxGui.Modules.Settings.SettingsPageFactory)
local this = settingsPageFactory:CreateNewPage()
this.LeaveFunc = function()
GuiService.SelectedCoreObject = nil -- deselects the button and prevents spamming the popup to save in studio when using gamepad
-- need to wait for render frames so on slower devices the leave button highlight will update
-- otherwise, since on slow devices it takes so long to leave you are left wondering if you pressed the button
for i = 1, LEAVE_GAME_FRAME_WAITS do
RunService.RenderStepped:wait()
end
game:Shutdown()
end
this.DontLeaveFunc = function(isUsingGamepad)
if this.HubRef then
this.HubRef:PopMenu(isUsingGamepad, true)
end
end
this.DontLeaveFromHotkey = function(name, state, input)
if state == Enum.UserInputState.Begin then
local isUsingGamepad = input.UserInputType == Enum.UserInputType.Gamepad1 or input.UserInputType == Enum.UserInputType.Gamepad2
or input.UserInputType == Enum.UserInputType.Gamepad3 or input.UserInputType == Enum.UserInputType.Gamepad4
this.DontLeaveFunc(isUsingGamepad)
end
end
this.DontLeaveFromButton = function(isUsingGamepad)
this.DontLeaveFunc(isUsingGamepad)
end
------ TAB CUSTOMIZATION -------
this.TabHeader = nil -- no tab for this page
------ PAGE CUSTOMIZATION -------
this.Page.Name = "LeaveGamePage"
this.ShouldShowBottomBar = false
this.ShouldShowHubBar = false
local leaveGameText = utility:Create'TextLabel'
{
Name = "LeaveGameText",
Text = "Are you sure you want to leave the game?",
Font = Enum.Font.SourceSansBold,
FontSize = Enum.FontSize.Size36,
TextColor3 = Color3.new(1,1,1),
BackgroundTransparency = 1,
Size = UDim2.new(1,0,0,200),
TextWrapped = true,
ZIndex = 2,
Parent = this.Page,
Position = isTenFootInterface and UDim2.new(0,0,0,100) or UDim2.new(0,0,0,0)
};
local leaveButtonContainer = utility:Create"Frame"
{
Name = "LeaveButtonContainer",
Parent = leaveGameText,
Size = UDim2.new(1,0,0,400),
BackgroundTransparency = 1,
Position = UDim2.new(0,0,1,0)
};
local leaveButtonLayout = utility:Create'UIGridLayout'
{
Name = "LeavetButtonsLayout",
CellSize = isTenFootInterface and UDim2.new(0, 300, 0, 80) or UDim2.new(0, 200, 0, 50),
CellPadding = UDim2.new(0,20,0,20),
FillDirection = Enum.FillDirection.Horizontal,
HorizontalAlignment = Enum.HorizontalAlignment.Center,
SortOrder = Enum.SortOrder.LayoutOrder,
VerticalAlignment = Enum.VerticalAlignment.Top,
Parent = leaveButtonContainer
};
if utility:IsSmallTouchScreen() then
leaveGameText.FontSize = Enum.FontSize.Size24
leaveGameText.Size = UDim2.new(1,0,0,100)
elseif isTenFootInterface then
leaveGameText.FontSize = Enum.FontSize.Size48
end
this.LeaveGameButton = utility:MakeStyledButton("LeaveGame", "Leave", nil, this.LeaveFunc)
this.LeaveGameButton.NextSelectionRight = nil
this.LeaveGameButton.Parent = leaveButtonContainer
------------- Init ----------------------------------
local dontleaveGameButton = utility:MakeStyledButton("DontLeaveGame", "Don't Leave", nil, this.DontLeaveFromButton)
dontleaveGameButton.NextSelectionLeft = nil
dontleaveGameButton.Parent = leaveButtonContainer
this.Page.Size = UDim2.new(1,0,0,dontleaveGameButton.AbsolutePosition.Y + dontleaveGameButton.AbsoluteSize.Y)
return this
end
----------- Public Facing API Additions --------------
PageInstance = Initialize()
PageInstance.Displayed.Event:connect(function()
GuiService.SelectedCoreObject = PageInstance.LeaveGameButton
ContextActionService:BindCoreAction(LEAVE_GAME_ACTION, PageInstance.DontLeaveFromHotkey, false, Enum.KeyCode.ButtonB)
end)
PageInstance.Hidden.Event:connect(function()
ContextActionService:UnbindCoreAction(LEAVE_GAME_ACTION)
end)
return PageInstance
@@ -0,0 +1,23 @@
local Constants = {
Color = {
BLUE = Color3.fromRGB(0, 162, 255),
GREY = Color3.fromRGB(155, 155, 155),
WHITE = Color3.fromRGB(255, 255, 255),
DARK = Color3.fromRGB(25, 25, 25),
},
TextSize = {
BUTTON = 36
},
Image = {
BUTTON = "rbxasset://textures/ui/Settings/LeaveGame/Button_1080.png",
BUTTON_SELECTOR = "rbxasset://textures/ui/Settings/LeaveGame/gr-item selector-8px corner.png",
PLAY_NEXT_SELECTOR = "rbxasset://textures/ui/Settings/LeaveGame/selectorWithIcon.png",
DOWN_ARROW = "rbxasset://textures/ui/Settings/LeaveGame/artAssets_DownArrow.png",
THUMB_ICON = "rbxasset://textures/ui/Settings/LeaveGame/thumb_strokeStyle.png",
PLAYER_NUMBER_ICON = "rbxasset://textures/ui/Settings/LeaveGame/playernumber_strokeStyle.png",
},
}
return Constants
@@ -0,0 +1,230 @@
--[[
A Roact play next game screen.
Props:
screenId : Variant
zIndex : number
ref : Roact.Ref
focused : bool
closeScreen : function()
numOfGames : number
]]
local GuiService = game:GetService("GuiService")
local CorePackages = game:GetService("CorePackages")
local HttpService = game:GetService("HttpService")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Otter = require(CorePackages.Otter)
local Settings = script.Parent.Parent.Parent
local Modules = Settings.Parent
local Common = Modules.Common
local Analytics = require(Common.Analytics)
local Constants = require(Settings.Pages.LeaveGameScreen.Constants)
local ResumeButton = require(Settings.Components.ResumeButton)
local LeaveButton = require(Settings.Components.LeaveButton)
local Utility = require(Settings.Utility)
local RobloxTranslator = require(Modules.RobloxTranslator)
--NOTE: This play next game view can be swaped different version such as one for touch and PC
local PlayNextGameView = require(Settings.Components.PlayNextGame.PlayNextGameXbox)
local ApiFetchRecentlyPlayedGames = require(Settings.Thunks.ApiFetchRecentlyPlayedGames)
local LEAVE_BUTTON_KEY = "LeaveButton"
local DONT_LEAVE_BUTTON_KEY = "DontLeaveButton"
local NEXT_GAME_LIST = "NextGameList"
local LEAVE_GAME_LABEL_KEY = "Feature.SettingsHub.Label.LeaveGame"
local LeaveGame = Roact.PureComponent:extend("LeaveGame")
local LEAVE_GAME_ALERT_SHOW_Y = 267
local LEAVE_GAME_ALERT_HIDE_Y = 183
-- NOTE: event context might need to be changed for other platforms.
local EVENT_CONTEXT = "XboxOne"
function LeaveGame:init()
self.screenId = self.props.screenId or HttpService:GenerateGUID(false)
self.ref = Roact.createRef()
self.analytics = Analytics.new()
self.motorOptions = {
dampingRatio = 1,
frequency = 5,
}
self.leaveButtonRef = Roact.createRef()
self.resume = function()
self.props.closeScreen()
end
self.enterNextGameList = function()
if #self.props.gamesList == 0 then
self:_selectButton()
return
end
local eventName = "EnterNextGameList"
self.analytics.EventStream:setRBXEventStream(EVENT_CONTEXT, eventName)
self:setState({
enterGameList = true
})
end
self.leaveNextGameList = function()
self:setState({
enterGameList = false
})
end
self.state = {
enterGameList = false,
}
end
function LeaveGame:willUpdate(nextProps, nextState)
assert(self.props.screenId == nextProps.screenId)
end
function LeaveGame:render()
local zIndex = self.props.zIndex or 1
local leaveGameAlert = Roact.createElement("Frame", {
Size = UDim2.new(0, 672, 0, 167),
Position = UDim2.new(0.5, 0, 0, LEAVE_GAME_ALERT_SHOW_Y),
AnchorPoint = Vector2.new(0.5, 0),
BackgroundTransparency = 1,
[Roact.Ref] = self.ref,
},{
LeaveGameText = Roact.createElement("TextLabel", {
Size = UDim2.new(1, 0, 0, 45),
Position = UDim2.new(0.5, 0, 0, 0),
AnchorPoint = Vector2.new(0.5, 0),
Text = RobloxTranslator:FormatByKey(LEAVE_GAME_LABEL_KEY),
Font = Enum.Font.SourceSans,
FontSize = Enum.FontSize.Size36,
TextColor3 = Constants.Color.WHITE,
BackgroundTransparency = 1,
TextWrapped = true,
ZIndex = zIndex + 1,
}),
[LEAVE_BUTTON_KEY] = Roact.createElement(LeaveButton, {
position = UDim2.new(0, 0, 1, 0),
anchorPoint = Vector2.new(0, 1),
objRef = self.leaveButtonRef,
zIndex = zIndex,
}),
[DONT_LEAVE_BUTTON_KEY] = Roact.createElement(ResumeButton, {
position = UDim2.new(1, 0, 1, 0),
anchorPoint = Vector2.new(1, 1),
onResume = self.resume,
zIndex = zIndex,
}),
Redirect = Roact.createElement("Frame", {
Size = UDim2.new(1.5, 0, 0, 1),
Position = UDim2.new(0.5, 0, 1, 100),
AnchorPoint = Vector2.new(0.5, 0),
Selectable = true,
BackgroundTransparency = 1,
[Roact.Event.SelectionGained] = function()
GuiService.SelectedCoreObject = nil
self.enterNextGameList()
end
}),
})
local playNextGameView
if #self.props.gamesList ~= 0 then
playNextGameView = Roact.createElement(PlayNextGameView, {
screenId = NEXT_GAME_LIST,
focused = self.state.enterGameList,
onLeaveNextGameList = self.leaveNextGameList,
gamesData = self.props.gamesList,
zIndex = zIndex,
})
end
return Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 1, 0),
BackgroundColor3 = Color3.fromRGB(25,25,25),
BackgroundTransparency = 0.7,
BorderSizePixel = 0,
ZIndex = zIndex,
},{
LeaveGameAlert = leaveGameAlert,
PlayNextGameView = playNextGameView,
})
end
function LeaveGame:_selectButton()
if self.props.focused then
local buttonRef = self.leaveButtonRef
local buttonObj = buttonRef and buttonRef.current or nil
if buttonObj ~= nil then
GuiService.SelectedCoreObject = buttonObj
end
end
end
function LeaveGame:didMount()
if #self.props.gamesList ~= self.props.numOfGames then
self.props.GetRecentlyPlayedGames(self.props.numOfGames, true)
end
delay(0, function()
if self.props.focused and self.ref.current then
GuiService:AddSelectionParent(self.screenId, self.ref.current)
end
self:_selectButton()
end)
self.motor = Otter.createSingleMotor(0)
self.motor:onStep(function(value)
local t = value/100
if self.ref.current ~= nil then
self.ref.current.Position = UDim2.new(0.5, 0, 0, Utility:Round(Utility:Lerp(t, LEAVE_GAME_ALERT_SHOW_Y, LEAVE_GAME_ALERT_HIDE_Y)))
end
end)
self.motor:start()
local eventName = "OpenedLeaveGameScreen"
self.analytics.EventStream:setRBXEventStream(EVENT_CONTEXT, eventName)
end
function LeaveGame:didUpdate(previousProps, previousState)
local targetPercentage = self.state.enterGameList and 100 or 0
self.motor:setGoal(Otter.spring(targetPercentage, self.motorOptions))
if self.state.enterGameList == true and
self.props.focused == previousProps.focused then
return
end
GuiService:RemoveSelectionGroup(self.screenId)
if self.props.focused and self.ref.current then
GuiService:AddSelectionParent(self.screenId, self.ref.current)
end
self:_selectButton()
end
function LeaveGame:willUnmount()
GuiService:RemoveSelectionGroup(self.screenId)
self.motor:destroy()
end
local function mapStateToProps(state, props)
local currentUniverseId = game.GameId
if state.RecentlyPlayedGamesFetchingStatus.fetching ~= false then
return {
gamesList = {}
}
end
local recentlyPlayedGamesState = state.RecentlyPlayedGames
local gameSort = recentlyPlayedGamesState.gameSort or {}
--Fliter our the current game if it is in the sort
local filteredSort = {}
for i in ipairs(gameSort) do
if gameSort[i].universeId ~= currentUniverseId then
table.insert(filteredSort, gameSort[i])
end
end
return {
gamesList = filteredSort,
}
end
local function mapDispatchToProps(dispatch)
return
{
GetRecentlyPlayedGames = function(numOfGames, forceUpdate)
return dispatch(ApiFetchRecentlyPlayedGames(numOfGames, forceUpdate))
end,
}
end
return RoactRodux.UNSTABLE_connect2(mapStateToProps, mapDispatchToProps)(LeaveGame)
@@ -0,0 +1,81 @@
local CorePackages = game:GetService("CorePackages")
local CoreGui = game:GetService("CoreGui")
local ContextActionService = game:GetService("ContextActionService")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Settings = script.Parent.Parent
local SettingsState = require(Settings.SettingsState)
local SettingsPageFactory = require(Settings.SettingsPageFactory)
local ApiFetchRecentlyPlayedGames = require(Settings.Thunks.ApiFetchRecentlyPlayedGames)
local LeaveGameScreen = require(Settings.Pages.LeaveGameScreen.LeaveGame)
-------------- CONSTANTS -------------
local LEAVE_GAME_ACTION = "LeaveGameCancelAction"
-- NOTE: The current game will be filtered out.
local NUM_OF_GAMES = 7
local PageInstance
local function Initialize()
local this = SettingsPageFactory:CreateNewPage()
------ TAB CUSTOMIZATION -------
this.TabHeader = nil -- no tab for this page
------ TAB CUSTOMIZATION -------
this.TabHeader = nil -- no tab for this page
------ PAGE CUSTOMIZATION -------
this.Page.Name = "LeaveGamePage"
this.ShouldShowBottomBar = false
this.ShouldShowHubBar = false
this.IsPageClipped = false
SettingsState.store:dispatch(ApiFetchRecentlyPlayedGames(NUM_OF_GAMES))
this.unMountFunction = function(isUsingGamepad)
if this.HubRef then
this.HubRef:PopMenu(isUsingGamepad, true)
end
end
this.unMountFromHotkey = function(name, state, input)
if state == Enum.UserInputState.Begin then
local isUsingGamepad = input.UserInputType == Enum.UserInputType.Gamepad1 or input.UserInputType == Enum.UserInputType.Gamepad2
or input.UserInputType == Enum.UserInputType.Gamepad3 or input.UserInputType == Enum.UserInputType.Gamepad4
this.unMountFunction(isUsingGamepad)
end
end
this.RoactScreen = Roact.createElement(RoactRodux.StoreProvider,
{
store = SettingsState.store,
},{
Screen = Roact.createElement("ScreenGui", {}, {
LeaveGameScreen = Roact.createElement(LeaveGameScreen, {
focused = true,
closeScreen = this.unMountFunction,
zIndex = 2,
numOfGames = NUM_OF_GAMES,
}),
})
})
return this
end
----------- Public Facing API Additions --------------
PageInstance = Initialize()
PageInstance.Displayed.Event:connect(function()
PageInstance.handle = Roact.mount(PageInstance.RoactScreen, CoreGui)
ContextActionService:BindCoreAction(LEAVE_GAME_ACTION, PageInstance.unMountFromHotkey, false, Enum.KeyCode.ButtonB)
end)
PageInstance.Hidden.Event:connect(function()
ContextActionService:UnbindCoreAction(LEAVE_GAME_ACTION)
if PageInstance.handle ~= nil then
Roact.unmount(PageInstance.handle)
PageInstance.handle = nil
end
end)
return PageInstance
@@ -0,0 +1,675 @@
--[[
Filename: Players.lua
Written by: Stickmasterluke
Version 1.0
Description: Player list inside escape menu, with friend adding functionality.
--]]
-------------- SERVICES --------------
local CoreGui = game:GetService("CoreGui")
local CorePackages = game:GetService("CorePackages")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local GuiService = game:GetService("GuiService")
local PlayersService = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local AnalyticsService = game:GetService("AnalyticsService")
local RunService = game:GetService("RunService")
----------- UTILITIES --------------
RobloxGui:WaitForChild("Modules"):WaitForChild("TenFootInterface")
local utility = require(RobloxGui.Modules.Settings.Utility)
local reportAbuseMenu = require(RobloxGui.Modules.Settings.Pages.ReportAbuseMenu)
local SocialUtil = require(RobloxGui.Modules:WaitForChild("SocialUtil"))
local EventStream = require(CorePackages.AppTempCommon.Temp.EventStream)
local FlagSettings = require(CoreGui.RobloxGui.Modules.Settings.Pages.ShareGame.FlagSettings)
local ShareGameIcons = require(CoreGui.RobloxGui.Modules.Settings.Pages.ShareGame.Spritesheets.ShareGameIcons)
local isTenFootInterface = require(RobloxGui.Modules.TenFootInterface):IsEnabled()
local RobloxTranslator = require(RobloxGui.Modules.RobloxTranslator)
------------ Constants -------------------
local FRAME_DEFAULT_TRANSPARENCY = .85
local FRAME_SELECTED_TRANSPARENCY = .65
local REPORT_PLAYER_IMAGE = isTenFootInterface and "rbxasset://textures/ui/Settings/Players/ReportFlagIcon@2x.png" or "rbxasset://textures/ui/Settings/Players/ReportFlagIcon.png"
local ADD_FRIEND_IMAGE = isTenFootInterface and "rbxasset://textures/ui/Settings/Players/AddFriendIcon@2x.png" or "rbxasset://textures/ui/Settings/Players/AddFriendIcon.png"
local FRIEND_IMAGE = isTenFootInterface and "rbxasset://textures/ui/Settings/Players/FriendIcon@2x.png" or "rbxasset://textures/ui/Settings/Players/FriendIcon.png"
local PLAYER_ROW_HEIGHT = 62
local PLAYER_ROW_SPACING = 80
------------ Variables -------------------
local platform = UserInputService:GetPlatform()
local PageInstance = nil
local localPlayer = PlayersService.LocalPlayer
while not localPlayer do
PlayersService.ChildAdded:wait()
localPlayer = PlayersService.LocalPlayer
end
------------ FAST FLAGS -------------------
local success, result = pcall(function() return settings():GetFFlag('UseNotificationsLocalization') end)
local FFlagUseNotificationsLocalization = success and result
local IsShareGamePageEnabledByPlatform = FlagSettings.IsShareGamePageEnabledByPlatform(platform)
local FFlagSettingsHubInviteToGameInStudio = settings():GetFFlag('SettingsHubInviteToGameInStudio4')
----------- CLASS DECLARATION --------------
local function Initialize()
local settingsPageFactory = require(RobloxGui.Modules.Settings.SettingsPageFactory)
local this = settingsPageFactory:CreateNewPage()
this.PageListLayout.Padding = UDim.new(0, PLAYER_ROW_SPACING - PLAYER_ROW_HEIGHT)
------ TAB CUSTOMIZATION -------
this.TabHeader.Name = "PlayersTab"
this.TabHeader.Icon.Image = isTenFootInterface and "rbxasset://textures/ui/Settings/MenuBarIcons/PlayersTabIcon@2x.png" or "rbxasset://textures/ui/Settings/MenuBarIcons/PlayersTabIcon.png"
if FFlagUseNotificationsLocalization then
this.TabHeader.Title.Text = "Players"
else
this.TabHeader.Icon.Title.Text = "Players"
end
----- FRIENDSHIP FUNCTIONS ------
local function getFriendStatus(selectedPlayer)
local success, result = pcall(function()
-- NOTE: Core script only
return localPlayer:GetFriendStatus(selectedPlayer)
end)
if success then
return result
else
return Enum.FriendStatus.NotFriend
end
end
------ PAGE CUSTOMIZATION -------
this.Page.Name = "Players"
local function showRightSideButtons(player)
return player and player ~= localPlayer and player.UserId > 0 and localPlayer.UserId > 0
end
local function createFriendStatusTextLabel(status, player)
if status == nil then
return nil
end
local fakeSelection = Instance.new("Frame")
fakeSelection.BackgroundTransparency = 1
local friendLabel = nil
local friendLabelText = nil
if status == Enum.FriendStatus.Friend or status == Enum.FriendStatus.FriendRequestSent then
friendLabel = Instance.new("TextButton")
friendLabel.BackgroundTransparency = 1
friendLabel.FontSize = Enum.FontSize.Size24
friendLabel.Font = Enum.Font.SourceSans
friendLabel.TextColor3 = Color3.new(1,1,1)
friendLabel.SelectionImageObject = fakeSelection
if status == Enum.FriendStatus.Friend then
friendLabel.Text = "Friend"
else
friendLabel.Text = "Request Sent"
end
elseif status == Enum.FriendStatus.Unknown or status == Enum.FriendStatus.NotFriend or status == Enum.FriendStatus.FriendRequestReceived then
local addFriendFunc = function()
if friendLabel and friendLabelText and friendLabelText.Text ~= "" then
friendLabel.ImageTransparency = 1
friendLabelText.Text = ""
if localPlayer and player then
AnalyticsService:ReportCounter("PlayersMenu-RequestFriendship")
AnalyticsService:TrackEvent("Game", "RequestFriendship", "PlayersMenu")
localPlayer:RequestFriendship(player)
end
end
end
friendLabel, friendLabelText = utility:MakeStyledButton("FriendStatus", "Add Friend", UDim2.new(0, 182, 0, 46), addFriendFunc)
friendLabelText.ZIndex = 3
friendLabelText.Position = friendLabelText.Position + UDim2.new(0,0,0,1)
end
if friendLabel then
friendLabel.Name = "FriendStatus"
friendLabel.Size = UDim2.new(0,182,0,46)
friendLabel.Position = UDim2.new(1,-198,0,7)
friendLabel.ZIndex = 3
end
return friendLabel
end
local function createFriendStatusImageLabel(status, player)
if status == Enum.FriendStatus.Friend or status == Enum.FriendStatus.FriendRequestSent then
local friendLabel = Instance.new("ImageButton")
friendLabel.Name = "FriendStatus"
friendLabel.Size = UDim2.new(0, 46, 0, 46)
friendLabel.Image = "rbxasset://textures/ui/Settings/MenuBarAssets/MenuButton.png"
friendLabel.ScaleType = Enum.ScaleType.Slice
friendLabel.SliceCenter = Rect.new(8,6,46,44)
friendLabel.AutoButtonColor = false
friendLabel.BackgroundTransparency = 1
friendLabel.ZIndex = 2
local friendImage = Instance.new("ImageLabel")
friendImage.BackgroundTransparency = 1
friendImage.Position = UDim2.new(0.5, 0, 0.5, 0)
friendImage.Size = UDim2.new(0, 28, 0, 28)
friendImage.AnchorPoint = Vector2.new(0.5, 0.5)
friendImage.ZIndex = 3
friendImage.Image = FRIEND_IMAGE
if status == Enum.FriendStatus.Friend then
friendImage.ImageTransparency = 0
else
friendImage.Image = ADD_FRIEND_IMAGE
friendImage.ImageTransparency = 0.5
end
friendImage.Parent = friendLabel
return friendLabel
elseif status == Enum.FriendStatus.Unknown or status == Enum.FriendStatus.NotFriend or status == Enum.FriendStatus.FriendRequestReceived then
local addFriendButton, addFriendImage = nil
local addFriendFunc = function()
if addFriendButton and addFriendImage and addFriendButton.ImageTransparency ~= 1 then
addFriendButton.ImageTransparency = 1
addFriendImage.ImageTransparency = 1
if localPlayer and player then
AnalyticsService:ReportCounter("PlayersMenu-RequestFriendship")
AnalyticsService:TrackEvent("Game", "RequestFriendship", "PlayersMenu")
localPlayer:RequestFriendship(player)
end
end
end
addFriendButton, addFriendImage = utility:MakeStyledImageButton("FriendStatus", ADD_FRIEND_IMAGE,
UDim2.new(0, 46, 0, 46), UDim2.new(0, 28, 0, 28), addFriendFunc)
addFriendButton.Name = "FriendStatus"
addFriendButton.Selectable = true
return addFriendButton
end
return nil
end
--- Ideally we want to select the first add friend button, but select the first report button instead if none are available.
local reportSelectionFound = nil
local friendSelectionFound = nil
local function friendStatusCreate(playerLabel, player)
local friendLabelParent = nil
if playerLabel then
friendLabelParent = playerLabel:FindFirstChild("RightSideButtons")
end
if friendLabelParent then
-- remove any previous friend status labels
for _, item in pairs(friendLabelParent:GetChildren()) do
if item and item.Name == "FriendStatus" then
if GuiService.SelectedCoreObject == item then
friendSelectionFound = nil
GuiService.SelectedCoreObject = nil
end
item:Destroy()
end
end
-- create new friend status label
local status = nil
if showRightSideButtons(player) then
status = getFriendStatus(player)
end
local friendLabel = nil
local wasIsPortrait = nil
utility:OnResized(playerLabel, function(newSize, isPortrait)
if friendLabel and isPortrait == wasIsPortrait then
return
end
wasIsPortrait = isPortrait
if friendLabel then
friendLabel:Destroy()
end
if isPortrait then
friendLabel = createFriendStatusImageLabel(status, player)
else
friendLabel = createFriendStatusTextLabel(status, player)
end
if friendLabel then
friendLabel.Name = "FriendStatus"
friendLabel.LayoutOrder = 2
friendLabel.Selectable = true
friendLabel.Parent = friendLabelParent
if UserInputService.GamepadEnabled and not friendSelectionFound then
friendSelectionFound = true
GuiService.SelectedCoreObject = friendLabel
end
end
end)
end
end
localPlayer.FriendStatusChanged:connect(function(player, friendStatus)
if player then
local playerLabel = this.Page:FindFirstChild("PlayerLabel"..player.Name)
if playerLabel then
friendStatusCreate(playerLabel, player)
end
end
end)
local buttonsContainer = utility:Create("Frame") {
Name = "ButtonsContainer",
Size = UDim2.new(1, 0, 0, 62),
BackgroundTransparency = 1,
Parent = this.Page,
Visible = false
}
local leaveGameFunc = function()
this.HubRef:SwitchToPage(this.HubRef.LeaveGamePage, false, 1)
end
local leaveButton, leaveLabel = utility:MakeStyledButton("LeaveButton", "Leave Game", UDim2.new(1 / 3, -5, 1, 0), leaveGameFunc)
leaveButton.AnchorPoint = Vector2.new(0, 0)
leaveButton.Position = UDim2.new(0, 0, 0, 0)
leaveLabel.Size = UDim2.new(1, 0, 1, -6)
leaveButton.Parent = buttonsContainer
local resetFunc = function()
this.HubRef:SwitchToPage(this.HubRef.ResetCharacterPage, false, 1)
end
local resetButton, resetLabel = utility:MakeStyledButton("ResetButton", "Reset Character", UDim2.new(1 / 3, -5, 1, 0), resetFunc)
resetButton.AnchorPoint = Vector2.new(0.5, 0)
resetButton.Position = UDim2.new(0.5, 0, 0, 0)
resetLabel.Size = UDim2.new(1, 0, 1, -6)
resetButton.Parent = buttonsContainer
local resumeGameFunc = function()
this.HubRef:SetVisibility(false)
end
local resumeButton, resumeLabel = utility:MakeStyledButton("ResumeButton", "Resume Game", UDim2.new(1 / 3, -5, 1, 0), resumeGameFunc)
resumeButton.AnchorPoint = Vector2.new(1, 0)
resumeButton.Position = UDim2.new(1, 0, 0, 0)
resumeLabel.Size = UDim2.new(1, 0, 1, -6)
resumeButton.Parent = buttonsContainer
utility:OnResized(buttonsContainer, function(newSize, isPortrait)
if isPortrait or utility:IsSmallTouchScreen() then
local buttonsFontSize = isPortrait and 18 or 24
buttonsContainer.Visible = true
buttonsContainer.Size = UDim2.new(1, 0, 0, isPortrait and 50 or 62)
resetLabel.TextSize = buttonsFontSize
leaveLabel.TextSize = buttonsFontSize
resumeLabel.TextSize = buttonsFontSize
else
buttonsContainer.Visible = false
buttonsContainer.Size = UDim2.new(1, 0, 0, 0)
end
end)
if FFlagUseNotificationsLocalization then
local function ApplyLocalizeTextSettingsToLabel(label)
label.AnchorPoint = Vector2.new(0.5,0.5)
label.Position = UDim2.new(0.5, 0, 0.5, -3)
label.Size = UDim2.new(0.75, 0, 0.5, 0)
end
ApplyLocalizeTextSettingsToLabel(leaveLabel)
ApplyLocalizeTextSettingsToLabel(resetLabel)
ApplyLocalizeTextSettingsToLabel(resetLabel)
end
local function reportAbuseButtonCreate(playerLabel, player)
local rightSideButtons = playerLabel:FindFirstChild("RightSideButtons")
if rightSideButtons then
local oldReportButton = rightSideButtons:FindFirstChild("ReportPlayer")
if oldReportButton then
if oldReportButton == GuiService.SelectedCoreObject then
reportSelectionFound = nil
end
oldReportButton:Destroy()
end
if showRightSideButtons(player) then
local reportPlayerFunction = function()
reportAbuseMenu:ReportPlayer(player)
end
local reportButton = utility:MakeStyledImageButton("ReportPlayer", REPORT_PLAYER_IMAGE,
UDim2.new(0, 46, 0, 46), UDim2.new(0, 28, 0, 28), reportPlayerFunction)
reportButton.Name = "ReportPlayer"
reportButton.Position = UDim2.new(1, -260, 0, 7)
reportButton.LayoutOrder = 1
reportButton.Selectable = true
reportButton.Parent = rightSideButtons
if not reportSelectionFound and not friendSelectionFound and UserInputService.GamepadEnabled then
reportSelectionFound = true
GuiService.SelectedCoreObject = reportButton
end
end
end
end
local createShareGameButton = nil
local createPlayerRow = nil
local function createRow(frameClassName)
local frame = Instance.new(frameClassName)
frame.Image = "rbxasset://textures/ui/dialog_white.png"
frame.ScaleType = "Slice"
frame.SliceCenter = Rect.new(10, 10, 10, 10)
frame.Size = UDim2.new(1, 0, 0, PLAYER_ROW_HEIGHT)
frame.Position = UDim2.new(0, 0, 0, 0)
frame.BackgroundTransparency = 1
frame.ZIndex = 2
frame.ImageTransparency = FRAME_DEFAULT_TRANSPARENCY
local icon = Instance.new("ImageLabel")
icon.Name = "Icon"
icon.BackgroundTransparency = 1
icon.Size = UDim2.new(0, 36, 0, 36)
icon.Position = UDim2.new(0, 12, 0, 12)
icon.ZIndex = 3
icon.Parent = frame
local textLabel = Instance.new("TextLabel")
textLabel.TextXAlignment = Enum.TextXAlignment.Left
textLabel.Font = Enum.Font.SourceSans
textLabel.FontSize = Enum.FontSize.Size24
textLabel.TextColor3 = Color3.new(1, 1, 1)
textLabel.BackgroundTransparency = 1
textLabel.Position = UDim2.new(0, 60, .5, 0)
textLabel.Size = UDim2.new(0, 0, 0, 0)
textLabel.ZIndex = 3
textLabel.Parent = frame
return frame
end
createShareGameButton = function()
local frame = createRow("ImageButton")
local textLabel = frame.TextLabel
local icon = frame.Icon
textLabel.Font = Enum.Font.SourceSansSemibold
textLabel.Text = RobloxTranslator:FormatByKey("Feature.SettingsHub.Action.InviteFriendsToPlay")
icon.Size = UDim2.new(0, 24, 0, 24)
icon.Position = UDim2.new(0, 18, 0, 18)
ShareGameIcons:ApplyImage(icon, "invite")
local function onHover(isHovering)
if isHovering then
frame.ImageTransparency = FRAME_SELECTED_TRANSPARENCY
else
frame.ImageTransparency = FRAME_DEFAULT_TRANSPARENCY
end
end
frame.InputBegan:Connect(function() onHover(true) end)
frame.InputEnded:Connect(function() onHover(false) end)
frame.Activated:Connect(function() onHover(false) end)
frame.TouchPan:Connect(function(_, totalTranslation)
local TAP_ACCURACY_THREASHOLD = 20
if math.abs(totalTranslation.Y) > TAP_ACCURACY_THREASHOLD then
onHover(false)
end
end)
return frame
end
createPlayerRow = function()
local frame = createRow("ImageLabel")
frame.TextLabel.Name = "NameLabel"
local rightSideButtons = Instance.new("Frame")
rightSideButtons.Name = "RightSideButtons"
rightSideButtons.BackgroundTransparency = 1
rightSideButtons.ZIndex = 2
rightSideButtons.Position = UDim2.new(0, 0, 0, 0)
rightSideButtons.Size = UDim2.new(1, -10, 1, 0)
rightSideButtons.Parent = frame
-- Selection Highlighting logic:
local updateHighlight = function(lostSelectionObject)
if frame then
if GuiService.SelectedCoreObject and GuiService.SelectedCoreObject ~= lostSelectionObject and GuiService.SelectedCoreObject.Parent == rightSideButtons then
frame.ImageTransparency = FRAME_SELECTED_TRANSPARENCY
else
frame.ImageTransparency = FRAME_DEFAULT_TRANSPARENCY
end
end
end
local fakeSelectionObject = nil
rightSideButtons.ChildAdded:connect(function(child)
if child:IsA("GuiObject") then
if fakeSelectionObject and child ~= fakeSelectionObject then
fakeSelectionObject:Destroy()
fakeSelectionObject = nil
end
child.SelectionGained:connect(function() updateHighlight(nil) end)
child.SelectionLost:connect(function() updateHighlight(child) end)
end
end)
fakeSelectionObject = Instance.new("Frame")
fakeSelectionObject.Selectable = true
fakeSelectionObject.Size = UDim2.new(0, 100, 0, 100)
fakeSelectionObject.BackgroundTransparency = 1
fakeSelectionObject.SelectionImageObject = fakeSelectionObject:Clone()
fakeSelectionObject.Parent = rightSideButtons
local rightSideListLayout = Instance.new("UIListLayout")
rightSideListLayout.Name = "RightSideListLayout"
rightSideListLayout.FillDirection = Enum.FillDirection.Horizontal
rightSideListLayout.HorizontalAlignment = Enum.HorizontalAlignment.Right
rightSideListLayout.VerticalAlignment = Enum.VerticalAlignment.Center
rightSideListLayout.SortOrder = Enum.SortOrder.LayoutOrder
rightSideListLayout.Padding = UDim.new(0, 20)
rightSideListLayout.Parent = rightSideButtons
pcall(function()
frame.NameLabel.Localize = false
end)
return frame
end
-- Manage cutting off a players name if it is too long when switching into portrait mode.
local function managePlayerNameCutoff(frame, player)
local wasIsPortrait = nil
local reportFlagAddedConnection = nil
local reportFlagChangedConnection = nil
local function reportFlagChanged(reportFlag, prop)
if prop == "AbsolutePosition" and wasIsPortrait then
local maxPlayerNameSize = reportFlag.AbsolutePosition.X - 20 - frame.NameLabel.AbsolutePosition.X
frame.NameLabel.Text = player.Name
local newNameLength = string.len(player.Name)
while frame.NameLabel.TextBounds.X > maxPlayerNameSize and newNameLength > 0 do
frame.NameLabel.Text = string.sub(player.Name, 1, newNameLength) .. "..."
newNameLength = newNameLength - 1
end
end
end
utility:OnResized(frame.NameLabel, function(newSize, isPortrait)
if wasIsPortrait ~= nil and wasIsPortrait == isPortrait then
return
end
wasIsPortrait = isPortrait
if isPortrait then
if reportFlagAddedConnection == nil then
reportFlagAddedConnection = frame.RightSideButtons.ChildAdded:connect(function(child)
if child.Name == "ReportPlayer" then
reportFlagChangedConnection = child.Changed:connect(function(prop) reportFlagChanged(child, prop) end)
reportFlagChanged(child, "AbsolutePosition")
end
end)
end
local reportFlag = frame.RightSideButtons:FindFirstChild("ReportPlayer")
if reportFlag then
reportFlagChangedConnection = reportFlag.Changed:connect(function(prop) reportFlagChanged(reportFlag, prop) end)
reportFlagChanged(reportFlag, "AbsolutePosition")
end
else
frame.NameLabel.Text = player.Name
end
end)
end
local function canShareCurrentGame()
return this.HubRef.ShareGamePage ~= nil and localPlayer.UserId > 0
end
local shareGameButton
local sortedPlayers
local existingPlayerLabels = {}
local livePlayers = {}
this.Displayed.Event:connect(function(switchedFromGamepadInput)
sortedPlayers = PlayersService:GetPlayers()
table.sort(sortedPlayers, function(item1,item2)
return item1.Name:lower() < item2.Name:lower()
end)
local extraOffset = 20
if utility:IsSmallTouchScreen() or utility:IsPortrait() then
extraOffset = 85
end
if IsShareGamePageEnabledByPlatform then
-- Create "invite friends" button if it doesn't exist yet
-- We shouldn't create this button if we're not in a live game
if canShareCurrentGame() and not shareGameButton
and (not RunService:IsStudio() or FFlagSettingsHubInviteToGameInStudio) then
local eventStream = EventStream.new()
shareGameButton = createShareGameButton()
shareGameButton.Activated:connect(function()
local eventContext = "inGame"
local eventName = "inputShareGameEntryPoint"
local additionalArgs = {
buttonName = "settingsHub",
}
eventStream:setRBXEventStream(eventContext, eventName, additionalArgs)
this.HubRef:AddToMenuStack(this.HubRef.Pages.CurrentPage)
this.HubRef:SwitchToPage(this.HubRef.ShareGamePage, nil, 1, true)
end)
-- Ensure the button is always at the top of the list
shareGameButton.LayoutOrder = 1
shareGameButton.Parent = this.Page
end
end
friendSelectionFound = nil
reportSelectionFound = nil
-- iterate through players to reuse or create labels for players
for index=1, #sortedPlayers do
local player = sortedPlayers[index]
local frame
frame = existingPlayerLabels[player.Name]
if player then
livePlayers[player.Name] = true
-- create label (frame) for this player index if one does not exist
if not frame or not frame.Parent then
frame = createPlayerRow((index - 1)*PLAYER_ROW_SPACING + extraOffset)
frame.Parent = this.Page
existingPlayerLabels[player.Name] = frame
end
frame.Name = "PlayerLabel" ..player.Name
-- Immediately assign the image to an image that isn't guaranteed to be generated
frame.Icon.Image = SocialUtil.GetFallbackPlayerImageUrl(math.max(1, player.UserId), Enum.ThumbnailSize.Size100x100, Enum.ThumbnailType.AvatarThumbnail)
-- Spawn a function to get the generated image
spawn(function()
local imageUrl = SocialUtil.GetPlayerImage(math.max(1, player.UserId), Enum.ThumbnailSize.Size100x100, Enum.ThumbnailType.AvatarThumbnail)
if frame and frame.Parent and frame.Parent == this.Page then
frame.Icon.Image = imageUrl
end
end)
frame.NameLabel.Text = player.Name
frame.ImageTransparency = FRAME_DEFAULT_TRANSPARENCY
if IsShareGamePageEnabledByPlatform then
-- extra index room for shareGameButton
frame.LayoutOrder = index + 1
else
frame.LayoutOrder = index
end
managePlayerNameCutoff(frame, player)
friendStatusCreate(frame, player)
if platform ~= Enum.Platform.XBoxOne and platform ~= Enum.Platform.PS4 then
reportAbuseButtonCreate(frame, player)
end
end
end
for playerName, frame in pairs(existingPlayerLabels) do
if not livePlayers[playerName] then
frame:Destroy()
existingPlayerLabels[playerName] = nil
end
end
utility:OnResized("MenuPlayerListExtraPageSize", function(newSize, isPortrait)
local extraOffset = 20
if utility:IsSmallTouchScreen() or utility:IsPortrait() then
extraOffset = 85
end
if IsShareGamePageEnabledByPlatform then
local inviteToGameRow = 1
local playerListRowsCount = #sortedPlayers + inviteToGameRow
this.Page.Size = UDim2.new(1,0,0, extraOffset + PLAYER_ROW_SPACING * playerListRowsCount - 5)
else
this.Page.Size = UDim2.new(1,0,0, extraOffset + PLAYER_ROW_SPACING * #sortedPlayers - 5)
end
end)
end)
PlayersService.PlayerRemoving:Connect(function (player)
livePlayers[player.Name] = nil
local playerLabel = existingPlayerLabels[player.Name]
if not playerLabel then
return
end
local buttons = playerLabel:FindFirstChild("RightSideButtons")
if not buttons then
return
end
local friendStatus = buttons:FindFirstChild("FriendStatus")
if friendStatus then
if GuiService.SelectedCoreObject == friendStatus then
friendSelectionFound = nil
GuiService.SelectedCoreObject = nil
end
friendStatus:Destroy()
end
local reportPlayer = buttons:FindFirstChild("ReportPlayer")
if reportPlayer then
if GuiService.SelectedCoreObject == reportPlayer then
reportSelectionFound = nil
GuiService.SelectedCoreObject = nil
end
reportPlayer:Destroy()
end
end)
return this
end
----------- Public Facing API Additions --------------
PageInstance = Initialize()
return PageInstance
@@ -0,0 +1,174 @@
--[[r
Filename: Record.lua
Written by: jeditkacheff
Version 1.0
Description: Takes care of the Record Tab in Settings Menu
--]]
-------------- SERVICES --------------
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local GuiService = game:GetService("GuiService")
local TextService = game:GetService("TextService")
local VRService = game:GetService("VRService")
local Settings = UserSettings()
local GameSettings = Settings.GameSettings
----------- UTILITIES --------------
RobloxGui:WaitForChild("Modules"):WaitForChild("TenFootInterface")
local utility = require(RobloxGui.Modules.Settings.Utility)
local isTenFootInterface = require(RobloxGui.Modules.TenFootInterface):IsEnabled()
------------ Variables -------------------
local PageInstance = nil
local success, result = pcall(function() return settings():GetFFlag('UseNotificationsLocalization') end)
local FFlagUseNotificationsLocalization = success and result
local FFlagKillGuiButtonSetVerb = settings():GetFFlag("KillGuiButtonSetVerb")
----------- CLASS DECLARATION --------------
local function Initialize()
local settingsPageFactory = require(RobloxGui.Modules.Settings.SettingsPageFactory)
local this = settingsPageFactory:CreateNewPage()
local isRecordingVideo = false
local recordingEvent = Instance.new("BindableEvent")
recordingEvent.Name = "RecordingEvent"
this.RecordingChanged = recordingEvent.Event
function this:IsRecording()
return isRecordingVideo
end
------ TAB CUSTOMIZATION -------
this.TabHeader.Name = "RecordTab"
this.TabHeader.Icon.Image = "rbxasset://textures/ui/Settings/MenuBarIcons/RecordTab.png"
this.TabHeader.Icon.AspectRatioConstraint.AspectRatio = 41 / 40
if FFlagUseNotificationsLocalization then
this.TabHeader.Title.Text = "Record"
else
this.TabHeader.Icon.Title.Text = "Record"
end
local function onVREnabled()
this.TabHeader.Visible = not VRService.VREnabled
end
onVREnabled()
VRService:GetPropertyChangedSignal("VREnabled"):connect(onVREnabled)
------ PAGE CUSTOMIZATION -------
this.Page.Name = "Record"
local function makeTextLabel(name, text, isTitle, parent, layoutOrder)
local leftPadding, rightPadding, bottomPadding, textSize, font = 10, 0, 10, 24, Enum.Font.SourceSans
if isTitle then
leftPadding, rightPadding, bottomPadding, textSize, font = 10, 0, 0, 36, Enum.Font.SourceSansBold
end
local container = utility:Create'Frame'
{
Name = name .. "Container",
BackgroundTransparency = 1,
ZIndex = 2,
LayoutOrder = layoutOrder,
Parent = parent
};
local textLabel = utility:Create'TextLabel'
{
Name = name,
BackgroundTransparency = 1,
Text = text,
TextWrapped = true,
Font = font,
TextSize = textSize,
TextColor3 = Color3.new(1,1,1),
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Top,
Position = UDim2.new(0, leftPadding, 0, 0),
Size = UDim2.new(1, -(leftPadding + rightPadding), 1, 0),
ZIndex = 2,
Parent = container
};
local function onResized(prop)
if prop == "AbsoluteSize" then
local textSize = TextService:GetTextSize(text, textLabel.TextSize, textLabel.Font, Vector2.new(parent.AbsoluteSize.X - leftPadding - rightPadding, 1e4))
container.Size = UDim2.new(1, 0, 0, textSize.Y + bottomPadding)
end
end
onResized("AbsoluteSize")
parent.Changed:connect(onResized)
return textLabel, container
end
-- need to override this function from SettingsPageFactory
-- DropDown menus require hub to to be set when they are initialized
function this:SetHub(newHubRef)
this.HubRef = newHubRef
---------------------------------- SCREENSHOT -------------------------------------
local closeSettingsFunc = function()
this.HubRef:SetVisibility(false, true)
end
local screenshotTitle = makeTextLabel("ScreenshotTitle", "Screenshot", true, this.Page, 1)
local screenshotBody = makeTextLabel("ScreenshotBody", "By clicking the 'Take Screenshot' button, the menu will close and take a screenshot and save it to your computer.", false, this.Page, 2)
this.ScreenshotButtonRow, this.ScreenshotButton = utility:AddButtonRow(this, "ScreenshotButton", "Take Screenshot", UDim2.new(0, 300, 0, 44), closeSettingsFunc)
this.ScreenshotButtonRow.LayoutOrder = 3
---------------------------------- VIDEO -------------------------------------
local videoTitle = makeTextLabel("VideoTitle", "Video", true, this.Page, 4)
local videoBody = makeTextLabel("VideoBody", "By clicking the 'Record Video' button, the menu will close and start recording your screen.", false, this.Page, 5)
local recordButtonRow, recordButton = utility:AddButtonRow(this, "RecordButton", "Record Video", UDim2.new(0, 300, 0, 44), closeSettingsFunc)
recordButtonRow.LayoutOrder = 6
recordButton.MouseButton1Click:connect(function()
recordingEvent:Fire(not isRecordingVideo)
end)
local gameOptions = settings():FindFirstChild("Game Options")
if gameOptions then
gameOptions.VideoRecordingChangeRequest:connect(function(recording)
isRecordingVideo = recording
if recording then
recordButton.RecordButtonTextLabel.Text = "Stop Recording"
else
recordButton.RecordButtonTextLabel.Text = "Record Video"
end
end)
end
if FFlagKillGuiButtonSetVerb then
recordButton.Activated:Connect(function()
CoreGui:ToggleRecording()
end)
this.ScreenshotButton.Activated:Connect(function()
CoreGui:TakeScreenshot()
end)
else
recordButton:SetVerb("RecordToggle")
this.ScreenshotButton:SetVerb("Screenshot")
end
this.Page.Size = UDim2.new(1,0,0,400)
end
return this
end
----------- Public Facing API Additions --------------
PageInstance = Initialize()
PageInstance.Displayed.Event:connect(function(switchedFromGamepadInput)
if switchedFromGamepadInput then
GuiService.SelectedCoreObject = PageInstance.ScreenshotButton
end
end)
return PageInstance
@@ -0,0 +1,385 @@
--[[
Filename: ReportAbuseMenu.lua
Written by: jeditkacheff
Version 1.0
Description: Takes care of the report abuse page in Settings Menu
--]]
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local GuiService = game:GetService("GuiService")
local PlayersService = game:GetService("Players")
local MarketplaceService = game:GetService("MarketplaceService")
local LocalizationService = game:GetService("LocalizationService")
local utility = require(RobloxGui.Modules.Settings.Utility)
local RobloxTranslator = require(RobloxGui.Modules.RobloxTranslator)
local ABUSE_TYPES_PLAYER = {
"Swearing",
"Inappropriate Username",
"Bullying",
"Scamming",
"Dating",
"Cheating/Exploiting",
"Personal Question",
"Offsite Links",
}
local FFlagReportAbuseMenuDescriptionFix = settings():GetFFlag('ReportAbuseMenuDescriptionFix')
local ABUSE_TYPES_GAME = {
"Inappropriate Content"
}
local DEFAULT_ABUSE_DESC_TEXT = " Short Description (Optional)"
if utility:IsSmallTouchScreen() then
DEFAULT_ABUSE_DESC_TEXT = " (Optional)"
end
pcall(function()
local LocalizationService = game:GetService("LocalizationService")
local CorescriptLocalization = LocalizationService:GetCorescriptLocalizations()[1]
if FFlagReportAbuseMenuDescriptionFix then
if utility:IsSmallTouchScreen() then
DEFAULT_ABUSE_DESC_TEXT = RobloxTranslator:FormatByKey("KEY_DESCRIPTION_OPTIONAL")
else
DEFAULT_ABUSE_DESC_TEXT = RobloxTranslator:FormatByKey("KEY_DESCRIPTION_SHORT_DECRIPTION_OPTIONAL")
end
else
DEFAULT_ABUSE_DESC_TEXT = CorescriptLocalization:GetString(
LocalizationService.RobloxLocaleId,
"KEY_DESCRIPTION_OPTIONAL")
end
end)
local PageInstance = nil
local success, result = pcall(function() return settings():GetFFlag('UseNotificationsLocalization') end)
local FFlagUseNotificationsLocalization = success and result
----------- CLASS DECLARATION --------------
local function Initialize()
local settingsPageFactory = require(RobloxGui.Modules.Settings.SettingsPageFactory)
local this = settingsPageFactory:CreateNewPage()
local playerNames = {}
local nameToRbxPlayer = {}
local nextPlayerToReport = nil
function this:GetPlayerFromIndex(index)
local playerName = playerNames[index]
if playerName then
return nameToRbxPlayer[playerName]
end
return nil
end
function this:SetNextPlayerToReport(player)
nextPlayerToReport = player
end
function this:UpdatePlayerDropDown()
playerNames = {}
nameToRbxPlayer = {}
local players = PlayersService:GetPlayers()
local index = 1
for i = 1, #players do
local player = players[i]
if player ~= PlayersService.LocalPlayer and player.UserId > 0 then
playerNames[index] = player.Name
nameToRbxPlayer[player.Name] = player
index = index + 1
end
end
table.sort(playerNames, function(a, b)
return a:lower() < b:lower()
end)
this.WhichPlayerMode:UpdateDropDownList(playerNames)
--Reset GameOrPlayerMode to Game if no other players
if index == 1 then
this.GameOrPlayerMode:SetSelectionIndex(1)
end
this.GameOrPlayerMode:SetInteractable(index > 1)
if this.GameOrPlayerMode.CurrentIndex == 1 then
this.WhichPlayerMode:SetInteractable(false)
this.TypeOfAbuseMode:UpdateDropDownList(ABUSE_TYPES_GAME)
this.TypeOfAbuseMode:SetInteractable(#ABUSE_TYPES_GAME > 1)
else
this.WhichPlayerLabel.ZIndex = 2
this.WhichPlayerMode:SetInteractable(index > 1)
this.TypeOfAbuseMode:UpdateDropDownList(ABUSE_TYPES_PLAYER)
this.TypeOfAbuseMode:SetInteractable(#ABUSE_TYPES_PLAYER > 1)
end
if nextPlayerToReport then
local playerSelected = this.WhichPlayerMode:SetSelectionByValue(nextPlayerToReport.Name)
nextPlayerToReport = nil
if this.GameOrPlayerMode.CurrentIndex == 2 then
if playerSelected then --if the reported player is still in game
--Auto select type of abuse when report a player
GuiService.SelectedCoreObject = this.TypeOfAbuseMode.DropDownFrame
else
GuiService.SelectedCoreObject = this.WhichPlayerMode.DropDownFrame
end
end
end
end
------ TAB CUSTOMIZATION -------
this.TabHeader.Name = "ReportAbuseTab"
this.TabHeader.Icon.Image = "rbxasset://textures/ui/Settings/MenuBarIcons/ReportAbuseTab.png"
if FFlagUseNotificationsLocalization then
this.TabHeader.Title.Text = "Report"
else
this.TabHeader.Icon.Title.Text = "Report"
end
------ PAGE CUSTOMIZATION -------
this.Page.Name = "ReportAbusePage"
-- need to override this function from SettingsPageFactory
-- DropDown menus require hub to to be set when they are initialized
function this:SetHub(newHubRef)
this.HubRef = newHubRef
if utility:IsSmallTouchScreen() then
this.GameOrPlayerFrame,
this.GameOrPlayerLabel,
this.GameOrPlayerMode = utility:AddNewRow(this, "Game or Player?", "Selector", {"Game", "Player"}, 2)
else
this.GameOrPlayerFrame,
this.GameOrPlayerLabel,
this.GameOrPlayerMode = utility:AddNewRow(this, "Game or Player?", "Selector", {"Game", "Player"}, 2, 3)
end
this.GameOrPlayerMode.Selection.LayoutOrder = 1
this.WhichPlayerFrame,
this.WhichPlayerLabel,
this.WhichPlayerMode = utility:AddNewRow(this, "Which Player?", "DropDown", {"update me"})
this.WhichPlayerMode:SetInteractable(false)
this.WhichPlayerLabel.ZIndex = 1
this.WhichPlayerFrame.LayoutOrder = 2
this.TypeOfAbuseFrame,
this.TypeOfAbuseLabel,
this.TypeOfAbuseMode = utility:AddNewRow(this, "Type Of Abuse", "DropDown", ABUSE_TYPES_GAME, 1)
this.TypeOfAbuseFrame.LayoutOrder = 3
if utility:IsSmallTouchScreen() then
this.AbuseDescriptionFrame,
this.AbuseDescriptionLabel,
this.AbuseDescription = utility:AddNewRow(this, DEFAULT_ABUSE_DESC_TEXT, "TextBox", nil, nil, 5)
this.AbuseDescriptionLabel.Text = "Abuse Description"
else
this.AbuseDescriptionFrame,
this.AbuseDescriptionLabel,
this.AbuseDescription = utility:AddNewRow(this, DEFAULT_ABUSE_DESC_TEXT, "TextBox", nil, nil, 5)
this.AbuseDescriptionFrame.Size = UDim2.new(1, -10, 0, 100)
this.AbuseDescription.Selection.Size = UDim2.new(1, 0, 1, 0)
end
this.AbuseDescriptionFrame.LayoutOrder = 4
this.AbuseDescription.Selection.FocusLost:connect(function()
if this.AbuseDescription.Selection.Text == "" then
this.AbuseDescription.Selection.Text = DEFAULT_ABUSE_DESC_TEXT
end
end)
local SelectionOverrideObject = utility:Create'ImageLabel'
{
Image = "",
BackgroundTransparency = 1
};
local submitButton, submitText = nil, nil
local function makeSubmitButtonActive()
submitButton.ZIndex = 2
submitButton.Selectable = true
submitText.ZIndex = 2
end
local function makeSubmitButtonInactive()
submitButton.ZIndex = 1
submitButton.Selectable = false
submitText.ZIndex = 1
end
local function updateAbuseDropDown()
this.WhichPlayerMode:ResetSelectionIndex()
this.TypeOfAbuseMode:ResetSelectionIndex()
if this.GameOrPlayerMode.CurrentIndex == 1 then
this.TypeOfAbuseMode:UpdateDropDownList(ABUSE_TYPES_GAME)
this.TypeOfAbuseMode:SetInteractable(#ABUSE_TYPES_GAME > 1)
this.TypeOfAbuseLabel.ZIndex = (#ABUSE_TYPES_GAME > 1 and 2 or 1)
this.WhichPlayerMode:SetInteractable(false)
this.WhichPlayerLabel.ZIndex = 1
makeSubmitButtonActive()
else
this.TypeOfAbuseMode:UpdateDropDownList(ABUSE_TYPES_PLAYER)
this.TypeOfAbuseMode:SetInteractable(#ABUSE_TYPES_PLAYER > 1)
this.TypeOfAbuseLabel.ZIndex = (#ABUSE_TYPES_PLAYER > 1 and 2 or 1)
if #playerNames > 0 then
this.WhichPlayerMode:SetInteractable(true)
this.WhichPlayerLabel.ZIndex = 2
else
this.WhichPlayerMode:SetInteractable(false)
this.WhichPlayerLabel.ZIndex = 1
end
makeSubmitButtonInactive()
end
end
local function cleanupReportAbuseMenu()
updateAbuseDropDown()
this.AbuseDescription.Selection.Text = DEFAULT_ABUSE_DESC_TEXT
this.HubRef:SetVisibility(false, true)
end
local function onReportSubmitted()
local abuseReason = nil
local reportSucceeded = false
if this.GameOrPlayerMode.CurrentIndex == 2 then
abuseReason = ABUSE_TYPES_PLAYER[this.TypeOfAbuseMode.CurrentIndex]
local currentAbusingPlayer = this:GetPlayerFromIndex(this.WhichPlayerMode.CurrentIndex)
if currentAbusingPlayer and abuseReason then
reportSucceeded = true
spawn(function()
PlayersService:ReportAbuse(currentAbusingPlayer, abuseReason, this.AbuseDescription.Selection.Text)
end)
end
else
abuseReason = ABUSE_TYPES_GAME[this.TypeOfAbuseMode.CurrentIndex]
if abuseReason then
reportSucceeded = true
spawn(function()
local placeId,placeName,placeDescription = tostring(game.PlaceId), "N/A", "N/A"
local abuseDescription = this.AbuseDescription.Selection.Text
pcall(function()
local productInfo = MarketplaceService:GetProductInfo(game.PlaceId, Enum.InfoType.Asset)
placeName = productInfo.Name
placeDescription = productInfo.Description
end)
local formattedText = string.format("User Report: \n %s \n".."Place Title: \n %s \n".."PlaceId: \n %s \n".."Place Description: \n %s \n",abuseDescription, placeName, placeId, placeDescription)
PlayersService:ReportAbuse(nil, abuseReason, formattedText)
end)
end
end
if reportSucceeded then
local alertText = "Thanks for your report! Our moderators will review the chat logs and evaluate what happened."
if abuseReason == 'Cheating/Exploiting' then
alertText = "Thanks for your report! We've recorded your report for evaluation."
elseif abuseReason == 'Inappropriate Username' then
alertText = "Thanks for your report! Our moderators will evaluate the username."
elseif abuseReason == "Bad Model or Script" or abuseReason == "Inappropriate Content" or abuseReason == "Offsite Link" or abuseReason == "Offsite Links" then
alertText = "Thanks for your report! Our moderators will review the place and make a determination."
end
utility:ShowAlert(alertText, "Ok", this.HubRef, cleanupReportAbuseMenu)
this.LastSelectedObject = nil
end
end
submitButton, submitText = utility:MakeStyledButton("SubmitButton", "Submit", UDim2.new(0,198,0,50), onReportSubmitted, this)
submitButton.AnchorPoint = Vector2.new(0.5,0)
submitButton.Position = UDim2.new(0.5,0,1,5)
if this.GameOrPlayerMode.CurrentIndex == 1 then
makeSubmitButtonActive()
else
makeSubmitButtonInactive()
end
submitButton.Parent = this.AbuseDescription.Selection
local function playerSelectionChanged(newIndex)
if newIndex ~= nil and this.TypeOfAbuseMode:GetSelectedIndex() ~= nil then
makeSubmitButtonActive()
else
makeSubmitButtonInactive()
end
end
this.WhichPlayerMode.IndexChanged:connect(playerSelectionChanged)
local function typeOfAbuseChanged(newIndex)
if newIndex ~= nil then
if this.GameOrPlayerMode.CurrentIndex == 1 then -- 1 is Report Game
makeSubmitButtonActive()
else -- 2 is Report Player
if this.WhichPlayerMode:GetSelectedIndex() then
makeSubmitButtonActive()
else
makeSubmitButtonInactive()
end
end
else
makeSubmitButtonInactive()
end
end
this.TypeOfAbuseMode.IndexChanged:connect(typeOfAbuseChanged)
this.GameOrPlayerMode.IndexChanged:connect(updateAbuseDropDown)
this:AddRow(nil, nil, this.AbuseDescription)
this.Page.Size = UDim2.new(1,0,0,submitButton.AbsolutePosition.Y + submitButton.AbsoluteSize.Y)
end
return this
end
----------- Public Facing API Additions --------------
do
PageInstance = Initialize()
PageInstance.Displayed.Event:connect(function()
PageInstance:UpdatePlayerDropDown()
end)
function PageInstance:ReportPlayer(player)
if player then
local setReportPlayerConnection = nil
PageInstance:SetNextPlayerToReport(player)
setReportPlayerConnection = PageInstance.Displayed.Event:connect(function()
PageInstance.GameOrPlayerMode:SetSelectionIndex(2)
if setReportPlayerConnection then
setReportPlayerConnection:disconnect()
setReportPlayerConnection = nil
end
end)
if not PageInstance.HubRef:GetVisibility() then
PageInstance.HubRef:SetVisibility(true, false, PageInstance)
else
PageInstance.HubRef:SwitchToPage(PageInstance, false)
end
end
end
end
return PageInstance
@@ -0,0 +1,165 @@
--[[
Filename: ResetCharacter.lua
Written by: jeditkacheff
Version 1.0
Description: Takes care of the reseting the character in Settings Menu
--]]
-------------- CONSTANTS -------------
local RESET_CHARACTER_GAME_ACTION = "ResetCharacterAction"
-------------- SERVICES --------------
local CoreGui = game:GetService("CoreGui")
local ContextActionService = game:GetService("ContextActionService")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local GuiService = game:GetService("GuiService")
local PlayersService = game:GetService("Players")
----------- UTILITIES --------------
local utility = require(RobloxGui.Modules.Settings.Utility)
------------ Variables -------------------
local PageInstance = nil
RobloxGui:WaitForChild("Modules"):WaitForChild("TenFootInterface")
local isTenFootInterface = require(RobloxGui.Modules.TenFootInterface):IsEnabled()
----------- CLASS DECLARATION --------------
local function Initialize()
local settingsPageFactory = require(RobloxGui.Modules.Settings.SettingsPageFactory)
local this = settingsPageFactory:CreateNewPage()
this.DontResetCharFunc = function(isUsingGamepad)
if this.HubRef then
this.HubRef:PopMenu(isUsingGamepad, true)
end
end
this.DontResetCharFromHotkey = function(name, state, input)
if state == Enum.UserInputState.Begin then
local isUsingGamepad = input.UserInputType == Enum.UserInputType.Gamepad1 or input.UserInputType == Enum.UserInputType.Gamepad2
or input.UserInputType == Enum.UserInputType.Gamepad3 or input.UserInputType == Enum.UserInputType.Gamepad4
this.DontResetCharFunc(isUsingGamepad)
end
end
this.DontResetCharFromButton = function(isUsingGamepad)
this.DontResetCharFunc(isUsingGamepad)
end
------ TAB CUSTOMIZATION -------
this.TabHeader = nil -- no tab for this page
------ PAGE CUSTOMIZATION -------
this.Page.Name = "ResetCharacter"
this.ShouldShowBottomBar = false
this.ShouldShowHubBar = false
local resetCharacterText = utility:Create'TextLabel'
{
Name = "ResetCharacterText",
Text = "Are you sure you want to reset your character?",
Font = Enum.Font.SourceSansBold,
FontSize = Enum.FontSize.Size36,
TextColor3 = Color3.new(1,1,1),
BackgroundTransparency = 1,
Size = UDim2.new(1,0,0,200),
TextWrapped = true,
ZIndex = 2,
Parent = this.Page,
Position = isTenFootInterface and UDim2.new(0,0,0,100) or UDim2.new(0,0,0,0)
};
local resetButtonContainer = utility:Create"Frame"
{
Name = "ResetButtonContainer",
Parent = resetCharacterText,
Size = UDim2.new(1,0,0,400),
BackgroundTransparency = 1,
Position = UDim2.new(0,0,1,0)
};
local resetButtonLayout = utility:Create'UIGridLayout'
{
Name = "ResetButtonsLayout",
CellSize = isTenFootInterface and UDim2.new(0, 300, 0, 80) or UDim2.new(0, 200, 0, 50),
CellPadding = UDim2.new(0,20,0,20),
FillDirection = Enum.FillDirection.Horizontal,
HorizontalAlignment = Enum.HorizontalAlignment.Center,
SortOrder = Enum.SortOrder.LayoutOrder,
VerticalAlignment = Enum.VerticalAlignment.Top,
Parent = resetButtonContainer
};
if utility:IsSmallTouchScreen() then
resetCharacterText.FontSize = Enum.FontSize.Size24
resetCharacterText.Size = UDim2.new(1,0,0,100)
elseif isTenFootInterface then
resetCharacterText.FontSize = Enum.FontSize.Size48
end
------ Init -------
local resetCharFunc = function()
local player = PlayersService.LocalPlayer
if player then
local character = player.Character
if character then
local humanoid = character:FindFirstChild('Humanoid')
if humanoid then
humanoid.Health = 0
end
end
end
end
this.ResetBindable = true
local onResetFunction = function()
if this.ResetBindable == true then
resetCharFunc()
elseif this.ResetBindable then
this.ResetBindable:Fire()
end
if this.HubRef then
this.HubRef:SetVisibility(false, true)
end
end
this.ResetCharacterButton = utility:MakeStyledButton("ResetCharacter", "Reset", nil, onResetFunction)
this.ResetCharacterButton.NextSelectionRight = nil
this.ResetCharacterButton.Parent = resetButtonContainer
local dontResetCharacterButton = utility:MakeStyledButton("DontResetCharacter", "Don't Reset", nil, this.DontResetCharFromButton)
dontResetCharacterButton.NextSelectionLeft = nil
dontResetCharacterButton.Parent = resetButtonContainer
this.Page.Size = UDim2.new(1,0,0,dontResetCharacterButton.AbsolutePosition.Y + dontResetCharacterButton.AbsoluteSize.Y)
return this
end
----------- Public Facing API Additions --------------
PageInstance = Initialize()
local isOpen = false
PageInstance.Displayed.Event:connect(function()
isOpen = true
GuiService.SelectedCoreObject = PageInstance.ResetCharacterButton
ContextActionService:BindCoreAction(RESET_CHARACTER_GAME_ACTION, PageInstance.DontResetCharFromHotkey, false, Enum.KeyCode.ButtonB)
end)
PageInstance.Hidden.Event:connect(function()
isOpen = false
ContextActionService:UnbindCoreAction(RESET_CHARACTER_GAME_ACTION)
end)
function PageInstance:SetResetCallback(bindableEvent)
if bindableEvent == false and isOpen then
-- We need to close this page if reseting was just disabled and the page is already open
PageInstance.HubRef:PopMenu(nil, true)
end
PageInstance.ResetBindable = bindableEvent
end
return PageInstance
@@ -0,0 +1,8 @@
local Modules = game:GetService("CorePackages").AppTempCommon
local Action = require(Modules.Common.Action)
return Action(script.Name, function(route)
return {
route = route,
}
end)
@@ -0,0 +1,8 @@
local Modules = game:GetService("CorePackages").AppTempCommon
local Action = require(Modules.Common.Action)
return Action(script.Name, function(route)
return {
route = route,
}
end)
@@ -0,0 +1,9 @@
local Modules = game:GetService("CorePackages").AppTempCommon
local Action = require(Modules.Common.Action)
return Action(script.Name, function(userId, inviteStatus)
return {
userId = tostring(userId),
inviteStatus = inviteStatus,
}
end)
@@ -0,0 +1,8 @@
local Modules = game:GetService("CorePackages").AppTempCommon
local Action = require(Modules.Common.Action)
return Action(script.Name, function(deviceLayout)
return {
deviceLayout = deviceLayout,
}
end)
@@ -0,0 +1,8 @@
local Modules = game:GetService("CorePackages").AppTempCommon
local Action = require(Modules.Common.Action)
return Action(script.Name, function(isSmallTouchScreen)
return {
isSmallTouchScreen = isSmallTouchScreen,
}
end)
@@ -0,0 +1,8 @@
local Modules = game:GetService("CorePackages").AppTempCommon
local Action = require(Modules.Common.Action)
return Action(script.Name, function(isActive)
return {
isActive = isActive,
}
end)
@@ -0,0 +1,8 @@
local Modules = game:GetService("CorePackages").AppTempCommon
local Action = require(Modules.Common.Action)
return Action(script.Name, function(searchText)
return {
searchText = searchText,
}
end)
@@ -0,0 +1,6 @@
local Modules = game:GetService("CorePackages").AppTempCommon
local Action = require(Modules.Common.Action)
return Action(script.Name, function()
return {}
end)
@@ -0,0 +1,30 @@
local CorePackages = game:GetService("CorePackages")
local AppTempCommon = CorePackages.AppTempCommon
local Modules = game:GetService("CoreGui").RobloxGui.Modules
local ShareGame = Modules.Settings.Pages.ShareGame
local PlaceInfos = require(AppTempCommon.LuaChat.Reducers.PlaceInfos)
local Users = require(AppTempCommon.LuaApp.Reducers.Users)
local Friends = require(AppTempCommon.LuaApp.Reducers.Friends)
local ConversationsSearch = require(ShareGame.Reducers.ConversationsSearch)
local DeviceInfo = require(ShareGame.Reducers.DeviceInfo)
local Invites = require(ShareGame.Reducers.Invites)
local Page = require(ShareGame.Reducers.Page)
local Toasts = require(ShareGame.Reducers.Toasts)
return function(state, action)
state = state or {}
return {
ConversationsSearch = ConversationsSearch(state.ConversationsSearch, action),
DeviceInfo = DeviceInfo(state.DeviceInfo, action),
Invites = Invites(state.Invites, action),
Page = Page(state.Page, action),
PlaceInfos = PlaceInfos(state.PlaceInfos, action),
Toasts = Toasts(state.Toasts, action),
Users = Users(state.Users, action),
Friends = Friends(state.Friends, action),
}
end
@@ -0,0 +1,53 @@
local CoreGui = game:GetService("CoreGui")
local CorePackages = game:GetService("CorePackages")
local AppTempCommon = CorePackages.AppTempCommon
local Modules = CoreGui.RobloxGui.Modules
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local ShareGame = Modules.Settings.Pages.ShareGame
local Constants = require(ShareGame.Constants)
local EventStream = require(AppTempCommon.Temp.EventStream)
local LayoutProvider = require(ShareGame.Components.LayoutProvider)
local ShareGamePageFrame = require(ShareGame.Components.ShareGamePageFrame)
local ShareGameApp = Roact.PureComponent:extend("App")
function ShareGameApp:render()
local pageTarget = self.props.pageTarget
local pageFrame = nil
if self.props.isPageOpen then
pageFrame = Roact.createElement(ShareGamePageFrame, {
zIndex = Constants.SHARE_GAME_Z_INDEX,
})
end
return Roact.createElement(LayoutProvider, nil, {
Roact.createElement(Roact.Portal, {
target = pageTarget,
}, {
ShareGamePageFrame = pageFrame
})
})
end
function ShareGameApp:didMount()
self.eventStream = EventStream.new()
end
function ShareGameApp:willUnmount()
self.eventStream:releaseRBXEventStream()
end
local connector = RoactRodux.UNSTABLE_connect2(function(state, props)
return {
isPageOpen = state.Page.IsOpen,
}
end)
return connector(ShareGameApp)
@@ -0,0 +1,56 @@
local CorePackages = game:GetService("CorePackages")
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local RobloxTranslator = require(RobloxGui.Modules.RobloxTranslator)
local ShareGame = RobloxGui.Modules.Settings.Pages.ShareGame
local RectangleButton = require(ShareGame.Components.RectangleButton)
local IconButton = require(ShareGame.Components.IconButton)
local Roact = require(CorePackages.Roact)
local Constants = require(ShareGame.Constants)
local ShareGameIcons = require(RobloxGui.Modules.Settings.Pages.ShareGame.Spritesheets.ShareGameIcons)
local BACK_IMAGE_SPRITE_PATH = ShareGameIcons:GetImagePath()
local BACK_IMAGE_SPRITE_FRAME = ShareGameIcons:GetFrame("back")
local BACK_BUTTON_TEXT_SIZE = 24
local BackButton = Roact.PureComponent:extend("BackButton")
function BackButton:render()
local isArrow = self.props.isArrow
local visible = self.props.visible
local anchorPoint = self.props.anchorPoint
local position = self.props.position
local size = self.props.size
local zIndex = self.props.zIndex
local onClick = self.props.onClick
if isArrow then
return Roact.createElement(IconButton, {
visible = visible,
anchorPoint = anchorPoint,
position = position,
size = size,
zIndex = zIndex,
onClick = onClick,
iconHorizontalAlignment = Enum.HorizontalAlignment.Left,
iconSpritePath = BACK_IMAGE_SPRITE_PATH,
iconSpriteFrame = BACK_IMAGE_SPRITE_FRAME,
})
else
return Roact.createElement(RectangleButton, self.props, {
TextLabel = Roact.createElement("TextLabel", {
Position = UDim2.new(0.5, 0, 0.5, 0),
Text = RobloxTranslator:FormatByKey("Feature.SettingsHub.Action.InviteFriendsBack"),
TextSize = BACK_BUTTON_TEXT_SIZE,
TextColor3 = Constants.Color.WHITE,
Font = Enum.Font.SourceSansSemibold,
ZIndex = zIndex,
}),
})
end
end
return BackButton
@@ -0,0 +1,77 @@
local CorePackages = game:GetService("CorePackages")
local AppTempCommon = CorePackages.AppTempCommon
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local RobloxTranslator = require(RobloxGui.Modules.RobloxTranslator)
local Roact = require(CorePackages.Roact)
local ShareGame = RobloxGui.Modules.Settings.Pages.ShareGame
local Constants = require(ShareGame.Constants)
local User = require(AppTempCommon.LuaApp.Models.User)
local LIST_PADDING = 2
local TITLE_FONT = Enum.Font.SourceSans
local TITLE_COLOR = Constants.Color.WHITE
local TITLE_TEXT_SIZE = 19
local PRESENCE_FONT = Enum.Font.SourceSans
local PRESENCE_TEXT_SIZE = 16
local ConversationDetails = Roact.PureComponent:extend("ConversationDetails")
function ConversationDetails:render()
local title = self.props.title
local presence = self.props.presence
local size = self.props.size
local layoutOrder = self.props.layoutOrder
local zIndex = self.props.zIndex
-- Show user presence if it was passed in
local presenceTextComponent
if presence ~= nil then
presenceTextComponent = Roact.createElement("TextLabel", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, PRESENCE_TEXT_SIZE),
Text = RobloxTranslator:FormatByKey(Constants.PresenceTextKey[presence]),
Font = PRESENCE_FONT,
TextColor3 = Constants.PresenceColors[presence],
TextSize = PRESENCE_TEXT_SIZE,
TextXAlignment = Enum.TextXAlignment.Left,
LayoutOrder = 1,
ZIndex = zIndex,
})
end
return Roact.createElement("Frame", {
BackgroundTransparency = 1,
Size = size,
LayoutOrder = layoutOrder,
ZIndex = zIndex,
}, {
UIListLayout = Roact.createElement("UIListLayout", {
FillDirection = Enum.FillDirection.Vertical,
HorizontalAlignment = Enum.HorizontalAlignment.Left,
VerticalAlignment = Enum.VerticalAlignment.Center,
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, LIST_PADDING)
}),
Title = Roact.createElement("TextLabel", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, TITLE_TEXT_SIZE),
Text = title or "",
Font = TITLE_FONT,
TextColor3 = TITLE_COLOR,
TextSize = TITLE_TEXT_SIZE,
TextXAlignment = Enum.TextXAlignment.Left,
TextWrapped = true,
LayoutOrder = 0,
ZIndex = zIndex,
}),
Presence = presenceTextComponent,
})
end
return ConversationDetails
@@ -0,0 +1,126 @@
local CorePackages = game:GetService("CorePackages")
local Players = game:GetService("Players")
local Modules = game:GetService("CoreGui").RobloxGui.Modules
local Roact = require(CorePackages.Roact)
local ShareGame = Modules.Settings.Pages.ShareGame
local ConversationDetails = require(ShareGame.Components.ConversationDetails)
local ConversationThumbnail = require(ShareGame.Components.ConversationThumbnail)
local EventStream = require(CorePackages.AppTempCommon.Temp.EventStream)
local InviteButton = require(ShareGame.Components.InviteButton)
local ENTRY_BG_IMAGE = "rbxasset://textures/ui/dialog_white.png"
local ENTRY_BG_SLICE = Rect.new(10, 10, 10, 10)
local ENTRY_BG_TRANSPARENCY = 0.85
local THUMBNAIL_SIZE = 32
local INVITE_BUTTON_WIDTH = 69
local CONTENTS_PADDING = 12
local ConversationEntry = Roact.PureComponent:extend("ConversationEntry")
function ConversationEntry:init()
self.eventStream = EventStream.new()
end
function ConversationEntry:render()
local visible = self.props.visible
local layoutOrder = self.props.layoutOrder
local zIndex = self.props.zIndex
local size = self.props.size
local title = self.props.title
local users = self.props.users
local inviteUser = self.props.inviteUser
local inviteStatus = self.props.inviteStatus
-- Presence gets passed in if there's only one user
local presence = self.props.presence
return Roact.createElement("ImageLabel", {
Visible = visible,
BackgroundTransparency = 1,
Image = ENTRY_BG_IMAGE,
ImageTransparency = ENTRY_BG_TRANSPARENCY,
ScaleType = Enum.ScaleType.Slice,
SliceCenter = ENTRY_BG_SLICE,
Size = size,
LayoutOrder = layoutOrder,
ZIndex = zIndex,
}, {
UIPadding = Roact.createElement("UIPadding", {
PaddingLeft = UDim.new(0, CONTENTS_PADDING),
PaddingRight = UDim.new(0, CONTENTS_PADDING),
PaddingTop = UDim.new(0, CONTENTS_PADDING),
PaddingBottom = UDim.new(0, CONTENTS_PADDING),
}),
UIListLayout = Roact.createElement("UIListLayout", {
FillDirection = Enum.FillDirection.Horizontal,
HorizontalAlignment = Enum.HorizontalAlignment.Left,
VerticalAlignment = Enum.VerticalAlignment.Center,
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, 12),
}),
Thumbnail = Roact.createElement(ConversationThumbnail, {
users = users,
size = UDim2.new(0, THUMBNAIL_SIZE, 0, THUMBNAIL_SIZE),
layoutOrder = 0,
zIndex = zIndex,
}),
Details = Roact.createElement(ConversationDetails, {
title = title,
presence = presence,
size = UDim2.new(
-- Make details fullwidth and subtract the width of its siblings
1, -(THUMBNAIL_SIZE + INVITE_BUTTON_WIDTH + CONTENTS_PADDING * 2),
1, 0
),
layoutOrder = 1,
zIndex = zIndex,
}),
InviteButton = Roact.createElement(InviteButton, {
size = UDim2.new(0, INVITE_BUTTON_WIDTH, 1, 0),
layoutOrder = 2,
zIndex = zIndex,
onInvite = function()
-- Check if this is a one-on-one convo
if #users == 1 then
inviteUser(users[1].id):andThen(function(results)
if not results then
return
end
-- Pluck the userIds out of the user list
local participants = {}
for _, user in pairs(users) do
table.insert(participants, user.id)
end
local localPlayer = Players.LocalPlayer
local senderId = tostring(localPlayer.UserId)
local eventContext = "inGame"
local eventName = "clickShareGameInviteSent"
local participantsString = table.concat(participants, ",")
local additionalArgs = {
btn = "settingsHub",
placeId = tostring(results.placeId),
senderId = senderId,
conversationId = tostring(results.conversationId),
participants = participantsString,
wasModerated = results.wasModerated,
}
self.eventStream:setRBXEventStream(eventContext, eventName, additionalArgs)
end)
end
end,
inviteStatus = inviteStatus,
}),
})
end
return ConversationEntry
@@ -0,0 +1,219 @@
local CorePackages = game:GetService("CorePackages")
local HttpRbxApiService = game:GetService("HttpRbxApiService")
local AppTempCommon = CorePackages.AppTempCommon
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local RobloxTranslator = require(RobloxGui.Modules.RobloxTranslator)
local Players = game:GetService("Players")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local ShareGame = RobloxGui.Modules.Settings.Pages.ShareGame
local ConversationEntry = require(ShareGame.Components.ConversationEntry)
local NoFriendsPage = require(ShareGame.Components.NoFriendsPage)
local LoadingFriendsPage = require(ShareGame.Components.LoadingFriendsPage)
local FriendsErrorPage = require(ShareGame.Components.FriendsErrorPage)
local Constants = require(ShareGame.Constants)
local httpRequest = require(AppTempCommon.Temp.httpRequest)
local User = require(AppTempCommon.LuaApp.Models.User)
local ReceivedUserInviteStatus = require(ShareGame.Actions.ReceivedUserInviteStatus)
local memoize = require(AppTempCommon.Common.memoize)
local Promise = require(AppTempCommon.LuaApp.Promise)
local ApiSendGameInvite = require(AppTempCommon.LuaApp.Thunks.ApiSendGameInvite)
local ApiFetchPlaceInfos = require(AppTempCommon.LuaApp.Thunks.ApiFetchPlaceInfos)
local RetrievalStatus = require(CorePackages.AppTempCommon.LuaApp.Enum.RetrievalStatus)
local ENTRY_HEIGHT = 62
local ENTRY_PADDING = 18
local NO_RESULTS_FONT = Enum.Font.SourceSans
local NO_RESULTS_TEXTCOLOR = Constants.Color.GRAY3
local NO_RESULTS_TEXTSIZE = 19
local NO_RESULTS_TRANSPRENCY = 0.22
local InviteStatus = Constants.InviteStatus
local PRESENCE_WEIGHTS = {
[User.PresenceType.ONLINE] = 3,
[User.PresenceType.IN_GAME] = 2,
[User.PresenceType.IN_STUDIO] = 1,
[User.PresenceType.OFFLINE] = 0,
}
local ConversationList = Roact.PureComponent:extend("ConversationList")
local function searchFilterPredicate(query, other)
if query == "" then
return true
end
return string.find(string.lower(other), query:lower(), 1, true) ~= nil
end
function ConversationList:render()
local children = self.props[Roact.Children] or {}
local friends = self.props.friends
local friendsRetrievalStatus = self.props.friendsRetrievalStatus
local layoutOrder = self.props.layoutOrder
local size = self.props.size
local zIndex = self.props.zIndex
local topPadding = self.props.topPadding
local invites = self.props.invites
local inviteUser = self.props.inviteUser
local searchText = self.props.searchText
children["RowListLayout"] = Roact.createElement("UIListLayout", {
FillDirection = Enum.FillDirection.Vertical,
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Top,
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, ENTRY_PADDING),
})
local numEntries = 0
-- Populate list of conversations with friends
for i, user in ipairs(friends) do
local isEntryShown = searchFilterPredicate(searchText, user.name)
children["User-" .. tostring(i)] = Roact.createElement(ConversationEntry, {
visible = isEntryShown,
size = UDim2.new(1, 0, 0, ENTRY_HEIGHT),
layoutOrder = i,
zIndex = zIndex,
title = user.name,
presence = user.presence,
users = {user},
inviteUser = inviteUser,
inviteStatus = invites[user.id],
})
if isEntryShown then
numEntries = numEntries + 1
end
end
if #friends == 0 or friendsRetrievalStatus == RetrievalStatus.Fetching then
local zeroFriendsComponent = LoadingFriendsPage
if friendsRetrievalStatus == RetrievalStatus.Done then
zeroFriendsComponent = NoFriendsPage
elseif friendsRetrievalStatus == RetrievalStatus.Failed then
zeroFriendsComponent = FriendsErrorPage
end
return Roact.createElement(zeroFriendsComponent, {
BorderSizePixel = 0,
LayoutOrder = layoutOrder,
Position = UDim2.new(0, 0, 0, topPadding),
ZIndex = zIndex,
})
else
if numEntries == 0 then
return Roact.createElement("TextLabel", {
BackgroundTransparency = 1,
LayoutOrder = layoutOrder,
Font = NO_RESULTS_FONT,
Size = UDim2.new(1, 0, 0, ENTRY_HEIGHT),
Text = RobloxTranslator:FormatByKey("Feature.SettingsHub.Label.InviteSearchNoResults"),
TextColor3 = NO_RESULTS_TEXTCOLOR,
TextSize = NO_RESULTS_TEXTSIZE,
TextTransparency = NO_RESULTS_TRANSPRENCY,
ZIndex = zIndex,
})
end
end
return Roact.createElement("ScrollingFrame", {
BackgroundTransparency = 1,
LayoutOrder = layoutOrder,
Size = size,
CanvasSize = UDim2.new(0, 0, 0, numEntries * (ENTRY_HEIGHT + ENTRY_PADDING)),
ScrollBarThickness = 0,
ZIndex = zIndex,
}, children)
end
local selectFriends = memoize(function(users)
local friends = {}
local function friendPreference(friend1, friend2)
local friend1Weight = PRESENCE_WEIGHTS[friend1.presence]
local friend2Weight = PRESENCE_WEIGHTS[friend2.presence]
if friend1Weight == friend2Weight then
return friend1.name:lower() < friend2.name:lower()
else
return friend1Weight > friend2Weight
end
end
for _, user in pairs(users) do
if user.isFriend then
friends[#friends + 1] = user
end
end
table.sort(friends, friendPreference)
return friends
end)
-- TODO: Update to use RoactRodux.UNSTABLE_connect2
local connector = RoactRodux.connect(function(store, props)
local state = store:getState()
return {
friends = selectFriends(
state.Users
),
friendsRetrievalStatus = state.Friends.retrievalStatus[tostring(Players.LocalPlayer.UserId)],
invites = state.Invites,
inviteUser = function(userId)
local requestImpl = httpRequest(HttpRbxApiService)
local latestState = store:getState()
return Promise.new(function(resolve, reject)
-- Check that we haven't already invited this user
if latestState.Invites[tostring(userId)] == InviteStatus.Pending then
reject()
else
resolve()
end
end):andThen(function()
local placeId = tostring(game.PlaceId)
local maybePlaceInfo = latestState.PlaceInfos[placeId]
-- TODO: This should be a Thunk
return Promise.new(function(resolve, reject)
-- Log that we've tried inviting this user
store:dispatch(ReceivedUserInviteStatus(userId, InviteStatus.Pending))
if maybePlaceInfo then
resolve(maybePlaceInfo)
else
store:dispatch(ApiFetchPlaceInfos(requestImpl, {placeId})):andThen(function(placeInfos)
if placeInfos[1] ~= nil then
resolve(placeInfos[1])
else
reject()
end
end, function()
reject()
end)
end
end):andThen(function(placeInfo)
return store:dispatch(ApiSendGameInvite(requestImpl, userId, placeInfo))
end):andThen(function(results)
store:dispatch(ReceivedUserInviteStatus(userId, results.resultType))
end, function()
store:dispatch(ReceivedUserInviteStatus(userId, InviteStatus.Failed))
end)
end)
end
}
end)
return connector(ConversationList)
@@ -0,0 +1,176 @@
local CorePackages = game:GetService("CorePackages")
local Modules = game:GetService("CoreGui").RobloxGui.Modules
local Roact = require(CorePackages.Roact)
local Constants = require(Modules.Settings.Pages.ShareGame.Constants)
local FFlagSettingsHubInviteToGameNoBackground = settings():GetFFlag("SettingsHubInviteToGameThumbnailNoBackground")
local BORDER_SIZE = 1
local BORDER_COLOR = Constants.Color.GRAY3
local THUMBNAIL_IMAGE_SIZE = Constants.InviteAvatarThumbnailSize
local THUMBNAIL_IMAGE_TYPE = Constants.InviteAvatarThumbnailType
local DEFAULT_THUMBNAIL_ICON = "rbxasset://textures/ui/LuaApp/graphic/ph-avatar-portrait.png"
-- (Borrowed values from LuaChat)
-- This lets us determine how to build the group thumbnail.
-- Index represents how many people are in the thumbnail!
local IMAGE_LAYOUT = {
[1] = {
{
Size = UDim2.new(1, 0, 1, 0),
Position = UDim2.new(0, 0, 0, 0),
FrameSize = UDim2.new(1, 0, 1, 0),
FramePosition = UDim2.new(0, 0, 0, 0),
},
},
[2] = {
{
Size = UDim2.new(2, 0, 1, 0),
Position = UDim2.new(-0.5, 0, 0, 0),
FrameSize = UDim2.new(0.5, -1, 1, 0),
FramePosition = UDim2.new(0, 0, 0, 0),
Border = {
BorderPosition = UDim2.new(0.5, -1, 0, 0),
BorderSize = UDim2.new(0, 1, 1, 0),
},
},
{
Size = UDim2.new(2, 0, 1, 0),
Position = UDim2.new(-0.5, 0, 0, 0),
FrameSize = UDim2.new(0.5, -1, 1, 0),
FramePosition = UDim2.new(0.5, 1, 0, 0),
},
},
[3] = {
{
Size = UDim2.new(2, 0, 1, 0),
Position = UDim2.new(-0.5, 0, 0, 0),
FrameSize = UDim2.new(0.5, -1, 1, 0),
FramePosition = UDim2.new(0, 0, 0, 0),
Border = {
BorderPosition = UDim2.new(0.5, -1, 0, 0),
BorderSize = UDim2.new(0, 1, 1, 0),
},
},
{
Size = UDim2.new(1, 0, 1, 0),
Position = UDim2.new(0, 0, 0, 0),
FrameSize = UDim2.new(0.5, -1, 0.5, -1),
FramePosition = UDim2.new(0.5, 1, 0, 0),
Border = {
BorderPosition = UDim2.new(0.5, 0, 0.5, -1),
BorderSize = UDim2.new(0.5, 0, 0, 1),
},
},
{
Size = UDim2.new(1, 0, 1, 0),
Position = UDim2.new(0, 0, 0, 0),
FrameSize = UDim2.new(0.5, -1, 0.5, -1),
FramePosition = UDim2.new(0.5, 1, 0.5, 1),
},
},
[4] = {
{
Size = UDim2.new(1, 0, 1, 0),
Position = UDim2.new(0, 0, 0, 0),
FrameSize = UDim2.new(0.5, -1, 0.5, -1),
FramePosition = UDim2.new(0, 0, 0, 0),
Border = {
BorderPosition = UDim2.new(0, 0, 0.5, -1),
BorderSize = UDim2.new(1, 0, 0, 1),
},
},
{
Size = UDim2.new(1, 0, 1, 0),
Position = UDim2.new(0, 0, 0, 0),
FrameSize = UDim2.new(0.5, -1, 0.5, -1),
FramePosition = UDim2.new(0.5, 1, 0, 0),
},
{
Size = UDim2.new(1, 0, 1, 0),
Position = UDim2.new(0, 0, 0, 0),
FrameSize = UDim2.new(0.5, -1, 0.5, -1),
FramePosition = UDim2.new(0, 0, 0.5, 1),
Border = {
BorderPosition = UDim2.new(0.5, -1, 0, 0),
BorderSize = UDim2.new(0, 1, 1, 0),
},
},
{
Size = UDim2.new(1, 0, 1, 0),
Position = UDim2.new(0, 0, 0, 0),
FrameSize = UDim2.new(0.5, -1, 0.5, -1),
FramePosition = UDim2.new(0.5, 1, 0.5, 1),
},
},
}
local ConversationThumbnail = Roact.PureComponent:extend("ConversationThumbnail")
function ConversationThumbnail:render()
local size = self.props.size
local layoutOrder = self.props.layoutOrder
local zIndex = self.props.zIndex
local users = self.props.users
local numUsers = #users
-- Render the thumbnail contents
local thumbnailContents = {}
for i, user in ipairs(users) do
local layoutData = IMAGE_LAYOUT[numUsers][i]
-- Render the avatar inside a clipping container
thumbnailContents["AvatarHolder-"..i] = Roact.createElement("Frame", {
BackgroundTransparency = 1,
ClipsDescendants = true,
Size = layoutData.FrameSize,
Position = layoutData.FramePosition,
ZIndex = zIndex,
}, {
Avatar = Roact.createElement("ImageLabel", {
BackgroundTransparency = 1,
Size = layoutData.Size,
Position = layoutData.Position,
Image = user and user.thumbnails and user.thumbnails[THUMBNAIL_IMAGE_TYPE]
and user.thumbnails[THUMBNAIL_IMAGE_TYPE][THUMBNAIL_IMAGE_SIZE] or DEFAULT_THUMBNAIL_ICON,
ZIndex = zIndex,
})
})
-- Render any borders between avatars
if layoutData.Border then
thumbnailContents["Border-"..i] = Roact.createElement("Frame", {
Size = layoutData.Border.BorderSize,
Position = layoutData.Border.BorderPosition,
BorderSizePixel = 0,
BackgroundColor3 = BORDER_COLOR,
ZIndex = zIndex,
})
end
end
return Roact.createElement("Frame", {
BackgroundTransparency = 1,
Size = size,
LayoutOrder = layoutOrder,
ZIndex = zIndex,
}, {
ContentsContainer = Roact.createElement("Frame", {
-- Render the border inwards instead of outwards
BackgroundTransparency = FFlagSettingsHubInviteToGameNoBackground and 1 or 0,
Size = UDim2.new(1, -BORDER_SIZE, 1, -BORDER_SIZE),
AnchorPoint = Vector2.new(0.5, 0.5),
Position = UDim2.new(0.5, 0, 0.5, 0),
BackgroundColor3 = Constants.Color.WHITE,
BorderColor3 = BORDER_COLOR,
BorderSizePixel = BORDER_SIZE,
ZIndex = zIndex,
}, thumbnailContents),
})
end
return ConversationThumbnail
@@ -0,0 +1,142 @@
local CorePackages = game:GetService("CorePackages")
local RunService = game:GetService("RunService")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local RobloxTranslator = require(RobloxGui.Modules.RobloxTranslator)
local ShareGame = RobloxGui.Modules.Settings.Pages.ShareGame
local Constants = require(ShareGame.Constants)
local StoppedToastTimer = require(ShareGame.Actions.StoppedToastTimer)
local InviteStatus = Constants.InviteStatus
local INNER_TEXT_PADDING = 6
local TEXT_SIZE = 16
local TIMER_LENGTH = 4
local ErrorToaster = Roact.PureComponent:extend("ErrorToaster")
function ErrorToaster:restartTimer()
if not self.timerConnection then
self.timerConnection =
RunService.RenderStepped:Connect(
function()
local failedInvites = self.props.failedInvites
if #failedInvites == 0 then
self:stopTimer()
return
end
local lastModerated = failedInvites[#failedInvites]
local goalTick = lastModerated.timeStamp + TIMER_LENGTH
local now = tick()
if now > goalTick then
self:stopTimer()
end
end
)
end
end
function ErrorToaster:stopTimer()
if self.timerConnection then
self.timerConnection:Disconnect()
self.timerConnection = nil
end
self.props.stoppedTimerDispatch()
end
function ErrorToaster:didUpdate()
local failedInvites = self.props.failedInvites
if #failedInvites ~= 0 then
self:restartTimer()
end
end
function ErrorToaster:render()
local deviceLayout = self.props.deviceLayout
local failedInvites = self.props.failedInvites
local layoutSpecific = Constants.LayoutSpecific[deviceLayout]
local toastHeight = layoutSpecific.TOAST_HEIGHT
if #failedInvites == 0 then
return
end
local errorMessageKey
local inviteStatus = failedInvites[#failedInvites].status
if inviteStatus == InviteStatus.Moderated then
errorMessageKey = "Feature.SettingsHub.Label.ModeratedInviteError"
elseif inviteStatus == InviteStatus.Failed then
errorMessageKey = "Feature.SettingsHub.Label.GameInviteError"
else
return
end
return Roact.createElement(
"ScreenGui",
{
DisplayOrder = 2,
ZIndexBehavior = Enum.ZIndexBehavior.Sibling
},
{
ToastFrame = Roact.createElement(
"Frame",
{
AnchorPoint = Vector2.new(0.5, 0),
BackgroundColor3 = Constants.Color.RED,
BorderSizePixel = 0,
Position = UDim2.new(0.5, 0, 0, 0),
Size = UDim2.new(0.5, 0, 0, toastHeight)
},
{
InnerTextPadding = Roact.createElement(
"UIPadding",
{
PaddingLeft = UDim.new(0, INNER_TEXT_PADDING),
PaddingRight = UDim.new(0, INNER_TEXT_PADDING)
}
),
ToastText = Roact.createElement(
"TextLabel",
{
BackgroundTransparency = 1,
Font = Enum.Font.SourceSansSemibold,
Size = UDim2.new(1, 0, 1, 0),
Text = RobloxTranslator:FormatByKey(errorMessageKey),
TextColor3 = Constants.Color.WHITE,
TextSize = TEXT_SIZE,
TextWrapped = true
}
)
}
)
}
)
end
local function mapStateToProps(state)
return {
deviceLayout = state.DeviceInfo.DeviceLayout,
failedInvites = state.Toasts.failedInvites
}
end
local function mapDispatchToProps(dispatch)
return {
stoppedTimerDispatch = function()
dispatch(StoppedToastTimer())
end
}
end
return RoactRodux.UNSTABLE_connect2(mapStateToProps, mapDispatchToProps)(ErrorToaster)
@@ -0,0 +1,52 @@
local CorePackages = game:GetService("CorePackages")
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local RobloxTranslator = require(RobloxGui.Modules.RobloxTranslator)
local ShareGame = RobloxGui.Modules.Settings.Pages.ShareGame
local Roact = require(CorePackages.Roact)
local Constants = require(ShareGame.Constants)
local FriendsErrorPage = Roact.PureComponent:extend("FriendsErrorPage")
function FriendsErrorPage:render()
local layoutOrder = self.props.LayoutOrder
local zIndex = self.props.ZIndex
local incrementingLayoutOrder = 0
local function incrementLayoutOrder()
incrementingLayoutOrder = incrementingLayoutOrder + 1
return incrementingLayoutOrder
end
return Roact.createElement("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 1, 0),
LayoutOrder = layoutOrder,
ZIndex = zIndex,
}, {
listLayout = Roact.createElement("UIListLayout", {
FillDirection = Enum.FillDirection.Vertical,
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Center,
SortOrder = Enum.SortOrder.LayoutOrder,
}),
subtitle = Roact.createElement("TextLabel", {
BackgroundTransparency = 1,
Text = RobloxTranslator:FormatByKey("Feature.SettingsHub.Label.LoadingFriendsListFailed"),
TextColor3 = Constants.Color.GRAY5,
TextTransparency = 0.22,
TextSize = 21,
TextWrapped = true,
Font = Enum.Font.SourceSans,
LayoutOrder = incrementLayoutOrder(),
Size = UDim2.new(0, 280, 0, 42),
TextYAlignment = Enum.TextYAlignment.Top,
ZIndex = zIndex,
}),
})
end
return FriendsErrorPage
@@ -0,0 +1,67 @@
local CorePackages = game:GetService("CorePackages")
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local Roact = require(CorePackages.Roact)
local ShareGame = RobloxGui.Modules.Settings.Pages.ShareGame
local Constants = require(ShareGame.Constants)
local BackButton = require(ShareGame.Components.BackButton)
local SearchArea = require(ShareGame.Components.SearchArea)
local RobloxTranslator = require(RobloxGui.Modules.RobloxTranslator)
local Header = Roact.PureComponent:extend("Header")
function Header:render()
local deviceLayout = self.props.deviceLayout
local size = self.props.size
local layoutOrder = self.props.layoutOrder
local zIndex = self.props.zIndex
local closePage = self.props.closePage
local searchAreaActive = self.props.searchAreaActive
local layoutSpecific = Constants.LayoutSpecific[deviceLayout]
local isDesktop = deviceLayout == Constants.DeviceLayout.DESKTOP
local isSearchingOnMobile = (not isDesktop) and searchAreaActive
return Roact.createElement("Frame", {
BackgroundTransparency = 1,
Size = size,
AnchorPoint = Vector2.new(0, 1),
LayoutOrder = layoutOrder,
ZIndex = zIndex,
}, {
Title = Roact.createElement("TextLabel", {
BackgroundTransparency = 1,
Visible = not isSearchingOnMobile,
Position = UDim2.new(0.5, 0, 0.5, 0),
Text = RobloxTranslator:FormatByKey("Feature.SettingsHub.Heading.InviteFriends"),
TextSize = layoutSpecific.PAGE_TITLE_TEXT_SIZE,
TextColor3 = Constants.Color.WHITE,
Font = Enum.Font.SourceSansSemibold,
ZIndex = self.props.zIndex,
}),
BackButton = Roact.createElement(BackButton, {
isArrow = not isDesktop,
visible = not isSearchingOnMobile,
position = UDim2.new(0, 0, 0.5, 0),
size = UDim2.new(
0, layoutSpecific.BACK_BUTTON_WIDTH,
0, layoutSpecific.BACK_BUTTON_HEIGHT
),
anchorPoint = Vector2.new(0, 0.5),
zIndex = zIndex,
onClick = closePage,
}),
SearchArea = Roact.createElement(SearchArea, {
fullWidthSearchBar = not isDesktop,
searchBoxMargin = layoutSpecific.SEARCH_BOX_MARGIN,
anchorPoint = Vector2.new(1, 0.5),
position = UDim2.new(1, 0, 0.5, 0),
zIndex = zIndex,
})
})
end
return Header
@@ -0,0 +1,75 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local IconButton = Roact.PureComponent:extend("IconButton")
function IconButton:render()
local visible = self.props.visible
local anchorPoint = self.props.anchorPoint
local position = self.props.position
local size = self.props.size
local zIndex = self.props.zIndex
local onClick = self.props.onClick
local iconHorizontalAlignment = self.props.iconHorizontalAlignment or Enum.HorizontalAlignment.Center
local iconVerticalAlignment = self.props.iconVerticalAlignment or Enum.VerticalAlignment.Center
local iconSpritePath = self.props.iconSpritePath
local iconSpriteFrame = self.props.iconSpriteFrame
local horizontalInset = self.props.horizontalInset or 12
local offsetLayoutOrder = 0
local iconLayoutOrder = 1
local iconSize = UDim2.new(0, iconSpriteFrame.size.X, 0, iconSpriteFrame.size.Y)
if iconHorizontalAlignment == Enum.HorizontalAlignment.Center then
horizontalInset = 0
elseif iconHorizontalAlignment == Enum.HorizontalAlignment.Left then
position = UDim2.new(position.X.Scale, position.X.Offset - horizontalInset, position.Y.Scale, position.Y.Offset)
elseif iconHorizontalAlignment == Enum.HorizontalAlignment.Right then
position = UDim2.new(position.X.Scale, position.X.Offset + horizontalInset, position.Y.Scale, position.Y.Offset)
offsetLayoutOrder = 1
iconLayoutOrder = 0
end
size = UDim2.new(size.X.Scale, size.X.Offset + horizontalInset, size.Y.Scale, size.Y.Offset)
return Roact.createElement("ImageButton", {
Visible = visible,
BackgroundTransparency = 1,
BorderSizePixel = 0,
AnchorPoint = anchorPoint,
Position = position,
Size = size,
ZIndex = zIndex,
[Roact.Event.Activated] = onClick,
}, {
IconLayout = Roact.createElement("UIListLayout", {
HorizontalAlignment = iconHorizontalAlignment,
VerticalAlignment = iconVerticalAlignment,
FillDirection = Enum.FillDirection.Horizontal,
SortOrder = Enum.SortOrder.LayoutOrder,
}),
Offset = Roact.createElement("Frame", {
BorderSizePixel = 0,
LayoutOrder = offsetLayoutOrder,
Size = UDim2.new(0, horizontalInset, 0, 0),
}),
BackIcon = Roact.createElement("ImageLabel", {
BackgroundTransparency = 1,
BorderSizePixel = 0,
Size = UDim2.new(0, iconSpriteFrame.size.X, 0, iconSpriteFrame.size.Y),
Image = iconSpritePath,
ImageRectOffset = iconSpriteFrame.offset,
ImageRectSize = iconSpriteFrame.size,
ZIndex = zIndex,
LayoutOrder = iconLayoutOrder,
}),
})
end
return IconButton
@@ -0,0 +1,71 @@
local CorePackages = game:GetService("CorePackages")
local AppTempCommon = CorePackages.AppTempCommon
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local RobloxTranslator = require(RobloxGui.Modules.RobloxTranslator)
local Roact = require(CorePackages.Roact)
local ShareGame = RobloxGui.Modules.Settings.Pages.ShareGame
local Immutable = require(AppTempCommon.Common.Immutable)
local Constants = require(ShareGame.Constants)
local RectangleButton = require(ShareGame.Components.RectangleButton)
local INVITE_TEXT_FONT = Enum.Font.SourceSansSemibold
local INVITE_TEXT_SIZE = 19
local InviteStatus = Constants.InviteStatus
local INVITE_STATUS_TEXT = {
[InviteStatus.Success] = "Feature.SettingsHub.Label.Invited",
[InviteStatus.Moderated] = "Feature.SettingsHub.Label.Moderated",
[InviteStatus.Pending] = "Feature.SettingsHub.Label.Sending",
}
local InviteButton = Roact.PureComponent:extend("InviteButton")
function InviteButton:render()
local anchorPoint = self.props.anchorPoint
local position = self.props.position
local size = self.props.size
local layoutOrder = self.props.layoutOrder
local zIndex = self.props.zIndex
local onInvite = self.props.onInvite
local inviteStatus = self.props.inviteStatus
if not inviteStatus or inviteStatus == InviteStatus.Failed then
local buttonProps = Immutable.Set(self.props, "onClick", function()
onInvite()
end)
return Roact.createElement(RectangleButton, buttonProps, {
InviteText = Roact.createElement("TextLabel", {
BackgroundTransparency = 1,
Position = UDim2.new(0.5, 0, 0.5, 0),
Font = INVITE_TEXT_FONT,
Text = RobloxTranslator:FormatByKey("Feature.SettingsHub.Action.InviteFriend"),
TextSize = INVITE_TEXT_SIZE,
TextColor3 = Constants.Color.WHITE,
ZIndex = zIndex,
})
})
else
local inviteText = INVITE_STATUS_TEXT[inviteStatus]
return Roact.createElement("TextLabel", {
BackgroundTransparency = 1,
AnchorPoint = anchorPoint,
Position = position,
Size = size,
Font = INVITE_TEXT_FONT,
Text = RobloxTranslator:FormatByKey(inviteText),
TextSize = INVITE_TEXT_SIZE,
TextColor3 = Constants.Color.WHITE,
LayoutOrder = layoutOrder,
ZIndex = zIndex,
})
end
end
return InviteButton
@@ -0,0 +1,134 @@
local CorePackages = game:GetService("CorePackages")
local AppTempCommon = CorePackages.AppTempCommon
local Modules = game:GetService("CoreGui").RobloxGui.Modules
local UserInputService = game:GetService("UserInputService")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local ShareGame = Modules.Settings.Pages.ShareGame
local Constants = require(ShareGame.Constants)
local SetDeviceOrientation = require(AppTempCommon.LuaApp.Actions.SetDeviceOrientation)
local SetIsSmallTouchScreen = require(ShareGame.Actions.SetIsSmallTouchScreen)
local SetDeviceLayout = require(ShareGame.Actions.SetDeviceLayout)
-- Magic values derived from CoreScripts/Modules/Settings/Utility.lua
local SMALL_DEVICE_HEIGHT = 500
local SMALL_DEVICE_WIDTH = 700
local LayoutProvider = Roact.Component:extend("LayoutProvider")
function LayoutProvider:didMount()
if workspace.CurrentCamera then
self:setObservedCamera(workspace.CurrentCamera)
end
self.cameraChangedListener = workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(function()
if workspace.CurrentCamera then
self:setObservedCamera(workspace.CurrentCamera)
end
end)
end
function LayoutProvider:willUnmount()
if self.cameraChangedListener then
self.cameraChangedListener:Disconnect()
end
if self.viewportSizeListener then
self.viewportSizeListener:Disconnect()
end
end
function LayoutProvider:setObservedCamera(camera)
if self.viewportSizeListener then
self.viewportSizeListener:Disconnect()
end
-- Listen for changes to ViewportSize and update DeviceInfo accordingly
self:checkAllDeviceInfo(camera.ViewportSize)
self.viewportSizeListener = camera:GetPropertyChangedSignal("ViewportSize"):Connect(function()
-- Hacky code awaits underlying mechanism fix.
-- Viewport will get a 0,0,1,1 rect before it is properly set.
local viewportSize = camera.ViewportSize
if viewportSize.X <= 1 or viewportSize.Y <= 1 then
return
end
self:checkAllDeviceInfo(viewportSize)
end)
end
function LayoutProvider:checkDeviceOrientation(viewportSize)
local deviceOrientation = viewportSize.X > viewportSize.Y and
Constants.DeviceOrientation.LANDSCAPE or Constants.DeviceOrientation.PORTRAIT
if self._deviceOrientation ~= deviceOrientation then
self._deviceOrientation = deviceOrientation
self.props.setDeviceOrientation(deviceOrientation)
end
end
function LayoutProvider:checkDeviceIsSmallTouchScreen(viewportSize)
local isSmallTouchScreen = UserInputService.TouchEnabled and
(viewportSize.X < SMALL_DEVICE_WIDTH or viewportSize.Y < SMALL_DEVICE_HEIGHT)
if self._isSmallTouchScreen ~= isSmallTouchScreen then
self._isSmallTouchScreen = isSmallTouchScreen
self.props.setIsSmallTouchScreen(self._isSmallTouchScreen)
end
end
function LayoutProvider:checkDeviceLayout()
local deviceLayout
if self._isSmallTouchScreen then
if self._deviceOrientation == Constants.DeviceOrientation.LANDSCAPE then
deviceLayout = Constants.DeviceLayout.PHONE_LANDSCAPE
else
deviceLayout = Constants.DeviceLayout.PHONE_PORTRAIT
end
elseif UserInputService.TouchEnabled then
if self._deviceOrientation == Constants.DeviceOrientation.LANDSCAPE then
deviceLayout = Constants.DeviceLayout.TABLET_LANDSCAPE
else
deviceLayout = Constants.DeviceLayout.TABLET_PORTRAIT
end
else
deviceLayout = Constants.DeviceLayout.DESKTOP
end
if self._deviceLayout ~= deviceLayout then
self._deviceLayout = deviceLayout
self.props.setDeviceLayout(self._deviceLayout)
end
end
function LayoutProvider:checkAllDeviceInfo(viewportSize)
self:checkDeviceOrientation(viewportSize)
self:checkDeviceIsSmallTouchScreen(viewportSize)
self:checkDeviceLayout()
end
function LayoutProvider:render()
return Roact.oneChild(self.props[Roact.Children])
end
-- TODO: Update to use RoactRodux.UNSTABLE_connect2
local connector = RoactRodux.connect(function(store)
return {
setDeviceOrientation = function(orientation)
return store:dispatch(SetDeviceOrientation(orientation))
end,
setIsSmallTouchScreen = function(isSmall)
return store:dispatch(SetIsSmallTouchScreen(isSmall))
end,
setDeviceLayout = function(deviceLayout)
return store:dispatch(SetDeviceLayout(deviceLayout))
end,
}
end)
return connector(LayoutProvider)
@@ -0,0 +1,53 @@
local CorePackages = game:GetService("CorePackages")
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local LoadingBar = require(CorePackages.AppTempCommon.LuaApp.Components.LoadingBar)
local ShareGame = RobloxGui.Modules.Settings.Pages.ShareGame
local Roact = require(CorePackages.Roact)
local Constants = require(ShareGame.Constants)
local LoadingFriendsPage = Roact.PureComponent:extend("LoadingFriendsPage")
local LOADING_INDICATOR_WIDTH = 70
local LOADING_INDICATOR_HEIGHT = 16
function LoadingFriendsPage:render()
local layoutOrder = self.props.LayoutOrder
local zIndex = self.props.ZIndex
local scale = 1
local incrementingLayoutOrder = 0
local function incrementLayoutOrder()
incrementingLayoutOrder = incrementingLayoutOrder + 1
return incrementingLayoutOrder
end
return Roact.createElement("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 1, 0),
LayoutOrder = layoutOrder,
ZIndex = zIndex,
}, {
listLayout = Roact.createElement("UIListLayout", {
FillDirection = Enum.FillDirection.Vertical,
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Center,
SortOrder = Enum.SortOrder.LayoutOrder,
}),
loadingIndicator = Roact.createElement("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(0, scale * LOADING_INDICATOR_WIDTH, 0, scale * LOADING_INDICATOR_HEIGHT),
LayoutOrder = incrementLayoutOrder(),
ZIndex = zIndex,
}, {
loadingBar = Roact.createElement(LoadingBar, {
ZIndex = zIndex,
}),
})
})
end
return LoadingFriendsPage
@@ -0,0 +1,79 @@
local CorePackages = game:GetService("CorePackages")
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local RobloxTranslator = require(RobloxGui.Modules.RobloxTranslator)
local ShareGame = RobloxGui.Modules.Settings.Pages.ShareGame
local Roact = require(CorePackages.Roact)
local Constants = require(ShareGame.Constants)
local ShareGameIcons = require(ShareGame.Spritesheets.ShareGameIcons)
local FRIENDS_ICON_FRAME = ShareGameIcons:GetFrame("friends")
local SHARE_GAME_ICONS_IMAGE = ShareGameIcons:GetImagePath()
local ICON_TO_SUBTITLE_PADDING = 34
local NoFriendsPage = Roact.PureComponent:extend("NoFriendsPage")
function NoFriendsPage:render()
local layoutOrder = self.props.LayoutOrder
local zIndex = self.props.ZIndex
local incrementingLayoutOrder = 0
local function incrementLayoutOrder()
incrementingLayoutOrder = incrementingLayoutOrder + 1
return incrementingLayoutOrder
end
return Roact.createElement("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 1, 0),
LayoutOrder = layoutOrder,
ZIndex = zIndex,
}, {
listLayout = Roact.createElement("UIListLayout", {
FillDirection = Enum.FillDirection.Vertical,
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Center,
SortOrder = Enum.SortOrder.LayoutOrder,
}),
friendsIcon = Roact.createElement("ImageLabel", {
BackgroundTransparency = 1,
Image = SHARE_GAME_ICONS_IMAGE,
ImageRectOffset = FRIENDS_ICON_FRAME.offset,
ImageRectSize = FRIENDS_ICON_FRAME.size,
Size = UDim2.new(0, 72, 0, 72),
LayoutOrder = incrementLayoutOrder(),
ZIndex = zIndex,
}),
iconToSubtitleSpacer = Roact.createElement("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(0, 0, 0, ICON_TO_SUBTITLE_PADDING),
LayoutOrder = incrementLayoutOrder(),
ZIndex = zIndex,
}),
subtitle = Roact.createElement("TextLabel", {
BackgroundTransparency = 1,
Text = RobloxTranslator:FormatByKey("Feature.SettingsHub.Label.NoFriendsScreen"),
TextColor3 = Constants.Color.GRAY5,
TextTransparency = 0.22,
TextSize = 21,
TextWrapped = true,
Font = Enum.Font.SourceSans,
LayoutOrder = incrementLayoutOrder(),
Size = UDim2.new(0, 280, 0, 42),
TextYAlignment = Enum.TextYAlignment.Top,
ZIndex = zIndex,
}),
bottomSpacer = Roact.createElement("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(0, 0, 0, 120),
LayoutOrder = incrementLayoutOrder(),
ZIndex = zIndex,
}),
})
end
return NoFriendsPage
@@ -0,0 +1,86 @@
local CorePackages = game:GetService("CorePackages")
local UserInputService = game:GetService("UserInputService")
local Roact = require(CorePackages.Roact)
local BUTTON_IMAGE = "rbxasset://textures/ui/Settings/MenuBarAssets/MenuButton.png"
local BUTTON_IMAGE_ACTIVE = "rbxasset://textures/ui/Settings/MenuBarAssets/MenuButtonSelected.png"
local BUTTON_SLICE = Rect.new(8, 6, 46, 44)
local DROPSHADOW_SIZE = {
Left = 4, Right = 4,
Top = 2, Bottom = 6,
}
local RectangleButton = Roact.PureComponent:extend("RectangleButton")
function RectangleButton:init()
self.state = {
isHovering = false,
}
end
function RectangleButton:render()
local size = self.props.size
local position = self.props.position
local anchorPoint = self.props.anchorPoint
local layoutOrder = self.props.layoutOrder
local zIndex = self.props.zIndex
local onClick = self.props.onClick
local children = self.props[Roact.Children] or {}
local buttonImage = self.state.isHovering and BUTTON_IMAGE_ACTIVE or BUTTON_IMAGE
-- Insert padding so that child elements of this component are positioned
-- inside the button as expected. This is to offset the dropshadow
-- extending outside the button bounds.
children["UIPadding"] = Roact.createElement("UIPadding", {
PaddingLeft = UDim.new(0, DROPSHADOW_SIZE.Left),
PaddingRight = UDim.new(0, DROPSHADOW_SIZE.Right),
PaddingTop = UDim.new(0, DROPSHADOW_SIZE.Top),
PaddingBottom = UDim.new(0, DROPSHADOW_SIZE.Bottom),
})
return Roact.createElement("ImageButton", {
BackgroundTransparency = 1,
Image = "",
Size = size,
Position = position,
AnchorPoint = anchorPoint,
LayoutOrder = layoutOrder,
ZIndex = zIndex,
[Roact.Event.InputBegan] = function()
self:setState({isHovering = true})
end,
[Roact.Event.InputEnded] = function()
self:setState({isHovering = false})
end,
[Roact.Event.Activated] = function()
if onClick then
self:setState({isHovering = false})
onClick()
end
end,
}, {
ButtonBackground = Roact.createElement("ImageLabel", {
BackgroundTransparency = 1,
Position = UDim2.new(
0, -DROPSHADOW_SIZE.Left,
0, -DROPSHADOW_SIZE.Top
),
Size = UDim2.new(
1, DROPSHADOW_SIZE.Left + DROPSHADOW_SIZE.Right,
1, DROPSHADOW_SIZE.Top + DROPSHADOW_SIZE.Bottom
),
Image = buttonImage,
ScaleType = Enum.ScaleType.Slice,
SliceCenter = BUTTON_SLICE,
ZIndex = zIndex,
}, children),
})
end
return RectangleButton
@@ -0,0 +1,199 @@
local CorePackages = game:GetService("CorePackages")
local AppTempCommon = CorePackages.AppTempCommon
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local ShareGame = RobloxGui.Modules.Settings.Pages.ShareGame
local SearchBox = require(ShareGame.Components.SearchBox)
local IconButton = require(ShareGame.Components.IconButton)
local Constants = require(ShareGame.Constants)
local Text = require(AppTempCommon.Common.Text)
local RobloxTranslator = require(RobloxGui.Modules.RobloxTranslator)
local SetSearchAreaActive = require(ShareGame.Actions.SetSearchAreaActive)
local SetSearchText = require(ShareGame.Actions.SetSearchText)
local ShareGameIcons = require(RobloxGui.Modules.Settings.Pages.ShareGame.Spritesheets.ShareGameIcons)
local SEARCH_ICON_SPRITE_PATH = ShareGameIcons:GetImagePath()
local SEARCH_ICON_SPRITE_FRAME = ShareGameIcons:GetFrame("search_large")
local SEARCH_ICON_SIZE = 44
local SEARCH_BOX_WIDTH = 177
local SEARCH_BOX_HEIGHT = 28
local CANCEL_TEXT_FONT = Enum.Font.SourceSans
local CANCEL_TEXT_COLOR = Constants.Color.GRAY3
local CANCEL_TEXT_SIZE = 20
local SearchArea = Roact.PureComponent:extend("SearchArea")
function SearchArea:init()
self.state = {
cancelText = RobloxTranslator:FormatByKey("Feature.SettingsHub.Action.CancelSearch"),
}
self.searchField = nil
end
function SearchArea:render()
local fullWidthSearchBar = self.props.fullWidthSearchBar
local searchBoxMargin = self.props.searchBoxMargin
local anchorPoint = self.props.anchorPoint
local position = self.props.position
local zIndex = self.props.zIndex
local searchAreaActive = self.props.searchAreaActive
local cancelText = self.state.cancelText
local cancelTextWidth = Text.GetTextWidth(cancelText, CANCEL_TEXT_FONT, CANCEL_TEXT_SIZE)
local searchBoxSizeOffset = searchBoxMargin + cancelTextWidth
local setSearchText = self.props.setSearchText
if fullWidthSearchBar then
-- When full-width is true, the search box spans the entire width of the
-- parent frame, and it becomes hidden behind a search button.
return Roact.createElement("Frame", {
AnchorPoint = anchorPoint,
Position = position,
Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1,
ZIndex = zIndex,
}, {
-- Render the search button if the search area hasn't been activated
-- yet.
SearchButton = Roact.createElement(IconButton, {
visible = not searchAreaActive,
anchorPoint = anchorPoint,
position = position,
size = UDim2.new(0, SEARCH_ICON_SIZE, 0, SEARCH_ICON_SIZE),
zIndex = zIndex,
iconHorizontalAlignment = Enum.HorizontalAlignment.Right,
iconSpritePath = SEARCH_ICON_SPRITE_PATH,
iconSpriteFrame = SEARCH_ICON_SPRITE_FRAME,
onClick = function(rbx)
self.props.setSearchAreaActive(true)
end,
}),
-- Show search box with cancel button if the search area is active
SearchBox = Roact.createElement(SearchBox, {
anchorPoint = Vector2.new(0, 0.5),
position = UDim2.new(0, 0, 0.5, 0),
size = UDim2.new(1, -searchBoxSizeOffset, 0, SEARCH_BOX_HEIGHT),
zIndex = zIndex,
visible = searchAreaActive,
modalFocused = searchAreaActive,
onTextChanged = setSearchText,
onTextBoxFocusLost = function(text)
if text == "" then
self.props.setSearchAreaActive(false)
end
end,
searchFieldRef = function(rbx)
self.searchField = rbx
end,
}),
Cancel = Roact.createElement("TextButton", {
BackgroundTransparency = 1,
AnchorPoint = Vector2.new(1, 0.5),
Position = UDim2.new(1, 0, 0.5, 0),
Size = UDim2.new(0, cancelTextWidth, 1, 0),
TextSize = CANCEL_TEXT_SIZE,
TextColor3 = CANCEL_TEXT_COLOR,
Font = CANCEL_TEXT_FONT,
ZIndex = zIndex,
Visible = searchAreaActive,
[Roact.Ref] = function(rbx)
if rbx then
-- Set initial text on this TextLabel
rbx.Text = cancelText
-- Listen to Text changed events in case of localization
-- (this is so we can trigger a resize)
if not self.textConnection then
local textChangedSignal = rbx:GetPropertyChangedSignal("Text")
self.textConnection = textChangedSignal:connect(function()
self:setState({
cancelText = rbx.Text
})
end)
end
end
end,
[Roact.Event.Activated] = function(rbx)
self.props.setSearchAreaActive(false)
end,
}),
})
else
-- Show a search box of a fixed size without any added behavior.
-- We don't bother setting SearchAreaActive here since we're not working
-- with a modal-style search box (i.e. nothing has to be shown or
-- hidden).
return Roact.createElement(SearchBox, {
anchorPoint = Vector2.new(1, 0.5),
position = UDim2.new(1, 0, 0.5, 0),
size = UDim2.new(0, SEARCH_BOX_WIDTH, 0, SEARCH_BOX_HEIGHT),
zIndex = zIndex,
onTextChanged = setSearchText,
searchFieldRef = function(rbx)
self.searchField = rbx
end,
})
end
end
function SearchArea:didUpdate(prevProps)
local fullWidthSearchBar = self.props.fullWidthSearchBar
if self.searchField then
-- Check if the page was closed so we can reset the search query
if not self.props.isPageOpen and prevProps.isPageOpen then
-- Spawn new thread so we don't trigger state updates from this
-- function
spawn(function()
-- Reset the entire search area
if fullWidthSearchBar then
self.props.setSearchAreaActive(false)
else
self.searchField.Text = ""
end
end)
end
-- Check if the search area has become active/inactive
if fullWidthSearchBar then
local searchAreaActive = self.props.searchAreaActive
local wasActive = prevProps.searchAreaActive
if searchAreaActive and not wasActive then
self.searchField:CaptureFocus()
elseif not searchAreaActive and wasActive then
self.searchField.Text = ""
end
end
end
end
-- TODO: Update to use RoactRodux.UNSTABLE_connect2
SearchArea = RoactRodux.connect(function(store)
local state = store:getState()
return {
isPageOpen = state.Page.IsOpen,
searchAreaActive = state.ConversationsSearch.SearchAreaActive,
setSearchAreaActive = function(isActive)
store:dispatch(SetSearchAreaActive(isActive))
end,
setSearchText = function(text)
store:dispatch(SetSearchText(text))
end,
}
end)(SearchArea)
return SearchArea
@@ -0,0 +1,155 @@
local CorePackages = game:GetService("CorePackages")
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local RobloxTranslator = require(RobloxGui.Modules.RobloxTranslator)
local Roact = require(CorePackages.Roact)
local Constants = require(RobloxGui.Modules.Settings.Pages.ShareGame.Constants)
local SearchBox = Roact.Component:extend("SearchBox")
local ShareGameIcons = require(RobloxGui.Modules.Settings.Pages.ShareGame.Spritesheets.ShareGameIcons)
local SHARE_GAME_SPRITE_PATH = ShareGameIcons:GetImagePath()
local SEARCH_BORDER_SPRITE_IMAGE = SHARE_GAME_SPRITE_PATH
local SEARCH_BORDER_SPRITE_FRAME = ShareGameIcons:GetFrame("search_border")
local SEARCH_BORDER_SLICE = Rect.new(3, 3, 4, 4)
local SEARCH_ICON_SPRITE_IMAGE = SHARE_GAME_SPRITE_PATH
local SEARCH_ICON_SPRITE_FRAME = ShareGameIcons:GetFrame("search_small")
local SEARCH_ICON_SIZE = 16
local CLEAR_ICON_SPRITE_IMAGE = SHARE_GAME_SPRITE_PATH
local CLEAR_ICON_SPRITE_FRAME = ShareGameIcons:GetFrame("clear")
local CLEAR_ICON_SIZE = 16
local SEARCH_MARGINS_HORIZONTAL = 8
local SEARCH_MARGINS_VERTICAL = 6
local SEARCH_FIELD_MARGINS = 12
local SEARCH_BOX_TEXT_SIZE = 16
function SearchBox:init()
self.state = {
isTextWritten = false,
}
self.searchField = nil
end
function SearchBox:render()
local anchorPoint = self.props.anchorPoint
local onTextBoxFocusLost = self.props.onTextBoxFocusLost
local position = self.props.position
local searchFieldRef = self.props.searchFieldRef
local size = self.props.size
local visible = self.props.visible
local zIndex = self.props.zIndex
local isTextWritten = self.state.isTextWritten
return Roact.createElement("ImageLabel", {
BackgroundTransparency = 1,
AnchorPoint = anchorPoint,
Position = position,
Size = size,
Visible = visible,
Image = SEARCH_BORDER_SPRITE_IMAGE,
ImageRectOffset = SEARCH_BORDER_SPRITE_FRAME.offset,
ImageRectSize = SEARCH_BORDER_SPRITE_FRAME.size,
ScaleType = Enum.ScaleType.Slice,
SliceCenter = SEARCH_BORDER_SLICE,
ZIndex = zIndex,
}, {
UIPadding = Roact.createElement("UIPadding", {
PaddingLeft = UDim.new(0, SEARCH_MARGINS_HORIZONTAL),
PaddingRight = UDim.new(0, SEARCH_MARGINS_HORIZONTAL),
PaddingTop = UDim.new(0, SEARCH_MARGINS_VERTICAL),
PaddingBottom = UDim.new(0, SEARCH_MARGINS_VERTICAL),
}),
SearchIcon = Roact.createElement("ImageLabel", {
BackgroundTransparency = 1,
Image = SEARCH_ICON_SPRITE_IMAGE,
ImageRectOffset = SEARCH_ICON_SPRITE_FRAME.offset,
ImageRectSize = SEARCH_ICON_SPRITE_FRAME.size,
Size = UDim2.new(0, SEARCH_ICON_SIZE, 0, SEARCH_ICON_SIZE),
ZIndex = zIndex
}),
SearchField = Roact.createElement("TextBox", {
BackgroundTransparency = 1,
AnchorPoint = Vector2.new(0, 0.5),
Position = UDim2.new(0, SEARCH_ICON_SIZE + SEARCH_FIELD_MARGINS, 0.5, 0),
Size = UDim2.new(
1, -(SEARCH_ICON_SIZE + CLEAR_ICON_SIZE + SEARCH_FIELD_MARGINS * 2),
1, 0
),
ClearTextOnFocus = false,
PlaceholderColor3 = Constants.Color.GREY3,
PlaceholderText = RobloxTranslator:FormatByKey("Feature.SettingsHub.Label.SearchForFriendsPlaceholder"),
Text = "",
TextColor3 = Constants.Color.WHITE,
TextWrapped = true,
TextXAlignment = Enum.TextXAlignment.Left,
TextSize = SEARCH_BOX_TEXT_SIZE,
Font = Enum.Font.SourceSans,
ZIndex = zIndex,
[Roact.Ref] = function(rbx)
self.searchField = rbx
-- Check if a higher reference function has been passed in and
-- then call it.
if searchFieldRef then
searchFieldRef(rbx)
end
end,
[Roact.Event.FocusLost] = function(rbx)
if onTextBoxFocusLost then
onTextBoxFocusLost(rbx.Text)
end
end
}),
ClearButton = Roact.createElement("ImageButton", {
BackgroundTransparency = 1,
AnchorPoint = Vector2.new(1, 0),
Position = UDim2.new(1, 0, 0, 0),
Size = UDim2.new(0, CLEAR_ICON_SIZE, 0, SEARCH_ICON_SIZE),
Image = CLEAR_ICON_SPRITE_IMAGE,
ImageRectOffset = CLEAR_ICON_SPRITE_FRAME.offset,
ImageRectSize = CLEAR_ICON_SPRITE_FRAME.size,
ZIndex = zIndex,
Visible = isTextWritten,
[Roact.Event.Activated] = function()
if self.searchField then
self.searchField.Text = ""
-- We lost focus when clicking this button so we have to
-- recapture it.
self.searchField:CaptureFocus()
end
end
})
})
end
function SearchBox:didMount()
local searchField = self.searchField
local onTextChanged = self.props.onTextChanged
if searchField then
self.textBoxChangedConnection = searchField:GetPropertyChangedSignal("Text"):Connect(function()
local text = searchField.Text
self:setState({
isTextWritten = text:len() > 0
})
onTextChanged(text)
end)
end
end
function SearchBox:willUnmount()
if self.textBoxChangedConnection then
self.textBoxChangedConnection:Disconnect()
end
end
return SearchBox
@@ -0,0 +1,102 @@
--[[
Container for both the Share Game Page contents and header
]]
local CoreGui = game:GetService("CoreGui")
local CorePackages = game:GetService("CorePackages")
local HttpRbxApiService = game:GetService("HttpRbxApiService")
local Players = game:GetService("Players")
local AppTempCommon = CorePackages.AppTempCommon
local Modules = CoreGui.RobloxGui.Modules
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local httpRequest = require(AppTempCommon.Temp.httpRequest)
local ApiFetchUsersFriends = require(AppTempCommon.LuaApp.Thunks.ApiFetchUsersFriends)
local RetrievalStatus = require(CorePackages.AppTempCommon.LuaApp.Enum.RetrievalStatus)
local ShareGame = Modules.Settings.Pages.ShareGame
local Header = require(ShareGame.Components.Header)
local ConversationList = require(ShareGame.Components.ConversationList)
local Constants = require(ShareGame.Constants)
local ClosePage = require(ShareGame.Actions.ClosePage)
local USER_LIST_PADDING = 10
local ShareGamePageFrame = Roact.PureComponent:extend("ShareGamePageFrame")
local ToasterComponent = require(ShareGame.Components.ErrorToaster)
function ShareGamePageFrame:init()
self.props.reFetch()
end
function ShareGamePageFrame:render()
local deviceLayout = self.props.deviceLayout
local zIndex = self.props.zIndex
local closePage = self.props.closePage
local searchAreaActive = self.props.searchAreaActive
local searchText = self.props.searchText
local layoutSpecific = Constants.LayoutSpecific[deviceLayout]
local headerHeight = layoutSpecific.HEADER_HEIGHT
return Roact.createElement("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 1, 0),
Position = UDim2.new(0, 0, 0, 0),
ZIndex = zIndex,
}, {
toasterPortal = Roact.createElement(Roact.Portal, {
target = CoreGui,
}, {
Toaster = Roact.createElement(ToasterComponent),
}),
Header = Roact.createElement(Header, {
deviceLayout = deviceLayout,
size = UDim2.new(1, 0, 0, headerHeight),
position = UDim2.new(0, 0, 0, -headerHeight),
layoutOrder = 0,
zIndex = zIndex,
closePage = closePage,
searchAreaActive = searchAreaActive,
}),
ConversationList = Roact.createElement(ConversationList, {
size = UDim2.new(1, 0, 1, layoutSpecific.EXTEND_BOTTOM_SIZE - USER_LIST_PADDING),
topPadding = USER_LIST_PADDING,
layoutOrder = 1,
zIndex = zIndex,
searchText = searchText,
}),
})
end
-- TODO: Update to use RoactRodux.UNSTABLE_connect2
ShareGamePageFrame = RoactRodux.connect(function(store)
local state = store:getState()
return {
deviceLayout = state.DeviceInfo.DeviceLayout,
searchAreaActive = state.ConversationsSearch.SearchAreaActive,
searchText = state.ConversationsSearch.SearchText,
closePage = function()
store:dispatch(ClosePage(Constants.PageRoute.SHARE_GAME))
end,
reFetch = function()
local userId = tostring(Players.LocalPlayer.UserId)
local friendsRetrievalStatus = state.Friends.retrievalStatus[userId]
if friendsRetrievalStatus ~= RetrievalStatus.Fetching then
local networkImpl = httpRequest(HttpRbxApiService)
store:dispatch(ApiFetchUsersFriends(networkImpl, userId, Constants.ThumbnailRequest.InviteToGame))
end
end
}
end)(ShareGamePageFrame)
return ShareGamePageFrame
@@ -0,0 +1,146 @@
local CorePackages = game:GetService("CorePackages")
local AppTempCommon = CorePackages.AppTempCommon
local User = require(AppTempCommon.LuaApp.Models.User)
local ThumbnailRequest = require(AppTempCommon.LuaApp.Models.ThumbnailRequest)
local FStringSettingsHubInviteToGameThumbnailType = settings():GetFVariable("SettingsHubInviteToGameThumbnailType")
local FStringSettingsHubInviteToGameThumbnailSize = settings():GetFVariable("SettingsHubInviteToGameThumbnailSize")
local DeviceLayout = {
PHONE_PORTRAIT = "PHONE_PORTRAIT",
PHONE_LANDSCAPE = "PHONE_LANDSCAPE",
TABLET_PORTRAIT = "TABLET_PORTRAIT",
TABLET_LANDSCAPE = "TABLET_LANDSCAPE",
DESKTOP = "DESKTOP",
}
local Color = {
WHITE = Color3.fromRGB(255, 255, 255),
GRAY1 = Color3.fromRGB(25, 25, 25),
GRAY2 = Color3.fromRGB(117, 117, 117),
GRAY3 = Color3.fromRGB(184, 184, 184),
GRAY4 = Color3.fromRGB(227, 227, 227),
GRAY5 = Color3.fromRGB(242, 242, 242),
GRAY6 = Color3.fromRGB(245, 245, 245),
RED = Color3.fromRGB(254, 68, 72),
}
local Constants = {
Color = Color,
PresenceColors = {
[User.PresenceType.ONLINE] = Color3.fromRGB(0, 162, 255),
[User.PresenceType.IN_GAME] = Color3.fromRGB(2, 183, 87),
[User.PresenceType.IN_STUDIO] = Color3.fromRGB(246, 136, 2),
[User.PresenceType.OFFLINE] = Color.GRAY3,
},
PresenceTextKey = {
[User.PresenceType.ONLINE] = "Common.Presence.Label.Online",
[User.PresenceType.IN_GAME] = "Common.Presence.Label.InGame",
[User.PresenceType.IN_STUDIO] = "Common.Presence.Label.InStudio",
[User.PresenceType.OFFLINE] = "Common.Presence.Label.Offline",
},
InviteStatus = {
Success = "Success", -- Should match API resultType
Moderated = "Moderated", -- Should match API resultType
Failed = "Failed",
Pending = "Pending",
},
DeviceOrientation = {
PORTRAIT = "PORTRAIT",
LANDSCAPE = "LANDSCAPE",
INVALID = "INVALID",
},
PageRoute = {
NONE = "NONE",
SETTINGS_HUB = "SETTINGS_HUB",
SHARE_GAME = "SHARE_GAME",
},
AvatarThumbnailTypes = {
AvatarThumbnail = "AvatarThumbnail",
HeadShot = "HeadShot",
},
AvatarThumbnailSizes = {
Size48x48 = "Size48x48",
Size60x60 = "Size60x60",
Size100x100 = "Size100x100",
Size150x150 = "Size150x150",
Size352x352 = "Size352x352",
},
SHARE_GAME_Z_INDEX = 2,
--[[
Used for determining how the ShareGame page will be rendered across
devices.
]]
DeviceLayout = DeviceLayout,
LayoutSpecific = {
[DeviceLayout.PHONE_LANDSCAPE] = {
HEADER_HEIGHT = 40,
PAGE_TITLE_TEXT_SIZE = 23,
SEARCH_BOX_MARGIN = 12,
PAGE_SIDE_MARGINS = 7,
BACK_BUTTON_HEIGHT = 44,
BACK_BUTTON_WIDTH = 44,
EXTEND_BOTTOM_SIZE = 0,
TOAST_HEIGHT = 40,
},
[DeviceLayout.PHONE_PORTRAIT] = {
HEADER_HEIGHT = 40,
PAGE_TITLE_TEXT_SIZE = 23,
SEARCH_BOX_MARGIN = 15,
PAGE_SIDE_MARGINS = 5,
BACK_BUTTON_HEIGHT = 44,
BACK_BUTTON_WIDTH = 44,
EXTEND_BOTTOM_SIZE = 0,
TOAST_HEIGHT = 40,
},
[DeviceLayout.TABLET_PORTRAIT] = {
HEADER_HEIGHT = 40,
PAGE_TITLE_TEXT_SIZE = 23,
SEARCH_BOX_MARGIN = 15,
PAGE_SIDE_MARGINS = 15,
BACK_BUTTON_HEIGHT = 44,
BACK_BUTTON_WIDTH = 44,
EXTEND_BOTTOM_SIZE = 0,
TOAST_HEIGHT = 80,
},
[DeviceLayout.TABLET_LANDSCAPE] = {
HEADER_HEIGHT = 60,
PAGE_TITLE_TEXT_SIZE = 23,
SEARCH_BOX_MARGIN = 15,
PAGE_SIDE_MARGINS = 5,
BACK_BUTTON_HEIGHT = 44,
BACK_BUTTON_WIDTH = 44,
EXTEND_BOTTOM_SIZE = 68,
TOAST_HEIGHT = 80,
},
[DeviceLayout.DESKTOP] = {
HEADER_HEIGHT = 60,
PAGE_TITLE_TEXT_SIZE = 29,
SEARCH_BOX_MARGIN = 0,
PAGE_SIDE_MARGINS = 0,
BACK_BUTTON_HEIGHT = 44,
BACK_BUTTON_WIDTH = 154,
EXTEND_BOTTOM_SIZE = 68,
TOAST_HEIGHT = 80,
},
},
}
Constants.InviteAvatarThumbnailType = Constants.AvatarThumbnailTypes[FStringSettingsHubInviteToGameThumbnailType]
or Constants.AvatarThumbnailTypes.HeadShot
Constants.InviteAvatarThumbnailSize = Constants.AvatarThumbnailSizes[FStringSettingsHubInviteToGameThumbnailSize]
or Constants.AvatarThumbnailSizes.Size100x100
Constants.ThumbnailRequest = {
InviteToGame = {ThumbnailRequest.fromData(
Constants.InviteAvatarThumbnailType,
Constants.InviteAvatarThumbnailSize
)},
}
return Constants
@@ -0,0 +1,45 @@
local Players = game:GetService("Players")
local FlagSettings = {}
local FFlagSettingsHubInviteToGameWindows = settings():GetFVariable("SettingsHubInviteToGameWindows5")
local FFlagSettingsHubInviteToGameOSX = settings():GetFVariable("SettingsHubInviteToGameOSX5")
local FFlagSettingsHubInviteToGameIOS = settings():GetFVariable("SettingsHubInviteToGameIOS5")
local FFlagSettingsHubInviteToGameAndroid = settings():GetFVariable("SettingsHubInviteToGameAndroid5")
local FFlagSettingsHubInviteToGameUWP = settings():GetFVariable("SettingsHubInviteToGameUWP5")
-- Helper function to throttle based on player Id:
function FlagSettings.ThrottleUserId(throttle, userId)
assert(type(throttle) == "number")
assert(type(userId) == "number")
-- Determine userRollout using last two digits of user ID:
-- (+1 to change range from 0-99 to 1-100 as 0 is off, 100 is full on):
local userRollout = (userId % 100) + 1
return userRollout <= throttle
end
function FlagSettings.IsShareGamePageEnabledByPlatform(platform)
local throttle
if platform == Enum.Platform.Windows then
throttle = FFlagSettingsHubInviteToGameWindows
elseif platform == Enum.Platform.OSX then
throttle = FFlagSettingsHubInviteToGameOSX
elseif platform == Enum.Platform.IOS then
throttle = FFlagSettingsHubInviteToGameIOS
elseif platform == Enum.Platform.Android then
throttle = FFlagSettingsHubInviteToGameAndroid
elseif platform == Enum.Platform.UWP then
throttle = FFlagSettingsHubInviteToGameUWP
end
local throttleNumber = tonumber(throttle)
if not throttleNumber then
return false
end
local userId = Players.LocalPlayer.UserId
return FlagSettings.ThrottleUserId(throttleNumber, userId)
end
return FlagSettings
@@ -0,0 +1,31 @@
local CoreGui = game:GetService("CoreGui")
local CorePackages = game:GetService("CorePackages")
local AppTempCommon = CorePackages.AppTempCommon
local Modules = CoreGui.RobloxGui.Modules
local ShareGame = Modules.Settings.Pages.ShareGame
local Immutable = require(AppTempCommon.Common.Immutable)
local ClosePage = require(ShareGame.Actions.ClosePage)
local SetSearchAreaActive = require(ShareGame.Actions.SetSearchAreaActive)
local SetSearchText = require(ShareGame.Actions.SetSearchText)
local DEFAULT_STATE = {
SearchAreaActive = false,
SearchText = "",
}
return function(state, action)
state = state or DEFAULT_STATE
if action.type == SetSearchAreaActive.name then
state = Immutable.Set(state, "SearchAreaActive", action.isActive)
elseif action.type == SetSearchText.name then
state = Immutable.Set(state, "SearchText", action.searchText)
elseif action.type == ClosePage.name then
state = DEFAULT_STATE
end
return state
end
@@ -0,0 +1,30 @@
local CorePackages = game:GetService("CorePackages")
local AppTempCommon = CorePackages.AppTempCommon
local Modules = game:GetService("CoreGui").RobloxGui.Modules
local ShareGame = Modules.Settings.Pages.ShareGame
local Immutable = require(AppTempCommon.Common.Immutable)
local Constants = require(ShareGame.Constants)
local SetDeviceLayout = require(ShareGame.Actions.SetDeviceLayout)
local SetDeviceOrientation = require(AppTempCommon.LuaApp.Actions.SetDeviceOrientation)
local SetIsSmallTouchScreen = require(ShareGame.Actions.SetIsSmallTouchScreen)
return function(state, action)
state = state or {
DeviceLayout = Constants.DeviceLayout.DESKTOP,
DeviceOrientation = Constants.DeviceOrientation.INVALID,
IsSmallTouchScreen = false,
}
if action.type == SetDeviceOrientation.name then
state = Immutable.Set(state, "DeviceOrientation", action.deviceOrientation)
elseif action.type == SetDeviceLayout.name then
state = Immutable.Set(state, "DeviceLayout", action.deviceLayout)
elseif action.type == SetIsSmallTouchScreen.name then
state = Immutable.Set(state, "IsSmallTouchScreen", action.isSmallTouchScreen)
end
return state
end
@@ -0,0 +1,18 @@
local CorePackages = game:GetService("CorePackages")
local AppTempCommon = CorePackages.AppTempCommon
local Modules = game:GetService("CoreGui").RobloxGui.Modules
local ShareGame = Modules.Settings.Pages.ShareGame
local Immutable = require(AppTempCommon.Common.Immutable)
local ReceivedUserInviteStatus = require(ShareGame.Actions.ReceivedUserInviteStatus)
return function(state, action)
state = state or {}
if action.type == ReceivedUserInviteStatus.name then
state = Immutable.Set(state, action.userId, action.inviteStatus)
end
return state
end
@@ -0,0 +1,31 @@
local CorePackages = game:GetService("CorePackages")
local AppTempCommon = CorePackages.AppTempCommon
local Modules = game:GetService("CoreGui").RobloxGui.Modules
local ShareGame = Modules.Settings.Pages.ShareGame
local Immutable = require(AppTempCommon.Common.Immutable)
local Constants = require(ShareGame.Constants)
local OpenPage = require(ShareGame.Actions.OpenPage)
local ClosePage = require(ShareGame.Actions.ClosePage)
return function(state, action)
state = state or {
IsOpen = false,
Route = Constants.PageRoute.NONE,
}
if action.type == OpenPage.name then
state = Immutable.JoinDictionaries(state, {
IsOpen = true,
Route = action.route or Constants.PageRoute.NONE,
})
elseif action.type == ClosePage.name then
state = Immutable.JoinDictionaries(state, {
IsOpen = false,
Route = action.route or Constants.PageRoute.NONE,
})
end
return state
end
@@ -0,0 +1,44 @@
local CorePackages = game:GetService("CorePackages")
local AppTempCommon = CorePackages.AppTempCommon
local Modules = game:GetService("CoreGui").RobloxGui.Modules
local ShareGame = Modules.Settings.Pages.ShareGame
local Immutable = require(AppTempCommon.Common.Immutable)
local ReceivedUserInviteStatus = require(ShareGame.Actions.ReceivedUserInviteStatus)
local StoppedToastTimer = require(ShareGame.Actions.StoppedToastTimer)
local Constants = require(ShareGame.Constants)
local InviteStatus = Constants.InviteStatus
return function(state, action)
state = state or {
failedInvites = {},
}
if action.type == ReceivedUserInviteStatus.name then
local inviteStatus = action.inviteStatus
if inviteStatus == InviteStatus.Moderated
or inviteStatus == InviteStatus.Failed then
local inviteStatusModel = {
timeStamp = tick(),
userId = action.userId,
status = inviteStatus,
}
state = Immutable.JoinDictionaries(state, {
failedInvites = Immutable.JoinDictionaries(
state.failedInvites, Immutable.Append(state.failedInvites, inviteStatusModel)
),
})
end
elseif action.type == StoppedToastTimer.name then
state = Immutable.JoinDictionaries(state, {
failedInvites = {},
})
end
return state
end
@@ -0,0 +1,40 @@
local IMAGE_PATH = "rbxasset://textures/ui/Settings/ShareGame/icons.png"
local function createFrameModel(offset, size)
return {
offset = offset,
size = size,
}
end
local SHEET_MODEL = {
frames = {
back = createFrameModel(Vector2.new(2, 30), Vector2.new(24, 24)),
clear = createFrameModel(Vector2.new(6, 62), Vector2.new(16, 16)),
invite = createFrameModel(Vector2.new(2, 86), Vector2.new(24, 24)),
search_border = createFrameModel(Vector2.new(11, 11), Vector2.new(7, 7)),
search_large = createFrameModel(Vector2.new(3, 143), Vector2.new(22, 22)),
search_small = createFrameModel(Vector2.new(6, 117), Vector2.new(16, 16)),
friends = createFrameModel(Vector2.new(0, 170), Vector2.new(72, 72)),
},
}
local ShareGameIcons = {}
function ShareGameIcons:GetFrame(key)
return SHEET_MODEL.frames[key]
end
function ShareGameIcons:GetImagePath()
return IMAGE_PATH
end
function ShareGameIcons:ApplyImage(guiObject, key)
local frameModel = self:GetFrame(key)
guiObject.Image = IMAGE_PATH
guiObject.ImageRectOffset = frameModel.offset
guiObject.ImageRectSize = frameModel.size
end
return ShareGameIcons
@@ -0,0 +1,58 @@
local CorePackages = game:GetService("CorePackages")
local AppTempCommon = CorePackages.AppTempCommon
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local ShareGame = RobloxGui.Modules.Settings.Pages.ShareGame
local ApiSendGameInvite = require(AppTempCommon.LuaApp.Thunks.ApiSendGameInvite)
local ApiFetchPlaceInfos = require(AppTempCommon.LuaApp.Thunks.ApiFetchPlaceInfos)
local Promise = require(AppTempCommon.LuaApp.Promise)
local ReceivedUserInviteStatus = require(ShareGame.Actions.ReceivedUserInviteStatus)
local Constants = require(ShareGame.Constants)
local InviteStatus = Constants.InviteStatus
return function(requestImpl, userId, placeId)
return function(store)
local latestState = store:getState()
return Promise.new(function(resolve, reject)
-- Check that we haven't already invited this user
if latestState.Invites[tostring(userId)] == InviteStatus.Pending then
reject()
else
resolve()
end
end):andThen(function()
local maybePlaceInfo = latestState.PlaceInfos[placeId]
return Promise.new(function(resolve, reject)
-- Log that we've tried inviting this user
store:dispatch(ReceivedUserInviteStatus(userId, InviteStatus.Pending))
if maybePlaceInfo then
resolve(maybePlaceInfo)
else
store:dispatch(ApiFetchPlaceInfos(requestImpl, {placeId})):andThen(function(placeInfos)
if placeInfos[1] ~= nil then
resolve(placeInfos[1])
else
reject()
end
end, function()
reject()
end)
end
end):andThen(function(placeInfo)
return store:dispatch(ApiSendGameInvite(requestImpl, userId, placeInfo))
end):andThen(function(results)
store:dispatch(ReceivedUserInviteStatus(userId, results.resultType))
return results
end, function()
store:dispatch(ReceivedUserInviteStatus(userId, InviteStatus.Failed))
end)
end)
end
end
@@ -0,0 +1,64 @@
--[[
Placeholder page for SettingsHub that manages top level Roact view
This page is a container and controller for the Roact app
--]]
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local ShareGame = RobloxGui.Modules.Settings.Pages.ShareGame
local Constants = require(ShareGame.Constants)
local OpenPage = require(ShareGame.Actions.OpenPage)
local ClosePage = require(ShareGame.Actions.ClosePage)
local settingsPageFactory = require(RobloxGui.Modules.Settings.SettingsPageFactory)
local this = settingsPageFactory:CreateNewPage()
this.TabHeader = nil -- no tab for this page
this.PageListLayout.Parent = nil -- no list layout for this page
this.ShouldShowBottomBar = false
this.ShouldShowHubBar = false
this.IsPageClipped = false
this.Page.Name = "ShareGameDummy"
this.Page.Size = UDim2.new(1, 0, 0, 0)
function this:ConnectHubToApp(settingsHub, shareGameApp)
this:SetHub(settingsHub)
shareGameApp.store.changed:connect(function(state, prevState)
local page = state.Page
local wasOpen = prevState.Page.IsOpen
-- Check if the user closed the page via the Roact app.
if page.Route == Constants.PageRoute.SHARE_GAME and (wasOpen and not page.IsOpen) then
-- Close the page to sync up Settings Hub with the state change
this.HubRef:PopMenu(nil, true)
end
end)
this.Displayed.Event:Connect(function()
local state = shareGameApp.store:getState()
if not state.Page.IsOpen then
-- Tell Roact app that the page was opened via Settings Hub
shareGameApp.store:dispatch(OpenPage(Constants.PageRoute.SETTINGS_HUB))
end
end)
this.Hidden.Event:Connect(function()
-- The user closed the page via the Settings Hub (instead of
-- pressing back on the page), so we have to sync the app state up
-- with the Settings Hub action.
local state = shareGameApp.store:getState()
if state.Page.IsOpen then
shareGameApp.store:dispatch(ClosePage(Constants.PageRoute.SETTINGS_HUB))
end
end)
shareGameApp.store:dispatch(ClosePage(Constants.PageRoute.SETTINGS_HUB))
end
return this
@@ -0,0 +1,28 @@
local CorePackages = game:GetService("CorePackages")
local Settings = script.Parent.Parent
local Actions = Settings.Actions
--TODO: Currently Under Migration to CorePackages
local Immutable = require(CorePackages.AppTempCommon.Common.Immutable)
--
local SetRecentlyPlayedGames = require(Actions.SetRecentlyPlayedGames)
local Game = require(Settings.Models.Game)
return function(state, action)
state = state or {}
if action.type == SetRecentlyPlayedGames.name then
local gameSortState = state.gameSort or {}
local gameSortData = action.gameSortData
local gameSort = {}
if gameSortData then
for i in ipairs(gameSortData) do
local gameData = Game.fromGameCache(gameSortData[i])
table.insert(gameSort, gameData)
end
end
state = {
gameSort = Immutable.JoinDictionaries(gameSortState, gameSort),
}
end
return state
end
@@ -0,0 +1,18 @@
local CorePackages = game:GetService("CorePackages")
local Settings = script.Parent.Parent
local Actions = Settings.Actions
--TODO: Currently Under Migration to CorePackages
local Immutable = require(CorePackages.AppTempCommon.Common.Immutable)
--
local SetRecentlyPlayedGamesFetching = require(Actions.SetRecentlyPlayedGamesFetching)
return function(state, action)
state = state or {}
if action.type == SetRecentlyPlayedGamesFetching.name then
state = {
fetching = action.fetching,
}
end
return state
end
@@ -0,0 +1,12 @@
local Reducers = script.Parent
local RecentlyPlayedGamesFetchingStatus = require(Reducers.RecentlyPlayedGamesFetchingStatus)
local RecentlyPlayedGames = require(Reducers.RecentlyPlayedGames)
return function(state, action)
state = state or {}
return {
RecentlyPlayedGamesFetchingStatus = RecentlyPlayedGamesFetchingStatus(state.RecentlyPlayedGamesFetchingStatus, action),
RecentlyPlayedGames = RecentlyPlayedGames(state.RecentlyPlayedGames, action),
}
end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,370 @@
--[[
Filename: SettingsPageFactory.lua
Written by: jeditkacheff
Version 1.0
Description: Base Page Functionality for all Settings Pages
--]]
----------------- SERVICES ------------------------------
local GuiService = game:GetService("GuiService")
local HttpService = game:GetService("HttpService")
local UserInputService = game:GetService("UserInputService")
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
----------- UTILITIES --------------
local utility = require(RobloxGui.Modules.Settings.Utility)
local StyleWidgets = require(RobloxGui.Modules.StyleWidgets)
----------- VARIABLES --------------
RobloxGui:WaitForChild("Modules"):WaitForChild("TenFootInterface")
local isTenFootInterface = require(RobloxGui.Modules.TenFootInterface):IsEnabled()
local success, result = pcall(function() return settings():GetFFlag('UseNotificationsLocalization') end)
local FFlagUseNotificationsLocalization = success and result
----------- CONSTANTS --------------
local HEADER_SPACING = 5
if utility:IsSmallTouchScreen() then
HEADER_SPACING = 0
end
----------- CLASS DECLARATION --------------
local function Initialize()
local this = {}
this.HubRef = nil
this.LastSelectedObject = nil
this.TabPosition = 0
this.Active = false
this.OpenStateChangedCount = 0
this.ShouldShowBottomBar = true
this.ShouldShowHubBar = true
this.IsPageClipped = true
local rows = {}
local displayed = false
------ TAB CREATION -------
this.TabHeader = utility:Create'TextButton'
{
Name = "Header",
Text = "",
BackgroundTransparency = 1,
Size = UDim2.new(1/5, 0,1,0),
Position = UDim2.new(0,0,0,0)
};
if utility:IsSmallTouchScreen() then
this.TabHeader.Size = UDim2.new(0,84,1,0)
elseif isTenFootInterface then
this.TabHeader.Size = UDim2.new(0,220,1,0)
end
this.TabHeader.MouseButton1Click:connect(function()
if this.HubRef then
this.HubRef:SwitchToPage(this, true)
end
end)
local icon = utility:Create'ImageLabel'
{
Name = "Icon",
BackgroundTransparency = 1,
Size = UDim2.new(0.75, 0, 0.75, 0),
Position = UDim2.new(0,10,0.5,-18),
Image = "",
ImageTransparency = 0.5,
Parent = this.TabHeader
};
local iconAspectRatio = utility:Create'UIAspectRatioConstraint'
{
Name = "AspectRatioConstraint",
AspectRatio = 1,
Parent = icon
};
local title = utility:Create'TextLabel'
{
Name = "Title",
Text = "",
Font = Enum.Font.SourceSansBold,
FontSize = Enum.FontSize.Size24,
TextColor3 = Color3.new(1,1,1),
BackgroundTransparency = 1,
Size = UDim2.new(1.05,0,1,0), --overwritten
Position = UDim2.new(1.2,0,0,0), --overwritten
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = 0.5
};
local titleTextSizeConstraint = Instance.new("UITextSizeConstraint")
titleTextSizeConstraint.MaxTextSize = 24
if FFlagUseNotificationsLocalization then
title.Parent = this.TabHeader
title.TextScaled = true
title.TextWrapped = true
titleTextSizeConstraint.Parent = title
else
title.Parent = icon
end
if utility:IsSmallTouchScreen() then
title.FontSize = Enum.FontSize.Size18
titleTextSizeConstraint.MaxTextSize = 18
elseif isTenFootInterface then
title.FontSize = Enum.FontSize.Size48
titleTextSizeConstraint.MaxTextSize = 48
end
local tabSelection = StyleWidgets.MakeTabSelectionWidget(this.TabHeader)
local titleScaleInitial = Vector2.new(title.Size.X.Scale, title.Size.Y.Scale)
local function onResized()
if not this.TabHeader then
return
end
if utility:IsSmallTouchScreen() then
this.TabHeader.Icon.Size = UDim2.new(0,34,0,28)
this.TabHeader.Icon.Position = UDim2.new(this.TabHeader.Icon.Position.X.Scale,this.TabHeader.Icon.Position.X.Offset,0.5,-14)
this.TabHeader.Icon.AnchorPoint = Vector2.new(0, 0)
elseif isTenFootInterface then
this.TabHeader.Icon.Size = UDim2.new(0,88,0,74)
this.TabHeader.Icon.Position = UDim2.new(0,0,0.5,0)
this.TabHeader.Icon.AnchorPoint = Vector2.new(0, 0.5)
else
this.TabHeader.Icon.Size = UDim2.new(0,44,0,37)
this.TabHeader.Icon.Position = UDim2.new(0,15,0.5,-18)
this.TabHeader.Icon.AnchorPoint = Vector2.new(0, 0)
end
local isPortrait = utility:IsPortrait()
if isPortrait then
this.TabHeader.Icon.Position = UDim2.new(0.5, 0, 0.5, 0)
this.TabHeader.Icon.AnchorPoint = Vector2.new(0.5, 0.5)
this.TabHeader.Icon.Size = UDim2.new(0.5, 0, 0.5, 0)
if FFlagUseNotificationsLocalization then
this.TabHeader.Title.Visible = false
else
this.TabHeader.Icon.Title.Visible = false
end
else
if FFlagUseNotificationsLocalization then
this.TabHeader.Title.Visible = true
else
this.TabHeader.Icon.Title.Visible = true
end
end
if FFlagUseNotificationsLocalization then
local iconSize = this.TabHeader.Icon.AbsoluteSize
local paddingLeft = 0.125
local paddingRight = 0.025
title.Position = UDim2.new(
paddingLeft,
iconSize.X,
0.225,
0
)
title.Size = UDim2.new(
titleScaleInitial.X - paddingLeft - paddingRight,
-iconSize.X,
0.5,
0
)
end
end --end local function onResized()
utility:OnResized(this.TabHeader, onResized)
------ PAGE CREATION -------
this.Page = utility:Create'Frame'
{
Name = "Page",
BackgroundTransparency = 1,
Size = UDim2.new(1,0,1,0)
};
this.PageListLayout = utility:Create'UIListLayout'
{
Name = "RowListLayout",
FillDirection = Enum.FillDirection.Vertical,
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Top,
Padding = UDim.new(0, 3),
SortOrder = Enum.SortOrder.LayoutOrder,
Parent = this.Page
};
-- make sure each page has a unique selection group (for gamepad selection)
GuiService:AddSelectionParent(HttpService:GenerateGUID(false), this.Page)
----------------- Events ------------------------
this.Displayed = Instance.new("BindableEvent")
this.Displayed.Name = "Displayed"
this.Displayed.Event:connect(function()
if not this.HubRef.Shield.Visible then return end
this:SelectARow()
end)
this.Hidden = Instance.new("BindableEvent")
this.Hidden.Event:connect(function()
if GuiService.SelectedCoreObject and GuiService.SelectedCoreObject:IsDescendantOf(this.Page) then
GuiService.SelectedCoreObject = nil
end
end)
this.Hidden.Name = "Hidden"
----------------- FUNCTIONS ------------------------
function this:SelectARow(forced) -- Selects the first row or the most recently selected row
if forced or not GuiService.SelectedCoreObject or not GuiService.SelectedCoreObject:IsDescendantOf(this.Page) then
if this.LastSelectedObject then
GuiService.SelectedCoreObject = this.LastSelectedObject
else
if rows and #rows > 0 then
local valueChangerFrame = nil
if type(rows[1].ValueChanger) ~= "table" then
valueChangerFrame = rows[1].ValueChanger
else
valueChangerFrame = rows[1].ValueChanger.SliderFrame and
rows[1].ValueChanger.SliderFrame or rows[1].ValueChanger.SelectorFrame
end
GuiService.SelectedCoreObject = valueChangerFrame
end
end
end
end
function this:Display(pageParent, skipAnimation)
this.OpenStateChangedCount = this.OpenStateChangedCount + 1
if this.TabHeader then
this.TabHeader.TabSelection.Visible = true
this.TabHeader.Icon.ImageTransparency = 0
if FFlagUseNotificationsLocalization then
this.TabHeader.Title.TextTransparency = 0
else
this.TabHeader.Icon.Title.TextTransparency = 0
end
end
this.Page.Parent = pageParent
this.Page.Visible = true
local endPos = UDim2.new(0,0,0,0)
local animationComplete = function()
this.Page.Visible = true
displayed = true
this.Displayed:Fire()
end
if skipAnimation then
this.Page.Position = endPos
animationComplete()
else
this.Page:TweenPosition(endPos, Enum.EasingDirection.In, Enum.EasingStyle.Quad, 0.1, true, animationComplete)
end
end
function this:Hide(direction, newPagePos, skipAnimation, delayBeforeHiding)
this.OpenStateChangedCount = this.OpenStateChangedCount + 1
if this.TabHeader then
this.TabHeader.TabSelection.Visible = false
this.TabHeader.Icon.ImageTransparency = 0.5
if FFlagUseNotificationsLocalization then
this.TabHeader.Title.TextTransparency = 0.5
else
this.TabHeader.Icon.Title.TextTransparency = 0.5
end
end
if this.Page.Parent then
local endPos = UDim2.new(1 * direction,0,0,0)
local animationComplete = function()
this.Page.Visible = false
this.Page.Position = UDim2.new(this.TabPosition - newPagePos,0,0,0)
displayed = false
this.Hidden:Fire()
end
local remove = function()
if skipAnimation then
this.Page.Position = endPos
animationComplete()
else
this.Page:TweenPosition(endPos, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.1, true, animationComplete)
end
end
if delayBeforeHiding then
local myOpenStateChangedCount = this.OpenStateChangedCount
delay(delayBeforeHiding, function()
if myOpenStateChangedCount == this.OpenStateChangedCount then
remove()
end
end)
else
remove()
end
end
end
function this:GetDisplayed()
return displayed
end
function this:GetVisibility()
return this.Page.Parent
end
function this:GetTabHeader()
return this.TabHeader
end
function this:SetHub(hubRef)
this.HubRef = hubRef
for i, row in next, rows do
if type(row.ValueChanger) == 'table' then
row.ValueChanger.HubRef = this.HubRef
end
end
end
function this:GetSize()
return this.Page.AbsoluteSize
end
function this:AddRow(RowFrame, RowLabel, ValueChangerInstance, ExtraRowSpacing)
rows[#rows + 1] = {SelectionFrame = RowFrame, Label = RowLabel, ValueChanger = ValueChangerInstance}
local rowFrameYSize = 0
if RowFrame then
rowFrameYSize = RowFrame.Size.Y.Offset
end
if ExtraRowSpacing then
this.Page.Size = UDim2.new(1, 0, 0, this.Page.Size.Y.Offset + rowFrameYSize + ExtraRowSpacing)
else
this.Page.Size = UDim2.new(1, 0, 0, this.Page.Size.Y.Offset + rowFrameYSize)
end
if this.HubRef and type(ValueChangerInstance) == 'table' then
ValueChangerInstance.HubRef = this.HubRef
end
end
return this
end
-------- public facing API ----------------
local moduleApiTable = {}
function moduleApiTable:CreateNewPage()
return Initialize()
end
return moduleApiTable
@@ -0,0 +1,20 @@
local CorePackages = game:GetService("CorePackages")
local Settings = script.Parent
local SettingsReducer = require(Settings.Reducers.SettingsReducer)
local Rodux = require(CorePackages.Rodux)
local Store = Rodux.Store
local SettingsState = {}
function SettingsState:Init()
self.store = Store.new(SettingsReducer, nil, {Rodux.thunkMiddleware})
end
function SettingsState:Destruct()
self.store:destruct()
end
SettingsState:Init()
return SettingsState
@@ -0,0 +1,30 @@
local CorePackages = game:GetService("CorePackages")
local Modules = game:GetService("CoreGui").RobloxGui.Modules
local Roact = require(CorePackages.Roact)
local Rodux = require(CorePackages.Rodux)
local RoactRodux = require(CorePackages.RoactRodux)
local ShareGame = Modules.Settings.Pages.ShareGame
local App = require(ShareGame.Components.App)
local AppReducer = require(ShareGame.AppReducer)
local ShareGameMaster = {}
function ShareGameMaster.createApp(parentGui)
local self = {}
self.store = Rodux.Store.new(AppReducer, nil, { Rodux.thunkMiddleware })
self._instanceHandle = Roact.mount(
Roact.createElement(RoactRodux.StoreProvider, {
store = self.store
}, {
Roact.createElement(App, {
pageTarget = parentGui,
})
})
)
return self
end
return ShareGameMaster
@@ -0,0 +1,83 @@
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local Actions = script.Parent.Parent.Actions
local SetRecentlyPlayedGamesFetching = require(Actions.SetRecentlyPlayedGamesFetching)
local SetRecentlyPlayedGames = require(Actions.SetRecentlyPlayedGames)
local RETRIES = 6
local RECENT_SORT_KEY = "-2"
local FEATURED_SORT_KEY = "8"
local function fetchRecentlyPlayedGamesAsync(store, numOfGames)
local success = false
local tryCount = 1
-- This retry logic is here for those cases where the appshell data model failed to get the recent sort and is retrying.
while tryCount <= RETRIES do
local sharedData = PlatformService:ReadSharedData() or {}
local gameCache = sharedData.games
if sharedData.sorts ~= nil and gameCache ~= nil then
local gamesIncluded = {}
local count = 0
local recentSortData = {}
if sharedData.sorts[RECENT_SORT_KEY] ~= nil and sharedData.sorts[RECENT_SORT_KEY].orderedSort ~= nil then
local recentSort = sharedData.sorts[RECENT_SORT_KEY].orderedSort
for i in ipairs(recentSort) do
if recentSort[i].HasData == false or count == numOfGames then
break
end
--Check if there are cached game data available.
local gameData = sharedData.games[tostring(recentSort[i].PlaceId)]
if gameData then
gamesIncluded[recentSort[i].PlaceId] = true
table.insert(recentSortData, gameData)
count = count + 1
end
end
end
if count < numOfGames then
-- Add recommended games to fill up the remining games.
if sharedData.sorts[FEATURED_SORT_KEY] ~= nil and sharedData.sorts[FEATURED_SORT_KEY].orderedSort ~= nil then
local backupSort = sharedData.sorts[FEATURED_SORT_KEY].orderedSort
for i in ipairs(backupSort) do
if backupSort[i].HasData == false or count == numOfGames then
break
end
local gameData = sharedData.games[tostring(backupSort[i].PlaceId)]
if gameData and gamesIncluded[backupSort[i].PlaceId] ~= true then
gamesIncluded[backupSort[i].PlaceId] = true
table.insert(recentSortData, gameData)
count = count + 1
end
end
end
end
if count >= numOfGames then
store:dispatch(SetRecentlyPlayedGames(recentSortData))
store:dispatch(SetRecentlyPlayedGamesFetching(false))
success = true
break
end
end
tryCount = tryCount + 1
wait(tryCount ^ 2)
end
if success == false then
store:dispatch(SetRecentlyPlayedGames(nil))
end
end
return function(numOfGames, forceUpdate)
return function(store)
local state = store:getState()
local isFetching = state.RecentlyPlayedGamesFetchingStatus.fetching
if isFetching then
return
elseif isFetching ~= nil and not forceUpdate then
return
end
store:dispatch(SetRecentlyPlayedGamesFetching(true))
spawn(function()
fetchRecentlyPlayedGamesAsync(store, numOfGames)
end)
end
end
File diff suppressed because it is too large Load Diff