Improved GUI
This commit is contained in:
parent
caf10ad4fc
commit
8926537bfc
315
atm.lua
315
atm.lua
|
@ -10,17 +10,93 @@ like recording transactions.
|
|||
local g = require("simple-graphics")
|
||||
local bankClient = require("bank-client")
|
||||
|
||||
-- The name of the peripheral where this ATM can draw money from.
|
||||
local CURRENCY_SOURCE = "minecraft:barrel_0"
|
||||
-- The name of the peripheral where this ATM can deposit money to.
|
||||
local CURRENCY_SINK = "minecraft:barrel_1"
|
||||
-- The name of the peripheral where this ATM interacts with the user.
|
||||
local CURRENCY_BIN = "minecraft:barrel_2"
|
||||
|
||||
local BANK_HOST = "central-bank"
|
||||
local SECURITY_KEY = "4514-1691-1660-7358-1884-0506-0878-7098-1511-3359-3602-3581-6910-0791-1843-5936"
|
||||
local modem = peripheral.find("modem") or error("No modem attached.")
|
||||
bankClient.init(peripheral.getName(modem), BANK_HOST, SECURITY_KEY)
|
||||
if not peripheral.isPresent(CURRENCY_SOURCE) then error("No CURRENCY_SOURCE peripheral named \""..CURRENCY_SOURCE.."\" was found.") end
|
||||
if not peripheral.isPresent(CURRENCY_SINK) then error("No CURRENCY_SINK peripheral named \""..CURRENCY_SINK.."\" was found.") end
|
||||
if not peripheral.isPresent(CURRENCY_BIN) then error("No CURRENCY_BIN peripheral named \""..CURRENCY_BIN.."\" was found.") end
|
||||
|
||||
local W, H = term.getSize()
|
||||
|
||||
local function isCurrency(itemStack)
|
||||
return itemStack ~= nil and itemStack.name == "minecraft:sunflower" and itemStack.nbt == "1b95aea642a1b0e9624787ed7227cf35"
|
||||
end
|
||||
|
||||
local function countCurrency(peripheralName)
|
||||
local inv = peripheral.wrap(peripheralName)
|
||||
if not inv then return 0 end
|
||||
local total = 0
|
||||
for slot, itemStack in pairs(inv.list()) do
|
||||
if isCurrency(itemStack) then total = total + itemStack.count end
|
||||
end
|
||||
return total
|
||||
end
|
||||
|
||||
local function getFreeSpace(peripheralName)
|
||||
local inv = peripheral.wrap(peripheralName)
|
||||
if not inv then return 0 end
|
||||
local space = 0
|
||||
for i = 1, inv.size() do
|
||||
local itemStack = inv.getItemDetail(i)
|
||||
if itemStack == nil then
|
||||
space = space + 64
|
||||
elseif isCurrency(itemStack) then
|
||||
space = space + (64 - itemStack.count)
|
||||
end
|
||||
end
|
||||
return space
|
||||
end
|
||||
|
||||
local function transferCurrency(fromName, toName, amount)
|
||||
local sourceInv = peripheral.wrap(fromName)
|
||||
local transferred = 0
|
||||
local attempts = 0
|
||||
while transferred < amount do
|
||||
local items = sourceInv.list()
|
||||
for slot, itemStack in pairs(items) do
|
||||
if isCurrency(itemStack) then
|
||||
local amountToTransfer = math.min(amount - transferred, itemStack.count)
|
||||
local actualTransferred = sourceInv.pushItems(toName, slot, amountToTransfer)
|
||||
transferred = transferred + actualTransferred
|
||||
end
|
||||
end
|
||||
attempts = attempts + 1
|
||||
if attempts > 10 and transferred < amount then
|
||||
return false, transferred
|
||||
end
|
||||
end
|
||||
return true, amount
|
||||
end
|
||||
|
||||
local function shortId(account)
|
||||
return "*" .. string.sub(account.id, -5, -1)
|
||||
end
|
||||
|
||||
local function isDigit(char)
|
||||
if #char ~= 1 then return false end
|
||||
local intValue = string.byte(char) - string.byte("0")
|
||||
return intValue >= 0 and intValue <= 9
|
||||
end
|
||||
|
||||
local function drawFrame()
|
||||
g.clear(term, colors.white)
|
||||
g.drawXLine(term, 1, W, 1, colors.black)
|
||||
g.drawText(term, 2, 1, "ATM", colors.white, colors.black)
|
||||
if bankClient.loggedIn() then
|
||||
local txt = "Logged in as " .. bankClient.state.auth.username
|
||||
local len = #txt
|
||||
g.drawText(term, W-len, 1, "Logged in as", colors.lightGray, colors.black)
|
||||
g.drawText(term, W-len+13, 1, bankClient.state.auth.username, colors.yellow, colors.black)
|
||||
end
|
||||
end
|
||||
|
||||
local function tryReadDiskCredentials(name)
|
||||
|
@ -91,6 +167,8 @@ local function tryLoginViaInput()
|
|||
end
|
||||
elseif keyCode == keys.tab and selectedInput == "username" then
|
||||
selectedInput = "password"
|
||||
elseif keyCode == keys.enter and selectedInput == "password" then
|
||||
return {username = username, password = password} -- Do login right away.
|
||||
end
|
||||
elseif event == "mouse_click" then
|
||||
local button = p1
|
||||
|
@ -153,6 +231,176 @@ local function checkCredentialsUI(credentials)
|
|||
return true
|
||||
end
|
||||
|
||||
local function currencyBinPreviewUpdater(x, y, fg, bg, delay)
|
||||
delay = delay or 1
|
||||
return function()
|
||||
while true do
|
||||
local amount = countCurrency(CURRENCY_BIN)
|
||||
g.drawXLine(term, x, x + 10, y, bg)
|
||||
g.drawText(term, x, y, tostring(amount), fg, bg)
|
||||
os.sleep(delay)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function showDepositUI(account)
|
||||
drawFrame()
|
||||
g.drawTextCenter(term, W/2, 3, "Deposit HandieMarks to your account "..shortId(account)..".", colors.black, colors.white)
|
||||
g.drawTextCenter(term, W/2, 5, "Add currency to the bin, then click to continue.", colors.black, colors.white)
|
||||
|
||||
g.drawText(term, 20, 8, "Amount to Deposit", colors.black, colors.white)
|
||||
|
||||
local continueButtonCoords = g.drawButton(term, 20, 12, 11, 3, "Continue", colors.white, colors.green)
|
||||
local cancelButtonCoords = g.drawButton(term, 20, 16, 11, 3, "Cancel", colors.white, colors.red)
|
||||
|
||||
local state = {cancel = false, doDeposit = false}
|
||||
parallel.waitForAny(
|
||||
currencyBinPreviewUpdater(20, 9, colors.orange, colors.gray, 0.5),
|
||||
function ()
|
||||
while true do
|
||||
local event, button, x, y = os.pullEvent("mouse_click")
|
||||
if button == 1 then
|
||||
if g.isButtonPressed(x, y, continueButtonCoords) and countCurrency(CURRENCY_BIN) > 0 then
|
||||
state.doDeposit = true
|
||||
return
|
||||
elseif g.isButtonPressed(x, y, cancelButtonCoords) then
|
||||
state.cancel = true
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
if state.cancel then return false end
|
||||
if state.doDeposit then
|
||||
local function tryReturnFundsInError(amount)
|
||||
local returnSuccess, returnAmount = transferCurrency(CURRENCY_SOURCE, CURRENCY_BIN, amount)
|
||||
if not returnSuccess then
|
||||
local missingAmount = amount - returnAmount
|
||||
g.appendAndDrawConsole(term, console, "Couldn't return all funds. You are still owed "..tostring(missingAmount).." $HMK. Please contact an administrator for assistance.", cx, cy)
|
||||
os.sleep(3)
|
||||
else
|
||||
g.appendAndDrawConsole(term, console, "Your funds have been returned to the bin. Please collect them.", cx, cy)
|
||||
os.sleep(3)
|
||||
end
|
||||
end
|
||||
-- Clear the buttons and show some status.
|
||||
g.fillRect(term, 1, W, 12, H-11, colors.white)
|
||||
local console = g.createConsole(W/2, H-11, colors.white, colors.black, "UP")
|
||||
local cx = W/2 - W/4
|
||||
local cy = 11
|
||||
local amount = countCurrency(CURRENCY_BIN)
|
||||
g.appendAndDrawConsole(term, console, "Making deposit with value of "..tostring(amount).." $HMK...", cx, cy)
|
||||
os.sleep(1)
|
||||
local success, actualAmount = transferCurrency(CURRENCY_BIN, CURRENCY_SINK, amount)
|
||||
if not success then
|
||||
g.appendAndDrawConsole(term, console, "Transfer failed! Actual transfer: "..tostring(actualAmount).." $HMK. Please contact an administrator to report the issue.", cx, cy)
|
||||
os.sleep(1)
|
||||
tryReturnFundsInError(actualAmount)
|
||||
return false
|
||||
end
|
||||
g.appendAndDrawConsole(term, console, "Transfer complete.", cx, cy)
|
||||
os.sleep(1)
|
||||
local tx, errorMsg = bankClient.recordTransaction(account.id, amount, "ATM deposit")
|
||||
if not tx then
|
||||
g.appendAndDrawConsole(term, console, "Failed to post transaction: " .. errorMsg, cx, cy)
|
||||
tryReturnFundsInError(amount)
|
||||
os.sleep(3)
|
||||
return false
|
||||
end
|
||||
g.appendAndDrawConsole(term, console, "Transaction posted to account.", cx, cy)
|
||||
os.sleep(2)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local function showWithdrawUI(account)
|
||||
drawFrame()
|
||||
g.drawTextCenter(term, W/2, 3, "Withdraw HandieMarks from your account "..shortId(account)..".", colors.black, colors.white)
|
||||
g.drawTextCenter(term, W/2, 5, "Enter an amount to withdraw:", colors.black, colors.white)
|
||||
g.drawXLine(term, 20, 30, 6, colors.gray)
|
||||
g.drawTextCenter(term, W/2, 7, "(Current balance: "..tostring(account.balance).." $HMK)", colors.gray, colors.white)
|
||||
local continueButtonCoords = g.drawButton(term, 20, 12, 11, 3, "Continue", colors.white, colors.green)
|
||||
local cancelButtonCoords = g.drawButton(term, 20, 16, 11, 3, "Cancel", colors.white, colors.red)
|
||||
|
||||
local inputValue = ""
|
||||
local function drawInputValue(val)
|
||||
g.drawXLine(term, 20, 30, 6, colors.gray)
|
||||
local amountColor = colors.orange
|
||||
local intValue = tonumber(val)
|
||||
if intValue ~= nil and intValue > account.balance then
|
||||
amountColor = colors.red
|
||||
end
|
||||
g.drawText(term, 20, 6, val, colors.orange, colors.gray)
|
||||
end
|
||||
while true do
|
||||
local event, p1, p2, p3 = os.pullEvent()
|
||||
if event == "char" and isDigit(p1) and #inputValue < 10 then
|
||||
inputValue = inputValue .. p1
|
||||
drawInputValue(inputValue)
|
||||
elseif event == "key" and p1 == keys.backspace and #inputValue > 0 then
|
||||
inputValue = string.sub(inputValue, 1, #inputValue - 1)
|
||||
drawInputValue(inputValue)
|
||||
elseif event == "mouse_click" and p1 == 1 then
|
||||
local x = p2
|
||||
local y = p3
|
||||
local amount = tonumber(inputValue)
|
||||
if g.isButtonPressed(x, y, continueButtonCoords) and amount ~= nil and amount > 0 and amount <= account.balance then
|
||||
local function tryReclaimFundsInError(amount)
|
||||
local returnSuccess, returnAmount = transferCurrency(CURRENCY_BIN, CURRENCY_SINK, amount)
|
||||
if not returnSuccess then
|
||||
g.appendAndDrawConsole(term, console, "Failed to reclaim funds. Please contact an administrator.", cx, cy)
|
||||
end
|
||||
end
|
||||
-- Do withdrawal
|
||||
g.fillRect(term, 1, W, 12, H-11, colors.white)
|
||||
local console = g.createConsole(W/2, H-11, colors.white, colors.black, "UP")
|
||||
local cx = W/2 - W/4
|
||||
local cy = 11
|
||||
g.appendAndDrawConsole(term, console, "Making withdrawal of " .. tostring(amount) .. " $HMK from account " .. shortId(account) .. ".", cx, cy)
|
||||
os.sleep(1)
|
||||
local withdrawn = 0
|
||||
while withdrawn < amount do
|
||||
local freeSpace = getFreeSpace(CURRENCY_BIN)
|
||||
if freeSpace < 1 then
|
||||
g.appendAndDrawConsole(term, console, "No space available in the bin. Please take some currency out to continue.", cx, cy)
|
||||
while getFreeSpace(CURRENCY_BIN) < 1 do
|
||||
g.appendAndDrawConsole(term, console, "Waiting for free space...", cx, cy)
|
||||
os.sleep(3)
|
||||
end
|
||||
end
|
||||
local amountToTransfer = math.min(freeSpace, amount - withdrawn)
|
||||
local success, actualTransfer = transferCurrency(CURRENCY_SOURCE, CURRENCY_BIN, amountToTransfer)
|
||||
withdrawn = withdrawn + actualTransfer
|
||||
if not success then
|
||||
-- Failure! Send the money back, if we can.
|
||||
g.appendAndDrawConsole(term, console, "Transfer failed! Please contact an administrator to report the issue.", cx, cy)
|
||||
os.sleep(3)
|
||||
tryReclaimFundsInError(withdrawn)
|
||||
return false
|
||||
else
|
||||
g.appendAndDrawConsole(term, console, "Transferred " .. tostring(actualTransfer) .. " $HMK.", cx, cy)
|
||||
os.sleep(1)
|
||||
end
|
||||
end
|
||||
local tx, errorMsg = bankClient.recordTransaction(account.id, amount * -1, "ATM withdrawal")
|
||||
if not tx then
|
||||
g.appendAndDrawConsole(term, console, "Failed to post transaction: " .. errorMsg, cx, cy)
|
||||
tryReclaimFundsInError(amount)
|
||||
os.sleep(3)
|
||||
return false
|
||||
end
|
||||
g.appendAndDrawConsole(term, console, "Transaction posted to account.", cx, cy)
|
||||
os.sleep(2)
|
||||
return true
|
||||
elseif g.isButtonPressed(x, y, cancelButtonCoords) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function showAccountUI(account)
|
||||
while true do
|
||||
drawFrame()
|
||||
|
@ -165,18 +413,37 @@ local function showAccountUI(account)
|
|||
g.drawText(term, 2, 7, "Name", colors.gray, colors.white)
|
||||
g.drawText(term, 2, 8, account.name, colors.black, colors.white)
|
||||
g.drawText(term, 2, 10, "Balance ($HMK)", colors.gray, colors.white)
|
||||
g.drawText(term, 2, 11, tostring(account.balance), colors.yellow, colors.white)
|
||||
g.drawText(term, 2, 11, tostring(account.balance), colors.orange, colors.white)
|
||||
|
||||
local buttons = {}
|
||||
buttons.deposit = g.drawButton(term, 35, 4, 17, 3, "Deposit", colors.white, colors.green)
|
||||
if account.balance > 0 then
|
||||
buttons.withdraw = g.drawButton(term, 35, 8, 17, 3, "Withdraw", colors.white, colors.purple)
|
||||
buttons.transfer = g.drawButton(term, 35, 12, 17, 3, "Transfer", colors.white, colors.orange)
|
||||
else
|
||||
buttons.close = g.drawButton(term, 35, 16, 17, 3, "Close Account", colors.white, colors.red)
|
||||
end
|
||||
local event, button, x, y = os.pullEvent("mouse_click")
|
||||
if button == 1 then
|
||||
if y == 2 and x >= W-3 then
|
||||
return -- exit back to the accounts UI
|
||||
elseif g.isButtonPressed(x, y, buttons.deposit) then
|
||||
local success = showDepositUI(account)
|
||||
if success then return end -- If successful, go back to the accounts page.
|
||||
elseif buttons.withdraw and g.isButtonPressed(x, y, buttons.withdraw) then
|
||||
local success = showWithdrawUI(account)
|
||||
if success then return end
|
||||
elseif buttons.transfer and g.isButtonPressed(x, y, buttons.transfer) then
|
||||
-- Do Transfer
|
||||
return
|
||||
elseif buttons.close and g.isButtonPressed(x, y, buttons.close) then
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function showAccountsUI()
|
||||
local accounts, errorMsg = bankClient.getAccounts()
|
||||
while true do
|
||||
drawFrame()
|
||||
g.drawXLine(term, 1, 19, 2, colors.gray)
|
||||
|
@ -184,30 +451,60 @@ local function showAccountsUI()
|
|||
g.drawXLine(term, 10, 35, 2, colors.lightGray)
|
||||
g.drawText(term, 11, 2, "Name", colors.white, colors.lightGray)
|
||||
g.drawXLine(term, 36, W, 2, colors.gray)
|
||||
g.drawText(term, 37, 2, "Balance ($HMK)", colors.white, colors.gray)
|
||||
g.drawText(term, 37, 2, "Balance", colors.white, colors.gray)
|
||||
g.drawText(term, W-6, 2, "Log Out", colors.white, colors.red)
|
||||
local accounts, errorMsg = bankClient.getAccounts()
|
||||
if accounts then
|
||||
for i, account in pairs(accounts) do
|
||||
local bg = colors.blue
|
||||
if i % 2 == 0 then bg = colors.lightBlue end
|
||||
local fg = colors.white
|
||||
local y = i + 2
|
||||
g.drawXLine(term, 1, W, y, bg)
|
||||
g.drawText(term, 2, y, "*" .. string.sub(account.id, -5, -1), fg, bg)
|
||||
g.drawText(term, 2, y, shortId(account), fg, bg)
|
||||
g.drawText(term, 11, y, account.name, fg, bg)
|
||||
g.drawText(term, 37, y, tostring(account.balance), fg, bg)
|
||||
end
|
||||
else
|
||||
g.drawTextCenter(term, W/2, 4, "Error: " .. errorMsg, colors.red, colors.white)
|
||||
end
|
||||
local event, button, x, y = os.pullEvent("mouse_click")
|
||||
if button == 1 and y > 2 and (y - 2) <= #accounts then
|
||||
local account = accounts[y-2]
|
||||
showAccountUI(account)
|
||||
if button == 1 then
|
||||
if accounts and y > 2 and (y - 2) <= #accounts then
|
||||
showAccountUI(accounts[y-2])
|
||||
elseif y == 2 and x >= W-6 then
|
||||
bankClient.logOut()
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function logoutAfterInactivity()
|
||||
local function now() return os.epoch("utc") end
|
||||
local DELAY = 30000
|
||||
local lastActivity = now()
|
||||
while now() < lastActivity + DELAY do
|
||||
parallel.waitForAny(
|
||||
function () os.sleep(1) end,
|
||||
function ()
|
||||
local event = os.pullEvent()
|
||||
if event == "mouse_click" or event == "key" or event == "key_up" or event == "char" then
|
||||
lastActivity = now()
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
bankClient.logOut()
|
||||
drawFrame()
|
||||
g.drawText(term, 2, 3, "Logged out due to inactivity.", colors.gray, colors.white)
|
||||
os.sleep(2)
|
||||
end
|
||||
|
||||
while true do
|
||||
local credentials = showLoginUI()
|
||||
local loginSuccess = checkCredentialsUI(credentials)
|
||||
if loginSuccess then
|
||||
showAccountsUI()
|
||||
parallel.waitForAny(showAccountsUI, logoutAfterInactivity)
|
||||
end
|
||||
return
|
||||
end
|
|
@ -96,6 +96,14 @@ function client.getAccounts()
|
|||
return response.data
|
||||
end
|
||||
|
||||
function client.getAccount(accountId)
|
||||
local response = requestAuth("GET_ACCOUNT", {accountId = accountId})
|
||||
if not response.success then
|
||||
return nil, response.error
|
||||
end
|
||||
return response.data
|
||||
end
|
||||
|
||||
function client.createAccount(accountName)
|
||||
local response = requestAuth("CREATE_ACCOUNT", {name = accountName})
|
||||
if not response.success then
|
||||
|
|
13
bank.lua
13
bank.lua
|
@ -309,6 +309,18 @@ local function handleGetUserAccounts(msg)
|
|||
return {success = true, data = getAccounts(msg.auth.username)}
|
||||
end
|
||||
|
||||
local function handleGetUserAccount(msg)
|
||||
if not msg.data or not msg.data.accountId then
|
||||
return {success = false, error = "Invalid request. Requires data.accountId."}
|
||||
end
|
||||
local accounts = getAccounts(msg.auth.username)
|
||||
local account = findAccountById(accounts, msg.data.accountId)
|
||||
if not account then
|
||||
return {success = false, error = "Account doesn't exist."}
|
||||
end
|
||||
return {success = true, data = account}
|
||||
end
|
||||
|
||||
local function handleCreateUserAccount(msg)
|
||||
if not msg.data or not msg.data.name then
|
||||
return {success = false, error = "Invalid request. Requires data.name."}
|
||||
|
@ -354,6 +366,7 @@ local BANK_REQUESTS = {
|
|||
["DELETE_USER"] = authProtect(handleDeleteUser),
|
||||
["RENAME_USER"] = authProtect(handleRenameUser),
|
||||
["GET_ACCOUNTS"] = authProtect(handleGetUserAccounts),
|
||||
["GET_ACCOUNT"] = authProtect(handleGetUserAccount),
|
||||
["CREATE_ACCOUNT"] = authProtect(handleCreateUserAccount),
|
||||
["DELETE_ACCOUNT"] = authProtect(handleDeleteUserAccount),
|
||||
["RENAME_ACCOUNT"] = authProtect(handleRenameUserAccount),
|
||||
|
|
Loading…
Reference in New Issue