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,41 @@
--[[
Connects to GuiService's browser close callback to retry purchase after upsell
]]
local CorePackages = game:GetService("CorePackages")
local GuiService = game:GetService("GuiService")
local Roact = require(CorePackages.Roact)
local ExternalEventConnection = require(script.Parent.ExternalEventConnection)
local retryAfterUpsell = require(script.Parent.Parent.Parent.Thunks.retryAfterUpsell)
local connectToStore = require(script.Parent.Parent.Parent.connectToStore)
local function BrowserPurchaseFinishedConnector(props)
local onBrowserWindowClosed = props.onBrowserWindowClosed
--[[
CLILUACORE-309: The browser window closing is the ONLY
indication we have about when the user finished interacting
with the upsell flow on desktop.
]]
return Roact.createElement(ExternalEventConnection, {
event = GuiService.BrowserWindowClosed,
callback = onBrowserWindowClosed,
})
end
local function mapDispatchToProps(dispatch)
return {
onBrowserWindowClosed = function()
dispatch(retryAfterUpsell())
end,
}
end
BrowserPurchaseFinishedConnector = connectToStore(
nil,
mapDispatchToProps
)(BrowserPurchaseFinishedConnector)
return BrowserPurchaseFinishedConnector
@@ -0,0 +1,35 @@
--[[
Connects relevant Roblox engine events to the rodux store
]]
local CorePackages = game:GetService("CorePackages")
local UserInputService = game:GetService("UserInputService")
local Roact = require(CorePackages.Roact)
local UpsellFlow = require(script.Parent.Parent.Parent.UpsellFlow)
local MarketplaceServiceEventConnector = require(script.Parent.MarketplaceServiceEventConnector)
local GamepadInputConnector = require(script.Parent.GamepadInputConnector)
local BrowserPurchaseFinishedConnector = require(script.Parent.BrowserPurchaseFinishedConnector)
local NativePurchaseFinishedConnector = require(script.Parent.NativePurchaseFinishedConnector)
local getUpsellFlow = require(script.Parent.Parent.Parent.NativeUpsell.getUpsellFlow)
local function EventConnections()
local upsellConnector
local upsellFlow = getUpsellFlow(UserInputService:GetPlatform())
if upsellFlow == UpsellFlow.Web then
upsellConnector = Roact.createElement(BrowserPurchaseFinishedConnector)
elseif upsellFlow == UpsellFlow.Mobile then
upsellConnector = Roact.createElement(NativePurchaseFinishedConnector)
end
return Roact.createElement("Folder", {}, {
MarketPlaceServiceEventConnector = Roact.createElement(MarketplaceServiceEventConnector),
GamepadInputConnector = Roact.createElement(GamepadInputConnector),
UpsellFinishedConnector = upsellConnector,
})
end
return EventConnections
@@ -0,0 +1,52 @@
--[[
A component that establishes a connection to a Roblox event when it is rendered.
]]
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local ExternalEventConnection = Roact.Component:extend("ExternalEventConnection")
function ExternalEventConnection:init()
self.connection = nil
end
--[[
Render the child component so that ExternalEventConnections can be nested like so:
Roact.createElement(ExternalEventConnection, {
event = UserInputService.InputBegan,
callback = inputBeganCallback,
}, {
Roact.createElement(ExternalEventConnection, {
event = UserInputService.InputEnded,
callback = inputChangedCallback,
})
})
]]
function ExternalEventConnection:render()
return Roact.oneChild(self.props[Roact.Children])
end
function ExternalEventConnection:didMount()
local event = self.props.event
local callback = self.props.callback
self.connection = event:Connect(callback)
end
function ExternalEventConnection:didUpdate(oldProps)
if self.props.event ~= oldProps.event or self.props.callback ~= oldProps.callback then
self.connection:Disconnect()
self.connection = self.props.event:Connect(self.props.callback)
end
end
function ExternalEventConnection:willUnmount()
self.connection:Disconnect()
self.connection = nil
end
return ExternalEventConnection
@@ -0,0 +1,100 @@
--[[
Sets whether or not gamepad buttons should be shown, based on recently
received inputs
]]
local CorePackages = game:GetService("CorePackages")
local UserInputService = game:GetService("UserInputService")
local Roact = require(CorePackages.Roact)
local ExternalEventConnection = require(script.Parent.ExternalEventConnection)
local SetGamepadEnabled = require(script.Parent.Parent.Parent.Actions.SetGamepadEnabled)
local connectToStore = require(script.Parent.Parent.Parent.connectToStore)
local gamepadInputs = {
[Enum.UserInputType.Gamepad1] = true,
[Enum.UserInputType.Gamepad2] = true,
[Enum.UserInputType.Gamepad3] = true,
[Enum.UserInputType.Gamepad4] = true,
}
local thumbstickInputs = {
[Enum.KeyCode.Thumbstick1] = true,
[Enum.KeyCode.Thumbstick2] = true,
}
--[[
Returns whether or not input changed, and what its new value is
UserInputService.InputChanged event fires very frequently when moving
the mouse or thumbsticks: we try to early-out if nothing changed so that
we don't send hundreds of dispatches through the store
]]
local function didInputChange(isGamepadEnabled, inputObject)
local newEnabledStatus = nil
if gamepadInputs[inputObject.UserInputType] then
if thumbstickInputs[inputObject.KeyCode] then
local position = inputObject.Position
if math.abs(position.X) > 0.1 or math.abs(position.Z) > 0.1 or math.abs(position.Y) > 0.1 then
newEnabledStatus = true
end
else
newEnabledStatus = true
end
else
newEnabledStatus = false
end
if newEnabledStatus ~= nil and newEnabledStatus ~= isGamepadEnabled then
return true, newEnabledStatus
end
return false, isGamepadEnabled
end
local function GamepadInputConnector(props)
local gamepadEnabled = props.gamepadEnabled
local inputChanged = props.inputChanged
local dispatchOnChange = function (inputObject)
local didChange, newEnabledStatus = didInputChange(gamepadEnabled, inputObject)
if didChange then
inputChanged(newEnabledStatus)
end
end
return Roact.createElement(ExternalEventConnection, {
event = UserInputService.InputChanged,
callback = dispatchOnChange,
}, {
Roact.createElement(ExternalEventConnection, {
event = UserInputService.InputBegan,
callback = dispatchOnChange,
})
})
end
local function mapStateToProps(state)
return {
gamepadEnabled = state.gamepadEnabled,
}
end
local function mapDispatchToProps(dispatch)
return {
inputChanged = function(newEnabledStatus)
dispatch(SetGamepadEnabled(newEnabledStatus))
end,
}
end
GamepadInputConnector = connectToStore(
mapStateToProps,
mapDispatchToProps
)(GamepadInputConnector)
return GamepadInputConnector
@@ -0,0 +1,19 @@
--[[
LayoutValuesConsumer will extract the LayoutValues object
from context and pass it into the given render callback
]]
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local LayoutValuesKey = require(script.Parent.Parent.Parent.LayoutValuesKey)
local LayoutValuesConsumer = Roact.Component:extend("LayoutValuesConsumer")
function LayoutValuesConsumer:render()
local service = self._context[LayoutValuesKey]
return self.props.render(service)
end
return LayoutValuesConsumer
@@ -0,0 +1,24 @@
--[[
LayoutValuesProvider is a simple wrapper component that injects the
specified services into context
]]
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local LayoutValuesKey = require(script.Parent.Parent.Parent.LayoutValuesKey)
local LayoutValuesProvider = Roact.Component:extend("LayoutValuesProvider")
function LayoutValuesProvider:init(props)
assert(type(props.layoutValues) == "table", "Expected required prop 'layoutValues' to be a table")
assert(type(props.render) == "function", "Expected prop 'render' to be a function")
self._context[LayoutValuesKey] = props.layoutValues
end
function LayoutValuesProvider:render()
return self.props.render()
end
return LayoutValuesProvider
@@ -0,0 +1,22 @@
--[[
LocalizationContextConsumer will extract the localization context
object from Roact context and provide it to the given render callback
Used for components that need to perform localization using this
project's LocalizationService
]]
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local LocalizationContextKey = require(script.Parent.Parent.Parent.LocalizationContextKey)
local LocalizationContextConsumer = Roact.Component:extend("LocalizationContextConsumer")
function LocalizationContextConsumer:render()
local localizationContext = self._context[LocalizationContextKey]
return self.props.render(localizationContext)
end
return LocalizationContextConsumer
@@ -0,0 +1,24 @@
--[[
LocalizationContextProvider is a simple wrapper component that injects the
specified services into context
]]
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local LocalizationContextKey = require(script.Parent.Parent.Parent.LocalizationContextKey)
local LocalizationContextProvider = Roact.Component:extend("LocalizationContextProvider")
function LocalizationContextProvider:init(props)
assert(props.localizationContext, "Missing required prop 'localizationContext'")
assert(props.render, "Missing required prop 'render'")
self._context[LocalizationContextKey] = props.localizationContext
end
function LocalizationContextProvider:render()
return self.props.render(LocalizationContextKey)
end
return LocalizationContextProvider
@@ -0,0 +1,77 @@
--[[
Connects Rodux store to external MarketplaceService events
]]
local CorePackages = game:GetService("CorePackages")
local MarketplaceService = game:GetService("MarketplaceService")
local Roact = require(CorePackages.Roact)
local ExternalEventConnection = require(script.Parent.ExternalEventConnection)
local PurchaseError = require(script.Parent.Parent.Parent.PurchaseError)
local ErrorOccurred = require(script.Parent.Parent.Parent.Actions.ErrorOccurred)
local completePurchase = require(script.Parent.Parent.Parent.Thunks.completePurchase)
local initiatePurchase = require(script.Parent.Parent.Parent.Thunks.initiatePurchase)
local connectToStore = require(script.Parent.Parent.Parent.connectToStore)
local function MarketplaceServiceEventConnector(props)
local onPurchaseRequest = props.onPurchaseRequest
local onProductPurchaseRequest = props.onProductPurchaseRequest
local onPurchaseGamePassRequest = props.onPurchaseGamePassRequest
local onServerPurchaseVerification = props.onServerPurchaseVerification
return Roact.createElement(ExternalEventConnection, {
event = MarketplaceService.PromptPurchaseRequested,
callback = onPurchaseRequest,
}, {
Roact.createElement(ExternalEventConnection, {
event = MarketplaceService.PromptProductPurchaseRequested,
callback = onProductPurchaseRequest,
}, {
Roact.createElement(ExternalEventConnection, {
event = MarketplaceService.PromptGamePassPurchaseRequested,
callback = onPurchaseGamePassRequest,
}, {
Roact.createElement(ExternalEventConnection, {
event = MarketplaceService.ServerPurchaseVerification,
callback = onServerPurchaseVerification,
})
})
})
})
end
MarketplaceServiceEventConnector = connectToStore(nil,
function(dispatch)
local function onPurchaseRequest(player, assetId, equipIfPurchased, currencyType)
dispatch(initiatePurchase(assetId, Enum.InfoType.Asset, equipIfPurchased))
end
local function onProductPurchaseRequest(player, productId, equipIfPurchased, currencyType)
dispatch(initiatePurchase(productId, Enum.InfoType.Product, equipIfPurchased))
end
local function onPurchaseGamePassRequest(player, gamePassId)
dispatch(initiatePurchase(gamePassId, Enum.InfoType.GamePass, false))
end
-- Specific to purchasing dev products
local function onServerPurchaseVerification(serverResponseTable)
if not serverResponseTable then
dispatch(ErrorOccurred(PurchaseError.UnknownFailure))
else
dispatch(completePurchase())
end
end
return {
onPurchaseRequest = onPurchaseRequest,
onProductPurchaseRequest = onProductPurchaseRequest,
onPurchaseGamePassRequest = onPurchaseGamePassRequest,
onServerPurchaseVerification = onServerPurchaseVerification,
}
end)(MarketplaceServiceEventConnector)
return MarketplaceServiceEventConnector
@@ -0,0 +1,45 @@
--[[
Connects to MarketplaceService's callback for completing a native purchase, so that we can
retry after an upsell purchase was processed
]]
local CorePackages = game:GetService("CorePackages")
local MarketplaceService = game:GetService("MarketplaceService")
local Roact = require(CorePackages.Roact)
local ExternalEventConnection = require(script.Parent.ExternalEventConnection)
local ErrorOccurred = require(script.Parent.Parent.Parent.Actions.ErrorOccurred)
local PurchaseError = require(script.Parent.Parent.Parent.PurchaseError)
local retryAfterUpsell = require(script.Parent.Parent.Parent.Thunks.retryAfterUpsell)
local connectToStore = require(script.Parent.Parent.Parent.connectToStore)
local function NativePurchaseFinishedConnector(props)
local nativePurchaseFinished = props.nativePurchaseFinished
return Roact.createElement(ExternalEventConnection, {
event = MarketplaceService.NativePurchaseFinished,
callback = nativePurchaseFinished,
})
end
local function mapDispatchToProps(dispatch)
return {
nativePurchaseFinished = function(player, productId, wasPurchased)
if wasPurchased then
dispatch(retryAfterUpsell())
else
dispatch(ErrorOccurred(PurchaseError.InvalidFunds))
end
end,
}
end
NativePurchaseFinishedConnector = connectToStore(
nil,
mapDispatchToProps
)(NativePurchaseFinishedConnector)
return NativePurchaseFinishedConnector
@@ -0,0 +1,22 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local LocalizationService = require(script.Parent.Parent.Parent.Localization.LocalizationService)
local LocalizationContextConsumer = require(script.Parent.LocalizationContextConsumer)
local function NumberLocalizer(props)
local number = props.number
local render = props.render
assert(typeof(number) == "number", "prop 'number' must be provided")
assert(typeof(render) == "function", "Render prop must be a function")
return Roact.createElement(LocalizationContextConsumer, {
render = function(localizationContext)
return render(LocalizationService.formatNumber(localizationContext, number))
end,
})
end
return NumberLocalizer
@@ -0,0 +1,23 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local LocalizationService = require(script.Parent.Parent.Parent.Localization.LocalizationService)
local LocalizationContextConsumer = require(script.Parent.LocalizationContextConsumer)
local function TextLocalizer(props)
local key = props.key
local params = props.params
local render = props.render
assert(typeof(key) == "string", "String key must be provided")
assert(typeof(render) == "function", "Render prop must be a function")
return Roact.createElement(LocalizationContextConsumer, {
render = function(localizationContext)
return render(LocalizationService.getString(localizationContext, key, params))
end,
})
end
return TextLocalizer
@@ -0,0 +1,21 @@
--[[
Helper for supplying the current Roblox Locale to the Roact
tree via context using the LocalizationContextProvider
]]
local CorePackages = game:GetService("CorePackages")
local LocalizationService = game:GetService("LocalizationService")
local Roact = require(CorePackages.Roact)
local LocalizationContextProvider = require(script.Parent.LocalizationContextProvider)
local getLocalizationContext = require(script.Parent.Parent.Parent.Localization.getLocalizationContext)
local function provideRobloxLocale(renderFunc)
return Roact.createElement(LocalizationContextProvider, {
localizationContext = getLocalizationContext(LocalizationService.RobloxLocaleId),
render = renderFunc
})
end
return provideRobloxLocale
@@ -0,0 +1,17 @@
--[[
Helpful wrapper around LayoutValuesConsumer to make it a
little less verbose to use
]]
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local LayoutValuesConsumer = require(script.Parent.LayoutValuesConsumer)
local function withLayoutValues(renderFunc)
return Roact.createElement(LayoutValuesConsumer, {
render = renderFunc,
})
end
return withLayoutValues