add gs
This commit is contained in:
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name, "accountInfo")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name, "bundleProductInfo")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name, "purchaseError")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name, "premiumInfo")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name, "productInfo")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name, "robuxProductId", "robuxPurchaseAmount")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name, "id", "equipIfPurchased", "isRobloxPurchase")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name, "id")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name, "id")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name, "id", "equipIfPurchased")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name, "id")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name, "key", "variation")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name, "state")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name, "enabled")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name, "id", "infoType", "equipIfPurchased")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name, "promptState")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name, "state")
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
return makeActionCreator(script.Name, "purchasingStartTime")
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
--[[
|
||||
A helper function to define a named Rodux action creator.
|
||||
|
||||
Takes a name followed by a list of fields that should be
|
||||
provided to the resulting action creator.
|
||||
|
||||
Returns an object with a name field that can also be called
|
||||
to create an action. When called, it will validate its given
|
||||
arguments against the expected set of arguments.
|
||||
]]
|
||||
local function makeActionCreator(name, ...)
|
||||
local fields = {...}
|
||||
|
||||
assert(type(name) == "string",
|
||||
"Bad argument #1 to makeActionCreator, expected string")
|
||||
|
||||
for i = 1, select("#", ...) do
|
||||
assert(typeof(select(i, ...)) == "string",
|
||||
"Bad argument to makeActionCreator, all arguments must be of type string")
|
||||
end
|
||||
|
||||
return setmetatable({
|
||||
name = name
|
||||
}, {
|
||||
__call = function(self, ...)
|
||||
local result = {
|
||||
type = name,
|
||||
}
|
||||
|
||||
assert(select("#", ...) == #fields,
|
||||
"Incorrect number of arguments provided to action creator " .. name)
|
||||
|
||||
for index, argName in ipairs(fields) do
|
||||
result[argName] = select(index, ...)
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
return makeActionCreator
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
return function()
|
||||
local makeActionCreator = require(script.Parent.makeActionCreator)
|
||||
|
||||
describe("the generated action creator", function()
|
||||
it("should throw if given an invalid arguments", function()
|
||||
expect(function()
|
||||
makeActionCreator(100)
|
||||
end).to.throw()
|
||||
expect(function()
|
||||
makeActionCreator("Test", 12)
|
||||
end).to.throw()
|
||||
end)
|
||||
|
||||
it("should generate a callable table with a 'name' field", function()
|
||||
local actionCreator = makeActionCreator("Action")
|
||||
|
||||
expect(type(actionCreator)).to.equal("table")
|
||||
expect(actionCreator.name).to.equal("Action")
|
||||
expect(actionCreator).never.to.throw()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("the action object generated by the action creator", function()
|
||||
it("should expect a matching number of inputs", function()
|
||||
local actionWithFields = makeActionCreator("AddItem", "id", "title")
|
||||
|
||||
expect(function()
|
||||
actionWithFields(10, "Apple", "extra arg")
|
||||
end).to.throw()
|
||||
expect(function()
|
||||
actionWithFields(10)
|
||||
end).to.throw()
|
||||
|
||||
expect(function()
|
||||
actionWithFields(10, "Orange")
|
||||
end).never.to.throw()
|
||||
end)
|
||||
|
||||
it("should correctly count trailing nils", function()
|
||||
local actionWithFields = makeActionCreator("SetProperty", "value", "default")
|
||||
|
||||
expect(function()
|
||||
actionWithFields("1", nil)
|
||||
end).never.to.throw()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
--[[
|
||||
Connects to GuiService's browser close callback to retry purchase after upsell
|
||||
]]
|
||||
local Root = script.Parent.Parent.Parent
|
||||
local GuiService = game:GetService("GuiService")
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local retryAfterUpsell = require(Root.Thunks.retryAfterUpsell)
|
||||
local connectToStore = require(Root.connectToStore)
|
||||
|
||||
local ExternalEventConnection = require(script.Parent.ExternalEventConnection)
|
||||
|
||||
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
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
--[[
|
||||
Connects relevant Roblox engine events to the rodux store
|
||||
]]
|
||||
local Root = script.Parent.Parent.Parent
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local UpsellFlow = require(Root.Enums.UpsellFlow)
|
||||
local getUpsellFlow = require(Root.NativeUpsell.getUpsellFlow)
|
||||
|
||||
local MarketplaceServiceEventConnector = require(script.Parent.MarketplaceServiceEventConnector)
|
||||
local InputTypeManager = require(script.Parent.InputTypeManager)
|
||||
local BrowserPurchaseFinishedConnector = require(script.Parent.BrowserPurchaseFinishedConnector)
|
||||
local NativePurchaseFinishedConnector = require(script.Parent.NativePurchaseFinishedConnector)
|
||||
local PlayerConnector = require(script.Parent.PlayerConnector)
|
||||
|
||||
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
|
||||
|
||||
local enableInputManager = UserInputService:GetPlatform() ~= Enum.Platform.XBoxOne
|
||||
|
||||
return Roact.createElement("Folder", {}, {
|
||||
MarketPlaceServiceEventConnector = Roact.createElement(MarketplaceServiceEventConnector),
|
||||
InputTypeManager = enableInputManager and Roact.createElement(InputTypeManager) or nil,
|
||||
UpsellFinishedConnector = upsellConnector,
|
||||
PlayerConnector = Roact.createElement(PlayerConnector),
|
||||
})
|
||||
end
|
||||
|
||||
return EventConnections
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
--[[
|
||||
A component that establishes a connection to a Roblox event when it is rendered.
|
||||
]]
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.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
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
--[[
|
||||
Sets whether or not gamepad buttons should be shown, based on recently
|
||||
received inputs
|
||||
]]
|
||||
local Root = script.Parent.Parent.Parent
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local MouseIconOverrideService = require(CorePackages.InGameServices.MouseIconOverrideService)
|
||||
|
||||
local SetGamepadEnabled = require(Root.Actions.SetGamepadEnabled)
|
||||
local PromptState = require(Root.Enums.PromptState)
|
||||
local connectToStore = require(Root.connectToStore)
|
||||
|
||||
local ExternalEventConnection = require(script.Parent.ExternalEventConnection)
|
||||
|
||||
local CURSOR_OVERRIDE_KEY = "PurchasePromptOverrideKey"
|
||||
|
||||
local gamepadInputs = {
|
||||
[Enum.UserInputType.Gamepad1] = true,
|
||||
[Enum.UserInputType.Gamepad2] = true,
|
||||
[Enum.UserInputType.Gamepad3] = true,
|
||||
[Enum.UserInputType.Gamepad4] = true,
|
||||
}
|
||||
|
||||
local InputTypeManager = Roact.Component:extend("InputTypeManager")
|
||||
|
||||
function InputTypeManager:init()
|
||||
local setGamepadEnabled = self.props.setGamepadEnabled
|
||||
|
||||
self.dispatchOnChange = function(lastInputType)
|
||||
local newEnabledStatus
|
||||
if gamepadInputs[lastInputType] then
|
||||
newEnabledStatus = true
|
||||
else
|
||||
newEnabledStatus = false
|
||||
end
|
||||
|
||||
setGamepadEnabled(newEnabledStatus)
|
||||
end
|
||||
|
||||
self.cursorOverridden = false
|
||||
end
|
||||
|
||||
function InputTypeManager:render()
|
||||
return Roact.createElement(ExternalEventConnection, {
|
||||
event = UserInputService.LastInputTypeChanged,
|
||||
callback = self.dispatchOnChange,
|
||||
})
|
||||
end
|
||||
|
||||
function InputTypeManager:didUpdate(prevProps, prevState)
|
||||
local didShow = prevProps.promptState == PromptState.None
|
||||
and self.props.promptState ~= PromptState.None
|
||||
local didHide = prevProps.promptState ~= PromptState.None
|
||||
and self.props.promptState == PromptState.None
|
||||
|
||||
local isShown = self.props.promptState ~= PromptState.None
|
||||
|
||||
local overrideStatus = self.props.gamepadEnabled
|
||||
and Enum.OverrideMouseIconBehavior.ForceHide
|
||||
or Enum.OverrideMouseIconBehavior.ForceShow
|
||||
|
||||
-- If we're already showing the prompt and the gamepad status changed
|
||||
if isShown and prevProps.gamepadEnabled ~= self.props.gamepadEnabled then
|
||||
if self.cursorOverridden then
|
||||
MouseIconOverrideService.pop(CURSOR_OVERRIDE_KEY)
|
||||
end
|
||||
MouseIconOverrideService.push(CURSOR_OVERRIDE_KEY, overrideStatus)
|
||||
self.cursorOverridden = true
|
||||
-- If the purchase prompt goes from None to shown
|
||||
elseif didShow then
|
||||
MouseIconOverrideService.push(CURSOR_OVERRIDE_KEY, overrideStatus)
|
||||
self.cursorOverridden = true
|
||||
-- If the purchase prompt goes from shown to None
|
||||
elseif didHide then
|
||||
MouseIconOverrideService.pop(CURSOR_OVERRIDE_KEY)
|
||||
self.cursorOverridden = false
|
||||
end
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
promptState = state.promptState,
|
||||
gamepadEnabled = state.gamepadEnabled,
|
||||
}
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
setGamepadEnabled = function(enabled)
|
||||
dispatch(SetGamepadEnabled(enabled))
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
InputTypeManager = connectToStore(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(InputTypeManager)
|
||||
|
||||
return InputTypeManager
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
--[[
|
||||
LayoutValuesConsumer will extract the LayoutValues object
|
||||
from context and pass it into the given render callback
|
||||
]]
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local LayoutValuesKey = require(Root.Symbols.LayoutValuesKey)
|
||||
|
||||
local LayoutValuesConsumer = Roact.Component:extend("LayoutValuesConsumer")
|
||||
|
||||
-- TODO(esauer): Add validation when adding t
|
||||
-- local validateProps = t.strictInterface({
|
||||
-- render = t.callback,
|
||||
-- })
|
||||
|
||||
function LayoutValuesConsumer:init()
|
||||
self.layoutValues = self._context[LayoutValuesKey]
|
||||
self.state = {
|
||||
layout = self.layoutValues.layout,
|
||||
}
|
||||
end
|
||||
|
||||
function LayoutValuesConsumer:render()
|
||||
-- assert(validateProps(self.props))
|
||||
return self.props.render(self.state.layout)
|
||||
end
|
||||
|
||||
function LayoutValuesConsumer:didMount()
|
||||
self.disconnectLayoutListener = self.layoutValues.signal:subscribe(function(newLayout)
|
||||
self:setState({
|
||||
layout = newLayout,
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
function LayoutValuesConsumer:willUnmount()
|
||||
self.disconnectLayoutListener()
|
||||
end
|
||||
|
||||
return LayoutValuesConsumer
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
--[[
|
||||
LayoutValuesProvider is a simple wrapper component that injects the
|
||||
specified services into context
|
||||
]]
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local ContentProvider = game:GetService("ContentProvider")
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local LayoutValues = require(Root.Services.LayoutValues)
|
||||
local LayoutValuesKey = require(Root.Symbols.LayoutValuesKey)
|
||||
local connectToStore = require(Root.connectToStore)
|
||||
|
||||
local LayoutValuesProvider = Roact.Component:extend("LayoutValuesProvider")
|
||||
|
||||
function LayoutValuesProvider:init(props)
|
||||
assert(type(props.isTenFootInterface) == "boolean", "Expected required prop 'isTenFootInterface' to be a boolean")
|
||||
assert(type(props.render) == "function", "Expected prop 'render' to be a function")
|
||||
|
||||
self.layoutValues = LayoutValues.new(self.props.isTenFootInterface, false)
|
||||
self._context[LayoutValuesKey] = self.layoutValues
|
||||
end
|
||||
|
||||
function LayoutValuesProvider:didMount()
|
||||
-- preload images
|
||||
spawn(function()
|
||||
local assets = {}
|
||||
|
||||
for _, image in pairs(self.layoutValues.layout.Image) do
|
||||
local decal = Instance.new("Decal")
|
||||
decal.Texture = image.Path
|
||||
table.insert(assets, decal)
|
||||
end
|
||||
|
||||
ContentProvider:PreloadAsync(assets)
|
||||
|
||||
for _,asset in pairs(assets) do
|
||||
asset:Destroy()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function LayoutValuesProvider:render()
|
||||
return self.props.render()
|
||||
end
|
||||
|
||||
function LayoutValuesProvider:didUpdate(previousProps)
|
||||
if self.props.isTenFootInterface ~= previousProps.isTenFootInterface then
|
||||
self.layoutValues:update(self.props.isTenFootInterface)
|
||||
end
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
abVariations = state.abVariations,
|
||||
}
|
||||
end
|
||||
|
||||
LayoutValuesProvider = connectToStore(
|
||||
mapStateToProps
|
||||
)(LayoutValuesProvider)
|
||||
|
||||
return LayoutValuesProvider
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
--[[
|
||||
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 Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local LocalizationContextKey = require(Root.Symbols.LocalizationContextKey)
|
||||
|
||||
local LocalizationContextConsumer = Roact.Component:extend("LocalizationContextConsumer")
|
||||
|
||||
function LocalizationContextConsumer:render()
|
||||
local localizationContext = self._context[LocalizationContextKey]
|
||||
|
||||
return self.props.render(localizationContext)
|
||||
end
|
||||
|
||||
return LocalizationContextConsumer
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
--[[
|
||||
LocalizationContextProvider is a simple wrapper component that injects the
|
||||
specified services into context
|
||||
]]
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local LocalizationContextKey = require(Root.Symbols.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
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
--[[
|
||||
Connects Rodux store to external MarketplaceService events
|
||||
]]
|
||||
local Root = script.Parent.Parent.Parent
|
||||
local MarketplaceService = game:GetService("MarketplaceService")
|
||||
local Players = game:GetService("Players")
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local ErrorOccurred = require(Root.Actions.ErrorOccurred)
|
||||
local PurchaseError = require(Root.Enums.PurchaseError)
|
||||
local completePurchase = require(Root.Thunks.completePurchase)
|
||||
local initiatePurchase = require(Root.Thunks.initiatePurchase)
|
||||
local initiateBundlePurchase = require(Root.Thunks.initiateBundlePurchase)
|
||||
local initiatePremiumPurchase = require(Root.Thunks.initiatePremiumPurchase)
|
||||
local initiateSubscriptionPurchase = require(Root.Thunks.initiateSubscriptionPurchase)
|
||||
local connectToStore = require(Root.connectToStore)
|
||||
|
||||
local GetFFlagPromptRobloxPurchaseEnabled = require(Root.Flags.GetFFlagPromptRobloxPurchaseEnabled)
|
||||
local GetFFlagDeveloperSubscriptionsEnabled = require(Root.Flags.GetFFlagDeveloperSubscriptionsEnabled)
|
||||
|
||||
local ExternalEventConnection = require(script.Parent.ExternalEventConnection)
|
||||
|
||||
local function MarketplaceServiceEventConnector(props)
|
||||
local onPurchaseRequest = props.onPurchaseRequest
|
||||
local onProductPurchaseRequest = props.onProductPurchaseRequest
|
||||
local onPurchaseGamePassRequest = props.onPurchaseGamePassRequest
|
||||
local onServerPurchaseVerification = props.onServerPurchaseVerification
|
||||
local onBundlePurchaseRequest = props.onBundlePurchaseRequest
|
||||
local onPremiumPurchaseRequest = props.onPremiumPurchaseRequest
|
||||
local onRobloxPurchaseRequest = props.onRobloxPurchaseRequest
|
||||
local onSubscriptionPurchaseRequest = props.onSubscriptionPurchaseRequest
|
||||
|
||||
return Roact.createFragment({
|
||||
Roact.createElement(ExternalEventConnection, {
|
||||
event = MarketplaceService.PromptPurchaseRequested,
|
||||
callback = onPurchaseRequest,
|
||||
}),
|
||||
RobloxPurchase = GetFFlagPromptRobloxPurchaseEnabled() and Roact.createElement(ExternalEventConnection, {
|
||||
event = MarketplaceService.PromptRobloxPurchaseRequested,
|
||||
callback = onRobloxPurchaseRequest,
|
||||
}),
|
||||
Roact.createElement(ExternalEventConnection, {
|
||||
event = MarketplaceService.PromptProductPurchaseRequested,
|
||||
callback = onProductPurchaseRequest,
|
||||
}),
|
||||
Roact.createElement(ExternalEventConnection, {
|
||||
event = MarketplaceService.PromptGamePassPurchaseRequested,
|
||||
callback = onPurchaseGamePassRequest,
|
||||
}),
|
||||
Roact.createElement(ExternalEventConnection, {
|
||||
event = MarketplaceService.ServerPurchaseVerification,
|
||||
callback = onServerPurchaseVerification,
|
||||
}),
|
||||
Roact.createElement(ExternalEventConnection, {
|
||||
event = MarketplaceService.PromptBundlePurchaseRequested,
|
||||
callback = onBundlePurchaseRequest,
|
||||
}),
|
||||
Roact.createElement(ExternalEventConnection, {
|
||||
event = MarketplaceService.PromptPremiumPurchaseRequested,
|
||||
callback = onPremiumPurchaseRequest,
|
||||
}),
|
||||
SubscriptionsPurchase = GetFFlagDeveloperSubscriptionsEnabled() and Roact.createElement(ExternalEventConnection, {
|
||||
event = MarketplaceService.PromptSubscriptionPurchaseRequested,
|
||||
callback = onSubscriptionPurchaseRequest,
|
||||
})
|
||||
})
|
||||
end
|
||||
|
||||
MarketplaceServiceEventConnector = connectToStore(nil,
|
||||
function(dispatch)
|
||||
local function onPurchaseRequest(player, assetId, equipIfPurchased, currencyType)
|
||||
if player == Players.LocalPlayer then
|
||||
dispatch(initiatePurchase(assetId, Enum.InfoType.Asset, equipIfPurchased, false))
|
||||
end
|
||||
end
|
||||
|
||||
local function onRobloxPurchaseRequest(assetId, equipIfPurchased)
|
||||
dispatch(initiatePurchase(assetId, Enum.InfoType.Asset, equipIfPurchased, true))
|
||||
end
|
||||
|
||||
local function onProductPurchaseRequest(player, productId, equipIfPurchased, currencyType)
|
||||
if player == Players.LocalPlayer then
|
||||
dispatch(initiatePurchase(productId, Enum.InfoType.Product, equipIfPurchased))
|
||||
end
|
||||
end
|
||||
|
||||
local function onPurchaseGamePassRequest(player, gamePassId)
|
||||
if player == Players.LocalPlayer then
|
||||
dispatch(initiatePurchase(gamePassId, Enum.InfoType.GamePass, false))
|
||||
end
|
||||
end
|
||||
|
||||
-- Specific to purchasing dev products
|
||||
local function onServerPurchaseVerification(serverResponseTable)
|
||||
if not serverResponseTable then
|
||||
dispatch(ErrorOccurred(PurchaseError.UnknownFailure))
|
||||
else
|
||||
local playerId = serverResponseTable["playerId"]
|
||||
if playerId ~= nil then
|
||||
playerId = tonumber(serverResponseTable["playerId"])
|
||||
end
|
||||
if playerId == Players.LocalPlayer.UserId then
|
||||
dispatch(completePurchase())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function onBundlePurchaseRequest(player, bundleId)
|
||||
if player == Players.LocalPlayer then
|
||||
dispatch(initiateBundlePurchase(bundleId))
|
||||
end
|
||||
end
|
||||
|
||||
local function onPremiumPurchaseRequest(player)
|
||||
if player == Players.LocalPlayer then
|
||||
dispatch(initiatePremiumPurchase())
|
||||
end
|
||||
end
|
||||
|
||||
local function onSubscriptionPurchaseRequest(player, subscriptionId)
|
||||
if player == Players.LocalPlayer then
|
||||
dispatch(initiateSubscriptionPurchase(subscriptionId))
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
onPurchaseRequest = onPurchaseRequest,
|
||||
onRobloxPurchaseRequest = onRobloxPurchaseRequest,
|
||||
onProductPurchaseRequest = onProductPurchaseRequest,
|
||||
onPurchaseGamePassRequest = onPurchaseGamePassRequest,
|
||||
onServerPurchaseVerification = onServerPurchaseVerification,
|
||||
onBundlePurchaseRequest = onBundlePurchaseRequest,
|
||||
onPremiumPurchaseRequest = onPremiumPurchaseRequest,
|
||||
onSubscriptionPurchaseRequest = onSubscriptionPurchaseRequest,
|
||||
}
|
||||
end)(MarketplaceServiceEventConnector)
|
||||
|
||||
return MarketplaceServiceEventConnector
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
local t = PurchasePromptDeps.t
|
||||
|
||||
local LocalizationService = require(Root.Localization.LocalizationService)
|
||||
|
||||
local LocalizationContextConsumer = require(script.Parent.LocalizationContextConsumer)
|
||||
|
||||
local validateProps = t.strictInterface({
|
||||
keys = t.table,
|
||||
render = t.callback,
|
||||
})
|
||||
|
||||
local validateItem = t.strictInterface({
|
||||
key = t.string,
|
||||
params = t.optional(t.table),
|
||||
})
|
||||
|
||||
local function MultiTextLocalizer(props)
|
||||
assert(validateProps(props))
|
||||
for _, item in pairs(props.keys) do
|
||||
assert(validateItem(item))
|
||||
end
|
||||
|
||||
local render = props.render
|
||||
|
||||
return Roact.createElement(LocalizationContextConsumer, {
|
||||
render = function(localizationContext)
|
||||
local textMap = {}
|
||||
for key, item in pairs(props.keys) do
|
||||
textMap[key] = LocalizationService.getString(localizationContext, item.key, item.params)
|
||||
end
|
||||
return render(textMap)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
return MultiTextLocalizer
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
--[[
|
||||
Connects to MarketplaceService's callback for completing a native purchase, so that we can
|
||||
retry after an upsell purchase was processed
|
||||
]]
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local MarketplaceService = game:GetService("MarketplaceService")
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local ErrorOccurred = require(Root.Actions.ErrorOccurred)
|
||||
local PurchaseError = require(Root.Enums.PurchaseError)
|
||||
local retryAfterUpsell = require(Root.Thunks.retryAfterUpsell)
|
||||
local connectToStore = require(Root.connectToStore)
|
||||
|
||||
local ExternalEventConnection = require(script.Parent.ExternalEventConnection)
|
||||
|
||||
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
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local LocalizationService = require(Root.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
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
--[[
|
||||
Connects to MarketplaceService's callback for completing a native purchase, so that we can
|
||||
retry after an upsell purchase was processed
|
||||
]]
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local Players = game:GetService("Players")
|
||||
local ABTestService = game:GetService("ABTestService")
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local connectToStore = require(Root.connectToStore)
|
||||
|
||||
local ExternalEventConnection = require(script.Parent.ExternalEventConnection)
|
||||
|
||||
local function PlayerConnector(props)
|
||||
local playerConnects = props.playerConnects
|
||||
|
||||
return Roact.createElement(ExternalEventConnection, {
|
||||
event = Players.PlayerAdded,
|
||||
callback = playerConnects,
|
||||
})
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
playerConnects = function(player)
|
||||
if player == Players.LocalPlayer then
|
||||
ABTestService:InitializeForUserId(Players.LocalPlayer.UserId)
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
PlayerConnector = connectToStore(
|
||||
nil,
|
||||
mapDispatchToProps
|
||||
)(PlayerConnector)
|
||||
|
||||
return PlayerConnector
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local LocalizationService = require(Root.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
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
--[[
|
||||
Helper for supplying the current Roblox Locale to the Roact
|
||||
tree via context using the LocalizationContextProvider
|
||||
]]
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local LocalizationService = game:GetService("LocalizationService")
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local getLocalizationContext = require(Root.Localization.getLocalizationContext)
|
||||
|
||||
local LocalizationContextProvider = require(script.Parent.LocalizationContextProvider)
|
||||
|
||||
local function provideRobloxLocale(renderFunc)
|
||||
return Roact.createElement(LocalizationContextProvider, {
|
||||
localizationContext = getLocalizationContext(LocalizationService.RobloxLocaleId),
|
||||
render = renderFunc
|
||||
})
|
||||
end
|
||||
|
||||
return provideRobloxLocale
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
--[[
|
||||
Helpful wrapper around LayoutValuesConsumer to make it a
|
||||
little less verbose to use
|
||||
]]
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local LayoutValuesConsumer = require(script.Parent.LayoutValuesConsumer)
|
||||
|
||||
local function withLayoutValues(renderFunc)
|
||||
return Roact.createElement(LayoutValuesConsumer, {
|
||||
render = renderFunc,
|
||||
})
|
||||
end
|
||||
|
||||
return withLayoutValues
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
local TextService = game:GetService("TextService")
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local UIBlox = PurchasePromptDeps.UIBlox
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
local t = PurchasePromptDeps.t
|
||||
|
||||
local withStyle = UIBlox.Style.withStyle
|
||||
|
||||
local AutoSizedText = Roact.PureComponent:extend("AutoSizedText")
|
||||
|
||||
local validateProps = t.strictInterface({
|
||||
text = t.string,
|
||||
width = t.number,
|
||||
layoutOrder = t.number,
|
||||
})
|
||||
|
||||
function AutoSizedText:render()
|
||||
assert(validateProps(self.props))
|
||||
|
||||
return withStyle(function(stylePalette)
|
||||
local theme = stylePalette.Theme
|
||||
local fonts = stylePalette.Font
|
||||
|
||||
local text = self.props.text
|
||||
local textSize = fonts.Body.RelativeSize * fonts.BaseSize
|
||||
local font = fonts.Body.Font
|
||||
|
||||
local totalTextSize
|
||||
if text ~= nil then
|
||||
totalTextSize = TextService:GetTextSize(text, textSize, font, Vector2.new(self.props.width, 10000))
|
||||
else
|
||||
totalTextSize = Vector2.new(0, 0)
|
||||
end
|
||||
|
||||
return Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(1, 0, 0, totalTextSize.Y),
|
||||
BackgroundTransparency = 1,
|
||||
Text = text,
|
||||
TextSize = textSize,
|
||||
TextColor3 = theme.TextDefault.Color,
|
||||
TextTransparency = theme.TextDefault.Transparency,
|
||||
Font = font,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextWrapped = true,
|
||||
LayoutOrder = self.props.layoutOrder,
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return AutoSizedText
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local UnitTestContainer = require(Root.Test.UnitTestContainer)
|
||||
|
||||
local AutoSizedText = require(script.Parent.AutoSizedText)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(UnitTestContainer, nil, {
|
||||
Roact.createElement(AutoSizedText, {
|
||||
text = "Test",
|
||||
width = 0,
|
||||
layoutOrder = 0,
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
local TextService = game:GetService("TextService")
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local UIBlox = PurchasePromptDeps.UIBlox
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
local t = PurchasePromptDeps.t
|
||||
local Cryo = PurchasePromptDeps.Cryo
|
||||
|
||||
local Images = UIBlox.App.ImageSet.Images
|
||||
local withStyle = UIBlox.Style.withStyle
|
||||
local IconSize = UIBlox.App.Constant.IconSize
|
||||
|
||||
local CHECK_ICON = "icons/status/success_small"
|
||||
local TEXT_LEFT_PADDING = IconSize.Small + 16
|
||||
|
||||
local BulletPoint = Roact.PureComponent:extend("BulletPoint")
|
||||
|
||||
local validateProps = t.strictInterface({
|
||||
text = t.string,
|
||||
width = t.number,
|
||||
layoutOrder = t.number,
|
||||
})
|
||||
|
||||
function BulletPoint:render()
|
||||
assert(validateProps(self.props))
|
||||
|
||||
return withStyle(function(stylePalette)
|
||||
local theme = stylePalette.Theme
|
||||
local fonts = stylePalette.Font
|
||||
|
||||
local text = self.props.text
|
||||
local textSize = fonts.Body.RelativeSize * fonts.BaseSize
|
||||
local font = fonts.Body.Font
|
||||
|
||||
local totalTextSize
|
||||
if text ~= nil then
|
||||
totalTextSize = TextService:GetTextSize(text, textSize, font,
|
||||
Vector2.new(self.props.width - TEXT_LEFT_PADDING, 10000))
|
||||
else
|
||||
totalTextSize = Vector2.new(0, 0)
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, totalTextSize.Y),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = self.props.layoutOrder,
|
||||
} , {
|
||||
Roact.createElement("ImageLabel", Cryo.Dictionary.join(Images[CHECK_ICON], {
|
||||
Position = UDim2.new(0, 2, 0, 2),
|
||||
Size = UDim2.new(0, IconSize.Small, 0, IconSize.Small),
|
||||
ImageColor3 = theme.IconDefault.Color,
|
||||
ImageTransparency = theme.IconDefault.Transparency,
|
||||
BackgroundTransparency = 1,
|
||||
})),
|
||||
Roact.createElement("TextLabel", {
|
||||
Position = UDim2.new(0, TEXT_LEFT_PADDING, 0, 0),
|
||||
Size = UDim2.new(1, -TEXT_LEFT_PADDING, 0, totalTextSize.Y),
|
||||
BackgroundTransparency = 1,
|
||||
Text = text,
|
||||
TextSize = textSize,
|
||||
TextColor3 = theme.TextDefault.Color,
|
||||
TextTransparency = theme.TextDefault.Transparency,
|
||||
Font = font,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextWrapped = true,
|
||||
LayoutOrder = self.props.layoutOrder,
|
||||
})
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return BulletPoint
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local UnitTestContainer = require(Root.Test.UnitTestContainer)
|
||||
|
||||
local BulletPoint = require(script.Parent.BulletPoint)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(UnitTestContainer, nil, {
|
||||
Roact.createElement(BulletPoint, {
|
||||
text = "Test",
|
||||
width = 0,
|
||||
layoutOrder = 0,
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
local Cryo = PurchasePromptDeps.Cryo
|
||||
local FitFrame = PurchasePromptDeps.FitFrame
|
||||
local FitFrameVertical = FitFrame.FitFrameVertical
|
||||
local UIBlox = PurchasePromptDeps.UIBlox
|
||||
local PartialPageModal = UIBlox.App.Dialog.Modal.PartialPageModal
|
||||
local ButtonType = UIBlox.App.Button.Enum.ButtonType
|
||||
local Images = UIBlox.App.ImageSet.Images
|
||||
|
||||
local PromptState = require(Root.Enums.PromptState)
|
||||
local launchPremiumUpsell = require(Root.Thunks.launchPremiumUpsell)
|
||||
local hideWindow = require(Root.Thunks.hideWindow)
|
||||
local connectToStore = require(Root.connectToStore)
|
||||
|
||||
local MultiTextLocalizer = require(script.Parent.Parent.Connection.MultiTextLocalizer)
|
||||
|
||||
local AutoSizedText = require(script.Parent.AutoSizedText)
|
||||
local BulletPoint = require(script.Parent.BulletPoint)
|
||||
|
||||
local PREMIUM_ICON = "icons/graphic/premium_large"
|
||||
|
||||
local PremiumModal = Roact.Component:extend(script.Name)
|
||||
|
||||
local PREMIUM_MODAL_LOC_KEY = "CoreScripts.PremiumModal.%s"
|
||||
|
||||
local CONTENT_PADDING = 24
|
||||
local CONDENSED_CONTENT_PADDING = 12
|
||||
local ICON_SIZE = 80
|
||||
local CONDENSED_ICON_SIZE = 48
|
||||
|
||||
-- TODO(esauer):
|
||||
-- Make the 120 calculation come directly from UIBlox Modal
|
||||
-- self.updateContentSizes should automatically know how to make the calculation
|
||||
|
||||
function PremiumModal:init()
|
||||
self.isCondensed = false
|
||||
-- Hack :( The modal should tell you what the width is, this just prevents having to render
|
||||
-- 540 (Max modal width) - 24 * 2 (Side paddings)
|
||||
self.contentSize = Vector2.new(self.props.screenSize.X > 492 and 492 or self.props.screenSize.X, 0)
|
||||
self.contentSizes, self.changeContentSizes = Roact.createBinding({
|
||||
padding = UDim.new(0, CONTENT_PADDING),
|
||||
iconSize = UDim2.new(1, 0, 0, ICON_SIZE)
|
||||
})
|
||||
|
||||
self.purchasePremium = function()
|
||||
self.props.purchasePremium()
|
||||
end
|
||||
|
||||
self.updateContentSizes = function()
|
||||
-- 120 is the height of the components of the modal not including the customized content
|
||||
if self.isCondensed then
|
||||
self.isCondensed = self.props.screenSize.Y < self.contentSize.Y + 120
|
||||
+ ICON_SIZE - CONDENSED_ICON_SIZE
|
||||
+ (CONTENT_PADDING - CONDENSED_CONTENT_PADDING) * 2
|
||||
else
|
||||
self.isCondensed = self.props.screenSize.Y <= self.contentSize.Y + 120
|
||||
end
|
||||
|
||||
self.changeContentSizes({
|
||||
padding = UDim.new(0, self.isCondensed and CONDENSED_CONTENT_PADDING or CONTENT_PADDING),
|
||||
iconSize = UDim2.new(1, 0, 0, self.isCondensed and CONDENSED_ICON_SIZE or ICON_SIZE)
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function PremiumModal:didUpdate(prevProps)
|
||||
if self.props.screenSize ~= prevProps.screenSize then
|
||||
self.updateContentSizes()
|
||||
end
|
||||
end
|
||||
|
||||
function PremiumModal:render()
|
||||
local promptState = self.props.promptState
|
||||
local premiumProductInfo = self.props.premiumProductInfo
|
||||
local screenSize = self.props.screenSize
|
||||
|
||||
return Roact.createElement(MultiTextLocalizer, {
|
||||
keys = {
|
||||
titleLocalizedText = {
|
||||
key = PREMIUM_MODAL_LOC_KEY:format("Title.PremiumRequired"),
|
||||
},
|
||||
monthlyLocalizedText = {
|
||||
key = PREMIUM_MODAL_LOC_KEY:format("Action.PricePerMonth"),
|
||||
params = {
|
||||
price = premiumProductInfo.currencySymbol..tostring(premiumProductInfo.price)
|
||||
},
|
||||
},
|
||||
descLocalizedText = {
|
||||
key = PREMIUM_MODAL_LOC_KEY:format("Body.Description"),
|
||||
},
|
||||
bulletPoint1Text = {
|
||||
key = PREMIUM_MODAL_LOC_KEY:format("Body.RobuxMonthlyV2"),
|
||||
params = {
|
||||
robux = premiumProductInfo.robuxAmount
|
||||
},
|
||||
},
|
||||
bulletPoint2Text = {
|
||||
key = PREMIUM_MODAL_LOC_KEY:format("Body.PremiumOnlyAreas"),
|
||||
},
|
||||
bulletPoint3Text = {
|
||||
key = PREMIUM_MODAL_LOC_KEY:format("Body.RobuxDiscount"),
|
||||
},
|
||||
},
|
||||
render = function(locTextMap)
|
||||
return Roact.createElement(PartialPageModal, {
|
||||
title = locTextMap.titleLocalizedText,
|
||||
screenSize = screenSize,
|
||||
buttonStackProps = {
|
||||
buttons = {
|
||||
{
|
||||
buttonType = ButtonType.PrimarySystem,
|
||||
props = {
|
||||
isDisabled = promptState ~= PromptState.PremiumUpsell,
|
||||
onActivated = self.purchasePremium,
|
||||
text = locTextMap.monthlyLocalizedText,
|
||||
},
|
||||
},
|
||||
},
|
||||
buttonHeight = 48,
|
||||
},
|
||||
onCloseClicked = self.props.setHideWindow
|
||||
}, {
|
||||
Roact.createElement(FitFrameVertical, {
|
||||
BackgroundTransparency = 1,
|
||||
width = UDim.new(1, 0),
|
||||
contentPadding = UDim.new(0, 24),
|
||||
margin = {
|
||||
top = 24,
|
||||
bottom = 24,
|
||||
},
|
||||
[Roact.Change.AbsoluteSize] = function(rbx)
|
||||
self.contentSize = rbx.AbsoluteSize
|
||||
self.updateContentSizes()
|
||||
end
|
||||
}, {
|
||||
Icon = Roact.createElement("ImageLabel", Cryo.Dictionary.join(Images[PREMIUM_ICON], {
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Size = self.contentSizes:map(function(values)
|
||||
return values.iconSize
|
||||
end),
|
||||
ScaleType = Enum.ScaleType.Fit,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 1,
|
||||
})),
|
||||
Roact.createElement(AutoSizedText, {
|
||||
text = locTextMap.descLocalizedText,
|
||||
width = self.contentSize.X,
|
||||
layoutOrder = 2,
|
||||
}),
|
||||
Roact.createElement(FitFrameVertical, {
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 3,
|
||||
width = UDim.new(1, 0),
|
||||
contentPadding = self.contentSizes:map(function(values)
|
||||
return values.padding
|
||||
end),
|
||||
}, {
|
||||
Bullet1 = Roact.createElement(BulletPoint, {
|
||||
text = locTextMap.bulletPoint1Text,
|
||||
width = self.contentSize.X,
|
||||
layoutOrder = 1,
|
||||
}),
|
||||
Bullet2 = Roact.createElement(BulletPoint, {
|
||||
text = locTextMap.bulletPoint2Text,
|
||||
width = self.contentSize.X,
|
||||
layoutOrder = 2,
|
||||
}),
|
||||
Bullet3 = Roact.createElement(BulletPoint, {
|
||||
text = locTextMap.bulletPoint3Text,
|
||||
width = self.contentSize.X,
|
||||
layoutOrder = 3,
|
||||
})
|
||||
}),
|
||||
})
|
||||
})
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
premiumProductInfo = state.premiumProductInfo,
|
||||
promptState = state.promptState,
|
||||
requestType = state.promptRequest.requestType,
|
||||
purchaseError = state.purchaseError,
|
||||
windowState = state.windowState,
|
||||
}
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
purchasePremium = function()
|
||||
dispatch(launchPremiumUpsell())
|
||||
end,
|
||||
setHideWindow = function()
|
||||
dispatch(hideWindow())
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
PremiumModal = connectToStore(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(PremiumModal)
|
||||
|
||||
return PremiumModal
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
local Rodux = PurchasePromptDeps.Rodux
|
||||
|
||||
local PromptState = require(Root.Enums.PromptState)
|
||||
local RequestType = require(Root.Enums.RequestType)
|
||||
local WindowState = require(Root.Enums.WindowState)
|
||||
local Reducer = require(Root.Reducers.Reducer)
|
||||
local UnitTestContainer = require(Root.Test.UnitTestContainer)
|
||||
|
||||
local PremiumModal = require(script.Parent.PremiumModal)
|
||||
PremiumModal = PremiumModal.getUnconnected()
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(UnitTestContainer, {
|
||||
overrideStore = Rodux.Store.new(Reducer)
|
||||
}, {
|
||||
Roact.createElement(PremiumModal, {
|
||||
premiumProductInfo = {
|
||||
premiumFeatureTypeName = "Subscription",
|
||||
mobileProductId = "com.roblox.robloxmobile.RobloxPremium450",
|
||||
description = "Roblox Premium 450",
|
||||
price = 4.99,
|
||||
robuxAmount = 450,
|
||||
isSubscriptionOnly = false,
|
||||
currencySymbol = "$",
|
||||
},
|
||||
promptState = PromptState.PremiumUpsell,
|
||||
promptRequest = {
|
||||
requestType = RequestType.Premium
|
||||
},
|
||||
windowState = WindowState.Hidden,
|
||||
screenSize = Vector2.new(100, 100)
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
local GuiService = game:GetService("GuiService")
|
||||
local ContextActionService = game:GetService("ContextActionService")
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
local Otter = PurchasePromptDeps.Otter
|
||||
local UIBlox = PurchasePromptDeps.UIBlox
|
||||
local InteractiveAlert = UIBlox.App.Dialog.Alert.InteractiveAlert
|
||||
local ButtonType = UIBlox.App.Button.Enum.ButtonType
|
||||
|
||||
local PurchaseError = require(Root.Enums.PurchaseError)
|
||||
local PromptState = require(Root.Enums.PromptState)
|
||||
local RequestType = require(Root.Enums.RequestType)
|
||||
local WindowState = require(Root.Enums.WindowState)
|
||||
local completeRequest = require(Root.Thunks.completeRequest)
|
||||
local launchPremiumUpsell = require(Root.Thunks.launchPremiumUpsell)
|
||||
local hideWindow = require(Root.Thunks.hideWindow)
|
||||
local connectToStore = require(Root.connectToStore)
|
||||
|
||||
local ExternalEventConnection = require(script.Parent.Parent.Connection.ExternalEventConnection)
|
||||
local MultiTextLocalizer = require(script.Parent.Parent.Connection.MultiTextLocalizer)
|
||||
|
||||
local PremiumModal = require(script.Parent.PremiumModal)
|
||||
|
||||
local PREMIUM_MODAL_LOC_KEY = "CoreScripts.PremiumModal.%s"
|
||||
local PREMIUM_BUTTON_BIND = "PremiumBackButton"
|
||||
|
||||
local PremiumPrompt = Roact.Component:extend(script.Name)
|
||||
|
||||
local SPRING_CONFIG = {
|
||||
dampingRatio = 1,
|
||||
frequency = 1.6,
|
||||
}
|
||||
|
||||
local function isRelevantRequestType(requestType)
|
||||
return requestType == RequestType.Premium
|
||||
end
|
||||
|
||||
function PremiumPrompt:init()
|
||||
self.state = {
|
||||
screenSize = Vector2.new(0, 0),
|
||||
}
|
||||
|
||||
self.changeScreenSize = function(rbx)
|
||||
if self.state.screenSize ~= rbx.AbsoluteSize then
|
||||
self:setState({
|
||||
screenSize = rbx.AbsoluteSize,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local animationProgress, setProgress = Roact.createBinding(0)
|
||||
|
||||
self.motor = Otter.createSingleMotor(0)
|
||||
self.motor:start()
|
||||
|
||||
self.motor:onStep(setProgress)
|
||||
self.animationProgress = animationProgress
|
||||
|
||||
self.motor:onComplete(function()
|
||||
-- Do not complete the request if we are waiting for the browser or native purchase to complete
|
||||
if self.props.windowState == WindowState.Hidden
|
||||
and isRelevantRequestType(self.props.requestType)
|
||||
and self.props.promptState ~= PromptState.UpsellInProgress then
|
||||
self.props.setCompleteRequest()
|
||||
end
|
||||
end)
|
||||
|
||||
self.onClose = function()
|
||||
self.props.hideWindow()
|
||||
end
|
||||
end
|
||||
|
||||
function PremiumPrompt:didUpdate(prevProps, prevState)
|
||||
if prevProps.windowState ~= self.props.windowState then
|
||||
local goal = (self.props.windowState == WindowState.Hidden or not isRelevantRequestType(self.props.requestType)) and 0 or 1
|
||||
self.motor:setGoal(Otter.spring(goal, SPRING_CONFIG))
|
||||
|
||||
if self.props.windowState == WindowState.Shown then
|
||||
ContextActionService:BindCoreAction(
|
||||
PREMIUM_BUTTON_BIND,
|
||||
function(actionName, inputState, inputObj)
|
||||
if inputState == Enum.UserInputState.Begin then
|
||||
self.onClose()
|
||||
end
|
||||
end, false, Enum.KeyCode.ButtonB)
|
||||
else
|
||||
ContextActionService:UnbindCoreAction(PREMIUM_BUTTON_BIND)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PremiumPrompt:RenderError()
|
||||
local purchaseError = self.props.purchaseError
|
||||
|
||||
local errorKey
|
||||
if purchaseError == PurchaseError.AlreadyPremium then
|
||||
errorKey = "Error.AlreadyPremium"
|
||||
elseif purchaseError == PurchaseError.PremiumUnavailablePlatform then
|
||||
errorKey = "Error.PlatformUnavailable"
|
||||
else
|
||||
errorKey = "Error.Unavailable"
|
||||
end
|
||||
|
||||
return Roact.createElement(MultiTextLocalizer, {
|
||||
keys = {
|
||||
titleLocalizedText = {
|
||||
key = PREMIUM_MODAL_LOC_KEY:format("Title.Error"),
|
||||
},
|
||||
errorLocalizedText = {
|
||||
key = PREMIUM_MODAL_LOC_KEY:format(errorKey),
|
||||
},
|
||||
okLocalizedText = {
|
||||
key = "CoreScripts.PurchasePrompt.Button.OK",
|
||||
},
|
||||
},
|
||||
render = function(textMap)
|
||||
return Roact.createElement(InteractiveAlert, {
|
||||
bodyText = textMap.errorLocalizedText,
|
||||
buttonStackInfo = {
|
||||
buttons = {
|
||||
{
|
||||
buttonType = ButtonType.PrimarySystem,
|
||||
props = {
|
||||
onActivated = self.onClose,
|
||||
text = textMap.okLocalizedText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
screenSize = self.state.screenSize,
|
||||
title = textMap.titleLocalizedText,
|
||||
})
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
function PremiumPrompt:render()
|
||||
local promptState = self.props.promptState
|
||||
local requestType = self.props.requestType
|
||||
|
||||
local contents
|
||||
if promptState == PromptState.None or not isRelevantRequestType(requestType) then
|
||||
--[[
|
||||
When the prompt is hidden, we'd rather not keep unused Roblox
|
||||
instances for it around, so we don't render them
|
||||
]]
|
||||
contents = nil
|
||||
else
|
||||
if promptState == PromptState.Error then
|
||||
contents = self:RenderError()
|
||||
else
|
||||
contents = Roact.createElement(PremiumModal,{
|
||||
screenSize = self.state.screenSize,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
AnchorPoint = Vector2.new(0.5, 0),
|
||||
Position = self.animationProgress:map(function(value)
|
||||
return UDim2.new(0.5, 0, 1 - value, 0)
|
||||
end),
|
||||
[Roact.Change.AbsoluteSize] = self.changeScreenSize,
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
PremiumPrompt = contents,
|
||||
OnCoreGuiMenuOpened = Roact.createElement(ExternalEventConnection, {
|
||||
event = GuiService.MenuOpened,
|
||||
callback = self.onClose,
|
||||
})
|
||||
})
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
premiumProductInfo = state.premiumProductInfo,
|
||||
promptState = state.promptState,
|
||||
requestType = state.promptRequest.requestType,
|
||||
purchaseError = state.purchaseError,
|
||||
windowState = state.windowState,
|
||||
}
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
purchasePremium = function()
|
||||
dispatch(launchPremiumUpsell())
|
||||
end,
|
||||
hideWindow = function()
|
||||
dispatch(hideWindow())
|
||||
end,
|
||||
setCompleteRequest = function()
|
||||
dispatch(completeRequest())
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
PremiumPrompt = connectToStore(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(PremiumPrompt)
|
||||
|
||||
return PremiumPrompt
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
local Rodux = PurchasePromptDeps.Rodux
|
||||
|
||||
local PromptState = require(Root.Enums.PromptState)
|
||||
local RequestType = require(Root.Enums.RequestType)
|
||||
local WindowState = require(Root.Enums.WindowState)
|
||||
local Reducer = require(Root.Reducers.Reducer)
|
||||
local UnitTestContainer = require(Root.Test.UnitTestContainer)
|
||||
|
||||
local PremiumPrompt = require(script.Parent.PremiumPrompt)
|
||||
PremiumPrompt = PremiumPrompt.getUnconnected()
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(UnitTestContainer, {
|
||||
overrideStore = Rodux.Store.new(Reducer)
|
||||
}, {
|
||||
Roact.createElement(PremiumPrompt, {
|
||||
premiumProductInfo = {
|
||||
premiumFeatureTypeName = "Subscription",
|
||||
mobileProductId = "com.roblox.robloxmobile.RobloxPremium450",
|
||||
description = "Roblox Premium 450",
|
||||
price = 4.99,
|
||||
robuxAmount = 450,
|
||||
isSubscriptionOnly = false,
|
||||
currencySymbol = "$",
|
||||
},
|
||||
promptState = PromptState.PremiumUpsell,
|
||||
promptRequest = {
|
||||
requestType = RequestType.Premium
|
||||
},
|
||||
windowState = WindowState.Hidden,
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
|
||||
local PromptState = require(Root.Enums.PromptState)
|
||||
local UpsellFlow = require(Root.Enums.UpsellFlow)
|
||||
local LocalizationService = require(Root.Localization.LocalizationService)
|
||||
local getUpsellFlow = require(Root.NativeUpsell.getUpsellFlow)
|
||||
local isMockingPurchases = require(Root.Utils.isMockingPurchases)
|
||||
local getPlayerPrice = require(Root.Utils.getPlayerPrice)
|
||||
local connectToStore = require(Root.connectToStore)
|
||||
|
||||
local TextLocalizer = require(script.Parent.Parent.Connection.TextLocalizer)
|
||||
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
|
||||
|
||||
local PURCHASE_DETAILS_KEY = "CoreScripts.PurchasePrompt.PurchaseDetails.%s"
|
||||
|
||||
local function AdditionalDetailLabel(props)
|
||||
return withLayoutValues(function(values)
|
||||
local layoutOrder = props.layoutOrder
|
||||
local messageKey = props.messageKey
|
||||
local messageParams = props.messageParams
|
||||
|
||||
if messageKey == nil then
|
||||
-- We return an empty frame to preserve UIListLayout spacing
|
||||
return Roact.createElement("Frame", {
|
||||
LayoutOrder = layoutOrder,
|
||||
Size = values.Size.AdditionalDetailsLabel,
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
end
|
||||
|
||||
return Roact.createElement(TextLocalizer, {
|
||||
key = messageKey,
|
||||
params = messageParams,
|
||||
render = function(localizedText)
|
||||
return Roact.createElement("TextLabel", {
|
||||
Text = localizedText,
|
||||
LayoutOrder = layoutOrder,
|
||||
Size = values.Size.AdditionalDetailsLabel,
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
TextColor3 = Color3.new(1, 1, 1),
|
||||
Font = Enum.Font.SourceSans,
|
||||
TextSize = values.TextSize.AdditionalDetails,
|
||||
TextYAlignment = Enum.TextYAlignment.Top,
|
||||
TextScaled = true,
|
||||
TextWrapped = true,
|
||||
}, {
|
||||
TextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
|
||||
MaxTextSize = values.TextSize.AdditionalDetails,
|
||||
})
|
||||
})
|
||||
end,
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
local promptState = state.promptState
|
||||
|
||||
local messageKey = nil
|
||||
local messageParams = nil
|
||||
|
||||
local isPlayerPremium = state.accountInfo.membershipType == 4
|
||||
local price = getPlayerPrice(state.productInfo, isPlayerPremium)
|
||||
local balance = state.accountInfo.balance
|
||||
|
||||
if promptState == PromptState.PromptPurchase then
|
||||
if price == 0 then
|
||||
messageKey = PURCHASE_DETAILS_KEY:format("BalanceUnaffected")
|
||||
elseif isMockingPurchases() then
|
||||
messageKey = PURCHASE_DETAILS_KEY:format("MockPurchase")
|
||||
else
|
||||
messageKey = PURCHASE_DETAILS_KEY:format("BalanceFutureV2")
|
||||
messageParams = {
|
||||
BALANCE_FUTURE = LocalizationService.numberParam(balance - price),
|
||||
}
|
||||
end
|
||||
elseif promptState == PromptState.RobuxUpsell then
|
||||
local upsellFlow = getUpsellFlow(UserInputService:GetPlatform())
|
||||
|
||||
if upsellFlow ~= UpsellFlow.Web then
|
||||
local upsellRobux = state.nativeUpsell.robuxPurchaseAmount
|
||||
local amountNeeded = price - balance
|
||||
|
||||
local amountRemaining = upsellRobux - amountNeeded
|
||||
messageKey = PURCHASE_DETAILS_KEY:format("RemainingAfterUpsell")
|
||||
messageParams = {
|
||||
REMAINING_ROBUX = LocalizationService.numberParam(amountRemaining),
|
||||
}
|
||||
end
|
||||
elseif promptState == PromptState.PurchaseComplete then
|
||||
if isMockingPurchases() then
|
||||
messageKey = PURCHASE_DETAILS_KEY:format("MockPurchaseComplete")
|
||||
else
|
||||
messageKey = PURCHASE_DETAILS_KEY:format("BalanceNow")
|
||||
messageParams = {
|
||||
BALANCE_NOW = LocalizationService.numberParam(balance - price),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
messageKey = messageKey,
|
||||
messageParams = messageParams,
|
||||
}
|
||||
end
|
||||
|
||||
local AdditionalDetailLabel = connectToStore(
|
||||
mapStateToProps
|
||||
)(AdditionalDetailLabel)
|
||||
|
||||
return AdditionalDetailLabel
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local UnitTestContainer = require(Root.Test.UnitTestContainer)
|
||||
|
||||
local AdditionalDetailLabel = require(script.Parent.AdditionalDetailLabel)
|
||||
|
||||
AdditionalDetailLabel = AdditionalDetailLabel.getUnconnected()
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(UnitTestContainer, nil, {
|
||||
Roact.createElement(AdditionalDetailLabel, {
|
||||
layoutOrder = 1,
|
||||
messageKey = "test",
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
|
||||
it("should create and destroy without errors when showing no text", function()
|
||||
local emptyMessageElement = Roact.createElement(UnitTestContainer, nil, {
|
||||
Roact.createElement(AdditionalDetailLabel, {
|
||||
layoutOrder = 1,
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(emptyMessageElement)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local GRAY = Color3.fromRGB(184, 184, 184)
|
||||
local BLUE = Color3.fromRGB(0, 162, 255)
|
||||
|
||||
local function Dot(props)
|
||||
local layoutOrder = props.layoutOrder
|
||||
local time = props.time
|
||||
local size = 0.8
|
||||
local color = GRAY
|
||||
|
||||
if time >= layoutOrder -1 and time <= layoutOrder then
|
||||
local animationProgress = math.sin(math.pi * (time % 1))
|
||||
size = size + (1 - size) * animationProgress
|
||||
color = GRAY:lerp(BLUE, animationProgress)
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1/3, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
LayoutOrder = layoutOrder,
|
||||
}, {
|
||||
Dot = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0.7, 0, size, 0),
|
||||
SizeConstraint = Enum.SizeConstraint.RelativeYY,
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
BackgroundColor3 = color,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
})
|
||||
end
|
||||
|
||||
return Dot
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local AnimatedDot = require(script.Parent.AnimatedDot)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(AnimatedDot, {
|
||||
time = 1,
|
||||
layoutOrder = 1,
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
local Cryo = PurchasePromptDeps.Cryo
|
||||
|
||||
local AutoResizeList = Roact.Component:extend("AutoResizeList")
|
||||
|
||||
function AutoResizeList:init()
|
||||
self.containerRef = Roact.createRef()
|
||||
self.listRef = Roact.createRef()
|
||||
|
||||
self.contentSizeCallback = function()
|
||||
if self.listRef.current and self.containerRef.current then
|
||||
self:resizeContainer()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function AutoResizeList:didMount()
|
||||
self:resizeContainer()
|
||||
end
|
||||
|
||||
function AutoResizeList:resizeContainer()
|
||||
self.containerRef.current.Size = UDim2.new(
|
||||
0, self.listRef.current.AbsoluteContentSize.X,
|
||||
0, self.listRef.current.AbsoluteContentSize.Y)
|
||||
end
|
||||
|
||||
function AutoResizeList:render()
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
local position = self.props.position
|
||||
local anchorPoint = self.props.anchorPoint
|
||||
local backgroundImage = self.props.backgroundImage
|
||||
local sliceCenter = self.props.sliceCenter
|
||||
|
||||
local horizontalAlignment = self.props.horizontalAlignment
|
||||
local verticalAlignment = self.props.verticalAlignment
|
||||
local fillDirection = self.props.fillDirection
|
||||
local listPadding = self.props.listPadding
|
||||
|
||||
local children = Cryo.Dictionary.join(self.props[Roact.Children] or {}, {
|
||||
["$ListLayout"] = Roact.createElement("UIListLayout", {
|
||||
HorizontalAlignment = horizontalAlignment,
|
||||
VerticalAlignment = verticalAlignment,
|
||||
FillDirection = fillDirection,
|
||||
Padding = listPadding,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
|
||||
[Roact.Ref] = self.listRef,
|
||||
[Roact.Change.AbsoluteContentSize] = self.contentSizeCallback,
|
||||
})
|
||||
})
|
||||
|
||||
if backgroundImage == nil then
|
||||
return Roact.createElement("Frame", {
|
||||
LayoutOrder = layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
Position = position,
|
||||
AnchorPoint = anchorPoint,
|
||||
|
||||
[Roact.Ref] = self.containerRef,
|
||||
}, children)
|
||||
else
|
||||
local scaleType = (sliceCenter ~= nil) and Enum.ScaleType.Slice or Enum.ScaleType.Stretch
|
||||
return Roact.createElement("ImageLabel", {
|
||||
Image = backgroundImage,
|
||||
LayoutOrder = layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
ScaleType = scaleType,
|
||||
SliceCenter = sliceCenter,
|
||||
Position = position,
|
||||
AnchorPoint = anchorPoint,
|
||||
|
||||
[Roact.Ref] = self.containerRef,
|
||||
}, children)
|
||||
end
|
||||
end
|
||||
|
||||
return AutoResizeList
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local AutoResizeList = require(script.Parent.AutoResizeList)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(AutoResizeList, {
|
||||
layoutOrder = 1,
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
local TextService = game:GetService("TextService")
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
local Cryo = PurchasePromptDeps.Cryo
|
||||
|
||||
local AutoSizedTextLabel = Roact.PureComponent:extend("AutoSizedTextLabel")
|
||||
|
||||
function AutoSizedTextLabel:render()
|
||||
local text = self.props.Text
|
||||
local textSize = self.props.TextSize
|
||||
local font = self.props.Font
|
||||
local width = self.props.width
|
||||
local maxHeight = self.props.maxHeight
|
||||
|
||||
local totalTextSize
|
||||
if text ~= nil then
|
||||
totalTextSize = TextService:GetTextSize(text, textSize, font, Vector2.new(width, 10000))
|
||||
else
|
||||
totalTextSize = Vector2.new(0, 0)
|
||||
end
|
||||
|
||||
local height = totalTextSize.Y
|
||||
if maxHeight and height > maxHeight then
|
||||
height = maxHeight
|
||||
end
|
||||
|
||||
local textLabelProps = Cryo.Dictionary.join(self.props, {
|
||||
width = Cryo.None,
|
||||
maxHeight = Cryo.None,
|
||||
Size = UDim2.new(0, width, 0, height),
|
||||
})
|
||||
|
||||
return Roact.createElement("TextLabel", textLabelProps)
|
||||
end
|
||||
|
||||
return AutoSizedTextLabel
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local AutoSizedTextLabel = require(script.Parent.AutoSizedTextLabel)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(AutoSizedTextLabel, {
|
||||
Text = "Hello",
|
||||
TextSize = 10,
|
||||
Font = Enum.Font.SourceSans,
|
||||
width = 100,
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
|
||||
it("should not throw even if no text is provided", function()
|
||||
local element = Roact.createElement(AutoSizedTextLabel, {
|
||||
TextSize = 10,
|
||||
Font = Enum.Font.SourceSans,
|
||||
width = 100,
|
||||
})
|
||||
|
||||
expect(Roact.mount(element)).to.be.ok()
|
||||
end)
|
||||
|
||||
it("should clamp its height if maxHeight was provided", function()
|
||||
local element = Roact.createElement(AutoSizedTextLabel, {
|
||||
Text = "Really long text that should get to a height larger than 1 line!",
|
||||
TextSize = 10,
|
||||
Font = Enum.Font.SourceSans,
|
||||
width = 100,
|
||||
maxHeight = 2,
|
||||
})
|
||||
|
||||
local folder = Instance.new("Frame")
|
||||
Roact.mount(element, folder)
|
||||
local textLabel = folder:FindFirstChildWhichIsA("GuiObject")
|
||||
|
||||
expect(textLabel.Size.Y.Offset).to.equal(2)
|
||||
end)
|
||||
end
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
local ContextActionService = game:GetService("ContextActionService")
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local ButtonState = require(Root.Enums.ButtonState)
|
||||
local WindowState = require(Root.Enums.WindowState)
|
||||
local connectToStore = require(Root.connectToStore)
|
||||
|
||||
local TextLocalizer = require(script.Parent.Parent.Connection.TextLocalizer)
|
||||
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
|
||||
|
||||
local GetFFlagPurchasePromptScaryModalV2 = require(Root.Flags.GetFFlagPurchasePromptScaryModalV2)
|
||||
local GetFFlagFixAcceptingCanceledPurchased = require(Root.Flags.GetFFlagFixAcceptingCanceledPurchased)
|
||||
|
||||
local Button = Roact.PureComponent:extend("Button")
|
||||
|
||||
function Button:init()
|
||||
local onClick = self.props.onClick
|
||||
local imageDown = self.props.imageDown
|
||||
local imageUp = self.props.imageUp
|
||||
|
||||
self.state = {
|
||||
currentImage = imageUp
|
||||
}
|
||||
|
||||
self.inputBegan = function()
|
||||
self:setState({
|
||||
currentImage = imageDown
|
||||
})
|
||||
end
|
||||
|
||||
self.inputEnded = function()
|
||||
self:setState({
|
||||
currentImage = imageUp
|
||||
})
|
||||
end
|
||||
|
||||
self.activated = function()
|
||||
if GetFFlagFixAcceptingCanceledPurchased() and self.props.windowState == WindowState.Hidden then
|
||||
return
|
||||
end
|
||||
|
||||
if GetFFlagPurchasePromptScaryModalV2() and self.props.buttonState ~= ButtonState.Enabled then
|
||||
return
|
||||
end
|
||||
|
||||
if GetFFlagPurchasePromptScaryModalV2() then
|
||||
self.props.onClick()
|
||||
else
|
||||
onClick()
|
||||
end
|
||||
self:setState({
|
||||
currentImage = imageUp
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function Button:didMount()
|
||||
-- Some buttons need to support additional button bindings
|
||||
local bindings = self.props.additionalBindings or {}
|
||||
table.insert(bindings, self.props.gamepadButton)
|
||||
|
||||
ContextActionService:BindCoreAction(
|
||||
self.props.stringKey,
|
||||
function(actionName, inputState, inputObj)
|
||||
--[[
|
||||
CLILUACORE-521:
|
||||
InputState MUST be 'Begin' in this case; otherwise, opening
|
||||
the settings menu it will create new ContextActionService
|
||||
bindings. When it does this, they trigger the 'Cancel' input
|
||||
state, which invoke our binding (in order to tell it that
|
||||
it's being canceled)
|
||||
]]
|
||||
if inputState == Enum.UserInputState.Begin then
|
||||
if GetFFlagFixAcceptingCanceledPurchased() then
|
||||
self.activated()
|
||||
else
|
||||
self.props.onClick()
|
||||
end
|
||||
end
|
||||
end,
|
||||
false,
|
||||
unpack(bindings)
|
||||
)
|
||||
end
|
||||
|
||||
function Button:willUnmount()
|
||||
ContextActionService:UnbindCoreAction(self.props.stringKey)
|
||||
end
|
||||
|
||||
function Button:render()
|
||||
assert(typeof(self.props.gamepadButton) == "EnumItem"
|
||||
and self.props.gamepadButton.EnumType == Enum.KeyCode,
|
||||
"Prop 'gamepadButton' is required and must be of type Enum.KeyCode")
|
||||
|
||||
return withLayoutValues(function(values)
|
||||
local stringKey = self.props.stringKey
|
||||
local font = self.props.font
|
||||
local size = self.props.size
|
||||
local position = self.props.position
|
||||
local buttonState = self.props.buttonState
|
||||
|
||||
local gamepadEnabled = self.props.gamepadEnabled
|
||||
local gamepadButton = self.props.gamepadButton
|
||||
|
||||
local imageData = values.Image[self.state.currentImage]
|
||||
|
||||
if GetFFlagPurchasePromptScaryModalV2() and buttonState ~= ButtonState.Enabled then
|
||||
imageData = values.Image[self.props.imageDown]
|
||||
end
|
||||
|
||||
local buttonContents = {
|
||||
ButtonLabel = Roact.createElement(TextLocalizer, {
|
||||
key = stringKey,
|
||||
render = function(localizedText)
|
||||
return Roact.createElement("TextLabel", {
|
||||
Text = localizedText,
|
||||
Font = font,
|
||||
Size = UDim2.new(0.6, 0, 0.8, 0),
|
||||
Position = UDim2.new(0.2, 0, 0.1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
TextColor3 = Color3.new(1, 1, 1),
|
||||
TextSize = values.TextSize.Button,
|
||||
TextScaled = true,
|
||||
TextWrapped = false,
|
||||
LayoutOrder = 2,
|
||||
}, {
|
||||
TextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
|
||||
MaxTextSize = values.TextSize.Button,
|
||||
}),
|
||||
})
|
||||
end,
|
||||
})
|
||||
}
|
||||
|
||||
if gamepadEnabled then
|
||||
-- Using a frame allows the icon to be left-aligned and still
|
||||
-- be in a size-constrained section of the overall button
|
||||
buttonContents.ButtonIcon = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0.2, 0, 0.8, 0),
|
||||
Position = UDim2.new(0, 0, 0.1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
}, {
|
||||
Icon = Roact.createElement("ImageLabel", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Position = UDim2.new(0, values.Size.ButtonIconPadding, 0, values.Size.ButtonIconYOffset),
|
||||
SizeConstraint = Enum.SizeConstraint.RelativeYY,
|
||||
ScaleType = Enum.ScaleType.Fit,
|
||||
Image = values.Image[gamepadButton.Name].Path,
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
})
|
||||
end
|
||||
|
||||
return Roact.createElement("ImageButton", {
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
AutoButtonColor = false,
|
||||
Modal = true,
|
||||
|
||||
Size = size,
|
||||
Position = position,
|
||||
ScaleType = Enum.ScaleType.Slice,
|
||||
Image = imageData.Path,
|
||||
SliceCenter = imageData.SliceCenter,
|
||||
|
||||
[Roact.Event.InputBegan] = self.inputBegan,
|
||||
[Roact.Event.InputEnded] = self.inputEnded,
|
||||
[Roact.Event.Activated] = self.activated,
|
||||
}, buttonContents)
|
||||
end)
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
gamepadEnabled = state.gamepadEnabled,
|
||||
buttonState = state.buttonState,
|
||||
windowState = state.windowState,
|
||||
}
|
||||
end
|
||||
|
||||
Button = connectToStore(mapStateToProps)(Button)
|
||||
|
||||
return Button
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local UnitTestContainer = require(Root.Test.UnitTestContainer)
|
||||
|
||||
local Button = require(script.Parent.Button)
|
||||
|
||||
Button = Button.getUnconnected()
|
||||
|
||||
it("should create and destroy without errors with gamepad disabled", function()
|
||||
local element = Roact.createElement(UnitTestContainer, nil, {
|
||||
Roact.createElement(Button, {
|
||||
gamepadEnabled = false,
|
||||
|
||||
stringKey = "testing123",
|
||||
gamepadButton = Enum.KeyCode.ButtonA,
|
||||
font = Enum.Font.SourceSans,
|
||||
imageUp = "ButtonUp",
|
||||
imageDown = "ButtonDown",
|
||||
onClick = function()
|
||||
end,
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
|
||||
it("should create and destroy without errors with gamepad enabled", function()
|
||||
local element = Roact.createElement(UnitTestContainer, nil, {
|
||||
Roact.createElement(Button, {
|
||||
gamepadEnabled = true,
|
||||
|
||||
stringKey = "testing123",
|
||||
gamepadButton = Enum.KeyCode.ButtonA,
|
||||
font = Enum.Font.SourceSans,
|
||||
imageUp = "ButtonUp",
|
||||
imageDown = "ButtonDown",
|
||||
onClick = function()
|
||||
end,
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local Button = require(script.Parent.Button)
|
||||
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
|
||||
|
||||
local function CancelButton(props)
|
||||
return withLayoutValues(function(values)
|
||||
local onClick = props.onClick
|
||||
|
||||
return Roact.createElement(Button, {
|
||||
font = Enum.Font.SourceSans,
|
||||
imageUp = "ButtonUpRight",
|
||||
imageDown = "ButtonDownRight",
|
||||
gamepadButton = Enum.KeyCode.ButtonB,
|
||||
|
||||
stringKey = "CoreScripts.PurchasePrompt.CancelPurchase.Cancel",
|
||||
size = UDim2.new(0.5, 0, 1, 0),
|
||||
position = UDim2.new(0.5, 0, 0, 0),
|
||||
|
||||
onClick = onClick,
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return CancelButton
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local UnitTestContainer = require(Root.Test.UnitTestContainer)
|
||||
|
||||
local CancelButton = require(script.Parent.CancelButton)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(UnitTestContainer, nil, {
|
||||
Roact.createElement(CancelButton, {
|
||||
onClick = function()
|
||||
end,
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local ClickScamDetector = require(Root.Utils.ClickScamDetector)
|
||||
|
||||
local Button = require(script.Parent.Button)
|
||||
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
|
||||
|
||||
local GetFFlagPurchasePromptScaryModalV2 = require(Root.Flags.GetFFlagPurchasePromptScaryModalV2)
|
||||
|
||||
local CONFIRM_BUTTON = Enum.KeyCode.ButtonA
|
||||
|
||||
local ConfirmButton = Roact.Component:extend("ConfirmButton")
|
||||
|
||||
function ConfirmButton:init()
|
||||
local onClick = self.props.onClick
|
||||
|
||||
self.activated = function()
|
||||
if self.clickScamDetector:isClickValid() then
|
||||
if GetFFlagPurchasePromptScaryModalV2() then
|
||||
self.props.onClick()
|
||||
else
|
||||
onClick()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.clickScamDetector = ClickScamDetector.new({
|
||||
buttonInput = CONFIRM_BUTTON,
|
||||
initialDelay = 0.5,
|
||||
})
|
||||
end
|
||||
|
||||
function ConfirmButton:willUnmount()
|
||||
self.clickScamDetector:destroy()
|
||||
end
|
||||
|
||||
function ConfirmButton:render()
|
||||
return withLayoutValues(function(values)
|
||||
local stringKey = self.props.stringKey
|
||||
|
||||
return Roact.createElement(Button, {
|
||||
font = Enum.Font.SourceSansBold,
|
||||
imageUp = "ButtonUpLeft",
|
||||
imageDown = "ButtonDownLeft",
|
||||
gamepadButton = CONFIRM_BUTTON,
|
||||
|
||||
stringKey = stringKey,
|
||||
size = UDim2.new(0.5, 0, 1, 0),
|
||||
|
||||
onClick = self.activated,
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return ConfirmButton
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local UnitTestContainer = require(Root.Test.UnitTestContainer)
|
||||
|
||||
local ConfirmButton = require(script.Parent.ConfirmButton)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(UnitTestContainer, nil, {
|
||||
Roact.createElement(ConfirmButton, {
|
||||
stringKey = "testing123",
|
||||
onClick = function()
|
||||
end,
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local TextLocalizer = require(script.Parent.Parent.Connection.TextLocalizer)
|
||||
|
||||
local AutoSizedTextLabel = require(script.Parent.AutoSizedTextLabel)
|
||||
local PurchasingAnimation = require(script.Parent.PurchasingAnimation)
|
||||
|
||||
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
|
||||
|
||||
local function InProgressContents(props)
|
||||
return withLayoutValues(function(values)
|
||||
return Roact.createElement("ImageLabel", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
|
||||
ScaleType = Enum.ScaleType.Slice,
|
||||
Image = values.Image.InProgressBackground.Path,
|
||||
SliceCenter = values.Image.InProgressBackground.SliceCenter,
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
}, {
|
||||
ListLayout = Roact.createElement("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 20)
|
||||
}),
|
||||
PurchasingText = Roact.createElement(TextLocalizer, {
|
||||
key = "CoreScripts.PurchasePrompt.Purchasing",
|
||||
render = function(localizedText)
|
||||
return Roact.createElement(AutoSizedTextLabel, {
|
||||
width = values.Size.Dialog.X.Offset,
|
||||
Text = localizedText,
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
TextColor3 = Color3.new(1, 1, 1),
|
||||
Font = Enum.Font.SourceSans,
|
||||
TextSize = values.TextSize.Purchasing,
|
||||
TextXAlignment = Enum.TextXAlignment.Center,
|
||||
TextYAlignment = Enum.TextYAlignment.Center,
|
||||
TextScaled = true,
|
||||
TextWrapped = true,
|
||||
LayoutOrder = 1,
|
||||
}, {
|
||||
TextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
|
||||
MaxTextSize = values.TextSize.Purchasing,
|
||||
}),
|
||||
})
|
||||
end,
|
||||
}),
|
||||
PurchasingAnimation = Roact.createElement(PurchasingAnimation, {
|
||||
layoutOrder = 2,
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return InProgressContents
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local UnitTestContainer = require(Root.Test.UnitTestContainer)
|
||||
|
||||
local InProgressContents = require(script.Parent.InProgressContents)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(UnitTestContainer, nil, {
|
||||
Roact.createElement(InProgressContents, {
|
||||
anchorPoint = Vector2.new(0, 0),
|
||||
position = UDim2.new(0, 0, 0, 0),
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Cryo = PurchasePromptDeps.Cryo
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
local UIBlox = PurchasePromptDeps.UIBlox
|
||||
local UIBloxImages = UIBlox.App.ImageSet.Images
|
||||
|
||||
local PurchaseError = require(Root.Enums.PurchaseError)
|
||||
local PromptState = require(Root.Enums.PromptState)
|
||||
local connectToStore = require(Root.connectToStore)
|
||||
|
||||
local PREMIUM_ICON = UIBloxImages["icons/graphic/premium_large"]
|
||||
local ADULT_ERROR_ICON = UIBloxImages["icons/status/error_large"]
|
||||
|
||||
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
|
||||
|
||||
local function ItemPreviewImage(props)
|
||||
return withLayoutValues(function(values)
|
||||
local layoutOrder = props.layoutOrder
|
||||
local promptState = props.promptState
|
||||
local purchaseError = props.purchaseError
|
||||
local productImageUrl = props.productImageUrl
|
||||
|
||||
local showPremiumIcon = false
|
||||
local showAdultErrorIcon = false
|
||||
local backgroundTransparency = 0
|
||||
if promptState == PromptState.AdultConfirmation or promptState == PromptState.U13PaymentModal
|
||||
or promptState == PromptState.U13MonthlyThreshold1Modal or promptState == PromptState.U13MonthlyThreshold2Modal then
|
||||
showAdultErrorIcon = true
|
||||
backgroundTransparency = 1
|
||||
elseif promptState == PromptState.Error then
|
||||
if purchaseError == PurchaseError.PremiumOnly then
|
||||
showPremiumIcon = true
|
||||
else
|
||||
productImageUrl = values.Image.ErrorIcon.Path
|
||||
end
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = values.Size.ItemPreviewContainerFrame,
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
LayoutOrder = layoutOrder,
|
||||
}, {
|
||||
PremiumIcon = showPremiumIcon and Roact.createElement("ImageLabel", Cryo.Dictionary.join(PREMIUM_ICON, {
|
||||
Size = values.Size.ItemPreview,
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
ImageTransparency = 0,
|
||||
})) or nil,
|
||||
ItemPreviewImageContainer = not showPremiumIcon and Roact.createElement("Frame", {
|
||||
Size = values.Size.ItemPreviewWhiteFrame,
|
||||
BackgroundTransparency = backgroundTransparency,
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
BorderSizePixel = 0,
|
||||
BackgroundColor3 = Color3.new(1, 1, 1),
|
||||
}, {
|
||||
ItemImage = showAdultErrorIcon and Roact.createElement("ImageLabel", Cryo.Dictionary.join(ADULT_ERROR_ICON, {
|
||||
Size = values.Size.ItemPreview,
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
ImageTransparency = 0,
|
||||
}))
|
||||
or Roact.createElement("ImageLabel", {
|
||||
Size = values.Size.ItemPreview,
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
Image = productImageUrl,
|
||||
ImageTransparency = 0,
|
||||
}),
|
||||
}) or nil,
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
promptState = state.promptState,
|
||||
purchaseError = state.purchaseError,
|
||||
productImageUrl = state.productInfo.imageUrl,
|
||||
}
|
||||
end
|
||||
|
||||
local ItemPreviewImage = connectToStore(
|
||||
mapStateToProps
|
||||
)(ItemPreviewImage)
|
||||
|
||||
return ItemPreviewImage
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local UnitTestContainer = require(Root.Test.UnitTestContainer)
|
||||
|
||||
local ItemPreviewImage = require(script.Parent.ItemPreviewImage)
|
||||
|
||||
ItemPreviewImage = ItemPreviewImage.getUnconnected()
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(UnitTestContainer, nil, {
|
||||
Roact.createElement(ItemPreviewImage, {
|
||||
layoutOrder = 1,
|
||||
imageUrl = "",
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local Button = require(script.Parent.Button)
|
||||
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
|
||||
|
||||
local function OkButton(props)
|
||||
return withLayoutValues(function(values)
|
||||
local onClick = props.onClick
|
||||
|
||||
return Roact.createElement(Button, {
|
||||
font = Enum.Font.SourceSans,
|
||||
imageUp = "ButtonUp",
|
||||
imageDown = "ButtonDown",
|
||||
gamepadButton = Enum.KeyCode.ButtonA,
|
||||
additionalBindings = {
|
||||
Enum.KeyCode.ButtonB,
|
||||
},
|
||||
|
||||
stringKey = "CoreScripts.PurchasePrompt.Button.OK",
|
||||
size = UDim2.new(1, 0, 0, values.Size.ButtonHeight-4),
|
||||
|
||||
onClick = onClick
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return OkButton
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local UnitTestContainer = require(Root.Test.UnitTestContainer)
|
||||
|
||||
local OkButton = require(script.Parent.OkButton)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(UnitTestContainer, nil, {
|
||||
Roact.createElement(OkButton, {
|
||||
onClick = function()
|
||||
end,
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local NumberLocalizer = require(script.Parent.Parent.Connection.NumberLocalizer)
|
||||
local AutoResizeList = require(script.Parent.AutoResizeList)
|
||||
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
|
||||
|
||||
return function(props)
|
||||
return withLayoutValues(function(values)
|
||||
local layoutOrder = props.layoutOrder
|
||||
local price = props.price
|
||||
|
||||
return Roact.createElement(AutoResizeList, {
|
||||
layoutOrder = layoutOrder,
|
||||
horizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
verticalAlignment = Enum.VerticalAlignment.Center,
|
||||
fillDirection = Enum.FillDirection.Horizontal,
|
||||
}, {
|
||||
RobuxIconContainer = Roact.createElement("Frame", {
|
||||
BorderSizePixel = 0,
|
||||
BackgroundTransparency = 1,
|
||||
Size = values.Size.RobuxIconContainerFrame,
|
||||
LayoutOrder = 1,
|
||||
}, {
|
||||
RobuxIcon = Roact.createElement("ImageLabel", {
|
||||
Size = values.Size.RobuxIcon,
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
AnchorPoint = Vector2.new(0, 0.5),
|
||||
Position = UDim2.new(0, 0, 0.5, 0),
|
||||
Image = values.Image.RobuxIcon.Path,
|
||||
}),
|
||||
}),
|
||||
PriceTextLabel = Roact.createElement(NumberLocalizer, {
|
||||
number = price,
|
||||
render = function(localizedNumber)
|
||||
return Roact.createElement("TextLabel", {
|
||||
Text = localizedNumber,
|
||||
LayoutOrder = 2,
|
||||
Size = values.Size.PriceTextLabel,
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
TextColor3 = values.TextColor.PriceLabel,
|
||||
Font = Enum.Font.SourceSansBold,
|
||||
TextSize = values.TextSize.Default,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextScaled = true,
|
||||
TextWrapped = true,
|
||||
}, {
|
||||
TextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
|
||||
MaxTextSize = values.TextSize.Default,
|
||||
})
|
||||
})
|
||||
end,
|
||||
})
|
||||
})
|
||||
end)
|
||||
end
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local UnitTestContainer = require(Root.Test.UnitTestContainer)
|
||||
|
||||
local Price = require(script.Parent.Price)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(UnitTestContainer, nil, {
|
||||
Roact.createElement(Price, {
|
||||
layoutOrder = 1,
|
||||
imageUrl = "",
|
||||
price = 50,
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local PromptState = require(Root.Enums.PromptState)
|
||||
local PurchaseError = require(Root.Enums.PurchaseError)
|
||||
|
||||
local LocalizationService = require(Root.Localization.LocalizationService)
|
||||
|
||||
local TextLocalizer = require(script.Parent.Parent.Connection.TextLocalizer)
|
||||
local AutoSizedTextLabel = require(script.Parent.AutoSizedTextLabel)
|
||||
local Price = require(script.Parent.Price)
|
||||
|
||||
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
|
||||
|
||||
local getPlayerPrice = require(Root.Utils.getPlayerPrice)
|
||||
local connectToStore = require(Root.connectToStore)
|
||||
|
||||
local PURCHASE_MESSAGE_KEY = "CoreScripts.PurchasePrompt.PurchaseMessage.%s"
|
||||
|
||||
local function ProductDescription(props)
|
||||
return withLayoutValues(function(values)
|
||||
local layoutOrder = props.layoutOrder
|
||||
local descriptionKey = props.descriptionKey
|
||||
local descriptionParams = props.descriptionParams
|
||||
local showPrice = props.showPrice
|
||||
local price = props.price
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
BorderSizePixel = 0,
|
||||
Size = values.Size.ProductDescription,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = layoutOrder,
|
||||
}, {
|
||||
ProductDescriptionPadding = Roact.createElement("UIPadding", {
|
||||
PaddingTop = UDim.new(0, values.Size.ProductDescriptionPaddingTop),
|
||||
}),
|
||||
ProductDescriptionListLayout = Roact.createElement("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
}),
|
||||
ProductDescriptionText = Roact.createElement(TextLocalizer, {
|
||||
key = descriptionKey,
|
||||
params = descriptionParams,
|
||||
render = function(localizedText)
|
||||
return Roact.createElement(AutoSizedTextLabel, {
|
||||
width = values.Size.ProductDescription.X.Offset - values.Size.HorizontalPadding,
|
||||
maxHeight = showPrice
|
||||
and values.Size.ProductDescription.Y.Offset - values.Size.RobuxIconContainerFrame.Y.Offset
|
||||
or values.Size.ProductDescription.Y.Offset,
|
||||
Text = localizedText,
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
TextColor3 = Color3.new(1, 1, 1),
|
||||
Font = Enum.Font.SourceSans,
|
||||
TextSize = values.TextSize.ProductDescription,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Top,
|
||||
TextScaled = true,
|
||||
TextWrapped = true,
|
||||
LayoutOrder = 1,
|
||||
}, {
|
||||
TextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
|
||||
MaxTextSize = values.TextSize.ProductDescription,
|
||||
}),
|
||||
})
|
||||
end,
|
||||
}),
|
||||
Price = showPrice and Roact.createElement(Price, {
|
||||
layoutOrder = 2,
|
||||
price = price,
|
||||
}) or nil,
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
local promptState = state.promptState
|
||||
local isPlayerPremium = state.accountInfo.membershipType == 4
|
||||
local price = getPlayerPrice(state.productInfo, isPlayerPremium)
|
||||
local isFree = price == 0
|
||||
local canPurchase = promptState ~= PromptState.Error
|
||||
|
||||
local descriptionKey
|
||||
local descriptionParams
|
||||
|
||||
|
||||
if promptState == PromptState.PurchaseComplete then
|
||||
descriptionKey = PURCHASE_MESSAGE_KEY:format("Succeeded")
|
||||
descriptionParams = {
|
||||
ITEM_NAME = state.productInfo.name,
|
||||
NEEDED_AMOUNT = LocalizationService.numberParam(
|
||||
price - state.accountInfo.balance
|
||||
),
|
||||
ASSET_TYPE = LocalizationService.nestedKeyParam(
|
||||
LocalizationService.getKeyFromItemType(state.productInfo.itemType))
|
||||
}
|
||||
elseif promptState == PromptState.RobuxUpsell then
|
||||
descriptionKey = PURCHASE_MESSAGE_KEY:format("NeedMoreRobux")
|
||||
descriptionParams = {
|
||||
ITEM_NAME = state.productInfo.name,
|
||||
NEEDED_AMOUNT = LocalizationService.numberParam(
|
||||
price - state.accountInfo.balance
|
||||
),
|
||||
ASSET_TYPE = LocalizationService.nestedKeyParam(
|
||||
LocalizationService.getKeyFromItemType(state.productInfo.itemType))
|
||||
}
|
||||
elseif promptState == PromptState.AdultConfirmation then
|
||||
descriptionKey = "CoreScripts.PurchasePrompt.PurchaseDetails.AgeLegalText"
|
||||
elseif promptState == PromptState.U13PaymentModal then
|
||||
descriptionKey = "CoreScripts.PurchasePrompt.PurchaseDetails.ScaryModalOne"
|
||||
elseif promptState == PromptState.U13MonthlyThreshold1Modal then
|
||||
descriptionKey = "CoreScripts.PurchasePrompt.PurchaseDetails.ScaryModalTwo"
|
||||
elseif promptState == PromptState.U13MonthlyThreshold2Modal then
|
||||
descriptionKey = "CoreScripts.PurchasePrompt.PurchaseDetails.ScaryModalParental"
|
||||
elseif promptState == PromptState.Error then
|
||||
descriptionKey = LocalizationService.getErrorKey(state.purchaseError)
|
||||
if state.purchaseError == PurchaseError.UnknownFailure then
|
||||
descriptionParams = {
|
||||
ITEM_NAME = state.productInfo.name
|
||||
}
|
||||
end
|
||||
elseif promptState ~= PromptState.None then
|
||||
if isFree then
|
||||
descriptionKey = PURCHASE_MESSAGE_KEY:format("Free")
|
||||
descriptionParams = {
|
||||
ITEM_NAME = state.productInfo.name,
|
||||
}
|
||||
else
|
||||
descriptionKey = PURCHASE_MESSAGE_KEY:format("Purchase")
|
||||
descriptionParams = {
|
||||
ASSET_TYPE = LocalizationService.nestedKeyParam(
|
||||
LocalizationService.getKeyFromItemType(state.productInfo.itemType)),
|
||||
ITEM_NAME = state.productInfo.name,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
descriptionKey = descriptionKey,
|
||||
descriptionParams = descriptionParams,
|
||||
showPrice = not isFree and canPurchase and (promptState ~= PromptState.AdultConfirmation
|
||||
and promptState ~= PromptState.U13PaymentModal and promptState ~= PromptState.U13MonthlyThreshold1Modal
|
||||
and promptState ~= PromptState.U13MonthlyThreshold2Modal),
|
||||
price = price,
|
||||
}
|
||||
end
|
||||
|
||||
local ProductDescription = connectToStore(
|
||||
mapStateToProps
|
||||
)(ProductDescription)
|
||||
|
||||
return ProductDescription
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
local Rodux = PurchasePromptDeps.Rodux
|
||||
|
||||
local Reducer = require(Root.Reducers.Reducer)
|
||||
local UnitTestContainer = require(Root.Test.UnitTestContainer)
|
||||
|
||||
local ProductDescription = require(script.Parent.ProductDescription)
|
||||
|
||||
ProductDescription = ProductDescription.getUnconnected()
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(UnitTestContainer, {
|
||||
overrideStore = Rodux.Store.new(Reducer, {
|
||||
productInfo = {
|
||||
price = 10,
|
||||
},
|
||||
})
|
||||
}, {
|
||||
Roact.createElement(ProductDescription, {
|
||||
descriptionKey = "CoreScripts.PurchasePrompt.PurchaseMessage.Purchase",
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local PromptState = require(Root.Enums.PromptState)
|
||||
local purchaseItem = require(Root.Thunks.purchaseItem)
|
||||
local launchRobuxUpsell = require(Root.Thunks.launchRobuxUpsell)
|
||||
local initiatePurchasePrecheck = require(Root.Thunks.initiatePurchasePrecheck)
|
||||
local getPlayerPrice = require(Root.Utils.getPlayerPrice)
|
||||
local connectToStore = require(Root.connectToStore)
|
||||
|
||||
local ConfirmButton = require(script.Parent.ConfirmButton)
|
||||
local CancelButton = require(script.Parent.CancelButton)
|
||||
local OkButton = require(script.Parent.OkButton)
|
||||
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
|
||||
|
||||
local GetFFlagPurchasePromptScaryModalV2 = require(Root.Flags.GetFFlagPurchasePromptScaryModalV2)
|
||||
|
||||
local CONFIRM_PURCHASE_KEY = "CoreScripts.PurchasePrompt.ConfirmPurchase.%s"
|
||||
|
||||
local PromptButtons = Roact.PureComponent:extend("PromptButtons")
|
||||
|
||||
function PromptButtons:render()
|
||||
return withLayoutValues(function(values)
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
local onClose = self.props.onClose
|
||||
local promptState = self.props.promptState
|
||||
local price = self.props.price
|
||||
|
||||
local onBuy = self.props.onBuy
|
||||
local onRobuxUpsell = self.props.onRobuxUpsell
|
||||
local onScaryModalConfirm = self.props.onScaryModalConfirm
|
||||
|
||||
local children
|
||||
if promptState == PromptState.PurchaseComplete
|
||||
or promptState == PromptState.Error
|
||||
then
|
||||
children = {
|
||||
UIPadding = Roact.createElement("UIPadding", {
|
||||
PaddingBottom = UDim.new(0, 4),
|
||||
}),
|
||||
OkButton = Roact.createElement(OkButton, {
|
||||
onClick = onClose,
|
||||
}),
|
||||
}
|
||||
else
|
||||
local confirmButtonStringKey = CONFIRM_PURCHASE_KEY:format("BuyNow")
|
||||
local leftButtonCallback = onBuy
|
||||
if price == 0 then
|
||||
confirmButtonStringKey = CONFIRM_PURCHASE_KEY:format("TakeFree")
|
||||
elseif promptState == PromptState.RobuxUpsell then
|
||||
confirmButtonStringKey = CONFIRM_PURCHASE_KEY:format("BuyRobuxV2")
|
||||
leftButtonCallback = onRobuxUpsell
|
||||
elseif promptState == PromptState.AdultConfirmation then
|
||||
confirmButtonStringKey = "CoreScripts.PurchasePrompt.Button.OK"
|
||||
leftButtonCallback = onRobuxUpsell
|
||||
elseif promptState == PromptState.U13PaymentModal or promptState == PromptState.U13MonthlyThreshold1Modal
|
||||
or promptState == PromptState.U13MonthlyThreshold2Modal then
|
||||
confirmButtonStringKey = "CoreScripts.PurchasePrompt.Button.OK"
|
||||
leftButtonCallback = onScaryModalConfirm
|
||||
end
|
||||
children = {
|
||||
ConfirmButton = Roact.createElement(ConfirmButton, {
|
||||
stringKey = confirmButtonStringKey,
|
||||
onClick = leftButtonCallback,
|
||||
}),
|
||||
CancelButton = Roact.createElement(CancelButton, {
|
||||
onClick = onClose,
|
||||
}),
|
||||
}
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
LayoutOrder = layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
Size = UDim2.new(1, 0, 0, values.Size.ButtonHeight)
|
||||
}, children)
|
||||
end)
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
local isPlayerPremium = state.accountInfo.membershipType == 4
|
||||
local price = getPlayerPrice(state.productInfo, isPlayerPremium)
|
||||
return {
|
||||
promptState = state.promptState,
|
||||
price = price,
|
||||
}
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
onBuy = function()
|
||||
dispatch(purchaseItem())
|
||||
end,
|
||||
onScaryModalConfirm = function()
|
||||
dispatch(launchRobuxUpsell())
|
||||
end,
|
||||
onRobuxUpsell = function()
|
||||
if (GetFFlagPurchasePromptScaryModalV2()) then
|
||||
dispatch(initiatePurchasePrecheck())
|
||||
else
|
||||
dispatch(launchRobuxUpsell())
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
PromptButtons = connectToStore(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(PromptButtons)
|
||||
|
||||
return PromptButtons
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local PromptState = require(Root.Enums.PromptState)
|
||||
|
||||
local UnitTestContainer = require(Root.Test.UnitTestContainer)
|
||||
|
||||
local PromptButtons = require(script.Parent.PromptButtons)
|
||||
PromptButtons = PromptButtons.getUnconnected()
|
||||
|
||||
local function noop()
|
||||
end
|
||||
|
||||
it("should create and destroy without errors with one button", function()
|
||||
local element = Roact.createElement(UnitTestContainer, nil, {
|
||||
Roact.createElement(PromptButtons, {
|
||||
layoutOrder = 1,
|
||||
onClose = noop,
|
||||
promptState = PromptState.PurchaseComplete,
|
||||
price = 1,
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
|
||||
it("should create and destroy without errors with two buttons", function()
|
||||
local element = Roact.createElement(UnitTestContainer, nil, {
|
||||
Roact.createElement(PromptButtons, {
|
||||
layoutOrder = 1,
|
||||
onClose = noop,
|
||||
promptState = PromptState.PromptPurchase,
|
||||
price = 1,
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local AutoResizeList = require(script.Parent.AutoResizeList)
|
||||
local ItemPreviewImage = require(script.Parent.ItemPreviewImage)
|
||||
local ProductDescription = require(script.Parent.ProductDescription)
|
||||
local PromptButtons = require(script.Parent.PromptButtons)
|
||||
local AdditionalDetailLabel = require(script.Parent.AdditionalDetailLabel)
|
||||
|
||||
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
|
||||
|
||||
local function PromptContents(props)
|
||||
return withLayoutValues(function(values)
|
||||
local onClose = props.onClose
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BorderSizePixel = 0,
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
ListLayout = Roact.createElement("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
}),
|
||||
PromptBody = Roact.createElement(AutoResizeList, {
|
||||
layoutOrder = 1,
|
||||
backgroundImage = values.Image.PromptBackground.Path,
|
||||
sliceCenter = values.Image.PromptBackground.SliceCenter,
|
||||
fillDirection = Enum.FillDirection.Vertical,
|
||||
}, {
|
||||
ProductInfo = Roact.createElement(AutoResizeList, {
|
||||
layoutOrder = 1,
|
||||
fillDirection = Enum.FillDirection.Horizontal,
|
||||
}, {
|
||||
ItemPreviewImage = Roact.createElement(ItemPreviewImage, {
|
||||
layoutOrder = 1,
|
||||
}),
|
||||
ProductDescription = Roact.createElement(ProductDescription, {
|
||||
layoutOrder = 2,
|
||||
})
|
||||
}),
|
||||
AdditionalDetails = Roact.createElement(AdditionalDetailLabel, {
|
||||
layoutOrder = 2,
|
||||
}),
|
||||
}),
|
||||
PromptButtons = Roact.createElement(PromptButtons, {
|
||||
layoutOrder = 2,
|
||||
onClose = onClose,
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return PromptContents
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
local Rodux = PurchasePromptDeps.Rodux
|
||||
|
||||
local PromptState = require(Root.Enums.PromptState)
|
||||
local Reducer = require(Root.Reducers.Reducer)
|
||||
local UnitTestContainer = require(Root.Test.UnitTestContainer)
|
||||
|
||||
local PromptContents = require(script.Parent.PromptContents)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(UnitTestContainer, {
|
||||
promptState = PromptState.PromptPurchase,
|
||||
overrideStore = Rodux.Store.new(Reducer, {
|
||||
promptState = PromptState.PromptPurchase,
|
||||
accountInfo = {
|
||||
balance = 100,
|
||||
},
|
||||
productInfo = {
|
||||
assetTypeId = 2, -- T-shirt
|
||||
price = 10,
|
||||
itemType = 2,
|
||||
},
|
||||
})
|
||||
}, {
|
||||
Roact.createElement(PromptContents, {
|
||||
layoutOrder = 1,
|
||||
onClose = function()
|
||||
end,
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
local GuiService = game:GetService("GuiService")
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
local Otter = PurchasePromptDeps.Otter
|
||||
|
||||
local RequestType = require(Root.Enums.RequestType)
|
||||
local PromptState = require(Root.Enums.PromptState)
|
||||
local WindowState = require(Root.Enums.WindowState)
|
||||
local hideWindow = require(Root.Thunks.hideWindow)
|
||||
local completeRequest = require(Root.Thunks.completeRequest)
|
||||
local connectToStore = require(Root.connectToStore)
|
||||
|
||||
local ExternalEventConnection = require(script.Parent.Parent.Connection.ExternalEventConnection)
|
||||
local PromptContents = require(script.Parent.PromptContents)
|
||||
local InProgressContents = require(script.Parent.InProgressContents)
|
||||
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
|
||||
|
||||
local PurchasePrompt = Roact.Component:extend(script.Name)
|
||||
|
||||
local SPRING_CONFIG = {
|
||||
dampingRatio = 1,
|
||||
frequency = 1.6,
|
||||
}
|
||||
|
||||
local function isRelevantRequestType(requestType)
|
||||
return requestType == RequestType.Asset
|
||||
or requestType == RequestType.Bundle
|
||||
or requestType == RequestType.GamePass
|
||||
or requestType == RequestType.Product
|
||||
or requestType == RequestType.Subscription
|
||||
end
|
||||
|
||||
function PurchasePrompt:init()
|
||||
local animationProgress, setProgress = Roact.createBinding(0)
|
||||
|
||||
self.motor = Otter.createSingleMotor(0)
|
||||
self.motor:start()
|
||||
|
||||
self.motor:onStep(setProgress)
|
||||
self.animationProgress = animationProgress
|
||||
|
||||
self.motor:onComplete(function()
|
||||
if self.props.windowState == WindowState.Hidden and isRelevantRequestType(self.props.requestType) then
|
||||
self.props.completeRequest()
|
||||
end
|
||||
end)
|
||||
|
||||
self.onClose = function()
|
||||
self.props.hideWindow()
|
||||
end
|
||||
end
|
||||
|
||||
function PurchasePrompt:didUpdate(prevProps, prevState)
|
||||
if prevProps.windowState ~= self.props.windowState then
|
||||
local goal = (self.props.windowState == WindowState.Hidden or not isRelevantRequestType(self.props.requestType)) and 0 or 1
|
||||
self.motor:setGoal(Otter.spring(goal, SPRING_CONFIG))
|
||||
end
|
||||
end
|
||||
|
||||
function PurchasePrompt:render()
|
||||
return withLayoutValues(function(values)
|
||||
local promptState = self.props.promptState
|
||||
local requestType = self.props.requestType
|
||||
|
||||
local contents
|
||||
if promptState == PromptState.None or not isRelevantRequestType(requestType) then
|
||||
--[[
|
||||
When the prompt is hidden, we'd rather not keep unused Roblox
|
||||
instances for it around, so we don't render them
|
||||
]]
|
||||
contents = nil
|
||||
elseif promptState == PromptState.PurchaseInProgress or promptState == PromptState.UpsellInProgress then
|
||||
contents = Roact.createElement(InProgressContents)
|
||||
else
|
||||
contents = Roact.createElement(PromptContents, {
|
||||
onClose = self.onClose,
|
||||
})
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = values.Size.Dialog,
|
||||
BorderSizePixel = 0,
|
||||
BackgroundTransparency = 1,
|
||||
AnchorPoint = self.animationProgress:map(function(value)
|
||||
return Vector2.new(0.5, 1 - 0.5 * value)
|
||||
end),
|
||||
Position = self.animationProgress:map(function(value)
|
||||
return UDim2.new(0.5, 0, 0.5 * value, 0)
|
||||
end),
|
||||
}, {
|
||||
PromptContents = contents,
|
||||
OnCoreGuiMenuOpened = Roact.createElement(ExternalEventConnection, {
|
||||
event = GuiService.MenuOpened,
|
||||
callback = self.onClose,
|
||||
})
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
promptState = state.promptState,
|
||||
requestType = state.promptRequest.requestType,
|
||||
windowState = state.windowState,
|
||||
}
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
hideWindow = function()
|
||||
dispatch(hideWindow())
|
||||
end,
|
||||
completeRequest = function()
|
||||
dispatch(completeRequest())
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
PurchasePrompt = connectToStore(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(PurchasePrompt)
|
||||
|
||||
return PurchasePrompt
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
local Rodux = PurchasePromptDeps.Rodux
|
||||
|
||||
local PromptState = require(Root.Enums.PromptState)
|
||||
local Reducer = require(Root.Reducers.Reducer)
|
||||
local UnitTestContainer = require(Root.Test.UnitTestContainer)
|
||||
|
||||
local PurchasePrompt = require(script.Parent.PurchasePrompt)
|
||||
PurchasePrompt = PurchasePrompt.getUnconnected()
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(UnitTestContainer, {
|
||||
overrideStore = Rodux.Store.new(Reducer, {
|
||||
promptState = PromptState.PromptPurchase,
|
||||
accountInfo = {
|
||||
balance = 100,
|
||||
},
|
||||
productInfo = {
|
||||
assetTypeId = 2, -- T-shirt
|
||||
price = 10,
|
||||
itemType = 2,
|
||||
},
|
||||
})
|
||||
}, {
|
||||
Roact.createElement(PurchasePrompt)
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
local Root = script.Parent.Parent.Parent
|
||||
local RunService = game:GetService("RunService")
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local AnimatedDot = require(script.Parent.AnimatedDot)
|
||||
local ExternalEventConnection = require(script.Parent.Parent.Connection.ExternalEventConnection)
|
||||
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
|
||||
|
||||
local ANIMATION_SPEED_MULTIPLIER = 2.00
|
||||
|
||||
local PurchasingAnimation = Roact.Component:extend("PurchasingAnimation")
|
||||
|
||||
function PurchasingAnimation:init()
|
||||
self.state = {
|
||||
gameTime = 0,
|
||||
}
|
||||
|
||||
self.onRenderStepped = function()
|
||||
self:setState({
|
||||
gameTime = Workspace.DistributedGameTime
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function PurchasingAnimation:render()
|
||||
return withLayoutValues(function(values)
|
||||
local layoutOrder = self.props.layoutOrder
|
||||
|
||||
local gameTime = self.state.gameTime
|
||||
|
||||
local animationTime = (gameTime * ANIMATION_SPEED_MULTIPLIER) % 3
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = values.Size.PurchasingAnimation,
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
LayoutOrder = layoutOrder,
|
||||
}, {
|
||||
RenderSteppedConnection = Roact.createElement(ExternalEventConnection, {
|
||||
event = RunService.RenderStepped,
|
||||
callback = self.onRenderStepped,
|
||||
}),
|
||||
ListLayout = Roact.createElement("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
}),
|
||||
AnimatedDot1 = Roact.createElement(AnimatedDot, {
|
||||
layoutOrder = 1,
|
||||
time = animationTime,
|
||||
}),
|
||||
AnimatedDot2 = Roact.createElement(AnimatedDot, {
|
||||
layoutOrder = 2,
|
||||
time = animationTime,
|
||||
}),
|
||||
AnimatedDot3 = Roact.createElement(AnimatedDot, {
|
||||
layoutOrder = 3,
|
||||
time = animationTime,
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return PurchasingAnimation
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local UnitTestContainer = require(Root.Test.UnitTestContainer)
|
||||
|
||||
local PurchasingAnimation = require(script.Parent.PurchasingAnimation)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(UnitTestContainer, nil, {
|
||||
Roact.createElement(PurchasingAnimation, {
|
||||
layoutOrder = 1,
|
||||
})
|
||||
})
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
local Root = script.Parent.Parent
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
local Rodux = PurchasePromptDeps.Rodux
|
||||
local RoactRodux = PurchasePromptDeps.RoactRodux
|
||||
local UIBlox = PurchasePromptDeps.UIBlox
|
||||
local StyleProvider = UIBlox.Style.Provider
|
||||
|
||||
local Reducer = require(Root.Reducers.Reducer)
|
||||
local Network = require(Root.Services.Network)
|
||||
local Analytics = require(Root.Services.Analytics)
|
||||
local PlatformInterface = require(Root.Services.PlatformInterface)
|
||||
local ExternalSettings = require(Root.Services.ExternalSettings)
|
||||
local Thunk = require(Root.Thunk)
|
||||
|
||||
local PremiumPrompt = require(script.Parent.PremiumPrompt.PremiumPrompt)
|
||||
local PurchasePrompt = require(script.Parent.PurchasePrompt.PurchasePrompt)
|
||||
local EventConnections = require(script.Parent.Connection.EventConnections)
|
||||
local LayoutValuesProvider = require(script.Parent.Connection.LayoutValuesProvider)
|
||||
local provideRobloxLocale = require(script.Parent.Connection.provideRobloxLocale)
|
||||
|
||||
local DarkTheme = require(CorePackages.AppTempCommon.LuaApp.Style.Themes.DarkTheme)
|
||||
local Gotham = require(CorePackages.AppTempCommon.LuaApp.Style.Fonts.Gotham)
|
||||
|
||||
local PurchasePromptApp = Roact.Component:extend("PurchasePromptApp")
|
||||
|
||||
function PurchasePromptApp:init()
|
||||
local initialState = {}
|
||||
|
||||
local network = Network.new()
|
||||
local analytics = Analytics.new()
|
||||
local platformInterface = PlatformInterface.new()
|
||||
local externalSettings = ExternalSettings.new()
|
||||
|
||||
self.state = {
|
||||
store = Rodux.Store.new(Reducer, initialState, {
|
||||
Thunk.middleware({
|
||||
[Network] = network,
|
||||
[Analytics] = analytics,
|
||||
[PlatformInterface] = platformInterface,
|
||||
[ExternalSettings] = externalSettings,
|
||||
}),
|
||||
}),
|
||||
isTenFootInterface = externalSettings.isTenFootInterface(),
|
||||
}
|
||||
end
|
||||
|
||||
function PurchasePromptApp:render()
|
||||
return provideRobloxLocale(function()
|
||||
return Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = self.state.store,
|
||||
}, {
|
||||
StyleProvider = Roact.createElement(StyleProvider, {
|
||||
style = {
|
||||
Theme = DarkTheme,
|
||||
Font = Gotham,
|
||||
},
|
||||
}, {
|
||||
PurchasePrompt = Roact.createElement(LayoutValuesProvider, {
|
||||
isTenFootInterface = self.state.isTenFootInterface,
|
||||
render = function()
|
||||
return Roact.createElement("ScreenGui", {
|
||||
AutoLocalize = false,
|
||||
IgnoreGuiInset = true,
|
||||
}, {
|
||||
PremiumPromptUI = Roact.createElement(PremiumPrompt),
|
||||
PurchasePromptUI = Roact.createElement(PurchasePrompt),
|
||||
EventConnections = Roact.createElement(EventConnections),
|
||||
})
|
||||
end
|
||||
})
|
||||
})
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return PurchasePromptApp
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
return function()
|
||||
local Root = script.Parent.Parent
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local PurchasePromptDeps = require(CorePackages.PurchasePromptDeps)
|
||||
local Roact = PurchasePromptDeps.Roact
|
||||
|
||||
local PurchasePromptApp = require(script.Parent.PurchasePromptApp)
|
||||
|
||||
it("should create and destroy without errors", function()
|
||||
local element = Roact.createElement(PurchasePromptApp)
|
||||
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
--[[
|
||||
Enumerated state of the whether the buttons are activated or not
|
||||
- Used to prevent futher action during network calls
|
||||
]]
|
||||
local createEnum = require(script.Parent.createEnum)
|
||||
|
||||
local ButtonState = createEnum("ButtonState", {
|
||||
"Enabled",
|
||||
"Disabled"
|
||||
})
|
||||
|
||||
return ButtonState
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
--[[
|
||||
Enumeration of types of items that can be purchased.
|
||||
]]
|
||||
local createEnum = require(script.Parent.createEnum)
|
||||
|
||||
local ItemType = createEnum("ItemType", {
|
||||
"Asset",
|
||||
"GamePass",
|
||||
"Product",
|
||||
"Bundle",
|
||||
})
|
||||
|
||||
return ItemType
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
--[[
|
||||
Enumerated state of the purchase prompt
|
||||
]]
|
||||
local createEnum = require(script.Parent.createEnum)
|
||||
|
||||
local PromptState = createEnum("PromptState", {
|
||||
"None",
|
||||
"PremiumUpsell",
|
||||
"RobuxUpsell",
|
||||
"PromptPurchase",
|
||||
"PurchaseInProgress",
|
||||
"UpsellInProgress",
|
||||
"AdultConfirmation",
|
||||
"U13PaymentModal",
|
||||
"U13MonthlyThreshold1Modal",
|
||||
"RequireEmailVerification",
|
||||
"U13MonthlyThreshold2Modal",
|
||||
"PurchaseComplete",
|
||||
"Error",
|
||||
})
|
||||
|
||||
return PromptState
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
--[[
|
||||
Enumeration of all possible error states
|
||||
]]
|
||||
local createEnum = require(script.Parent.createEnum)
|
||||
|
||||
local PurchaseError = createEnum("PurchaseError", {
|
||||
-- Pre-purchase network failures
|
||||
"CannotGetBalance",
|
||||
"CannotGetItemPrice",
|
||||
|
||||
-- Premium
|
||||
"AlreadyPremium",
|
||||
"PremiumUnavailable",
|
||||
"PremiumUnavailablePlatform",
|
||||
|
||||
-- Item unvailable
|
||||
"NotForSale",
|
||||
"AlreadyOwn",
|
||||
"PremiumOnly",
|
||||
"Under13",
|
||||
"Limited",
|
||||
"Guest",
|
||||
"ThirdPartyDisabled",
|
||||
"NotEnoughRobux",
|
||||
"NotEnoughRobuxXbox",
|
||||
"NotEnoughRobuxNoUpsell",
|
||||
|
||||
-- Network-reported failures
|
||||
"UnknownFailure",
|
||||
"UnknownFailureNoItemName",
|
||||
"PurchaseDisabled",
|
||||
"InvalidFunds",
|
||||
})
|
||||
|
||||
return PurchaseError
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
--[[
|
||||
Enumeration of possible responses from purchase warning end point
|
||||
]]
|
||||
local createEnum = require(script.Parent.createEnum)
|
||||
|
||||
local PurchaseWarning = createEnum("PurchaseWarning", {
|
||||
"NoAction",
|
||||
"U13PaymentModal",
|
||||
"U13MonthlyThreshold1Modal",
|
||||
"RequireEmailVerification",
|
||||
"U13MonthlyThreshold2Modal",
|
||||
})
|
||||
|
||||
return PurchaseWarning
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
local createEnum = require(script.Parent.createEnum)
|
||||
|
||||
local RequestType = createEnum("RequestType", {
|
||||
"None",
|
||||
"Asset",
|
||||
"Bundle",
|
||||
"GamePass",
|
||||
"Product",
|
||||
"Premium",
|
||||
"Subscription",
|
||||
})
|
||||
|
||||
return RequestType
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
local createEnum = require(script.Parent.createEnum)
|
||||
|
||||
local UpsellFlow = createEnum("UpsellFlow", {
|
||||
"Web",
|
||||
"Mobile",
|
||||
"Xbox",
|
||||
"Unavailable",
|
||||
"None",
|
||||
})
|
||||
|
||||
return UpsellFlow
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local createEnum = require(script.Parent.createEnum)
|
||||
|
||||
local WindowState = createEnum("WindowState", {
|
||||
"Hidden",
|
||||
"Shown",
|
||||
})
|
||||
|
||||
return WindowState
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
--[[
|
||||
An implementation of an enumerated type in Lua. Creates enumerated
|
||||
types with uniquely identifiable values (using symbol)
|
||||
|
||||
Note that resulting enum object does not associate ordinals with its
|
||||
values, and cannot be iterated through. It can, however, test if a provided
|
||||
value is a member of its set of values with the `isMember` function
|
||||
|
||||
This is valuable for the purchase prompt because it relies heavily on
|
||||
enumerated values to determine things like state and which errors occurred.
|
||||
]]
|
||||
local Root = script.Parent.Parent
|
||||
local Symbol = require(Root.Symbols.Symbol)
|
||||
local strict = require(Root.strict)
|
||||
|
||||
--[[
|
||||
Returns a new enum type with the given name.
|
||||
]]
|
||||
local function createEnum(enumName, values)
|
||||
assert(typeof(enumName) == "string", "Bad argument #1, expected string")
|
||||
assert(typeof(values) == "table", "Bad argument #2, expected list of values")
|
||||
|
||||
local enumInternal = {}
|
||||
|
||||
for _, valueName in ipairs(values) do
|
||||
assert(valueName ~= "isMember", "Shadowing 'isMember' function is not allowed")
|
||||
assert(typeof(valueName) == "string", "Only string names are supported for enum types")
|
||||
|
||||
local enumValue = Symbol.named(valueName)
|
||||
local asString = ("%s.%s"):format(enumName, valueName)
|
||||
getmetatable(enumValue).__tostring = function()
|
||||
return asString
|
||||
end
|
||||
|
||||
enumInternal[valueName] = enumValue
|
||||
end
|
||||
|
||||
function enumInternal.isMember(value)
|
||||
if typeof(value) ~= "userdata" then
|
||||
return false
|
||||
end
|
||||
|
||||
for _, enumeratedValue in pairs(enumInternal) do
|
||||
if value == enumeratedValue then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local enum = newproxy(true)
|
||||
getmetatable(enum).__index = enumInternal
|
||||
|
||||
return strict(enumInternal, enumName)
|
||||
end
|
||||
|
||||
return createEnum
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
return function()
|
||||
local createEnum = require(script.Parent.createEnum)
|
||||
|
||||
describe("validation rules", function()
|
||||
it("should throw errors if given invalid values", function()
|
||||
expect(function()
|
||||
createEnum(1, {})
|
||||
end).to.throw()
|
||||
|
||||
expect(function()
|
||||
createEnum("MyEnum", "not a table")
|
||||
end).to.throw()
|
||||
end)
|
||||
|
||||
it("should throw errors if provided table contains invalid values", function()
|
||||
expect(function()
|
||||
createEnum("IllegalShadowing", {
|
||||
"isMember",
|
||||
})
|
||||
end).to.throw()
|
||||
|
||||
expect(function()
|
||||
createEnum("MyEnum", {
|
||||
"Test",
|
||||
12,
|
||||
})
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("enum properties", function()
|
||||
it("should have a reasonable string format for debugging", function()
|
||||
local MyEnum = createEnum("MyEnum", {
|
||||
"Value",
|
||||
})
|
||||
|
||||
expect(tostring(MyEnum.Value)).to.equal("MyEnum.Value")
|
||||
end)
|
||||
|
||||
it("should provide an isMember function to check membership", function()
|
||||
local MyEnum = createEnum("MyEnum", {
|
||||
"Value",
|
||||
})
|
||||
|
||||
expect(MyEnum.isMember(MyEnum.Value)).to.equal(true)
|
||||
expect(MyEnum.isMember(newproxy(true))).to.equal(false)
|
||||
expect(MyEnum.isMember("Value")).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should generate objects that are unique even when named the same", function()
|
||||
|
||||
local Enum1 = createEnum("MyEnum", {
|
||||
"Value",
|
||||
})
|
||||
local Enum2 = createEnum("MyEnum", {
|
||||
"Value",
|
||||
})
|
||||
|
||||
expect(Enum1.isMember(Enum1.Value)).to.equal(true)
|
||||
expect(Enum2.isMember(Enum2.Value)).to.equal(true)
|
||||
|
||||
expect(Enum1.isMember(Enum2.Value)).to.equal(false)
|
||||
expect(Enum2.isMember(Enum1.Value)).to.equal(false)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+1
@@ -0,0 +1 @@
|
||||
return game:DefineFastFlag("BypassThirdPartySettingForRobloxPurchase", false)
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
game:DefineFastFlag("AdultConfirmationEnabledV4", false)
|
||||
|
||||
return function()
|
||||
return game:GetFastFlag("AdultConfirmationEnabledV4")
|
||||
end
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
game:DefineFastFlag("AdultConfirmationEnabledNew", false)
|
||||
|
||||
return function()
|
||||
return game:GetFastFlag("AdultConfirmationEnabledNew")
|
||||
end
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
return function()
|
||||
return settings():GetFFlag("DeveloperSubscriptionsEnabled")
|
||||
end
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
game:DefineFastFlag("DisableRobuxUpsell", false)
|
||||
|
||||
return function()
|
||||
return game:GetFastFlag("DisableRobuxUpsell")
|
||||
end
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
game:DefineFastFlag("FixAcceptingCanceledPurchased", false)
|
||||
|
||||
return function()
|
||||
return game:GetFastFlag("FixAcceptingCanceledPurchased")
|
||||
end
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
game:DefineFastFlag("HideThirdPartyPurchaseFailure", false)
|
||||
|
||||
return function()
|
||||
return game:GetFastFlag("HideThirdPartyPurchaseFailure")
|
||||
end
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
game:DefineFastFlag("IGPPPremiumPriceV2", false)
|
||||
|
||||
return function()
|
||||
return game:GetFastFlag("IGPPPremiumPriceV2")
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user