add base-apps with manifests, layout system, and testing documentation
- Rename test-apps to base-apps with proper manifest.json for each app - Add is_system_app flag to app discovery and Lua API - Fix icon path resolution for /system/icons/ paths - Add layout.lua and layout.rcss for reusable UI components - Update home screen to dynamically load all apps from manifests - Update all app RML files to use layout components - Comprehensive testing framework documentation with JSON action format - Add tests/ directory structure for automated UI testing
This commit is contained in:
200
base-apps/com.mosis.home/home.lua
Normal file
200
base-apps/com.mosis.home/home.lua
Normal file
@@ -0,0 +1,200 @@
|
||||
-- home.lua - Home screen dynamic app rendering
|
||||
-- Handles system apps and discovered third-party apps
|
||||
|
||||
-- System apps with their navigation keys and colors
|
||||
local system_apps = {
|
||||
-- Row 1
|
||||
{name = "Phone", icon = "phone", color = "#4CAF50", nav = "dialer"},
|
||||
{name = "Messages", icon = "message", color = "#2196F3", nav = "messages"},
|
||||
{name = "Contacts", icon = "contacts", color = "#FF9800", nav = "contacts"},
|
||||
{name = "Browser", icon = "browser", color = "#F44336", nav = "browser"},
|
||||
-- Row 2
|
||||
{name = "Gallery", icon = "gallery", color = "#9C27B0", nav = nil},
|
||||
{name = "Camera", icon = "camera", color = "#00BCD4", nav = "camera"},
|
||||
{name = "Settings", icon = "settings", color = "#607D8B", nav = "settings"},
|
||||
{name = "Music", icon = "music", color = "#E91E63", nav = "music"},
|
||||
-- Row 3
|
||||
{name = "Calendar", icon = "calendar", color = "#3F51B5", nav = nil},
|
||||
{name = "Clock", icon = "clock", color = "#009688", nav = nil},
|
||||
{name = "Notes", icon = "notes", color = "#795548", nav = nil},
|
||||
{name = "Maps", icon = "maps", color = "#FF5722", nav = nil},
|
||||
-- Row 4
|
||||
{name = "Store", icon = "store", color = "#8BC34A", nav = "store"},
|
||||
{name = "Files", icon = "files", color = "#CDDC39", nav = nil},
|
||||
{name = "Calculator", icon = "calculator", color = "#FFC107", nav = nil},
|
||||
{name = "Weather", icon = "weather", color = "#673AB7", nav = nil},
|
||||
}
|
||||
|
||||
-- State
|
||||
local installed_apps = {}
|
||||
local home_document = nil -- Store document reference
|
||||
|
||||
-- Initialize on load (receives document from onload event)
|
||||
function initHome(doc)
|
||||
print("[Home] Initializing home screen...")
|
||||
home_document = doc
|
||||
|
||||
-- Get installed third-party apps
|
||||
if mosis and mosis.apps then
|
||||
installed_apps = mosis.apps.getInstalled() or {}
|
||||
print("[Home] Found " .. #installed_apps .. " installed apps")
|
||||
|
||||
-- Filter to only third-party (non-system) apps
|
||||
local third_party = {}
|
||||
for _, app in ipairs(installed_apps) do
|
||||
if not app.is_system_app then
|
||||
table.insert(third_party, app)
|
||||
print("[Home] Third-party app: " .. app.name .. " (" .. app.package_id .. ")")
|
||||
end
|
||||
end
|
||||
installed_apps = third_party
|
||||
else
|
||||
print("[Home] Warning: mosis.apps API not available")
|
||||
installed_apps = {}
|
||||
end
|
||||
|
||||
-- Render dynamic apps
|
||||
renderThirdPartyApps()
|
||||
end
|
||||
|
||||
-- Generate a color based on package_id
|
||||
function getAppColor(package_id)
|
||||
local colors = {
|
||||
"#BB86FC", "#03DAC6", "#FF9800", "#2196F3",
|
||||
"#4CAF50", "#F44336", "#E91E63", "#3F51B5",
|
||||
"#009688", "#795548", "#FF5722", "#673AB7"
|
||||
}
|
||||
|
||||
-- Simple hash of package_id to pick a color
|
||||
local hash = 0
|
||||
for i = 1, #package_id do
|
||||
hash = hash + package_id:byte(i)
|
||||
end
|
||||
|
||||
return colors[(hash % #colors) + 1]
|
||||
end
|
||||
|
||||
-- Get first letter for placeholder icon
|
||||
function getAppInitial(name)
|
||||
return name:sub(1, 1):upper()
|
||||
end
|
||||
|
||||
-- Render third-party apps into the grid
|
||||
function renderThirdPartyApps()
|
||||
-- Use stored document reference
|
||||
if not home_document then
|
||||
print("[Home] Could not get document reference")
|
||||
return
|
||||
end
|
||||
|
||||
local grid = home_document:GetElementById("third-party-apps")
|
||||
if not grid then
|
||||
print("[Home] third-party-apps container not found")
|
||||
return
|
||||
end
|
||||
|
||||
-- Clear existing content
|
||||
grid.inner_rml = ""
|
||||
|
||||
if #installed_apps == 0 then
|
||||
print("[Home] No third-party apps to display")
|
||||
return
|
||||
end
|
||||
|
||||
-- Build HTML for each app
|
||||
local html = ""
|
||||
for _, app in ipairs(installed_apps) do
|
||||
local color = getAppColor(app.package_id)
|
||||
local initial = getAppInitial(app.name)
|
||||
local icon_html
|
||||
|
||||
-- Check if app has an icon
|
||||
if app.icon and app.icon ~= "" then
|
||||
local icon_path
|
||||
-- Check if icon is already a full path (starts with / or contains :/)
|
||||
if app.icon:sub(1, 1) == "/" or app.icon:find(":/") then
|
||||
-- Already a full path
|
||||
icon_path = app.icon
|
||||
elseif app.install_path and app.install_path ~= "" then
|
||||
-- Relative filename - construct full path from install_path
|
||||
icon_path = app.install_path .. "/" .. app.icon
|
||||
else
|
||||
icon_path = app.icon
|
||||
end
|
||||
-- Use file:// prefix for absolute paths to prevent RmlUi URL resolution
|
||||
local src_path = icon_path
|
||||
if icon_path:sub(1, 1) == "/" then
|
||||
src_path = "file://" .. icon_path
|
||||
end
|
||||
-- Use img tag for actual icon
|
||||
icon_html = '<img src="' .. src_path .. '" style="width: 48px; height: 48px;"/>'
|
||||
print("[Home] Loading icon: " .. src_path)
|
||||
else
|
||||
-- Fallback to initial letter
|
||||
icon_html = '<span style="font-size: 28px; color: #000000;">' .. initial .. '</span>'
|
||||
end
|
||||
|
||||
html = html .. [[
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: ]] .. color .. [[;"
|
||||
onclick="launchThirdPartyApp(']] .. app.package_id .. [[')">
|
||||
]] .. icon_html .. [[
|
||||
</div>
|
||||
<span class="app-icon-label">]] .. app.name .. [[</span>
|
||||
</div>
|
||||
]]
|
||||
end
|
||||
|
||||
grid.inner_rml = html
|
||||
print("[Home] Rendered " .. #installed_apps .. " third-party apps")
|
||||
end
|
||||
|
||||
-- Get app info by package_id
|
||||
function getAppInfo(package_id)
|
||||
for _, app in ipairs(installed_apps) do
|
||||
if app.package_id == package_id then
|
||||
return app
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Launch a third-party app
|
||||
function launchThirdPartyApp(package_id)
|
||||
print("[Home] Launching app: " .. package_id)
|
||||
|
||||
if mosis and mosis.apps then
|
||||
local success = mosis.apps.launch(package_id)
|
||||
if success then
|
||||
print("[Home] App sandbox started: " .. package_id)
|
||||
|
||||
-- Get app info for sandbox switching and UI loading
|
||||
local app_info = getAppInfo(package_id)
|
||||
if app_info and app_info.install_path and app_info.entry_point then
|
||||
-- Switch sandbox context to this app (registers timer, fs, json, crypto APIs)
|
||||
if switchAppSandbox then
|
||||
switchAppSandbox(package_id, app_info.install_path)
|
||||
print("[Home] Sandbox context switched to: " .. package_id)
|
||||
end
|
||||
|
||||
-- Now load the app's UI document
|
||||
local entry_path = app_info.install_path .. "/" .. app_info.entry_point
|
||||
print("[Home] Loading app screen: " .. entry_path)
|
||||
local loaded = loadScreen(entry_path)
|
||||
if loaded then
|
||||
print("[Home] App UI loaded: " .. package_id)
|
||||
else
|
||||
print("[Home] Failed to load app UI: " .. entry_path)
|
||||
end
|
||||
else
|
||||
print("[Home] App info missing entry point: " .. package_id)
|
||||
end
|
||||
else
|
||||
print("[Home] Failed to launch app: " .. package_id)
|
||||
end
|
||||
else
|
||||
print("[Home] Cannot launch app: mosis.apps not available")
|
||||
end
|
||||
end
|
||||
|
||||
-- initHome() is called via onload in home.rml
|
||||
176
base-apps/com.mosis.home/home.rml
Normal file
176
base-apps/com.mosis.home/home.rml
Normal file
@@ -0,0 +1,176 @@
|
||||
<rml>
|
||||
<head>
|
||||
<link type="text/rcss" href="../../ui/html.rcss"/>
|
||||
<link type="text/rcss" href="../../ui/theme.rcss"/>
|
||||
<link type="text/rcss" href="../../ui/components.rcss"/>
|
||||
<script src="../../scripts/navigation.lua"></script>
|
||||
<script src="home.lua"></script>
|
||||
<title>Virtual Smartphone - Home</title>
|
||||
<style>
|
||||
.home-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #121212;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.home-content {
|
||||
flex: 1;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
.app-icon-image img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dock-item img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.status-bar-icons img {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Third-party apps section */
|
||||
.app-grid-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Third-party apps use same sizing as system apps */
|
||||
#third-party-apps .app-icon {
|
||||
width: 25%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#third-party-apps .app-icon-image {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 12px auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#third-party-apps .app-icon-image:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
#third-party-apps .app-icon-label {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="home-screen" onload="initHome(document)">
|
||||
<!-- Status Bar -->
|
||||
<div class="status-bar">
|
||||
<span class="status-bar-time">12:30</span>
|
||||
<div class="status-bar-icons">
|
||||
<img src="../../icons/wifi.tga" style="width: 24px; height: 24px;"/>
|
||||
<img src="../../icons/signal.tga" style="width: 24px; height: 24px;"/>
|
||||
<img src="../../icons/battery.tga" style="width: 24px; height: 24px;"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- App Grid -->
|
||||
<div class="home-content">
|
||||
<div class="app-grid">
|
||||
<!-- Row 1 -->
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #4CAF50;" onclick="navigateTo('dialer')"><img src="../../icons/phone.tga"/></div>
|
||||
<span class="app-icon-label">Phone</span>
|
||||
</div>
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #2196F3;" onclick="navigateTo('messages')"><img src="../../icons/message.tga"/></div>
|
||||
<span class="app-icon-label">Messages</span>
|
||||
</div>
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #FF9800;" onclick="navigateTo('contacts')"><img src="../../icons/contacts.tga"/></div>
|
||||
<span class="app-icon-label">Contacts</span>
|
||||
</div>
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #F44336;" onclick="navigateTo('browser')"><img src="../../icons/browser.tga"/></div>
|
||||
<span class="app-icon-label">Browser</span>
|
||||
</div>
|
||||
|
||||
<!-- Row 2 -->
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #9C27B0;"><img src="../../icons/gallery.tga"/></div>
|
||||
<span class="app-icon-label">Gallery</span>
|
||||
</div>
|
||||
<div id="app-camera" class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #00BCD4;" onclick="navigateTo('camera')"><img src="../../icons/camera.tga"/></div>
|
||||
<span class="app-icon-label">Camera</span>
|
||||
</div>
|
||||
<div id="app-settings" class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #607D8B;" onclick="navigateTo('settings')"><img src="../../icons/settings.tga"/></div>
|
||||
<span class="app-icon-label">Settings</span>
|
||||
</div>
|
||||
<div id="app-music" class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #E91E63;" onclick="navigateTo('music')"><img src="../../icons/music.tga"/></div>
|
||||
<span class="app-icon-label">Music</span>
|
||||
</div>
|
||||
|
||||
<!-- Row 3 -->
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #3F51B5;"><img src="../../icons/calendar.tga"/></div>
|
||||
<span class="app-icon-label">Calendar</span>
|
||||
</div>
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #009688;"><img src="../../icons/clock.tga"/></div>
|
||||
<span class="app-icon-label">Clock</span>
|
||||
</div>
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #795548;"><img src="../../icons/notes.tga"/></div>
|
||||
<span class="app-icon-label">Notes</span>
|
||||
</div>
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #FF5722;"><img src="../../icons/maps.tga"/></div>
|
||||
<span class="app-icon-label">Maps</span>
|
||||
</div>
|
||||
|
||||
<!-- Row 4 -->
|
||||
<div id="app-store" class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #8BC34A;" onclick="navigateTo('store')"><img src="../../icons/store.tga"/></div>
|
||||
<span class="app-icon-label">Store</span>
|
||||
</div>
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #CDDC39;"><img src="../../icons/files.tga"/></div>
|
||||
<span class="app-icon-label">Files</span>
|
||||
</div>
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #FFC107;"><img src="../../icons/calculator.tga"/></div>
|
||||
<span class="app-icon-label">Calculator</span>
|
||||
</div>
|
||||
<div class="app-icon">
|
||||
<div class="app-icon-image" style="background-color: #673AB7;"><img src="../../icons/weather.tga"/></div>
|
||||
<span class="app-icon-label">Weather</span>
|
||||
</div>
|
||||
|
||||
<!-- Third-party apps (dynamically populated by home.lua) -->
|
||||
<div id="third-party-apps" class="app-grid-section">
|
||||
<!-- Apps will be rendered here by home.lua -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dock -->
|
||||
<div class="dock">
|
||||
<div id="dock-phone" class="dock-item" style="background-color: #4CAF50;" onclick="navigateTo('dialer')"><img src="../../icons/phone.tga"/></div>
|
||||
<div id="dock-messages" class="dock-item" style="background-color: #2196F3;" onclick="navigateTo('messages')"><img src="../../icons/message.tga"/></div>
|
||||
<div id="dock-contacts" class="dock-item" style="background-color: #FF9800;" onclick="navigateTo('contacts')"><img src="../../icons/contacts.tga"/></div>
|
||||
<div id="dock-browser" class="dock-item" style="background-color: #F44336;" onclick="navigateTo('browser')"><img src="../../icons/browser.tga"/></div>
|
||||
</div>
|
||||
</body>
|
||||
</rml>
|
||||
176
base-apps/com.mosis.home/lock.rml
Normal file
176
base-apps/com.mosis.home/lock.rml
Normal file
@@ -0,0 +1,176 @@
|
||||
<rml>
|
||||
<head>
|
||||
<link type="text/rcss" href="../../ui/html.rcss"/>
|
||||
<link type="text/rcss" href="../../ui/theme.rcss"/>
|
||||
<link type="text/rcss" href="../../ui/components.rcss"/>
|
||||
<script src="../../scripts/navigation.lua"></script>
|
||||
<title>Lock Screen</title>
|
||||
<style>
|
||||
.lock-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #121212;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.lock-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.lock-time {
|
||||
font-size: 72px;
|
||||
font-weight: 200;
|
||||
color: #FFFFFF;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.lock-date {
|
||||
font-size: 18px;
|
||||
color: #B3B3B3;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.lock-swipe {
|
||||
position: absolute;
|
||||
bottom: 100px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.lock-swipe-text {
|
||||
font-size: 18px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.lock-swipe-icon {
|
||||
font-size: 24px;
|
||||
color: #666666;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.lock-shortcuts {
|
||||
position: absolute;
|
||||
bottom: 32px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 32px;
|
||||
}
|
||||
|
||||
.lock-shortcut {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 28px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
color: #FFFFFF;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.lock-shortcut:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.lock-notifications {
|
||||
margin-top: 48px;
|
||||
width: 100%;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.lock-notification {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.lock-notification-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
background-color: #2196F3;
|
||||
margin-right: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.lock-notification-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.lock-notification-title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.lock-notification-text {
|
||||
font-size: 16px;
|
||||
color: #B3B3B3;
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="lock-screen" onclick="goHome()">
|
||||
<!-- Status Bar -->
|
||||
<div class="status-bar">
|
||||
<span class="status-bar-time">12:30</span>
|
||||
<div class="status-bar-icons">
|
||||
<span>*</span>
|
||||
<span>+</span>
|
||||
<span>|</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="lock-content">
|
||||
<div class="lock-time">12:30</div>
|
||||
<div class="lock-date">Wednesday, January 15</div>
|
||||
|
||||
<!-- Notifications -->
|
||||
<div class="lock-notifications">
|
||||
<div class="lock-notification">
|
||||
<div class="lock-notification-icon">M</div>
|
||||
<div class="lock-notification-content">
|
||||
<div class="lock-notification-title">Messages</div>
|
||||
<div class="lock-notification-text">John: Hey, are you coming to...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lock-notification">
|
||||
<div class="lock-notification-icon" style="background-color: #4CAF50;">P</div>
|
||||
<div class="lock-notification-content">
|
||||
<div class="lock-notification-title">Missed Call</div>
|
||||
<div class="lock-notification-text">Mom - 10 minutes ago</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Swipe to unlock -->
|
||||
<div class="lock-swipe">
|
||||
<div class="lock-swipe-icon">^</div>
|
||||
<div class="lock-swipe-text">Swipe up to unlock</div>
|
||||
</div>
|
||||
|
||||
<!-- Shortcuts -->
|
||||
<div class="lock-shortcuts">
|
||||
<div class="lock-shortcut" onclick="navigateTo('dialer')"><img src="../../icons/phone.tga" style="width: 32px; height: 32px;"/></div>
|
||||
<div class="lock-shortcut"><img src="../../icons/camera.tga" style="width: 32px; height: 32px;"/></div>
|
||||
</div>
|
||||
</body>
|
||||
</rml>
|
||||
16
base-apps/com.mosis.home/manifest.json
Normal file
16
base-apps/com.mosis.home/manifest.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "com.mosis.home",
|
||||
"name": "Home",
|
||||
"version": "1.0.0",
|
||||
"version_code": 1,
|
||||
"entry": "home.rml",
|
||||
"icon": "/system/icons/home.tga",
|
||||
"description": "Mosis home screen and app launcher",
|
||||
"developer": {
|
||||
"name": "Mosis Team",
|
||||
"email": "dev@mosis.dev"
|
||||
},
|
||||
"is_system_app": true,
|
||||
"permissions": [],
|
||||
"min_api_version": 1
|
||||
}
|
||||
Reference in New Issue
Block a user