add toast and app transition animations
Toast: - Bigger size with bold text (18dp padding, 16dp font) - Pop-up animation from bottom with bounce effect - Fade-out animation when dismissing - Cancels previous toast when showing new one App transitions: - Opening: fade in + scale from 0.8 to 1.0 with back-out easing - Closing: fade out + scale from 1.0 to 0.8 - Skip animation on initial shell load - Async close animation before loading previous app
This commit is contained in:
@@ -30,8 +30,8 @@ function initShell(doc)
|
||||
-- Update time display
|
||||
updateTime()
|
||||
|
||||
-- Load home screen by default
|
||||
loadAppContent_internal("home", "apps/home/home_content.rml")
|
||||
-- Load home screen by default (skip animation on initial load)
|
||||
loadAppContent_internal("home", "apps/home/home_content.rml", true)
|
||||
|
||||
print("[Shell] Shell initialized")
|
||||
end
|
||||
@@ -46,8 +46,8 @@ end
|
||||
|
||||
-- ===== APP LOADING =====
|
||||
|
||||
-- Internal function to load app content
|
||||
function loadAppContent_internal(app_id, app_path)
|
||||
-- Internal function to load app content with optional animation
|
||||
function loadAppContent_internal(app_id, app_path, skip_animation)
|
||||
if not app_container then
|
||||
print("[Shell] ERROR: No app container")
|
||||
return false
|
||||
@@ -68,6 +68,18 @@ function loadAppContent_internal(app_id, app_path)
|
||||
current_app_path = app_path
|
||||
print("[Shell] App loaded: " .. app_id)
|
||||
|
||||
-- Play opening animation (unless skipped for initial load)
|
||||
if not skip_animation then
|
||||
app_container:SetClass("app-opening", true)
|
||||
app_container:SetClass("app-closing", false)
|
||||
-- Remove animation class after it completes
|
||||
if setTimeout then
|
||||
setTimeout(function()
|
||||
app_container:SetClass("app-opening", false)
|
||||
end, 300)
|
||||
end
|
||||
end
|
||||
|
||||
-- If home was loaded, populate apps dynamically
|
||||
if app_id == "home" then
|
||||
populateHomeApps()
|
||||
@@ -126,6 +138,21 @@ end
|
||||
|
||||
-- ===== NAVIGATION =====
|
||||
|
||||
-- Play closing animation then execute callback
|
||||
local function playCloseAnimation(callback)
|
||||
if app_container and setTimeout then
|
||||
app_container:SetClass("app-closing", true)
|
||||
app_container:SetClass("app-opening", false)
|
||||
setTimeout(function()
|
||||
app_container:SetClass("app-closing", false)
|
||||
if callback then callback() end
|
||||
end, 200)
|
||||
else
|
||||
-- No animation support, execute immediately
|
||||
if callback then callback() end
|
||||
end
|
||||
end
|
||||
|
||||
-- Go back to previous app
|
||||
function shellGoBack()
|
||||
print("[Shell] goBack called (history depth: " .. #nav_history .. ")")
|
||||
@@ -139,17 +166,20 @@ function shellGoBack()
|
||||
local previous = table.remove(nav_history)
|
||||
print("[Shell] Going back to: " .. previous.app_id)
|
||||
|
||||
-- Don't push to history when going back
|
||||
local temp_app = current_app
|
||||
current_app = nil
|
||||
current_app_path = nil
|
||||
-- Play closing animation, then load previous app
|
||||
playCloseAnimation(function()
|
||||
-- Don't push to history when going back
|
||||
local temp_app = current_app
|
||||
current_app = nil
|
||||
current_app_path = nil
|
||||
|
||||
local success = loadAppContent_internal(previous.app_id, previous.app_path)
|
||||
if not success then
|
||||
-- Restore state on failure
|
||||
current_app = temp_app
|
||||
end
|
||||
return success
|
||||
local success = loadAppContent_internal(previous.app_id, previous.app_path)
|
||||
if not success then
|
||||
-- Restore state on failure
|
||||
current_app = temp_app
|
||||
end
|
||||
end)
|
||||
return true
|
||||
else
|
||||
print("[Shell] No history - already at root")
|
||||
if current_app ~= "home" then
|
||||
@@ -171,10 +201,12 @@ function shellGoHome()
|
||||
-- Clear history
|
||||
nav_history = {}
|
||||
|
||||
-- Load home
|
||||
current_app = nil
|
||||
current_app_path = nil
|
||||
loadAppContent_internal("home", "apps/home/home_content.rml")
|
||||
-- Play closing animation, then load home
|
||||
playCloseAnimation(function()
|
||||
current_app = nil
|
||||
current_app_path = nil
|
||||
loadAppContent_internal("home", "apps/home/home_content.rml")
|
||||
end)
|
||||
end
|
||||
|
||||
-- Show recents (placeholder)
|
||||
@@ -209,6 +241,8 @@ end
|
||||
|
||||
-- ===== TOASTS =====
|
||||
|
||||
local active_toast_id = nil
|
||||
|
||||
function showToast(message, type)
|
||||
type = type or "default"
|
||||
|
||||
@@ -218,24 +252,39 @@ function showToast(message, type)
|
||||
return
|
||||
end
|
||||
|
||||
local class = "toast"
|
||||
if type == "success" then
|
||||
class = "toast toast-success"
|
||||
elseif type == "error" then
|
||||
class = "toast toast-error"
|
||||
elseif type == "warning" then
|
||||
class = "toast toast-warning"
|
||||
-- Cancel any pending hide timer
|
||||
if active_toast_id and clearTimeout then
|
||||
clearTimeout(active_toast_id)
|
||||
active_toast_id = nil
|
||||
end
|
||||
|
||||
-- Clear existing toasts and add new one
|
||||
local toast_html = '<div class="' .. class .. '"><span>' .. message .. '</span></div>'
|
||||
local type_class = ""
|
||||
if type == "success" then
|
||||
type_class = " toast-success"
|
||||
elseif type == "error" then
|
||||
type_class = " toast-error"
|
||||
elseif type == "warning" then
|
||||
type_class = " toast-warning"
|
||||
end
|
||||
|
||||
-- Create toast with visible animation class
|
||||
local toast_html = '<div id="active-toast" class="toast toast-visible' .. type_class .. '"><span>' .. message .. '</span></div>'
|
||||
container.inner_rml = toast_html
|
||||
|
||||
-- Auto-remove after 3 seconds
|
||||
-- Auto-hide after 2.5 seconds (start hide animation, then remove)
|
||||
if setTimeout then
|
||||
setTimeout(function()
|
||||
container.inner_rml = ""
|
||||
end, 3000)
|
||||
active_toast_id = setTimeout(function()
|
||||
local toast = shell_document:GetElementById("active-toast")
|
||||
if toast then
|
||||
-- Switch to hiding animation
|
||||
toast:SetAttribute("class", "toast toast-hiding" .. type_class)
|
||||
-- Remove after animation completes
|
||||
setTimeout(function()
|
||||
container.inner_rml = ""
|
||||
active_toast_id = nil
|
||||
end, 300)
|
||||
end
|
||||
end, 2500)
|
||||
end
|
||||
|
||||
print("[Shell] Toast: " .. message .. " (" .. type .. ")")
|
||||
|
||||
@@ -51,6 +51,37 @@
|
||||
background-color: #121212;
|
||||
}
|
||||
|
||||
/* App launch/close animations */
|
||||
.app-opening {
|
||||
animation: 0.25s back-out app-open;
|
||||
}
|
||||
|
||||
.app-closing {
|
||||
animation: 0.2s quadratic-in app-close;
|
||||
}
|
||||
|
||||
@keyframes app-open {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes app-close {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scale(1.0);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
/* System navigation bar at bottom - always visible */
|
||||
.shell-nav-bar {
|
||||
height: 56px;
|
||||
@@ -97,28 +128,37 @@
|
||||
|
||||
/* ===== OVERLAY LAYERS ===== */
|
||||
|
||||
/* Toast container - bottom of screen, above nav bar */
|
||||
/* Toast container - top of screen, below status bar */
|
||||
#toast-container {
|
||||
position: absolute;
|
||||
bottom: 70px;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
bottom: 70dp;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 500;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toast {
|
||||
display: inline-block;
|
||||
background-color: #323232;
|
||||
color: #FFFFFF;
|
||||
padding: 14dp 24dp;
|
||||
border-radius: 8dp;
|
||||
font-size: 14dp;
|
||||
padding: 18dp 28dp;
|
||||
margin: 0 16dp;
|
||||
border-radius: 12dp;
|
||||
font-size: 16dp;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
pointer-events: auto;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.toast-visible {
|
||||
animation: 0.4s back-out toast-pop-in;
|
||||
}
|
||||
|
||||
.toast-hiding {
|
||||
animation: 0.2s quadratic-in toast-pop-out;
|
||||
}
|
||||
|
||||
.toast-success {
|
||||
@@ -136,9 +176,15 @@
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
@keyframes toast-in {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
@keyframes toast-pop-in {
|
||||
0% { opacity: 0; transform: translateY(50dp) scale(0.8); }
|
||||
70% { transform: translateY(-5dp) scale(1.02); }
|
||||
100% { opacity: 1; transform: translateY(0) scale(1.0); }
|
||||
}
|
||||
|
||||
@keyframes toast-pop-out {
|
||||
from { opacity: 1; transform: translateY(0) scale(1.0); }
|
||||
to { opacity: 0; transform: translateY(30dp) scale(0.9); }
|
||||
}
|
||||
|
||||
/* Dialog/Modal overlay */
|
||||
|
||||
Reference in New Issue
Block a user